Enable smooth shading with Three.js - javascript

I'm rendering an object with textures using MTL and OBJ files with Three.js. My code here works but my model is displayed as flat shaded. How do I enable smooth shading?
var scene = new THREE.Scene();
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setPath('assets/');
mtlLoader.setBaseUrl('assets/');
mtlLoader.load('asset.mtl', function(materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.setPath('assets/');
objLoader.load('asset.obj', function(object) {
//
// This solved my problem
//
object.traverse(function(child) {
if(child instanceof THREE.Mesh)
{
child.material.shading = THREE.SmoothShading;
}
});
//
//
scene.add(object);
});
});
EDIT:
I updated my code with a solution that fixed my problem based on the accepted answer.

It could be one of two things that I can think of right now.
It could be that the material is set to FlatShading. In this case just somehow retrieve the object and use object.material.shading = THREE.SmoothShading; to fix.
If that doesn't change it, it's possible that the object contains per-vertex-normals (meaning that every vertex of every triangle has a normal attached to it) and that all normals for each triangle point in the same direction. This is something that should better be solved in the 3d-editing process, but you can also re-compute the normals in three.js:
object.geometry.computeVertexNormals(true);
This should [1] recompute the normals for smooth surfaces. However, it will only work for regular Geometries and Indexed BufferGeometries (or, to put it the other way around: it won't work if the geometry doesn't have information about vertices being reused for adjacent faces)
[1]: I didn't test it myself and just go after what I just read in the code

You may need to smooth geometry as follows:
geometry = BufferGeometryUtils.mergeVertices(geometry, 0.1);
geometry.computeVertexNormals(true);

Related

Overlaying texture onto STL loaded mesh

I'm looking for an efficient method of overlaying a texture to cover a mesh. I'm not an expert, more a novice, when it comes to 3 dimensional mapping/objects. Below shows how I would like the end product to look.
When attempting to apply texture with the following code, the end result looks similar to below. I have not done any UV mapping, I believe my answer may be lay here. As you can see from the below image it roughly takes the general shade of the picture but I get the impression that the texture is being drawn between each vertice of the model rather than across the entirity.
var textureLoader = new THREE.TextureLoader();
var texture = textureLoader.load('resource/images/materials/Mahogany.jpg');
var STLLoader = new THREE.STLLoader();
STLLoader.load( 'test.stl', function ( geometry1 ) {
var meshMaterial = new THREE.MeshBasicMaterial({map:texture});
var mesh = new THREE.Mesh( geometry1, meshMaterial );
mesh.scale.set(1, 1, 1);
mesh.position.set(5, 20, 80);
scene.add(mesh);
});
The cube has the correct texturing, whereas my STL loaded mesh does not.
Please ignore the rotation of the object in the above picture, I will move to unioning my objects together once I have fixed my texturing issues.
Fairly new at asking questions on here so please do comment to help me expand my question if it's too general or not percise enough. Thank you.
You may use
THREE.MeshPhongMaterial()
instead of
THREE.MeshBasicMaterial()
THREE.MeshPhongMaterial() will wrap the material outside the object and we can get curved material as per the object.

How to Change a Box's dimensions/size after creation?

One can easily create a THREE.BoxGeometry where you have to pass arguments when creating as three separated arguments for width, height, and depth.
I would like to create any and all THREE[types]() with no parameters and set the values after that.
Is there a way to set the dimensions/size of the box geometry after creation (possibly buried in a Mesh already too)? other then scaling etc.
I couldn't find this in the documentation if so, otherwise maybe a major feature request if not a bug there. Any thoughts on how to classify this? maybe just a documentation change.
If you want to scale a mesh, you have two choices: scale the mesh
mesh.scale.set( x, y, z );
or scale the mesh's geometry
mesh.geometry.scale( x, y, z );
The first method modifies the mesh's matrix transform.
The second method modifies the vertices of the geometry.
Look at the source code so you understand what each scale method is doing.
three.js r.73
When you instantiate a BoxGeometry object, or any other geometry for that matter, the vertices and such buffers are created on the spot using the parameters provided. As such, it is not possible to simply change a property of the geometry and have the vertices update; the entire object must be re-instantiated.
You will need to create your geometries as you have the parameters for them available. You can however create meshes without geometries, add them to a scene, and update the mesh's geometry property once you have enough information to instantiate the object. If not that, you could also set a default value at first and then scale to reach your target.
Technically, scaling only creates the illusion of an updated geometry and the question did say (other then scaling). So, I would say a better approach would be to reassign the geometry property of your mesh to a new geometry.
mesh.geometry = new THREE.BoxGeometry(newSize, newSize, newSize)
With this approach you can update any aspect of the geometry including width segments for example. This is especially useful when working with non box geometries like cylinders or spheres.
Here is a full working example using this approach:
let size = 10
let newSize = 20
// Create a blank geometry and make a mesh from it.
let geometry = new THREE.BoxGeometry()
let material = new THREE.MeshNormalMaterial()
let mesh = new THREE.Mesh(geometry, material)
// Adding this mesh to the scene won't display anything because ...
// the geometry has no parameters yet.
scene.add(mesh)
// Unless you intend to reuse your old geometry dispose of it...
// this will significantly reduce memory footprint.
mesh.geometry.dispose()
// Update the mesh geometry to a new geometry with whatever parameters you desire.
// You will now see these changes reflected in the scene.
mesh.geometry = new THREE.BoxGeometry(size, size, size)
// You can update the geometry as many times as you like.
// This can be done before or after adding the mesh to the scene.
mesh.geometry = new THREE.BoxGeometry(newSize, newSize, newSize)

three.js applyMatrix after scale results in error

