Specifying the range for Depth of Field (MeshDepthMaterial range?) - three.js

I have adapted this post processing example http://mrdoob.github.com/three.js/examples/webgl_postprocessing_dof.html to apply a Depth of Field / Bokeh effect. How can I specify the focus range (or whatever it could be called)?
If the camera far plane is at 10000, and the model size is 10 it was impossible to focus into invidual parts of the model - because it tries to focus from 1-10000 (camera-near to camera-far) instead of 1-10 (between camera and the back of my model), the actual area of interest.
It did work fine after I realised to set the camera far plane as low as possible (to about same as scene size), so the focus can adjusted where the actual model is.
Now I can't do the camera far plane trick anymore, because I added a skybox, so the camera needs to have it's far plane quite far related to the model size. That messes up the Depth of Field; I can focus very close or very far, but the whole model is either completely blurred or not blurred at all as the adjustable distance is way too big (all the way to the skybox).
If I know the area I want to be able to focus at, how can I specify it in my code?
Here is my setup code:
dof_material_depth = new THREE.MeshDepthMaterial();
dof_scene = new THREE.Scene();
dof_camera = new THREE.OrthographicCamera(SCREEN_WIDTH / - 2, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, SCREEN_HEIGHT / - 2, -10000, 10000 );
dof_camera.position.z = 100;
dof_scene.add( dof_camera );
var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat };
dof_rtTextureDepth = new THREE.WebGLRenderTarget(SCREEN_WIDTH, SCREEN_HEIGHT, pars );
dof_rtTextureColor = new THREE.WebGLRenderTarget(SCREEN_WIDTH, SCREEN_HEIGHT, pars );
dof_bokeh_shader = THREE.BokehShader;
dof_bokeh_uniforms = THREE.UniformsUtils.clone(dof_bokeh_shader.uniforms );
dof_bokeh_uniforms[ "tColor" ].value = dof_rtTextureColor;
dof_bokeh_uniforms[ "tDepth" ].value = dof_rtTextureDepth;
dof_bokeh_uniforms[ "focus" ].value = 1.1;
dof_bokeh_uniforms[ "aspect" ].value = SCREEN_WIDTH / SCREEN_HEIGHT;
dof_materialBokeh = new THREE.ShaderMaterial( {
uniforms: dof_bokeh_uniforms,
vertexShader: dof_bokeh_shader.vertexShader,
fragmentShader: dof_bokeh_shader.fragmentShader
});
dof_quad = new THREE.Mesh( new THREE.PlaneGeometry(SCREEN_WIDTH, SCREEN_HEIGHT), dof_materialBokeh );
dof_quad.position.z = -500;
dof_scene.add(dof_quad );
And here the rendering part:
renderer.render(scene, camera, dof_rtTextureColor, true );
scene.overrideMaterial = dof_material_depth;
renderer.render(scene, camera, dof_rtTextureDepth, true );
dof_scene.overrideMaterial = null;
render(dof_scene, dof_camera );
var delta = 0.01;
composerScene.render( delta);
EDIT:
I did manage to get desired results by setting a low far plane for the camera just before rendering the depth material, then reverting back to normal before rendering the composite:
renderer.render(scene, camera, dof_rtTextureColor, true );
var oldfar = camera.far; // this goes to skybox
camera.far = scenesize; // this goes to just behind the model
scene.overrideMaterial = dof_material_depth;
renderer.render(scene, camera, dof_rtTextureDepth, true );
camera.far = oldfar;
dof_scene.overrideMaterial = null;
render(dof_scene, dof_camera );
var delta = 0.01;
composerScene.render( delta);
This works perfect. I will leave the question open though, as I'm quite new to WebGLL / 3D programming in general, want to learn, and would like to know if it's possible to do this in the shaders/materials setup phase.

Related

Shadows Distance problems in ThreeJS

