ThreeJS shadows do not function as expected - three.js

I am having a simple three.js scene and have added some spheres. I want to achieve a simple lighting and shadowing effect but I get strange dark shapes on my spheres.
I guess something is wrong with my settings : jsFiddle
this is the core of this simple piece of code :
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.physicallyBasedShading = true;
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
renderer.shadowCameraNear = 0.1;
renderer.shadowCameraFar = 1000;
renderer.shadowCameraFov = 100;
renderer.shadowMapBias = 0.0039;
renderer.shadowMapDarkness = 0.5;
renderer.shadowMapWidth = 1024;
renderer.shadowMapHeight = 1024;
document.body.appendChild( renderer.domElement );
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 150, 50, 150 );
// controls
controls = new THREE.OrbitControls( camera );
// lights
var light = new THREE.SpotLight( 0xF5F6CE, 1 );
light.position.set( 300, 300, 60 );
light.castShadow = true;
light.shadowMapWidth = 1024; // power of 2
light.shadowMapHeight = 1024;
light.shadowCameraNear = 200; // keep near and far planes as tight as possible
light.shadowCameraFar = 500; // shadows not cast past the far plane
light.shadowCameraFov = 20;
light.shadowBias = -0.00022; // a parameter you can tweak if there are artifacts
light.shadowDarkness = 0.8;
light.shadowCameraVisible = true;
scene.add( light );
// axes
scene.add( new THREE.AxisHelper( 200 ) );
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
for (var z = 0; z < 3; z++) {
var sphere = new THREE.Mesh(new THREE.SphereGeometry(10,32, 32), new THREE.MeshPhongMaterial({ color: "#FFFF00", side: THREE.DoubleSide, })); // with MeshLambertMaterial still not working
sphere.position.set(20*x, 20*y, 20*z);
castShadow = true;
sphere.castShadow = true;
sphere.receiveShadow = true ;
scene.add(sphere);
}
}
}

The blackish artifacts are coming from the two-sidedness of the spheres combined with shadow mapping.
Remove side: THREE.DoubleSide from their material definition and you should be good.
http://jsfiddle.net/Us54P/331/
You can also get different results with tweaking the shading parameters, for example, you can add renderer.shadowMapType = THREE.PCFSoftShadowMap;. See here: http://jsfiddle.net/Us54P/332/
Could also be a bug (or a debugging feature), because it disappears when you disable light.shadowCameraVisible = true;: http://jsfiddle.net/Us54P/334/

Related

Three.JS VRAM memory leak when adding removing THREE.Geometry to scene

I have encountered a VRAM memory leak in my app.
The app adds and removes THREE.Geometry very often to create a volumetric animation.
If instead of a THREE.Geometry with it's own populated vertices, I used instead THREE.SphereBufferGeometry, the memory leak doesn't happen.
I have created a minimal app to prove this memory leak is real.
The memory leak increase VRAM memory very slowly, but it does fill up in the end.
I think that pools won't help, since it's VRAM and not managed memory.
I do use dispose.
If you can make this sample work and not have memory leak, that might solve my issue:
https://jsfiddle.net/4a7ksryd/16/
Edit: I am adding here the code of the app:
var camera, scene, renderer;
var geometry, material, mesh;
var lastSphere;
var lastGeo;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
camera.position.z = 1;
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
var light = new THREE.DirectionalLight( 0xffffff, 1, 100 );
light.position.set( 0, 4, 0 ); //default; light shining from top
light.castShadow = true; // default false
//Set up shadow properties for the light
light.shadow.mapSize.width = 1024; // default
light.shadow.mapSize.height = 1024; // default
light.shadow.camera.near = 1; // default
light.shadow.camera.far = 20; // default
scene.add( light );
//Create a sphere that cast shadows (but does not receive them)
geometry = new THREE.SphereBufferGeometry( 0.1, 32, 32 );
material = new THREE.MeshStandardMaterial( { color: 0xff0000 } );
// geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
// material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh( geometry, material );
mesh.position.y = 0.1;
mesh.castShadows = true;
mesh.receiveShadow = false;
scene.add( mesh );
var planeGeometry = new THREE.PlaneBufferGeometry( 15, 15, 1, 1 );
var planeMaterial = new THREE.MeshStandardMaterial( { color: 0xffffff, emissive:0x111111 } )
var plane = new THREE.Mesh( planeGeometry, planeMaterial );
plane.position.y = -0.2;
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true;
scene.add( plane );
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
var dim = 32;
var geo1 = new THREE.Geometry();
const numVertices = dim*dim;
var vertices = new Array(numVertices);
for (var i=0; i<vertices.length; i++)
{
const x = i%dim;
const y = (Math.floor(i/dim))%dim;
vertices[i] = new THREE.Vector3(x*0.1, y*0.1, 0);
}
const numFaces = (dim-1)*(dim-1)*2;
var faces = new Array(numFaces);
for (var i=0; i<(faces.length/2); i++)
{
const x = i%(dim-1);
const y = Math.floor(i/(dim-1))%(dim-1);
faces[2*i] = new THREE.Face3(x+y*dim, x+1+y*dim, x+(y+1)*dim);
faces[2*i+1] = new THREE.Face3(x+1+y*dim, x+1+(y+1)*dim, x+(y+1)*dim);
}
var uv = new Array(numFaces);
for (var i=0; i<uv.length; i++)
uv[i] = [new THREE.Vector2(0, 0), new THREE.Vector2(0, 0), new THREE.Vector2(0, 0)];
geo1.faces = faces;
geo1.vertices = vertices;
geo1.faceVertexUvs[0] = uv;
geo1.uvsNeedUpdate = true;
geo1.verticesNeedUpdate = true;
geo1.elementsNeedUpdate = true;
// var sphereGeometry = new THREE.SphereBufferGeometry( 0.1, 256, 256 );
var sphereGeometry = geo1;
var sphereMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
var sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
sphere.position.y = 0.1+Math.sin(mesh.rotation.x)*0.1;
sphere.position.x = 0.5;
sphere.castShadow = true; //default is false
sphere.receiveShadow = false; //default
if (lastGeo!=null)
lastGeo.dispose();
if (lastSphere!=null)
scene.remove(lastSphere);
scene.add( sphere );
lastSphere = sphere;
lastGeo = sphereGeometry;
// geo1.dispose();
renderer.render( scene, camera );
}
This is actually a bug in three.js. I've filed a PR to fix the issue:
https://github.com/mrdoob/three.js/pull/20479

