Object with a higher renderOrder being clipped by rotated element - three.js

A rotated object (cylinder in this case) cuts off objects (a triangle made by lines in this case) even though the renderOrder of the second object is higher. See this jsfiddle demo for the effect.
The triangle should be rendered completely on top of the cylinder but is cut off where the outside of the cylinder intersects with it. It's easier to understand what's happening when a texture is used, but jsfiddle is bad at using external images.
var mesh, renderer, scene, camera, controls;
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: true,
preserveDrawingBuffer: true
});
renderer.setClearColor(0x24132E, 1);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(0, 0, 7);
camera.lookAt(scene.position)
scene.add(camera);
var geometry = new THREE.CylinderGeometry(1, 1, 100, 32, 1, true);
var material = new THREE.MeshBasicMaterial({
color: 0x0000ff
});
material.side = THREE.DoubleSide;
mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = Math.PI / 2;
scene.add(mesh);
var c = 3, // Side length of the triangle
a = c / 2,
b = Math.sqrt(c * c - a * a),
yOffset = -b / 3; // The vertical offset (if 0, triangle is on x axis)
// Draw the red triangle
var geo = new THREE.Geometry();
geo.vertices.push(
new THREE.Vector3(0, b + yOffset, 0),
new THREE.Vector3(-a, 0 + yOffset, 0),
new THREE.Vector3(a, 0 + yOffset, 0),
new THREE.Vector3(0, b + yOffset, 0)
);
var lineMaterial = new THREE.LineBasicMaterial({
color: 0xff0000,
linewidth: 5,
linejoin: "miter"
});
plane = new THREE.Line(geo, lineMaterial);
// Place it on top of the cylinder
plane.renderOrder = 2; // This should override any clipping, right?
scene.add(plane);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
Am I doing something wrong or is this a bug?

for the effect that you want use a second scene and render it onto the first one
function init(){
.....
renderer.autoClear = false;
scene.add(tube);
overlayScene.add(triangle);
.....
}
function render() {
renderer.clear();
renderer.render(scene, camera);
renderer.clearDepth();
renderer.render(overlayScene, camera);
}
renderOrder does not mean what you think it means, look at the implementation in WebGLRenderer
objects are sorted by the order, if it meant what you anticipated from it, there would always be some fixed rendering order and colliding objects would be seen through each other, renderOrder is AFAIK used when you have issues with order of transparent/ not opaque objects

I worte a little plugin for three.js for flares for my game. Three.js built-in flares plugin is slow and I preferred not to run another rendering pass which was cutting framerate in half. Here's how I got flares visible on top of objects which were actually in front of them.
Material parameters:
{
side: THREE.FrontSide,
blending: THREE.AdditiveBlending,
transparent: true,
map: flareMap,
depthWrite: false,
polygonOffset: true,
polygonOffsetFactor: -200
}
depthWrite - set to false
polygonOffset - set to true
polygonOffsetFactor - give negative number to get object in front of others. Give it some really high value to be really on top of everything i.e. -10000
Ignore other params, they are needed for my flares

Related

How to put a transparent color mask on a 3D object in three.js?

I've been trying to 'submerge' a 3D object in a semi-transparent 3D plane of water (without the whole water plane showing), and after having experimented with custom blending modes for hours, I don't really get how to do it.
Fiddle here: https://jsfiddle.net/mglonnro/p2ju4qbk/34/
var camera, scene, renderer, geometry, material, mesh,
surface_geometry, surface_material, surface_mesh,
bottom_geometry, bottom_material, bottom_mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 200, 500);
camera.lookAt(0, 0, 0);
scene.add(camera);
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
bottom_geometry = new THREE.PlaneBufferGeometry(10000, 10000);
bottom_material = new THREE.MeshBasicMaterial({
color: 0xFFAAAA,
side: THREE.DoubleSide
});
bottom_mesh = new THREE.Mesh(bottom_geometry, bottom_material);
bottom_mesh.rotation.set(Math.PI / 2, 0, 0);
bottom_mesh.position.set(0, -200, 0);
scene.add(bottom_mesh);
surface_geometry = new THREE.PlaneBufferGeometry(400, 400);
surface_material = new THREE.MeshBasicMaterial({
color: 0x0000ff,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.6
});
surface_mesh = new THREE.Mesh(surface_geometry, surface_material);
surface_mesh.rotation.set(Math.PI / 2, 0, 0);
scene.add(surface_mesh);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
}
The cube is submerged, as it should be, and the parts covered by the transparent water look like I want them to look.
The problem, however, is that I want ONLY the cube and its submerged parts to be rendered, NOT the rest of the water plane.
In other words:
There are three objects in the scene:
the redish "bottom" farthest away
the cube, partly above, partly
below the water
the water
Is there some way to blend these together so that the water pixels are rendered only when they are on top of a cube pixel, not when they are just on top of the background/bottom?
EDIT: SOLUTION
Add stencil write functionality to the cube:
const stencilId = 1;
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial({
stencilWrite: true,
stencilFunc: THREE.AlwaysStencilFunc,
stencilZPass: THREE.ReplaceStencilOp,
stencilRef: stencilId
});
Add stencil test functionality to the surface:
surface_material = new THREE.MeshBasicMaterial({
color: 0x0000ff,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.6,
stencilWrite: true,
stencilFunc: THREE.EqualStencilFunc,
stencilRef: stencilId
});
Realize that the three.js version in jsfiddle is too old to support stencils and move to codepen :)
Stencil Test is a natural way to achieve that.
All objects which should have water on top of them write some stencil value.
Water plane has stencil test set to this value.
three.js has stencil example, but it uses IncrementWrap and DecrementWrap logic which is not needed for your case.
I recommend trying ReplaceStencilOp for cube and EqualStencilFunc for water.

