THREE DirectionalLight falling off with distance - three.js

According to the docs for THREE.DirectionalLight:
This light will behave as though it is infinitely far away and the rays produced from it are all parallel.
But, I'm finding that as a move an object with THREE.MeshStandardMaterial farther away from the light (but kept at the same relative angle), the intensity of the light decreases. This doesn't seem right to me.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var dpr = window.devicePixelRatio;
var renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true});
renderer.setPixelRatio(dpr);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.z = 700;
var planeGeom = new THREE.PlaneGeometry(200, 200);
var plane = new THREE.Mesh(planeGeom, new THREE.MeshStandardMaterial({color: 0xff00ff, metalness: 1}));
plane.position.z = -10;
scene.add(plane);
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-50, 50, 100);
scene.add(light);
var helper = new THREE.DirectionalLightHelper(light, 10);
scene.add(helper);
function update(time) {
plane.position.x = 200 * Math.sin(time);
plane.position.y = 200 * Math.cos(time/2);
}
function render() {
requestAnimationFrame(render);
update(performance.now() / 1000);
renderer.render(scene, camera);
}
render();
body {
margin: 0;
padding: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script>
I expect the plane to be lit the same no matter what position it's in. What am I doing wrong?

You are using MeshStandardMaterial and have set metalness to 1.
Metals reflect primarily specularly; the diffuse component of the reflection is minimal.
So what you are seeing is the "hot spot" of the reflection. Set the metalness to zero, for example, and you will see primarily a diffuse reflection.
Also, when using MeshStandardMaterial, you should include an environment map (material.envMap) so there is something to reflect. Materials -- especially metals -- will look much better that way.
three.js r.87

Related

Soft shadow has an unintended offset

I'm currently working on a soft / blurred shadow effect that is casted on a plane directly under my object (just for giving it some more depth). The light source (DirectionalLight) shares the center coordinates of the object but with an offset in Y, so that it's straight above. It is pointing down to the center of the object.
I experimented a little bit with the shadow parameters of the light and found out that lowering the shadow map size gives me quite a nice soft shadow effect which would be sufficient for me. For example:
light.shadow.mapSize.width = 32;
light.shadow.mapSize.height = 32;
However, i noticed that there is an offset to the shadow which lets the observer assume that the light source is not coming directly from above:
I created this fiddle from which i created the image. As shadow type i use the PCFSoftShadowMap.
With this setup I would assume that the shadow effect is equally casted on all four sides of the cube, but it's obviously not. I also noticed that this 'offset' gets smaller when increasing the shadow map size and is barely noticable when using for example sizes like 512 or 1024.
This method would be an easy and performant solution for the desired effect, so I really appreciate any help on this
EDIT:
As stated out in the comments, tweaking the radius of the LightShadow isn't a satisfiying solution because the shadow gradient has hard edges instead of soft ones.
I think what is happening is that your shadowmap is low enough resolution, that you're seeing rounding error. If you switch back to THREE.BasicShadowMap, I think you will see that the physical lightmap pixels being hit happen to lie on the side of the object that you're seeing the larger edge, and as you move the object, the shadow will move in steps the size of the pixels on the map.
Generally in practice, you want to use a higher res lightmap, and keep its coverage area as tight around the focal point of your scene as possible to give you the most resolution from the lightmap. Then you can tweak the .radius of of the LightShadow to get the right softness.
One solution i came up with is using four light sources, all with a very slight positional offset, so that the 'shadow-offset' would come from four different directions (http://jsfiddle.net/683049eb/):
// a basic three.js scene
var container, renderer, scene, camera, controls, light, light2, light3, light4, cubeCenter, cube;
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xccccff);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container = document.createElement('div');
document.body.appendChild(container);
container.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 200, 800);
camera.lookAt(scene.position);
// (camera) controls
// mouse controls: left button to rotate,
// mouse wheel to zoom, right button to pan
controls = new THREE.OrbitControls(camera, renderer.domElement);
var size = 100;
// ambient light
var ambient = new THREE.AmbientLight(0xffffff, 0.333);
scene.add(ambient);
// mesh
var cubeGeometry = new THREE.BoxGeometry(size, size, size);
var cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000
});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.y = size / 2.0;
cube.castShadow = true;
cube.receiveShadow = false;
scene.add(cube);
// Get bounding box center
var boundingBox = new THREE.Box3().setFromObject(cube);
cubeCenter = new THREE.Vector3();
boundingBox.getCenter(cubeCenter);
var position1 = new THREE.Vector3(0, size * 2, 0.0000001);
createDirectionalLight(scene, 0.15, position1, size, cubeCenter);
var position2 = new THREE.Vector3(0, size * 2, -0.0000001);
createDirectionalLight(scene, 0.15, position2, size, cubeCenter);
var position3 = new THREE.Vector3(0.0000001, size * 2, 0);
createDirectionalLight(scene, 0.15, position3, size, cubeCenter);
var position4 = new THREE.Vector3(-0.0000001, size * 2, 0);
createDirectionalLight(scene, 0.15, position4, size, cubeCenter);
// shadow plane
var planeGeometry = new THREE.PlaneGeometry(500, 500, 100, 100);
var planeMaterial = new THREE.MeshLambertMaterial({
// opacity: 0.6,
color: 0x65bf32,
side: THREE.FrontSide
});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;
scene.add(plane);
// events
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
function createDirectionalLight(scene, intensity, position, cameraSize, targetPosition) {
var light = new THREE.DirectionalLight(0xffffff, intensity);
light.position.set(position.x, position.y, position.z);
light.target.position.set(targetPosition.x, targetPosition.y, targetPosition.z);
light.target.updateMatrixWorld(true);
light.castShadow = true;
scene.add(light);
light.shadow.mapSize.width = 32;
light.shadow.mapSize.height = 32;
light.shadow.camera.left = -cameraSize;
light.shadow.camera.right = cameraSize;
light.shadow.camera.bottom = -cameraSize;
light.shadow.camera.top = cameraSize;
light.shadow.camera.near = 1.0;
light.shadow.camera.far = cameraSize * 3;
light.shadow.bias = 0.0001;
scene.add(new THREE.CameraHelper(light.shadow.camera));
}
<script src="http://threejs.org/build/three.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>