Selfshadow plane affected by a displacement map not working

Working on some kind of fictional treasure map. I'm cutting a large displacement map intosmaller tiles as I don't yet how wide the final terrain is going to be -- right now it's 5*5, but it could be wider in the future
For some reasons, I am having issues projecting shadows on the displaced planes.
I don't know where the problem is coming from. Maybe it's the way I push meshes into an array through a function, i'm afraid i'm not doing this the right way.
I'd like to achieve the result using a directional light
Here is a c4d draft of what i'm trying to achieve
and here is what i'm able to do in the browser (didnt manage to tile them properly yet :^)
var renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
var material = [];
var texture = [];
var tile = [];
var planeRes = 128;
var planesize = 1;
var dim = 5;
var size = dim * dim;
var DispScale = 2;
var geometry = new THREE.PlaneBufferGeometry(planesize,planesize,planeRes, planeRes);
function tileGenerator(inc) {
if (inc < 10) {
texture[inc] = new THREE.TextureLoader().load('cut25lowres/image_part_00' + inc + '.jpg');
} else {
texture[inc] = new THREE.TextureLoader().load('cut25lowres/image_part_0' + inc + '.jpg');
}
material[inc] = new THREE.MeshPhongMaterial({
color: 0xffffff,
displacementMap: texture[inc],
side: THREE.DoubleSide,
receiveShadow : true,
castShadow : true
});
tile[inc] = new THREE.Mesh(geometry, material[inc]);
}
for (var i = 1; i < size + 1; i++) {
tileGenerator(i);
}
for (var i = 1; i < size + 1; i++) {
tile[i].position.set(-planesize * (i % dim)+1, 0, -planesize * Math.ceil(i / dim)+1 );
tile[i].rotation.x = Math.PI / 2 + Math.PI;
tile[i].rotation.z = Math.PI / 2;
scene.add(tile[i]);
}
var dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
dirLight.castShadow = true;
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 6;
dirLight.shadow.mapSize.set( 1024, 1024 );
var targetObject = new THREE.Object3D();
targetObject.position.x = -10;
targetObject.position.z = -10;
dirLight.position.y = 3;
scene.add(targetObject);
dirLight.target = targetObject;
scene.add( dirLight );
Edit : Here is a cleaner version without the array as it's not part of the problem
jsfiddle.net/clemtre/3y9tqc6j/34
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var scene = new THREE.Scene();
var heightmap = new THREE.TextureLoader().load('https://i.imgur.com/MVYhfd7.jpeg');
var geometry = new THREE.PlaneGeometry(20, 20, 100, 100);
var material = new THREE.MeshPhongMaterial({
color: 0xffffff,
displacementMap: heightmap,
displacementScale: 10
});
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 1, 1).normalize();
light.castShadow = true;
scene.add(light);
var plane = new THREE.Mesh(geometry, material);
plane.rotation.x = -Math.PI/2;
scene.add(plane);
camera.position.z = -20;
camera.position.y = 5;
controls.update();
var animate = function() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
animate();
Many thanks!

