In a three.js rendering, I have a small texture which I want to repeat multiple times. For this texture there is the map, a displacement map, a normal map, an ambient occlusion map and a specular map. As long as the repeating pattern is 1 in the x and 1 in the y direction, the image looks as expected. There is displacement where expected.
If the repeat values are greater than 1, all of the maps appear to scaled correctly except for the displacement map. The displacement map does not appear to be repeated. The displace is the same as shown in the previous image.
A code snippet for applying these maps to a plane follows:
///////////////////////////////////////////////
// add texture plane for a test
xRep = 1;
yRep = 1;
var loader = new THREE.TextureLoader();
var tex = loader.load('images/img.png');
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set(xRep, yRep);
var nloader = new THREE.TextureLoader();
var ntex = loader.load('images/img-normal.png');
ntex.wrapS = ntex.wrapT = THREE.RepeatWrapping;
ntex.repeat.set(xRep, yRep);
var aloader = new THREE.TextureLoader();
var atex = aloader.load('images/img-ao.png');
atex.wrapS = atex.wrapT = THREE.RepeatWrapping;
atex.repeat.set(xRep, yRep);
var dloader = new THREE.TextureLoader();
var dtex = dloader.load('images/img-v003-disp.png');
dtex.wrapS = dtex.wrapT = THREE.RepeatWrapping;
dtex.repeat.set(xRep, yRep);
var sloader = new THREE.TextureLoader();
var stex = sloader.load('images/img-v003-spec.png');
stex.wrapS = stex.wrapT = THREE.RepeatWrapping;
stex.repeat.set(xRep, yRep);
var faceMaterial = new THREE.MeshPhongMaterial({
color: 0xa0a0a0,
shininess: 30,
//map : tex,
//bumpMap : tex,
//bumpScale : 1,
displacementMap: dtex,
displacementScale: 10,
normalMap: ntex,
//normalScale : (1,1),
aoMap: atex,
//aoMapIntensity : 1,
specularMap: stex
//_last: 0
});
face = new THREE.Mesh(new THREE.PlaneGeometry(50, 50, 256, 256),
faceMaterial);
face.position.z = 50;
face.receiveShadow = true;
face.castShadow = true;
scene.add(face);
How can this snippet be modified so the displacement map is repeated the same as the other maps?
Note: This issue appear to be related to the discussion at issue #7826 on Git hub.
You want your displacement map to repeat, but the displacement map does not honor the offset repeat value in the current version of three.js.
A work-around is to modify the UVs of your geometry.
Suppose you want the displacement map to be repeated 3 times. Then, multiply your UVs by 3.
Using PlaneBufferGeometry, follow this pattern to do so:
var uvs = geometry.attributes.uv.array;
for ( var i = 0; i < uvs.length; i ++ ) uvs[ i ] *= 3;
For the other textures, if you wanted to, you could compensate by scaling their repeat values accordingly:
texture.repeat.set( 1 / 3, 1 / 3 );
If you are changing the buffer geometry UVs after rendering at least once, you will need to set:
mesh.geometry.attributes.uv.needsUpdate = true;
Also, be sure to read this post.
three.js r.79
Related
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! :)
Using Three.js r75
I am trying to display cubes that change color depending on an integer value from green to red. I have tried multiple ways as I am stuck on this. I was unable to make cubeMat.material.color.setRGB work and creating a new Three.Color doesn't seem to work either. Please note I merge all the geometries at the end for one draw call. I am hoping this isn't the issue.
[UPDATE]
I am confirming the rgb values are set correctly with getStyle however they do not render correctly. All cube stacks should be different colors.
function colorData(percentage){
var rgbString = "",
r = parseInt(percentage * 25.5),
g = parseInt(((percentage * 25.5) - 255) * -1),
b = 0;
rgbString = "rgb("+r+","+g+",0)";
return rgbString;
}
...
var position = latLongToSphere(objectCoord[1], objectCoord[0], 300),
rgb = colorData(objectMag),
cubeColor = new THREE.Color(rgb),
cubeMat = new THREE.MeshBasicMaterial({color: cubeColor}),
cubeHeight = objectMag * 175,
cubeGeom = new THREE.BoxGeometry(3,3,cubeHeight,1,1,1),
cube = new THREE.Mesh(cubeGeom, cubeMat);
// set position of cube on globe, point to center, merge together for one draw call
cube.geometry.colorsNeedUpdate = true;
cube.position.set(position.x, position.y, position.z);
cube.lookAt(lookCenter);
cube.updateMatrix();
console.log(cube.material.color.getStyle());
geom.merge(cube.geometry, cube.matrix);
You are merging geometries so you can render with a single draw call and a single material, but you want each of the merged geometries to have a different color.
You can achieve that by defining vertexColors (or faceColor) in your geometry. Here is a pattern to follow:
// geometry
var geometry = new THREE.Geometry();
for ( var count = 0; count < 10; count ++ ) {
var geo = new THREE.BoxGeometry( 5, 5, 5 );
geo.translate( THREE.Math.randFloat( - 5, 5 ), THREE.Math.randFloat( - 5, 5 ), THREE.Math.randFloat( - 5, 5 ) );
var color = new THREE.Color().setHSL( Math.random(), 0.5, 0.5 );
for ( var i = 0; i < geo.faces.length; i ++ ) {
var face = geo.faces[ i ];
face.vertexColors.push( color, color, color ); // all the same in this case
//face.color.set( color ); // this works, too; use one or the other
}
geometry.merge( geo );
}
Then, when you specify the material for the merged geometry, set vertexColors like so:
// material
var material = new THREE.MeshPhongMaterial( {
color: 0xffffff,
vertexColors: THREE.VertexColors // or THREE.FaceColors, if defined
} );
Your geometry will be rendered with a single draw call. You can verify that by typing renderer.info into the console. renderer.info.render.calls should be 1.
three.js r.75
cubeMat.material.color.setRGB won't work because it's like you're calling the material twice (cubeMat and material), try this instead:
cube.material.color.setRGB( value, value, value );
Turns out if you merge the geometry the materials cant have different colors.
I had to set the face color of each cube before merging.
See
Changing material color on a merged mesh with three js
Three js materials of different colors are showing up as one color
I am trying to map lat/long data to a sphere. I am able to get vectors with different positions and set the position of the cube mesh to those. After I merge and display it appears that there is only one cube. I am assuming that all the cubes are in the same position. Wondering where I am going wrong here. (latLongToSphere returns a vector);
// simple function that converts the data to the markers on screen
function renderData() {
// the geometry that will contain all the cubes
var geom = new THREE.Geometry();
// add non reflective material to cube
var cubeMat = new THREE.MeshLambertMaterial({color: 0xffffff,opacity:0.6, emissive:0xffffff});
for (var i = quakes.length - 1; i >= 0; i--) {
var objectCache = quakes[i]["geometry"]["coordinates"];
// calculate the position where we need to start the cube
var position = latLongToSphere(objectCache[0], objectCache[1], 600);
// create the cube
var cubeGeom = new THREE.BoxGeometry(2,2,2000,1,1,1),
cube = new THREE.Mesh(cubeGeom, cubeMat);
// position the cube correctly
cube.position.set(position.x, position.y, position.z);
cube.lookAt( new THREE.Vector3(0,0,0) );
// merge with main model
geom.merge(cube.geometry, cube.matrix);
}
// create a new mesh, containing all the other meshes.
var combined = new THREE.Mesh(geom, cubeMat);
// and add the total mesh to the scene
scene.add(combined);
}
You have to update the mesh matrix before merging its geometry:
cube.updateMatrix();
geom.merge(cube.geometry, cube.matrix);
jsfiddle: http://jsfiddle.net/L0rdzbej/222/
I'm making a custom three.js geometry for non-orthogonal cubes. It is loosely based on the existing Box-geometry in three.js, but greatly simplified insofar that it only supports one segment per side and also has the absolute position of its vertices fed directly to it.
I have problems both in wire frame rendering and texture rendering. In wire frame rendering I only get to see one of the six sides, as can be seen here:
This is the snippet that I use for setting the material:
if (woodTexture) {
texture = THREE.ImageUtils.loadTexture( 'crate.gif' );
texture.anisotropy = makeRenderer.renderer.getMaxAnisotropy();
material = new THREE.MeshBasicMaterial( { map: texture } );
} else {
material = new THREE.MeshBasicMaterial( { color: color, wireframe: true, side: THREE.DoubleSide } );
}
I know for sure the path for crate.gif is valid, as it works for Box geometries.
Here follows my faulty geometry. The 'quadruplets' array contains six arrays with each four Vector3 instances. Each inner array delineates a side of the cube.
THREE.Box3Geometry = function (quadruplets, debug) {
THREE.Geometry.call(this);
var constructee = this; // constructee = the instance currently being constructed by the Box3Geometry constructor
buildPlane(quadruplets[0], 0, debug); // px
buildPlane(quadruplets[1], 1); // nx
buildPlane(quadruplets[2], 2); // py
buildPlane(quadruplets[3], 3); // ny
buildPlane(quadruplets[4], 4); // pz
buildPlane(quadruplets[5], 5); // nz
function buildPlane(quadruplet, materialIndex, debug) {
// populate the vertex array:
constructee.vertices.push(quadruplet[0]);
constructee.vertices.push(quadruplet[1]);
constructee.vertices.push(quadruplet[2]);
constructee.vertices.push(quadruplet[3]);
// construct faceVertexUvs:
var uva = new THREE.Vector2(0, 1);
var uvb = new THREE.Vector2(0, 0);
var uvc = new THREE.Vector2(1, 0);
var uvd = new THREE.Vector2(1, 1);
// construct faces:
var a = 0; // vertex: u:50, v:50
var b = 2; // vertex: u:50, v:-50
var c = 3; // vertex: u:-50, v:-50
var d = 1; // vertex: u:-50, v:50
// construct normal:
var pv0 = quadruplet[1].clone().sub(quadruplet[0]); // pv = plane vector
var pv1 = quadruplet[2].clone().sub(quadruplet[0]);
normal = new THREE.Vector3(0,0,0).crossVectors(pv0, pv1).normalize();;
var face1 = new THREE.Face3(a, b, d);
face1.normal.copy(normal);
face1.vertexNormals.push(normal.clone(), normal.clone(), normal.clone());
face1.materialIndex = materialIndex;
constructee.faces.push(face1);
constructee.faceVertexUvs[ 0 ].push([ uva, uvb, uvd ]);
var face2 = new THREE.Face3(b, c, d);
face2.normal.copy(normal);
face2.vertexNormals.push(normal.clone(), normal.clone(), normal.clone());
face2.materialIndex = materialIndex;
constructee.faces.push(face2);
constructee.faceVertexUvs[ 0 ].push([ uvb.clone(), uvc, uvd.clone() ]);
}
this.mergeVertices();
};
THREE.Box3Geometry.prototype = Object.create(THREE.Geometry.prototype);
And this is the Box geometry from which I was "inspired".
You build a nice array of vertices, but give every face1/face2 combo the same set of indexes into that array: 0, 1, 2, 3. You essentially define the same quad 6 times.
What you need to do is keep a running base offset and add that to the vertex indices used to define each face. If you look at your BoxGeometry example, you'll that they do exactly that.
I am trying to render a large forest of 100,000+ very simple-looking trees in ThreeJS. Creating many individual meshes is of course out of the question. My current method uses GeometryUtils.merge to create one large geometry which reduces the number of draw calls and this works pretty well. But approaching 100k, it bogs down. I need to improve performance further and I have a feeling there may be another trick or two to increase performance by another factor of 10 or more.
The code is below and I've also created a JSFiddle demonstrating my current technique: http://jsfiddle.net/RLtNL/
//tree geometry (two intersecting y-perpendicular triangles)
var triangle = new THREE.Shape();
triangle.moveTo(5, 0);
triangle.lineTo(0, 12);
triangle.lineTo(-5, 0);
triangle.lineTo(5, 0);
var tree_geometry1 = new THREE.ShapeGeometry(triangle);
var matrix = new THREE.Matrix4();
var tree_geometry2 = new THREE.ShapeGeometry(triangle);
tree_geometry2.applyMatrix(matrix.makeRotationY(Math.PI / 2));
//tree material
var basic_material = new THREE.MeshBasicMaterial({color: 0x14190f});
basic_material.color = new THREE.Color(0x14190f);
basic_material.side = THREE.DoubleSide;
//merge into giant geometry for max efficiency
var forest_geometry = new THREE.Geometry();
var dummy = new THREE.Mesh();
for (var i = 0; i < 1000; i++)
{
dummy.position.x = Math.random() * 1000 - 500;
dummy.position.z = Math.random() * 1000 - 500;
dummy.position.y = 0;
dummy.geometry = tree_geometry1;
THREE.GeometryUtils.merge(forest_geometry, dummy);
dummy.geometry = tree_geometry2;
THREE.GeometryUtils.merge(forest_geometry, dummy);
}
//create mesh and add to scene
var forest_mesh = new THREE.Mesh(forest_geometry, basic_material);
scene.add(forest_mesh);
Can anyone suggest further techniques to make this load and render more quickly?
How about using billboards to render the trees? The 2D nature of billboards seem to suit this particular problem. Create a simple png tree texture with transparency, and add each tree as a PointCloud object - http://threejs.org/docs/#Reference/Objects/PointCloud
Most low-end graphics cards can render well over 10,000 billboard objects without a drop in framerate. I've updated your code using the billboards technique (changing the number of trees to 10,000 and using a 100 pixel high tree texture): http://jsfiddle.net/wayfarer_boy/RLtNL/8/ - extract of the code below:
geometry = new THREE.Geometry();
sprite = new THREE.Texture(image);
sprite.needsUpdate = true;
for (var i = 0; i < 10000; i++) {
var vertex = new THREE.Vector3();
vertex.x = Math.random() * 1000 - 500;
vertex.y = 0;
vertex.z = Math.random() * 1000 - 500;
geometry.vertices.push(vertex);
}
material = new THREE.PointCloudMaterial({
size: 50,
sizeAttenuation: true,
map: sprite,
transparent: true,
alphaTest: 0.5
});
particles = new THREE.PointCloud(geometry, material);
// particles.sortParticles = true;
// Removed line above and using material.alphaTest instead
// Thanks #WestLangley
scene.add(particles);
renderer = new THREE.WebGLRenderer({
clearAlpha: 1,
alpha: true
});