I'm currently working with a directionnal ligth for my scene. A small character can move around. All shadows works. But if I start to go a bit further with the avatar, the shadows don't work anymore on it. I think it's may come from the angle of the light but I can't change it. I can draw a square around the shadow/non-shadow area.
Here is my code. Huge numbers were here for me to test things
const dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
dirLight.color.setHSL( 0.1, 1, 0.95 );
dirLight.position.set( - 1, 1.75, 1 );
dirLight.position.multiplyScalar( 30 );
dirLight.distance = 1200;
dirLight.focus = 1200;
dirLight.angle = Math.PI / 1;
scene.add( dirLight );
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
const d = 100;
dirLight.shadow.camera.left = - d;
dirLight.shadow.camera.right = d;
dirLight.shadow.camera.top = d;
dirLight.shadow.camera.bottom = - d;
dirLight.shadow.camera.far = 35000;
dirLight.shadow.bias = - 0;
const dirLightHelper = new THREE.DirectionalLightHelper( dirLight, 10 );
scene.add( dirLightHelper );
Here is the code of the ground that recive shadows
const groundGeo = new THREE.PlaneBufferGeometry( 10000, 10000 );
const groundMat = new THREE.MeshLambertMaterial( { color: 0xffffff } );
groundMat.color.setHSL( 0.095, 1, 0.75 );
const ground = new THREE.Mesh( groundGeo, groundMat );
ground.position.y = - 33;
ground.rotation.x = - Math.PI / 2;
ground.receiveShadow = true;
scene.add( ground );
I reused a huge part of the code provided by the ThreeJS example with the flamingo https://threejs.org/examples/?q=light#webgl_lights_hemisphere
Here is also a link to an imgur where I just put some picture to see the shadows
https://imgur.com/a/MGrc5cw
Thanks
If your shadows are working within a limited area, then it must be that your character is walking beyond the frustum of the shadow-camera.
To create directional shadows, you create an orthographic camera and assign its dimensions with .left, .right, .top, .bottom, in this case your camera is has 100 in each direction. When your character walks beyond those 200 units, it is outside the shadow camera, and it no longer receives shadows.
You should add a CameraHelper to the light shadow to let you see the space within its view frustum, as seen in the DirectionalLightShadow code sample
const helper = new THREE.CameraHelper( dirLight.shadow.camera );
scene.add( helper );
Maybe you could have the shadow camera follow the character so he'll always be inside of it.

Soft shadow has an unintended offset