Directional light shadow of boxes is not rendered on plane mesh

The following light is added to my scene:
var light = new THREE.DirectionalLight( 0xffffff, 1.5 );
light.position.set( 1000, 1000, 1000 );
light.castShadow = true;
light.shadow = new THREE.DirectionalLightShadow(
new THREE.OrthographicCamera(
-100, 100,
100, -100, -500, 500 ) );
light.shadow.bias = - 0.00022;
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
The plane is as follows:
var planeGeometry = new THREE.PlaneGeometry( 200, 200 );
planeGeometry.rotateX( - Math.PI / 2 );
var planeMaterial = new THREE.ShadowMaterial();
planeMaterial.opacity = 1;
var plane = new THREE.Mesh( planeGeometry, planeMaterial );
plane.position.y = 0;
plane.receiveShadow = true;
scene.add( plane );
The boxes are:
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
for ( var i = 0; i < 20; i ++ ) {
var object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } ) );
object.scale.x = Math.random() * 5 + 1;
object.scale.y = Math.random() * 5 + 1;
object.scale.z = Math.random() * 5 + 1;
object.position.x = Math.random() * 30;
object.position.y = object.scale.y / 2.0;
object.position.z = Math.random() * 60;
object.castShadow = true;
object.receiveShadow = true;
scene.add( object );
}
Directional light shadow of boxes is not rendered on plane mesh. Could you pls help what do I wrong? Please refer to the following screenshot.
Some of the shadow map properties have been renamed in the recent versions.
Setting up the renderer for shadow maps (and choosing the more computational expensive shadow map type):
var renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
Setting up the light (notice how it also works with THREE.PointLight):
var light = new THREE.PointLight( 0xffffff, 1, 100 );
light.position.set( 0, 12, 0 );
light.castShadow = true; // default false
light.shadow.mapSize.width = 1024; // default 512
light.shadow.mapSize.height = 1024; // default 512
light.shadow.camera.near = 2; // default 0.5
light.shadow.camera.far = 100; // default 500
scene.add( light );
For plane material use var planeMaterial = new THREE.MeshLambertMaterial();
( ShadowMaterial works from r77 https://github.com/mrdoob/three.js/issues/1791)
var renderer = new THREE.WebGLRenderer( {antialias:true, alpha: true } );
renderer.shadowMap.enabled = true; // enable shadows rendering
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // to antialias the shadow
var light_target = new THREE.Object3D(); // believe me
light_target.position.set(0,0,0); // not necessary, but to be clear
var light = new THREE.DirectionalLight( 0xffffff,1.5,1000) );
light.position.set(1000, 1000, 1000);
light.target = light_target;
light.castShadow = true;
light.shadowMapWidth = 2048*2; // px of the rendered shadow texture
light.shadowMapHeight = 2048*2; // px of the rendered shadow texture
var d = 100; /// your plane is 200x200 and we targeted its center
var distance = light.position.distanceTo(light_target.position);
light.shadowCameraLeft = -d;
light.shadowCameraRight = d; light.shadowCameraTop = d; light.shadowCameraBottom = -d; light.shadowCameraNear = distance-d; light.shadowCameraFar = distance+d; light.shadowBias = -0.001;
light.shadowDarkness = 0.5;
scene.add( light ); // you missed this!

Three JS - Casting Shadows

I am trying to get the shadow from the ball appear on the floor, but it doesn't seem to work. Here is my code that declares the renderer, camera, lights, and objects:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMapEnabled = true;
renderer.shadowMapType = THREE.PCFSoftShadowMap;
document.body.appendChild( renderer.domElement );
var planegeo = new THREE.PlaneGeometry(100,100);
var geomaterial = new THREE.MeshLambertMaterial({color: "#EA8E12", side: THREE.DoubleSide});
var plane = new THREE.Mesh(planegeo, geomaterial);
plane.rotation.x += 90;
plane.position.z = -2;
scene.add(plane);
var light = new THREE.AmbientLight( "#EA8E12", 101);
var light2 = new THREE.DirectionalLight("yellow", 100);
light.position.set(0, 100, 0);
light2.position.set(0, 100, 0);
scene.add(light);
scene.add(light2);
var sphereParent = new THREE.Object3D();
var geometry = new THREE.SphereGeometry(1, 100, 100);
var material = new THREE.MeshPhongMaterial( {color: "yellow", shininess: 100});
var circle = new THREE.Mesh(geometry, material);
sphereParent.add(circle);
sphereParent.position.set(0,3,0);
scene.add(sphereParent);
light2.castShadow = true;
circle.castShadow = true;
sphereParent.castShadow = true;
plane.receiveShadow = true;
Does anyone can tell me what I am missing? Thanks
This should get you started. Modify the parameters as needed:
light2.castShadow = true;
light2.shadowMapWidth = 1024;
light2.shadowMapHeight = 1024;
light2.shadowMapDarkness = 0.75;
light2.shadowCameraNear = 1;
light2.shadowCameraFar = 1000;
light2.shadowDarkness = 0.75;
/* since you have a directional light */
light2.shadowCameraLeft = -500;
light2.shadowCameraRight = 500;
light2.shadowCameraTop = 500;
light2.shadowCameraBottom = -500;
light2.shadowCameraVisible = true; /* debugging */
on the renderer side this might help:
renderer.shadowMapDebug = true;

