Exporting a simple model with texture from Blender to three.js - three.js

Note: I want to avoid modifying the model in the javascript code and do all the model design inside Blender.
Note #2: While this question is long, it is actually a basic problem (title says it all). The below is "walk-through" to the problem.
I am trying export Blender models to threejs.org as DAE model but have problem with models with texture (I have tried JSON and OBJ+MTL format too):
To make things simpler, here are the steps I perform (and fail) to add texture to simple "Startup file" which contains a cube, camera, and light:
Select the cube
In the Materials panel, make sure the default Material for the cube is selected.
In the Texture panel, make sure "Tex" (the default texture for the material) is selected.
For this texture, set Type to "Image or Movie"
In the Image section of panel, open a grass1.jpg (a 512x512 image) as the texture.
In the Mapping section, change the Coordinates to UV.
Export the model as Collada model, checking "Include UV Textures", "Include Material Textures", and "Copy" checkboxes.
You can download blend, dae, and texture file mentioned in these steps.
Then I use the following code to load the DAE model, but I get this error and the cube is not shown:
256 [.WebGLRenderingContext]GL ERROR :GL_INVALID_OPERATION :
glDrawElements: attempt to access out of range vertices in attribute 2
WebGL: too many errors, no more errors will be reported to the console for this context.
<script src="js/three.min.js"></script>
<script src="js/OrbitControls.js"></script>
<script src="js/ColladaLoader.js"></script>
<script>
var scene, camera, renderer;
init();
animate();
function init() {
scene = new THREE.Scene();
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(WIDTH, HEIGHT);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 10000);
camera.position.set(10,10,10);
scene.add(camera);
window.addEventListener('resize', function() {
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
var loader = new THREE.ColladaLoader();
loader.load( 'models/untitled.dae', function(geometry) {
dae = geometry.scene;
scene.add(dae);
var gridXZ = new THREE.GridHelper(100, 10);
gridXZ.setColors( new THREE.Color(0x8f8f8f), new THREE.Color(0x8f8f8f) );
gridXZ.position.set(0,0,0);
scene.add(gridXZ);
});
controls = new THREE.OrbitControls(camera, renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
controls.update();
}
</script>
And here is the screenshot of Blender after mentioned 7 steps:
Update: Exporting as js file using JSON exporter for Blender doesn't work either and resulted the very same error.
Update 2: Same error after exporting to OBJ+MTL and loading them with OBJMTLLoader.

The problem is you have not set up UV coordinates for your model. By default, each face applies the whole texture, but in blender the UVs are blank when exporting.
You want to specifically set up your UV coordinates. These are coordinates that show how to apply a texture to each face.
Make sure to UV unwrap your model in blender. Go to edit mode (tab), select all faces, press "u", and click "unwrap". Then try to re-export.
Unwrap is just 1 method, there are many. Experiment with different methods in blender to get the results you want (possibly the "reset" option).

Related

ThreeJS: White PNG image loaded as texture, used as material and rendered as plane has grey edges

I'm having an issue when rendering a white material in ThreeJS version 87.
Here are the steps to replicate:
A white PNG image that is loaded as texture
This texture is used to create a MeshBasicMaterial (passed as parameter map)
The MeshBasicMaterial is used along a plane Geometry to create a Mesh
The Mesh is added to an empty Scene and rendered on a WebGLRenderer with alpha: true and clearColor as white
The problem is that the rendered texture now has grey edges on parts that should be fully white.
This happens with any image with white edges. I've also tried many different configurations for the renderer and the material but to no avail.
I've made a very simple CodePen that replicates the behavior as simple as possible. Does anyone know how can this problem be solved?
CodePen:
https://codepen.io/ivan-i1/pen/pZxwZX
var renderer, width, height, scene, camera, dataUrl, threeTexture, geometry, material, mesh;
width = window.innerWidth;
height = window.innerHeight;
dataUrl = '//data url from image';
threeTexture = new THREE.ImageUtils.loadTexture(dataUrl);
material = new THREE.MeshBasicMaterial({
map: threeTexture,
transparent: true,
alphaTest: 0.1
});
material.needsUpdate = true;
geometry = new THREE.PlaneGeometry(5, 5);
mesh = new THREE.Mesh(geometry, material);
mesh.position.z = -5;
scene = new THREE.Scene();
scene.add(mesh);
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
renderer = new THREE.WebGLRenderer({
alpha: true
});
document.body.appendChild( renderer.domElement );
renderer.setSize(width, height);
renderer.setClearColor( 0xffffff, 1 );
//renderer.render(scene, camera);
function render() {
//Finally, draw to the screen
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
Any help is truly appreciated.
ThreeJS/87
Edit:
I think I'm lacking more precision on my post.
This is the original full alpha image:
It might not show because its all white
And this is the same image with different transparencies on 4 quadrants:
This one too might not show because its all white
I got a helpful answer where I was told to make the alphaTest higher, but the problem is that doing that wipes out the transparent parts out of the images, and I need to conserve those parts.
Here is a copy of the codepen with the updated images and showing the same (but slight) grey edges:
codepen
Sorry for not being as precise the first time, any further help is even more appreciated.
Set alphaTest to 0.9.. or higher.. observe the improvement.
Your star texture has gray or black in the area outside the star, which is why you're seeing a gray halo. You can fix it by filling the image with white, (but not changing the alpha channel) in your image editing tool.
Also, you should upgrade to latest three.js (r95)
edit:
I'm not sure what your exact expectation is.. but there are many different settings that control alpha blending in THREE. There is renderer.premultipliedAlpha = true/false (defaults to true) and material.transparent = true/false; material.alphaTest is a threshold value to control at what level alpha is ignored completely. There are also the material.blending, .blendEquation .blendEquation, .blendEquationAlpha, blendDst and blendSrc. etc. etc. You probably need to read up on those.
https://threejs.org/docs/#api/materials/Material
For instance.. here is your texture with:
renderer.premultipliedAlpha = false;
notice the black border on one quadrant of your texture.
https://codepen.io/manthrax/pen/KBraNB

Animations in Three.js with GLTF from Blender Exporter

I am experimenting with GLTF and Three.js, and I am having a devil of a time trying to get animations to work. My end goal is to be able to create keyframe animated meshes in Blender, export them to GLTF, and use them in Aframe-based WebVR scenes. At the moment, however, I'm just trying to get them to load and animate in a simple Three.js test harness page.
I'm trying to do a very basic test to get this working. I took Blender's default cube scene, removed the camera and the light, and created a keyframe animation to spin the cube 360 degrees around the Z axis. I then exported that cube to GLTF. First, I tried the GLTF export add on, and then I tried exporting it to Collada and using Cesium's tool to convert it to GLTF. Both versions of the GLTF file load and render the cube properly, but the cube does not animate.
I was able to use this same blend file and export to JSON using Three's own JSON export add on for Blender, and everything works fine there. So, I feel like I must be doing something stupid in my Javascript or there is something about GLTF I am missing.
Can anyone tell me what I'm doing wrong here? I'm getting to hair-yanking time here.
Here's the Javascript I'm trying to use for the GLTF version (specifically the binary version from Cesium's tool):
var scene = null;
var camera = null;
var renderer = null;
var mixer = null;
var clock = new THREE.Clock();
function init3D() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var ambientLight = new THREE.AmbientLight(0x080818);
scene.add(ambientLight);
var pointLight = new THREE.PointLight(0xffffff, 1, 100);
pointLight.position.set(-5, 1, 5);
scene.add(pointLight);
camera.position.z = 5;
camera.position.y = 1.5;
}
function loadScene() {
// Instantiate a loader
var loader = new THREE.GLTFLoader();
// Load a glTF resource
loader.load('gltf/SpinCube.glb',
function (gltf) {
var model = gltf.scene;
scene.add(model);
mixer = new THREE.AnimationMixer(model);
mixer.clipAction(gltf.animations[0]).play();
render();
});
}
function render() {
requestAnimationFrame(render);
var delta = clock.getDelta();
if (mixer != null) {
mixer.update(delta);
};
renderer.render(scene, camera);
}
init3D();
loadScene();
The problem appeared to have been a bug in the version of Three.js's GLTF2Loader that I was using at the time. I pulled a copy of Three.js from the dev branch, and my animations showed correctly.

Three.js & Dat.gui - TrackballControls renderer.domElement disables rotate and pan

I am trying to use dat.gui with a very simple three.js (r73) scene but am running into an issue with rotate and pan not working after adding "renderer.domElement" to the trackballControls initialization. Zoom works as expected.
Without renderer.domElement, I get a working rotate, zoom, pan functionality but the dat.gui interface sliders "latch" when clicked, which is just annoying and not functional. The issue as described here: Issue while using dat.GUI in a three.js example.
Looked over more info here but didn't see a great resolution: https://github.com/mrdoob/three.js/issues/828
Also found this issue. Defining the container element aka renderer.domElement doesn't work. I am unable to click within outside of the canvas area without the scene rotating.
Allow mouse control of three.js scene only when mouse is over canvas
Has anyone run into the same thing recently? If so, what workarounds are possible? Any help is appreciated.
-
The code is setup as follows:
// setup scene
// setup camera
// setup renderer
// ..
var trackballControls = new THREE.TrackballControls(camera, renderer.domElement);
trackballControls.rotateSpeed = 3.0;
trackballControls.zoomSpeed = 1.0;
trackballControls.panSpeed = 1.0;
// ..
// render loop
var clock = new THREE.Clock();
function render() {
stats.update();
var delta = clock.getDelta();
trackballControls.update(delta);
requestAnimationFrame( render );
renderer.render( scene, camera );
}
I debugged the issue with the help of this post: Three.js Restrict the mouse movement to Scene only.
Apparently, if you append the renderer.domElement child after initializing the trackballControls, it doesn't know anything about the renderer.domElement object. This also does something strange to dat.gui as described previously.
Basically, make sure this line:
document.getElementById("WebGL-output").appendChild(renderer.domElement);
appears before this line:
var trackballControls = new THREE.TrackballControls(camera, renderer.domElement);
Make sure the renderer DOM element is added to the html before it is being used as a reference.
document.body.appendChild(renderer.domElement);

Why does this ThreeJs plane appear to get a kink in it as the camera moves down the y-axis?

I have an instance of THREE.PlaneBufferGeometry that I apply an image texture to like this:
var camera, scene, renderer;
var geometry, material, mesh, light, floor;
scene = new THREE.Scene();
THREE.ImageUtils.loadTexture( "someImage.png", undefined, handleLoaded, handleError );
function handleLoaded(texture) {
var geometry = new THREE.PlaneBufferGeometry(
texture.image.naturalWidth,
texture.image.naturalHeight,
1,
1
);
var material = new THREE.MeshBasicMaterial({
map: texture,
overdraw: true
});
floor = new THREE.Mesh( geometry, material );
floor.material.side = THREE.DoubleSide;
scene.add( floor );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, texture.image.naturalHeight * A_BUNCH );
camera.position.z = texture.image.naturalWidth * 0.5;
camera.position.y = SOME_INT;
camera.lookAt(floor.position);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth,window.innerHeight);
appendToDom();
animate();
}
function handleError() {
console.log(arguments);
}
function appendToDom() {
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene,camera);
}
Here's the code pen: http://codepen.io/anon/pen/qELxvj?editors=001
( Note: ThreeJs "pollutes" the global scope, to use a harsh term, and then decorates THREE using a decorator pattern--relying on scripts loading in the correct order without using a module loader system. So, for brevity's sake, I simply copy-pasted the source code of a few required decorators into the code pen to ensure they load in the right order. You'll have to scroll down several thousand lines to the bottom of the code pen to play with the code that instantiates the plane, paints it and moves the camera. )
In the code pen, I simply lay the plane flat against the x-y axis, looking straight up the z-axis, as it were. Then, I slowly pan the camera down along the y-axis, continuously pointing it at the plane.
As you can see in the code pen, as the camera moves along the y-axis in the negative direction, the texture on the plane appears to develop a kink in it around West Texas.
Why? How can I prevent this from happening?
I've seen similar behaviour, not in three.js, not in a browser with webGL but with directX and vvvv; still, i think you'll just have to set widthSegments/heightSegments of your PlaneBufferGeometry to a higher level (>4) and you're set!

