Why are Three.js Geometries losing precision with large numbers? - three.js

I'm trying to draw this hexagon in Three.js. If I use small numbers, it draws fine. But if I use big numbers (that seem to fit into a double without losing precision), Three.js seems to have issues drawing it precisely. I have demonstrated this in a code pen here. The code of it is below. Notice if you change the offsetX and offsetY to be very large numbers, it does not draw the same. I don't quite understand why and was wondering if someone could shed some light on this for me. It seems like these numbers all fit within the double type...
var scene = new THREE.Scene();
const offsetX = 0;
const offsetY = 0;
// comment the previous two lines or following two lines to see the difference
// const offsetX = 3031034;
// const offsetY = 4647776;
var camera = new THREE.OrthographicCamera(offsetX-3,offsetX+3,offsetY-3,offsetY+3,1,100);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(400, 400);
document.body.appendChild( renderer.domElement );
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(offsetX + 1.039688, offsetY + 0.850723, 0));
geometry.vertices.push(new THREE.Vector3(offsetX + 0.913957, offsetY + 1.068813, 0));
geometry.vertices.push(new THREE.Vector3(offsetX + 0.661409, offsetY + 1.069356, 0));
geometry.vertices.push(new THREE.Vector3(offsetX + 0.534591, offsetY + 0.851991, 0));
geometry.vertices.push(new THREE.Vector3(offsetX + 0.660321, offsetY + 0.633721, 0));
geometry.vertices.push(new THREE.Vector3(offsetX + 0.91287, offsetY + 0.633178, 0));
geometry.vertices.push(new THREE.Vector3(offsetX + 1.039688, offsetY + 0.850723, 0));
var material = new THREE.LineBasicMaterial({ linewidth: 1, color: 0x00ff00 });
var line = new THREE.Line( geometry, material );
scene.add( line );
camera.position.z = 5;
renderer.render(scene, camera);

While the numbers you show can be represented with sufficient precision in a double format to render the hexagon with no visible distortion, it appears WebGL does not use a double format throughout its operation.
three.js uses WebGL, and WebGL uses OpenGL ES. Information about floating-point formats or arithmetic in the documentation for WebGL and OpenGL ES is spotty or missing. We can only informally infer the formats used from oblique references in the documentation and from the observed behavior.
Most likely, some component in the software is using the IEEE-754 basic 32-bit binary floating-point format. In this format, the significand1 has 24 bits.
With the offsets you show, such as 4,647,776, the exponent of the floating-point format must be set so the highest bit of the significand represents 4,194,304, which is 222. Then the lowest bit is 2−1, or ½.
This means all the coordinates for the vertices of your hexagon, .661409, .851991, .633721, and so on, will be rounded to the nearest ½. Obviously this distorts the drawing. Tinkering with other values for the offsets shows effects consistent with 32-bit binary floating-point precision.
Footnote
1 A floating-point number consists of a sign bit s, a significand f, and an exponent e, and represents the value (−1)s • f • be, where b is the base or radix of the format (2 for binary). The significand is also called the fraction portion of the floating-point representation, hence the f for significand.

Related

threejs - Defect in rotation - THREE.OrbitControls

I use OrbitControls now but still i have strange bug. It is hard to explain. When i drag mouse down in the begin work normally and then in one moment whole scene begin to rotate in wrong direction and flip my whole scene.
I got warnings :
OrbitControls.js:1103 [Violation] Added non-passive event listener to
a scroll-blocking 'wheel' event. Consider marking event handler as
'passive' to make the page more responsive. See
https://www.chromestatus.com/feature/5745543795965952
Here is my code:
controls = new THREE.OrbitControls(camera, renderer.domElement);
//controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.05;
controls.screenSpacePanning = true;
controls.minDistance = 14;
controls.maxDistance = 120;
controls.maxPolarAngle = Math.PI / 3;
controls.target.set(5, 4, -20);
I need to limit rotation , disable 360 rotating scene.
For example i wanna allow max angle of 45.
Try this, i had a familiar issue and applied it to my code and worked
camera.up = new THREE.Vector3( 0, 0, 1 );
Did you take a look at the documentation? It outlines four different properties to limit angles of rotation. These are the defaults:
// How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
controls.minPolarAngle = 0; // radians
controls.maxPolarAngle = Math.PI; // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
controls.minAzimuthAngle = - Infinity; // radians
controls.maxAzimuthAngle = Infinity; // radians
Edit:
The above solution is for OrbitControls, which is not what the original question asked. TrackballControls does not offer the ability to limit angles of rotation.

