THREE.MeshBasicMaterial renders, but THREE.MeshLambertMaterial does not - javascript

I'm working on a project that makes a sort of randomized sheet. It stores arrays of x, y, and z coordinates and draws triangles between the points. You can see this working pretty well in this screenshot.
I used MeshBasicMaterial to make that sheet, but wanted to switch to MeshLambertMaterial to take advantage of lighting. When I try this, I get a sheet that looks like this.
This is the working Basic Mesh code on green tiles:
for(j = 0; j < h-1; j++) { //h is the number of tiles vertically
for(i = 0; i < w-1; i++) { //w is the number of tiles horizontally
o = ((j%2==1)?1:0); //checks if the row is odd
var geom = new THREE.Geometry();
var a = new THREE.Vector3(x[i][j], y[i][j] ,z[i][j]);
var b = new THREE.Vector3(x[i+1][j], y[i+1][j] ,z[i+1][j]);
var c = new THREE.Vector3(x[i+o][j+1], y[i+o][j+1] ,z[i+o][j+1]);
geom.vertices.push(a);
geom.vertices.push(b);
geom.vertices.push(c);
geom.faces.push(new THREE.Face3(0,1,2));
tile1[i][j] = new THREE.Mesh(
geom,
new THREE.MeshBasicMaterial({color: 'green'})
);
scene.add(tile1[i][j]);
}
}
And this is the failing Lambert Mesh code on red tiles (note that I only changed 'Basic' to 'Lambert'):
for(j = 0; j < h-1; j++) { //h is the number of tiles vertically
for(i = 0; i < w-1; i++) { //w is the number of tiles horizontally
o = ((j%2==1)?0:1); //checks if the row is even
var geom = new THREE.Geometry();
var a = new THREE.Vector3(x[i+o][j], y[i+o][j] ,z[i+o][j]);
var b = new THREE.Vector3(x[i+1][j+1], y[i+1][j+1] ,z[i+1][j+1]);
var c = new THREE.Vector3(x[i][j+1], y[i][j+1] ,z[i][j+1]);
geom.vertices.push(a);
geom.vertices.push(b);
geom.vertices.push(c);
geom.faces.push(new THREE.Face3(0,1,2));
tile2[i][j] = new THREE.Mesh(
geom,
new THREE.MeshLambertMaterial({color: 'red'})
);
scene.add(tile2[i][j]);
}
}
A cube created with the following Lambert Mesh works perfectly and catches light properly.
scene.add(new THREE.Mesh(new THREE.BoxGeometry(10,1000,5),new THREE.MeshLambertMaterial({color:'red'})));
Why does the Lambert Mesh not work on a geometry that Basic Mesh works on?
EDIT: I placed a colored box under the sheet to test how the box would react to lighting and found that the tiles above it weren't failing to render, but were just black. They are opaque, but don't use the color or pick up light the way the box does.

You should have lights in your scene to profit from THREE.LambertMaterial. Did you setup your scene lighting correctly?
EDIT:
I found out where your problem is. You should add a normal to your faces, otherwise the WebGL renderer does not know how to render the light bouncing of the THREE.LambertMaterial on the surfaces. So change your code like this:
face = new THREE.Face3( 0, 1, 2 );
face.normal = new THREE.Vector3().crossVectors(
new THREE.Vector3().subVectors( b, a ),
new THREE.Vector3().subVectors( c, b )
).normalize();
geom.faces.push( face );
Now your faces should render.
Instead of doing this manually you can also use the geometry methods for calculating them:
geometry.computeFaceNormals();
geometry.computeVertexNormals();

Related

Three.js Multiple Materials in Plane Geometry

