three.js: switch from simple material to multi- on runtime - javascript

r65
Hello,
I need to switch from simple material to multi-material on runtime. But can't. May be I miss something obvious?
My test code is below (jsfiddle: http://jsfiddle.net/plotnik/8RtTy/3/). Simple material is assigned to object initially, multi-material is assigned after the render() call to simulate runtime substitution.
var geom = new THREE.CubeGeometry(1, 1, 1);
var materialSimple = new THREE.MeshLambertMaterial({color: 0x020202});
// 6-color
var materialMulti = new THREE.MeshFaceMaterial([
new THREE.MeshLambertMaterial( { color: 0xff0000 }),
new THREE.MeshLambertMaterial( { color: 0xffff00 }),
new THREE.MeshLambertMaterial( { color: 0xffffff }),
new THREE.MeshLambertMaterial( { color: 0x00ffff }),
new THREE.MeshLambertMaterial( { color: 0x0000ff }),
new THREE.MeshLambertMaterial( { color: 0x000000 })
]);
var mesh = new THREE.Mesh(geom, materialSimple);
scene.add(mesh);
var render = function () {
requestAnimationFrame(render);
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
};
render();
// switch to multi-material
mesh.material = materialMulti;
mesh.material.needsUpdate = true;
The code doesn't work as I expect. The result looks like only materialMulti[0] (of red color) is assigned to the whole mesh.
I will be grateful for the assistance.

Switching materials at runtime can be tricky with WebGLRenderer. You can read more about it in the Wiki article How to Update Things with WebGLRenderer.
In you case, you can just use MeshFaceMaterial all the time, and achieve the effect you want by changing only the material colors.
EDIT: MeshFaceMaterial has been deprecated. You can now pass in an array of materials directly to the Mesh constructor`like so:
var mesh = new THREE.Mesh( geometry, materials_array );
three.js r.91

Related

ThreeJS new mesh vs clone

I'm wondering which style is more effective creating a new mesh or cloning one.
For example I have a loop that creates multiple times the "same mesh".
But I also noticed that if I have an opacity of the mesh set as 0 and I want to change it later to be visible, this would effect all the meshes that have the same material. Could it be because they have the same uuid.
var material = new THREE.MeshPhongMaterial({
color: 0xff0000,
transparent: true, // Make the material transparent
opacity: 0 // Set material opacity to 0
});
var geometry = new THREE.PlaneGeometry(width, height);
$.each(things, function(i, something) {
var mesh = new THREE.Mesh(geometry, material);
// Mesh positioning
scene.add(mesh);
});
So if I use the same material and geometry multiple times and use the raycaster to change the mesh opacity it would change the opacity of all the meshes.
Should I use the clone inside the each loop or create the material and the geometry of the mesh again and again?
$.each(things, function(i, something) {
var mesh = new THREE.Mesh(geometry.clone(), material.clone());
// Mesh positioning
scene.add(mesh);
});
or
$.each(things, function(i, something) {
var material = new THREE.MeshPhongMaterial({
color: 0xff0000,
transparent: true, // Make the material transparent
opacity: 0 // Set material opacity to 0
});
var geometry = new THREE.PlaneGeometry(width, height);
var mesh = new THREE.Mesh(geometry, material);
// Mesh postioning here
scene.add(mesh);
});
In your first method all the meshes are built up on the SAME GEOMETRY and MATERIAL(As you said they also have the same uuid).
So when you change the properties of the material it will affect all the meshes which are using the same material. But when you clone the material, it will create a NEW MATERIAL WITH THE SAME PROPERTIES, and also changes on the original material will not affect the cloned material. So most probably this method will work for you,
$.each(things, function(i, something) {
var mesh = new THREE.Mesh(geometry, material.clone());
// rest of your code...
});
I tried the following and it worked for me,
var material = new THREE.MeshPhongMaterial({
color: 0xff0000,
transparent: true,
opacity: 0.5
});
var geometry = new THREE.PlaneGeometry(100, 100, 1, 1);
for( var i = 0; i < 5; i++ ){
scene.add( new THREE.Mesh( geometry, material.clone() ) );
}

Different customDepthMaterial for every material

I have a tree model, that consists from only one mesh and has two materials: bark and branch. I export this mesh from Blender to .json and than add it on the scene:
var texture2 = new THREE.TextureLoader().load('models/branch1.png');
var loader = new THREE.JSONLoader();
loader.load("models/treeTest.json", function (geometry, materials) {
materials.forEach(function (material) {
material.alphaTest = 0.5;
});
mesh = new THREE.Mesh( geometry, materials );
mesh.customDepthMaterial = new THREE.MeshDepthMaterial( {
depthPacking: THREE.RGBADepthPacking,
map: texture2,
alphaTest: 0.5
} );
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
});
Result:
As you can see branches and bark use the same customDepthMaterial and because of that they have same shadows. I need to give customDepthMaterial only to branches, trunk must stay not transparent. I have tried to add customDepthMaterial on material with this:
materials.forEach(function (material) {
material.customDepthMaterial = new THREE.MeshDepthMaterial( {
depthPacking: THREE.RGBADepthPacking,
map: texture2,
alphaTest: 0.5
} );
});
but this code doesnt work (no errors in console, shadows just doesnt have transparency).
I know, that I can split my mesh into two parts (branches and trunk) and than use ObjectLoader, but I need the better solution.

Phong material on a .obj model not reacting to area light in three.js

I am trying to add a phong material to my .obj model along with a shader Area light, However i tried to use Meshface material using array to load both materials but the model completely disappears when i do so.
The scenario works when i use either of them but not both together, is there anyway to overcome this issue ?
Here is the DEMO:
CODE:
// area light
var lightColor = 0xffffff;
var lightIntensity = 1;
var geometry = new THREE.PlaneGeometry(100, 50);
var material = new THREE.MeshBasicMaterial({
color: lightColor,
transparent: true,
opacity: 0.7,
side: THREE.FrontSide
});
areaLight = new THREE.Mesh(geometry, material);
areaLight.position.set(0, 25, -25);
areaLight.rotation.set(0, 0, 0);
areaLight.scale.set(1, 1, 1);
scene.add(areaLight);
var Black = new THREE.MeshPhongMaterial({
color: 0x000000,
})
// wireframe hack
areaLight.add(new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
wireframe: true
})));
// areaLight verts
var vertices = areaLight.geometry.vertices;
var verts = [];
verts.push(vertices[0]);
verts.push(vertices[1]);
verts.push(vertices[3]);
verts.push(vertices[2]);
// uniforms
var uniforms = {
color: {
type: "c",
value: new THREE.Color(0xaaaadd)
},
lightColor: {
type: "c",
value: areaLight.material.color
},
lightIntensity: {
type: "f",
value: lightIntensity
},
lightverts: {
type: "v3v",
value: verts
},
lightMatrixWorld: {
type: "m4",
value: areaLight.matrixWorld
}
};
// attributes
var attributes = {};
// material
var material = new THREE.ShaderMaterial({
attributes: attributes,
uniforms: uniforms,
vertexShader: document.getElementById('vertex_shader').textContent,
fragmentShader: document.getElementById('fragment_shader').textContent,
shading: THREE.SmoothShading
});
var onError = function(xhr) {};
THREE.Loader.Handlers.add(/\.dds$/i, new THREE.DDSLoader());
var mtl1Loader = new THREE.MTLLoader();
mtl1Loader.setBaseUrl('neavik/newmail/');
mtl1Loader.setPath('neavik/newmail/');
mtl1Loader.load('chandelier.mtl', function(materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.setPath('neavik/newmail/');
objLoader.load('chandelier.obj', function(object4) {
object4.castShadow = true;
object4.receiveShadow = true;
object4.updateMatrix();
object4.position.set(40, 28, 40); //(0,-5,0.5);
object4.scale.x = 0.09;
object4.scale.y = 0.05;
object4.scale.z = 0.09
var mats = [];
mats.push(new THREE.MeshPhongMaterial({
color: 0x000000
}));
mats.push(material);
var faceMaterial = new THREE.MeshFaceMaterial(mats);
object4.traverse(function(child) {
if (child instanceof THREE.Mesh) {
if (child.material.name == "chandelier_Outside") {
child.material = Black; // When i use faceMaterial here the object disappears
child.castShadow = true;
child.receiveShadow = true;
}
}
});
scene.add(object4);
});
})
// plane geometry
var geometry = new THREE.PlaneGeometry(200, 200);
geometry.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2));
// plane
mesh = new THREE.Mesh(geometry, material);
mesh.position.y = -0.1;
scene.add(mesh);
// torus knot
var geometry = new THREE.TorusKnotGeometry(10, 4, 256, 32, 1, 3, 1);
// mesh
mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 28, 0);
scene.add(mesh);
}
The main issue section
var mats = [];
mats.push(new THREE.MeshPhongMaterial({
color: 0x000000
}));
mats.push(material);
var faceMaterial = new THREE.MeshFaceMaterial(mats);
object4.traverse(function(child) {
if (child instanceof THREE.Mesh) {
if (child.material.name == "chandelier_Outside") {
child.material = Black; // When i use faceMaterial here the object disappears
child.castShadow = true;
child.receiveShadow = true;
}
}
});
scene.add(object4);
});
})
Area light reference:http://jsfiddle.net/hh74z2ft/1/
Expected result is similar to this image that even the chandelier should be illuminated like torusknot along with phongness [no phongness in this image ]
[]2
Unfortunately, if I understood you correctly, this will not work.
A material (among other things) defines how a given fragment of a triangle will be shaded. There can always be only one material for any given triangle. The face-material is for handling the case where you want to render different faces with different materials, but it's still just one material per triangle.
There is a concept in three.js named multiMaterial (have a look at THREE.SceneUtils.createMultiMaterialObject()). But that will simply create multiple meshes with the same geometry and different materials, so every triangle will get rendered twice, once with every material.
Now the Problem is that the AreaLight implementation has it's very own concept of how the light is represented (via shader-uniforms), and that is completely different from how the phong-material works. So, even if you did somehow add a phong-material to the object to get the specular reflections, the phong-material wouldn't know how to deal with the area-light source (it wouldn't even know that there is any light in the scene).
In order to get the behaviour you like to see you would need to add a specular term (the calculation-formula for specular reflections of the surface) to the fragment-shader and maybe some uniforms to control it's behaviour. This is a really complicated thing to do, especially for area-lights (if it were easy, we'd already have it in three.js).
However, there is some work being done based on latest research in that field, you might want to have a look at this issue and the demo here. There is also a pull-request for this here.

Wrapping a torus with a spiral

I'm trying to create the effect similar to hula hoop covered in tape using three.js.
The 3d model should end up looking something like below.
I can create the hoop in 3d space using TorusGeometry with the ability to pan around, but I have not managed to work out how to get a 2nd TorusGeometry to break into sections.
What is the best way of creating this effect?
// hoop
hoop = new THREE.TorusGeometry(20, .5, 100, 50);
var hoopMaterial = new THREE.MeshPhongMaterial({
ambient: 0xffffff,
color: 0x028fde,
specular: 0x555555,
shininess: 0
});
hoopMesh = new THREE.Mesh(hoop, hoopMaterial);
hoopMesh.position.z = 0;
scene.add(hoopMesh);
hoopTape1 = new THREE.TorusGeometry(20.1, .6, 0, 50);
var hoopTapeMaterial = new THREE.MeshPhongMaterial({
ambient: 0xffffff,
color: 0xDE5102,
specular: 0x555555,
shininess: 0
});
hoopTape1Mesh = new THREE.Mesh(hoopTape1, hoopTapeMaterial);
hoopMesh.position.z = 0;
scene.add(hoopTape1Mesh);
jsfiddle with current working code.
You will have to apply a texture to your torus that is seamless to achieve that effect. The texture should be similar to this one:
Then use this code pattern:
var geometry = new THREE.TorusBufferGeometry( 6, 0.4, 16, 64 );
var loader = new THREE.TextureLoader();
var texture = loader.load( 'stripe.jpg', function ( texture ) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 16, 0.5 ); // or whatever you like
render();
} );
var material = new THREE.MeshPhongMaterial( {
color: 0x998877,
specular: 0x050505,
shininess: 50,
map: texture
} );
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
three.js r.77

customize terrain.js (three.js) Need to add lambert/wire material

I've recently started to play with three.js, and I am using terrain.js demo as a start for a design project I am working on.
I will like to add a hybrid shader "wireframe/lambert"
the default comes with wire shader only.
This is the code from the demo, using basic material:
var matrix = new THREE.MeshBasicMaterial({
color:0x10ce58,
wireframe:true
});
var geometry = new THREE.PlaneGeometry(width, height, modelWidth, modelHeight);
mesh = new THREE.Mesh(geometry, matrix);
mesh.doubleSided = false;
and I tried something like this but I only get the "lambert" rendering and not the lambert and wire combined, any ideas?
var darkMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff , shading: THREE.FlatShading, overdraw: true} );
var wireframeMaterial = new THREE.MeshBasicMaterial( { color: 0x10ce58, wireframe: true, transparent: true } );
var multiMaterial = [ darkMaterial, wireframeMaterial ];
var geometry = new THREE.PlaneGeometry(width, height, modelWidth, modelHeight);
mesh = new THREE.Mesh(geometry, multiMaterial);
mesh.doubleSided = false;
Thanks for your time in advanced,
Regards
-Manuel
This is the code I am using in my non working example for materials (http://jsfiddle.net/xnqUb/3/)
var geometry = new THREE.PlaneGeometry(width, height, model.length - 1, model.length - 1, materials);
materials = [
new THREE.MeshLambertMaterial( { color: 0xffffff, shading: THREE.FlatShading, overdraw: true } ),
new THREE.MeshLambertMaterial({ color: 0x10ce58, wireframe: true,})
];
var mesh = new THREE.Mesh(geometry);
object = THREE.SceneUtils.createMultiMaterialObject(geometry, materials);

Categories

Resources