我假设所需的HTML标签内容是第三方数据,例如图像或iframe,并且不能与WebGL一起使用,因此必须是HTML标签,而不能是sprite。
有一个GPU计算的食谱。每次场景更改时重复。很抱歉我无法为Three.js(不知道引擎)执行此操作。
阶段1,使用标签可见性构建图像
创建包含HTML标签大小、标签ID(从0开始的整数)和标签位置的数组(和索引)缓冲区。
创建渲染缓冲区和新的WebGL程序,用于将其渲染到其中。该程序的着色器将呈现包括“标签阴影”在内的简化场景。现在片段着色器的简化算法如下:对于任何对象,都要呈现白色。除了标签之外,根据标签ID呈现颜色。
如果您当前的程序具有雾、透明对象、高度图或某些过程逻辑,则它也可能包含在着色器中(取决于它是否可以覆盖标签)。
结果可能看起来像这样(但这并不重要):
![content of renderbuffer](https://istack.dev59.com/VJqM6.webp)
如果颜色不是白色,则存在标签。(假设我只有3个标签,那么我的颜色是#000000、#010000、#020000,它们看起来都像黑色,但实际上不是。)
第二阶段,收集图像中标签的透明度数据
我们需要另一个WebGL程序和渲染缓冲区。我们将在渲染缓冲区中渲染点,每个点都是一个像素大小,并且相互靠近。点代表标签。因此,我们需要带有标签位置的数组缓冲区(以及标签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++;
}
}
return transparency/totalSize;
}
vec2 tagSize2d = calculateSize(tagPosition);
float transparency = calculateTransparency(tagSize2d, tagPosition.xy);
点的位置和透明度将作为变量输入到FS中。FS将根据透明度渲染一些颜色(例如完全可见的白色,不可见的黑色以及部分可见的灰色阴影)。
这个阶段的结果是一张图像,每个像素代表一个标签,像素的颜色是标签的透明度。根据你有多少个标签,一些像素可能没有意义,并且具有clearColor值。像素的位置对应于标签ID。
第三阶段,使用JavaScript读取值
要读取数据,请使用readPixels(或者可能使用texImage2D?)。简单的方法来做这个。
然后,您可以使用基于标签ID的for循环,将数据从类型化数组写入您的JavaScript状态机中。现在,您在JavaScript中拥有透明度值,可以更改CSS值。
想法
在第一阶段,减小渲染缓冲区的大小会显著提高性能(它还降低了第二阶段中的纹理查找),几乎没有成本。
如果您在第一阶段直接使用readPixels,并尝试使用javascript从屏幕读取数据,即使您仅使用320*200像素大小的渲染缓冲区,js也必须执行分辨率相同次数的迭代。因此,如果场景每时每刻都会改变,则只需空置for循环。
var time = Date.now();
for(var i=0;i<320*200*60;i++) { // 64000*60 times per second
}
console.log(Date.now() - time);
在我的机器上需要大约4100毫秒。但是使用第二阶段,您只需要执行与可见区域中的标签数量相同的迭代次数。(对于50个标签,可能是3000 * 60)。
我看到的最大问题是实现的复杂性。
这种技术的瓶颈是readpixels和texture lookups。您可以考虑不以FPS速度调用第三阶段,而是以较慢的预定义速度调用。
THREE.Sprite
替代 HTML 元素? - WestLangley