I'm currently working on a soft / blurred shadow effect that is casted on a plane directly under my object (just for giving it some more depth). The light source (DirectionalLight) shares the center coordinates of the object but with an offset in Y, so that it's straight above. It is pointing down to the center of the object.
I experimented a little bit with the shadow parameters of the light and found out that lowering the shadow map size gives me quite a nice soft shadow effect which would be sufficient for me. For example:
light.shadow.mapSize.width = 32;
light.shadow.mapSize.height = 32;
However, i noticed that there is an offset to the shadow which lets the observer assume that the light source is not coming directly from above:
I created this fiddle from which i created the image. As shadow type i use the PCFSoftShadowMap.
With this setup I would assume that the shadow effect is equally casted on all four sides of the cube, but it's obviously not. I also noticed that this 'offset' gets smaller when increasing the shadow map size and is barely noticable when using for example sizes like 512 or 1024.
This method would be an easy and performant solution for the desired effect, so I really appreciate any help on this
EDIT:
As stated out in the comments, tweaking the radius of the LightShadow isn't a satisfiying solution because the shadow gradient has hard edges instead of soft ones.
I think what is happening is that your shadowmap is low enough resolution, that you're seeing rounding error. If you switch back to THREE.BasicShadowMap, I think you will see that the physical lightmap pixels being hit happen to lie on the side of the object that you're seeing the larger edge, and as you move the object, the shadow will move in steps the size of the pixels on the map.
Generally in practice, you want to use a higher res lightmap, and keep its coverage area as tight around the focal point of your scene as possible to give you the most resolution from the lightmap. Then you can tweak the .radius of of the LightShadow to get the right softness.
One solution i came up with is using four light sources, all with a very slight positional offset, so that the 'shadow-offset' would come from four different directions (http://jsfiddle.net/683049eb/):
// a basic three.js scene
var container, renderer, scene, camera, controls, light, light2, light3, light4, cubeCenter, cube;
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xccccff);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container = document.createElement('div');
document.body.appendChild(container);
container.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 200, 800);
camera.lookAt(scene.position);
// (camera) controls
// mouse controls: left button to rotate,
// mouse wheel to zoom, right button to pan
controls = new THREE.OrbitControls(camera, renderer.domElement);
var size = 100;
// ambient light
var ambient = new THREE.AmbientLight(0xffffff, 0.333);
scene.add(ambient);
// mesh
var cubeGeometry = new THREE.BoxGeometry(size, size, size);
var cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000
});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.y = size / 2.0;
cube.castShadow = true;
cube.receiveShadow = false;
scene.add(cube);
// Get bounding box center
var boundingBox = new THREE.Box3().setFromObject(cube);
cubeCenter = new THREE.Vector3();
boundingBox.getCenter(cubeCenter);
var position1 = new THREE.Vector3(0, size * 2, 0.0000001);
createDirectionalLight(scene, 0.15, position1, size, cubeCenter);
var position2 = new THREE.Vector3(0, size * 2, -0.0000001);
createDirectionalLight(scene, 0.15, position2, size, cubeCenter);
var position3 = new THREE.Vector3(0.0000001, size * 2, 0);
createDirectionalLight(scene, 0.15, position3, size, cubeCenter);
var position4 = new THREE.Vector3(-0.0000001, size * 2, 0);
createDirectionalLight(scene, 0.15, position4, size, cubeCenter);
// shadow plane
var planeGeometry = new THREE.PlaneGeometry(500, 500, 100, 100);
var planeMaterial = new THREE.MeshLambertMaterial({
// opacity: 0.6,
color: 0x65bf32,
side: THREE.FrontSide
});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;
scene.add(plane);
// events
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
function createDirectionalLight(scene, intensity, position, cameraSize, targetPosition) {
var light = new THREE.DirectionalLight(0xffffff, intensity);
light.position.set(position.x, position.y, position.z);
light.target.position.set(targetPosition.x, targetPosition.y, targetPosition.z);
light.target.updateMatrixWorld(true);
light.castShadow = true;
scene.add(light);
light.shadow.mapSize.width = 32;
light.shadow.mapSize.height = 32;
light.shadow.camera.left = -cameraSize;
light.shadow.camera.right = cameraSize;
light.shadow.camera.bottom = -cameraSize;
light.shadow.camera.top = cameraSize;
light.shadow.camera.near = 1.0;
light.shadow.camera.far = cameraSize * 3;
light.shadow.bias = 0.0001;
scene.add(new THREE.CameraHelper(light.shadow.camera));
}
<script src="http://threejs.org/build/three.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>

three.js sprites seem to ignore parent's scale when using a PerspectiveCamera

