使用GLSL着色器隐藏面

时间:2015-02-11 22:47:39

标签: opengl-es three.js glsl webgl

我试图让着色器与three.js一起工作。这是一个用于WebGL的JavaScript库。我刚刚开始阅读GLSL,所以有些事情我遇到了麻烦。基本上我想揭示和隐藏几何的面。我克隆了几何体的面阵并重新排序。重新排序的数组按照我想要显示/隐藏它们的顺序具有面。我已经读过Uniforms可以从管道内的任何地方访问。我可以将数组声明为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);
        } 

2 个答案:

答案 0 :(得分:4)

您是否使用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);

这有多少意义吗?您现在可以通过设置u_triangleToHide

来选择要消失的三角形

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

那样的平等
  float diff = abs(v_triangleId - u_triangleToHide);
  if (diff < 0.01) {
     discard;
  }

或者您可以使用一些更复杂的检查来每隔7个隐藏其他一个或根据某个范围或其他任何方式调整alpha。

Here's an exampleanother example

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

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

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

Here's an example

在示例中,我有一个2D画布(左上角)。我使用2D API绘制它。每个像素对应于球体上的一个像素。然后,我通过调用texImage2D(..., 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的可选特征。根据{{​​3}},97%的GPU支持这一点,因此您最安全。如果您使用这种技术,对于那些不能工作的3%的人,您应该通过检查是否gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) > 0进行检查。如果没有退回或至少告诉用户它不会起作用。

答案 1 :(得分:0)

通常在WebGL中你进行绘制调用,并且必须指定从哪里开始绘制以及在每次调用中绘制多少个顶点,很明显你只能&#34;不能绘制& #34;几何体的一部分,或将其分成几个绘制调用。

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

(编辑:此属性显然仅适用于BufferGeometry而不是常规Geometry,因此您可能需要转换它。)