如何将纹理数组绑定到WebGL着色器统一变量?

14
我需要处理许多仅共享少量纹理的对象。手动逐一定义和加载纹理(如SO 上的另一篇文章中所述)感觉不太对...更何况在 WebGL 中没有switch(index){case:...}语句。因此,我想将要用于顶点的纹理作为顶点属性传递,并使用该数字作为片段着色器中某个“纹理数组”的索引。但是Samplers 的 OpenGL wiki(并非 WebGL 的完美参考,但是我找到了这个)说:

sampler 变量只能以两种方式之一定义。它可以被定义为函数参数或统一变量。

uniform sampler2D texture1;

对我来说,这听起来就像我不能有任何采样器数组。我已经阅读了一些关于纹理单元的页面,但直到现在,这仍然是个谜。

在上面引用的SO帖子中,Toji暗示了一个解决方案,但想要一个单独的问题 - voila!
谢谢,nobi
PS:我知道使用“纹理集”这个可能性 - 如果这更有效或不那么复杂 - 我很乐意听到经验!

属性标记是我认为相当不错的想法。我知道它违背了人们更好的本能,但手动加载和if(aId == 1)c = texture2D(sampler1,p); else if(aID == 2) c = texture2D(sampler2 ...至少起作用。 :-/ 纹理单元就像纹理的“临时寄存器”。包括绘图在内的一些GL操作需要将1个纹理与1个纹理单元关联起来,我们可以自由分配。 - david van brink
1个回答

18

您必须使用常量值对采样器数组进行索引,以便您可以执行以下操作

#define numTextures 4

precision mediump float;
varying float v_textureIndex;
uniform sampler2D u_textures[numTextures];

vec4 getSampleFromArray(sampler2D textures[4], int ndx, vec2 uv) {
    vec4 color = vec4(0);
    for (int i = 0; i < numTextures; ++i) {
      vec4 c = texture2D(u_textures[i], uv);
      if (i == ndx) {
        color += c;
      }
    }
    return color;
}

void main() {
    gl_FragColor = getSampleFromArray(u_textures, int(v_textureIndex), vec2(0.5, 0.5));
}

你还需要告诉它使用哪些纹理单元。
var textureLoc = gl.getUniformLocation(program, "u_textures");
// Tell the shader to use texture units 0 to 3
gl.uniform1iv(textureLoc, [0, 1, 2, 3]);

上面的示例使用常量纹理坐标只是为了保持简单,但当然你可以使用任何纹理坐标。

以下是一个示例:

var canvas = document.getElementById("c");
var gl = canvas.getContext('webgl');

// Note: createProgramFromScripts will call bindAttribLocation
// based on the index of the attibute names we pass to it.
var program = webglUtils.createProgramFromScripts(
    gl, 
    ["vshader", "fshader"], 
    ["a_position", "a_textureIndex"]);
gl.useProgram(program);
var textureLoc = gl.getUniformLocation(program, "u_textures[0]");
// Tell the shader to use texture units 0 to 3
gl.uniform1iv(textureLoc, [0, 1, 2, 3]);

var positions = [
      1,  1,  
     -1,  1,  
     -1, -1,  
      1,  1,  
     -1, -1,  
      1, -1,  
];
    
var textureIndex = [
    0, 1, 2, 3, 0, 1,
];

var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);

var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(textureIndex), gl.STATIC_DRAW);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(1, 1, gl.UNSIGNED_BYTE, false, 0, 0);

var colors = [
    [0, 0, 255, 255],
    [0, 255, 0, 255],
    [255, 0, 0, 255],
    [0, 255, 255, 255],
];

// make 4 textures
colors.forEach(function(color, ndx) {
    gl.activeTexture(gl.TEXTURE0 + ndx);
    var tex = gl.createTexture();
   gl.bindTexture(gl.TEXTURE_2D, tex);
   gl.texImage2D(
      gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
      gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color));
});


gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2);
canvas { border: 1px solid black; }
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script id="vshader" type="whatever">
    attribute vec4 a_position;
    attribute float a_textureIndex;
    varying float v_textureIndex;
    void main() {
      gl_Position = a_position;
      v_textureIndex = a_textureIndex;
    }    
</script>
<script id="fshader" type="whatever">
#define numTextures 4
precision mediump float;
varying float v_textureIndex;
uniform sampler2D u_textures[numTextures];
    
vec4 getSampleFromArray(sampler2D textures[4], int ndx, vec2 uv) {
    vec4 color = vec4(0);
    for (int i = 0; i < numTextures; ++i) {
      vec4 c = texture2D(u_textures[i], uv);
      if (i == ndx) {
        color += c;
      }
    }
    return color;
}
    
void main() {
    gl_FragColor = getSampleFromArray(u_textures, int(v_textureIndex + 0.5), vec2(0.5, 0.5));
}
</script>
<canvas id="c" width="300" height="300"></canvas>


非常感谢,这看起来很有前途。我只是想知道为什么你需要写 if (ndx==0) {...textures[0]... 而不是直接写 textures[ndx]?并且完全摆脱函数 getSampleFromArray ?我一回到度假就会尝试这个 -- - virtualnobi
2
因为WebGL基于OpenGL ES 2.0,所以不能使用除常量索引表达式以外的任何东西来索引采样器数组。请参见GLSL 1.0.17附录A第5节[http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf],这意味着您可以根据常量或基于常量的循环索引进行索引,但不能使用其他内容。 - gman
你可以执行其他操作,比如循环遍历所有的纹理并乘以颜色。但是这样做快还是慢我不确定。换句话说:uniform vec4 u_colors[4]; ... vec4 color = vec4(0,0,0,0); for (int i = 0; i < 4; ++i) { color += texture2D(u_samplers[i], uv) * u_colors[i]; }; gl_FragColor = color; ... 通过设置u_color您可以关闭单个纹理。在您的情况下,我想您也可以执行 if (i == (int)(v_textureIndex)) { color = texture2D(...) } - gman
运行得非常好。我花了一些时间才弄清楚 gl.uniform1iv(textureLoc, [0, 1, 2, 3]); 是在设置 u_textures[0]u_textures[3],至少我认为是这样的。而且在任何时候使用的纹理数量(因此也就是纹理单元)有一个限制为32个,对吗?非常感谢。 - virtualnobi
是的,gl.uniform1iv 正在设置 u_textures[0] -> [3]。纹理单元的限制是 GPU 特定的。WebGL 至少需要 8 个。您可以调用 gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) 来查找用户机器上有多少个纹理单元。顶点着色器有自己的限制 gl.getParameter(gl.VERTEX_MAX_TEXTURE_IMAGE_UNITS),它可以为 0。 - gman
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接