How to preserve threejs texture scale while applying texture rotation

I'd like to enable a user to rotate a texture on a rectangle while keeping the aspect ratio of the texture image intact. I'm doing the rotation of a 1:1 aspect ratio image on a surface that is rectangular (say width: 2 and length: 1)
Steps to reproduce:
In the below texture rotation example
https://threejs.org/examples/?q=rotation#webgl_materials_texture_rotation
If we change one of the faces of the geometry like below:
https://github.com/mrdoob/three.js/blob/master/examples/webgl_materials_texture_rotation.html#L57
var geometry = new THREE.BoxBufferGeometry( 20, 10, 10 );
Then you can see that as you play around with the rotation control, the image aspect ratio is distorted. (form a square to a weird shape)
At 0 degree:
At some angle between 0 and 90:
I understand that by changing the repeatX and repeatY factor I can control this. It's also easy to see what the values would be at 0 degree, 90 degree rotations.
But I'm struggling to come up with the formula for repeatX and repeatY that works for any texture rotation given length and width of the rectangular face.
Unfortunately when stretching geometry like that, you'll get a distortion in 3D space, not UV space. In this example, one UV.x unit occupies twice as much 3D space as one UV.y unit:
This is giving you those horizontally-skewed diamonds when in between rotations:
Sadly, there's no way to solve this with texture matrix transforms. The horizontal stretching will be applied after the texture transform, in 3D space, so texture.repeat won't help you avoid this. The only way to solve this is by modifying the UVs so the UV.x units take up as much 3D space as UV.y units:
With complex models, you'd do this kind of "equalizing" in a 3D editor, but since the geometry is simple enough, we can do it via code. See the example below. I'm using a width/height ratio variable to use in my UV.y remapping, that way the UV transformations will match up, regardless of how much wider it is.
//////// Boilerplate Three setup
const renderer = new THREE.WebGLRenderer({canvas: document.querySelector("canvas")});
const camera = new THREE.PerspectiveCamera(50, 1, 1, 100);
camera.position.z = 3;
const scene = new THREE.Scene();
/////////////////// CREATE GEOM & MATERIAL
const width = 2;
const height = 1;
const ratio= width / height; // <- magic number that will help with UV remapping
const geometry = new THREE.BoxBufferGeometry(width, height, width);
let uvY;
const uvArray = geometry.getAttribute("uv").array;
// Re-map UVs to avoid distortion
for (let i2 = 0; i2 < uvArray.length; i2 += 2){
uvY = uvArray[i2 + 1]; // Extract Y value,
uvY -= 0.5; // center around 0
uvY /= ratio; // divide by w/h ratio
uvY += 0.5; // remove center around 0
uvArray[i2 + 1] = uvY;
}
geometry.getAttribute("uv").needsUpdate = true;
const uvMap = new THREE.TextureLoader().load("https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/uv_grid_opengl.jpg");
// Now we can apply texture transformations as expected
uvMap.center.set(0.5, 0.5);
uvMap.repeat.set(0.25, 0.5);
uvMap.anisotropy = 16;
const material = new THREE.MeshBasicMaterial({map: uvMap});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("resize", resize);
// Add rotation on mousemove
function onMouseMove(ev) {
uvMap.rotation = (ev.clientX / window.innerWidth) * Math.PI * 2;
}
function resize() {
const width = window.innerWidth;
const height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
function animate(time) {
mesh.rotation.y = Math.cos(time/ 3000) * 2;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
resize();
requestAnimationFrame(animate);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://threejs.org/build/three.js"></script>
<canvas></canvas>
First of all, I agree with the solution #Marquizzo provided to your problem. And setting UV explicitly to the geometry should be the easiest way to solve your problem.
But #Marquizzo did not answer why changing the matrix of the texture (set repeatX and repeatY) does not work.
We all know the 2D rotation matrix R
cos -sin
sin cos
UVs are calculated in the shader with a transform matrix T, which is the texture matrix from your question.
T * UV = new UV
To simplify the question, we only consider rotation. And assume we have another additional matrix X for calculating the new UV. Then we have
X * R * UV = new UV
The question now is whether we can find a solution ofX, so that with any rotation, new UV of any points in your question can be calculated correctly. If there is a solution of X, then we can simply use
var X = new Matrix3();
//X.set(x,y,z,...)
texture.matrix.premultiply(X);
Otherwise, we can't find the approach you expected.
Let's create several equations to figure out X.
In the pic below, ABCD is one face of your geometry, and the transparent green is the texture. The UV of point A is (0,1), point B is (0,0), and (1,0), (1,1) for C and D respectively.
The first equation comes from the consideration, without any rotation, the original UV should never be changed (UV for A is always (0,1)). So we should have
X * I * (0, 1) = (0, 1) // I is the identity matrix
From here we can see X should also be an identity matrix.
Then let's see whether the identity matrix X can satisfy the second equation. What's the second equation? Simplify again, let B be the rotation centre(origin) and rotate the texture 90 degrees(counterclockwise). We use -90 to calculate UV though we rotate 90 degrees.
The new UV for point A after rotating the texture 90 degrees should be the current value of E. The value of E is (a/b, 0). Then we have
From this equation we can see X should not be an identity matrix, which means, WE ARE NOT ABLE TO FIND A SOLUTION OF X TO SOLVE YOUR PROBLEM WITH
X * R * UV = new UV
Certainly, you can change the shader of calculating new UVs, but it's even harder than the way #Marquizzo provided.

Objects with big speed are flickering/jumping

I have a container that contains three objects, when the container is moving with big speed it's children start to flicker and then jumping after some time, why is that so?
function init() {
// ...
geometry = new THREE.CubeGeometry(100, 100, 100);
mesh = new THREE.Mesh(geometry, material);
container.add(mesh);
geometry = new THREE.CubeGeometry(50, 50, 50);
mesh = new THREE.Mesh(geometry, material);
mesh.position.z = 75;
container.add(mesh);
geometry = new THREE.CubeGeometry(25, 25, 50);
mesh = new THREE.Mesh(geometry, material);
mesh.position.z = 100;
container.add(mesh);
// ...
}
function render() {
// The bigger the speed of object the more its jumping
container.position.z += 1000000;
}
jsfiddle
EDIT:
I did some investigation and found out that when I apply modelVIewMatrix on position of children and compare difference (distance) between those children, the difference is changing after some time and that is when the objects start to jump.
jsfiddle
basically the problems are caused by floating point arithmetic errors..
the projection matrix used from camera looks like this - notice it contains non-integers
[1.7243168354034424, 0, 0, 0,
0, 2.1445069313049316, 0, 0,
0, 0, -1.0002000331878662, -1,
0, 0, -2.000200033187866, 0]
javascript uses floating-point representation of numbers, while it to some degree works well with integers there are slight errors with real numbers generally
for instance if you type 0.1 + 0.2 into javascript console the output will not be 0.3 but 0.30000000000000004
when you set position of your cube thingy to a few millions these errors in the projection matrix that were on less significant places move up and become noticeable,
the bigger the number the worse it gets and that is why it starts out like a smal vibration and then becomes utter disaster
if you really need objects to move that way you will have to make changes to THREE library and represent its matrix numbers in more bits(in some bytearray) or hack around floating-point operations in some other way

How to click an object in THREE.js

I'm working my way through this book, and I'm doing okay I guess, but I've hit something I do not really get.
Below is how you can log to the console and object in 3D space that you click on:
renderer.domElement.addEventListener('mousedown', function(event) {
var vector = new THREE.Vector3(
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0
);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(
camera.position,
vector.sub(camera.position).normalize()
);
var intersects = raycaster.intersectObjects(OBJECTS);
if (intersects.length) {
console.log(intersects[0]);
}
}, false);
Here's the book's explanation on how this code works:
The previous code listens to the mousedown event on the renderer's canvas.
Get that, we're finding the domElement the renderer is using by using renderer.domElement. We're then binding an event listener to it with addEventListner, and specifing we want to listening for a mousedown. When the mouse is clicked, we launch an anonymous function and pass the eventvariable into the function.
Then,
it creates a new Vector3 instance with the mouse's coordinates on the screen
relative to the center of the canvas as a percent of the canvas width.
What? I get how we're creating a new instance with new THREE.Vector3, and I get that the three arguments Vector3 takes are its x, y and z coordinates, but that's where my understanding completely and utterly breaks down.
Firstly, I'm making an assumption here, but to plot a vector, surely you need two points in space in order to project? If you give it just one set of coords, how does it know what direction to project from? My guess is that you actually use the Raycaster to plot the "vector"...
Now onto the arguments we're passing to Vector3... I get how z is 0. Because we're only interested in where we're clicking on the screen. We can either click up or down, left or right, but not into or out of the screen, so we set that to zero. Now let's tackle x:
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
We're getting the PixelRatio of the device, timsing it by where we clicked along the x axis, dividing by the renderer's domElement width, timsing this by two and taking away one.
When you don't get something, you need to say what you do get so people can best help you out. So I feel like such a fool when I say:
I don't get why we even need the pixel ratio I don't get why we times that by where we've clicked along the x
I don't get why we divide that by the width
I utterly do not get why we need to times by 2 and take away 1. Times by 2, take away 1. That could genuinely could be times by an elephant, take away peanut and it would make as much sense.
I get y even less:
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
Why are we now randomly using -devicePixelRatio? Why are now deciding to add one rather than minus one?
That vector is then un-projected (from 2D into 3D space) relative to the camera.
What?
Once we have the point in 3D space representing the mouse's location,
we draw a line to it using the Raycaster. The two arguments that it
receives are the starting point and the direction to the ending point.
Okay, I get that, it's what I was mentioning above. How we need two points to plot a "vector". In THREE talk, a vector appears to be called a "raycaster".
However, the two points we're passing to it as arguments don't make much sense. If we were passing in the camera's position and the vector's position and drawing the projection from those two points I'd get that, and indeed we are using the camera.position for the first points, but
vector.sub(camera.position).normalize()
Why are we subtracting the camera.position? Why are we normalizing? Why does this useless f***** book not think to explain anything?
We get the direction by subtracting the mouse and camera positions and
then normalizing the result, which divides each dimension by the
length of the vector to scale it so that no dimension has a value
greater than 1.
What? I'm not being lazy, not a word past by makes sense here.
Finally, we use the ray to check which objects are located in the
given direction (that is, under the mouse) with the intersectObjects
method. OBJECTS is an array of objects (generally meshes) to check; be
sure to change it appropriately for your code. An array of objects
that are behind the mouse are returned and sorted by distance, so the
first result is the object that was clicked. Each object in the
intersects array has an object, point, face, and distance property.
Respectively, the values of these properties are the clicked object
(generally a Mesh), a Vector3 instance representing the clicked
location in space, the Face3 instance at the clicked location, and the
distance from the camera to the clicked point.
I get that. We grab all the objects the vector passes through, put them to an array in distance order and log the first one, i.e. the nearest one:
console.log(intersects[0]);
And, in all honestly, do you think I should give up with THREE? I mean, I've gotten somewhere with it certainly, and I understand all the programming aspects of it, creating new instances, using data objects such as arrays, using anonymous functions and passing in variables, but whenever I hit something mathematical I seem to grind to a soul-crushing halt.
Or is this actually difficult? Did you find this tricky? It's just the book doesn't feel it's necessary to explain in much detail, and neither do other answers , as though this stuff is just normal for most people. I feel like such an idiot. Should I give up? I want to create 3D games. I really, really want to, but I am drawn to the poetic idea of creating an entire world. Not math. If I said I didn't find math difficult, I would be lying.
I understand your troubles and I'm here to help. It seems you have one principal question: what operations are performed on the vector to prepare it for click detection?
Let's look back at the original declaration of vector:
var vector = new THREE.Vector3(
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0
);
renderer.devicePixelRatio relates to a ratio of virtual site pixels /
real device pixels
event.pageX and .pageY are mouseX, mouseY
The this context is renderer.domElement, so .width, .height, .offsetLeft/Right relate to that
1 appears to be a corrective "magic" number for the calculation (for the purpose of being as visually exact as possible)
We don't care about the z-value, THREE will handle that for us. X and Y are our chief concern. Let's derive them:
We first find the distance of the mouse to the edge of the canvas: event.pageX - this.offsetLeft
We divide that by this.width to get the mouseX as a percentage of the screen width
We multiply by renderer.devicePixelRatio to convert from device pixels to site pixels
I'm not sure why we multiply by 2, but it might have to do with an assumption that the user has a retina display (someone can feel free to correct me on this if it's wrong).
1 is, again, magic to fix what might be just an offset error
For y, we multiply the whole expression by -1 to compensate for the inverted coordinate system (0 is top, this.height is bottom)
Thus you get the following arguments for the vector:
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0
Now, for the next bit, a few terms:
Normalizing a vector means simplifying it into x, y, and z components less than one. To do so, you simply divide the x, y, and z components of the vector by the magnitude of the vector. It seems useless, but it's important because it creates a unit vector (magnitude = 1) in the direction of the mouse vector!
A Raycaster casts a vector through the 3D landscape produced in the canvas. Its constructor is THREE.Raycaster( origin, direction )
With these terms in mind, I can explain why we do this: vector.sub(camera.position).normalize(). First, we get the vector describing the distance from the mouse position vector to the camera position vector, vector.sub(camera.position). Then, we normalize it to make it a direction vector (again, magnitude = 1). This way, we're casting a vector from the camera to the 3D space in the direction of the mouse position! This operation allows us to then figure out any objects that are under the mouse by comparing the object position to the ray's vector.
I hope this helps. If you have any more questions, feel free to comment and I will answer them as soon as possible.
Oh, and don't let the math discourage you. THREE.js is by nature a math-heavy language because you're manipulating objects in 3D space, but experience will help you get past these kinds of understanding roadblocks. I would continue learning and return to Stack Overflow with your questions. It may take some time to develop an aptitude for the math, but you won't learn if you don't try!
This is more universal no matter the render dom location, and the dom and its ancesters's padding margin.
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
here is a demo, scroll to the bottom to click the cube.
<!DOCTYPE html>
<html>
<head>
<script src="http://threejs.org/build/three.min.js"></script>
<link rel="stylesheet" href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" />
<style>
body {
font-family: Monospace;
background-color: #fff;
margin: 0px;
}
#canvas {
background-color: #000;
width: 200px;
height: 200px;
border: 1px solid black;
margin: 10px;
padding: 0px;
top: 10px;
left: 100px;
}
.border {
padding:10px;
margin:10px;
height:3000px;
overflow:scroll;
}
</style>
</head>
<body>
<div class="border">
<div style="min-height:1000px;"></div>
<div class="border">
<div id="canvas"></div>
</div>
</div>
<script>
// Three.js ray.intersects with offset canvas
var container, camera, scene, renderer, mesh,
objects = [],
count = 0,
CANVAS_WIDTH = 200,
CANVAS_HEIGHT = 200;
// info
info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '30px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.style.color = '#f00';
info.style.backgroundColor = 'transparent';
info.style.zIndex = '1';
info.style.fontFamily = 'Monospace';
info.innerHTML = 'INTERSECT Count: ' + count;
info.style.userSelect = "none";
info.style.webkitUserSelect = "none";
info.style.MozUserSelect = "none";
document.body.appendChild( info );
container = document.getElementById( 'canvas' );
renderer = new THREE.WebGLRenderer();
renderer.setSize( CANVAS_WIDTH, CANVAS_HEIGHT );
container.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, CANVAS_WIDTH / CANVAS_HEIGHT, 1, 1000 );
camera.position.y = 250;
camera.position.z = 500;
camera.lookAt( scene.position );
scene.add( camera );
scene.add( new THREE.AmbientLight( 0x222222 ) );
var light = new THREE.PointLight( 0xffffff, 1 );
camera.add( light );
mesh = new THREE.Mesh(
new THREE.BoxGeometry( 200, 200, 200, 1, 1, 1 ),
new THREE.MeshPhongMaterial( { color : 0x0080ff }
) );
scene.add( mesh );
objects.push( mesh );
// find intersections
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
// mouse listener
document.addEventListener( 'mousedown', function( event ) {
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
info.innerHTML = 'INTERSECT Count: ' + ++count;
}
}, false );
function render() {
mesh.rotation.y += 0.01;
renderer.render( scene, camera );
}
(function animate() {
requestAnimationFrame( animate );
render();
})();
</script>
</body>
</html>