I am trying to use a single plane to show multiple materials. I am currently using the MultiMaterial with the materials I plan to use inside of (textured).
The issue I am having is that the materials I use seem to get split across the entire plane into little chunks for each face. However I would like to have a material cover a 1 / n amount of the plane/mesh.
My current code (shortened):
var splitX = 2;
var splitY = 2;
var geometry = new THREE.PlaneGeometry(800, 800, splitX, splitY);
var materials = [
new THREE.MeshBasicMaterial({
map: preloaded texture..., side: THREE.DoubleSide
}),
new THREE.MeshBasicMaterial({
color: 0xff0000, side: THREE.DoubleSide
})
];
// set a single square inside the plane to the desired textured material
geometry.faces[0].materialIndex = 0;
geometry.faces[1].materialIndex = 0;
// set the other squares inside the plane to use the coloured material
for(var i = 1; i < geometry.faces.length / 2; i++) {
geometry.faces[i * 2].materialIndex = 1;
geometry.faces[i * 2 + 1].materialIndex = 1;
}
var mesh = new THREE.Mesh(geometry, new THREE.MultiMaterial(materials));
scene.add(mesh);
The output: http://prnt.sc/e8un2a
I marked each corner of the texture to see whether it would show the entire texture in the 2 faces I specified and it did not. Any help would be appreciated to resolve this! :)

3D Curve Geometry in Three.js

I have an array of Vector3s that define an arbitrary curved shape in 3D space. I can render an outline of the curve in Three.js by using a THREE.Geometry and a THREE.Line, but I'd like to fill it with color.
I tried using THREE.ShapeGeometry and THREE.Mesh, but it seems as though THREE.ShapeGeometry is for 2D planes only (the z-coordinates of my vertices are being ignored). I also tried to use THREE.Geometry and define the faces in addition to the vertices I wanted, but had no luck.
How should I go about doing this?
Code:
geom.vertices = curve.getPoints(100);
for (var i = 0; i < 97; i++) {
geom.faces.push(new THREE.Face3(i, i + 1, i + 2));
}
var material = new THREE.MeshNormalMaterial();
obj = new THREE.Mesh(geom, material);
scene.add(obj);
Fixed by changing the code in the question above to the excerpt below:
geom.vertices = curve.getPoints(100);
for (var i = 0; i < 98; i++) {
geom.faces.push(new THREE.Face3(0, i + 1, i + 2));
}
var materialObj = { color : 0xff0000, side: THREE.DoubleSide };
var material = new THREE.MeshBasicMaterial(materialObj);
obj = new THREE.Mesh(geom, material);
scene.add(obj);

How would I create a terrain like this in Three.js

I am trying to create a terrain from a heightmap with a "closed" bottom see the example here:
http://demos.playfuljs.com/terrain/
My terrain generation function is as so:
var img = document.getElementById("landscape-image");
var numSegments = img.width - 1; // We have one less vertex than pixel
var geometry = new THREE.PlaneGeometry(2400, 2400, numSegments, numSegments);
var material = new THREE.MeshLambertMaterial({
color: 0xccccff,
wireframe: false
});
plane = new THREE.Mesh(geometry, material);
plane.name = 'Terrain';
// set height of vertices
for (var i = 0; i < plane.geometry.vertices.length; i++) {
plane.geometry.vertices[i].z = (terrain[i]/255) * height;
}
geometry.computeFaceNormals();
geometry.computeVertexNormals();
plane.position.x = 0;
plane.rotation.x = 0;
plane.position.y = -10;
The problem I am having is how do I create that connected bottom part of the terrain with a THREE.PlaneGeometry. I can't extrude as:
The bottom must be flat if I extrude it will be bumpy like the
terrain.
Extrude takes a Shape object, not a Geometry object.
Really scratching my head here anyone done this before.
Edit: Maybe I could use two planes and merge them but how would I merge the side faces into a single piece of Geometery ?
P.S. The example draws straight to the canvas
create a plane for each side which has your amount of Segments in width and 1 in height. them set the top Vertices according to your heightmap
something like this
var geometry_l = new THREE.PlaneGeometry(2400, 0, numSegments, 1);
plane_l = new THREE.Mesh(geometry_l, material);
for (var i = 0; i < numSegments; i++) {
plane_l.geometry_l.vertices[i].z = (Terrain[0][i]/255) * height;
}
//Offset to the edge of your main plane
you might want to Change your Terrain Array to be two-dimensional for this. so you can always read the row you want.

Three.js - position object at vertex of another