Three.js - How to slowly change the camera's orientation untill it reaches a specific vector

I'm using Three.js to develop a player for 360° pictures, and I need some advice.
I have created a few cliquable meshs inside the scene. Currently, when the user clicks on a mesh, the camera's orientation is brutally changed to the mesh's direction. (this done by calling THREE.Camera.lookat()).
What I want is that when the users clicks, the camera transitions smoothly from it's target vector to the mesh's direction. I would like that the camera takes about 1 second to go from its current vector to the mesh's direction.
I have seen that tween is a library with which we can animate the scene, but I didn't really understand how it works.
Do you know what I could use to implement this animation ?
If tween can help me, can you explain how tween comes into play with three.js, or can you link some githubs or else ?
Thank you for feedbacks.
Just an extension of the manthrax's idea with Tween.js
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 0);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var sphere = new THREE.Mesh(new THREE.SphereGeometry(10, 32, 24), new THREE.MeshBasicMaterial({
color: "yellow",
wireframe: true
}));
scene.add(sphere);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var startRotation = new THREE.Quaternion();
var targetRotation = new THREE.Quaternion();
window.addEventListener("mousedown", onMouseDown, false);
function onMouseDown(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
let newPosition = raycaster.ray.at(10);
setPoint(newPosition);
// manthrax's idea + Tween.js
startRotation.copy(camera.quaternion);
camera.lookAt(newPosition);
camera.updateMatrixWorld();
targetRotation = camera.quaternion.clone();
camera.quaternion.copy(startRotation);
new TWEEN.Tween(camera.quaternion).to(targetRotation, 1000).easing(TWEEN.Easing.Bounce.Out).delay(250).start();
// one of benefits of using Tween.js is easings
// you can find many of them here
// https://sole.github.io/tween.js/examples/03_graphs.html
}
function setPoint(position) {
let point = new THREE.Mesh(new THREE.SphereGeometry(0.125, 4, 2), new THREE.MeshBasicMaterial({
color: "red",
wireframe: true
}));
point.position.copy(position);
scene.add(point);
}
render()
function render() {
requestAnimationFrame(render);
TWEEN.update(); // don't forget to put this line into the animation loop, when you use Tween.js
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>
Something like:
var targetRotation,startTime,transitionDuration;
var startRotation = camera.quaternion.clone();
function smoothTransition(newTarget){
startRotation.copy(camera.quaternion);
camera.lookAt(newTarget);
camera.updateMatrixWorld();
targetRotation = camera.rotation.clone();
startTime = performance.now();
transitionDuration = 1000;
}
In animate:
if(startRotation){
var playTime = (performance.now()-startTime)/transitionDuration;
if(playTime>1)playTime = 1;
Quaternion.slerp(startRotation,targetRotation,camera.rotation,playTime);
camera.updateMatrixWorld();
}

Shadows working on three.js r-54 but not working on three.js r-84

I have a problem with shadows on three.js
In this scenario: https://jsfiddle.net/manumid/q5qhjkka/2/ I have a cube with a light pointing to it. It uses three.js r-54. The shadow works fine.
But in the same example, using three.js r-84: https://jsfiddle.net/manumid/krpgr0x0/ the shadow does not appear.
The only difference between codes is, besides the three.js version, the light shadow helper (although this has no relation with my shadow problem):
On r-54:
spot.shadowCameraVisible = true;
On r-84:
scene.add (new THREE.CameraHelper(spot.shadow.camera));
Thank you!
A lot of props seem to have changed between those 30 versions of three. I don't have it looking exactly the same but hopefully this at least helps? A lot of the shadow and shadowMap names are in different places and spotlight has quite a lot more controls now. A good playpen to see how the properties affect the light can be found here https://threejs.org/examples/#webgl_lights_spotlight
https://jsfiddle.net/4to72rkn/
var scene, camera, renderer, cube, cubeM, terrain, spot;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, 400 / 300, 0.01, 10000);
camera.position.set(600, 0, 5000);
scene.add(camera);
renderer = new THREE.WebGLRenderer()
renderer.setClearColor (0xdddddd, 1)
renderer.setSize(400, 300);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container = document.getElementById('canv_0')
container.appendChild(renderer.domElement);
cubeM = new THREE.MeshPhongMaterial({
color: 0xff0000
});
cube = new THREE.Mesh(
new THREE.CubeGeometry(1000, 1000, 1000), cubeM);
cube.position.set(0, 0, 0);
cube.rotation.set(0, 0, 0);
cube.castShadow = true;
scene.add(cube);
terrain = new THREE.Mesh(
new THREE.CubeGeometry(10000, 1000, 10000), new THREE.MeshPhongMaterial({
color: 0x00ff00
}));
terrain.receiveShadow = true;
terrain.position.set(0, -2000, 0);
terrain.rotation.set(0, 0, 0);
scene.add(terrain);
spot = new THREE.SpotLight( 0xffffff, 1 );
spot.shadow.camera.near = 1000;
spot.shadow.camera.far = 10000;
spot.castShadow = true;
spot.position.set(-1000, 2000, 1500);
spot.distance = 20000;
spot.penumbra = 1;
scene.add(spot);
scene.add (new THREE.CameraHelper(spot.shadow.camera));
(function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.js"></script>
<div id="canv_0" width="400" height="300">
</div>

Three.js - Faces missing after Blender import

I tried to import a JSON exported file of my 3D model and import it in Three.js but it seems some faces are missing.
I am not sure if it was an export problem because when I rotate it, the left face exists but the right face does not, vice versa.
Here is my original model in Blender:
var scene, camera, renderer;
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
var SPEED = 0.01;
function init() {
scene = new THREE.Scene();
initMesh();
initCamera();
initLights();
initRenderer();
document.body.appendChild(renderer.domElement);
}
function initCamera() {
camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 1, 10);
camera.position.set(0, 3.5, 5);
camera.lookAt(scene.position);
}
function initRenderer() {
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(WIDTH, HEIGHT);
}
function initLights() {
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
directionalLight.position.set( 0, 1, 0 );
scene.add( directionalLight );
}
var mesh = null;
function initMesh() {
var loader = new THREE.JSONLoader();
loader.load('./model.json', function(geometry, materials) {
mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
mesh.scale.x = mesh.scale.y = mesh.scale.z = 0.75;
mesh.translation = THREE.GeometryUtils.center(geometry);
mesh.position.x = -5;
scene.add(mesh);
});
}
function rotateMesh() {
if (!mesh) {
return;
}
mesh.rotation.y -= SPEED;
}
function render() {
requestAnimationFrame(render);
rotateMesh();
renderer.render(scene, camera);
}
init();
render();
Hope you can help me with this problem.
Thanks in advance!
I would suspect your problem has to do with the face-normals pointing in the wrong direction. To check if this is the case you could try to set all materials to double-sided:
materials.forEach(function(mat) {
mat.side = THREE.DoubleSide;
});
With Double-Sided mode, the faces are drawn regardless of the normals direction, so you should see all faces if enabled.
Or you could use the THREE.FaceNormalsHelper to have a look at the normals yourself.
scene.add(new THREE.FaceNormalsHelper(mesh, 2, 0x00ff00, 1));
This will render arrows for all faces indicating the normal-direction.
If the normals are wrong you can fix this in blender by selecting all affected faces and using the command Mesh>Faces>Flip Normals from the menu or in the Tools-Panel on the right-hand side in the "Shading/UV"-Tab. Sometimes just selecting all faces and running "Recalculate Normals" from the Tools will work as well.
Blender also has a display-mode for face-normals in the right hand menu in the "Mesh Display"-Section.

