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
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 have an icosahedron mesh which I am rotating and then adding circle geometries and setting their location to each vertex at every frame in the animation loop.
geometry = new THREE.IcosahedronGeometry(isoRadius, 1);
var material = new THREE.MeshBasicMaterial({
color: wireframeColor,
wireframe: true
});
isoMesh = new THREE.Mesh(geometry, material);
scene.add(isoMesh);
Set each circle geometries location as the icosahedron mesh rotates:
function animate() {
isoMesh.rotation.x += 0.005;
isoMesh.rotation.y += 0.002;
// update vertices
isoMesh.updateMatrix();
isoMesh.geometry.applyMatrix(isoMesh.matrix);
isoMesh.rotation.set(0, 0, 0);
for (var i = 0; i < geometry.vertices.length; i++) {
nodes[i].position.copy(geometry.vertices[i]);
nodes[i].lookAt(camera.position);
}
Where var geometry is the geometry of the icosahedron. If I remove the line "isoMesh.rotation.set(0, 0, 0);", the icosahedron rotates correctly, but the rotation of the nodes compounds and spins way too quickly. If I add that line, the nodes rotate correctly, but the icosahedron does not move at all.
I do not understand three.js well enough yet to understand what is happening. Why would adding and removing this affect the nodes' and icosahedron's rotations separately? I believe it has something to do with the difference between the mesh and the geometry since I am using the geometry to position the nodes, but the rotation of the mesh is what shows visually. Any idea what is happening here?
The solution it multi-layered.
Your Icosahedron:
You were half-way there with rotating your icosahedron and its vertices. Rather than applying the rotation to all the vertices (which would actually cause some pretty extreme rotation), apply the rotation to the mesh only. But that doesn't update the vertices, right? Right. More on that in a moment.
Your Circles:
You have the right idea of placing them at each vertex, but as WestLangley said, you can't use lookAt for objects with rotated/translated parents, so you'll need to add them directly to the scene. Also, if you can't get the new positions of the vertices for the rotated icosahedron, the circles will simply remain in place. So let's get those updated vertices.
Getting Updated Vertex Positions:
Like I said above, rotating the mesh updates its transformation matrix, not the vertices. But we can USE that updated transformation matrix to get the updated matrix positions for the circles. Object3D.localToWorld allows us to transform a local THREE.Vector3 (like your icosahedron's vertices) into world coordinates. (Also note that I did a clone of each vertex, because localToWorld overwrites the given THREE.Vector3).
Takeaways:
I've tried to isolate the parts relative to your question into the JavaScript portion of the snippet below.
Try not to update geometry unless you have to.
Only use lookAt with objects in the world coordinate system
Use localToWorld and worldToLocal to transform vectors between
coordinate systems.
// You already had this part
var geometry = new THREE.IcosahedronGeometry(10, 1);
var material = new THREE.MeshBasicMaterial({
color: "blue",
wireframe: true
});
var isoMesh = new THREE.Mesh(geometry, material);
scene.add(isoMesh);
// Add your circles directly to the scene
var nodes = [];
for(var i = 0, l = geometry.vertices.length; i < l; ++i){
nodes.push(new THREE.Mesh(new THREE.CircleGeometry(1, 32), material));
scene.add(nodes[nodes.length - 1]);
}
// This is called in render. Get the world positions of the vertices and apply them to the circles.
var tempVector = new THREE.Vector3();
function updateVertices(){
if(typeof isoMesh !== "undefined" && typeof nodes !== "undefined" && nodes.length === isoMesh.geometry.vertices.length){
isoMesh.rotation.x += 0.005;
isoMesh.rotation.y += 0.002;
for(var i = 0, l = nodes.length; i < l; ++i){
tempVector.copy(isoMesh.geometry.vertices[i]);
nodes[i].position.copy(isoMesh.localToWorld(tempVector));
nodes[i].lookAt(camera.position);
}
}
}
html *{
padding: 0;
margin: 0;
width: 100%;
overflow: hidden;
}
#host {
width: 100%;
height: 100%;
}
<script src="http://threejs.org/build/three.js"></script>
<script src="http://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script>
<div id="host"></div>
<script>
// INITIALIZE
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(WIDTH, HEIGHT);
document.getElementById('host').appendChild(renderer.domElement);
var stats= new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
var camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 50;
var trackballControl = new THREE.TrackballControls(camera, renderer.domElement);
trackballControl.rotateSpeed = 5.0; // need to speed it up a little
var scene = new THREE.Scene();
var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);
scene.add(light);
function render(){
if(typeof updateVertices !== "undefined"){
updateVertices();
}
renderer.render(scene, camera);
stats.update();
}
function animate(){
requestAnimationFrame(animate);
trackballControl.update();
render();
}
animate();
</script>
I have created a BufferGeometry to store a bunch of triangles (for later manipulation with a vertex shader). The vertice position coordinates and colors were generated randomly and added as arrays inside the bufferGeometry.attributes property. When used to make a mesh it worked fine. here is the code:-
//... Buffer Geometry
triangles = 500;
Bgeometry = new THREE.BufferGeometry();
Bgeometry.dynamic = true;
//... Bvertex_coords
Bvertex_coords = new Float32Array( triangles * 3 * 3 );
for ( var i = 0, l = triangles * 3 * 3; i < l; i += 3 )
{
Bvertex_coords[ i ] = Math.random() - 0.5;//... x
Bvertex_coords[ i + 1 ] = 2*(Math.random() - 0.5);//... y
Bvertex_coords[ i + 2 ] = Math.random() - 0.5;//... z
}
Bgeometry.addAttribute( 'position', new THREE.BufferAttribute( Bvertex_coords, 3 ) );
Next I want to create a similar BufferGeometry but this time the triangles should be initially arranged into a spherical "surface". So I created a "donor" SphereBufferGeometry with the appropriate configuration.
The idea was to then simply copy the relevant data from the donor SphereBufferGeometry into the BufferGeometry.
Here is the code I have been using:-
//... Buffer Geometries
////... new (r72) method - create a "donor" sphereBufferGeometry
var Cgeometry = new THREE.SphereBufferGeometry( 1, 4, 4 );
var num_Cgeom_pos_coords = Cgeometry.attributes.position.array.length;
var num_Cgeom_vertices = num_Cgeom_pos_coords/3;
//...Dgeometry
//... first make a plain buffer geometry
//... then copies data from an existing BufferSphereGeometry
Dgeometry = new THREE.BufferGeometry();
Dgeometry.dynamic = true;
Dpos_coords = new Float32Array( num_Cgeom_pos_coords );
Dpos_normals = new Float32Array( num_Cgeom_pos_coords );
for ( var pc = 0; pc < num_Cgeom_pos_coords ; pc ++ )
{
Dpos_coords[ pc ] = Cgeometry.attributes.position.array[pc];
Dpos_normals[ pc ] = Cgeometry.attributes.normal.array[pc];
}
Dgeometry.addAttribute( 'position', new THREE.BufferAttribute( Dpos_coords, 3 ) );
Dgeometry.addAttribute( 'normal', new THREE.BufferAttribute( Dpos_normals,3 ) );
Pack_of_Triangles_mesh_D = new THREE.Mesh( Dgeometry, Sphere_Ord_material);
For some reason when I copy from the SphereBufferGeometry to a BufferGeometry and make a mesh only a small fraction (about 20-25%?) of the triangles are actually plotted.
QUESTION
So my question is: What do I need to do to get all the triangles to plot?
UPDATE1
By contrast the following method works OK: (1) make a mesh with a SphereGeometry, (2) make a new BufferGeometry with .setFromObject(mesh) (3) copy from the previous BufferGeometry to a new BufferGeometry and use the latter to make a new mesh. But this seems a long-winded approach.
UPDATE2
The following method also works OK when Cgeometry is a SphereBufferGeometry.
Dgeometry = new THREE.BufferGeometry();
Dgeometry.copy ( Cgeometry );
var num_Dgeom_pos_coords = Dgeometry.attributes.position.array.length;
var num_D_geom_vertices = num_D_geom_pos_coords/3;
It seems that the original problem is due to indexing being used in a SphereBufferGeometry and no index being built by default in a new BufferGeometry.
There doesn't seem to be a simple way of copying the index from the SphereBufferGeometry to the new BufferGeometry.
I am a bit confused because I thought the idea of BufferGeomtry involved doing without indexing.
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