how is the final color of a rendered mesh is determined? - three.js

We give color while initializing a material. We also specify a color while initializing ambient and directional light sources. How is the final color of the mesh is determined.
I see no change in the final color of mesh when i change the color of the material. However the rendered color of the mesh is changing while i change the color of light sources (ambient or directional).
So
1) what is the use of specifying a color, while initializing a material ?, and
2) How is the final color of the mesh is determined
darkMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
darkMaterialL = new THREE.MeshLambertMaterial( { color: 0xffff00 } );
darkMaterialP = new THREE.MeshPhongMaterial( { color: 0xffff00 } );
var ambientLight = new THREE.AmbientLight(0x00ff00);
var light = new THREE.PointLight(0x000000);
light.position.set(0,150,100);
scene.add(ambientLight);
scene.add(light);
The above are the lights and materials i used.

I wrote a jsfiddle for you to take a look at: http://jsfiddle.net/fnR4E/
var camera, scene, renderer;
var geometry = new Array();
var material = new Array();
var mesh = new Array();
var light;
var angle = 0.1;
init();
render();
function init() {
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 5;
camera.position.y = 5;
scene = new THREE.Scene();
geometry[0] = new THREE.SphereGeometry(1, 8, 6, 0, Math.PI * 2, 0, Math.PI);
geometry[1] = new THREE.SphereGeometry(1, 8, 6, 0, Math.PI * 2, 0, Math.PI);
geometry[2] = new THREE.SphereGeometry(1, 8, 6, 0, Math.PI * 2, 0, Math.PI);
material[0] = new THREE.MeshBasicMaterial({ color: 0xff0000 });
material[1] = new THREE.MeshLambertMaterial({ ambient: 0xffffff, color: 0x00FF00 });
material[2] = new THREE.MeshPhongMaterial({ ambient: 0xffffff, color: 0xdddddd, specular: 0xFFFFFF, shininess: 15 });
mesh[0] = new THREE.Mesh(geometry[0], material[0]);
mesh[1] = new THREE.Mesh(geometry[1], material[1]);
mesh[2] = new THREE.Mesh(geometry[2], material[2]);
var ambientLight = new THREE.AmbientLight(0x007700);
var light = new THREE.PointLight(0xFFFFFF);
light.position.set(0, 2, 0);
scene.add(ambientLight);
scene.add(light);
mesh[0].position.set(-2, 0, 0);
mesh[2].position.set(2, 0, 0);
scene.add(mesh[0]);
scene.add(mesh[1]);
scene.add(mesh[2]);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function render() {
requestAnimationFrame(render);
camera.position.x = 5 * Math.cos(angle);
camera.position.z = 5 * Math.sin(angle);
camera.lookAt(new THREE.Vector3(0, 0, 0));
angle += 0.01;
renderer.render(scene, camera);
}
The first mesh is using MeshBasicMaterial which essentially means it is lit by material color alone, for proof you can change the values of ambientLight and light to whatever you want and it won't effect the rendered color of this mesh.
The following two meshes (the first is MeshLambertMaterial and the second is MeshPhongMaterial) use both lights. For additional reading on the theory behind each of the shading models (Lambertian and Phong) check out these excellent wikipedia articles:
http://en.wikipedia.org/wiki/Lambertian_reflectance
http://en.wikipedia.org/wiki/Phong_reflection_model
Here is a more "practical" explanation of what is going on (but you'll probably at least want to refer to the wiki articles for the equations that are discussed below):
The ambientLight is multiplied by the material 'ambient' value to produce the mesh ambient color. This color only gets used up to the amount specified by the diffuse color of the material. For example, if material ambient value is 0xFFFFFF and AmbientLight is 0x00FF00 then the mesh has a fully green ambient light - but, if the diffuse color of the material ('color') contains NO green color channel (e.g. 0xFF00FF) then there is no ambient light applied to the mesh. Alternatively, if there is a diffuse color of 0x007700 (half of the full green channel) then you will see ambient light on the object of the color 0x007700.
The diffuse color is denoted by the material 'color' value. This is the perceived color of the mesh. In both the Lambert and BlinnPhong shading models this color is multiplied by the dot product of the vertex or fragment normal with the light vector. In essence, this means that the more directly lit a vertex or fragment is - the closer to the full diffuse color it will be. A vertex or fragment that is not directly lit by a light source at all is black. AmbientLight sources are not included in this dot product calculation.
NOTE: Occluding meshes are not accounted for in this dot product calculation. Only the angle between the light source and the vertex or fragment is considered.
Finally, the MeshPhongMaterial uses an additional property called specular. This is the reflective light that produces the "shiny" spot on a mesh. This comes from calculating the angle of reflection against the normal from the light source. The material property 'specular' determines the color of this reflection spot. Once again, AmbientLight sources are not included in this lighting calculation.
NOTE: Once again, occluding meshes are not accounted for in this calculation.
Fixed the problem.

Related

ThreeJS - Create cube where the surfaces are transparent instead of the cube volume

I am using the following code to create this 3D transparent cube.
// Create the cube itself
const cubeGeom = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( {color: 0x00ff00, opacity:0.4, transparent:true});
const cube = new THREE.Mesh( cubeGeom, material );
// Also add a wireframe to the cube to better see the depth
const _wireframe = new THREE.EdgesGeometry( cubeGeom ); // or WireframeGeometry( geometry )
const wireframe = new THREE.LineSegments( _wireframe);
// Rotate it a little for a better vantage point
cube.rotation.set(0.2, -0.2, -0.1)
wireframe.rotation.set(0.2, -0.2, -0.1)
// add to scene
scene.add( cube )
scene.add( wireframe );
As can been seen, the cube appears as a single volume that is transparent. Instead, I would want to create a hollow cube with 6 transparent faces. Think of a cube made out of 6 transparent and colored window-panes. See this example: my desired result would be example 1 for each of the 6 faces, but now it is like example 2.
Update
I tried to create individual 'window panes'. However the behavior is not as I would expect.
I create individual panes like so:
geometry = new THREE.PlaneGeometry( 1, 1 );
material = new THREE.MeshBasicMaterial( {color: 0x00ff00, side: THREE.DoubleSide, transparent:true, opacity:0.2});
planeX = new THREE.Mesh( geometry, material);
planeY = new THREE.Mesh( geometry, material);
planeZ = new THREE.Mesh( geometry, material);
And then I add all three planes to wireframe.
Then I rotate them a little, so they intersect at different orientations.
const RAD_TO_DEG = Math.PI * 2 / 360;
planeX.rotation.y = RAD_TO_DEG * 90
planeY.rotation.x = RAD_TO_DEG * 90
Now I can see the effect of 'stacking' the panes on top of each other, however it is not as it should be.
I would instead expect something like this based on real physics (made with terrible paint-skills). That is, the color depends on the number of overlapping panes.
EDIT
When transparent panes overlap from the viewing direciton, transparancy appears to work perfectly. However, when the panes intersect it breaks.
Here I have copied the snipped provided by #Anye and added one.rotation.y = Math.PI * 0.5 and commented out two.position.set(0.5, 0.5, 0.5); so that the panes intersect.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var cube = new THREE.Group();
one = new Pane();
two = new Pane();
one.rotation.y = Math.PI * 0.5
one.position.z = 0.2;
// two.position.set(0.5, 0.5, 0.5);
cube.add(one);
cube.add(two);
cube.rotation.set(Math.PI / 4, Math.PI / 4, Math.PI / 4);
scene.add(cube);
function Pane() {
let geometry = new THREE.PlaneGeometry(1, 1);
let material = new THREE.MeshBasicMaterial({color:0x00ff00, transparent: true, opacity: 0.4});
let mesh = new THREE.Mesh(geometry, material);
return mesh;
}
camera.position.z = 2;
var animate = function () {
requestAnimationFrame( animate );
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 640px;
height: 360px;
}
<html>
<head>
<title>Demo</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
</body>
</html>
EDIT
The snipped looks pretty good; it clearly shows a different color where the panes overlap. However, it does not show this everywhere. See this image. The left is what the snippet generates, the right is what it should look like. Only the portion of overlap that is in front of the intersection shows the discoloration, while the section behind the intersection should, but does not show discoloration.
You might want to take a look at CSG, Constructive Solid Geometry. With CSG, you can create a hole in your original cube using a boolean. To start, you could take a look at this quick tutorial. Below are some examples of what you can do with CSG.
var cube = new CSG.cube();
var sphere = CSG.sphere({radius: 1.3, stacks: 16});
var geometry = cube.subtract(sphere);
=>
CSG, though, has some limitations, since it isn't made specifically for three.js. A cheap alternative would be to create six individual translucent panes, and format them to create a cube. Then you could group them:
var group = new THREE.Group();
group.add(pane1);
group.add(pane2);
group.add(pane3);
group.add(pane4);
group.add(pane5);
group.add(pane6);
Update
Something may be wrong with your code, which is why it isn't shading accordingly for you. See this minimal example, which shows how the panes shade appropriately based on overlaps.
Update 2
I updated the snippet so the 2 panes aren't touching at all... I am still able to see the shading. Maybe if you were to try to reproduce this example?
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var cube = new THREE.Group();
one = new Pane();
two = new Pane();
one.rotation.y = Math.PI * 0.5;
one.position.z = 0.2;
cube.add(one);
cube.add(two);
cube.rotation.set(Math.PI / 4, Math.PI / 4, Math.PI / 4);
scene.add(cube);
function Pane() {
let geometry = new THREE.PlaneGeometry(1, 1);
let material = new THREE.MeshBasicMaterial({color:0x00ff00, transparent: true, opacity: 0.4});
material.depthWrite = false
let mesh = new THREE.Mesh(geometry, material);
return mesh;
}
camera.position.z = 2;
var animate = function () {
requestAnimationFrame( animate );
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 640px;
height: 360px;
}
<html>
<head>
<title>Demo</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
</body>
</html>
Update 3
Below is a screenshot of what I see in your snippet... Seems to be working fine...
You're experiencing one of my first head-scratchers:
ShaderMaterial transparency
As the answer to that question states, the three.js transparency system performs order-dependent transparency. Normally, it will take whichever object is closest to the camera (by mesh position), but because all of your planes are centered at the same point, there is no winner, so you get some strange transparency effects.
If you move the plane meshes out to form the actual sides of the box, then you should see the effect you're looking for. But that won't be the end of strange transparency effects, And you would need to implement your own Order-Independent Transparency (or find an extension library that does it for you) to achieve more physically-accurate transparency effects.

How to put a transparent color mask on a 3D object in three.js?

I've been trying to 'submerge' a 3D object in a semi-transparent 3D plane of water (without the whole water plane showing), and after having experimented with custom blending modes for hours, I don't really get how to do it.
Fiddle here: https://jsfiddle.net/mglonnro/p2ju4qbk/34/
var camera, scene, renderer, geometry, material, mesh,
surface_geometry, surface_material, surface_mesh,
bottom_geometry, bottom_material, bottom_mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 200, 500);
camera.lookAt(0, 0, 0);
scene.add(camera);
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
bottom_geometry = new THREE.PlaneBufferGeometry(10000, 10000);
bottom_material = new THREE.MeshBasicMaterial({
color: 0xFFAAAA,
side: THREE.DoubleSide
});
bottom_mesh = new THREE.Mesh(bottom_geometry, bottom_material);
bottom_mesh.rotation.set(Math.PI / 2, 0, 0);
bottom_mesh.position.set(0, -200, 0);
scene.add(bottom_mesh);
surface_geometry = new THREE.PlaneBufferGeometry(400, 400);
surface_material = new THREE.MeshBasicMaterial({
color: 0x0000ff,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.6
});
surface_mesh = new THREE.Mesh(surface_geometry, surface_material);
surface_mesh.rotation.set(Math.PI / 2, 0, 0);
scene.add(surface_mesh);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
}
The cube is submerged, as it should be, and the parts covered by the transparent water look like I want them to look.
The problem, however, is that I want ONLY the cube and its submerged parts to be rendered, NOT the rest of the water plane.
In other words:
There are three objects in the scene:
the redish "bottom" farthest away
the cube, partly above, partly
below the water
the water
Is there some way to blend these together so that the water pixels are rendered only when they are on top of a cube pixel, not when they are just on top of the background/bottom?
EDIT: SOLUTION
Add stencil write functionality to the cube:
const stencilId = 1;
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial({
stencilWrite: true,
stencilFunc: THREE.AlwaysStencilFunc,
stencilZPass: THREE.ReplaceStencilOp,
stencilRef: stencilId
});
Add stencil test functionality to the surface:
surface_material = new THREE.MeshBasicMaterial({
color: 0x0000ff,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.6,
stencilWrite: true,
stencilFunc: THREE.EqualStencilFunc,
stencilRef: stencilId
});
Realize that the three.js version in jsfiddle is too old to support stencils and move to codepen :)
Stencil Test is a natural way to achieve that.
All objects which should have water on top of them write some stencil value.
Water plane has stencil test set to this value.
three.js has stencil example, but it uses IncrementWrap and DecrementWrap logic which is not needed for your case.
I recommend trying ReplaceStencilOp for cube and EqualStencilFunc for water.

