1
\$\begingroup\$

I have a three.js (REVISION: '68') issue with the lighting of THREE.Geometry objects:

Issue

I'm using the THREE.Geometry class to build up objects using vertices and faces, then I computeFaceNormals() and computeVertexNormals() before adding it to the scene.

The lighting is obviously not calculating correctly, and it looks like the light calculates before the object is moved into position (or some other issue causing all objects to have identical lighting regardless of position).

My light code is:

hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.6 );
hemiLight.color.setHSL( 0.6, 1, 0.6 );
hemiLight.groundColor.setHSL( 0.095, 1, 0.75 );
hemiLight.position.set( 0, 50, 0 );
app.scene.add( hemiLight );

dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
dirLight.color.setHSL( 0.1, 1, 0.95 );
dirLight.position.set( -1, 1.75, 1 );
dirLight.position.multiplyScalar( 50 );
app.scene.add( dirLight );

Note I also tried the documentation's sample-code for the Spotlight the issue persisted.

I am using a comparison of THREE.Geometry objects: the first with a single material for the at mesh creation, and the second with faces individually assigned a material, and passed to the mesh with THREE.MeshFaceMaterial(..) Note that I tried both single material Geometries and multi-material Geometries side-by-side and there is no difference.

Issue2

Note I changed the color of one of my test cubes from blue to green compared to the header image.

The issue does not appear to be related to the multi-material code.

I understand that the Three.Geometry class behaves differently compared to BoxGeometry, etc. (For example, computeFaceNormals() and computeVertexNormals() needs to be explicitly called for Three.Geometry, but not for BoxGeometry). I think I may be missing some other difference around flagging the material/lighting/geometry for update.

My code to create my two plain test cubes is:

var testGeo = new THREE.Geometry();

testGeo.vertices.push(new THREE.Vector3(0,0,0));
testGeo.vertices.push(new THREE.Vector3(0,20,0));
testGeo.vertices.push(new THREE.Vector3(20,20,0));
testGeo.vertices.push(new THREE.Vector3(20,0,0));
testGeo.vertices.push(new THREE.Vector3(0,0,20));
testGeo.vertices.push(new THREE.Vector3(0,20,20));
testGeo.vertices.push(new THREE.Vector3(20,20,20));
testGeo.vertices.push(new THREE.Vector3(20,0,20));

testGeo.faces.push(new THREE.Face3(0,1,2));
testGeo.faces.push(new THREE.Face3(2,3,0));

testGeo.faces.push(new THREE.Face3(2,3,7));
testGeo.faces.push(new THREE.Face3(7,6,2));

testGeo.faces.push(new THREE.Face3(0,1,5));
testGeo.faces.push(new THREE.Face3(5,4,0));

testGeo.faces.push(new THREE.Face3(0,3,4));
testGeo.faces.push(new THREE.Face3(4,7,3));

testGeo.faces.push(new THREE.Face3(1,2,6));
testGeo.faces.push(new THREE.Face3(6,5,1));

testGeo.faces.push(new THREE.Face3(4,5,6));
testGeo.faces.push(new THREE.Face3(6,7,4));


testGeo.computeFaceNormals();
testGeo.computeVertexNormals();

var solidMatA = new THREE.MeshLambertMaterial({
    color: 'blue' 
})
solidMatA.side = THREE.DoubleSide;
var cubeA = new THREE.Mesh( testGeo,  solidMatA );

cubeA.position.x = -40;
cubeA.position.y = -30;
cubeA.position.z = -30;
app.scene.add(cubeA);

var testMaterialsListB = [];
var testGeo2 = new THREE.Geometry();

