如何快速查找某个点是否在复杂场景中被遮挡?

时间:2016-04-20 13:41:23

标签: javascript three.js webgl raytracing

我有一个复杂的3D场景,我需要在3D坐标的基础上显示HTML元素。 (我只是在顶部覆盖div标签并使用CSS进行定位。)但是,当3D坐标被模型遮挡时,我还需要部分隐藏它(例如,使其透明)(或措辞)换句话说,当它在相机中不可见时)。这些模型可能有数十万个面孔,我需要一种方法来确定它是否模糊得足以让它每秒运行多次。

目前,我正在使用Three.js的内置光线跟踪器,其代码如下:

// pos   = vector with (normalized) x, y coordinates on canvas
// dir   = vector from camera to target point

const raycaster = new THREE.Raycaster();
const d = dir.length(); // distance to point
let intersects = false;
raycaster.setFromCamera(pos, camera);
const intersections = raycaster.intersectObject(modelObject, true);
if (intersections.length > 0 && intersections[0].distance < d)
    intersects = true;

// if ray intersects at a point closer than d, then the target point is obscured
// otherwise it is visible

然而,在这些复杂的模型上,这是非常慢(帧速率从50 fps下降到8 fps)。我一直在寻找更好的方法来做到这一点,但到目前为止,我还没有找到任何在这种情况下运作良好的方法。

是否有更好,更有效的方法可以找出场景中的模型是否可见或模糊点?

4 个答案:

答案 0 :(得分:3)

我不知道有什么快速的方法,但你确实有一些选择。我不太了解three.js,告诉你如何使用该库,但总体上谈论WebGL ......

如果您可以使用WebGL 2.0,则可以使用遮挡查询。这归结为

var query = gl.createQuery();
gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
// ... draw a small quad at the specified 3d position ...
gl.endQuery(gl.ANY_SAMPLES_PASSED);
// some time later, typically a few frames later (after a fraction of a second)
if (gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE))
{
     gl.getQueryParameter(query, gl.QUERY_RESULT);
}

但请注意,查询结果仅在几帧之后可用。

如果WebGl 2.0不是一个选项,那么您应该将场景绘制到帧缓冲区,在那里您可以附加自己的纹理来代替普通的z缓冲区。有一个扩展使用适当的深度纹理(more details here),但是如果不可能,你总是可以回退到使用片段着色器绘制场景,片段着色器输出每个像素的深度。

然后,您可以在深度纹理上使用gl.ReadPixels()。再次,请注意GPU-> CPU传输的延迟,这一点总是很重要。

说了这么多,根据你的DOM对象的样子,将DOM对象渲染到纹理中并使用四边形作为3d场景的一部分绘制该纹理会更容易,更快。

答案 1 :(得分:0)

我将假设所需html标签的内容是第三方数据,如图像或iframe,并且不能与webgl一起使用,因此它必须是html标签,不能是精灵。

有GPU计算的食谱。每次场景更改时重复。对不起,我不能为Three.js做这件事(不知道引擎)。

第1阶段,使用标签可见性构建图像

创建包含html标签大小,标签ID(0中的int数字)和标签位置的数组(和索引)缓冲区。

创建renderbuffer和新的WebGL程序,用于渲染它。该程序的着色器将呈现简化的场景,包括标签的阴影和#34;。现在片段着色器的简化算法如下:对于任何对象呈现白色。除标签外,根据标签ID渲染颜色。

如果您当前的程序有雾,透明对象,高度图或某些程序逻辑,它也可能包含在着色器中(取决于它是否可以覆盖标记)。

结果看起来像这样(但并不重要):

content of renderbuffer

如果颜色与白色不同,则有标签。 (假设我只有3个标签,那么我的颜色是#000000,#010000,#020000,它们看起来都像黑色,但不是。)

第2阶段,收集有关图片中标签的透明度数据

我们需要另一个WebGL程序和renderbuffer。我们将点渲染到渲染缓冲区,每个点都是一个像素大并且彼此相邻。点代表标签。所以我们需要带有标签位置的数组缓冲区(和标签ID,但这可以在着色器中推导出来)。我们还绑定前一阶段的纹理。

现在顶点着色器的代码将执行以下操作,基于标签ID属性,它将设置该点的位置。然后用纹理查找计算透明度,伪代码:

attribute vec3 tagPosition;
attribute float tagId;

float calculateTransparency(vec2 tagSize, vec2 tagPosition) {
    float transparency = 0;
    for(0-tagSize.x+tagPosition.x) {
        for(0-tagSize.y+tagPosition.y) {
            if(textureLookup == tagId) transparency++; // notice that texture lookup is used only for area where tag could be
        }
    }
    return transparency/totalSize;
}

vec2 tagSize2d = calculateSize(tagPosition);
float transparency = calculateTransparency(tagSize2d, tagPosition.xy);

点位置和透明度将随FS变化而变化。 FS将根据透明度渲染一些颜色(例如,白色表示完全可见,黑色表示不可见,灰色表示可见部分)。

此阶段的结果是图像,其中每个像素呈现一个标签,并且像素的颜色是标签透明度。根据您拥有的标签数量,某些像素可能没有任何意义,并且具有clearColor值。像素位置对应于标签ID。

第3阶段,使用javascript读取值

要回读数据,请使用readPixels(或者可以使用texImage2D?)。 Simple way to do it

然后,您可以使用基于标记ID的forloop,并将类型化数据中的数据写入您的javascript状态机。现在你在javascript中有透明度值,你可以改变CSS值。

<强>观

在第1阶段,减小渲染缓冲区的大小将导致显着的性能提升(它还降低了阶段2中的纹理查找),几乎为零成本。

如果您在第1阶段之后直接使用readPixels并尝试使用javascript从屏幕读取数据,即使您使用的渲染缓冲区仅为320 * 200px,js也必须执行与分辨率相同的迭代次数。因此,如果场景每时每刻都会发生变化,那么只需清空forloop:

var time = Date.now();
for(var i=0;i<320*200*60;i++) { // 64000*60 times per second
}
console.log(Date.now() - time);

在我的机器上花费了大约4100毫秒。但是对于第2阶段,您必须只进行与可见区域中的标记一样多的迭代。 (50个标签可能是3000 * 60)。

我看到的最大问题是实施的复杂性。

这种技术的瓶颈是读像素和纹理查找。您可能会考虑不以FPS速度调用第3阶段,而是以较慢的预定义速度调用。

答案 2 :(得分:0)

假设您的div定位与底层3D场景同步,您应该能够使用“div”下方的readPixels查询just one像素。

再次假设您控制几何,在div将覆盖并测试它的纹理中添加“out of context”颜色(或alpha值)不是一个可行的hack吗? / p>

如果没有纹理,请修改几何体以“覆盖”上覆div边界内的单个顶点,并为其提供等效的“脱离上下文”颜色或alpha值,以提供给片段着色器。

答案 3 :(得分:0)

Here in this answer你找到了一个很好的例子,使用THREE.Frustum来检测对象是否可见:

var frustum = new THREE.Frustum();
var cameraViewProjectionMatrix = new THREE.Matrix4();
camera.updateMatrixWorld();
camera.matrixWorldInverse.getInverse( camera.matrixWorld );
cameraViewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
frustum.setFromMatrix( cameraViewProjectionMatrix );

visible = frustum.intersectsObject( object );

不确定这是否会为您提供您所追求的性能。 也许你可以测试一下它的工作情况,并对你的研究结果发表评论,让其他人最终找到类似的解决方案。