I am trying to make multiple WebGLRenderer render on the same canvas, it works for the very first frame, but if it does the work in the animation loop - it can't render things properly. I wonder if there are some limitations in that approach?
Here is the sample code i can't make working properly if requestAnimationFrame is called inside the update function.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, WIDTH / HEIGHT, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
// + scene filler
const scene2 = new THREE.Scene();
const camera2 = new THREE.OrthographicCamera(-WIDTH / 2, WIDTH / 2, HEIGHT / 2, -HEIGHT / 2, 0, 30);
const renderer2 = new THREE.WebGLRenderer({ canvas: renderer.domElement });
renderer2.autoClear = false;
// + scene2 filler
function update() {
renderer.render(scene, camera);
renderer2.render(scene2, camera2);
requestAnimationFrame(update);
}
https://codesandbox.io/s/l75w6qyw2m?module=%2Fsrc%2Findex.js
I am not sure what to expect though, but i would really want to make that working, i need to have a separate renderer to render several scenes with different cameras, and i was looking for opportunity to do that in separate renderer to provide a nicer public interface to work with for my plugin.
It's not possible to have 2 WebGLRenderers in one canvas. The reason is that each canvas has ONE WebGLContext as a native limitation, it cannot create a second context.
However, it looks like you're just trying to render two scenes, one on top of the other. You could do that with a single renderer. You just have to turn off its auto-clear property, then use it to render the two scenes, one on top of the other like this:
renderer.autoClear = false;
function update() {
renderer.render(scene, camera);
renderer.render(hudScene, hudCamera);
requestAnimationFrame(update);
}
Related
So I want a simple solution. A toggle button which refreshes a Three.js scene depending on the user, if he wants an anti-aliased version of the scene or not. My refresh_scene function is as follows:
function refresh_scene()
{
scene.remove( floor );
floor.geometry.dispose();
floor.material.dispose();
floor = null;
scene.dispose();
scene = null;
controls.dispose();
controls = null;
camera = null;
renderer.dispose();
renderer = null;
canvas = null;
cancelAnimationFrame(reqAnimFrame);
visualizer();
}
This function is called every time the button is pressed. The visualizer() then just displays all objects again. This also includes getting the canvas, making a new renderer, new scene, etc. For some reason, the new renderer does not display an anti-aliased version of the scene, but the same scene over and over again, despite the fact that the toggle button toggles an antialiasing_ value which is then used in the visualizer() function to reinitialize the renderer according to the new value:
renderer = new THREE.WebGLRenderer({canvas, antialias: antialiasing_});
where antialiasing_ is a bool which toggles true or false. Am I doing something wrong? Any help would be appreciated. Can upload other parts of code if necessary.
PREMISE:
I'm trying to create a static scene that has a fixed image background and a geometry in front of it. Since the scene is static, I don't need and envMap.
I created two scenes and cameras (one for the background and one for the geometries) as suggested in this SO question, and this demo, and updated the procedure to consider that THREE.ImageUtils.loadTexture() is deprecated. Here is the working code:
// load the background texture
var loader = new THREE.TextureLoader();
texture = loader.load('path_to_image.jpg');
var backgroundMesh = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2, 0),
new THREE.MeshBasicMaterial({
map: texture
})
);
backgroundMesh.material.depthTest = false;
backgroundMesh.material.depthWrite = false;
// create your background scene
backgroundScene = new THREE.Scene();
backgroundCamera = new THREE.Camera();
backgroundScene.add( backgroundCamera );
backgroundScene.add( backgroundMesh );
The variables backgroundScene and backgroundCamera are global and the following procedure is called inside the init() function. All scenes and cameras are later rendered using:
renderer.autoClear = false;
renderer.clear();
renderer.render( backgroundScene , backgroundCamera);
renderer.render(scene, camera);
PROBLEM:
I implemented an event listener that is supposed to change the background image and geometry when a button is pressed, however this is not working.
I thought that loading the new texture and changing the material property of the backgroundScene variable, clearing the renderer and rendering the scene again would do the job. Here is the code:
var loader = new THREE.TextureLoader();
var texture = loader.load('path_to_new_image.jpg');
console.debug(texture);
console.debug(backgroundScene.children[1].material.map);
backgroundScene.children[1].material.map = texture;
console.debug(backgroundScene.children[1].material.map);
renderer.clear();
renderer.render( backgroundScene , backgroundCamera );
renderer.render(scene, camera);
The console.debug() show me that the new texture is actually loaded and the backgroundScene material is changed accordingly.
However, while the geometries are rendered fine I am left with a blank background and get the following error: [.Offscreen-For-WebGL-0x364ad7e56700]RENDER WARNING: there is no texture bound to the unit 0.
Any ideas of what is going on? Thanks for your help!
you will need to call object.material.needsUpdate = true; for the change to take effect (see here). When the map-property of the material is changed, three.js needs to re-setup the texture-binding, which is skipped unless the needsUpdate-flag is set.
Alternatively, if you just change the material.map.image-property it should work without that extra-step.
I have a world map layer as a plane geometry, now i want to handle click events on different parts (For example continents or country). How to handle popup events on particular part like (popup of information or video playing or image data with links etc.)
Here's a fiddle that already has the scene setup.
var camera, scene, renderer;
var geometry, material, mesh;
window.addEventListener("click", onClick, false);
function onClick() {
alert("Replace me with code to add an object!");
}
var init = function () {
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 500;
scene = new THREE.Scene();
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshBasicMaterial({
color: 0x000000,
wireframe: true,
wireframeLinewidth: 2
});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
var animate = function () {
requestAnimationFrame(animate);
mesh.rotation.x = Date.now() * 0.0005;
mesh.rotation.y = Date.now() * 0.001;
renderer.render(scene, camera);
}
init();
animate();
the click event is applied to whole scene, i have created multiple cubes in that example and when clicked on that it is showing the same click event for all parts.
What you ask is too complex, you need to break it down. First problem you want to solve is to find out which object is being clicked.
You can do raycasting or gpu picking. since you sound like a beginner I would suggest you to start with raycasting.
There are some things you need to do in order to figure out where on the map the click occured (and this is just one way to do it, another one would be to use GPU-picking, which could be even easier):
use the raycaster that three.js provides to get information about the object that is under the cursor when the click happened. (docs/example)
in the result from the raycaster you will get the uv-coordinates for the point of intersection (so basically, where in your map-texture the click occurred).
now, you can either store your geographic features as values relative to these UV-coordinates or you need to convert the UV-values into a geographical format to work with (WGS84, or latitude/longitude). For that you need to know the type of projection that was used to create the map (probably a mercator-projection). Note that there are lots of libraries available for these kinds of conversions.
Now that you know where in your world-map the click occurred, you can test it against geographic features. Let's say continents. For each continent you will need to define a polygon in the same coordinate-system you are using for the coordinates of the clicks. Then you can do a 'point in polygon'-test (here's an implementation) to check if the clicked location is inside the polygon.
I have a larger model that freeze my scene.
As I don't need this model from the beginning it would be cool to load this model in the background. Are webworkers a solution for this?
Can anyone guide me how to accomplish it , or is it possible at all ?
Thanks.
var scene = new THREE.Scene();
var 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 loader = new THREE.JSONLoader();
loader.load("models.js", function (smooth) {
smooth.mergeVertices();
smooth.computeFaceNormals();
smooth.computeVertexNormals();
var modifier = new THREE.SubdivisionModifier(1);
modifier.modify(smooth);
var mesh = new THREE.Mesh(smoothnew THREE.MeshBasicMaterial({
color: 0x00ff00
}));
scene.add(mesh);
});
var render = function () {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
render();
JavaScript is single threaded and uses "cooperative" scheduling for its events, which means that if you have a function with a long loop that doesn't give up its execution nothing else will be able to be executed in the meantime.
Your method in the loader is essentially that, a single method that'll run its computations to completion before allowing other JavaScript code to run. So rather than using a worker you might be able to split it up a bit by making it more event driven and improve the experience that way, something like this:
loader.load("models.js", function (smooth) {
smooth.mergeVertices();
smooth.computeFaceNormals();
smooth.computeVertexNormals();
window.setTimeout( function() {
var modifier = new THREE.SubdivisionModifier(1);
modifier.modify(smooth);
window.setTimeout( function() {
var mesh = new THREE.Mesh(smoothnew THREE.MeshBasicMaterial({
color: 0x00ff00
}));
scene.add(mesh);
}, 100 );
}, 100 );
});
(The points where I added setTimeout() are entirely arbitrary since I have no way of knowing where the biggest delays are without a jsfiddle to run your code + data).
This will only really work if none of these method calls themselves take up the majority of time. If e.g. mergeVertices() itself takes up most of the CPU time the only way to solve the freezing is by offloading that computation to a worker. The you'll need to pass the data around, compute it on the worker, and have the main code add it to the scene (the worker doesn't have access to the WebGL context). The complexity of that solution might not make the effort worth it however.
Workers are not suitable for a solution that requires DOM manipulation
I am working with ThreeJS on a basic 3d scene that has OrbitControls. Everything works great, except it causes my entire site to lag, as it is looping itself even when the user is not looking at it. I want a function that I can call to start and stop the rendering when certain conditions are met (in this case, the user isn't viewing the canvas). I have a start function that works just fine, but the stop function does not seem to be working, as my site goes unbearably slow after ThreeJS has initialized.
I have looked and looked for a solution to this problem, and have found a couple 'solutions', but for whatever reason they do not work with my application. My assumption is that these solutions are from old versions of ThreeJS.
Here is my code in my main.js file:
var scene,
camera,
controls,
render,
requestId = undefined;
function init() {
scene = new THREE.Scene();
var threeJSCanvas = document.getElementById("threeJS");
camera = new THREE.PerspectiveCamera( 75, threeJSCanvas.width / threeJSCanvas.height, 0.1, 1000 );
controls = new THREE.OrbitControls( camera );
// Controls and Camera settings
// Create Geometry.
}
function render() {
requestId = requestAnimationFrame(render);
renderer.render(scene, camera);
}
function start() {
render();
}
function stop() {
window.cancelAnimationFrame(requestId);
requestId = undefined;
}
In my other javascript file, there is a conditional inside of my pageChange function (this is a multipage app), that looks like the following:
if (page == 5) { // The page with the canvas on it
if (!locationsRendered) {
init();
locationsRendered = true;
}
} else { // If the page is not the page with the canvas on it
if (locationsRendered) {
stop();
}
}
locationsRendered is initialized earlier in this second javascript file in the local scope.
Any help would be much appreciated, as I can not let this simple 3D scene lag my entire app after it has been loaded. It's just not realistic.
If your scene is static, there is no reason for an animation loop. You only need to re-render when the camera moves due to a mouse or touch event.
Just use this pattern:
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', render );
function render() {
renderer.render( scene, camera );
}
three.js r.67
I was using trackball controls in my scene and therefore couldn't use the solution above (as the trackball controls continue updating after mouse events finish triggering).
To solve this problem, I used:
function animate() {
renderer.render(scene, camera);
controls.update();
}
renderer.setAnimationLoop(animate);
That runs the animation loop indefinitely. To pause the animation, one can then specify null as the animation loop:
renderer.setAnimationLoop(null); // pause the animation
And to resume the animation, just pass the animation loop again:
renderer.setAnimationLoop(animate); // resume the animation
An alternative solution to completely stopping the render loop is to reduce the frames per second rate and thereby reducing resource consumption.
This approach is particularly useful if you need responsive update on your scene while not necessarily animating, but also need to snap back normal speeds when you need to.
a simple setTimout() achieves this nicely.
var fps 10;
function render() {
//reduce framerate
setTimeout(()=>{
requestAnimationFrame(render);
//must be called to enable rotating
controls.update();
renderer.render(scene, camera);
}, 1000/fps)
};