Inquiry regarding the use of screen space coordinates for rendering the sun and background elements

My goal is to create a custom shader in three.js that renders a background and sun. The concept involves calculating a screen space position for the sun and using these coordinates in the fragment shader for rendering. The desired outcome is to have the sun always displayed at the horizon at (0,1000,-1000). Upon running the live example and looking up, it seems to be working as intended.

However, upon moving the camera around (so it's aligned along the (0,-1,1) vector), you'll notice that the sun suddenly appears mirrored and flipped along the XY plane. Why does this occur? Could this issue be related to how screen space coordinates are computed and evaluated within the shader?

The live example serves as a simplified test case of an existing GitHub issue.

var container;

var camera, cameraFX, scene, sceneFX, renderer;

var uniforms;

var sunPosition = new THREE.Vector3( 0, 1000, - 1000 );
var screenSpacePosition = new THREE.Vector3();

init();
animate();

function init() {

container = document.getElementById( 'container' );

camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 2000 );
camera.position.set( 0, 0, 10 );

cameraFX = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );

scene = new THREE.Scene();

scene.add( new THREE.AxesHelper( 5 ) );

sceneFX = new THREE.Scene();

var geometry = new THREE.PlaneBufferGeometry( 2, 2 );

uniforms = {
"aspect": { value: window.innerWidth / window.innerHeight },
"sunPositionScreenSpace": { value: new THREE.Vector2() }
};

var material = new THREE.ShaderMaterial( {

uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent

} );

var quad = new THREE.Mesh( geometry, material );
sceneFX.add( quad );

renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.autoClear = false;
container.appendChild( renderer.domElement );

var controls = new THREE.OrbitControls( camera, renderer.domElement );

}


//

function animate( timestamp ) {

requestAnimationFrame( animate );

renderer.clear();

// background/sun pass

screenSpacePosition.copy( sunPosition ).project( camera );

screenSpacePosition.x = ( screenSpacePosition.x + 1 ) / 2;
screenSpacePosition.y = ( screenSpacePosition.y + 1 ) / 2;

uniforms[ "sunPositionScreenSpace" ].value.copy( screenSpacePosition );

renderer.render( sceneFX, cameraFX );

// beauty pass

renderer.clearDepth();
renderer.render( scene, camera );

}
body {
  margin: 0;
}
canvas {
  display: block;
}
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8afee2f8efefcabaa4bbbbbca4bb">[email protected]</a>/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="9aeef2e8ffffdaaab4ababacb4ab">[email protected]</a>/examples/js/controls/OrbitControls.js"></script>

<div id="container">

</div>
<script id="vertexShader" type="x-shader/x-vertex">

varying vec2 vUv;

void main(){

vUv = uv;

gl_Position = vec4( position, 1.0 );

}

</script>

<script id="fragmentShader" type="x-shader/x-fragment">

varying vec2 vUv;

uniform vec2 sunPositionScreenSpace;
uniform float aspect;

const vec3 sunColor = vec3( 1.0, 0.0, 0.0 );
const vec3 bgColor = vec3( 1.0, 1.0, 1.0 );

void main() {

vec2 diff = vUv - sunPositionScreenSpace;
diff.x *= aspect;

// background/sun drawing

float prop = clamp( length( diff ) / 0.5, 0.0, 1.0 );
prop = 0.35 * pow( 1.0 - prop, 3.0 );

gl_FragColor.rgb = mix( sunColor, bgColor, 1.0 - prop );
gl_FragColor.a = 1.0;

}

</script>

Answer №1

One major reason for the issue is the sun's position behind the viewpoint. It is crucial to understand that in perspective projection, the viewing volume forms a Frustum. Each point gets projected along a ray from the camera position onto the viewport. If a point is positioned behind the viewpoint, it appears mirrored due to this projection along the ray.
Typically, this isn't a problem because any geometry located in front of the near plane gets clipped.

The clipspace coordinate involves Homogeneous coordinates. To address this, one must calculate the clipspace coordinate and verify if the z component is less than zero.
It's important to note that using Vector3.project won't work as expected since the function computes normalized device space coordinates. In NDC, distinguishing whether a position lies in front or behind the camera becomes challenging after the Perspective divide where the sign of the z component is lost. Clipping operations occur in clip space according to the rule:

-w <= x, y, z <= w.

To specify the sun's position via Homogeneous direction:

var sunPosition = new THREE.Vector4( 0, 1, - 1, 0 );

To compute the clip space coordinate and check the negativity of the z component:

let clipPosition = sunPosition
   .clone()
   .applyMatrix4(camera.matrixWorldInverse)
   .applyMatrix4(camera.projectionMatrix);

screenSpacePosition.x = ( clipPosition.x / clipPosition.w + 1 ) / 2;
screenSpacePosition.y = ( clipPosition.y / clipPosition.w + 1 ) / 2;

