How many times is gl.DrawElements called? - three.js

Tell me how many times THREEjs calls the function gl.DrawElements in one frame. Once ?, or on each object the function is caused.
// Render at once //
ONE gl.DrawElements ( box + sphere + plane ) = SCENE
OR
// Render each object independently //
gl.DrawElements ( box ) + gl.DrawElements ( sphere ) + gl.DrawElements ( plane ) = SCENE
I bad write in English, I’m sorry. I hope my question is clear. Thanks for the answer.

You can look up how many times three.js called gl.drawXXX by looking at renderer.info.render.calls
From the example below we see that each "Mesh" has a draw call. If we added shadows it would likely be one draw call per mesh per light drawing shadows. Three.js has optional culling so if something is not visible it might not try to draw it.
'use strict';
/* global THREE */
function main() {
const infoElem = document.querySelector('#info');
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function makeInstance(geometry, color, x) {
const material = new THREE.MeshPhongMaterial({color});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.position.x = x;
return cube;
}
const cubes = [
makeInstance(geometry, 0x44aa88, 0),
makeInstance(geometry, 0x8844aa, -2),
makeInstance(geometry, 0xaa8844, 2),
];
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
cubes.forEach((cube, ndx) => {
const speed = 1 + ndx * .1;
const rot = time * speed;
cube.rotation.x = rot;
cube.rotation.y = rot;
});
renderer.render(scene, camera);
infoElem.textContent = JSON.stringify(renderer.info, null, 2);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
#info {
position: absolute;
left: 0;
top: 0;
color: white;
font-size: x-small;
}
<canvas id="c"></canvas>
<pre id="info"></pre>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r94/three.min.js"></script>

Another way is to use a tool such as this:
https://spector.babylonjs.com
Or the built in inspector that comes with Firefox (nightly?). It will give you more information on the draw call.

Related

Is there a way to get the depth values using WebGL and Three.js?

I am trying to get the depth values of each pixel in the canvas element. Is there a way to find these depth values using WebGL and Three.js?
What I majorly want is that for eg. in the image below, the red background should have 0 as the depth value whereas the 3D model should have the depth values based on the distance from the camera.
Using the X,Y coordinates of the canvas, is there a method to access the depth values?
[Edit 1]: Adding more information
I pick three random points as shown below, then I ask the user to input the depth values for each of these points. Once the input is received from the user, I will compute the difference between the depth values in three.js and the values inputted from the user.
Basically, I would require a 2D array of the canvas size where each pixel corresponds to an array value. This 2D array must contain the value 0 if the pixel is a red background, or contain the depth value if the pixel contains the 3D model.
Two ways come to mind.
One you can just use RayCaster
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
.info {
position: absolute;
left: 1em;
top: 1em;
padding: 1em;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: xx-small;
}
.info::after{
content: '';
position: absolute;
border: 10px solid transparent;
border-top: 10px solid rgba(0, 0, 0, 0.7);
top: 0;
left: -10px;
}
<canvas id="c"></canvas>
<script type="module">
// Three.js - Picking - RayCaster
// from https://threejsfundamentals.org/threejs/threejs-picking-raycaster.html
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 60;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 200;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 30;
const points = [
[170, 20],
[400, 50],
[225, 120],
].map((point) => {
const infoElem = document.createElement('pre');
document.body.appendChild(infoElem);
infoElem.className = "info";
infoElem.style.left = `${point[0] + 10}px`;
infoElem.style.top = `${point[1]}px`;
return {
point,
infoElem,
};
});
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
// put the camera on a pole (parent it to an object)
// so we can spin the pole to move the camera around the scene
const cameraPole = new THREE.Object3D();
scene.add(cameraPole);
cameraPole.add(camera);
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
camera.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + (max - min) * Math.random();
}
function randomColor() {
return `hsl(${rand(360) | 0}, ${rand(50, 100) | 0}%, 50%)`;
}
const numObjects = 100;
for (let i = 0; i < numObjects; ++i) {
const material = new THREE.MeshPhongMaterial({
color: randomColor(),
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.position.set(rand(-20, 20), rand(-20, 20), rand(-20, 20));
cube.rotation.set(rand(Math.PI), rand(Math.PI), 0);
cube.scale.set(rand(3, 6), rand(3, 6), rand(3, 6));
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
const raycaster = new THREE.Raycaster();
function render(time) {
time *= 0.001; // convert to seconds;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
cameraPole.rotation.y = time * .1;
for (const {point, infoElem} of points) {
const pickPosition = {
x: (point[0] / canvas.clientWidth ) * 2 - 1,
y: (point[1] / canvas.clientHeight) * -2 + 1, // note we flip Y
};
raycaster.setFromCamera(pickPosition, camera);
const intersectedObjects = raycaster.intersectObjects(scene.children);
if (intersectedObjects.length) {
// pick the first object. It's the closest one
const intersection = intersectedObjects[0];
infoElem.textContent = `position : ${point[0]}, ${point[1]}
distance : ${intersection.distance.toFixed(2)}
z depth : ${((intersection.distance - near) / (far - near)).toFixed(3)}
local pos: ${intersection.point.x.toFixed(2)}, ${intersection.point.y.toFixed(2)}, ${intersection.point.z.toFixed(2)}
local uv : ${intersection.uv.x.toFixed(2)}, ${intersection.uv.y.toFixed(2)}`;
} else {
infoElem.textContent = `position : ${point[0]}, ${point[1]}`;
}
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
The other way is to do as you mentioned and read the depth buffer. Unfortunately there is no direct way to read the depth buffer.
To read the depth values you need 2 render targets. You'd render to the first target. That gives you both a color texture with the rendered image and a depth texture with the depth values. You can't read a depth texture directly but you can draw it to another color texture and then read the color texture. Finally you can draw the first color texture to the cavnas.
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
.info {
position: absolute;
left: 1em;
top: 1em;
padding: 1em;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: xx-small;
}
.info::after{
content: '';
position: absolute;
border: 10px solid transparent;
border-top: 10px solid rgba(0, 0, 0, 0.7);
top: 0;
left: -10px;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const points = [
[170, 20],
[400, 50],
[225, 120],
].map((point) => {
const infoElem = document.createElement('pre');
document.body.appendChild(infoElem);
infoElem.className = "info";
infoElem.style.left = `${point[0] + 10}px`;
infoElem.style.top = `${point[1]}px`;
return {
point,
infoElem,
};
});
const renderTarget = new THREE.WebGLRenderTarget(1, 1);
renderTarget.depthTexture = new THREE.DepthTexture();
const depthRenderTarget = new THREE.WebGLRenderTarget(1, 1, {
depthBuffer: false,
stenciBuffer: false,
});
const rtFov = 60;
const rtAspect = 1;
const rtNear = 0.1;
const rtFar = 200;
const rtCamera = new THREE.PerspectiveCamera(rtFov, rtAspect, rtNear, rtFar);
rtCamera.position.z = 30;
const rtScene = new THREE.Scene();
rtScene.background = new THREE.Color('white');
// put the camera on a pole (parent it to an object)
// so we can spin the pole to move the camera around the scene
const cameraPole = new THREE.Object3D();
rtScene.add(cameraPole);
cameraPole.add(rtCamera);
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
rtCamera.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + (max - min) * Math.random();
}
function randomColor() {
return `hsl(${rand(360) | 0}, ${rand(50, 100) | 0}%, 50%)`;
}
const numObjects = 100;
for (let i = 0; i < numObjects; ++i) {
const material = new THREE.MeshPhongMaterial({
color: randomColor(),
});
const cube = new THREE.Mesh(geometry, material);
rtScene.add(cube);
cube.position.set(rand(-20, 20), rand(-20, 20), rand(-20, 20));
cube.rotation.set(rand(Math.PI), rand(Math.PI), 0);
cube.scale.set(rand(3, 6), rand(3, 6), rand(3, 6));
}
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1);
const scene = new THREE.Scene();
camera.position.z = 1;
const sceneMaterial = new THREE.MeshBasicMaterial({
map: renderTarget.texture,
});
const planeGeo = new THREE.PlaneBufferGeometry(2, 2);
const plane = new THREE.Mesh(planeGeo, sceneMaterial);
scene.add(plane);
const depthScene = new THREE.Scene();
const depthMaterial = new THREE.MeshBasicMaterial({
map: renderTarget.depthTexture,
});
const depthPlane = new THREE.Mesh(planeGeo, depthMaterial);
depthScene.add(depthPlane);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
let depthValues = new Uint8Array(0);
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
renderTarget.setSize(canvas.width, canvas.height);
depthRenderTarget.setSize(canvas.width, canvas.height);
rtCamera.aspect = canvas.clientWidth / canvas.clientHeight;
rtCamera.updateProjectionMatrix();
}
cameraPole.rotation.y = time * .1;
// draw render target scene to render target
renderer.setRenderTarget(renderTarget);
renderer.render(rtScene, rtCamera);
renderer.setRenderTarget(null);
// render the depth texture to another render target
renderer.setRenderTarget(depthRenderTarget);
renderer.render(depthScene, camera);
renderer.setRenderTarget(null);
{
const {width, height} = depthRenderTarget;
const spaceNeeded = width * height * 4;
if (depthValues.length !== spaceNeeded) {
depthValues = new Uint8Array(spaceNeeded);
}
renderer.readRenderTargetPixels(
depthRenderTarget,
0,
0,
depthRenderTarget.width,
depthRenderTarget.height,
depthValues);
for (const {point, infoElem} of points) {
const offset = ((height - point[1] - 1) * width + point[0]) * 4;
infoElem.textContent = `position : ${point[0]}, ${point[1]}
z depth : ${(depthValues[offset] / 255).toFixed(3)}`;
}
}
// render the color texture to the canvas
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
The problem is you can only read UNSIGNED_BYTE values from the texture so your depth values only go from 0 to 255 which is not really enough resolution to do much.
To solve that issue you have to encode the depth values across channels when drawing the depth texture to the 2nd render target which means you need to make your own shader. three.js has some shader snippets for packing the values so hacking a shader using ideas from this article we can get better depth values.
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
.info {
position: absolute;
left: 1em;
top: 1em;
padding: 1em;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: xx-small;
}
.info::after{
content: '';
position: absolute;
border: 10px solid transparent;
border-top: 10px solid rgba(0, 0, 0, 0.7);
top: 0;
left: -10px;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const points = [
[170, 20],
[400, 50],
[225, 120],
].map((point) => {
const infoElem = document.createElement('pre');
document.body.appendChild(infoElem);
infoElem.className = "info";
infoElem.style.left = `${point[0] + 10}px`;
infoElem.style.top = `${point[1]}px`;
return {
point,
infoElem,
};
});
const renderTarget = new THREE.WebGLRenderTarget(1, 1);
renderTarget.depthTexture = new THREE.DepthTexture();
const depthRenderTarget = new THREE.WebGLRenderTarget(1, 1, {
depthBuffer: false,
stenciBuffer: false,
});
const rtFov = 60;
const rtAspect = 1;
const rtNear = 0.1;
const rtFar = 200;
const rtCamera = new THREE.PerspectiveCamera(rtFov, rtAspect, rtNear, rtFar);
rtCamera.position.z = 30;
const rtScene = new THREE.Scene();
rtScene.background = new THREE.Color('white');
// put the camera on a pole (parent it to an object)
// so we can spin the pole to move the camera around the scene
const cameraPole = new THREE.Object3D();
rtScene.add(cameraPole);
cameraPole.add(rtCamera);
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
rtCamera.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + (max - min) * Math.random();
}
function randomColor() {
return `hsl(${rand(360) | 0}, ${rand(50, 100) | 0}%, 50%)`;
}
const numObjects = 100;
for (let i = 0; i < numObjects; ++i) {
const material = new THREE.MeshPhongMaterial({
color: randomColor(),
});
const cube = new THREE.Mesh(geometry, material);
rtScene.add(cube);
cube.position.set(rand(-20, 20), rand(-20, 20), rand(-20, 20));
cube.rotation.set(rand(Math.PI), rand(Math.PI), 0);
cube.scale.set(rand(3, 6), rand(3, 6), rand(3, 6));
}
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1);
const scene = new THREE.Scene();
camera.position.z = 1;
const sceneMaterial = new THREE.MeshBasicMaterial({
map: renderTarget.texture,
});
const planeGeo = new THREE.PlaneBufferGeometry(2, 2);
const plane = new THREE.Mesh(planeGeo, sceneMaterial);
scene.add(plane);
const depthScene = new THREE.Scene();
const depthMaterial = new THREE.MeshBasicMaterial({
map: renderTarget.depthTexture,
});
depthMaterial.onBeforeCompile = function(shader) {
// the <packing> GLSL chunk from three.js has the packDeathToRGBA function.
// then at the end of the shader the default MaterialBasicShader has
// already read from the material's `map` texture (the depthTexture)
// which has depth in 'r' and assigned it to gl_FragColor
shader.fragmentShader = shader.fragmentShader.replace(
'#include <common>',
'#include <common>\n#include <packing>',
).replace(
'#include <fog_fragment>',
'gl_FragColor = packDepthToRGBA( gl_FragColor.r );',
);
};
const depthPlane = new THREE.Mesh(planeGeo, depthMaterial);
depthScene.add(depthPlane);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
let depthValues = new Uint8Array(0);
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
renderTarget.setSize(canvas.width, canvas.height);
depthRenderTarget.setSize(canvas.width, canvas.height);
rtCamera.aspect = canvas.clientWidth / canvas.clientHeight;
rtCamera.updateProjectionMatrix();
}
cameraPole.rotation.y = time * .1;
// draw render target scene to render target
renderer.setRenderTarget(renderTarget);
renderer.render(rtScene, rtCamera);
renderer.setRenderTarget(null);
// render the depth texture to another render target
renderer.setRenderTarget(depthRenderTarget);
renderer.render(depthScene, camera);
renderer.setRenderTarget(null);
{
const {width, height} = depthRenderTarget;
const spaceNeeded = width * height * 4;
if (depthValues.length !== spaceNeeded) {
depthValues = new Uint8Array(spaceNeeded);
}
renderer.readRenderTargetPixels(
depthRenderTarget,
0,
0,
depthRenderTarget.width,
depthRenderTarget.height,
depthValues);
for (const {point, infoElem} of points) {
const offset = ((height - point[1] - 1) * width + point[0]) * 4;
const depth = depthValues[offset ] * ((255 / 256) / (256 * 256 * 256)) +
depthValues[offset + 1] * ((255 / 256) / (256 * 256)) +
depthValues[offset + 2] * ((255 / 256) / 256);
infoElem.textContent = `position : ${point[0]}, ${point[1]}
z depth : ${depth.toFixed(3)}`;
}
}
// render the color texture to the canvas
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
Note depthTexture uses a webgl extension which is an optional feature not found on all devices
To work around that would require drawing the scene twice. Once with your normal materials and then again to a color render target using the MeshDepthMaterial.

Three.js. Why does this RGBA texture not change the associated material opacity?

In three.js I create a material that uses a texture to control transparency. The texture is created from a canvas. The canvas is drawn with fillStyle of rgba. Alpha varies across the canvas. The effect I am after is to vary transparency across the object the material is attached to. That is not happening. The object remains opaque.
Code:
tubeTexture = new THREE.Texture(canvas);
tubeTexture.center.set(0.5, 0.5);
tubeTexture.rotation = Math.PI/2.0;
// turn off any filtering to create sharp edges when highlighting
// tube section based on colorRamp highlighting.
tubeTexture.minFilter = tubeTexture.magFilter = THREE.NearestFilter;
// let tubeMaterial = new THREE.MeshBasicMaterial({ map: tubeTexture });
let tubeMaterial = new THREE.MeshPhongMaterial({ map: tubeTexture });
tubeMaterial.side = THREE.DoubleSide;
tubeMaterial.transparent = true;
// let tubeMaterial = sceneManager.stickMaterial.clone();
const tubeMesh = new THREE.Mesh(tubeGeometry, tubeMaterial);
What am I missing?
It seems to work for me
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({
canvas: canvas
});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 256;
ctx.canvas.height = 256;
ctx.fillStyle = 'rgba(255, 255, 255, 0.25)';
ctx.beginPath();
ctx.arc(128, 128, 120, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.beginPath();
ctx.arc(128, 128, 64, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'rgba(255, 255, 255, 1.0)';
ctx.beginPath();
ctx.arc(128, 128, 32, 0, Math.PI * 2);
ctx.fill();
const texture = new THREE.CanvasTexture(ctx.canvas);
const root = new THREE.Object3D();
scene.add(root);
function makeInstance(geometry, color, x) {
const material = new THREE.MeshPhongMaterial({
color,
map: texture,
transparent: true,
side: THREE.DoubleSide,
alphaTest: 0.1,
});
const cube = new THREE.Mesh(geometry, material);
root.add(cube);
cube.position.x = x;
return cube;
}
const cubes = [
makeInstance(geometry, 0x44aa88, 0),
makeInstance(geometry, 0x8844aa, -2),
makeInstance(geometry, 0xaa8844, 2),
];
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
root.rotation.y = time * .2;
cubes.forEach((cube, ndx) => {
const speed = 1 + ndx * .1;
const rot = time * speed;
cube.rotation.x = rot;
cube.rotation.y = rot;
});
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r103/three.min.js"></script>
Of course there are the normal issues related to transparency and sorting. An object will not be consistantly transparent to itself, only to other objects.

Make a camera rotate along z axis while moving and changing lookAt (rollercoaster view) in Three.js

Hi i am having a problem maybe you can help me.
I have a camera that is going down a tube following a path. and a camera that rotates around that tube always pointing toward the next point in the tube. However, the camera sometimes can be below or beside the tube like a roller coaster. Like this
I have the position of point a and the position of the camera which is point b. I am always looking at point a+1
var bpoints = this.cameraPathpoints;
var apoints = this.pathPoints;
this.camera.position.copy(bpoints[i]);
this.camera.lookAt(apoints[i+1]);
The camera is always looking at the point correctly however i want that the camera rotates in its z axis so that it is always normal to the tube. I tried making some calculations so that the camera rotates in its z axis so that the camera always faces normal to the tube, however my calculations work only on certain positions. Maybe there is a simpler way to do this. Thank you very much for any help.
var angleRadians = Math.atan2(cpv[this.cameraPos].pos.y - centePoints[this.cameraPos].pos.y, cpv[this.cameraPos].pos.x - centePoints[this.cameraPos].pos.x);
if(angleRadians > 0 && angleRadians > Math.PI/2){
console.log("+90",(Math.PI/2) - angleRadians);
angleRadians = (Math.PI/2) - angleRadians;
this.camera.rotateZ(angleRadians);
console.log("rotated ", angleRadians * 180/Math.PI);
}
else if(angleRadians > 0 && angleRadians < Math.PI/2 && anglesum >
Math.PI/2){
console.log("-90",(Math.PI/2) - angleRadians);
angleRadians = (Math.PI/2) - angleRadians;
this.camera.rotateZ(-angleRadians);
console.log("rotated ", -angleRadians * 180/Math.PI);
}
else if(angleRadians > 0 && angleRadians < Math.PI/2){
console.log("-90",(Math.PI/2) + angleRadians);
angleRadians = -(Math.PI/2) - (angleRadians/Math.PI/2);
this.camera.rotateZ(angleRadians);
console.log("rotated ", angleRadians * 180/Math.PI);
}
else if(angleRadians < 0 && angleRadians < -Math.PI/2){
console.log("--90");
angleRadians = (Math.PI/2) + angleRadians;
this.camera.rotateZ(-angleRadians);
console.log("rotated ",-angleRadians * 180/Math.PI);
}else if(angleRadians < 0 && angleRadians > -Math.PI/2){
console.log("+-90");
angleRadians = (Math.PI/2) - angleRadians;
this.camera.rotateZ(-angleRadians);
console.log("rotated ", -angleRadians * 180/Math.PI);
}
Rather than doing math, make the camera a child of some other THREE.Object3D and use lookAt with that object. Set the camera's position and rotation relative to that object.
Below the object is called the mount. It goes down the path (center of the tube). The camera is a child of mount. The tube has a 1 unit radius so setting the camera.position.y to 1.5 makes it outside the tube. lookAt makes non-camera objects look down positive Z but the camera looks down negative Z so we rotate the camera 180 degrees.
Example:
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: canvas});
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);
const fov = 40;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.y = 1.5; // 2 units above the mount
camera.rotation.y = Math.PI; // the mount will lootAt positiveZ
const mount = new THREE.Object3D();
mount.add(camera);
scene.add(mount);
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(1, -2, -4);
scene.add(light);
}
const curve = new THREE.Curves.GrannyKnot();
const tubularSegments = 200;
const radius = 1;
const radialSegments = 6;
const closed = true;
const tube = new THREE.TubeBufferGeometry(
curve, tubularSegments, radius, radialSegments, closed);
const texture = new THREE.DataTexture(new Uint8Array([128, 255, 255, 128]),
2, 2, THREE.LuminanceFormat);
texture.needsUpdate = true;
texture.magFilter = THREE.NearestFilter;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 100, 4 );
const material = new THREE.MeshPhongMaterial({
map: texture,
color: '#8CF',
flatShading: true,
});
const mesh = new THREE.Mesh(tube, material);
scene.add(mesh);
const target = new THREE.Vector3();
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
const t = time * 0.1 % 1;
curve.getPointAt(t, mount.position);
curve.getPointAt((t + 0.01) % 1, target);
mount.lookAt(target);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r102/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r102/js/CurveExtras.js"></script>
You can easily orient the camera relative to the mount to say look more toward the path or way by setting camera.rotation.x. If you want to rotate around the mount either change the mount's up property or add another object between the mount and the camera and set its Z rotation.
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: canvas});
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);
const fov = 40;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.y = 1.5; // 2 units above the mount
camera.rotation.y = Math.PI; // the mount will lootAt positiveZ
const mount = new THREE.Object3D();
const subMount = new THREE.Object3D();
subMount.rotation.z = Math.PI * .5;
subMount.add(camera);
mount.add(subMount);
scene.add(mount);
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(1, -2, -4);
scene.add(light);
}
const curve = new THREE.Curves.GrannyKnot();
const tubularSegments = 200;
const radius = 1;
const radialSegments = 6;
const closed = true;
const tube = new THREE.TubeBufferGeometry(
curve, tubularSegments, radius, radialSegments, closed);
const texture = new THREE.DataTexture(new Uint8Array([128, 255, 255, 128]),
2, 2, THREE.LuminanceFormat);
texture.needsUpdate = true;
texture.magFilter = THREE.NearestFilter;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 100, 4 );
const material = new THREE.MeshPhongMaterial({
map: texture,
color: '#8CF',
flatShading: true,
});
const mesh = new THREE.Mesh(tube, material);
scene.add(mesh);
const target = new THREE.Vector3();
const target2 = new THREE.Vector3();
const mountToTarget = new THREE.Vector3();
const targetToTarget2 = new THREE.Vector3();
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
const t = time * 0.1 % 1;
curve.getPointAt(t, mount.position);
curve.getPointAt((t + 0.01) % 1, target);
// set mount up to be perpenticular to the
// curve
curve.getPointAt((t + 0.02) % 1, target2);
mountToTarget.subVectors(mount.position, target).normalize();
targetToTarget2.subVectors(target2, target).normalize();
mount.up.crossVectors(mountToTarget, targetToTarget2);
mount.lookAt(target);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r102/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r102/js/CurveExtras.js"></script>

Rotate at specific pivot point

I'm trying to pivot a piece of a headphone set. It's capable of doing such in my model (in Maya).. but I can't seem to figure it out in threejs.
I know I can rotate my objects X Y and Z by doing something like this:
object.rotateX(THREE.Math.degToRad(degreeX));
object.rotateY(THREE.Math.degToRad(degreeY));
object.rotateZ(THREE.Math.degToRad(degreeZ));
But how do I keep the pivot point stationary while the rests rotates/moves? So in my example, I'd want the ear piece to be able to move left and right based off of the black-ish screw you see in my picture.
You could nest your headphones Mesh inside another THREE.Group, reposition the headphones inside this group so the pivot is in the desired position, then rotate the parent.
// You take your headphones and nest them inside a Group
var headphones = new THREE.Mesh(geom, material);
var parent = new THREE.Group();
parent.add(headphones);
// Then you move your headphones to the desired pivot position
headphones.position.set(-5, 0.1, 0);
// Parent is going to rotate around it origin
parent.rotateX(THREE.Math.degToRad(degreeX));
Note that if you want the pivot to be at (5, -0.1, 0), you should move headphones in the opposite direction: (-5, 0.1, 0).
Parent your model to another THREE.Object3D but to make it easy use the SceneUtils.attach function.
Example:
Click then drag, each time you click the pivot object will be moved to that location and then the model (the cube) will be attached to the pivot by calling THREE.SceneUtils.attach(model, scene, pivot). When you let off the mouse the model is detached using THREE.SceneUtils.detach(model, pivot, scene).
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: canvas});
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
// make the camera look down
camera.position.set(0, 10, 0);
camera.up.set(0, 0, -1);
camera.lookAt(0, 0, 0);
const scene = new THREE.Scene();
scene.background = new THREE.Color('black');
scene.add(new THREE.GridHelper(40, 40));
let model;
{
const cubeSize = 3;
const cubeGeo = new THREE.BoxBufferGeometry(cubeSize, cubeSize, cubeSize);
const cubeMat = new THREE.MeshBasicMaterial({color: 'red'});
model = new THREE.Mesh(cubeGeo, cubeMat);
model.position.set(.5, .5, .5);
scene.add(model);
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
}
render();
let rotate = false;
const startPos = {x:0, y:0};
const raycaster = new THREE.Raycaster();
const pivot = new THREE.Object3D();
scene.add(pivot);
pivot.add(new THREE.AxesHelper(.5));
function setPivotPoint(e) {
startPos.x = e.clientX;
startPos.y = e.clientY;
const normalizedPosition = {
x: e.clientX / canvas.clientWidth * 2 - 1,
y: e.clientY / canvas.clientHeight * -2 + 1,
};
// this part is NOT important to the answer. The question
// is how to rotate from some point. This code is picking
// a point. Which point to pick was not part of the question
// but to demo the solution it's important to pick a point
// put the pivot where the mouse was clicked
raycaster.setFromCamera(normalizedPosition, camera);
const intersection = raycaster.intersectObjects(scene.children)[0];
if (intersection) {
if (rotate) {
removeFromPivot();
}
pivot.position.copy(intersection.point);
pivot.rotation.set(0,0,0);
pivot.updateMatrixWorld();
rotate = true;
// this the important part. We're making the cube
// a child of 'pivot' without it moving in world space
THREE.SceneUtils.attach(model, scene, pivot);
render();
}
}
function rotatePivot(e) {
e.preventDefault();
if (rotate) {
const dx = e.clientX - startPos.x;
const dy = e.clientY - startPos.y;
const maxDelta = Math.abs(dx) > Math.abs(dy) ? dx : dy;
pivot.rotation.y = maxDelta * 0.01;
render();
}
}
function removeFromPivot() {
if (rotate) {
rotate = false;
THREE.SceneUtils.detach(model, pivot, scene);
window.removeEventListener('mousemove', rotatePivot);
window.removeEventListener('mouseup', removeFromPivot);
}
}
canvas.addEventListener('mousedown', (e) => {
e.preventDefault();
setPivotPoint(e);
if (rotate) {
window.addEventListener('mousemove', rotatePivot);
window.addEventListener('mouseup', removeFromPivot);
}
});
}
main();
html, body {
margin: 0;
height: 100%;
}
#c {
width: 100%;
height: 100%;
display: block;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/js/utils/SceneUtils.js"></script>

