I'm importing 3D models into three.js using MTLLoader and OBJLoader. The .obj files contain no normals so the resulting models look blocky. I'm attempting to smooth them by converting the BufferGeometry returned by OBJLoader into a Geometry object, and then calculating the normals. This works perfectly for many objects, but some of them turn almost completely black.
Investigating this, I noticed that on the models that turn black the vertex normals (computed by computeVertexNormals()) are (0, 0, 0) for almost every vertex, while on the normal-looking models they are decimal values between -1 and 1.
Here is my code:
function loadModel() {
// Load the materials first
mtlLoader.load('texture.mtl', function (materials) {
materials.preload();
// Load the object and attach the materials
objLoader
.setMaterials(materials)
.load('model.obj', function (object) {
object.children[0].geometry = new THREE.Geometry().fromBufferGeometry(object.children[0].geometry);
object.children[0].geometry.computeFaceNormals();
object.children[0].geometry.mergeVertices();
object.children[0].geometry.computeVertexNormals();
scene.add(object);
}, undefined, function (error) {
console.log(error);
});
}, undefined, function (error) {
console.log(error);
}
);
}
How can I fix/prevent this?
Related
I've imported a 3D model into a THREE.Js project I've been working on, and I want to add several copies of them to the scene. Here's the code I've been using to duplicate it:
let ball = new THREE.Mesh();
loader.load( './ball.gltf', function ( gltf ) {
gltf.scene.traverse(function(model) { //for gltf shadows!
if (model.isMesh) {
model.castShadow = true;
model.material = sphereMaterial;
}
});
ball = gltf.scene;
scene.add( ball );
}, undefined, function ( error ) {
console.error( error );
} );
const ball2 = ball.clone()
ball2.position.set(0.5, 0.5, 0.5)
scene.add(ball2)
However, only one of the "balls" show up in the scene from the loader.load() call. Does anyone happen to know what I should do differently to successfully duplicate the model?
Your onLoad() handler (function ( gltf ) {...}) is asynchronous, and the code that clones ball (const ball2 = ball.clone()) is executing before ball is initialized with the GLTF data. So at the time ball.clone() executes, ball is simply the empty Mesh you created before loading the GLTF, and that empty Mesh is what gets cloned.
I suspect you were, at some point, getting a console error relating to reading clone on undefined, and that's why you added the line to initialize ball to an empty Mesh, which is unnecessary.
There are a few ways to handle this, but the simplest is to move the code that clones ball into the onLoad handler (i.e., after scene.add( ball )).
Proof of concept here.
I would like to rotate the coordinate axes of specific segments of a .fbx model. E.g. as illustrated on the image I would like to rotate the coordinate axes of the right arm 90 degress around the Z-axis.
I can see that THREE.Object3D.DefaultUp can change the default coordinate system of an object, but I cannot apply it to the fbx segment objects.
Just to be clear, I dont want to rotate the mesh/arm, but only the coordinate axes that define the arms rotation. Is that possible?
I load the .fbx model and define the arm as so:
// model
const loader = new THREE.FBXLoader();
loader.load( '{{ url_for('static', filename='ybot.fbx') }}', function ( object ) {
myObj = object;
myObj.traverse( function ( child ) {
if ( child.isMesh ) {
child.castShadow = true;
child.receiveShadow = true;
}
});
rightArm = myObj.getObjectByName('mixamorigRightArm');
// Do the rotations here
scene.add( myObj );
});
}
I'm actually trying to add some materials to my Gltf objects in three js r113, but I don't know how to use correctly material parameters and the light as I see my object in three js viewer. This is What I get in Firefox
and this is what I'm dreaming about
.
I guess this is the value I need to apply to my code
This is how I add my gltf and how I try to add materials :
// Load a glTF resource of FENCE
gltfLoader.load( 'Fence.gltf', function ( gltf ) {
fenceModel = gltf.scene;
// fenceModel.traverse(function (child) {
// if (child.isMesh) {
// child.material = new THREE.MeshLambertMaterial({
// color: 0xc07341, //light Brown
// reflectivity: 0,
// dithering: true
// });
// }
// });
});
var ambientlight = new THREE.AmbientLight(0xffffff, 0.5 );
scene.add( ambientlight );
Actually my floor is only a gltf file with no materials.
Maybe I need to add some shadow porperties to my floor and then I could see the fence shadow ?
I need some help to understand how to do a better light effect on object.
Thank you, sorry for my english I'm using translator to help me.
Ps: My gltf object contain texture in it.
I've been learning how to integrate ThreeJS with Mapbox, using this example. It struck me as weird that the approach is to leave the loaded model in its own coordinate system, and transform the camera location on render. So I attempted to rewrite the code, so that the GLTF model is transformed when loaded, then the ThreeJS camera is just synchronised with the Mapbox camera, with no further modifications.
The code now looks like this:
function newScene() {
const scene = new THREE.Scene();
// create two three.js lights to illuminate the model
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
scene.add(directionalLight);
const directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
scene.add(directionalLight2);
return scene;
}
function newRenderer(map, gl) {
// use the Mapbox GL JS map canvas for three.js
const renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
renderer.autoClear = false;
return renderer;
}
// create a custom layer for a 3D model per the CustomLayerInterface
export function addModel(modelPath, origin, altitude = 0, orientation = [Math.PI / 2, 0, 0]) {
const coords = mapboxgl.MercatorCoordinate.fromLngLat(origin, altitude);
// transformation parameters to position, rotate and scale the 3D model onto the map
const modelTransform = {
translateX: coords.x,
translateY: coords.y,
translateZ: coords.z,
rotateX: orientation[0],
rotateY: orientation[1],
rotateZ: orientation[2],
/* Since our 3D model is in real world meters, a scale transform needs to be
* applied since the CustomLayerInterface expects units in MercatorCoordinates.
*/
scale: coords.meterInMercatorCoordinateUnits()
};
const scaleVector = new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale)
return {
id: "3d-model",
type: "custom",
renderingMode: "3d",
onAdd: function(map, gl) {
this.map = map;
this.camera = new THREE.Camera();
this.scene = newScene();
this.renderer = newRenderer(map, gl);
// use the three.js GLTF loader to add the 3D model to the three.js scene
new THREE.GLTFLoader()
.load(modelPath, gltf => {
gltf.scene.position.fromArray([coords.x, coords.y, coords.z]);
gltf.scene.setRotationFromEuler(new THREE.Euler().fromArray(orientation));
gltf.scene.scale.copy(scaleVector);
this.scene.add(gltf.scene);
const bbox = new THREE.Box3().setFromObject(gltf.scene);
console.log(bbox);
this.scene.add(new THREE.Box3Helper(bbox, 'blue'));
});
},
render: function(gl, matrix) {
this.camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
// this.map.triggerRepaint();
}
}
}
It basically works, in that a model is loaded and drawn in the right location in the Mapbox world. However, instead of looking like this:
It now looks like this, a mangled mess that jitters around chaotically as the camera moves:
I'm not yet familiar enough with ThreeJS to have any idea what I did wrong.
Here's a side-by-side comparison of the old, functional code on the right, vs the new broken code on the left.
Further investigation
I suspect possibly the cause is to do with shrinking all the coordinates down to within the [0..1] range of the projected coordinate system, and losing mathematical precision, perhaps. When I scale the model up by 100 times, it renders like this - messy and glitchy, but at least recognisable as something.
I am trying to apply a texture to a simple 3-d cube model exported from Blender 2.78b (as a matter of fact, the default blender cube). I exported the model in the standard three.js JSON format. I realize that I should be able to apply a texture in Blender on top of the geometry, but I want to "style" the blender model in three.js at runtime. Thus I am only interested in using blender to provide the geometry for my mesh.
I have created a plunker illustrating the situation.
I load in a blender model as blenderGeom, and then apply a MeshBasicMaterial with map set to a brick texture (unfortunately, I have to load the texture as Base64, since plunker doesn't allow you to upload images). I then apply the exact same material/texture to a native three.js BoxGeometry nonBlenderCubeGeom:
function loadModel() {
console.log('now in loadModel');
var promise = new Promise( (resolve, reject) => {
var loader = new THREE.JSONLoader();
// load a resource
loader.load(
'cube.json', (blenderGeom, materials) => {
console.log(`loadModel: now loading cube: geomery=${geometry}`);
let cubeMaterial = new THREE.MeshBasicMaterial({
color: 0xff8080,
wireframe: false,
map: brickTexture
})
nonBlenderCubeGeom = new THREE.BoxGeometry(50, 50, 50);
blenderCubeGeom = blenderGeom;
blenderCube = new THREE.Mesh(blenderGeom, cubeMaterial); //no work
blenderCube.position.x = -20;
nonBlenderCube = new THREE.Mesh(nonBlenderCubeGeom, cubeMaterial); //work
nonBlenderCube.position.x = 20;
blenderCube.scale.set(10, 10, 10);
scene.add(blenderCube);
scene.add(nonBlenderCube);
As you can see from running the plunker, only the native three.js object on the right is textured, and the blender model on the left is not:
Am I doing something wrong? Is it simply a restriction that you can only texture a model in Blender?
Three.js r84. Blender 2.78b. I'm assuming this on three.js side, but I'm opening it up to blender as well.
Many Thanks.