Rendering a large number of colored particles using three.js and the canvas renderer

I am trying to use the Three.js library to display a large number of colored points on the screen (about half a million to million for example). I am trying to use the Canvas renderer rather than the WebGL renderer if possible (The web pages would also be displayed in the Google Earth Client bubbles, which seems to work with Canvas renderer but not the WebGL renderer.)
While I have the problem solved for a small number of points (tens of thousands) by modifying the code from here, I am having trouble scaling it beyond that.
But in the the following code using WebGL and the Particle System I can render half a million random points, but without colors.
...
var particles = new THREE.Geometry();
var pMaterial = new THREE.ParticleBasicMaterial({
color: 0xFFFFFF,
size: 1,
sizeAttenuation : false
});
// now create the individual particles
for (var p = 0; p < particleCount; p++) {
// create a particle with randon position values,
// -250 -> 250
var pX = Math.random() * POSITION_RANGE - (POSITION_RANGE / 2),
pY = Math.random() * POSITION_RANGE - (POSITION_RANGE / 2),
pZ = Math.random() * POSITION_RANGE - (POSITION_RANGE / 2),
particle = new THREE.Vertex(
new THREE.Vector3(pX, pY, pZ)
);
// add it to the geometry
particles.vertices.push(particle);
}
var particleSystem = new THREE.ParticleSystem(
particles, pMaterial);
scene.add(particleSystem);
...
Is the reason for the better performance of the above code due to the Particle System? From what I have read in the documentation it seems the Particle System can only be used by the WebGL renderer.
So my question(s) are
a) Can I render such large number of particles using the Canvas renderer or is it always going to be slower than the WebGL/ParticleSystem version? If so, how do I go about doing that? What objects and or tricks do I use to improve performance?
b) Is there a compromise I can reach if I give up some features? In other words, can I still use the Canvas renderer for the large dataset if I give up the need to color the individual points?
c) If I have to give up the Canvas and use the WebGL version, is it possible to change the colors of the individual points? It seems the color is set by the material passed to the ParticleSystem and that sets the color for all the points.
EDIT: ParticleSystem and PointCloud has been renamed to Points. In addition, ParticleBasicMaterial and PointCloudMaterial has been renamed to PointsMaterial.
This answer only applies to versions of three.js prior to r.125.
To have a different color for each particle, you need to have a color array as a property of the geometry, and then set vertexColors to THREE.VertexColors in the material, like so:
// vertex colors
var colors = [];
for( var i = 0; i < geometry.vertices.length; i++ ) {
// random color
colors[i] = new THREE.Color();
colors[i].setHSL( Math.random(), 1.0, 0.5 );
}
geometry.colors = colors;
// material
material = new THREE.PointsMaterial( {
size: 10,
transparent: true,
opacity: 0.7,
vertexColors: THREE.VertexColors
} );
// point cloud
pointCloud = new THREE.Points( geometry, material );
Your other questions are a little too general for me to answer, and besides, it depends on exactly what you are trying to do and what your requirements are. Yes, you can expect Canvas to be slower.
EDIT: Updated for three.js r.124

Resources