Issue with a stationary directional light tracking the movement of a rotating object and/or changes in the camera perspective

I've been facing a challenge in implementing a day-night cycle with a directional light in an Earth model using custom shaders. Everything seems to work fine with the night and day maps, as well as the light, as long as I don't manipulate the camera. In other words, the Earth rotates while the light source stays stationary, and the night and day cycles update correctly. However, when I rotate the camera using the mouse, the light seems to follow the camera, resulting in an always-illuminated part of the Earth being visible.

Here's how I've set up the light source:

var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5,3,5);
scene.add(light);

And here's how I pass the parameters to the shader:

uniforms_earth = {
    sunPosition: { type: "v3", value: light.position },
    dayTexture: { type: "t", value: THREE.ImageUtils.loadTexture( "daymap.jpg" ) },
    nightTexture: { type: "t", value: THREE.ImageUtils.loadTexture( "images/nightmap.jpg" ) }
};

For the vertex shader:

varying vec2 v_Uv;
varying vec3 v_Normal;

uniform vec3 sunPosition;
varying vec3 v_vertToLight;

void main() {

    v_Uv = uv;
    v_Normal = normalMatrix * normal;

    vec4 worldPosition = modelViewMatrix * vec4(position, 1.0);

    v_vertToLight = normalize(sunPosition - worldPosition.xyz);

    gl_Position = projectionMatrix * worldPosition;

}

The fragment shader is as follows:

uniform sampler2D dayTexture;
uniform sampler2D nightTexture;

varying vec2 v_Uv;
varying vec3 v_Normal;

varying vec3 v_vertToLight;

void main( void ) {

    vec3 dayColor = texture2D(dayTexture, v_Uv).rgb;
    vec3 nightColor = texture2D(nightTexture, v_Uv).rgb;

    vec3 fragToLight = normalize(v_vertToLight);

    float cosineAngleSunToNormal = dot(normalize(v_Normal), fragToLight);


    cosineAngleSunToNormal = clamp(cosineAngleSunToNormal * 10.0, -1.0, 1.0);

    float mixAmount = cosineAngleSunToNormal * 0.5 + 0.5;

    vec3 color = mix(nightColor, dayColor, mixAmount);

    gl_FragColor = vec4( color, 1.0 );

}

Lastly, I use the THREE library for camera controls:

var controls = new THREE.TrackballControls(camera);
    

To update the Earth rotation within the render function:

function render() {
    controls.update();
    earth.rotation.y += rotation_speed; 
    requestAnimationFrame(render);
    renderer.render(scene, camera);
}

I have attempted to modify how I calculate v_vertToLight so that both the vertex and light position are in the same world space:

v_vertToLight = normalize((modelViewMatrix*vec4(sunPosition, 1.0)).xyz - worldPosition.xyz);

While this prevents the light from shifting when I adjust the camera, it causes the night-day shadow to remain static, as the light appears to start rotating with the Earth itself.

Answer №1

What you are referring to as worldPosition is actually a position in view space, not world space. Therefore, it would be more appropriate to rename this variable:

vec4 worldPosition = modelViewMatrix * vec4(position, 1.0);

vec4 viewPosition = modelViewMatrix * vec4(position, 1.0);

The sunPosition, on the other hand, represents a position in world space. It needs to be converted to view space before being used to calculate the view space light vector. This conversion should be done using the viewMatrix instead of the modelViewMatrix. In essence, the modelViewMatrix transforms from model space to view space, while the viewMatrix transforms from world space to view space (refer to the documentation on three.js - WebGLProgram):

vec4 viewSunPos = viewMatrix * vec4(sunPosition, 1.0);
v_vertToLight = normalize(viewSunPos.xyz - viewPosition.xyz);

It's important to note that v_vertToLight and v_Normal should both be either view space vectors or world space vectors to maintain consistency in the reference system. Otherwise, calculating the dot product between these vectors would not yield meaningful results.

Below is a snippet of the vertex shader code for reference:

varying vec2 v_Uv;
varying vec3 v_Normal;

uniform vec3 sunPosition;
varying vec3 v_vertToLight;

void main() {

    vec4 viewPosition = modelViewMatrix * vec4(position, 1.0);
    vec4 viewSunPos   = viewMatrix * vec4(sunPosition, 1.0);

    v_Uv = uv;
    
    v_Normal      = normalMatrix * normal;
    v_vertToLight = normalize(viewSunPos.xyz - viewPosition.xyz);

    gl_Position = projectionMatrix * viewPosition;
}

For a simple example utilizing the vertex shader, refer to the code snippet below:

(function onLoad() {
  // code snippet here
})();
<script src="https://cdn.jsdelivr.net/npm/three@0.129.0/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.129.0/examples/js/controls/OrbitControls.js"></script>

<script type='x-shader/x-vertex' id='vertex-shader'>
// vertex shader code here
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
// fragment shader code here
</script>

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

Redirecting JavaScript form to search engine

