Animations in Three.js with GLTF from Blender Exporter - three.js

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.

Related

GLTF animation is not working in Three js

I'm struggling to get an animation to play together with my GLTF 3D model. Most similar issues that I've seen on Stack Overflow are relating to the mixer not being updated. Which is not the problem in my case.
This is my fiddle: https://jsfiddle.net/rixi/djqz1nb5/11/
import * as THREE from "https://threejsfundamentals.org/threejs/resources/threejs/r122/build/three.module.js";
import { GLTFLoader } from "https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/loaders/GLTFLoader.js";
import { OrbitControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/controls/OrbitControls.js";
let clock, controls, scene, camera, renderer, mixer, container, model;
initScene();
animate();
function initScene() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
clock = new THREE.Clock();
renderer = new THREE.WebGLRenderer();
controls = new OrbitControls(camera, renderer.domElement);
controls.update();
container = document.getElementById("container");
container.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);
}
scene.background = new THREE.Color("#f8edeb");
// LIGHT
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(2, 2, 5);
//HELPERS
const axesHelper = new THREE.AxesHelper(5);
let gridHelper = new THREE.GridHelper(30, 30);
scene.add(light, axesHelper, gridHelper);
//GLTF START
const GLTFloader = new GLTFLoader();
GLTFloader.load("https://richardlundquist.github.io/library/alice_TEST2.glb", function (gltf) {
model = gltf;
mixer = new THREE.AnimationMixer(gltf.scene);
mixer.clipAction(gltf.animations[0]).play();
scene.add(model.scene);
});
camera.position.set(0, 20, 50);
function animate() {
requestAnimationFrame(animate);
let delta = clock.getDelta();
if (mixer) {
mixer.update(clock.getDelta());
}
renderer.render(scene, camera);
}
There is no error in the console. The animation is listed in the array and plays as it should in Don McCurdy's GLTF viewer (https://gltf-viewer.donmccurdy.com/)
But for some reason it will not play in my three js setup. Any clues? I would be extremely grateful for any help or hints on how to solve the issue.
I found two critical errors here.
At the top of your code, you pull in Three r122 with GLTFLoader r132. These need to be the same revision. Try setting them all to r132.
You call getDelta() twice here:
let delta = clock.getDelta();
if (mixer) {
mixer.update(clock.getDelta());
}
The second call to getDelta() comes immediately after the first, so always returns zero delta time. Thus, the animation never moves forward.

Manually specifying camera matrices in ThreeJS

I'm working on a project where I will draw 3D graphics on a video that is filmed with a real camera. I get provided a static projection and view matrix and my task is to draw the actual graphics on top. I've got it working in pure WebGL and know I'm trying to do it in ThreeJS. The problem is that I can't find a way to manually set the projection and view matrix for the camera in ThreeJS.
I've tried to set camera.matrixAutoUpdate = false and calling camera.projectionMatrix.set(matrix) and camera.matrixWorldInverse.set(matrix), but it doesn't seam to work. It is like the actual variable isn't passed to the shader.
Does anyone know how this can be done?
This is what I've got so far:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.matrixAutoUpdate = false;
camera.projectionMatrix.set(...)
camera.matrixWorldInverse.set(...)
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.BoxGeometry();
var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
var animate = function() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();

Using OutlinePass (THREE.js r102) with skinned mesh

