Threejs: Shadow appearing as box shape - three.js

I set up a simple glb viewer with three.js. The model casts and accepts shadows. The problem is that dark boxes appear once I set a spotLight. I'm not sure what the problem is.
I uploaded the project here: https://github.com/maxibenner/threejsviewer

Configuring proper shadows can sometimes be difficult.
var spotLight = new THREE.SpotLight(0xffa95c, 2)
spotLight.castShadow = true
spotLight.position.set(2,2,-2)
spotLight.angle = Math.PI * 0.1;
spotLight.shadow.camera.near = 1;
spotLight.shadow.camera.far = 4;
spotLight.shadow.bias = - 0.002;
spotLight.shadow.mapSize.set( 1024, 1024 );
The idea is to move the spot light closer to the model and tighten the shadow camera's frustum as good as possible. The bias configuration is necessary to avoid self-shadowing artifacts. A bigger shadow map size (default is 512x512) sharpens the shadows.
As mentioned in the comment, adding an instance of CameraHelper to your scene is very helpful when optimizing shadows:
scene.add( new THREE.CameraHelper( spotLight.shadow.camera ) );

Related

Seams on cube edges when using texture atlas with three.js

I have seams between horizontal faces of the cube when use texture atlas in three.js.
This is demo: http://jsfiddle.net/rnix/gtxcj3qh/7/ or http://jsfiddle.net/gtxcj3qh/8/ (from comments)
Screenshot of the problem:
Here I use repeat and offset:
var materials = [];
var t = [];
var imgData = document.getElementById("texture_atlas").src;
for ( var i = 0; i < 6; i ++ ) {
t[i] = THREE.ImageUtils.loadTexture( imgData ); //2048x256
t[i].repeat.x = 1 / 8;
t[i].offset.x = i / 8;
//t[i].magFilter = THREE.NearestFilter;
t[i].minFilter = THREE.NearestFilter;
t[i].generateMipmaps = false;
materials.push( new THREE.MeshBasicMaterial( { map: t[i], overdraw: 0.5 } ) );
}
var skyBox = new THREE.Mesh( new THREE.CubeGeometry( 1024, 1024, 1024), new THREE.MeshFaceMaterial(materials) );
skyBox.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, -1 ) );
scene.add( skyBox );
The atlas has size 2048x256 (power of two). I also tried manual UV-mapping instead of repeat, but the result is the same. I use 8 tiles instead of 6 because I have thought precision of division 1/6 causes the problem, but not.
Pixels on this line are from next tile in atlas. I tried completly white atlas and there was not any artefacts. This explains why there are not seams on vertical borders of Z-faces. I have played with filters, wrapT, wrapS and mipmaps but it does not help. Increasing resolution does not help. There is 8192x1024 atlas http://s.getid.org/jsfiddle/atlas.png I tried another atlas, the result is the same.
I know that I can split atlas into separate files and it works perfectly but it is not convenient.
Whats wrong?
I think the issue is the filtering problem with texture sheets. On image borders in a texture sheet, the gpu may pick the texel from either the correct image or the neighbor image due to limited precision. Because the colors are usually very different, this results in the visible seams. In regular textures, this is solved with CLAMP_TO_EDGE.
If you must use texture alias, then you need to fake CLAMP_TO_EDGE behavior by padding the image borders. See this answer https://gamedev.stackexchange.com/questions/61796/sprite-sheet-textures-picking-up-edges-of-adjacent-texture. It should look something like this: (exaggerated borders for clarity)
Otherwise, the simpler solution is to use a different texture for each face. Webgl supports the cube texture and that is usually used the majority of the time to implement skyboxes.
Hack the uv, replace all value 1.0 with 0.999, replace all value 0 with 0.001 will fakely resolve part of this problem.

Three.js terrain shadowing

I've done a terrain using planegeometry object. I set a vertexes' y coordinate to bump my terrain. Then I added a directional light to my scene and see that there is no shadowing of the "hills" etc.
I added also a sphere and noticed that there is also no shadow on this terrain.
var light = new THREE.DirectionalLight(0xffffff, 1);
light.castShadow = true;
light.shadowCameraVisible = true;
light.position.set(-300, 120, -200); // CHANGED
scene.add(light);
scene.add( new THREE.DirectionalLightHelper(light, 0.2) );
Directional light with sphere above the terrain
But when I replaced directional light by a spotlight I've seen shadowing on a terrain that I want to have.
var slight = new THREE.SpotLight(0xffffff,1);
slight.position.set(-100,60,100);
slight.shadowCameraVisible = true;
scene.add(slight);
scene.add(new THREE.SpotLightHelper(slight, 0.5));
Spotlight with terrain
So the questions are:
how can I do a light that looks like sunshine so the terrain will be not plane color but depend on light? (in future it will be a part of a real city)
what should I do to see the objects' shadows on the terrain? (from fiddle example: sphere's shadow)
Thanks
You are modifying the vertices of your terrain. When you do so, you have to also modify the vertex normals. One way to do that is like so:
geometry.computeVertexNormals();
To create shadows you must enable them.
renderer.shadowMap.enabled = true;
three.js r.130

Three.js - Sprite / Text Label Performance

