I'm creating a mesh per json object stored in an array containing 100 of them, so I want to add 100 meshes to the scene, and I've got plans to even add to that later on.
This puts an enormous strain on my project in terms of performance, as you could imagine. So, I'm trying to figure out how to cut this array up in 10 manageable parts, and have 10 of these "Planet" objects be added per scene. So there'd be 10 scenes with each scene containing 10 meshes.
I have no idea if this is even doable since this is my first work on three.js. How do I go about it? My code:
var scene1 = new THREE.Scene();
var scene2 = new THREE.Scene();
var scene3 = new THREE.Scene();
var scene4 = new THREE.Scene();
var scene5 = new THREE.Scene();
var scene6 = new THREE.Scene();
var scene7 = new THREE.Scene();
var scene8 = new THREE.Scene();
var scene9 = new THREE.Scene();
var scene10 = new THREE.Scene();
var data = [{
"Planet": "1",
}, {
"Planet": "2",
}, {
"Planet": "3",
}
// this continues up until planet 100
];
//this adds 100 meshes to the scene
for (var i = 0; i < data.length; i++) {
var loader = new THREE.TextureLoader();
var material = new THREE.MeshPhongMaterial({ map: loader.load('image.jpg') });
var geometry = new THREE.SphereGeometry(30, 50, 50);
mesh = new THREE.Mesh(geometry, material);
//random x and z positions so they don't spawn on same spot
mesh.position.x = THREE.Math.randInt(-500, 500);
mesh.position.z = THREE.Math.randInt(-500, 500);
scene1.add(mesh);
}
Edit:
My apologies for not being clear enough as I should have been. The main goal is actually having the functionality of switching through 10 scenes, and seeing 10 "planets" in each scene based on the data array. I've written this code so that it's up to the user to determine which scene is to be rendered and which aren't. Rendering and seeing 100 planets in one scene isn't just horrible for the performance, it's not what my project needs to look like.
var userInput = 1;
if (userInput === 1) {
renderer.render(scene1, camera);
} else {
renderer.render(eval('scene' + userInput), camera);
}
To answer your specific question, you would probably start by storing the scenes in an array:
// Setup scenes
var scenes = [];
for(var i=0;i<10;i++) {
scenes.push( new THREE.Scene() );
}
Then assign the meshes to a scene based on your counter:
//this adds 100 meshes to the scene
for (var i = 0; i < data.length; i++) {
...
var sceneIndex = Math.floor(i/10);
var curScene = scenes[sceneIndex];
curScene.add(mesh);
}
But I'm not sure what your goal is here in terms of improving performance. Is there more code involved than what you've shown? If you still plan on rendering all 10 scenes simultaneously, this won't be any faster than having a single scene.
Related
I'm creating a 3D game, and I just began. However, I quickly ran into a problem with the localhost GET taking more than 2 minutes, and after like 15-45 seconds of life, then the canvas turns white and in console, I get a warn showing that the WebGL context has been lost. Also, in Task Manager, the game takes up 30% of the CPU and 100% of the GPU.
It is for a new online 3d multiplayer game. I've tried to dispose the memory after a new frame, but that didn't work. I've tried also to pre-load all the textures to use less CPU, but the 30% CPU remains the same. This is my code: (client-side)
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene.autoUpdate = false;
var preLoad = new THREE.TextureLoader();
var sunTexture = preLoad.load("/static/sun-texture.jpg");
var mercuryTexture = preLoad.load("/static/mercury-texture.jpg");
var socketio = io();
var Geometries = [];
socketio.on("TX2", function (data) {
Geometries = [];
var collectedEntities = [];
data.objects.forEach(obj => {
collectedEntities.push(obj);
});
DisplayAllEntities(collectedEntities);
});
function DisplayAllEntities(objects) {
var loader;
objects.forEach(obj => {
if (obj.geometry == "sphere") {
if (obj.type != "ordinary_sphere") {
switch (obj.type) {
case "sun":
var material = new THREE.MeshBasicMaterial({ map: sunTexture });
break;
case "mercury":
var material = new THREE.MeshBasicMaterial({ map: mercuryTexture });
camera.position.z = obj.z + 500;
break;
}
}
loader = new THREE.TextureLoader();
var texture = loader.load(obj.texture)
var geometry = new THREE.SphereGeometry(obj.radius, 50, 50, 0, Math.PI * 2, 0, Math.PI * 2);
var mesh = new THREE.Mesh(geometry, material);
Geometries.push(mesh);
} else if (obj.geometry == "cube") {
loader = new THREE.TextureLoader();
var texture = loader.load(obj.texture)
var geometry = new THREE.CubeGeometry(obj.width, obj.height, obj.depth);
var material = new THREE.MeshBasicMaterial({ map: texture });
var mesh = new THREE.Mesh(geometry, material);
mesh.position = {"x": obj.x, "y": obj.y, "z": obj.z}
Geometries.push(mesh);
}
loader = null;
});
scene.children = [];
scene.dispose();
Geometries.forEach(obj => {
scene.add(obj);
});
render();
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
The server just makes calculations about positions, and sends them to clients to render them.
I expect to get a lower CPU and GPU usage and a much lower load time, but the performance still remains the same.
It is very inefficient to create each object every time you need to render it, and especially to load the corresponding textures. A better solution would be set up the objects beforehand, and then to update these objects continually. This would require
a server emit for initializing (setting up objects, loading textures etc.)
a server emit for game state updates (adding/removing items if needed)
a server emit to update the positions. (this is the one that will execute 60 times per second)
Each object to have a unique id given by the server at creation so that the client knows which object server references.
This involves a bit more effort but would boost performance greatly
I've been trying to find the fastest way to change a mesh's vertices with three.js. I found that if I change parts of mesh.geometry.attributes.position.array, then set mesh.geometry.attributes.position.needsUpdate=true, it works well and doesn't have to rebuild arrays or recreate opengl buffers. I found that needsUpdate=true changes the version number of the attribute and that makes it resend the attributes vertices array to the opengl buffer.
So I tried doing that myself instead by calling gl.bindBuffer() then gl.bufferData() but then after doing that every loop for a while it crashes on my call to new Float32Array(). Which is weird because when I check my memory usage I'm only using 4MB right before it crashes. I realize it's not the best way to be deallocating/reallocating the array every loop just to make it slightly bigger when I could be doubling the size of the array when it gets full, but I want to understand why it's crashing when done this way.
https://jsfiddle.net/q1txL19c/3/ Crashes in 20 seconds.
But if I change the if(0) to if(1) it works.
What is three.js doing differently that makes it not crash? Why does new Float32Array() fail when not much javascript memory has been used up according to the profiler?
<!doctype html>
<html>
<body style='margin:0;padding:0'>
<script src="https://threejs.org/build/three.js"></script>
<script>
var camera, scene, renderer, mesh
var triangles = 1
init()
function init()
{
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, .1, 10000)
camera.position.z = 15
scene.add(camera)
var geometry = new THREE.BufferGeometry()
var material = new THREE.MeshBasicMaterial( {side: THREE.FrontSide, transparent:false, vertexColors: THREE.VertexColors} )
mesh = new THREE.Mesh(geometry, material)
var positions = new Float32Array([1,1,0, 0,1,0, 0,0,0])
geometry.addAttribute('position', new THREE.BufferAttribute(positions,3))
var colors = new Float32Array([0,0,1, 0,0,0, 0,0,0])
geometry.addAttribute('color', new THREE.BufferAttribute(colors,3))
scene.add(mesh)
renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setClearColor( 0x6699DD )
document.body.appendChild(renderer.domElement)
loop()
}
function addTriangle(geometry)
{
// Make 3 new vertices, each with x,y,z. 9 total positions.
var newVertices = []
for(var i=0; i<9; i++)
newVertices[i] = Math.random()*10-5
appendArrayToAttribute(geometry.attributes.position, newVertices)
// Make 3 new colors, 1 for each new vertex, each with r,g,b. 9 total slots.
var newColors = []
for(var i=0; i<9; i++)
newColors[i] = Math.random()
appendArrayToAttribute(geometry.attributes.color, newColors)
}
function appendArrayToAttribute(attribute, arrayToAppend)
{
// Make a new array for the geometry to fit the 9 extra positions at the end, since you can't resize Float32Array
try
{
var newArray = new Float32Array(attribute.array.length + arrayToAppend.length)
}
catch(e)
{
console.log(e)
if(!window.alerted)
{
alert("out of memory!? can't allocate array size="+(attribute.array.length + arrayToAppend.length))
window.alerted = true
}
return false
}
newArray.set(attribute.array)
newArray.set(arrayToAppend, attribute.array.length)
attribute.setArray(newArray)
if(0)
{
attribute.needsUpdate = true
}
else
{
// Have the geometry use the new array and send it to opengl.
var gl = renderer.context
gl.bindBuffer(gl.ARRAY_BUFFER, renderer.properties.get(attribute).__webglBuffer)
gl.bufferData(gl.ARRAY_BUFFER, attribute.array, gl.STATIC_DRAW)
}
}
function loop()
{
requestAnimationFrame(loop)
mesh.rotation.x += 0.01
mesh.rotation.y += 0.02
renderer.render(scene, camera)
for(var i=0;i<10;i++)
{
addTriangle(mesh.geometry)
triangles++
}
if(Math.random()<.03)
{
console.log("triangles="+triangles)
var gl = renderer.context
console.log("gl buffer size="+gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE))
}
}
</script>
</body>
</html>
You can add faces to BufferGeometry after the first render, but you must pre-allocate your geometry attribute buffers to be large enough, as they can't be resized.
Also, you will be updating array values, not instantiating new arrays.
You can update the number of faces to render like so:
geometry.setDrawRange( 0, 3 * numFacesToDraw ); // 3 vertices for each face
See this related answer and demo.
three.js r.84
I'm trying to create multiple meshes, and then merge them into one (with Three.js r.71). Each mesh can have different materials on different faces. I need to get all the materials appear correctly on the resulting mesh.
I found a way to achieve the desired result, but my code looks pretty awful now. Are the developers of three.js really kept in mind this method?
I could not find a suitable example. Can anyone show a better way to do this?
var totalGeom = new THREE.Geometry();
var meshes = [getBlock(), getBlock()];
meshes[0].position.x = 1;
for (var i = 0; i < meshes.length; i++) {
var mesh = meshes[i];
totalGeom.mergeMesh(mesh);
for (var j = mesh.geometry.faces.length-1; j <= 0; j--) {
totalGeom.faces[j].materialIndex = mesh.geometry.faces[j].materialIndex;
}
}
var materials = meshes[0].material.materials.concat(meshes[1].material.materials);
var totalMesh = new THREE.Mesh(totalGeom, new THREE.MeshFaceMaterial(materials));
scene.add(totalMesh);
function getBlock() {
var geometry = new THREE.BoxGeometry(1, 1, 1, 1, 1, 1);
var material = new THREE.MeshFaceMaterial([
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/2.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/2.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/1.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/3.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/2.png')}),
new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('sides/2.png')})
]);
return new THREE.Mesh(geometry, material);
}
I would suggest you that instead of mixing all the meshes inside one, you create a group containing all the different meshes. If you do that, you don't need to mix all the materials and you avoid a lot of problems there. And, of course, you get all the advantges of having all the meshes on the same object.
As an example, to do so, follow this procedure:
var scene = new THREE.Scene();
var group = new THREE.Object3D();
var numObjects = 5; //As an example
for(var i=0;i<numObjects;i++){
var cubeGeometry = new THREE.BoxGeometry(100, 100, 100);
var material = new THREE.MeshPhongMaterial();
var mesh = new THREE.Mesh(cubeGeometry, material);
group.add(mesh);
}
scene.add(group);
My display has a resolution of 7680x4320 pixels. I want to display up to 4 million different colored squares. And I want to change the number of squares with a slider. If have currently two versions. One with canvas-fillRect which looks somethink like this:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
for (var i = 0; i < num_squares; i ++) {
ctx.fillStyle = someColor;
ctx.fillRect(pos_x, pos_y, pos_x + square_width, pos_y + square_height);
// set pos_x and pos_y for next square
}
And one with webGL and three.js. Same loop, but I create a box geometry and a mesh for every square:
var geometry = new THREE.BoxGeometry( width_height, width_height, 0);
for (var i = 0; i < num_squares; i ++) {
var material = new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } );
material.emissive = new THREE.Color( Math.random(), Math.random(), Math.random() );
var object = new THREE.Mesh( geometry, material );
}
They both work quite fine for a few thousand squares. The first version can do up to one million squares, but everything over a million is just awful slow. I want to update the color and the number of squares dynamically.
Does anyone has tips on how to be more efficient with three.js/ WebGL/ Canvas?
EDIT1: Second version: This is what I do at the beginning and when the slider has changed:
// Remove all objects from scene
var obj, i;
for ( i = scene.children.length - 1; i >= 0 ; i -- ) {
obj = scene.children[ i ];
if ( obj !== camera) {
scene.remove(obj);
}
}
// Fill scene with new objects
num_squares = gui_dat.squareNum;
var window_pixel = window.innerWidth * window.innerHeight;
var pixel_per_square = window_pixel / num_squares;
var width_height = Math.floor(Math.sqrt(pixel_per_square));
var geometry = new THREE.BoxGeometry( width_height, width_height, 0);
var pos_x = width_height/2;
var pos_y = width_height/2;
for (var i = 0; i < num_squares; i ++) {
//var object = new THREE.Mesh( geometry, );
var material = new THREE.Material()( { color: Math.random() * 0xffffff } );
material.emissive = new THREE.Color( Math.random(), Math.random(), Math.random() );
var object = new THREE.Mesh( geometry, material );
object.position.x = pos_x;
object.position.y = pos_y;
pos_x += width_height;
if (pos_x > window.innerWidth) {
pos_x = width_height/2;
pos_y += width_height;
}
scene.add( object );
}
The fastest way to draw squares is to use the gl.POINTS primitive and then setting gl_PointSize to the pixel size.
In three.js, gl.POINTS is wrapped inside the THREE.PointCloud object.
You'll have to create a geometry object with one position for each point and pass that to the PointCloud constructor.
Here is an example of THREE.PointCloud in action:
http://codepen.io/seanseansean/pen/EaBZEY
geometry = new THREE.Geometry();
for (i = 0; i < particleCount; i++) {
var vertex = new THREE.Vector3();
vertex.x = Math.random() * 2000 - 1000;
vertex.y = Math.random() * 2000 - 1000;
vertex.z = Math.random() * 2000 - 1000;
geometry.vertices.push(vertex);
}
...
materials[i] = new THREE.PointCloudMaterial({size:size});
particles = new THREE.PointCloud(geometry, materials[i]);
I didn't dig through all the code but I've set the particle count to 2m and from my understanding, 5 point clouds are generated so 2m*5 = 10m particles and I'm getting around 30fps.
The highest number of individual points I've seen so far was with potree.
http://potree.org/, https://github.com/potree
Try some demo, I was able to observe 5 millions of points in 3D at 20-30fps. I believe this is also current technological limit.
I didn't test potree on my own, so I cant say much about this tech. But there is data convertor and viewer (threejs based) so should only figure out how to convert the data.
Briefly about your question
The best way handle large data is group them as quad-tree (2d) or oct-tree (3d). This will allow you to not bother program with part that is too far from camera or not visible at all.
On the other hand, program doesnt like when you do too many webgl calls. Try to understand it like this, you want to do create ~60 images each second. But each time you set some parameter for GPU, program must do some sync. Spliting data means you will need to do more setup so tree must not be too detialed.
Last thing, someone said:
You'll probably want to pass an array of values as one of the shader uniforms
I dont suggest it, bad idea. Texture lookup is quite fast, but attributes are always faster. If we are talking about 4M points, you cant afford reading data from uniforms.
Sorry I cant help you with the code, I could do it without threejs, Im not threejs expert :)
I would recommend trying pixi framework( as mentioned in above comments ).
It has webgl renderer and some benchmarks are very promising.
http://www.goodboydigital.com/pixijs/bunnymark_v3/
It can handle allot of animated sprites.
If your app only displays the squares, and doesnt animate, and they are very simple sprites( only one color ) then it would give better performance than the demo link above.
I'm using the Three.js javascript library. To test it I downloaded the an example from here.
I'm trying to display several times the same element using a for loop. There two questions related (1, 2) but it's not exactly what I want. My problem is that if I create the element inside the loop it will only display the last element of the iteration. In this particular case the element in position (12,12).
But, if I do an action like an alert it will display all the elements. Also if I have any other functions that delays the execution.
I saw some examples running, as the mrdoob examples, but I would like this code running because I need to load several mesh instead of generating primitive figures.
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
var group;
// Call functions
init();
animate();
// Sets up the scene.
function init() {
// Iterator
var i, j;
// 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,20,20);
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);
// 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);
group = new THREE.Object3D();
for(i=0; i < 15; i+=3) {
for(j=0; j < 15; j+=3) {
var loader = new THREE.JSONLoader();
loader.load( "models/treehouse_logo.js", function(geometry){
var material = new THREE.MeshLambertMaterial({color: 0x55B663});
var mesh = new THREE.Mesh(geometry, material);
mesh.position.set(i,0,j);
group.add(mesh);
});
//alert("iteration"+i+" "+j);
}
}
scene.add( group );
// 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() {
// Read more about requestAnimationFrame at http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
requestAnimationFrame(animate);
// Render the scene.
renderer.render(scene, camera);
controls.update();
}
What you are doing here is incredibly inefficient:
for(i=0; i < 15; i+=3) {
for(j=0; j < 15; j+=3) {
var loader = new THREE.JSONLoader();
loader.load( "models/treehouse_logo.js", function(geometry){
var material = new THREE.MeshLambertMaterial({color: 0x55B663});
var mesh = new THREE.Mesh(geometry, material);
mesh.position.set(i,0,j);
group.add(mesh);
});
//alert("iteration"+i+" "+j);
}
}
This would be much better done like this (untested):
var loader = new THREE.JSONLoader();
loader.load( "models/treehouse_logo.js", function( geometry ){
var material, mesh, i, j, instance;
material = new THREE.MeshLambertMaterial({ color: 0x55B663 });
mesh = new THREE.Mesh( geometry, material );
for ( i = 0; i < 15; i += 3 ) {
for ( j = 0; j < 15; j += 3 ) {
instance = mesh.clone();
instance.position.set( i, 0, j );
group.add( instance );
}
}
});
You'd need to do repeat this pattern for each unique mesh.
The problems your current approach has are:
More memory needed by the GPU for each identical mesh
More memory needed by the browser to remember each identical mesh
More processing power required by the GPU as more memory needs to be processed
Each time you call the loader, you instruct the browser to execute a request. That's some 25 identical requests in your case. They should come from the cache, but it'll still be slow.
You may have variables scoping issues too which gives issues with the loader callback, but I'm not entirely sure about that.
alert() makes for a very poor debugging tool by the way as it changes the way the browser reacts: it stops executing JavaScript when the alert is open and that affects the loader and similar things. You're better off with the Console logging methods.
I would say it is because you are setting the loader variable in each iteration of the loop which will override the loader of the last iteration.
Why is the actual loading being done in a loop? Why not load it once and clone it?
eg.
group = new THREE.Object3D();
var loader = new THREE.JSONLoader();
loader.load( "models/treehouse_logo.js", function(geometry){
var material = new THREE.MeshLambertMaterial({color: 0x55B663});
for(i=0; i < 15; i+=3) {
for(j=0; j < 15; j+=3) {
var mesh = new THREE.Mesh(geometry.clone(), material);
mesh.position.set(i,0,j);
group.add(mesh);
}
}
});
scene.add( group );
The above code is untested