The problem:
I am looking to update the vertice positions on a mesh after scaling. I am doing this because I need to calculate the volume of the mesh. I am creating a cloned mesh to do this because I need to keep scale an active parameter in the origin mesh.
I was using the answer at How to update vertices geometry after rotate or move object
Which worked fine with three.js release .70, but has broken on release .72
My code:
var volumeClone = new THREE.Mesh (this.mesh.geometry.clone(), new THREE.MeshBasicMaterial( { color: 0xff0000 } ));
volumeClone.scale.z = heightScale;
volumeClone.updateMatrix();
volumeClone.geometry.applyMatrix( volumeClone.matrix );
volumeClone.matrix.identity();
volumeClone.geometry.verticesNeedUpdate = true;
volumeClone.scale.set( 1, 1, 1 );
console(calculateVolume(volumeClone));
Result:
In Chrome is:
Uncaught TypeError: Cannot read property 'setFromPoints' of undefined
THREE.Geometry.computeBoundingBox
#lib.min.js:3THREE.Geometry.applyMatrix
What I have tried:
I have attempted to investigate and isolate the issue within Geometry.js and Box3.js as well as understand the programmatic flow to understand why "this.boundBox" is undefined, but I haven't found the issue.
Question:
Is the syntax correct? Was there an update to three.js in this area?
The answer turned out to be that extrudeGeometry does not contain the same properties as regular geometry and so doing work on it doesn't work since particulars are missing. The answer is to convert bufferGeometry to regular geometry before trying to work with it as regular geometry. I understand the performance benefits of using buffers, but in this case I can create a geometry for measurements and trash it when I get a result.
So rather than "clone" geometry as shown in my question, I switch to creating the particular type Geometry from the buffer instance:
var volumeClone = new THREE.Mesh (new THREE.Geometry().fromBufferGeometry( this.mesh.geometry._bufferGeometry ), new THREE.MeshBasicMaterial( { color: 0xff0000 } ));
volumeClone.scale.z = heightScale;
volumeClone.updateMatrix();
volumeClone.geometry.applyMatrix( volumeClone.matrix );
volumeClone.matrix.identity();
volumeClone.geometry.verticesNeedUpdate = true;
volumeClone.scale.set( 1, 1, 1 );
console(calculateVolume(volumeClone));
This has the added benefit of working with any geometry type I have thrown at it thus far: extrusions, imported mesh, primitives...

THREEJS Shader Material overwritten

I have a shader material that works as expected.
This shader has a texture attached.
I want to create 2 meshes using this shader material, with a different texture for each mesh.
Problem is that if I try to render my 2 meshes in the scene, the first object's material got overwritten somehow and uses the same material as the second object.
var dataShader = VJS.shaders.data;
var uniforms = dataShader.parameters.uniforms;
// texture size (2048 for now)
uniforms.uTextureSize.value = stack._textureSize;
// array of 16 textures
uniforms.uTextureContainer.value = textures;
// texture dimensions
uniforms.uDataDimensions.value = new THREE.Vector3(stack._columns, stack._rows, stack._nbFrames);
// world to model
uniforms.uWorldToData.value = stack._lps2IJK; //new THREE.Matrix4();
var sliceMaterial = new THREE.ShaderMaterial({
// 'wireframe': true,
'side': THREE.DoubleSide,
'transparency': true,
'uniforms': uniforms,
'vertexShader': dataShader.parameters.vertexShader,
'fragmentShader': dataShader.parameters.fragmentShader,
});
var slice = new THREE.Mesh(sliceGeometry, sliceMaterial);
// this is an Object3D that is added to the scene
this.add(slice);
Does it make sense? Is it the expected behavior? If so, is there a good way to bypass this issue?
Thanks
You need to create two instances of the material, using the same shader, and assign the appropriate texture/uniforms for each.
edit
Copying uniforms is a bit tricky. You lose the reference i think when you clone the material, so you might want to be careful how you manage them.

Face normals on dynamic geometry

I'm trying to create a vertex animation for a mesh.
Just imagine a vertex shader, but in software instead of hardware.
Basically what I do is to apply a transformation matrix to each vertex. The mesh it's ok but the normals doesn't look good at all.
I've try to use both computeVertexNormals() and computeFaceNormals() but it just doesn't work.
The following code is the one I used for the animation (initialVertices are the initial vertices generated by the CubeGeometry):
for (var i=0;i<mesh1.geometry.vertices.length; i++)
{
var vtx=initialVertices[i].clone();
var dist = vtx.y;
var rot=clock.getElapsedTime() - dist*0.02;
matrix.makeRotationY(rot);
vtx.applyMatrix4(matrix);
mesh1.geometry.vertices[i]=vtx;
}
mesh1.geometry.verticesNeedUpdate = true;
Here there're two examples, one working correctly with CanvasRenderer:
http://kile.stravaganza.org/lab/js/dynamic/canvas.html
and the one that doesn't works in WebGL:
http://kile.stravaganza.org/lab/js/dynamic/webgl.html
Any idea what I'm missing?
You are missing several things.
(1) You need to set the ambient reflectance of the material. It is reasonable to set it equal to the diffuse reflectance, or color.
var material = new THREE.MeshLambertMaterial( {
color:0xff0000,
ambient:0xff0000
} );
(2) If you are moving vertices, you need to update centroids, face normals, and vertex normals -- in the proper order. See the source code.
mesh1.geometry.computeCentroids();
mesh1.geometry.computeFaceNormals();
mesh1.geometry.computeVertexNormals();
(3) When you are using WebGLRenderer, you need to set the required update flags:
mesh1.geometry.verticesNeedUpdate = true;
mesh1.geometry.normalsNeedUpdate = true;
Tip: is it a good idea to avoid new and clone in tight loops.
three.js r.63

Categories

Resources