Why doesn't my directional light affect the whole scene?

Please see this fiddle - http://jsfiddle.net/vnr2v6mL/
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.BoxGeometry(100, 100, 1);
var material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
var plane = new THREE.Mesh(geometry, material);
scene.add(plane);
camera.position.z = 5;
//scene.add(new THREE.AmbientLight(0xdddddd));
var dl = new THREE.DirectionalLight(0x0000ff, 1.0);
dl.position.set(0, 0, 10);
scene.add(dl);
scene.add( new THREE.DirectionalLightHelper(dl, 2.5) );
function render() {
requestAnimationFrame( render );
renderer.render( scene, camera );
}
render();
$(document).ready(function() {
$(document).bind("mousewheel DOMMouseScroll", function(e) {
var delta = e.type === 'DOMMouseScroll' ? -e.originalEvent.detail : e.originalEvent.wheelDelta;
camera.position.z += delta / 120;
});
});
It is my understanding that the lighting should be infinite and parallel, so why does it only light up a circle in the middle of my scene?
Thanks in advance.
You are using MeshPhongMaterial. What you are seeing is the specular highlight only.
Your light is blue. However, your material is green, so it reflects only green light. Therefore, there is no diffuse light reflected from the material.
The material specular reflectance is, by default, 0x111111. So all colors are reflected specularly. Since your light is blue, you get a blue light reflected specularly. In other words, a blue "hot spot".
Consider using a white light, 0xffffff, and adjust the light intensity.
Fiddle: http://jsfiddle.net/vnr2v6mL/1/
three.js r.70

