为什么我的闭塞剔除失败与Three.js?



首先我知道Three.js没有官方对遮挡剔除的支持。然而,我认为有可能在屏幕外画布中进行遮挡剔除,并将结果复制回我的Three.js WebGLCanvas。

基本上,我想转换这个演示到Three.JS演示。我使用Three.js创建所有内容,并在同步的屏幕外画布中测试针对每个边界框的遮挡剔除。如果任何边界框被遮挡,我在主画布中关闭该球体的可见性。这些就是我在这段代码中所做的。但我不知道为什么它不能遮挡任何球体。

我认为一个可能的问题可能来自计算边界框的ModelViewProjection矩阵,但我没有看到任何错误。有人能帮帮我吗?

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>

至少这些是我发现的问题。

  1. 您需要写入深度缓冲区(否则如何遮挡任何内容?)

    so removegl.depthMask(false)

  2. 你需要gl.flushOffscreenCanvas,因为在屏幕外,一个不会自动添加给你。我通过使用普通画布并将其添加到页面中发现了这一点。我还通过注释gl.colorMask(false, false, false, false)来打开绘图,只是为了再次检查您的方框是否画得正确。我注意到,当我有一些东西在工作时,当我切换回屏幕外画布时,它的行为就不同了。如果我没有将普通画布添加到页面中,我发现同样不同的行为。添加gl.flush修复了不同的行为。

  3. depthSort不工作

    我通过改变着色器使用一种颜色来检查这一点,我通过i / NUM_SPHERES作为颜色,这使得它们没有被排序。问题是

    return sortPositionB[2] - sortPositionA[2];
    

    必须是

    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>

最新更新