/examples/js/postprocessing/OutlinePass.js from THREE.js r102 does not appear to work with skinned meshes. Specifically, the rendered outline always stays in the mesh's rest position.
Is there some way to get this working (that is, to update the outline to reflect the current pose of an animated mesh)? OutlinePass does not appear to be documented (mod the comments in the code itself).
Is there some other accepted method of outlining animated meshes? I'm in the process of migrating some code from r7x, where I ended up accomplishing this by manually creating a copy of the mesh and applying a shader material that scales along the normals. I can do that again, but if there's a simpler/better supported method to accomplish the same effect I'd rather use it instead of reproducing a method that breaks every new major release.
A simple jsfiddle illustrating the issue:
https://jsfiddle.net/L69pe5q2/3/
This is the code from the jsfiddle. The mesh I use is the SimpleSkinning.gltf example from the three.js distribution. In the jsfiddle I load it from a dataURI so it doesn't complain about XSS loading, and I've edited the base64-encoded data out (and replaced it with [FOO]) in the code below, purely for readability.
The OutlinePass is created and added to the composer in initComposer().
var camera, light, renderer, composer, mixer, loader, clock;
var scene, mesh, outlinePass;
var height = 480,
width = 640;
var clearColor = '#666666';
load();
function load() {
loader = new THREE.GLTFLoader();
clock = new THREE.Clock();
scene = new THREE.Scene();
loader.load('data:text/plain;base64,[FOO]', function(obj) {
scene.add(obj.scene);
mixer = new THREE.AnimationMixer(obj.scene);
var clip = THREE.AnimationClip.findByName(obj.animations,
'Take 01');
var a = mixer.clipAction(clip);
a.reset();
a.play();
mesh = obj.scene;
mesh.position.set(-7, 2.5, -7);
init();
animate();
});
}
function init() {
initCamera();
initScene();
initRenderer();
initComposer();
outlinePass.selectedObjects = [mesh];
}
function initCamera() {
camera = new THREE.PerspectiveCamera(30, width / height, 1, 10000);
camera.position.set(7, 0, 7);
camera.lookAt(0, 0, 0);
}
function initScene() {
light = new THREE.AmbientLight(0xffffff)
scene.add(light);
}
function initRenderer() {
renderer = new THREE.WebGLRenderer({
width: width,
height: height,
antialias: false,
});
renderer.setSize(width, height);
renderer.setClearColor(clearColor);
document.body.appendChild(renderer.domElement);
}
function initComposer() {
var renderPass, copyPass;
composer = new THREE.EffectComposer(renderer);
renderPass = new THREE.RenderPass(scene, camera);
composer.addPass(renderPass);
outlinePass = new THREE.OutlinePass(new THREE.Vector2(width, height),
scene, camera);
composer.addPass(outlinePass);
outlinePass.edgeStrength = 10;
outlinePass.edgeThickness = 4;
outlinePass.visibleEdgeColor.set('#ff0000');
copyPass = new THREE.ShaderPass(THREE.CopyShader);
copyPass.renderToScreen = true;
composer.addPass(copyPass);
}
function animate() {
var delta = clock.getDelta();
requestAnimationFrame(animate);
update(delta);
render(delta);
}
function update(delta) {
if (mixer) mixer.update(delta);
}
function render(delta) {
composer.render();
}
according to Mugen87 in Jan 2019 he said:
With this small patch, it's now possible to use the outline pass with animated meshes. The only thing users have to do at app level is to set morphTargets or skinning to true for OutlinePass.depthMaterial and OutlinePass.prepareMaskMaterial. That's of course still a manual effort but at least the more complicated shader enhancement is already done.
take this example:
https://jsfiddle.net/2ybks7rd/
reference link on github

Exporting a simple model with texture from Blender to 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).

Three.js OBJLoader does not load obj file

I am trying to load a obj file using OBJloader.js
I am trying to load "plane.obj" file which exists inside the same folder where the html files exists and "OBJLoader.js" also exists in the same folder.
Page doesn't show up anything.
Here is the code :
var scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.CubeGeometry(1,1,1);
var material = new THREE.MeshBasicMaterial({color: 0x00ff00});
var cube = new THREE.Mesh(geometry, material); scene.add(cube); camera.position.z = 5;
function render() {
requestAnimationFrame(render);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
var texture = THREE.ImageUtils.loadTexture( 'tex.jpg' );
var loader = new THREE.OBJLoader();
loader.load( 'plane.obj', function ( object ) {
scene.add( object );
} );
render();
This is likely caused by trying to load a resource from the file system. You're probably getting a same origin policy security violation and need to serve up your page and resources from the same protocol, domain and port. There are a few easy ways to do this - I use a simple http server app via Node JS. check out How to run things locally for more options.
Well, it turned out for me to be caused by the absence of light in the scene. Also, improper camera position may also lead to "invisible" OBJ models. Try adding the following lines:
var ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);

Resources