I'm trying to have text sprites in the 3d scene with constant size (regardless of camera distance) using a PerspectiveCamera. In order to get non-sprites to have constant size, I make them children of a special "scaled" object which adjusts its scale as the camera distance to origin changes (see the code below). This works well to keep a general object roughly the same visual size, but when I add a sprite to the scaled object, the sprite seems to ignore its parent's scale (so it gets smaller and bigger as you zoom out and in).
Interestingly, when we switch to an orthographic camera (uncomment the appropriate line below), this special scaled object doesn't seem to affect children anymore (i.e., children don't stay a constant size). However, since we have an orthographic camera, sprites no longer scale as the camera distance changes (so they maintain a constant size), but this is independent of the scaled object.
I notice a few other similar questions and answers, including adjust the scale of the sprites themselves (it seems much easier to add all my sprites to a single scaling object), use an orthographic camera overlay to draw sprites (see also this) (but I want my sprites to be inside the 3d perspective scene).
So, my questions are: why do sprites not use scale according to their parent's scale when using a PerspectiveCamera? Also, why does my scaled object not work with the orthographic camera? Are these bugs or features of the cameras?
Thanks!
http://jsfiddle.net/LLbcs/8/
var camera, scene, renderer, geometry, material, mesh, text, controls;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000); var scenescale=1;
//camera = new THREE.OrthographicCamera( -7,7,7,-7, 1, 20 );
camera.position.z = 10;
scene.add(camera);
scaled=new THREE.Object3D();
scene.add(scaled);
var textmaterial = new THREE.SpriteMaterial( {color: 'red', useScreenCoordinates: true, map: texttexture("hi")});
text = new THREE.Sprite( textmaterial );
text.position.set( 1, 1, 0);
scaled.add(text);
var geometry = new THREE.BoxGeometry( 1, 1,1 );
var material = new THREE.MeshLambertMaterial( { color: 0xffffff } );
mesh = new THREE.Mesh( geometry, material );
mesh.position.set(0,3,0);
scaled.add(mesh);
var light = new THREE.PointLight('green');
light.position.set(10,15,10);
camera.add(light);
light = new THREE.PointLight(0x333333);
light.position.set(-10,-15,-8);
camera.add(light);
renderer = new THREE.WebGLRenderer();
controls = new THREE.OrbitControls( camera, renderer.domElement );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
var scale = camera.position.length()/10;
scaled.scale.set(scale,scale,scale);
render();
}
function render() {
renderer.render(scene, camera);
}
function texttexture(string) {
var fontFace = "Arial"
var size = "50";
var color = "white"
var squareTexture = true;
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.height = size;
var font = "Normal " + size + "px " + fontFace;
context.font = font;
var metrics = context.measureText(string);
var textWidth = metrics.width;
canvas.width = textWidth;
if (squareTexture) {
canvas.height = canvas.width;
}
var aspect = canvas.width / canvas.height;
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = color;
// Must set the font again for the fillText call
context.font = font;
context.fillText(string, canvas.width / 2, canvas.height / 2);
var t = new THREE.Texture(canvas);
t.needsUpdate = true;
return t;
}
If you want text to appear over a 3D scene and you don't care if it is static, why not try layering a div over the scene instead?
This will allow you to save graphics bandwidth and memory, improving performance of your scene and give you much better flexibility over what you display. It's also much easier to do and to maintain.

in three.js, the spotlight is not showing a cone on a plane

I have a very simple example: a spot light pointed at a plane. I am expecting to see a cone of light whose diameter depends on the setting of the spot light angle. I cannot see any cone, the whole plane is illuminated, even for very narrow settings of angle.
Here is my jfiddle: http://jsfiddle.net/blwoodley/WLtL4/1/
I'd love to know the source code that produced this picture from https://github.com/mrdoob/three.js/pull/3291 by West Langley. It obviously is working fine in that case.
So I must be doing something obviously wrong, but I can't figure it out.
Some of the code from the jfiddle, it doesn't get much simpler than this:
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 100000);
camera.position.x = 100;
camera.position.y = 100;
camera.position.z = 200;
camera.lookAt({x: 0,y: 0,z: 0});
scene = new THREE.Scene();
var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
var floorMaterial = new THREE.MeshLambertMaterial({ color: 0x222222, side:THREE.DoubleSide });
floor = new THREE.Mesh(new THREE.PlaneGeometry(2000,2000,10,10), floorMaterial);
floor.rotation.x = Math.PI / 2;
floor.position.y = -40;
scene.add(floor);
var light;
light = new THREE.SpotLight(0x008888);
light.position.set(0, 40, 0);
light.lookAt(floor);
light.angle = Math.PI/4;
light.intensity = 30;
light.distance=0;
scene.add(light);
// RENDERER
webglRenderer = new THREE.WebGLRenderer();
webglRenderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
webglRenderer.domElement.style.position = "relative";
container.appendChild(webglRenderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
This is subtle.
You are using MeshLambertMaterial for the plane. You need to change it to MeshPhongMaterial, so the lighting is rendered properly.
As explained here, for MeshLambertMaterial, the illumination calculation is performed only at each vertex.
For MeshPhongMaterial, the illumination calculation is performed at each texel.
So make these changes
floorGeometry = new THREE.PlaneGeometry( 1000, 1000 ); // no need to tessellate it now
var floorMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff } ); // 0x222222 is too dark
light.intensity = 1; // a reasonable value
Updated fiddle: http://jsfiddle.net/WLtL4/5/
three.js r.63
Also try to disable target for testing.
I'm getting really weird behavior from it. Sometimes it makes it not render the light at all, no idea why yet. I'll make a demo of the problem later.