I have an IcosahedronGeometry defined like this (with all the code about colors and non-position stuff omitted):
var radius = 200;
geometry = new THREE.IcosahedronGeometry(radius, 2);
var materials = [
new THREE.MeshPhongMaterial({}),
new THREE.MeshBasicMaterial({})
];
group1 = THREE.SceneUtils.createMultiMaterialObject(geometry, materials);
group1.position.x = 0;
// rotate a bit just so it spins off-axis
group1.rotation.x = -1.87;
Which creates an almost spherical, many-sided shape.
I want to place little spheres at just a few of the vertices of this shape. Let's say 10 spheres. I do this by copying 10 vertices into an array, like this:
var vertexArray = [];
for (var i = 0; i < 10; i++) {
vertexArray.push(geometry4.vertices[i])
}
then, I use the Vectors copied into vertexArray to set the positions off Sprites:
for (var i = 0; i < vertexArray.length; i++) {
var loader = new THREE.TextureLoader()
var spriteMaterial = new THREE.SpriteMaterial(
{
map: loader.load('/glow.png'),
blending: THREE.AdditiveBlending,
side: THREE.BackSide
})
var sprite = new THREE.Sprite(spriteMaterial)
sprite.position.set(vertexArray[i].x, vertexArray[i].y, vertexArray[i].z)
// do i need rotate these by the same amount?
sprite.rotation.x = -1.87
scene.add(sprite)
}
This all works fine, except that the Sprites don't line up with the actual vertices on the Icosahedron, they just sit randomly (seemingly) somewhere on the faces. Occasionally a Sprite will sit exactly on a vertex.
Am I copying the vertices wrong, or missing a step in here?
Add an object as a child of a parent object, and the child object will rotate with the parent.
Use this pattern, instead:
var sprite = new THREE.Sprite( spriteMaterial );
sprite.position.copy( vertexArray[i] );
group1.add( sprite );
three.js r.76

Threejs PlaneGeometry doesn't receive shadows or reflections

I'm pretty new to 3d and to threejs and I can't figure out how I can get a PlaneGeometry to show individually illuminated polygons i.e. receive shadows or show reflection. What I basically do is taking a PlaneGeometry applying some noise to every z value of the vertices. Then I have a simple directional light in my scene which is supposed to make the emerging noise pattern on the plane visible. I tried different things like plane.castShadow = true or renderer.shadowMapEnabled = true without success. Am I just missing a simple option or is this way more complicated than I think?
Here's are the relevant pieces of my code
renderer.setSize(width, height);
renderer.setClearColor(0x111111, 1);
...
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.9);
directionalLight.position.set(10, 2, 20);
directionalLight.castShadow = true;
directionalLight.shadowCameraVisible = true;
scene.add( directionalLight );
var geometry = new THREE.PlaneGeometry(20, 20, segments, segments);
var index = 0;
for(var i=0; i < segments + 1; i++) {
for(var j=0; j < segments + 1; j++) {
zOffset = simplex.noise2D(i * xNoiseScale, j * yNoiseScale) * 5;
geometry.vertices[index].z = zOffset;
index++;
}
}
var material = new THREE.MeshLambertMaterial({
side: THREE.DoubleSide,
color: 0xf50066
});
var plane = new THREE.Mesh(geometry, material);
plane.rotation.x = -Math.PI / 2.35;
plane.castShadow = true;
plane.receiveShadow = true;
scene.add(plane);
This is the output I get. Obviously the plane is aware of the light because the bottom side is darker than the upper side but there is no sign of any individual polygons receiving individual lightening and no 3d structure is visible. Interestingly when I put in a different geometry like a BoxGeometry individual polygons are illuminated individually (see 2nd image). Any ideas?
Ok I figured it out thanks to this post. The trick is to use the THREE.FlatShading shader on the material. Important to note is that after every update of the vertices two things need to be done. Before rendering geometry.normalsNeedUpdate must be set to true so the renderer also incorporates the newly oriented vertices. Also geometry.computeFaceNormals() needs to be called before rendering because when you alter the vertices the normals are not the same anymore.

Categories