There is a three.js scene with some 3D objects and 200 - 300 small text labels (< 10% are visible to the camera at one perspective). Adding the text sprites decreased the FPS from 60 to 30 - 40 and its also very memory consuming.
Is there a way to make the sprites faster?
I read about caching material, but labels are all unique - so this isn't possible.
Test: https://jsfiddle.net/h9sub275/4/
(You can change SPRITE_COUNT to see an FPS drop on your machine)
Edit 1: Setting the canvas size to the text bounds will decrease memory consumption, but not improve the FPS.
var Test = {
SPRITE_COUNT : 700,
init : function() {
this.renderer = new THREE.WebGLRenderer({antialias : true}); // false, a bit faster without antialias
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.container = document.getElementById('display');
this.container.appendChild(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
this.scene = new THREE.Scene();
this.group = new THREE.Object3D();
this.scene.add(this.group);
for (var i = 0; i < this.SPRITE_COUNT; i++) {
var sprite = this.makeTextSprite('label ' + i, 24);
sprite.position.set(Math.random() * 20 - 10, Math.random() * 20 - 10, Math.random() * 20 - 10);
this.group.add(sprite);
}
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.left = '0px';
this.stats.domElement.style.top = '0px';
document.body.appendChild(this.stats.domElement);
this.render();
},
render : function() {
var self = this;
this.camera.rotation.x += 0.002;
this.renderer.render(this.scene, this.camera);
this.stats.update();
requestAnimationFrame(function() {self.render();});
},
makeTextSprite : function(message, fontsize) {
var ctx, texture, sprite, spriteMaterial,
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
ctx.font = fontsize + "px Arial";
// setting canvas width/height before ctx draw, else canvas is empty
canvas.width = ctx.measureText(message).width;
canvas.height = fontsize * 2; // fontsize * 1.5
// after setting the canvas width/height we have to re-set font to apply!?! looks like ctx reset
ctx.font = fontsize + "px Arial";
ctx.fillStyle = "rgba(255,0,0,1)";
ctx.fillText(message, 0, fontsize);
texture = new THREE.Texture(canvas);
texture.minFilter = THREE.LinearFilter; // NearestFilter;
texture.needsUpdate = true;
spriteMaterial = new THREE.SpriteMaterial({map : texture});
sprite = new THREE.Sprite(spriteMaterial);
return sprite;
}
};
window.onload = function() {Test.init();};
You are correct, three.js is indeed causing the GPU to use a lot of texture memory this way. Keep in mind that every texture sent to the GPU must be as high as it is wide, so you'll be wasting a lot of memory by making a single canvas for each sprite.
A challenge here is the three.js design choice to have a single set of UV coordinates on a Texture; even when you combine the label sprites in a single texture map and you .clone() your texture for each material without some extra effort, it'll still send each Texture to the GPU without sharing the memory. In short, it currently has no documented way of telling it these textures are the same, and you can't point each Material at the same Texture as it's not the Material which keeps the UVs. https://github.com/mrdoob/three.js/issues/5821 discusses that problem.
I've worked around these issues by combining my sprites in one or more texture maps. For this I created a "sprite texture atlas manager" which manages the allocation of sprite textures as I need them, and I ported a knapsack algorithm to JS which helps me to (mostly) fill up these texture maps with my labels so not a lot of memory is wasted.
I've extracted my code for this out in a separate library, and it is available here: https://github.com/Leeft/three-sprite-texture-atlas-manager with a live example (which does not yet use sprites, but that should be easy to add) at http://jsfiddle.net/Shiari/sbda72k9/.
Fortunately, while this is not documented yet, I also found that it's quite easy now in recent versions (r73 at least, perhaps r72 as well) to force the textures to share the GPU memory by making sure they all have the same .uuid value. My library makes sure to make use of this, and in my testing so far (with 2048x2048 sprite maps; I only need two of those at the size that I'm rendering) that this brings GPU memory down from ~2.6GB when not shared to ~300-600MB when shared. (2048px is far too large when you're only placing a single label there though, and reducing the texture size helps greatly when the maps are not shared).
Lastly, as per your own answer, drawcalls and culling are also a performance issue in r73. I never hit that problem though, since I was already batching my draw calls by grouping everything.
This was a bug in the three.js WebGLRenderer (missing view frustum check for sprites in <= r73). It is already fixed in the dev branch. So you can expect it to be available in r74.
Issue and Details https://github.com/mrdoob/three.js/issues/7371
Fixed version with latest dev build: https://jsfiddle.net/h9sub275/9/
Performance Test with r73: https://jsfiddle.net/h9sub275/7/
(Click to see the performance difference between manually removing invisible sprites and not removing them)
Latest Dev Build:
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.js"> </script>

THREE.js spotlight casting shadow with no object between the near plane and the receiver

I'm having troubles with getting shadows to work in three.js . On the image below you can see a spotlight casting a shadow on an object with plane geometries. There is no object in between the spotlight near plane and the plane geometries of the wall.
What can cause such behavior?
The light is configured this way:
var spotLight = new THREE.SpotLight(0xFFFFFF, 1);
spotLight.position.set(0, -20, 100);
spotLight.target.position.set(0,0,0);
spotLight.shadowDarkness = 0.5;
spotLight.shadowCameraVisible = true;
spotLight.castShadow = true;
spotLight.shadowMapWidth = 1024;
spotLight.shadowMapHeight = 1024;
spotLight.shadowCameraNear = 10;
spotLight.shadowCameraFar = 200;
spotLight.shadowCameraFov = 10;
spotLight.shadowBias = 0.1;
scene.add(spotLight);
The rectangular meshes are BufferGeometries converted from PlaneGeometries with an png uv mapped image as the texture with .receiveShadow set to true.
THREE.js version r71.
Any help will be very much appreciated.
Maybe it is self-shadowing, try to play with spotLight.shadowBias. Usually, a value of 0.05 is used for avoid self-shadowing.

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