ThreeJS shadow not rendering

I've looked through a number of other S.O. questions, followed all of the advice, but I'm still clueless as to why I can't get shadows to render on this very basic scene.
http://jsfiddle.net/4Txgp/
[Updated] Code:
var SCREEN_WIDTH = window.innerWidth - 25;
var SCREEN_HEIGHT = window.innerHeight - 25;
var camera, scene;
var canvasRenderer, webglRenderer;
var container, mesh, geometry, plane;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.x = 1200;
camera.position.y = 1000;
camera.lookAt({
x: 0,
y: 0,
z: 0
});
scene = new THREE.Scene();
var groundMaterial = new THREE.MeshLambertMaterial({
color: 0x6C6C6C
});
plane = new THREE.Mesh(new THREE.PlaneGeometry(10000, 10000, 100, 100), groundMaterial);
plane.rotation.x = -Math.PI / 2;
plane.receiveShadow = true;
scene.add(plane);
// LIGHTS
// scene.add(new THREE.AmbientLight(0x666666));
/*
var light;
light = new THREE.DirectionalLight(0xdfebff, 1.75);
light.position.set(600, 800, 100);
//light.position.multiplyScalar(1.3);
light.castShadow = true;
light.shadowCameraVisible = true;
light.shadowMapWidth = light.shadowMapHeight = 2048;
var d = 50;
light.shadowCameraLeft = -d;
light.shadowCameraRight = d;
light.shadowCameraTop = d;
light.shadowCameraBottom = -d;
light.shadowCameraFar = 500;
light.shadowDarkness = 0.5;
scene.add(light);
*/
var spotLight = new THREE.SpotLight( 0xffffff );
spotLight.position.set( 700, 1000, 100 );
spotLight.castShadow = true;
spotLight.shadowCameraVisible = true;
spotLight.shadowMapWidth = 2048;
spotLight.shadowMapHeight = 2048;
spotLight.shadowCameraNear = 100;
spotLight.shadowCameraFar = 2000;
spotLight.shadowCameraFov = 30;
scene.add( spotLight );
var boxgeometry = new THREE.CubeGeometry(100, 200, 100);
var boxmaterial = new THREE.MeshLambertMaterial({
color: 0x0aeedf
});
var cube = new THREE.Mesh(boxgeometry, boxmaterial);
cube.position.x = 0;
cube.position.y = 100;
cube.position.z = 0;
scene.add(cube);
// RENDERER
webglRenderer = new THREE.WebGLRenderer();
webglRenderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
//webglRenderer.domElement.style.position = "relative";
webglRenderer.shadowMapEnabled = true;
webglRenderer.shadowMapSoft = true;
container.appendChild(webglRenderer.domElement);
}
function animate() {
var timer = Date.now() * 0.0002;
camera.position.x = Math.cos(timer) * 1000;
camera.position.z = Math.sin(timer) * 1000;
requestAnimationFrame(animate);
render();
}
function render() {
camera.lookAt(scene.position);
webglRenderer.render(scene, camera);
}
I have a scene with a plane, an object (cube), a spotlight (copied directly from http://threejs.org/docs/58/#Reference/Lights/SpotLight for testing purposes) , and a camera. It renders fine, except that the cube is not casting a shadow on the "ground" (plane), and the shading looks like everything has been done in a basic material. I'm using a combo of Phongs and Lamberts.
My directional light is set to castShadow = true;, and my plane is set with receiveShadow = true, along with shadow map settings. The renderer itself has shadowMapEnabled = true.
I've tried various solutions, I remember with previous versions of ThreeJS there would be external library calls depending on what you wanted to do, but I've also seen other examples on JSFiddle just calling ThreeJS by itself, as well as examples from the official site, that work fine.
Any hints/info/constructive remarks about overlooking something simple and small?
You need to set
cube.castShadow = true;
and make sure the shadow camera far plane reaches the cube.
three.js r.144

Resources