I'm trying to render shadows using the latest version of three.js (r102), and I'm not sure what I am doing wrong. I am using MeshPhongMaterial with castShadow and receiveShadow set on all relevant meshes, and a directional light facing towards the scene content. Could someone take a look at this and help me figure out how to get these shadows working? Thanks!
Live demo (toggle shadows in the menu):
https://argonjs.github.io/three-web-layer/
Source:
https://github.com/argonjs/three-web-layer
If you add a small cube in front of your WebLayer3D, it correctly casts shadows on rendered DOM layers:
//in app.ts just after light with shadow camera:
let geometryBox = new THREE.BoxBufferGeometry( 0.01, 0.01, 0.01 )
let materialRed = new THREE.MeshPhongMaterial( {color: 0xff0000} )
let cubeSmall = new THREE.Mesh( geometryBox, materialRed )
cubeSmall.position.set( 0.1, -0.03, 0.1 )
cubeSmall.castShadow = true
cubeSmall.receiveShadow = true
scene.add( cubeSmall )
So, only the planes produced by the WebLayer3D do not cast shadows, the setup for the scene / camera / light is correct.
Update: the explanation below is not the reason, see the solution with material.shadowSide in another answer.
If you look at the tree of objects in three.js realm (i.e. traversing through children[]), starting with todoLayer - a lot of them will have castShadow at "false". You will have to re-think your strategy here. Also note, castShadow=false on parent Object3D turns it off for the children.
I figured it out after the hint from Alex (thanks Alex!).
Basically, as strange as it seems, a plane in three.js will not cast shadows unless it is double-sided (Update: Or unless material.shadowSide is set to THREE.FrontSide). Once I set THREE.DoubleSide on the plane material, it worked as expected. Basically, for a textured plane to cast shadows, the following is needed (as of three.js r102):
var mesh = new THREE.Mesh(
new THREE.PlaneGeometry(1,1,2,2),
new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide, // important!
alphaTest: 0.1,
})
mesh.customDepthMaterial = new THREE.MeshDepthMaterial({
map: texture
depthPacking: THREE.RGBADepthPacking,
alphaTest: 0.1
})
I also had to adjust the light's shadow bias in order to eliminate artifacts.
Related
I have two meshes on my scene. One cylinder and one classic plane in the middle.
I applied a png texture on my cylinder so we can see through. It seems to work for the cylinder.
On this screenshot you'll easily see my issue : I don't understand why my image is not visible behind my cylinder.
The code I used for my cylinder :
myCylinderMesh.material.transparent = true;
myCylinderMesh.material.side = THREE.DoubleSide;
How can I manage to see the part of the image hidden behind the cylinder ?
EDIT 1 :
I added the code that #ScieCode sent me :
myCylinderMesh.material.alphaTest = 0.5;
Here's the result :
It works better : now I can see the part missing of my image. But there's one thing missing : the opacity of my cylinder. I'm supposed to see my image behind the letters too.
Currently I have this opacity :
myCylinderMesh.material.opacity = 0.7;
Do you know what I am missing ? Thanks
EDIT 2 :
Here's the code for my two meshes :
Cylinder :
geoCylinder = new THREE.CylinderBufferGeometry( 0.4, 0.4, 2*Math.PI*0.4/(2048/128), 64, 1, true );
matCylinder = new THREE.MeshBasicMaterial( { map:texture, transparent:true, color:0x000000, alphaTest: 0.5, opacity: 0.6, side: THREE.DoubleSide } );
meshCylinder = new THREE.Mesh( geoCylinder, matCylinder );
Plane :
geoPlane = new THREE.PlaneBufferGeometry( 0.8, 0.8 );
matPlane = new THREE.MeshBasicMaterial( { map: texturePlane, transparent:true} );
meshPlane = new THREE.Mesh( geoPlane, matPlane );
This behavior happens because of how transparency rendering works internally. Transparent objects need to be sorted/rendered separately from opaque objects. This assures that objects will render as expected on the final image. (not always, though)
The problem here is that your plane geometry is inside the cylinder geometry, when sorting it will either be rendered first or later. Which, in turn, causes these artifacts you are experiencing here. The whole transparency render is a lot more complex than what I'm making it to be.
Since your plane object doesn't need to be translucent, you can simply set the alphaTest property of its material. Which will only render the fragment pixels with alpha greater than this value. This will also prevent that object from being considered transparent and it will always be rendered first, fixing the artifacts in your scene.
JSFiddle
Additional info: When using a transparent material with DoubleSide, you might experience self transparency problems. This happens for the same reason I just explained, but between faces of the same object. A possible solution for this problem is to set depthWrite = false, this prevent the object from writing to the depth buffer. So every face will get rendered, disregarding if another face occludes it.
I want a material with:
Textures
Not receiving lights
Receiving shadows
I tried with the following library materials:
MeshBasicMaterial: Does not support shadows
MeshLamberMaterial: If you disable lights (material.lights = false) it also disables shadows
ShadowMaterial: Does not support textures
Is a custom ShaderMaterial the only way to achieve it?
In three.js, as in real life, shadows are the absence of light. So for a built-in three.js material to receive shadows, it must respond to light.
However, you can modify a built-in material's shader to achieve the effect you want with just a few lines of code. Here is an example to get you started:
THREE.ShaderLib[ 'lambert' ].fragmentShader = THREE.ShaderLib[ 'lambert' ].fragmentShader.replace(
`vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;`,
`#ifndef CUSTOM
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;
#else
vec3 outgoingLight = diffuseColor.rgb * ( 1.0 - 0.5 * ( 1.0 - getShadowMask() ) ); // shadow intensity hardwired to 0.5 here
#endif`
);
Then, to use it:
var material = new THREE.MeshLambertMaterial( { map: texture } );
material.defines = material.defines || {};
material.defines.CUSTOM = "";
In spite of its name, this material will behave like MeshBasicMaterial, but will darken when it is in shadow. And furthermore, MeshLambertMaterial will still work as expected.
three.js r.88
In a past version, maybe .72, you could cast and receive shadows with the MeshBasicMaterial. It was simple. Then the concept of ambient light changed in three.js and MeshBasicMaterial could no longer support shadows.
THREE.ShadowMaterial was introduced to compensate for the limitation. It works great! But it really only works on PlaneGeometry because by it's nature, THREE.ShadowMaterial is transparent, so the shadows cast inside and outside the object3d with ShadowMaterial are seen.
The idea is that you use two meshes, one with the MeshBasicMaterial, and the other with ShadowMaterial.
shape = new THREE.BoxGeometry(1,1,1),
basicMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000
}),
mesh = new THREE.Mesh(shape, basicMaterial),
shadowMaterial = new THREE.ShadowMaterial({opacity:.2}),
mesh2 = new THREE.Mesh(shape, shadowMaterial),
You can see an example of the problem, here: https://jsfiddle.net/7d47oLkh/
The shadows cast at the bottom of the box are incorrect for the use-case.
The answer is, NO. There is no easy way to support full-bright basic materials that also accept and cast a shadow in three.js.
In my current project, I need a way to outline a mesh.This color outline will represent the object's current state, relevant for me.
The problem is that it is a custom mesh, loaded using JSONLoader.
I've tried different approaches, following (mainly) these 2 examples: https://stemkoski.github.io/Three.js/Outline.html and
THREEx.geometricglow. In both cases, I scale the mesh outline to a bit bigger than the original. My main problem is that scaling equally in all axis will not cover my object the way I intended to.
Here is the code I'm using:
var outlineMaterial2 = new THREE.MeshBasicMaterial( { color: 0x00ff00, side: THREE.BackSide, transparent: true, opacity:0.5 } );
var outlineMesh2 = new THREE.Mesh( object.geometry, outlineMaterial2 );
outlineMesh2.position.copy(object.position);
outlineMesh2.scale.copy(object.scale);
outlineMesh2.scale.multiplyScalar(1.1)
scene.add( outlineMesh2 );
`
With a simple cube mesh, the outline will be good.
But with my custom mesh, the scale will not fit the shape correctly.
Here is a image demonstrating: http://s13.postimg.org/syujtd75z/print1.png
Also, using Stemkoski approach, the outlining mesh will also show in front of the object, not just outline (as seen in the above picture).
My question is: How should I resize the mesh? For what I know, it might have something to do with face normals.
Thanks for your time.
I would like somebody to explain me how I can achieve the blue semi-transparent intermittent sphere of this example: (the one next to the intermittent red sphere)
http://threejs.org/examples/webgl_materials.html
I believe in first place that this is a matter of using the right material with the right settings (specially because the example is about materials) but not sure anyway.
Hopefully you do not feel my question does not deserve to be made here. I was trying to analyze it but definitely it is written in a non-friendly way for newbies, and I've not been able to separate this part from the rest, not I find an explanation anywhere else.
To create, for example, a partially transparent blue sphere, you could try:
var sphereGeom = new THREE.SphereGeometry( 40, 32, 16 );
var blueMaterial = new THREE.MeshBasicMaterial( { color: 0x0000ff, transparent: true, opacity: 0.5 } );
var sphere = new THREE.Mesh( sphereGeom, blueMaterial );
For more examples of creating semi-transparent materials, check out
http://stemkoski.github.io/Three.js/Translucence.html
If you want the sphere to fade in and out, you can change the transparency in your update or render function -- make the sphere a global object, also create a (global) clock object to keep track of the time in your initialization, for example, with
clock = new THREE.Clock();
and then in your update, you could, for example, write
sphere.material.opacity = 0.5 * (1 + Math.sin( clock.getElapsedTime() ) );
Does someone know how to make the spotlight reflection here: http://web251.merkur.ibone.ch/webgl/three/ look like the one here: http://web251.merkur.ibone.ch/webgl ? i.e. that it gets reflected to the camera? It's weird that it doesn't do so automatically. If you move around the planet with the mouse you'll notice. In this scene everything is static but the camera, and with camera movement also the eye E gets moved around, right? So what I'd expect is the spotLight reflection on the planet is rerendered/recalculated all the time, e.g. with Blinn's Halfvector, leading to a reflection on the planet inbetween E and the spotlight.
Help is really appreciated, we've searched for hours but couldn't find a clue what was wrong with our code!
Thanks in advance
Doidel
The first bit of starting code I used for playing with this was:
http://mrdoob.github.com/three.js/examples/webgl_materials_shaders.html
The key is adding a specularity map with the specularMap property for the (Phong) Material.
Such can be done as follows:
var MySpecularMap = THREE.ImageUtils.loadTexture( "MySpecularImage.jpg" );
var Color = THREE.ImageUtils.loadTexture( "MyColorImage.jpg" );
var mappedTexture = new THREE.MeshPhongMaterial( { color: 0xffffff, map: Color, specular: 0xffffff, specularMap: MymapSpecular} );
sphere = new THREE.SphereGeometry( 600, 32, 32 );
globe = new THREE.Mesh( sphere, mappedTexture );
scene.add( globe );
Also, for this type of demo OrbitControls seem to be the best.
Here's a finished example with {ColorMap, SpecularMap, BumpMap, Clouds, SkyDome}:
http://randompast.github.io/randomtests/three.js/earth/1/index.html