Superimposing of color

I want to paint cubes red color by means of a mouse. But thus the green cube (at the left) becomes not red, but black. The white cube (on the right) is colored normally. What to do?
example here
// init
var material = new THREE.MeshLambertMaterial({
color: 0x00ff00,
side: THREE.DoubleSide,
vertexColors: THREE.FaceColors
});
var geometry = new THREE.BoxGeometry(100, 100, 100, 4, 4, 4);
var Cube = new THREE.Mesh(geometry, material);
Cube.position.x = -100;
scene.add(Cube);
objects.push(Cube);
var material = new THREE.MeshLambertMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
vertexColors: THREE.FaceColors
});
var geometry = new THREE.BoxGeometry(100, 100, 100, 4, 4, 4);
var Cube = new THREE.Mesh(geometry, material);
Cube.position.x = 100;
scene.add(Cube);
objects.push(Cube);
document.addEventListener('mousedown', onDocumentMouseDown, false);
//
function onDocumentMouseDown(event) {
var vector = new THREE.Vector3(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
vector.unproject(camera);
raycaster.set(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(objects);
if (intersects.length > 0) {
var index = intersects[0].faceIndex;
// change the color of the closest face.
intersects[0].face.color = color;
intersects[0].object.geometry.colorsNeedUpdate = true;
}
}
In your example, the final color is the component-wise product of the material color ( 0x00ff00 ) and the face color ( 0xff0000 ), which results in black ( 0x000000 ).
For that reason, when you have face colors, it is a good idea to set the material color to white.
three.js r.69
I suspect your lighting model is the cause of this. If you try painting the dark sides of the white cube, you will also see black faces. There is a large difference between white ffffff and green 00ff00. Your white cube even appears blue due to the hemi light.
Try using a point light instead of your hemi light and see if it makes a difference.

ThreeJS texture reversed

I'm new to threejs just doing a basic cube with a texture to the backside. I have words on colour sides to the texture. However the words come out mirrored like. How can I get them to come out correctly.
You can negatively scale your cube to undo the mirror effect, like this:
cube.scale.x = -1;
There are two things you can do:
Reverse or rotate your UV coordinates on each face on the cube until you get the desired result. This is easy since the UV coordinates of a cube are usually 0.0 and 1.0.
Use an image package to rotate the textures as you want them.
I think I had the same problem as you with regards to texturing a cube.
As I understand it all surfaces come out correct orientation except the backside. The way i got around this was to place the textures on the cube per face and then alter the UV mapping of the back face.
This solved the problem of the back face being oriented incorrectly and also as a result of UV mapping I am now able to put textures on irregular faces to like pyramids etc.
Here is the solution by changing the UV of the backface. Just replace the loaded texture with a local texture cut and paste into notepad and save as html file and your good to go.
<html>
<head>
</head> <body> <script src="js/three.min.js"></script> <script> var
scene, camera, renderer; var geometry, material; var modarray=[];
var material=[]; var rotation=0; init(); animate(); function init()
{
renderer = new THREE.WebGLRenderer();
//renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize( window.innerWidth, 100 );
document.body.appendChild( renderer.domElement );
/////////// // Camera// ///////////
camera = new THREE.OrthographicCamera( window.innerWidth / - 2,
window.innerWidth / 2, 100 / 2, 100 / - 2, - 500, 1000 );
camera.position.z = 2000; camera.position.y = 0; camera.position.x = 0; scene= new THREE.Scene();
geometry = new THREE.BoxGeometry( 50, 50, 50 ); geometry2 = new THREE.BoxGeometry( 50, 50, 50 );
/////////////////////////////// // Store Materials for blocks//
/////////////////////////////// var bricks; material[0] = new
THREE.MeshPhongMaterial( { map:
THREE.ImageUtils.loadTexture('10.png') } );
var basex=-455; //////////////////////////////////////////////////
// Vector array to hold where UV will be placed //
////////////////////////////////////////////////// bricks = [new
THREE.Vector2(1, 0), new THREE.Vector2(1, 1), new
THREE.Vector2(0, 1), new THREE.Vector3(0, 0)];
///////////////////////////////////////////////////// // choose what
face this eccects from vertex array // // in this case backside
// // choose the orientation of the triangles //
/////////////////////////////////////////////////////
geometry.faceVertexUvs[0][10] = [ bricks[0], bricks[1], bricks[3]];
geometry.faceVertexUvs[0][11] = [ bricks[1], bricks[2], bricks[3]];
modarray[0] = new THREE.Mesh( geometry, material[0]); modarray[1] = new THREE.Mesh( geometry2, material[0]);
modarray[0].position.x=basex; modarray[0].position.z=1000;
modarray[0].position.y=0;
scene.add(modarray[0]);
modarray[1].position.x=basex+65; modarray[1].position.z=1000;
modarray[1].position.y=0;
scene.add(modarray[0]); scene.add(modarray[1]);
////////// // LIGHT// ////////// var light2 = new
THREE.AmbientLight(0xffffff); light2.position.set(0,100,2000);
scene.add(light2);
}
//////////////////// // Animation Loop // ///////////////////
function animate() {
requestAnimationFrame( animate ); var flag=0;
for(n=0; n<2; n++) {
modarray[n].rotation.x=rotation;
} rotation+=0.03;
renderer.render( scene, camera );
}
</script> <p>The cube on the left is with UV mapping to correct the
back surface.
The cube on the right is without the UV mapping.</p> </body>
</html>

Resources