Let me preface this with saying I'm very inexperienced with 3D graphics.
Problem
I'm using Three.js. I have two spheres which (deliberately) collide in my WebGL model. When my spheres are incredibly large, the overlapping spheres appear "broken" where they intersect, but smaller spheres render perfectly fine.
I have a very specific reason for using such large units for some objects, and scaling down objects isn't really an option.
Example
Here is a fiddle for the larger spheres: http://jsfiddle.net/YSX7h/
and for the smaller ones: http://jsfiddle.net/7Lca2/
Code
var radiusUnits = 1790; // 179000000
var container;
var camera, scene, renderer;
var clock = new THREE.Clock();
var cross;
var plane;
var controls;
var cube;
var cubeMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff, vertexColors: THREE.VertexColors } );
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 3500000);
controls = new THREE.OrbitControls(camera);
camera.position.set(2000, 2000, 2000);
camera.position.z = 500;
scene = new THREE.Scene();
var texture = THREE.ImageUtils.loadTexture('http://i.imgur.com/qDAEVoo.jpg');
var material = new THREE.MeshBasicMaterial({
color: 0xFFFFFF,
map: texture,
opacity:1
});
var material1 = new THREE.MeshBasicMaterial({ color: 0xFF0000, wireframe: true, opacity:1 });
var geometry = new THREE.SphereGeometry(radiusUnits, 32, 32);
var geometry1 = new THREE.SphereGeometry(radiusUnits, 32, 32);
var mesh = new THREE.Mesh(geometry, material);
var mesh1 = new THREE.Mesh(geometry1, material1);
mesh1.position.set(0, 1000, 0);
mesh.position.set(0, -1000, 0);
scene.add(mesh);
scene.add(mesh1);
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
document.body.appendChild(renderer.domElement);
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onWindowResize() {
renderer.setSize( window.innerWidth, window.innerHeight );
render();
}
function animate() {
controls.update();
requestAnimationFrame( animate );
}
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
(function animloop(){
requestAnimFrame(animloop);
render();
})();
function render() {
var delta = clock.getDelta();
renderer.render( scene, camera );
}
Why, exactly, does this happen? And is there anything I can do to fix this, short of scaling down these objects?
Thanks in advance.
The short answer, set your z near plane further away
Change
to
Note: I don't know if 1000 will work, if it doesn't try 10000.
A zBuffer, the thing used to be able to tell which pixels go in front of other previously drawn pixels, has limited resolution. In WebGL it could be 16bits, 24 or 32. I'm guessing 24 is the most common. For the point of illustration let's assume it was just 4 bits though. That would mean for a given z range there are only 16 possible values. Given the standard math used for 3D projection, on a 4 bit zbuffer, if the range was
zNear = 0.1
andzFar = 3500000
the 16 possible values are something likeAs you can see the range between values increase exponentially meaning there is almost no resolution far away from the camera.
If we increase
zNear
to 1000 we getYou can see it starting to spread out a little. On a 24bit depth buffer with
zNear
at 0.1 andzFar
at 3500000 the range between the last 15 units isWhere as with
zNear
at 1000 they'reWhich is probably a little more reasonable? It's basically saying 2 points that are less than ~728 units different when far away from the camera may be sorted incorrectly. Or to put it in a positive light, as long as 2 points are at least 728 units away from each other in their distance from the camera they'll be sorted correctly.
All of this is to point out that you have to set your near and far clipping planes appropriately for your application.
I should probably note that the math being applied is just the most common math and probably the same math that three.js used by default. With your own vertex shaders you could make the zbuffer do something else. Here's a good article on it.
You could also try using a logarithmic depth buffer:
You're suffering from a precision issue in the depth buffer. The larger the scale of your scene the more pronounced this becomes -- especially for objects that span a large distance. The depth buffer has only 32bits (floating point I believe) to work with. As you increase the Z-Range of your camera the precision drops. A standard camera tends to have increased precision near the "near" plane, and reduces to the distance (though I'm not positive on what matrix three.js is actually using).
Either you reduce your scene size, or move the near plane further away from the camera. If you search the topic of depth buffers and precision you can find a lot of information on this topic. Refer to the generic OpenGL info as well, not just three.js or WebGL.
Note: To clarify, the reason the two scenes are different is because you haven't scaled the camera's settings the same. In the scene that works simple set the near plane to "0.00001" and you'll see the same problem.