使用 GLSL 着色器隐藏面部

Hide faces with GLSL shader

我正在尝试编写着色器以使用 three.js。这是 WebGL 的 javascript 库。我刚开始阅读 GLSL,所以有些事情我遇到了麻烦。基本上我想要显示和隐藏几何体的面。我已经克隆了我的几何图形的面数组并重新排序。重新排序的数组中的面孔按照我想要 reveal/hide 的顺序排列。我读到可以从管道内的任何地方访问制服。我可以只将数组声明为 Uniform 并从片段着色器访问它吗?我在命令 gl_FragColor 中加入了 运行 并用它来调整整个几何图形的不透明度。我在想也许我可以使用 gl_FragColor 将特定面孔的不透明度设置为 0。我也在查看规格并找到 gl_SampleID。 gl_SampleID会告诉我现在的脸号吗?很抱歉提出所有问题,但我仍在努力解决问题。如果有更好的办法,请告诉我。我一直在尝试调整现有的着色器,因为我喜欢它的纹理效果。这是一些示例代码。

        uniform float time;
        uniform vec2 resolution;

        uniform sampler2D texture1;
        uniform sampler2D texture2;

        varying vec2 vUv;

        uniform float alpha;
        uniform int face_Array;

        void main( void ) {

            vec2 position = -1.0 + 2.0 * vUv;

            vec4 noise = texture2D( texture1, vUv );
            vec2 T1 = vUv + vec2( 1.5, -1.5 ) * time  *0.02;
            vec2 T2 = vUv + vec2( -0.5, 2.0 ) * time * 0.01;

            T1.x += noise.x * 2.0;
            T1.y += noise.y * 2.0;
            T2.x -= noise.y * 0.2;
            T2.y += noise.z * 0.2;

            float p = texture2D( texture1, T1 * 2.0 ).a;

            vec4 color = texture2D( texture2, T2 * 2.0 );
            vec4 temp = color * ( vec4( p, p, p, p ) * 2.0 ) + ( color * color - 0.1 );

            if( temp.r > 1.0 ){ temp.bg += clamp( temp.r - 2.0, 0.0, 100.0 ); }
            if( temp.g > 1.0 ){ temp.rb += temp.g - 1.0; }
            if( temp.b > 1.0 ){ temp.rg += temp.b - 1.0; }

            gl_FragColor = vec4(temp.r, temp.g, temp.b, alpha);
        } 

通常在 WebGL 中你会进行绘图调用,并且必须指定从哪里开始绘图以及在每次调用中要绘制多少个顶点,很明显你可以只 "not draw" 部分您的几何图形,或将其拆分为多个绘制调用。

在 threejs 中,它被抽象成一个名为 drawCalls 的 属性。如果您已经按照要隐藏它们的顺序对几何图形进行了排序,您所要做的就是创建一个 drawCall 并随时间修改范围。如果没有指定 drawCalls threejs 渲染整个几何体,因为这可能是大多数情况下人们想要的。

(编辑:此外,这个 属性 显然只存在于 BufferGeometry 而不是常规几何体,因此您可能需要转换它。)

不清楚您使用的是 WebGL 还是 three.js。它们不是一回事

无论如何,假设我明白你想做什么。一种方法是创建一个属性,我们称之为 "triangleId" 或 "vertexId"。由你决定。 "vertexId" 可能更好。

--顶点着色器-

attribute float vertexId;
varying float v_triangleId;
...

void main() {

   // pass triangleId to the fragment shader
   v_triangleId = floor(vertexId / 3.0) + 0.05;   

   ...
}

-- 片段着色器 --

...
varying float v_triangleId;
uniform float u_triangleToHide;

void main() {
  if (v_triangleId == u_triangleToHide) {
    discard;
  }
}

然后制作一个缓冲区并用您的顶点 ID 填充它。

 var vertIds = new Float32Array(numVertices);
 for (var i = 0; i < numVertices; ++i) {
   vertIds[i] = i;
 }
 var vertIdBuffer = gl.createBuffer();
 gl.bindBuffer(gl.ARRAY_BUFFER, vertIdBuffer);
 gl.bufferData(gl.ARRAY_BUFFER, vertIds, gl.STATIC_DRAW);

并设置属性

 gl.enableVertexAttribArray(locationOfVertIdAttrib);
 gl.vertexAttribPointer(locationOfVertIdAttrib, 1, gl.FLOAT, false, 0, 0);

那么多有意义吗?您现在可以 select 通过设置 u_triangleToHide

让哪个三角形消失

当然你可能想检查一些 epsilon 而不是像

这样的相等性
  float diff = abs(v_triangleId - u_triangleToHide);
  if (diff < 0.01) {
     discard;
  }

或者您可以使用一些更复杂的检查来每隔 7 个隐藏一个,或者根据某个范围调整 alpha,等等。

Here's an example and another example

另一种方法是分配第二个纹理和第二组 UV 坐标。分配特定三角形的每个顶点以查看第二个纹理中的相同像素。换一种说法。使第一个三角形的所有 3 个顶点都看向纹理中的第一个像素。使第二个三角形的所有顶点都看向纹理中的第二个像素。使第3个三角形的所有顶点都看向纹理中的第3个像素。

在每个三角形的片段着色器中,您可以查找与纹理中该像素对应的像素。您可以使用它来决定显示或不显示该三角形。更新纹理以更改显示或不显示的三角形,或将它们设置为灰度以更改其 alpha。如果您将纹理作为渲染目标,您甚至可以对其进行渲染。

如果您要使用此方法,不妨让您也使用 RGBA 纹理而不是单通道纹理为每个三角形着色。

Here's an example

在示例中,我有一个 2D canvas(左上角)。我正在使用 2D API 来绘制它。每个像素对应于球体上的一个像素。然后我通过调用 texImage2D(..., canvas).

将 canvas 每帧上传到纹理

注意:执行这些技术中的任何一种都非常需要非索引顶点。换句话说,使用 gl.drawArrays 而不是 gl.drawElements。您可以使用 gl.drawElements 但因为每个顶点都是唯一的,您的索引缓冲区将只是 [0, 1, 2, 3, 4, 5, 6, ...].


Brendan 在评论中指出,在片段着色器中丢弃会更有效。您可以通过移动相机后面的顶点来实现。

--顶点着色器--

attribute float vertexId;
uniform float u_triangleToHide;

void main() {

   ...

   triangleId = floor(vertexId / 3.0) + 0.05;   
   float diff = abs(triangleId - u_triangleToHide);
   if (diff < 0.01) {
     gl_Position = vec4(0, 0, 2, 1);  // put behind the camera
  }

这样做的好处是 GPU 只会剪裁三角形。 GPU 试图渲染三角形和其中的每个像素的另一种方式,然后我们的片段着色器一次丢弃每个像素。

这是采用该技术的示例

Discarding by triangle id in three JS

Discarding by triangle id in WebGL (tdl)

Discarding by triangle using UVs looking into texture in WebGL (tdl)

注意:最后一个需要从顶点着色器中的纹理读取,这是 WebGL 的 可选 功能。根据 webglstats 97% 的 GPU 支持这一点,因此您基本上是安全的。如果你使用这种技术,对于那 3% 的人来说它不起作用,你应该通过检查是否 gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) > 0 来检查。如果不回退或至少告诉用户它不会工作。