I'm trying to set up a very simple scene with Three.js, showing an imported mesh rotating. I combined a couple of the examples from the Three.js documentation and arrived at the following code:
var scene, camera, renderer;
var geometry, material, mesh;
init();
animate();
function init(){
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 1000;
// geometry = new THREE.BoxGeometry(200, 200, 200);
var jsonLoader = new THREE.JSONLoader();
jsonLoader.load('handgun.js', object_to_scene(geometry, material));
}
function object_to_scene(geometry, material){
material = new THREE.MeshBasicMaterial({
color: 0xff0000,
wireframe: true
});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate(){
requestAnimationFrame(animate);
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
}
You can see the original example commented out in the above, where we generated a Box using Three.js. That worked fine. However, importing the JSON file of the custom 3D model isn't working. Checking my console reveals the following error:
TypeError: onLoad is not a function three.js:18294:4
THREE.JSONLoader.prototype.load/<() three.js:18294
THREE.XHRLoader.prototype.load/<() three.js:18010
This appears to be an error with Three.js itself, however I've found only two instances of people reporting it on the Github account, and both were told that the Github was for reporting bugs, not asking for help (if this isn't a bug, then what is it?).
Has anyone else encountered this issue, and if you have resolved it, how did you do so?
Thanks!
Instead of
jsonLoader.load( 'handgun.js', object_to_scene( geometry, material ) );
You need to do this:
jsonLoader.load( 'handgun.js', object_to_scene );
object_to_scene( geometry, material ) is a function call that, in your case, returns undefined.
three.js r.75
Related
So, I have been using rollup to bundle my js files, created for usage with three js. What I thought of is to minimize and bundle the three js files using rollup js.
I used npm and installed three and rollup using the command npm install rollup three.
The index.js file as created is:
import { Scene, PerspectiveCamera, WebGLRenderer } from 'three'
const scene = new Scene()
const camera = new PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10)
camera.position.z = 1
geometry = new BoxGeometry( 0.2, 0.2, 0.2 )
material = new MeshNormalMaterial()
mesh = new THREE.Mesh( geometry, material )
const basic = basicScene()
scene.add( mesh )
const renderer = new WebGLRenderer({antialias: true})
renderer.setSize( window.innerWidth, window.innerHeight )
document.body.appendChild( renderer.domElement )
function animate() {
requestAnimationFrame(animate)
mesh.rotation.x += 0.01
mesh.rotation.y += 0.01
renderer.render( scene, camera )
}
animate()
The rollup config as used is rollup.config.js:
export default [{
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'cjs'
}
}]
The final bundle.js file as generated after running rollup --config is:
'use strict';var three=require('three');const scene = new three.Scene();
const camera = new three.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10);
camera.position.z = 1;
geometry = new BoxGeometry( 0.2, 0.2, 0.2 );
material = new MeshNormalMaterial();
mesh = new THREE.Mesh( geometry, material );
const basic = basicScene();
scene.add( mesh );
const renderer = new three.WebGLRenderer({antialias: true});
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
renderer.render( scene, camera );
}
animate();
I have the following doubts with the given bundle.js file that is generated:
1. The bundle js as created uses require, which results in ReferenceError: require is not defined for rollup
2. Secondly, not sure why the bundled version is not really the complete minified version of the produced js file?
Great, if some could suggest the probable issue?
There is actually a minimal sample project if you use the npm version of three.js and build your project via rollup. It demonstrates all the bits which are necessary for a proper setup:
https://github.com/Mugen87/three-jsm
A few comments to your question:
It seems you are not importing all classes from the three package. For example BoxGeometry or MeshNormalMaterial are missing.
Besides, you are referring to the Mesh class over the THREE namespace which is obviously wrong.
When importing npm packages, rollup requires the usage of #rollup/plugin-node-resolve.
Minifying does not happen automatically. You need to use a separate plugin for this e.g. rollup-plugin-terser.
Except for the last point, the mentioned sample project implements all above steps which are necessary for a proper build.
I have a scene which contains 15-20 objects, 4 lights. And properties of my renderer are
function getRenderer(container, width, height) {
var renderer;
renderer = new THREE.WebGLRenderer({ alpha: false, antialias: true, preserveDrawingBuffer: false });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
container.appendChild(renderer.domElement);
renderer.shadowMapEnabled = true;
renderer.shadowMapType = THREE.PCFSoftShadowMap;
renderer.setClearColor(new THREE.Color(0xCCE0FF), 1);
renderer.gammaInput = true;
renderer.gammaOutput = true;
renderer.clear();
return renderer;
}
My render loop renders the scene every second.
function renderLoop() {
this.renderer.render(this.scene, this.camera);
setTimeout(function () {
renderLoop();
}, 1000);
}
The problem I am facing is this.renderer.render(this.scene, this.camera) is taking about 100 ms to render the scene but I want it to be below 33ms so that I can have frame rate of at least 30 fps.
Is there a way to optimize the renderer performance by any means (like changing any properties or something)?
I don't want to use worker.js as my scene is static and doesn't contain any complex calculations.
If you have a static scene, there is no reason to have an animation loop. You just have to render once after the scene -- and all your assets -- load.
That is why there are callbacks for the loader functions. And that is why there is a THREE.LoadingManager.
There are many possible use cases. Study the three.js examples to find solutions for your particular use case.
If you are using OrbitControls to control the camera, you can force a re-render whenever the camera moves, like so:
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', render ); // use if there is no animation loop
three.js r.75
I've exported a simplistic blender model I made earlier with the plugin provided by three.js, which created a .json file. I then tried to import that file into my project, but am having no success at all.
I have it on a local server, because of file transfers not being allowed through file://, therefore, I'm using HTTP.
The issue is that I cannot see the model. I'm using the following code to import it:
var loader = new THREE.JSONLoader();
loader.load( 'model/mountain.json', function ( geometry ) {
var mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial() );
scene.add( mesh );
});
I have both camera and scenes setup properly because I can create and add native three.js shapes, such as boxes and plains, and I'm 100% sure the file is in the correct place.
I asked on reddit, and /u/Egenimo helped me with the answer!
Here's the whole code, as I figure it might help people who need to set up a scene aswell.
var canvas = document.getElementById("bgcanvas"),
renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true }),
windowHeight = window.innerHeight,
windowWidth = window.innerWidth,
camera, scene, renderer, loader;
init();
function init() {
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000, 0);
renderer.shadowMapEnabled = true;
renderer.shadowMapType = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 5;
// scene
scene = new THREE.Scene();
loader = new THREE.JSONLoader();
loader.load( 'cube.json', function ( geometry ) {
var mesh = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial({overdraw: true}));
scene.add( mesh );
});
console.log(scene.children.length);
render();
}
function render() {
requestAnimationFrame(function() {
render();
});
renderer.render(scene, camera);
}
I then ran into the issue of having a more complex model, with several objects, and ThreeJS would only import a single one, therefore, after looking it up, I found this answer by the Man himself!.
Hope this helps someone in need!
I have a 3D object from Blender: cylinder.obj which I am able to render on the screen using Three.js. I also have the code in place to rotate the object using mouse. All this scene is inside a big div, say 600x600 pixel div.
I have the following code for render logic:
// Renders the scene and updates the render as needed.
function animate() {
// Render the scene.
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
This in my understanding is creating a loop and rendering the object again and again on the page.
Is there a way I can render the object once in the scene and then only redraw when there is any change to the object like texture change or when rotating the object using mouse. Once this texture application is done or the mouse rotate is complete, I want the scene to say intact and not consume any CPU for 3D rendering.
So, in brief, is there a way to render the scene only when needed and then stay idle and not consume a lot of CPU when the scene is behaving like an image.
I am new to three.js. I am testing this application in Chrome and IE11. Let me know if any more information is needed for clarification.
EDIT: Adding full JS code:
// global variables
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
var WIDTH = $("#myDiv").width(),
HEIGHT = $("#myDiv").height();
// Sets up the scene.
function init() {
// Create the scene and set the scene size.
scene = new THREE.Scene();
// Create a renderer and add it to the DOM.
if (Detector.webgl)
renderer = new THREE.WebGLRenderer({ antialias: true });
else
renderer = new THREE.CanvasRenderer(); //IE10 and below, and may be mobile devices
renderer.setSize(WIDTH, HEIGHT);
renderer.domElement.id = 'mycanvas'; //setting id for canvas element
console.log(renderer);
$("#myDiv").append(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, 100);
camera.position.set(-3, 4, 12);
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.setClearColor(0x333F47, 1);
var light2 = new THREE.PointLight(0xffffff);
light2.position.set(0, 10, -10);
scene.add(light2);
var light3 = new THREE.PointLight(0xffffff);
light3.position.set(1, 5, 10);
scene.add(light3);
// Load in the mesh and add it to the scene.
var objLoader = new THREE.OBJLoader();
objLoader.load('objects/male.obj', function (object) {
object.position.y = 0;
object.scale.x = object.scale.y = object.scale.z = 1;
scene.add(object);
});
}
// Renders the scene and updates the render as needed.
function draw() {
//requestAnimationFrame(draw);
// Render the scene.
renderer.render(scene, camera);
}
init();
draw();
To do something only after something happens is called event-driven programming.
For example:
function OnMouseMove(evt) {
// do some transform update on object
// after done updating - draw!
draw();
}
function textureLoaded() {
// hurray, my texture's now loaded, I'm ready to draw now
draw();
}
function draw() {
renderer.render(scene, camera);
}
So instead drawing with timer, you draw thing only when you need them to, and in your case, after mouse move or after the texture's finished loading.
You could then attach OnMouseMove to an event listener, and textureLoaded as some Three.js's callback function for texture loading.
P. S. GPU is actually doing the drawing, not the CPU.
Figured out the logic using start - stop animation function on my events from link: Prevent requestAnimationFrame from running all the time
I am new to Three.JS and am trying to load a load a very simple (single cube) Sketchup model into Three.JS via the ColladaLoader, I get no errors but nothing is displayed:
var renderer = new THREE.WebGLRenderer();
var loader = new THREE.ColladaLoader();
var scene = new THREE.Scene();
var camera = new THREE.Camera();
loader.load('Test.dae', function (result) {
scene.add(result.scene);
});
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(0, 0, 5);
scene.add(camera);
renderer.render(scene,camera);
Can anyone spot any immediate errors? Thanks
Fixed. Whilst I had declared the renderer, I hadn't attached it to the document. The following code works:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(0, 0, 4);
var loader = new THREE.ColladaLoader();
loader.load("test.dae", function (result) {
scene.add(result.scene);
});
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
You need to rerender the scene after the collada is loaded. Something like
loader.load('Test.dae', function (result) {
scene.add(result.scene);
renderer.render(scene,camera);
});
Additionally:
The camera might be inside your geometry. Faces are one-sided by default, they are invisible when looking from back/inside. You could try changing the camera or object location and/or set material.side = THREE.DoubleSide so the faces are visible from both front and back.
The scale of the model can be way off, so it can be too small or large to show. Try different camera positions (eg. z= -1000, -100, -10, -1, 1, 10, 100, 1000) and use lookAt() to point it to 0,0,0 or, I think colladaloader has a scale setting nowadays, not sure.
Loader defaults to position 0,0,0, so center of the world/scene. That doesn't necessarily mean center of screen, depends on camera. In some cases the Collada model itself can be such that the center is away from visible objects, so when it's placed on the scene it can be effectively "off-center". That's quite unlikely though.
You dont have any lights in the scene.
loader.load('test.dae', function colladaReady(collada) {
localObject = collada.scene;
localObject.scale.x = localObject.scale.y = localObject.scale.z = 2;
localObject.updateMatrix();
scene.add(localObject);
I think you need to add the object in the collada scene, or there might be problem with the scaling of the object that you are adding, scale it and than update the matrix of the object.
If your still having trouble it may be that some versions of IE stop you downloading local files (your Test.dae) so it may be worth trying it using firefox or putting your code up on a server.