Isometric cube, size does not match up with tilesize 128x128

I've been trying for bigger parts of the night to make a export code that quickly will let me texture cubes and export them to a game i'm making, but for some reason I can't make my cube to cover the entire 128x128 width and height that I want it to have.
I have the following code:
function init(){
if( Detector.webgl ){
renderer = new THREE.WebGLRenderer({
antialias : false, // to get smoother output
preserveDrawingBuffer : true // to allow screenshot
});
renderer.setClearColorHex( 0xBBBBBB, 1 );
// uncomment if webgl is required
//}else{
// Detector.addGetWebGLMessage();
// return true;
}else{
renderer = new THREE.CanvasRenderer();
}
renderer.setSize(128,128);
document.getElementById('container').appendChild(renderer.domElement);
// add Stats.js - https://github.com/mrdoob/stats.js
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.bottom = '0px';
document.body.appendChild( stats.domElement );
var zoom = 1.0;
// create a scene
scene = new THREE.Scene();
// put a camera in the scene
camera = new THREE.OrthographicCamera(WIDTH / -zoom, HEIGHT / zoom, WIDTH / zoom, HEIGHT / -zoom, -2000, 1000);
//camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(0.45,0.45,0.45);
camera.lookAt(scene.position);
//camera.position.set(0, 0, 5);
scene.add(camera);
// create a camera contol
//cameraControls = new THREEx.DragPanControls(camera)
// transparently support window resize
THREEx.WindowResize.bind(renderer, camera);
// allow 'p' to make screenshot
THREEx.Screenshot.bindKey(renderer);
// allow 'f' to go fullscreen where this feature is supported
//if( THREEx.FullScreen.available() ){
// THREEx.FullScreen.bindKey();
// document.getElementById('inlineDoc').innerHTML += "- <i>f</i> for fullscreen";
//}
// here you add your objects
// - you will most likely replace this part by your own
//var geometry = new THREE.TorusGeometry( 1, 0.42 );
var cubeSize = 128;
var geometry = new THREE.CubeGeometry( cubeSize, cubeSize, cubeSize );
var material = new THREE.MeshNormalMaterial();
mesh= new THREE.Mesh( geometry, material );
mesh.rotation.x = 0;
mesh.rotation.y = 0;
mesh.rotation.z = 0;
scene.add( mesh );
}
I've been trying out different "zooms" but it still ends up either too big or too small.
The point with all this is to end up with a code that can generate something like this:
https://dl.dropboxusercontent.com/u/5256694/cube_ex.png
What am I doing wrong?
Kind Regards
Hiam
Instead of thinking about the parameters of THREE.OrthographicCamera as "zoom" levels, you should think of them as coordinates of boundary planes for what the camera is able to see.
Also see the answer to Three.js - Orthographic camera for more details about using orthographic cameras in Three.js

Resources