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.