Three js - Referencing individual meshes in imported blender scene

I am new to three.js and I have a basic blender scene with 2 different meshes which I have also named. I have managed to import the meshes into three but I would like to know how to reference and manipulate each mesh? is it possible if the 2 meshes are in the same file or should I load mesh1.js, mesh2.js etc?
This is the code:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body style="margin: 0;">
<script src="https://rawgithub.com/mrdoob/three.js/master/build/three.js"></script>
<script src="js/OrbitControls.js"></script>
<script>
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
init();
animate();
// Sets up the scene.
function init() {
// Create the scene and set the scene size.
scene = new THREE.Scene();
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
// Create a renderer and add it to the DOM.
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(WIDTH, HEIGHT);
document.body.appendChild(renderer.domElement);
// Create a camera, zoom it out from the model a bit, and add it to the scene.
camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 20000);
camera.position.set(0,6,0);
scene.add(camera);
// Create an event listener that resizes the renderer with the browser window.
window.addEventListener('resize', function() {
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
// Set the background color of the scene.
renderer.setClearColorHex(0x333F47, 1);
// Create a light, set its position, and add it to the scene.
var light = new THREE.PointLight(0xffffff);
light.position.set(-100,-200,100);
scene.add(light);
var light2 = new THREE.PointLight(0xffffff);
light2.position.set(-100,200,100);
scene.add(light2);
// Load in the mesh and add it to the scene.
var loader = new THREE.JSONLoader();
loader.load( "tree-land.js", function( geometry, materials ){
var material = new THREE.MeshFaceMaterial( materials );
mesh = new THREE.Mesh( geometry, material );
scene.add(mesh);
});
// Add OrbitControls so that we can pan around with the mouse.
controls = new THREE.OrbitControls(camera, renderer.domElement);
}
// Renders the scene and updates the render as needed.
function animate() {
requestAnimationFrame(animate);
// Render the scene.
renderer.render(scene, camera);
controls.update();
}
</script>
</body>
</html>
The mesh variable you have declared is an Object3D, which can be manipulated using all the methods and properties as seen in the documentation: http://threejs.org/docs/#Reference/Core/Object3D
It looks like the call to JSONLoader.load returns a single geometry and material via the callback, so would not seem to support loading multiple files in a single call or multiple geometries in a single file. You might want to take a look at some of the other loaders for that purpose. I have successfully used the ColladaLoader (not in the documentation). Blender exports to Collada as well. The code is in examples/js/loaders of the repository.

Resources