Three.js - Why shadow only show in a small area - javascript

I imported a model and found that shadow only show in a small area(green area in the picture). What can I do to let all objects show their shadow.
Here is my code.
light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
light.castShadow = true;
light.shadow.camera.near = 0.01; // same as the camera
light.shadow.camera.far = 1000; // same as the camera
light.shadow.camera.fov = 50; // same as the camera
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
scene.add( light );
Thanks!!
EDIT:
I add gui to change light.shadow.camera.top / light.shadow.camera.bottom / light.shadow.camera.left / light.shadow.camera.right, but nothing happens.
var gui = new dat.GUI();
gui.add( light.shadow.camera, 'top' ).min( 1 ).max( 100000 ).onChange( function ( value ) {
light.shadow.camera.bottom = -value;
light.shadow.camera.left = value;
light.shadow.camera.right = -value;
});

That's happening because directional lights use an OrthographicCamera to draw a shadowmap to cast shadows. If there are objects outside the view of this camera, it won't be able to calculate their shadows, and will have the effect you're seeing outside the green box. If you want to extend the area that this camera covers, you can modify the .left .right .top .bottom properties of this shadow camera to cover your entire scene. I'm using a box of 100 units in the example below;
var side = 100;
light.shadow.camera.top = side;
light.shadow.camera.bottom = -side;
light.shadow.camera.left = side;
light.shadow.camera.right = -side;
... but you can change the dimensions to whatever you need. Keep in mind that .fov does nothing in your example code because ortho cameras don't use the field-of-view property.

All right, it would be great if you could show me a live example of your code as I have fixed this before for a project in boxelizer.com
The issue can be fixed by changing the light.shadow.camera.left, right, bottom and top properties as suggested before, however we might not be able to see what the effective area of our shadow might be and hence we might be really close to fixing it but not at all. My suggestion is using a helper momentarily just to see the effective shadow area of your light with:
var shadowHelper = new THREE.CameraHelper( light.shadow.camera );
scene.add( shadowHelper );
You are also welcome to see all the code I used in the link I referenced to.

Here's a function I use to set the shadowMap dimensions and coverage area:
//sz is the size in world units that the shadow should cover. (area)
// mapSize is the width,height of the texture to be used for shadowmap (resolution)
var setShadowSize=(light1, sz, mapSz)=>{
light1.shadow.camera.left = sz;
light1.shadow.camera.bottom = sz;
light1.shadow.camera.right = -sz;
light1.shadow.camera.top = -sz;
if(mapSz){
light1.shadow.mapSize.set(mapSz,mapSz)
}
}
setShadowSize(myLight,15.0,1024);

Related

Problem with mouse-camera raycasting in threejs