in three.js, the spotlight is not showing a cone on a plane

I have a very simple example: a spot light pointed at a plane. I am expecting to see a cone of light whose diameter depends on the setting of the spot light angle. I cannot see any cone, the whole plane is illuminated, even for very narrow settings of angle.
Here is my jfiddle: http://jsfiddle.net/blwoodley/WLtL4/1/
I'd love to know the source code that produced this picture from https://github.com/mrdoob/three.js/pull/3291 by West Langley. It obviously is working fine in that case.
So I must be doing something obviously wrong, but I can't figure it out.
Some of the code from the jfiddle, it doesn't get much simpler than this:
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 100000);
camera.position.x = 100;
camera.position.y = 100;
camera.position.z = 200;
camera.lookAt({x: 0,y: 0,z: 0});
scene = new THREE.Scene();
var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
var floorMaterial = new THREE.MeshLambertMaterial({ color: 0x222222, side:THREE.DoubleSide });
floor = new THREE.Mesh(new THREE.PlaneGeometry(2000,2000,10,10), floorMaterial);
floor.rotation.x = Math.PI / 2;
floor.position.y = -40;
scene.add(floor);
var light;
light = new THREE.SpotLight(0x008888);
light.position.set(0, 40, 0);
light.lookAt(floor);
light.angle = Math.PI/4;
light.intensity = 30;
light.distance=0;
scene.add(light);
// RENDERER
webglRenderer = new THREE.WebGLRenderer();
webglRenderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
webglRenderer.domElement.style.position = "relative";
container.appendChild(webglRenderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
This is subtle.
You are using MeshLambertMaterial for the plane. You need to change it to MeshPhongMaterial, so the lighting is rendered properly.
As explained here, for MeshLambertMaterial, the illumination calculation is performed only at each vertex.
For MeshPhongMaterial, the illumination calculation is performed at each texel.
So make these changes
floorGeometry = new THREE.PlaneGeometry( 1000, 1000 ); // no need to tessellate it now
var floorMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff } ); // 0x222222 is too dark
light.intensity = 1; // a reasonable value
Updated fiddle: http://jsfiddle.net/WLtL4/5/
three.js r.63
Also try to disable target for testing.
I'm getting really weird behavior from it. Sometimes it makes it not render the light at all, no idea why yet. I'll make a demo of the problem later.

Resources