Raycaster fails to detect Cubes

How do you combine a Square and a Rectangle into one object that a Raycaster can detect successfully?
I created a custom “Tree” object by making a “Trunk” - which is just a long rectangle, and then sticking a Square object on top of that Trunk.
I then “planted” that Tree on top of a Sphere, and I'm trying to get my raycaster to detect it.
It’s not working.
Here’s my code:
// My custom “Tree” Object:
var Tree = function(treeColor) {
this.mesh = new THREE.Object3D();
this.mesh.name = "tree";
// I start with the TRUNK - which is just an elongated Cube - meaning a Rectangle:
var trunkGeometry = new THREE.CubeGeometry(0.2, 0.5, 0.2);
var trunkMaterial = new THREE.MeshBasicMaterial({ color: "blue", wireframe: false });
var treeTrunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
treeTrunk.position.set(0, 0, 0);
treeTrunk.rotation.x = -Math.PI * 0.5;
this.mesh.add(treeTrunk);
// Then I create the FOLIAGE - which is just a regular Cube:
var foliageGeometry = new THREE.CubeGeometry(0.5, 0.5, 0.5);
var foliageMaterial = new THREE.MeshBasicMaterial({ color: treeColor, wireframe: false });
var treeFoliage = new THREE.Mesh(foliageGeometry, foliageMaterial);
treeFoliage.position.set(0, 0.5, 0);
// And then I attach/add the FOLIAGE to the TRUNK:
treeTrunk.add(treeFoliage);
}
// Next I make a basic Sphere:
theSun = new THREE.Mesh(new THREE.SphereGeometry(3, 32, 24), new THREE.MeshBasicMaterial( {color: "white", wireframe: false} ));
scene.add(theSun);
// And then I make a Tree and add it to my Sphere (`theSun`):
var oneTree = new Tree("red");
let rx = Math.random() * Math.PI * 2;
let ry = Math.random() * Math.PI;
oneTree.mesh.position.setFromSphericalCoords(3.2, ry, rx);
oneTree.mesh.lookAt(theSun.position);
theSun.add(oneTree.mesh);
// Next I add both theSun and the Tree to my “objectsForRayCasterArray” - a global var I use in my raycaster test:
objectsForRayCasterArray.push(oneTree);
objectsForRayCasterArray.push(theSun);
// In my render() function, I do the usual raycasting business:
rayCaster = new THREE.Raycaster();
function render() {
requestAnimationFrame(render);
controls.update();
renderer.render(scene, camera);
// Raycasting stuff:
rayCaster.setFromCamera(mousePosition, camera);
var intersectingObjectsArray = rayCaster.intersectObjects(objectsForRayCasterArray);
if (intersectingObjectsArray.length > 0) {
if (intersectedObject != intersectingObjectsArray[0].object) {
console.log(“Intersected!”);
}
}
NOTE: when I use this same code but instead of a Tree I just place a regular Cube on my Sphere object, everything works just fine. The raycaster detects the Cube and fires the Alert.
My best guess is that since you're nesting the treeFoliage inside treeTrunk, and that inside mesh, you're going to have to use a recursive intersection test.
According to the docs, intersectObjects() accepts a second argument to perform a recursive hit-test. This means it will iterate through all descendants, instead of just doing a shallow check of top-level objects:
rayCaster.intersectObjects(objectsForRayCasterArray, true);

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>

how to draw a flat shape in 3-space in three.js?

I'm trying to construct a collection of flat shapes in three.js. Each one is defined as a series of coplanar Vector3 points, but the shapes are not all coplanar. Imagine two flat rectangles as the roof of a house, but with much more complex shapes.
I can make flat Shape objects and then rotate and position them, but since my shapes are conceived in 3d coordinates, it would be much simpler to keep it all in 3-space, which the Shape object doesn't like.
Is there some much more direct way to simply specify an array of coplanar Vector3's, and let three.js do the rest of the work?
I thought about this problem and came up with the idea, when you have a set of co-planar points and you know the normal of the plane (let's name it normal), which your points belong to.
We need to rotate our set of points to make it parallel to the xy-plane, thus the normal of that plane is [0, 0, 1] (let's name it normalZ). To do it, we find quaternions with .setFromUnitVectors() of THREE.Quaternion():
var quaternion = new THREE.Quaternion().setFromUnitVectors(normal, normalZ);
var quaternionBack = new THREE.Quaternion().setFromUnitVectors(normalZ, normal);
Apply quaternion to our set of points
As it's parallel to xy-plane now, z-coordinates of points don't matter, so we can now create a THREE.Shape() object of them. And then create THREE.ShapeGeometry() (name it shapeGeom) from given shape, which will triangulate our shape.
We need to put our points back to their original positions, so we'll apply quaternionBack to them.
After all, we'll assign our set of points to the .vertices property of the shapeGeom.
That's it. If it'll work for you, let me know ;)
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 20, 40);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target = new THREE.Vector3(10, 0, 10);
controls.update();
var grid = new THREE.GridHelper(50, 50, 0x808080, 0x202020); // xy-grid
grid.geometry.rotateX(Math.PI * 0.5);
scene.add(grid);
var points = [ // all of them are on the xz-plane
new THREE.Vector3(5, 0, 5),
new THREE.Vector3(25, 0, 5),
new THREE.Vector3(25, 0, 15),
new THREE.Vector3(15, 0, 15),
new THREE.Vector3(15, 0, 25),
new THREE.Vector3(5, 0, 25),
new THREE.Vector3(5, 0, 5)
]
var geom = new THREE.BufferGeometry().setFromPoints(points);
var pointsObj = new THREE.Points(geom, new THREE.PointsMaterial({
color: "red"
}));
scene.add(pointsObj);
var line = new THREE.LineLoop(geom, new THREE.LineBasicMaterial({
color: "aqua"
}));
scene.add(line);
// normals
var normal = new THREE.Vector3(0, 1, 0); // I already know the normal of xz-plane ;)
scene.add(new THREE.ArrowHelper(normal, new THREE.Vector3(10, 0, 10), 5, 0xffff00)); //yellow
var normalZ = new THREE.Vector3(0, 0, 1); // base normal of xy-plane
scene.add(new THREE.ArrowHelper(normalZ, scene.position, 5, 0x00ffff)); // aqua
// 1 quaternions
var quaternion = new THREE.Quaternion().setFromUnitVectors(normal, normalZ);
var quaternionBack = new THREE.Quaternion().setFromUnitVectors(normalZ, normal);
// 2 make it parallel to xy-plane
points.forEach(p => {
p.applyQuaternion(quaternion)
});
// 3 create shape and shapeGeometry
var shape = new THREE.Shape(points);
var shapeGeom = new THREE.ShapeGeometry(shape);
// 4 put our points back to their origins
points.forEach(p => {
p.applyQuaternion(quaternionBack)
});
// 5 assign points to .vertices
shapeGeom.vertices = points;
var shapeMesh = new THREE.Mesh(shapeGeom, new THREE.MeshBasicMaterial({
color: 0x404040
}));
scene.add(shapeMesh);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.90.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.90.0/examples/js/controls/OrbitControls.js"></script>

LineDashedMaterial - keep dashes the same length when changing line length

I have a created a dashed line which changes position and length over time. To update it's position I'm adjusting the vertices of it's geometry object. This works great, except when the line becomes longer or shorter the dashes also get longer or shorter, despite being defined in absolute units, not relative to the length of the line.
I am already calling geometry.computeLineDistances() on initialisation (I have to do this to get the dashing to work at all), and I expected that if I simply called this in every render loop they would keep to the right scale, but that doesn't work.
Is there anything I can do to keep the dashes fixed length as the line changes length?
Here is a jsfiddle that illustrates the problem (code below): https://jsfiddle.net/fyr519L8/
var camera, scene, renderer, geometry, material, mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 30, 0);
camera.lookAt(scene.position);
scene.add(camera);
geometry = new THREE.Geometry();
geometry.vertices.push(
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(0, 0, 15));
geometry.computeLineDistances();
material = new THREE.LineDashedMaterial({
color: 0x000000,
dashSize: 1,
gapSize: 1,
linewidth: 1
});
mesh = new THREE.Line(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
geometry.vertices[0].z = Math.sin(Date.now() / 1000) * 10;
geometry.verticesNeedUpdate = true;
renderer.render(scene, camera);
}
If I got you right, then you can do this:
function render() {
geometry.vertices[0].z = Math.sin(Date.now() / 1000) * 10;
geometry.verticesNeedUpdate = true;
geometry.computeLineDistances(); // re-calculate line distances
geometry.lineDistancesNeedUpdate = true;
renderer.render(scene, camera);
}
UPD#1 Method .computeLineDistances() has been moved from THREE.Geometry() to THREE.Line() (r94)

Resources