testGeo2.vertices.push(new THREE.Vector3(0,0,0));
testGeo2.vertices.push(new THREE.Vector3(0,20,0));
testGeo2.vertices.push(new THREE.Vector3(20,20,0));
testGeo2.vertices.push(new THREE.Vector3(20,0,0));
testGeo2.vertices.push(new THREE.Vector3(0,0,20));
testGeo2.vertices.push(new THREE.Vector3(0,20,20));
testGeo2.vertices.push(new THREE.Vector3(20,20,20));
testGeo2.vertices.push(new THREE.Vector3(20,0,20));

testGeo2.faces.push(new THREE.Face3(0,1,2));
testGeo2.faces.push(new THREE.Face3(2,3,0));

testGeo2.faces.push(new THREE.Face3(2,3,7));
testGeo2.faces.push(new THREE.Face3(7,6,2));

testGeo2.faces.push(new THREE.Face3(0,1,5));
testGeo2.faces.push(new THREE.Face3(5,4,0));

testGeo2.faces.push(new THREE.Face3(0,3,4));
testGeo2.faces.push(new THREE.Face3(4,7,3));

testGeo2.faces.push(new THREE.Face3(1,2,6));
testGeo2.faces.push(new THREE.Face3(6,5,1));

testGeo2.faces.push(new THREE.Face3(4,5,6));
testGeo2.faces.push(new THREE.Face3(6,7,4));

for (var i = 0; i < testGeo2.faces.length; i++)
{

        var matB = new THREE.MeshLambertMaterial( {color: 'green'} );
        matB.side = THREE.DoubleSide;
        testMaterialsListB.push(matB);

}

testGeo2.computeFaceNormals();
testGeo2.computeVertexNormals();

var cubeB = new THREE.Mesh( testGeo2,  new THREE.MeshFaceMaterial( testMaterialsListB) );

cubeB.position.x = -60;
cubeB.position.y = -30;
cubeB.position.z = -30;

app.scene.add(cubeB);

Thanks!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Since you are using three.js, you might consider making your code above a Stack Snippet to allow folks to reproduce the issue easily. \$\endgroup\$ Commented Jan 28, 2015 at 17:48
  • \$\begingroup\$ I reposted the question with JSFiddle to view the issue in live code: stackoverflow.com/questions/28215201/… \$\endgroup\$
    – DavidWaugh
    Commented Jan 29, 2015 at 13:09

2 Answers 2

1
\$\begingroup\$

Your triangles might be specified in a clockwise order instead of WebGL's preferred counter-clockwise order. To verify, switch your material side to THREE.FrontSide and run it again with THREE.BackSide and see if one gives you the correct results. If the THREE.BackSide works, then you have to go back and flip your ordering in all the Face3 creations.

Regarding shading looking similar with stacked objects: three.js does not take other objects into account when lighting each object in the render stage. Let's say you wanted to make a Minecraft clone with a bunch of boxes all stacked on top of one another. Well, three.js will calculate each individual box's lighting as if it were the only one in the world. Even if you stack them densely on top of each other, they will all look the same - light on top, dark beneath.

The lighting effect you are probably wanting is achieved by ShadowMaps in three.js. Shadowing portions of an object that are obscured by other objects is a complex and expensive task which is still being hashed out by the creators of three.js.

The normal ShadowMap mode in three.js looks OK for now however and it works in most cases.

\$\endgroup\$
1
\$\begingroup\$

Just to follow up in case others stumble across this:

While I did re-arrange the vertex order to ensure 90 degree perpendicular normals, I found that didn't make a material difference to adjacent object lighting.

The answer is that three.js doesn't intrinsically calculate lighting across adjacent objects in the scene graph, and while the shadowmap feature attempts to solve this, my investigations showed that a shadowmap solution didn't really work for my use case.

Instead, I applied only 1 of the two geometry calculations to get a simulated light effect

What I had:

geo.computeFaceNormals();
geo.computeVertexNormals();

What I ended up using:

geo.computeFaceNormals();
//geo.computeVertexNormals();

With computeVertexNormals()... enter image description here

Without: enter image description here

While not a perfect light effect (very flat), it should get me by. Thanks for the help posters.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .