First I knew that Three.js does not have official support for occlusion culling. However, I thought it's possible to do occlusion culling in an offscreen canvas and copy the result back to my Three.js WebGLCanvas.
Basically, I want to transform this demo
to a Three.JS demo. I use Three.js to create everything, and in a synced offscreen canvas, I test occlusion culling against each bounding box. If any bounding box is occluded, I turn off the visibility of that sphere in the main canvas. Those are what I did in this snippet. but I don't know why it failed to occlude any sphere.
I think a possible issue might be coming from calculating the ModelViewProjection matrix of the bounding box, but I don't see anything wrong. Could somebody please help?
var camera, scene, renderer, light;
var spheres = [];
var NUM_SPHERES, occludedSpheres = 0;
var gl;
var boundingBoxPositions;
var boundingBoxProgram, boundingBoxArray, boundingBoxModelMatrixLocation, viewProjMatrixLocation;
var viewMatrix, projMatrix;
var firstRender = true;
var sphereCountElement = document.getElementById("num-spheres");
var occludedSpheresElement = document.getElementById("num-invisible-spheres");
// depth sort variables
var sortPositionA = new THREE.Vector3();
var sortPositionB = new THREE.Vector3();
var sortModelView = new THREE.Matrix4();
init();
animate();
function init() {
scene = new THREE.Scene();
scene.add( new THREE.AmbientLight( 0x222222 ) );
light = new THREE.DirectionalLight( 0xffffff, 1 );
scene.add( light );
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// set up offscreen canvas
var offscreenCanvas = new OffscreenCanvas(window.innerWidth, window.innerHeight);
gl = offscreenCanvas.getContext('webgl2');
if ( !gl ) {
console.error("WebGL 2 not available");
document.body.innerHTML = "This example requires WebGL 2 which is unavailable on this system."
}
// define spheres
var GRID_DIM = 6;
var GRID_OFFSET = GRID_DIM / 2 - 0.5;
NUM_SPHERES = GRID_DIM * GRID_DIM;
sphereCountElement.innerHTML = NUM_SPHERES;
var geometry = new THREE.SphereGeometry(20, 64, 64);
var material = new THREE.MeshPhongMaterial( {
color: 0xff0000,
specular: 0x050505,
shininess: 50,
emissive: 0x000000
} );
geometry.computeBoundingBox();
for ( var i = 0; i < NUM_SPHERES; i ++ ) {
var x = Math.floor(i / GRID_DIM) - GRID_OFFSET;
var z = i % GRID_DIM - GRID_OFFSET;
var mesh = new THREE.Mesh( geometry, material );
spheres.push(mesh);
scene.add(mesh);
mesh.position.set(x * 35, 0, z * 35);
mesh.userData.query = gl.createQuery();
mesh.userData.queryInProgress = false;
mesh.userData.occluded = false;
}
//////////////////////////
// WebGL code
//////////////////////////
// boundingbox shader
var boundingBoxVSource = document.getElementById("vertex-boundingBox").text.trim();
var boundingBoxFSource = document.getElementById("fragment-boundingBox").text.trim();
var boundingBoxVertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(boundingBoxVertexShader, boundingBoxVSource);
gl.compileShader(boundingBoxVertexShader);
if (!gl.getShaderParameter(boundingBoxVertexShader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(boundingBoxVertexShader));
}
var boundingBoxFragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(boundingBoxFragmentShader, boundingBoxFSource);
gl.compileShader(boundingBoxFragmentShader);
if (!gl.getShaderParameter(boundingBoxFragmentShader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(boundingBoxFragmentShader));
}
boundingBoxProgram = gl.createProgram();
gl.attachShader(boundingBoxProgram, boundingBoxVertexShader);
gl.attachShader(boundingBoxProgram, boundingBoxFragmentShader);
gl.linkProgram(boundingBoxProgram);
if (!gl.getProgramParameter(boundingBoxProgram, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(boundingBoxProgram));
}
// uniform location
boundingBoxModelMatrixLocation = gl.getUniformLocation(boundingBoxProgram, "uModel");
viewProjMatrixLocation = gl.getUniformLocation(boundingBoxProgram, "uViewProj");
// vertex location
boundingBoxPositions = computeBoundingBoxPositions(geometry.boundingBox);
boundingBoxArray = gl.createVertexArray();
gl.bindVertexArray(boundingBoxArray);
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, boundingBoxPositions, gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
gl.bindVertexArray(null);
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame(animate);
render();
}
function depthSort(a, b) {
sortPositionA.copy(a.position);
sortPositionB.copy(b.position);
sortModelView.copy(viewMatrix).multiply(a.matrix);
sortPositionA.applyMatrix4(sortModelView);
sortModelView.copy(viewMatrix).multiply(b.matrix);
sortPositionB.applyMatrix4(sortModelView);
return sortPositionB[2] - sortPositionA[2];
}
function render() {
var timer = Date.now() * 0.0001;
camera.position.x = Math.cos( timer ) * 250;
camera.position.z = Math.sin( timer ) * 250;
camera.lookAt( scene.position );
light.position.copy( camera.position );
occludedSpheres = 0;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0, 0, 0, 1);
gl.enable(gl.DEPTH_TEST);
gl.colorMask(true, true, true, true);
gl.depthMask(true);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
if (!firstRender) {
viewMatrix = camera.matrixWorldInverse.clone();
projMatrix = camera.projectionMatrix.clone();
var viewProjMatrix = projMatrix.multiply(viewMatrix);
spheres.sort(depthSort);
// for occlusion test
gl.colorMask(false, false, false, false);
gl.depthMask(false);
gl.useProgram(boundingBoxProgram);
gl.bindVertexArray(boundingBoxArray);
for (var i = 0; i < NUM_SPHERES; i ++) {
spheres[i].visible = true;
spheres[i].rotation.y += 0.003;
var sphereData = spheres[i].userData;
gl.uniformMatrix4fv(boundingBoxModelMatrixLocation, false, spheres[i].matrix.elements);
gl.uniformMatrix4fv(viewProjMatrixLocation, false, viewProjMatrix.elements);
// check query results here (will be from previous frame)
if (sphereData.queryInProgress && gl.getQueryParameter(sphereData.query, gl.QUERY_RESULT_AVAILABLE)) {
sphereData.occluded = !gl.getQueryParameter(sphereData.query, gl.QUERY_RESULT);
if (sphereData.occluded) occludedSpheres ++;
sphereData.queryInProgress = false;
}
// Query is initiated here by drawing the bounding box of the sphere
if (!sphereData.queryInProgress) {
gl.beginQuery(gl.ANY_SAMPLES_PASSED_CONSERVATIVE, sphereData.query);
gl.drawArrays(gl.TRIANGLES, 0, boundingBoxPositions.length / 3);
gl.endQuery(gl.ANY_SAMPLES_PASSED_CONSERVATIVE);
sphereData.queryInProgress = true;
}
if (sphereData.occluded) {
spheres[i].visible = false;
}
}
occludedSpheresElement.innerHTML = occludedSpheres;
}
firstRender = false;
renderer.render(scene, camera);
}
function computeBoundingBoxPositions(box) {
var dimension = box.max.sub(box.min);
var width = dimension.x;
var height = dimension.y;
var depth = dimension.z;
var x = box.min.x;
var y = box.min.y;
var z = box.min.z;
var fbl = {x: x, y: y, z: z + depth};
var fbr = {x: x + width, y: y, z: z + depth};
var ftl = {x: x, y: y + height, z: z + depth};
var ftr = {x: x + width, y: y + height, z: z + depth};
var bbl = {x: x, y: y, z: z };
var bbr = {x: x + width, y: y, z: z };
var btl = {x: x, y: y + height, z: z };
var btr = {x: x + width, y: y + height, z: z };
var positions = new Float32Array([
//front
fbl.x, fbl.y, fbl.z,
fbr.x, fbr.y, fbr.z,
ftl.x, ftl.y, ftl.z,
ftl.x, ftl.y, ftl.z,
fbr.x, fbr.y, fbr.z,
ftr.x, ftr.y, ftr.z,
//right
fbr.x, fbr.y, fbr.z,
bbr.x, bbr.y, bbr.z,
ftr.x, ftr.y, ftr.z,
ftr.x, ftr.y, ftr.z,
bbr.x, bbr.y, bbr.z,
btr.x, btr.y, btr.z,
//back
fbr.x, bbr.y, bbr.z,
bbl.x, bbl.y, bbl.z,
btr.x, btr.y, btr.z,
btr.x, btr.y, btr.z,
bbl.x, bbl.y, bbl.z,
btl.x, btl.y, btl.z,
//left
bbl.x, bbl.y, bbl.z,
fbl.x, fbl.y, fbl.z,
btl.x, btl.y, btl.z,
btl.x, btl.y, btl.z,
fbl.x, fbl.y, fbl.z,
ftl.x, ftl.y, ftl.z,
//top
ftl.x, ftl.y, ftl.z,
ftr.x, ftr.y, ftr.z,
btl.x, btl.y, btl.z,
btl.x, btl.y, btl.z,
ftr.x, ftr.y, ftr.z,
btr.x, btr.y, btr.z,
//bottom
bbl.x, bbl.y, bbl.z,
bbr.x, bbr.y, bbr.z,
fbl.x, fbl.y, fbl.z,
fbl.x, fbl.y, fbl.z,
bbr.x, bbr.y, bbr.z,
fbr.x, fbr.y, fbr.z,
]);
return positions;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r125/three.js"></script>
<div id="occlusion-controls">
Spheres: <span id="num-spheres"></span><br> Culled spheres: <span id="num-invisible-spheres"></span><br>
</div>
<script type="x-shader/vs" id="vertex-boundingBox">#version 300 es
layout(std140, column_major) uniform;
layout(location=0) in vec4 position;
uniform mat4 uModel;
uniform mat4 uViewProj;
void main() {
gl_Position = uViewProj * uModel * position;
}
</script>
<script type="x-shader/vf" id="fragment-boundingBox">#version 300 es
precision highp float;
layout(std140, column_major) uniform;
out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
At a minimum, these are the issues I found.
you need to write to the depth buffer (otherwise how would anything occlude?)
so remove gl.depthMask(false)
you need to gl.flush the OffscreenCanvas because being offscreen, one is not automatically added for you. I found this out by using a normal canvas and adding it to the page. I also turned on drawing by commenting out gl.colorMask(false, false, false, false) just to double check that your boxes are drawn correctly. I noticed that when I got something kind of working it behaved differently when I switched back to the offscreen canvas. I found the same different behavior if I didn't add the normal canvas to the page. Adding in the gl.flush fixed the different behavior.
depthSort was not working
I checked this by changing the shader to use a color and I passed in i / NUM_SPHERES as the color which made it clear they were not being sorted. The issue was this
return sortPositionB[2] - sortPositionA[2];
needs to be
return sortPositionB.z - sortPositionA.z;
var camera, scene, renderer, light;
var spheres = [];
var NUM_SPHERES, occludedSpheres = 0;
var gl;
var boundingBoxPositions;
var boundingBoxProgram, boundingBoxArray, boundingBoxModelMatrixLocation, viewProjMatrixLocation;
var viewMatrix, projMatrix;
var firstRender = true;
var sphereCountElement = document.getElementById("num-spheres");
var occludedSpheresElement = document.getElementById("num-invisible-spheres");
// depth sort variables
var sortPositionA = new THREE.Vector3();
var sortPositionB = new THREE.Vector3();
var sortModelView = new THREE.Matrix4();
init();
animate();
function init() {
scene = new THREE.Scene();
scene.add( new THREE.AmbientLight( 0x222222 ) );
light = new THREE.DirectionalLight( 0xffffff, 1 );
scene.add( light );
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// set up offscreen canvas
var offscreenCanvas = new OffscreenCanvas(window.innerWidth, window.innerHeight);
//var offscreenCanvas = document.createElement('canvas');
//offscreenCanvas.width = window.innerWidth;
//offscreenCanvas.height = window.innerHeight;
//document.body.appendChild(offscreenCanvas);
gl = offscreenCanvas.getContext('webgl2');
if ( !gl ) {
console.error("WebGL 2 not available");
document.body.innerHTML = "This example requires WebGL 2 which is unavailable on this system."
}
// define spheres
var GRID_DIM = 6;
var GRID_OFFSET = GRID_DIM / 2 - 0.5;
NUM_SPHERES = GRID_DIM * GRID_DIM;
sphereCountElement.innerHTML = NUM_SPHERES;
var geometry = new THREE.SphereGeometry(20, 64, 64);
var material = new THREE.MeshPhongMaterial( {
color: 0xff0000,
specular: 0x050505,
shininess: 50,
emissive: 0x000000
} );
geometry.computeBoundingBox();
for ( var i = 0; i < NUM_SPHERES; i ++ ) {
var x = Math.floor(i / GRID_DIM) - GRID_OFFSET;
var z = i % GRID_DIM - GRID_OFFSET;
var mesh = new THREE.Mesh( geometry, material );
spheres.push(mesh);
scene.add(mesh);
mesh.position.set(x * 35, 0, z * 35);
mesh.userData.query = gl.createQuery();
mesh.userData.queryInProgress = false;
mesh.userData.occluded = false;
}
//////////////////////////
// WebGL code
//////////////////////////
// boundingbox shader
var boundingBoxVSource = document.getElementById("vertex-boundingBox").text.trim();
var boundingBoxFSource = document.getElementById("fragment-boundingBox").text.trim();
var boundingBoxVertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(boundingBoxVertexShader, boundingBoxVSource);
gl.compileShader(boundingBoxVertexShader);
if (!gl.getShaderParameter(boundingBoxVertexShader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(boundingBoxVertexShader));
}
var boundingBoxFragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(boundingBoxFragmentShader, boundingBoxFSource);
gl.compileShader(boundingBoxFragmentShader);
if (!gl.getShaderParameter(boundingBoxFragmentShader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(boundingBoxFragmentShader));
}
boundingBoxProgram = gl.createProgram();
gl.attachShader(boundingBoxProgram, boundingBoxVertexShader);
gl.attachShader(boundingBoxProgram, boundingBoxFragmentShader);
gl.linkProgram(boundingBoxProgram);
if (!gl.getProgramParameter(boundingBoxProgram, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(boundingBoxProgram));
}
// uniform location
boundingBoxModelMatrixLocation = gl.getUniformLocation(boundingBoxProgram, "uModel");
viewProjMatrixLocation = gl.getUniformLocation(boundingBoxProgram, "uViewProj");
// vertex location
boundingBoxPositions = computeBoundingBoxPositions(geometry.boundingBox);
boundingBoxArray = gl.createVertexArray();
gl.bindVertexArray(boundingBoxArray);
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, boundingBoxPositions, gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
gl.bindVertexArray(null);
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame(animate);
render();
}
function depthSort(a, b) {
sortPositionA.copy(a.position);
sortPositionB.copy(b.position);
sortModelView.copy(viewMatrix).multiply(a.matrix);
sortPositionA.applyMatrix4(sortModelView);
sortModelView.copy(viewMatrix).multiply(b.matrix);
sortPositionB.applyMatrix4(sortModelView);
return sortPositionB.z - sortPositionA.z;
}
function render() {
var timer = Date.now() * 0.0001;
camera.position.x = Math.cos( timer ) * 250;
camera.position.z = Math.sin( timer ) * 250;
camera.lookAt( scene.position );
light.position.copy( camera.position );
occludedSpheres = 0;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0, 0, 0, 1);
gl.enable(gl.DEPTH_TEST);
gl.colorMask(true, true, true, true);
gl.depthMask(true);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
if (!firstRender) {
viewMatrix = camera.matrixWorldInverse.clone();
projMatrix = camera.projectionMatrix.clone();
var viewProjMatrix = projMatrix.multiply(viewMatrix);
spheres.sort(depthSort);
// for occlusion test
gl.colorMask(false, false, false, false);
//gl.depthMask(false);
gl.useProgram(boundingBoxProgram);
gl.bindVertexArray(boundingBoxArray);
for (var i = 0; i < NUM_SPHERES; i ++) {
spheres[i].visible = true;
spheres[i].rotation.y += 0.003;
var sphereData = spheres[i].userData;
gl.uniformMatrix4fv(boundingBoxModelMatrixLocation, false, spheres[i].matrix.elements);
gl.uniformMatrix4fv(viewProjMatrixLocation, false, viewProjMatrix.elements);
gl.uniform4f(gl.getUniformLocation(boundingBoxProgram, 'color'),
i / NUM_SPHERES, 0, 0, 1);
// check query results here (will be from previous frame)
if (sphereData.queryInProgress && gl.getQueryParameter(sphereData.query, gl.QUERY_RESULT_AVAILABLE)) {
sphereData.occluded = !gl.getQueryParameter(sphereData.query, gl.QUERY_RESULT);
if (sphereData.occluded) occludedSpheres ++;
sphereData.queryInProgress = false;
}
// Query is initiated here by drawing the bounding box of the sphere
if (!sphereData.queryInProgress) {
gl.beginQuery(gl.ANY_SAMPLES_PASSED_CONSERVATIVE, sphereData.query);
gl.drawArrays(gl.TRIANGLES, 0, boundingBoxPositions.length / 3);
gl.endQuery(gl.ANY_SAMPLES_PASSED_CONSERVATIVE);
sphereData.queryInProgress = true;
}
if (sphereData.occluded) {
spheres[i].visible = false;
}
}
gl.flush();
occludedSpheresElement.innerHTML = occludedSpheres;
}
firstRender = false;
renderer.render(scene, camera);
}
function computeBoundingBoxPositions(box) {
var dimension = box.max.sub(box.min);
var width = dimension.x;
var height = dimension.y;
var depth = dimension.z;
var x = box.min.x;
var y = box.min.y;
var z = box.min.z;
var fbl = {x: x, y: y, z: z + depth};
var fbr = {x: x + width, y: y, z: z + depth};
var ftl = {x: x, y: y + height, z: z + depth};
var ftr = {x: x + width, y: y + height, z: z + depth};
var bbl = {x: x, y: y, z: z };
var bbr = {x: x + width, y: y, z: z };
var btl = {x: x, y: y + height, z: z };
var btr = {x: x + width, y: y + height, z: z };
var positions = new Float32Array([
//front
fbl.x, fbl.y, fbl.z,
fbr.x, fbr.y, fbr.z,
ftl.x, ftl.y, ftl.z,
ftl.x, ftl.y, ftl.z,
fbr.x, fbr.y, fbr.z,
ftr.x, ftr.y, ftr.z,
//right
fbr.x, fbr.y, fbr.z,
bbr.x, bbr.y, bbr.z,
ftr.x, ftr.y, ftr.z,
ftr.x, ftr.y, ftr.z,
bbr.x, bbr.y, bbr.z,
btr.x, btr.y, btr.z,
//back
fbr.x, bbr.y, bbr.z,
bbl.x, bbl.y, bbl.z,
btr.x, btr.y, btr.z,
btr.x, btr.y, btr.z,
bbl.x, bbl.y, bbl.z,
btl.x, btl.y, btl.z,
//left
bbl.x, bbl.y, bbl.z,
fbl.x, fbl.y, fbl.z,
btl.x, btl.y, btl.z,
btl.x, btl.y, btl.z,
fbl.x, fbl.y, fbl.z,
ftl.x, ftl.y, ftl.z,
//top
ftl.x, ftl.y, ftl.z,
ftr.x, ftr.y, ftr.z,
btl.x, btl.y, btl.z,
btl.x, btl.y, btl.z,
ftr.x, ftr.y, ftr.z,
btr.x, btr.y, btr.z,
//bottom
bbl.x, bbl.y, bbl.z,
bbr.x, bbr.y, bbr.z,
fbl.x, fbl.y, fbl.z,
fbl.x, fbl.y, fbl.z,
bbr.x, bbr.y, bbr.z,
fbr.x, fbr.y, fbr.z,
]);
return positions;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r125/three.js"></script>
<div id="occlusion-controls">
Spheres: <span id="num-spheres"></span><br> Culled spheres: <span id="num-invisible-spheres"></span><br>
</div>
<script type="x-shader/vs" id="vertex-boundingBox">#version 300 es
layout(std140, column_major) uniform;
layout(location=0) in vec4 position;
uniform mat4 uModel;
uniform mat4 uViewProj;
void main() {
gl_Position = uViewProj * uModel * position;
}
</script>
<script type="x-shader/vf" id="fragment-boundingBox">#version 300 es
precision highp float;
layout(std140, column_major) uniform;
out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
Related
I did tests on particle systems some time ago. because I'm working on it again at the moment. i want to use a texture but i only get black particles. i think the problem lies in the uv coordinates but i don't know how to use it in this case.
I have described in the code where I suspect the problem.
how do i access the texture coordinates in the shader in this case
var camera, controls, scene, renderer, container;
var PI = Math.PI;
var clock = new THREE.Clock();
var plane;
var MAX_Planes = 2000;
var velocity = [];
var geometry;
function init() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
var aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
camera.position.set(0, 0, 4000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//---------------shader---------------
var VertexShader = `
varying vec3 vUv;
uniform vec3 pos;
void main() {
vUv = uv;
vec4 finalPosition;
finalPosition = modelViewMatrix * vec4( pos, 1.0 );
finalPosition.xyz += vec3(position.x, position.y, 0.0);
finalPosition = projectionMatrix * finalPosition;
gl_Position = finalPosition;
}`;
var FragmentShader = `
varying vec3 vUv;
uniform sampler2D tDiffuse;
void main() {
gl_FragColor = vec4(texture2D(tDiffuse, vUv).rgb, 1.);
//gl_FragColor = vec4(1.0, 1.0, 0.8, 1.0); //just for testing
}`;
var loader = new THREE.TextureLoader();
var texture = loader.load( 'textures/test.jpg' ); //the texture is loaded correctly. I tested that with a box
var uniform = {
tDiffuse: {value: texture},
pos: { value: new THREE.Vector3(0,0,0) },
}
var material = new THREE.ShaderMaterial( {
uniforms: uniform,
vertexShader: VertexShader,
fragmentShader: FragmentShader,
transparent: true,
depthTest: false,
depthWrite: false
});
//-------------------------------------------------
//create a plane: points, normals, uv
const vertices = [
{ pos: [-20, -20, 0], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 20, -20, 0], norm: [ 0, 0, 1], uv: [1, 1], },
{ pos: [-20, 20, 0], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 20, 20, 0], norm: [ 0, 0, 1], uv: [1, 0], },
];
const numVertices = vertices.length;
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
//arrays for buffergeometry
const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
//fill arrays with vertices
var posPointer = 0;
var nrmPointer = 0;
var uvPointer = 0;
for(var i = 0; i <= MAX_Planes; i++) {
var posNdx = 0;
var nrmNdx = 0;
var uvNdx = 0;
for (const vertex of vertices) {
positions.set(vertex.pos, posNdx + posPointer);
normals.set(vertex.norm, nrmNdx + nrmPointer);
uvs.set(vertex.uv, uvNdx + uvPointer);
posNdx += positionNumComponents;
nrmNdx += normalNumComponents;
uvNdx += uvNumComponents;
}
posPointer = i * posNdx;
nrmPointer = i * nrmNdx;
uvPointer = i * uvNdx;
}
//create buffergeometry and assign the attribut arrays
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
var ndx = 0;
var indices = [];
//instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
for(var i = 0; i < MAX_Planes; i++){
indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
ndx += 4;
}
geometry.setIndex(indices);
var materials = [];
geometry.clearGroups();
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6*(i+1), i );
materials.push(material.clone());
}
plane = new THREE.Mesh(geometry, materials);
scene.add(plane);
//----------------------velocity---------------------------
for(var i = 0; i < MAX_Planes; i++){
velocity[i] = new THREE.Vector3(
Math.random()*2-1,
Math.random()*2-1,
Math.random()*2-1);
}
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
var loop = 0;
function render() {
loop = loop + 0.5;
for(var i = 0; i < MAX_Planes; i++){
var pos = new THREE.Vector3(0, 0, 0);
pos.x += velocity[i].x*loop;
pos.y += velocity[i].y*loop;
pos.z += velocity[i].z*loop;
plane.material[i].uniforms.pos.value = pos;
}
plane.geometry.attributes.position.needsUpdate = true;
plane.geometry.attributes.uv.needsUpdate = true;
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------
my initial assumption was wrong. I suspect the problem now in the regrouping. with the following line i see the texture
plane = new THREE.Mesh(geometry, material);
but that's no use to me. each group "one plane inside the bufferarray" should have its own material and for that i need the grouping but obviously i'm doing something wrong because i don't see anything with the "materials" array
ah, the problem is that the texture is not cloned when the material is cloned. cloning the material only works if I work purely with shader code without texture. if i want to have the texture i have to instantiate the material every time in the for loop
I've changed the version of my three.js because i wanted to use projector but now all my texture went white. I can't find what is wrong with it. I'm not getting any error in the console, which makes me a bit lost.
I was using r122.
Any body know what is happening ?
most of the code here comes from a code pen :
https://codepen.io/yitliu/pen/bJoQLw
//----------VARIABLES----------
const dpi = window.devicePixelRatio;
const theCanvas = document.getElementById('canvas');
var h = window.innerHeight,
w = window.innerWidth;
aspectRatio = w / h,
fieldOfView = 25,
nearPlane = .1,
farPlane = 1000;
container = document.getElementById('container');
var dae, scene, camera, renderer;
var Colors = {
cyan: 0x248079,
brown: 0xA98F78,
brownDark: 0x9A6169,
green: 0x65BB61,
greenLight: 0xABD66A,
blue: 0x6BC6FF
};
function init() {
//----------SCENE----------
scene = new THREE.Scene();
//----------CAMERA----------
camera = new THREE.PerspectiveCamera(
fieldOfView,
aspectRatio,
nearPlane,
farPlane);
//----------RENDER----------
renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
renderer.setSize(w * dpi, h * dpi);
theCanvas.style.width = `${w}px`;
theCanvas.style.height = `${h}px`;
renderer.shadowMapEnabled = true;
renderer.shadowMapType = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
camera.position.set(-5, 6, 8);
camera.lookAt(new THREE.Vector3(0, 0, 0));
//---------LIGHT---------
var light = new THREE.AmbientLight(0xffffff, .5);
var shadowLight = new THREE.DirectionalLight(0xffffff, .5);
shadowLight.position.set(200, 200, 200);
shadowLight.castShadow = true;
var backLight = new THREE.DirectionalLight(0xffffff, .2);
backLight.position.set(-100, 200, 50);
backLight.castShadow = true;
scene.add(backLight);
scene.add(light);
scene.add(shadowLight);
//---------CONTROLS---------
const controls = new THREE.OrbitControls(camera, renderer.domElement);
//---------GEOMETRY---------
var geometry_left = new THREE.CubeGeometry(2, .2, 4);
var material_grass = new THREE.MeshLambertMaterial({ color: Colors.greenLight });
var ground_left = new THREE.Mesh(geometry_left, material_grass);
ground_left.position.set(-1, 0.1, 0);
ground_left.receiveShadow = true;
scene.add(ground_left);
var geometry_bot = new THREE.CubeGeometry(4, .2, 1);
var material_grass = new THREE.MeshLambertMaterial({ color: Colors.greenLight });
var ground_bot = new THREE.Mesh(geometry_bot, material_grass);
ground_bot.position.set(0, 0.1, 2);
ground_bot.receiveShadow = true;
scene.add(ground_bot);
var geometry_top = new THREE.CubeGeometry(4, .2, 1);
var material_grass = new THREE.MeshLambertMaterial({ color: Colors.greenLight });
var ground_top = new THREE.Mesh(geometry_top, material_grass);
ground_top.position.set(0, 0.1, -2);
ground_top.receiveShadow = true;
scene.add(ground_top);
var geometry_river = new THREE.CubeGeometry(1, .1, 4);
var material_river = new THREE.MeshLambertMaterial({ color: Colors.blue });
var river = new THREE.Mesh(geometry_river, material_river);
river.position.set(.5, .1, 0);
river.receiveShadow = true;
scene.add(river);
var geometry_bed = new THREE.CubeGeometry(1, .05, 4);
var bed = new THREE.Mesh(geometry_bed, material_grass);
bed.position.set(.5, .025, 0);
scene.add(bed);
var geometry_right = new THREE.CubeGeometry(1, .2, 4);
var ground_right = new THREE.Mesh(geometry_right, material_grass);
ground_right.position.set(1.5, 0.1, 0);
ground_right.receiveShadow = true;
scene.add(ground_right);
var tree = function (x, z) {
this.x = x;
this.z = z;
var material_trunk = new THREE.MeshLambertMaterial({ color: Colors.brownDark });
var geometry_trunk = new THREE.CubeGeometry(.15, .15, .15);
var trunk = new THREE.Mesh(geometry_trunk, material_trunk);
trunk.position.set(this.x, .275, this.z);
trunk.castShadow = true;
trunk.receiveShadow = true;
scene.add(trunk);
var geometry_leaves = new THREE.CubeGeometry(.25, .4, .25);
var material_leaves = new THREE.MeshLambertMaterial({ color: Colors.green });
var leaves = new THREE.Mesh(geometry_leaves, material_leaves);
leaves.position.set(this.x, .2 + .15 + .4 / 2, this.z);
leaves.castShadow = true;
scene.add(leaves);
}
tree(-1.5, -1.5);
//tree(-1.25,.75);
tree(-.25, -.85);
tree(0.4, 2);
//tree(1.25,-2);
tree(1.75, .35);
var material_wood = new THREE.MeshLambertMaterial({ color: Colors.brown });
var geometry_block = new THREE.CubeGeometry(1.3, .02, .6);
var block = new THREE.Mesh(geometry_block, material_wood);
block.position.set(.5, .21, .2);
block.castShadow = true;
block.receiveShadow = true;
scene.add(block);
//---------CAR---------
var loader = new THREE.ColladaLoader();
loader.load('offroadcar.dae', function (collada) {
dae = collada.scene;
dae.scale.x = dae.scale.y = dae.scale.z = 0.1;
dae.position.set(-1, .2, 0);
dae.updateMatrix();
//customizeShadow(dae, .25)
scene.add(dae);
dae.castShadow = true;
dae.receiveShadow = true;
})
}// end of init
//---------STARTING---------
init();
animate();
//---------LISTENERS---------
theCanvas.addEventListener('click', function (evt) {
clickInfo.userHasClicked = true;
clickInfo.x = evt.clientX - theCanvas.offsetLeft;
clickInfo.y = evt.clientY - theCanvas.offsetTop;
}, false);
//---------METHODS---------
function animate() {
renderer.render(scene, camera);
requestAnimationFrame(function () { animate(); });
}
I think showing a code example is the best. Therefore i have created a small example that shows the problem.
I want to create a buffer geometry in which each group has its own shader. although i always create a new instance in the material array, i cannot use the uniforms of the individual shaders independently. what i adjust in the uniform of one shader in the array always has the same effect on all other shaders in the material array.
Before i ask, i try to advance through research, but here i have reached a point where i can not get any further. Does anyone know why the individual shaders in the material array are dependent on each other and how to avoid this?
var camera, controls, scene, renderer, container;
var PI = Math.PI;
var clock = new THREE.Clock();
var plane;
var MAX_Planes = 100;
var velocity = [];
var geometry;
var test1, test2, test3;
function init() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//renderer.sortObjects = true;
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
var aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
camera.position.set(0, 0, 4000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//---------------shaders---------------
var BasicVertexShader = `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`;
var BasicFragmentShader = `
void main() {
gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0);
}`;
var VertexShader = `
varying vec3 sPos;
uniform vec3 pos;
uniform float stretch;
void main() {
float rotation = 0.0;
sPos = position;
vec3 scale;
scale.x = 1.0*stretch;
scale.y = 1.0*stretch;
scale.z = 1.0*stretch;
vec3 alignedPosition = vec3(position.x * scale.x, position.y * scale.y, 0.0);
vec3 rotatedPosition;
rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;
rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;
rotatedPosition.z = alignedPosition.z;
vec4 finalPosition;
finalPosition = modelViewMatrix * vec4( 0, 0, 0, 1.0 );
finalPosition.xyz += rotatedPosition;
finalPosition = projectionMatrix * finalPosition;
gl_Position = finalPosition;
// gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`;
var FragmentShader = `
varying vec3 sPos;
void main() {
vec3 nDistVec = normalize(sPos);
float dist = pow(sPos.x, 2.0) + pow(sPos.y, 2.0);
float magnitude = 1.0/dist * pow(4.0, 2.0);
// gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0) * magnitude;
gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0);
}`;
var uniform = {
stretch: {type: 'f', value: 1.0},
pos: { value: new THREE.Vector3(0,0,0) },
}
Shader = new THREE.ShaderMaterial( {
uniforms: uniform,
vertexShader: VertexShader,
fragmentShader: FragmentShader,
transparent: true,
depthTest: false,
depthWrite: false
});
//just for tests
var Shade = new THREE.ShaderMaterial( {
vertexShader: BasicVertexShader,
fragmentShader: BasicFragmentShader,
side:THREE.DoubleSide
});
//-------------------------------------------------
//create a plane: points, normals, uv
const vertices = [
{ pos: [-10, -10, 0], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 10, -10, 0], norm: [ 0, 0, 1], uv: [1, 1], },
{ pos: [-10, 10, 0], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 10, 10, 0], norm: [ 0, 0, 1], uv: [1, 0], },
];
const numVertices = vertices.length;
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
//arrays for buffergeometry
const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
//fill arrays with vertices
var posPointer = 0;
var nrmPointer = 0;
var uvPointer = 0;
for(var i = 0; i <= MAX_Planes; i++) {
var posNdx = 0;
var nrmNdx = 0;
var uvNdx = 0;
for (const vertex of vertices) {
positions.set(vertex.pos, posNdx + posPointer);
normals.set(vertex.norm, nrmNdx + nrmPointer);
uvs.set(vertex.uv, uvNdx + uvPointer);
posNdx += positionNumComponents;
nrmNdx += normalNumComponents;
uvNdx += uvNumComponents;
}
posPointer = i * posNdx;
nrmPointer = i * nrmNdx;
uvPointer = i * uvNdx;
}
//create buffergeometry and assign the attribut arrays
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
var ndx = 0;
var indices = [];
//instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
for(var i = 0; i < MAX_Planes; i++){
indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
ndx += 4;
}
geometry.setIndex(indices);
var materials = [];
geometry.clearGroups();
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6, i );
materials.push(Shader);
}
plane = new THREE.Mesh(geometry, materials);
scene.add(plane);
plane.material[0].uniforms.stretch.value = 2;
plane.material[1].uniforms.stretch.value = 3;
test1 = Object.keys(plane.material);
test2 = plane.material[0].uniforms.stretch.value; //why this returns 3 and not 2?
test3 = plane.material[1].uniforms.stretch.value;
//the goal is that each group has its own Shader without effecting the other ones
//----------------------velocity---------------------------
for(var i = 0; i < MAX_Planes; i++){
velocity[i] = new THREE.Vector3(
Math.random()*2-1,
Math.random()*2-1,
Math.random()*2-1);
}
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
function render() {
document.getElementById("demo1").innerHTML = test1;
document.getElementById("demo2").innerHTML = test2;
document.getElementById("demo3").innerHTML = test3;
for(var i = 0; i < MAX_Planes; i++){
for(var j = 0; j < 4; j++){
plane.geometry.attributes.position.array[i*12+3*j] = plane.geometry.attributes.position.array[i*12+3*j] + velocity[i].x;
plane.geometry.attributes.position.array[i*12+3*j+1] = plane.geometry.attributes.position.array[i*12+3*j+1] + velocity[i].y;
plane.geometry.attributes.position.array[i*12+3*j+2] = plane.geometry.attributes.position.array[i*12+3*j+2] + velocity[i].z;
}
}
plane.geometry.attributes.position.needsUpdate = true;
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------
You are pushing references to a single ShaderMaterial into every index of your materials array.
materials.push(Shader);
Because each index is a reference, changing the properties on the object in one index will naturally change the object in all other indices.
If you want each group to have its own properties, then you will need to provide a unique material to each index. You can still do this by only creating one original definition, then using Material.clone to create copies.
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6, i );
materials.push( Shader.clone() ); // creates a unique copy for each index
}
this did not leave me in peace and it occurred to me that if i prealocate the buffer geometry, i would have to do that with the shaders for each group. I would now have made this much more complicated than with ".clone ()". good that i checked again. Your advice works wonderfully. I have corrected the positioning update and that is exactly the result that i had in mind. Here is the customized code for anyone interested. I will now combine this with my particle emitter.
var camera, controls, scene, renderer, container;
var PI = Math.PI;
var clock = new THREE.Clock();
var plane;
var MAX_Planes = 2000;
var velocity = [];
var geometry;
function init() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//renderer.sortObjects = true;
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
var aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
camera.position.set(0, 0, 4000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//---------------shader---------------
var VertexShader = `
varying vec3 sPos;
uniform vec3 pos;
uniform float stretch;
void main() {
float rotation = 0.0;
sPos = position;
vec3 scale;
scale.x = 1.0*stretch;
scale.y = 1.0*stretch;
scale.z = 1.0*stretch;
vec3 alignedPosition = vec3(position.x * scale.x, position.y * scale.y, 0.0);
vec3 rotatedPosition;
rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;
rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;
rotatedPosition.z = alignedPosition.z;
vec4 finalPosition;
finalPosition = modelViewMatrix * vec4( pos, 1.0 );
finalPosition.xyz += rotatedPosition;
finalPosition = projectionMatrix * finalPosition;
gl_Position = finalPosition;
}`;
var FragmentShader = `
varying vec3 sPos;
void main() {
vec3 nDistVec = normalize(sPos);
float dist = pow(sPos.x, 2.0) + pow(sPos.y, 2.0);
float magnitude = 1.0/dist * pow(3.0, 2.0);
float alpha = 1.0;
if(magnitude < 0.01){
alpha = 0.0;
}
gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), alpha) * magnitude;
// gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0);
}`;
var uniform = {
stretch: {type: 'f', value: 1.0},
pos: { value: new THREE.Vector3(0,0,0) },
}
var Shader = new THREE.ShaderMaterial( {
uniforms: uniform,
vertexShader: VertexShader,
fragmentShader: FragmentShader,
transparent: true,
depthTest: false,
depthWrite: false
});
//-------------------------------------------------
//create a plane: points, normals, uv
const vertices = [
{ pos: [-20, -20, 0], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 20, -20, 0], norm: [ 0, 0, 1], uv: [1, 1], },
{ pos: [-20, 20, 0], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 20, 20, 0], norm: [ 0, 0, 1], uv: [1, 0], },
];
const numVertices = vertices.length;
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
//arrays for buffergeometry
const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
//fill arrays with vertices
var posPointer = 0;
var nrmPointer = 0;
var uvPointer = 0;
for(var i = 0; i <= MAX_Planes; i++) {
var posNdx = 0;
var nrmNdx = 0;
var uvNdx = 0;
for (const vertex of vertices) {
positions.set(vertex.pos, posNdx + posPointer);
normals.set(vertex.norm, nrmNdx + nrmPointer);
uvs.set(vertex.uv, uvNdx + uvPointer);
posNdx += positionNumComponents;
nrmNdx += normalNumComponents;
uvNdx += uvNumComponents;
}
posPointer = i * posNdx;
nrmPointer = i * nrmNdx;
uvPointer = i * uvNdx;
}
//create buffergeometry and assign the attribut arrays
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
var ndx = 0;
var indices = [];
//instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
for(var i = 0; i < MAX_Planes; i++){
indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
ndx += 4;
}
geometry.setIndex(indices);
var materials = [];
geometry.clearGroups();
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6, i );
materials.push(Shader.clone());
}
plane = new THREE.Mesh(geometry, materials);
scene.add(plane);
//----------------------velocity---------------------------
for(var i = 0; i < MAX_Planes; i++){
velocity[i] = new THREE.Vector3(
Math.random()*2-1,
Math.random()*2-1,
Math.random()*2-1);
}
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
var loop = 0;
function render() {
loop = loop + 0.5;
for(var i = 0; i < MAX_Planes; i++){
var pos = new THREE.Vector3(0, 0, 0);
pos.x += velocity[i].x*loop;
pos.y += velocity[i].y*loop;
pos.z += velocity[i].z*loop;
plane.material[i].uniforms.pos.value = pos;
}
plane.geometry.attributes.position.needsUpdate = true;
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------
I set 2000 rectangles here and am pleasantly surprised at how smoothly the code runs. Even with 5000 rectangles everything went smoothly, even though each rectangle has its own shader. buffer geometries are really cool.
Thank you TheJim01
without your advice i would have preinitialized all shaders in a for loop. Such as vertexshader[i], fragmentshader[i], uniforms[i], Shader[i]. Using
".clone()" is of course much better 👍
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.
have pb with rotate texture, i read this question
Three.js Rotate Texture and there guys propose rotate in canvas, and it work good if you have rectangle, but i have pb with polygon, so after rotating i will have black area in some corner, so that solution is not for me, so maybe who know how i can rotate texture by threejs???
//Here's some code showing texture rotation/repeat/offset/center/etc.
var renderer = new THREE.WebGLRenderer();
var w = 600;
var h = 200;
renderer.setSize(w, h);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
45, // Field of view
w / h, // Aspect ratio
0.1, // Near
10000 // Far
);
camera.position.set(15, 10, 15);
camera.lookAt(scene.position);
controls = new THREE.OrbitControls(camera, renderer.domElement);
var light = new THREE.PointLight(0xFFFF00);
light.position.set(20, 20, 20);
scene.add(light);
var light1 = new THREE.AmbientLight(0x808080);
light1.position.set(20, 20, 20);
scene.add(light1);
var light2 = new THREE.PointLight(0x00FFFF);
light2.position.set(-20, 20, -20);
scene.add(light2);
var light3 = new THREE.PointLight(0xFF00FF);
light3.position.set(-20, -20, -20);
scene.add(light3);
var planeGeom = new THREE.PlaneGeometry(40, 40);
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 64;
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(256,0,0,0.95)'
ctx.fillRect(0, 0, 32, 32);
ctx.fillRect(32, 32, 32, 32);
var srnd = (rng) => (Math.random() - 0.5) * 2 * rng
for (var i = 0; i < 300; i++) {
ctx.fillStyle = `rgba(${srnd(256)|0}, ${srnd(256)|0}, ${srnd(256)|0}, ${srnd(1)})`
ctx.fillRect(srnd(60) | 0, srnd(60) | 0, 5, 5);
}
ctx.fillStyle = 'rgba(256,256,0,0.95)'
ctx.fillText("TEST", 2, 10);
ctx.fillText("WOOO", 32, 45);
var tex = new THREE.Texture(canvas)
tex.magFilter = THREE.NearestFilter;
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
tex.magFilter = tex.minFilter = THREE.NearestFilter;
tex.needsUpdate = true;
var material = new THREE.MeshLambertMaterial({
color: 0x808080,
map: tex,
transparent: true,
side: THREE.DoubleSide
});
var mesh = new THREE.Mesh(planeGeom, material);
scene.add(mesh);
renderer.setClearColor(0xdddddd, 1);
tex.repeat.x = tex.repeat.y = 2;
//fun effect
for (var i = 1; i < 10; i++) {
var m;
scene.add(m = mesh.clone());
m.position.z += i * 1.1;
}
(function animate() {
var tm = performance.now() * 0.0001
tex.rotation = Math.sin(tm) * Math.PI
//tex.offset.x = tex.offset.y = -2;
//tex.offset.x = Math.sin(tex.rotation) * -0.5;
//tex.offset.y = Math.cos(tex.rotation) * -0.5;
tex.repeat.x = tex.repeat.y = Math.sin(tex.rotation * 1.5) * 3;
tex.center.x = 0.5;
tex.center.y = 0.5;
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
})();
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>