I learn how to use Three.js by following the tutorial on discoverthreejs.com.
I have no worries about creating meshes and geometry via three.js
The problem is when I want to load models coming from blender or others.
I use blender 2.8 to create my model and export it as a .glb file. I test the file with a gtlf viewer and everything works as expected.
But as soon as I want to import my model with Three.js to my website, I get this error:
I thought it came from my model, I tried to export it in gltf or glb: same error.
I downloaded another model available on the web: same error.
I use parcel.js if it helps.
{
"name": "cedric_grvl",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"clean": "rm -rf dist",
"dev": "parcel src/index.html --host 192.168.0.37 --open Firefox"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"autoprefixer": "^9.7.3",
"parcel-bundler": "^1.12.4",
"postcss-custom-properties": "^9.0.2",
"postcss-modules": "^1.4.1",
"postcss-preset-env": "^6.7.0",
"sass": "^1.23.7",
"three": "^0.111.0"
}
}
Everything is test in my index.js.
Here is how I call Three.js: (all is good here)
*index.js*
import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
Here are the function for Three.js (tutorial)(all good here)
*index.js*
// these need to be accessed inside more than one function so we'll declare them first
let container;
let camera;
let controls;
let renderer;
let scene;
let mesh;
function init() {
container = document.querySelector( `[data-js="canvas"]` );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xFFFFFF );
createCamera();
createControls();
createLights();
createMeshes();
createRenderer();
// start the animation loop
renderer.setAnimationLoop( () => {
update();
render();
} );
}
function createCamera() {
camera = new THREE.PerspectiveCamera(
35, // FOV
container.clientWidth / container.clientHeight, // aspect
0.1, // near clipping plane
100, // far clipping plane
);
camera.position.set( -4, 4, 10 );
}
function createControls() {
controls = new OrbitControls( camera, container );
}
function createLights() {
const ambientLight = new THREE.HemisphereLight(
0xddeeff, // sky color
0x202020, // ground color
5, // intensity
);
const mainLight = new THREE.DirectionalLight( 0xffffff, 5 );
mainLight.position.set( 10, 10, 10 );
scene.add( ambientLight, mainLight );
}
function createMeshes() {
const geometry = new THREE.BoxBufferGeometry( 2, 2, 2 );
const material = new THREE.MeshStandardMaterial( { color: 0x800080 } );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
}
function createRenderer() {
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( container.clientWidth, container.clientHeight );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.gammaFactor = 2.2;
renderer.gammaOutput = true;
renderer.physicallyCorrectLights = true;
container.appendChild( renderer.domElement );
}
// perform any updates to the scene, called once per frame
// avoid heavy computation here
function update() {
// Don't delete this function!
}
// render, or 'draw a still image', of the scene
function render() {
renderer.render( scene, camera );
}
// a function that will be called every time the window gets resized.
// It can get called a lot, so don't put any heavy computation in here!
function onWindowResize() {
// set the aspect ratio to match the new browser window aspect ratio
camera.aspect = container.clientWidth / container.clientHeight;
// update the camera's frustum
camera.updateProjectionMatrix();
// update the size of the renderer AND the canvas
renderer.setSize( container.clientWidth, container.clientHeight );
}
window.addEventListener( 'resize', onWindowResize );
// call the init function to set everything up
init();
Problem is here maybe I do something wrong.
const loader = new GLTFLoader();
const url = "./assets/models/test.glb";
// Here, 'gltf' is the object that the loader returns to us
const onLoad = ( gltf ) => {
console.log( gltf );
};
loader.load( url, onLoad );
I've been thinking about a problem with the path
I tried :
'/src/assets/models/test.glb'
'assets/models/test.glb'
Here is my folder structure:
Thx for your time
In your code import the model like this
import MODEL from './assets/Horse.glb'
Model will be the path to the glb asset then use it to load like this:
loader.load( MODEL, function ( glb ) {
that.scene.add( glb.scene );
}, undefined, function ( error ) {
console.error( error );
});
I found a solution discourse.threejs.org
const parcelPath = new URL('./public/models/hands.glb', import.meta.url);
loader.load( parcelPath.href , function ( glb ) {
that.scene.add( glb.scene );
});
I am trying to add a level .obj for my program but it renders black. The .mtl file requires several images placed everywhere (not one space is non-textured). I used to same object in my last project and it works, but it doesn't in my current project. When I remove the materials the lighting affects it, but when I add it, it is pitch black. The renderer renders continously. Also, there are no errors in the console.
Here is the code used in my current project: (MaterialLoader is an MTLLoader instance and ObjectLoader is an OBJLoader instance)
MaterialLoader.load("bbb/bbb.mtl",
function(materials) {
materials.preload()
ObjectLoader.setMaterials(materials)
ObjectLoader.load("bbb.obj",
function(model) {
let mesh = model.children[0]
scene.add(mesh)
}, null, function(error) {alert(error)}
)
}, null, function(error) {alert(error)}
)
Here is the code from my previous project (the loader variable is an OBJLoader instance, and the materials load successfully here.)
mtlLoader.load(
"bbb.mtl",
function(materials) {
materials.preload()
loader.setMaterials(materials)
loader.load("bbb.obj",
function(obj) {
level = obj.children[0]
scene.add(level)
}, null,
function(error) { alert(error) }
)
}, null,
function(error) { alert(error) }
)
https://discourse.threejs.org/t/objloader-mtlloader-not-loading-texture-image/2534
try change object material color, like this:
model.children[0].material.color.r = 1;
model.children[0].material.color.g = 1;
model.children[0].material.color.b = 1;
its work for me
Your code works when tested! Maybe it's an issue with the material export, uv unwrapping, the texture's path, do you have lighting added to the scene, etc. Here's my test code:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75,320/240,1,500);
camera.position.set( 0,2,2 );
camera.lookAt( scene.position );
var lightIntensity = 1;
var lightDistance = 10;
var light = new THREE.AmbientLight( 0xFFFFFF, lightIntensity, lightDistance );
light.position.set( 0, 5, 0 );
scene.add( light );
var grid = new THREE.GridHelper(10,10);
scene.add( grid );
var renderer = new THREE.WebGLRenderer({});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( 320,240 );
renderer.domElement.style.margin = '0 auto';
renderer.domElement.style.display = 'block';
renderer.domElement.style.backgroundColor = '#dddddd';
$(document.body).append(renderer.domElement);
function update(){
renderer.render( scene, camera );
requestAnimationFrame( update );
}
update();
mtl_loader = new THREE.MTLLoader();
mtl_loader.load("assets/doughnut.mtl",
function(materials) {
materials.preload()
var obj_loader = new THREE.OBJLoader();
obj_loader.setMaterials(materials)
obj_loader.load("assets/doughnut.obj",
function(object) {
let mesh = object.children[0]
scene.add(mesh);
}, null, function(error) {alert(error)}
)
}, null, function(error) {alert(error)}
);
I am trying to make a skybox, and every tutorial I have tried will not work. I thought I could make an array and pass it as a param for the material like I saw in an earlier example, but the method has apparently changed to TextureLoader() since then. Below is my code:
// Adds a skybox around the content
var skyBoxMaterials = [
new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader( 'images/skybox/sky1.jpg') }),
new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader( 'images/skybox/sky2.jpg') }),
new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader( 'images/skybox/sky3.jpg') }),
new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader( 'images/skybox/sky4.jpg') }),
new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader( 'images/skybox/sky5.jpg') }),
new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader( 'images/skybox/sky6.jpg') }),
];
var skyBoxGeom = new THREE.CubeGeometry( 10000, 10000, 10000, 1, 1, 1);
skyBox = new THREE.Mesh( skyBoxGeom, skyBoxMaterials );
skyBox.position.set(0, 25.1, 0);
scene.add( skyBox );
When I run it currently, I get the error "Uncaught TypeError: Cannot read property 'x' of undefined" in the console infinitely looping until the server is killed. I could not find the exact answer in the examples, docs, or in another question, here. Any suggestions?
UPDATE: After some more digging, I finally found the example I needed in the docs under cubeGeometry, but it still does not render. My code is below:
// Adds a skybox around the content
var loader = new THREE.CubeTextureLoader();
loader.setPath( 'images/skybox/' );
var textureCube = loader.load( [
'sky1.jpg', 'sky2.jpg',
'sky3.jpg', 'sky4.jpg',
'sky5.jpg', 'sky6.jpg'
] );
var skyMaterials = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap:
textureCube } );
var skyBoxGeom = new THREE.CubeGeometry( 10000, 10000, 10000, 1, 1, 1);
skyBox = new THREE.Mesh( skyBoxGeom, skyMaterials );
skyBox.position.set(0, 25.1, 0);
scene.add( skyBox );
I do not have any error messages in the console, but the cube is not rendering at all. The other objects in the scene render normally.
You can use .background property of THREE.Scene() straight, as it accepts cube textures.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( 0, 0, 300 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var controls = new THREE.OrbitControls( camera, renderer.domElement );
var loader = new THREE.CubeTextureLoader();
loader.setCrossOrigin( "" );
loader.setPath( 'https://threejs.org/examples/textures/cube/skybox/' );
var cubeTexture = loader.load( [
'px.jpg', 'nx.jpg',
'py.jpg', 'ny.jpg',
'pz.jpg', 'nz.jpg'
] );
scene.background = cubeTexture;
render();
function render() {
requestAnimationFrame(render);
renderer.render( scene, camera );
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
Have you downloaded the Three.js Master file? It has all the examples in working order. So all the examples you see on the Examples Page are there along with support files, textures assets etc. This way, you can start your project with a working example and build from there. You will need to run them from your local server (from your post, I gather you already know this).
There are a few examples that may help you like Panorama Cube. In the examples directory of the download, you will find a file called 'webgl_panorama_cube.html' that will be your local copy of this example.
I have in this fiddle an example of the problem I'm having: http://jsfiddle.net/saward/78Bjk/7/
If you uncomment scene.add(tree_mesh) and scene.add(mesh2), and comment out scene.add(mesh) then you can see both objects. But when I merge both, the material information for tree_mesh seems to be lost. If I modify the mesh to use the same material as mesh2, then both objects display but obviously with the wrong material.
I would appreciate help in understanding what's going on here and how to fix it!
Thanks
Here is the code from the fiddle (requires three.js r57):
var camera, scene, renderer, geometry, material, mesh1, mesh2, mesh;
init();
animate();
function init() {
var tree = {
"metadata" :
{
"formatVersion" : 3.1,
"sourceFile" : "tree.obj",
"generatedBy" : "OBJConverter",
"vertices" : 24,
"faces" : 18,
"normals" : 0,
"colors" : 0,
"uvs" : 0,
"materials" : 1
},
"scale" : 1.000000,
"materials": [ {
"DbgColor" : 15658734,
"DbgIndex" : 0,
"DbgName" : "Material",
"colorAmbient" : [0.0, 0.0, 0.0],
"colorDiffuse" : [0.64, 0.64, 0.64],
"colorSpecular" : [0.5, 0.5, 0.5],
"illumination" : 2,
"opticalDensity" : 1.0,
"specularCoef" : 96.078431,
"transparency" : 1.0
}],
"vertices": [1.000000,-1.000000,-1.000000,1.000000,-1.000000,1.000000,-1.000000,-1.000000,1.000000,-1.000000,-1.000000,-1.000000,0.590806,-0.802478,-0.590806,0.590806,-0.802478,0.590807,-0.590806,-0.802478,0.590806,-0.590806,-0.802478,-0.590806,0.406036,0.737103,-0.406036,0.406036,0.737103,0.406036,-0.406036,0.737103,0.406036,-0.406036,0.737103,-0.406036,0.406036,-0.810673,-0.406036,0.406036,-0.810673,0.406036,-0.406036,-0.810673,0.406036,-0.406036,-0.810673,-0.406036,-0.703524,0.091037,0.703524,-0.703524,0.091037,-0.703524,0.703524,0.091037,-0.703524,0.703524,0.091037,0.703524,-0.703524,1.498086,0.703524,-0.703524,1.498086,-0.703524,0.703524,1.498086,-0.703524,0.703524,1.498086,0.703524],
"morphTargets": [],
"morphColors": [],
"normals": [],
"colors": [],
"uvs": [[]],
"faces": [3,0,1,2,3,0,3,4,7,6,5,0,3,0,4,5,1,0,3,1,5,6,2,0,3,2,6,7,3,0,3,4,0,3,7,0,3,12,8,9,13,0,3,13,9,10,14,0,3,14,10,11,15,0,3,15,11,8,12,0,3,8,9,10,11,0,3,15,14,13,12,0,3,20,21,17,16,0,3,21,22,18,17,0,3,22,23,19,18,0,3,23,20,16,19,0,3,16,17,18,19,0,3,23,22,21,20,0]
};
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 500;
scene.add(camera);
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
var loader = new THREE.JSONLoader();
var tree_obj = loader.parse(tree, null);
var tree_materials = tree_obj.materials;
var tree_face_materials = new THREE.MeshFaceMaterial(tree_materials);
var tree_geo = tree_obj.geometry;
var tree_mesh = new THREE.Mesh(tree_geo, tree_face_materials);
tree_mesh.scale.x = tree_mesh.scale.y = tree_mesh.scale.z = 100;
mesh2 = new THREE.Mesh(geometry, material);
mesh2.position.y = 200;
console.log(tree_mesh.scale);
//scene.add(tree_mesh);
//scene.add(mesh2);
materials = [];
THREE.GeometryUtils.setMaterialIndex(tree_mesh.geometry, 0);
THREE.GeometryUtils.setMaterialIndex(mesh2.geometry, 1);
materials.push(tree_face_materials);
materials.push(material);
var new_geo = new THREE.Geometry();
THREE.GeometryUtils.merge(new_geo, tree_mesh);
THREE.GeometryUtils.merge(new_geo, mesh2);
mesh = new THREE.Mesh(new_geo, new THREE.MeshFaceMaterial(materials));
scene.add(mesh);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
}
Update: The way I specifically solved this was by using the current dev version of three.js 58, to add the materials for each model to a larger array.
As noted by the accepted answer, you can't use MeshFaceMaterial as a material, so iterate over all the materials within the MeshFaceMaterial, adding them in order.
Then note the index of the first material in the larger array for each model. Then when it comes time to merge the meshes, instead of using setMaterialIndex, give it the index of the first material in the array for that specific model. Along these lines:
THREE.GeometryUtils.merge(large_geo, some_mesh, some_mesh.material_offset);
large_mesh = new THREE.Mesh(large_geo, new THREE.MeshFaceMaterial(materials_list));
Where "some_mesh.material_offset" will be a new value you set and store for yourself somewhere. This will not work with r57 or lower.
When using MeshFaceMaterial( materials ), then materials must be an array of materials. You can't include MeshFaceMaterial as one of the materials.
In your case, it would be:
materials = [];
materials.push( tree_obj.materials[0] );
materials.push( material );
mesh = new THREE.Mesh( new_geo, new THREE.MeshFaceMaterial( materials ) );
Fiddle: http://jsfiddle.net/78Bjk/8/
three.js r.57
My model loads fine with this code:
loader.load( "js/charWalk01.js", function( geometry, materials ) {
mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial() );
scene.add( mesh );
} );
However, when I try to use the MeshFaceMaterial (so as to use the material in the JSON file), I get two really odd three.min.js error messages (below).
loader.load( "js/charWalk01.js", function( geometry, materials ) {
materials[ 0 ].morphTargets = true;
mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial() );
scene.add( mesh );
} );
The errors are:
TypeError: 'undefined' is not an object (evaluating 'a.map') three.min.js:347
TypeError: 'undefined' is not an object (evaluating 'ma.attributes') three.min.js:429
The JSON file is perfectly normal (created with the OBJ converter), here's the material code from it:
"materials": [ {
"DbgColor" : 15658734,
"DbgIndex" : 0,
"DbgName" : "Mat.1",
"colorDiffuse" : [1.0, 1.0, 1.0],
"colorSpecular" : [0.4, 0.52, 0.53],
"illumination" : 4,
"mapDiffuse" : "Character_01.jpg"
}],
Any help as to why those errors might appear?
Cheers,
Ian
You need to pass materials as an argument to MeshFaceMaterials, like so:
loader.load( "js/charWalk01.js", function( geometry, materials ) {
materials[ 0 ].morphTargets = true;
mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial( materials ) );
scene.add( mesh );
} );
three.js r.53