I have a custom mesh geometry (three js) in mapbocx. I am trying to create a light for casting directional shadows but I always end up woth the light source in the base plane (which results in no casted shadows on my objects above the plane). Does anyone know how I can move the light source so it is above the plane? I added a helper to see the scope box and I would like to move it upwards along the z-vector in the image below.
//Create a WebGLRenderer and turn on shadows in the renderer
const renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
//Add Ambient light
const amblight = new THREE.AmbientLight(0xffffff, 0.8);
amblight.position.set(8, 10, 5); //default; light shining from top
scene.add(amblight);
//Create a DirectionalLight and turn on shadows for the light
const light = new THREE.DirectionalLight(0xffffff, 0.5);
//light.position.set(8, 10, 5); //default; light shining from top
light.position.y = 2000;
light.position.x = 10;
light.position.z = 5;
light.castShadow = true; // default false
scene.add(light);
//scene.add(light.target);
//Set up shadow properties for the light
light.shadow.mapSize.width = 512;
light.shadow.mapSize.height = 512;
light.shadow.camera.left = -100;
light.shadow.camera.right = 100;
light.shadow.camera.top = 100;
light.shadow.camera.bottom = -100;
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 100; //Scope box depth
//Create a plane that receives shadows (but does not cast them)
const planeGeometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
const planeMaterial = new THREE.MeshPhongMaterial({
color: 0x808080,
opacity: 0.8,
transparent: true,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
scene.add(plane);
const meshString = result.mesh.meshString;
const mesh = meshToThreejs(rhino, meshString, THREE);
//scene.add(mesh);
//Add shadows
mesh.castShadow = true; //default is false
mesh.receiveShadow = true; //default
scene.add(mesh);
//ENd shadows
//Create a helper for the shadow camera (optional)
const helper = new THREE.CameraHelper(light.shadow.camera);
scene.add(helper);
"move the light source so it is above the plane" - It looks like you already know how to do this, just change the z number.
light.position.z = 20;
// or
light.position.set(0, 0, 20);
// Check note below - If y is up
light.position.y = 20;
// or
light.position.set(0, 20, 0);
Just a note, by default Y is up in Three.js unless you have already handled that in code not shown here. If you need to check this add the axesHelper to your scene. The X axis is red. The Y axis is green. The Z axis is blue. Make sure the camera is moved in the correct direction.
const axesHelper = new THREE.AxesHelper( 100 );
scene.add( axesHelper );
If you are still not getting shadows you could try to add a sphere like in the Three.js docs (https://threejs.org/docs/#api/en/lights/shadows/DirectionalLightShadow)
//Create a sphere that cast shadows (but does not receive them)
const sphereGeometry = new THREE.SphereGeometry( 5, 32, 32 );
const sphereMaterial = new THREE.MeshStandardMaterial( { color: 0xff0000 } );
const sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
sphere.castShadow = true; //default is false
sphere.receiveShadow = false; //default
scene.add( sphere );
If that is casting a shadow correctly then perhaps there is an issue with your mesh, or the height of those buildings is so small that the shadows are really small
Related
I have an application that includes meshes rendered with MeshPhongMaterials. They work well with the full suite of lights available in three.js.
However, I want to combine them with imported GLB/GLTF models. In order to have the models lit, I believe I must use an environment map such as the following:
function _Environment() {
const env_scene = new THREE.Scene();
const roomMaterial = new THREE.MeshStandardMaterial( { side: THREE.BackSide } );
const room = new THREE.Mesh( new THREE.BoxGeometry(), roomMaterial );
room.position.set( 0, 0, 0 );
room.scale.set( 40, 40, 40 );
env_scene.add( room );
const env_alight = new THREE.AmbientLight(0xFFFFFF, .1);
env_scene.add(env_alight);
return env_scene;
}
function main() {
canvas = document.getElementById('c');
renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true});
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputEncoding = THREE.sRGBEncoding;
const aspect = 4/3; // the canvas default
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(5, 0, 2);
camera.up.set(0,1,0);
camera.lookAt(new THREE.Vector3());
camera.updateProjectionMatrix()
const environment = new _Environment();
const pmremGenerator = new THREE.PMREMGenerator(renderer);
scene = new THREE.Scene();
scene.background = new THREE.Color(DefaultBackgroundColor);
scene.environment = pmremGenerator.fromScene(environment).texture;
dlight = new THREE.DirectionalLight(0xFFFFFF, .7);
dlight.position.set(5, 5, 10);
dlight.target.position.set(0, 0, 0);
scene.add(dlight);
scene.add(dlight.target);
alight = new THREE.AmbientLight(0xFFFFFF, .3);
scene.add(alight);
requestAnimationFrame(render);
}
However, it seems that the environment map causes the Phong materials to show up saturated and I cannot find a good combination of lights that work.
One can always convert everything to PBR, but am I missing something? Can Phong and PBR co-exist in a well lit, natural looking scene?
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>
I was just experimenting with some lightning in three.js and came across a problem which I seem to be the only on having.
The setup is simple, two PointLight, one PlaneGeometry and one BoxGeometry.
"use strict";
var scale = 0.8;
var w = parseInt('' + Math.floor(innerWidth * scale));
var h = parseInt('' + Math.floor(innerHeight * scale));
var camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 1000);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var scene = new THREE.Scene();
// init
{
scene.background = new THREE.Color(0x404040);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.BasicShadowMap;
renderer.setSize(w, h);
document.body.appendChild(renderer.domElement);
}
// plane
{
let geometry = new THREE.PlaneGeometry(40, 40, 10, 10);
let material = new THREE.MeshLambertMaterial({
color: 0x70B009,
side: THREE.DoubleSide
});
var plane = new THREE.Mesh(geometry, material);
plane.lookAt(new THREE.Vector3());
plane.rotateX(90 / 180 * Math.PI);
plane.receiveShadow = true;
scene.add(plane);
}
// box
{
let geometry = new THREE.BoxGeometry(1, 1, 1);
let material = new THREE.MeshLambertMaterial({
color: 0xFF6C00
});
var orangeCube = new THREE.Mesh(geometry, material);
orangeCube.castShadow = true;
scene.add(orangeCube);
}
// pointlights
{
var mapSize = 2 << 10;
var pointLight1 = new THREE.PointLight(0xFFFFFF, 0.6, 100);
pointLight1.castShadow = true;
pointLight1.shadow.mapSize.set(mapSize, mapSize);
scene.add(pointLight1);
var pointLight2 = new THREE.PointLight(0xFFFFFF, 0.6, 100);
pointLight2.castShadow = true;
pointLight2.shadow.mapSize.set(mapSize, mapSize);
scene.add(pointLight2);
}
// position camera, lights and box
{
pointLight1.position.set(0, 15, -15);
pointLight2.position.set(0, 15, 15);
orangeCube.position.set(0, 5, 0);
camera.position.set(10, 10, 0);
camera.lookAt(new THREE.Vector3());
}
// render once
{
renderer.render(scene, camera);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>
Which works quite well, but one problem. The lights do not eliminate the shadow projected by the other PointLight.
Does someone know how to fix this?
Thank you for your help.
As explained in this SO answer, shadows in MeshLambertMaterial are an approximation. Try MeshPhongMaterial, for example.
In MeshPhongMaterial and MeshStandardMaterial, shadows are the absence of light. If there is light from two light sources, shadow intensity can vary where the shadows overlap. See this three.js example.
three.js r.91
See this jfiddle: http://jsfiddle.net/blwoodley/5Tr4D/1/
I have a blue spot light that shines on a rotating rotating square. This casts a shadow to the underlying ground. Except that it only casts a shadow on one side of the square.
I saw this discussion: https://github.com/mrdoob/three.js/issues/3544 which indicates that face culling on planar surfaces is the cause. the recommendation is to give my square some depth, i.e. make it a cube.
I can do that with this simple example, but I'm encountering the same problem with a parametric geometry that is a surface. Is there a way to get both sides to cast a shadow without having to give my geometry a depth in doesn't have or need?
Here is the main function in the fiddle that replicates the problem with a plane:
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 = 100;
camera.lookAt({x: 0,y: 0,z: 0});
scene = new THREE.Scene();
var groundMaterial = new THREE.MeshLambertMaterial({
color: 0xffffff, side:THREE.DoubleSide
});
plane = new THREE.Mesh(new THREE.PlaneGeometry(2000,2000,10,10), groundMaterial);
plane.rotation.x = Math.PI / 2;
plane.position.y = -40;
plane.receiveShadow = true;
scene.add(plane);
var light;
light = new THREE.SpotLight(0x0000ff);
light.position.set(40, 40, 0);
light.castShadow = true;
light.shadowCameraVisible = true;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
light.position.set(24, 20, 0);
light.lookAt(plane);
light.castShadow = true;
light.angle = .8;
light.intensity = 30;
light.distance=0;
light.shadowCameraNear = 2;
light.shadowCameraFar = 100;
light.shadowCameraFov = 100;
light.shadowDarkness = 1;
light.shadowCameraVisible = true;
scene.add(light);
var planeGeo = new THREE.PlaneGeometry(20,20,20,20)
_planeMesh = new THREE.Mesh(planeGeo, new THREE.MeshLambertMaterial( { color: 0x00ff00, side:THREE.DoubleSide } ) );
_planeMesh.castShadow = true;
scene.add( _planeMesh );
// 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);
window.addEventListener('resize', onWindowResize, false);
}
Yes, this is a feature.
WebGLRenderer, by default, culls front faces when rendering shadows. This is OK, because it is assumed that objects have depth. You can cull back faces, instead, if you want:
renderer.shadowMapCullFace = THREE.CullFaceBack;
... but culling neither is not an option.
The material.side property is not taken into consideration when casting shadows.
three.js r.63
I have a cube geometry and a mesh, and i don't know how to change the width (or height... i can change x, y and z though).
Here's a snippet of what i have right now:
geometry = new THREE.CubeGeometry( 200, 200, 200 );
material = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } );
mesh = new THREE.Mesh( geometry, material );
// WebGL renderer here
function render(){
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render( scene, camera );
}
function changeStuff(){
mesh.geometry.width = 500; //Doesn't work.
mesh.width = 500; // Doesn't work.
geometry.width = 500; //Doesn't work.
mesh.position.x = 500// Works!!
render();
}
Thanks!
EDIT
Found a solution:
mesh.scale.x = 500;
Just to complete comment and solution from question (and have an answer present with example code):
// create a cube, 1 unit for width, height, depth
var geometry = new THREE.CubeGeometry(1,1,1);
// each cube side gets another color
var cubeMaterials = [
new THREE.MeshBasicMaterial({color:0x33AA55, transparent:true, opacity:0.8}),
new THREE.MeshBasicMaterial({color:0x55CC00, transparent:true, opacity:0.8}),
new THREE.MeshBasicMaterial({color:0x000000, transparent:true, opacity:0.8}),
new THREE.MeshBasicMaterial({color:0x000000, transparent:true, opacity:0.8}),
new THREE.MeshBasicMaterial({color:0x0000FF, transparent:true, opacity:0.8}),
new THREE.MeshBasicMaterial({color:0x5555AA, transparent:true, opacity:0.8}),
];
// create a MeshFaceMaterial, allows cube to have different materials on each face
var cubeMaterial = new THREE.MeshFaceMaterial(cubeMaterials);
var cube = new THREE.Mesh(geometry, cubeMaterial);
cube.position.set(0,0,0);
scene.add( cube );
cube.scale.x = 2.5; // SCALE
cube.scale.y = 2.5; // SCALE
cube.scale.z = 2.5; // SCALE
A slightly advanced, dynamic example (still the same scaling) implemented here:
You can dispose the geometry of cube and affect the new one like this :
let new_geometry = new THREE.CubeGeometry(500,200,200);
geometry.dispose();
cube.geometry = new_geometry;
Scale properties can be used to for changing width, height and and depth of cube.
//creating a cube
var geometry = new THREE.BoxGeometry(1,1,1);
var material = new THREE.MeshBasicMaterial({color:"white"});
var cube = new THREE.Mesh(geometry, material);
//changing size of cube which is created.
cube.scale.x = 30;
cube.scale.y = 30;
cube.scale.z = 30;