I am struggling with creating a form that enables a user to input text and then directs them to a specified search engine with the input as the query. I am encountering difficulties in getting the JavaScript to properly redirect. An interesting observatio ...

Ways to repair the mouse hover transform scale effect (animation included)

I am currently facing an issue with my GridView that contains images. When I hover over the top of the image, it displays correctly, but when I move to the bottom, it does not show up. After some investigation, I suspect that there may be an overlay being ...

Using Blob to save CSV file on Safari

Here are the codes I am using to generate a download link for users to download a .csv file from my website. var link = document.createElement("a"); link.id = "csvDwnLink"; window.URL = window.URL || window.webkitURL; var csv = "\ufeff" + CSV, b ...

Issue with retrieving the value of a JavaScript dynamically generated object property

I'm currently working on a React Material-UI autocomplete component and facing challenges with accessing a Javascript Object property within a handleSelect function. Although I can retrieve the townname value using document.getElementById, I know thi ...

Real-time Broadcasts Straight to Your Web Browser

Feeling a bit frustrated with my current situation. I am eager to stream a live video broadcast to a web browser. At the moment, I am utilizing ffmpeg to stream a directshow live source as a webm stream to node.js, which then sends the stream to the http ...

Combining hover and mousedown event listeners in jQuery: Tips and tricks

htmlCODE: <div class="original_circle" style="border-radius: 50%; background-color: blue; width: 40px; height:40px; z-index: 0"> <div class="another_circle" style="position:absolute; border-radius: 50%; background-color: coral; width: 40px; h ...

Using Javascript to establish a connection with a Joomla MySQL database

Recently, I was tasked with building a website in Joomla that utilizes a MySQL database. As part of this project, I am required to develop a JavaScript function that connects to the MySQL database. Do you have any advice or tips for me? ...

typescript mock extending interface

I'm currently working with a typescript interface called cRequest, which is being used as a type in a class method. This interface extends the express Request type. I need to figure out how to properly mock this for testing in either jest or typemoq. ...

What is the best way to utilize an HTML form for updating a database entry using the "patch" method?

I have been attempting to update documents in my mongoDB database using JavaScript. I understand that forms typically only support post/get methods, which has limitations. Therefore, I am looking for an alternative method to successfully update the documen ...

Exploring the world of AngularJS for the first time

I'm currently delving into the world of AngularJS and I encountered an issue with my first example. Why is it not working as expected? Here's a look at the HTML snippet: <html ng-app> <head> <title></title> <sc ...

What steps should I take to address a problem involving a callback function?

I'm currently working on an application that aims to connect users with friends based on specific questions they answer. However, I keep encountering an error message stating "TypeError [ERR_INVALID_CALLBACK]: Callback must be a function" when the cod ...

Are the commands overlapping?

Currently, I am in the process of developing a dynamic discord chat bot using JavaScript and node.js. One of my goals is to implement a specific command without interfering with an existing one. The bot functions flawlessly across all servers where it is ...

Setting a random number as an id in the constructor in Next JS can be achieved by generating a

What steps can be taken to resolve the error message displayed below? Error: The text content does not match the HTML rendered by the server. For more information, visit: https://nextjs.org/docs/messages/react-hydration-error Provided below is the code i ...

Using regular expressions in Javascript to extract decimal numbers from a string for mathematical operations

I'm currently working on a Vue method where I extract information from a WordPress database. The data retrieved sometimes contains unnecessary text that I want to filter out. Using the prodInfo variable, the input data looks something like this: 2,5k ...

What is the process for importing Buffer into a Quasar app that is using Vite as the build tool

I'm having issues with integrating the eth-crypto module into my Quasar app that utilizes Vite. The errors I'm encountering are related to the absence of the Buffer object, which is expected since it's typically found in the front end. Is ...

Steps for eliminating a button when the API no longer provides any information

Everything is functioning smoothly with the code below, but I would like to enhance it so that when I call getNextPers() and there is no information available, the Ver Mais button disappears. I have been researching solutions without success, so any assi ...

The functionality of React-router-dom protected routes seems to be malfunctioning

Understanding Protected Routes in React.js: While looking at the implementation of protected routes, you may notice that 'false' is being directly used in the if statement. However, even with this condition, the page is still accessible. Why doe ...

Why won't Node.js let me redirect to my error page?

I've been putting together my newsletter project with the Mailchimp API, everything seems to be working fine except for when I try to redirect to a failure page if the status code is not 200. The browser shows an error message saying 'localhost r ...

How can I implement a toggle button to display additional details for a specific row within a table created using React.js?

I'm currently working on a project using Next.js and have come across an unexpected issue. Upon reading a JSON file, I populate a table with the data retrieved from it. Each piece of information in the table has hidden details that should only be reve ...

Utilize a singular object to contain multiple instances of the useState hook

const [regionData, setRegionData] = useState({ country: "", city: "", population: "", location: "", temp_min: "" }); Does anyone know a more efficient and cleaner way to replace these individual useState hooks by organizing them into ...