decay and distance with physically correct lighting in three.js

What does the distance setting mean in three.js in relation to physically based lighting?
For non physically based lighting the distance setting is a setting where the light's influence fades out linearly. Effectively
lightAffect = 1 - min(1, distanceFromLight / distance)
I don't know physically based lighting well but it seems to me real lights don't have a distance setting, they just have a power output (lumens) and decay based on the atmosphere density. Three.js has both a power setting and a decay setting although it's not clear at all what decay should be set to as the docs effectively just say to set it to 2.
What should I be setting distance for a physically based PointLight for example if I want physically based lighting?
'use strict';
/* global dat */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: canvas});
renderer.physicallyCorrectLights = true;
const fov = 45;
const aspect = 2; // the canvas default
const zNear = 0.1;
const zFar = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
camera.position.set(0, 10, 20);
camera.lookAt(0, 5, 0);
const scene = new THREE.Scene();
scene.background = new THREE.Color('black');
{
const planeSize = 40;
const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
const planeMat = new THREE.MeshPhongMaterial({
color: '#A86',
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(planeGeo, planeMat);
mesh.rotation.x = Math.PI * -.5;
scene.add(mesh);
} {
const cubeSize = 4;
const cubeGeo = new THREE.BoxBufferGeometry(cubeSize, cubeSize, cubeSize);
const cubeMat = new THREE.MeshPhongMaterial({color: '#8AC'});
const mesh = new THREE.Mesh(cubeGeo, cubeMat);
mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
scene.add(mesh);
}
{
const sphereRadius = 3;
const sphereWidthDivisions = 32;
const sphereHeightDivisions = 16;
const sphereGeo = new THREE.SphereBufferGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
const sphereMat = new THREE.MeshPhongMaterial({color: '#CA8'});
const mesh = new THREE.Mesh(sphereGeo, sphereMat);
mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
scene.add(mesh);
}
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.PointLight(color, intensity);
light.power = 800;
light.distance = 20;
light.position.set(0, 10, 5);
scene.add(light);
light.decay = 2;
const helper = new THREE.PointLightHelper(light);
scene.add(helper);
const onChange = () => {
helper.update();
render();
};
setTimeout(onChange);
window.onresize = onChange;
const gui = new dat.GUI();
gui.add(light, 'distance', 0, 100).onChange(onChange);
gui.add(light, 'decay', 0, 4).onChange(onChange);
gui.add(light, 'power', 0, 3000).onChange(onChange);
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
}
}
main();
html, body {
margin: 0;
height: 100%;
}
#c {
width: 100%;
height: 100%;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>
<canvas id="c"></canvas>
Reading through three.js source and the paper it's linked to, at least as of r95 the distance setting should basically be Infinity for physically based lights.
In the paper they point out physically based lights shine to infinity but of course in a 3D engine that's no good. Most 3D engines need to compute the minimum number of lights per object drawn so a lightDistance setting was added, if the light is further way than lightDistance they can ignore the light. The problem is there will be sharp edge if they just stop using the light past lightDistance so they hacked in a falloff.
three.js copied that lightDistance and falloff setting from the paper but three.js does not cull lights from calculations when lights are far away so there seems to be no reason not to set distance to infinity AFAICT, at least as of r95.

Resources