I try to make a very simple program with a clickable 3d object in Threejs. My code is based on
https://threejs.org/examples/#webgl_interactive_cubes
It works when I click on the object (although the resulting array contains the object twice, I assume because the ray intersects it when entering it and when exiting it).
But when I click in an area just surrounding the object raycaster.intersectObjects returns the object although it should return an empty array.
What am I doing wrong? Why is the object also intersected when I click next to it and not on it? And is an object always included twice in the intersect array because of the ray entering and exiting it?
A working example of the code is here:
https://codepen.io/ettir_deul/pen/PoGLNZx
(open the console to see the intersect array after you clicked on the screen)
And the code looks like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="libs/three.min.js.r116.1"></script>
</head>
<body>
<div id="threejsCanvas"></div>
<script>
let scene, center, camera, renderer, raycaster;
const mouse = new THREE.Vector2();
const threejsCanvas = document.getElementById("threejsCanvas");
init3d();
function init3d(){
scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAEE);
center = new THREE.Vector3(0, 0, 0);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(center.x, center.y, center.z+1);
camera.lookAt(center);
buttonMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0.2), new THREE.MeshBasicMaterial({map : null, color: 0x008844, wireframe: false, side: THREE.DoubleSide}));
buttonMesh.position.set(center.x, center.y, center.z);
scene.add(buttonMesh);
buttonMesh.objId = "buttonMesh";
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
threejsCanvas.appendChild(renderer.domElement);
renderer.render(scene, camera);
raycaster = new THREE.Raycaster();
document.addEventListener('click', onMouseClick, false);
}
function onMouseClick(event){
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
console.log(intersects);
}
</script>
</body>
</html>
the reason for getting the object twice is the
side: THREE.DoubleSide
in the material. You can use THREE.FrontSide
The reason for picking the object when the mouse is close is the canvas and window dimensions not being equal.
If you add :
body{
padding: 0;
margin: 0;
}
in the CSS it is fixed.
Here is a codepen that works.
Two results - is OK. You got 2 faces of mesh. Each one intersects ray. Check result intersection properties: face, faceIndex, point.
Raycaster is not precise and pretty slow. You can use its 'params' to change precision (see https://threejs.org/docs/#api/en/core/Raycaster). I suggest using GPUPicker. It is precise and super fast. Check here:
https://github.com/brianxu/GPUPicker
Edit: Yes you can change material to avoid 2 intersections. But it is often it is not acceptable for surfaces.
Edit: Yes precision settings affects only points and line picking. But GPUPicker can pick exact rendering result of points (shader effects, symbols with transparency). But standard raycaster - can't.

How to create hotspots in 3d enviroment using threejs?

I'm a beginner to three.js.I'm trying to build something similar to this https://virtualshowroom.nissan.in/car-selected.html?selectedCar=ext360_deep_blue_pearl. I built everything using three.js, but I'm not able to figure out how to create a hotspot(like the red dot in the above link) and show pop up when you click on it. below is my project code, let me know if anything else is required.
<html>
<head>
<title>My first three.js app</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<h1></h1>
<script src="./three.js"></script>
<script type="module">
import { GLTFLoader } from 'https://threejs.org/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
var renderer,scene,camera;
scene = new THREE.Scene();
scene.background = new THREE.Color(0xfff6e6)
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var loader = new GLTFLoader();
var hlight = new THREE.AmbientLight(0x404040, 100)
scene.add(hlight)
var directionalLight = new THREE.DirectionalLight(0xffffff, 100)
directionalLight.position.set(0,1,0)
directionalLight.castShadow = true
scene.add(directionalLight)
var light = new THREE.PointLight(0xffffff, 10)
light.position.set(0, 300, 500)
scene.add(light)
var light2 = new THREE.PointLight(0xffffff, 10)
light2.position.set(500, 100, 0)
scene.add(light2)
var light3 = new THREE.PointLight(0xffffff, 10)
light3.position.set(0, 100, -500)
scene.add(light3)
var light4 = new THREE.PointLight(0xffffff, 10)
light4.position.set(-5000, 300, 0)
scene.add(light4)
var controls = new OrbitControls(camera, renderer.domElement);
document.body.appendChild(renderer.domElement)
var loader = new GLTFLoader();
loader.load( './scene.gltf', function ( gltf )
{
scene.add( gltf.scene );
}, undefined, function ( error ) { console.error( error ); } );
// load a image resource
camera.position.z = 5;
var animate = function () {
requestAnimationFrame( animate );
renderer.render( scene, camera );
};
animate();
</script>
</body>
</html>
Those “hotspots” as you call them are Annotations where the annotation content is basically pure HTML.
The tutorial in the link is probably the best step-by-step readiness you can follow to learn how to do it in your scene.
I can give a walkthrough on the steps required to get the desired effect since I have done it a few times myself.
define a 3d point in your scene where the hotspot should be. You can optionally nest this in a an other Object3D to make sure it scales, moves and rotates with the model / parent.
Add a plane to this point load a image texture to this plane. and there you have your visible hotspot
update the hotspots to make sure they are always looking at the camera by using the lookAt function.
when the user clicks the screen cast a raycast against all the hotspots you have in your scene. Easiest way to do this is by storing all your hotspots in an array.
When the raycast hits a hotspot get the location either of the hitpoint or the hotspots location. Transform that to screen coordinates. Search on stackoverflow how to do this. I am sure there is a post about this.
Final step display your html on the correct location you obtained from the previous step.
The advantage of this method is that the hotspot will integrate nicely with the model in your scene. Since html based hotspots will always be on top of the scene.
That is about all that is to it. Let me know if you need any further clarification!

How to change the position of a vertex of a given threejs.Box3?

My aim here is to create a box, then change one of its vertex's position through geometry, and then display it.
var anchor = new THREE.Vector3( 300, 300, 3 );
var cursor = new THREE.Vector3( 500, 550, 50 );
var box2 = new THREE.Box3( anchor, cursor );
var box2Helper = new THREE.Box3Helper( box2 );
box2Helper.geometry.vetices[0] = new THREE.Vector3( 20, 20, 20 );
box2Helper.geometry.verticesNeedUpdate = true;
scene.add( box2Helper );
This code works fine without those two geometry lines.
What is the way to change the position of the existing vertexes of the box through geometry class?
I think there may be some confusion here about what the Box3 class is... It's just a 3d boundingbox.. To make a deformable box with vertices, you want a new THREE.Mesh(new THREE.BoxGeometry(1,1,1), new THREE.MeshStandardMaterial() )
then you can modify the mesh.geometry.vertices like you are doing..
THREE.Box3 is more of a utility class used to represent bounding boxes of things.. it's used for frustum culling and stuff.

Rendering single mesh color change

I have a grid that contains boxes, very similar to http://threejs.org/examples/#canvas_interactive_voxelpainter. Now I initiated a hover state when a box on the scene is mouseover it turns the background gray. Which is great! Except when I multiple "box" on the grid and I go to change the material background color of the hovered item, it renders all of the "box's" with a gray background.
Heres what I am doing:
var voxel = new THREE.Mesh( this.cubeGeometry, this.cubeMaterial );
voxel.position.copy( intersect.point ).add( intersect.face.normal );
voxel.position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
this.scene.add( voxel );
this.blocks.push( voxel );
var domEvents = new THREEx.DomEvents(this.camera, this.renderer.domElement)
// DOM events for inside 3d rendering
domEvents.addEventListener(voxel, 'mouseover', this.onDocumentMouseOverCube.bind(this),false);
domEvents.addEventListener(voxel, 'mouseout', this.onDocumentMouseOutCube.bind(this),false);
Here we create our box - we than give it eventListeners for that specific mesh. Once this mesh is hovered over, our mouseover is executed:
this.mouse.x = ( event.origDomEvent.clientX / this.renderer.domElement.width ) * 2 - 1;
this.mouse.y = - ( event.origDomEvent.clientY / this.renderer.domElement.height ) * 2 + 1;
this.raycaster.setFromCamera( this.mouse, this.camera );
var intersects = this.raycaster.intersectObjects( this.blocks );
if ( intersects.length > 0 ) {
var intersect = intersects[ 0 ];
if ( intersect.object != this.plane ) {
console.log(intersect.object);
// update color on hover
intersect.object.material.color.setHex(this.colorHover);
console.log("hover color");
this.render();
}
}
Now this works great, the only issue is - this.render() is called (this.renderer.render( this.scene, this.camera )) like it should be. But when I have multiple box's on it goes ahead and changes every single background color of each box I have even logged all my objects to confirm object.material.color is the gray hex for only one box and that not all of the box's are being set, which proves to be true. I am sending the correct data over. So I am assuming it has to do with the rendering of the actual engine?
Suggestions?
There is only one instance of the material, which is shared among the meshes. The easy solution is to clone the material for each mesh:
var voxel = new THREE.Mesh( this.cubeGeometry, this.cubeMaterial.clone() );
Now every box accepts its own color.
I dont know if this still applies but thinking about performance you would want to go with a custom shader material, because the attributes and vertex/fragment programs are copied by reference then. See this post Three.js, sharing ShaderMaterial between meshes but with different uniform sets.
Example code:
var phongShader = THREE.ShaderLib.phong;
this.shaderMaterial = new THREE.ShaderMaterial({
uniforms: phongShader.uniforms,
vertexShader: phongShader.vertexShader,
fragmentShader: phongShader.fragmentShader,
lights:true
});
var voxel = new THREE.Mesh( this.cubeGeometry, this.shaderMaterial.clone() );
And then you change the color via uniforms like so:
intersect.object.material.uniforms.diffuse.value.setHex( this.colorHover );
Three.js r.71
PS: cloning the default Phong material in r.71 also shows only one programm in the renderer info for me, so maybe Three.js is optimizing this internally.

Three.js: How to update wireframeHelpers position?

I added a WireframeHelper to my scene like this:
var wfh = new THREE.WireframeHelper(mesh, 0x000000);
wfh.material.linewidth = 2;
scene.add(wfh);
How do I change the wireframeHelpers position?
I tried:
wfh.position.x += 10;
and
wfh.matrixAutoUpdate = true
wfh.applyMatrix(new THREE.Matrix4().makeTranslation(10, 0, 0));
The second one moves my wireframeHelper but not to the position it should move.
The WireframeHelper will move, automatically, when the mesh is moved. Move the mesh, instead.
If you want to see only the helper, you can set mesh.visible = false, but the mesh must be included in the scene graph.
three.js r.65

Categories