我有一个复杂的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)。我一直在寻找更好的方法来做到这一点,但到目前为止,我还没有找到任何在这种情况下运作良好的方法。
是否有更好,更有效的方法可以找出场景中的模型是否可见或模糊点?
答案 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渲染颜色。
如果您当前的程序有雾,透明对象,高度图或某些程序逻辑,它也可能包含在着色器中(取决于它是否可以覆盖标记)。
结果看起来像这样(但并不重要):
如果颜色与白色不同,则有标签。 (假设我只有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 );
不确定这是否会为您提供您所追求的性能。 也许你可以测试一下它的工作情况,并对你的研究结果发表评论,让其他人最终找到类似的解决方案。