if (clipPosition.z < 0.0) {
    // [...]
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The upload functionality in Codeigniter seems to be malfunctioning

I am currently developing a content management system using Codeigniter. I am facing an issue with uploading files from the system. Although I am able to receive the file, the do_upload method seems to be not functioning correctly. I have looked for soluti ...

Launching Angular Application on GitHub Pages

I am encountering an issue with my GitHub Pages website that is connected to a custom domain. My domain is hosting a web application created with Angular, but I am having trouble loading the .js and .css files when I visit the site. In the DevTools/Networ ...

Format the date using moment() for the last week of the current year and return it with the year. Next year's

I encountered an unusual problem when using Moment.js with Angular.js. When I use the .toISOString() method, I get the correct date, but when I use the .format() method, the date is completely wrong. Here is an example to illustrate the issue: Code snip ...

Sending the results from a Vue.js component to a text input field in HTML

Using vue.js and the v-for function to read QR codes has been a challenge for me. For example: <ul v-for="(scan,key) in scans" :key="key" > {{scan.content}} </ul> I need to extract the value inside {{scan.content}}, like an EmployeeID, but I ...

The correlation between frames per second (FPS) and the milliseconds required to render a frame in the stats plugin is known as the frame

Recently, I've implemented the Stats.js plugin to keep track of my three.js performance. Something seems off with the FPS (frames rendered per second) and MS (milliseconds needed to render a frame) information: According to my calculations, if it ta ...

The script continues to run despite receiving an XHR error status of 0

I have created an asynchronous audio upload script that is working flawlessly, except for one issue that is really bothering me. The problem lies in the error handling aspect, where an error is being handled with an xhr status of 0 even though the upload p ...

Guide to developing and showcasing a proof of concept (PoC) for a Google Chrome vulnerability using Out of Bounds (oob) method

While reading through an article discussing the Proof of Concept of a CVE in Google Chrome (OOB issue), I came across this intriguing code snippet: (https://medium.com/@elniak/cve-2024-4761-exploiting-chromes-javascript-engine-highly-exploited-poc-presente ...

Unleashing the power of JavaScript: Sharing arrays and data structures effortlessly

Currently, I am utilizing HTML & JavaScript on the client side and NodeJs for the server side of my project. Incorporated in my form are multiple radio buttons. When the user clicks on the submit button, my intention is to post the results to the server. ...

execute a script using an npm module

Imagine having a function like this index.js greet = () => { console.log('hello'); } greet(); and you want it to execute as soon as the page loads, so you include greet();. This works fine when the file is imported in your index.html in ...

My Next.js application is facing permission issues while trying to access getServerSideProps with Firebase

For my latest project, I am in the process of developing a chat application. The goal is that when a user navigates to the URL www.myproject.com/chat/[id], the app will retrieve the conversation data. While my security rules seem to be adequate, changing ...

Missing data: Node JS fails to recognize req.body

I've looked through various posts and I'm feeling quite lost with this issue. When I run console.log(req), the output is as follows: ServerResponse { ... req: IncomingMessage { ... url: '/my-endpoint', method: &a ...

The leaflet.js library threw an error: LatLng object is not valid due to invalid coordinates (NaN, NaN)

Currently, I am working with a JSON file that contains coordinates in pairs. "_id" : ObjectId("59407457838b932e0677999e"), "type" : "MultiPoint", "name" : "points", "coordinates" : [ [ -73.958, 40.8003 ], ...

Search for a specific node within an Ajax response using jQuery

Struggling with extracting a specific node from an XML document obtained via an Ajax call in my JavaScript code. The returned XML contains key/value pairs, and I am aiming to extract the value linked to a particular key. Let's imagine the structure o ...

Fix surface orientations on potentially corrupted .stl files

I am facing a challenge in Three.js where I need to fix the normals on files that are coming in with potential issues. It's unclear whether the problem lies in the scanning process or during the file uploads. While we are investigating the upload func ...

How can I turn off shadows for every component?

Is it feasible to deactivate shadows and elevation on all components using a configuration setting? ...

After making a call to the backend in React, the action function consistently returns the same reducer type

As I finalize my 2FA implementation, the last step involves detecting whether a user has enabled it or not. I've created a function that checks for this and returns the following: if (!user.twoFactorTokenEnabled) { // Allow user to login if 2FA ...

Implement a JavaScript function that loads fresh content onto the webpage without the need to refresh the

$("span.removeFromCart").on("click",function(){ var id = $(this).attr("data-id"); $.ajax({ type: "GET", url: "ajax.php?id="+id+"&action=remove" }) .don ...

Combine API calls using promises

The functionality of a plugin I'm using is currently not functioning as expected, leading me to merge two separate requests. Below is the code I am utilizing: Although I am able to receive a response, I am facing difficulties in checking for response ...

Jquery : The initial call to Ajax is not being made

I need help with a Jquery code I am working on. Here is the code: function fetch_data(){ var dataObj; $.ajax({ url: "XXX.soesite.XXX", success: function(result) { dataObj = ['Hi']; console.log("Ins ...

Oops! It seems that an invalid BCrypt hash triggered an unspecified "error" event that was not handled

Attempting to develop an API for login in nodejs. However, when checking the login route via HTTP requester, nothing is displayed in the output. Instead, the command line shows an error Error: Uncaught, unspecified "error" event. (Not a valid BCrypt hash.) ...