如何将一个大矩阵发送到着色器?

3
我希望在顶点着色器中发送一个大于4*4(mat4)的矩阵。我想将矩阵放入纹理中,然后将纹理发送到着色器中。
问题是我不知道具体该如何操作。您能否给我提供一个基本示例,演示如何将矩阵设置到纹理中,并如何在着色器中获取纹理中的元素?
以下是我的代码的一些部分:假设有两个矩阵:m1和m2:
r1 = m1.rows,
r2 = m2.rows,
c1 = m1.cols,
c2 = m2.cols,
d1 = m1.data,
d2 = m2.data;

将数据放入纹理中:
count = Math.max(r1, r2) * Math.max(c1, c2);
var texels = new Float32Array(3 * count); // RGB
        /* same dimensions for both matrices */
        if (r1 == r2 && c1 == c2) {
            /* put m1 in red channel and m2 in green channel */
            var i = 0, index1 = 0, index2 = 0;
            do {
                texels[i++] = d1[index1++];
                texels[i++] = d2[index2++];
                i++; // skip blue channel
            } while (--count);
        } else {
            var index, row = 0, col = 0;
            for (index = 0; index < r1 * c1; index++) {
                texels[index * 3] = d1[index];
            }

            for (index = 0; index < r2 * c2; index++) {
                texels[index * 3 + 1] = d2[index];
            }
        }

制作纹理:
var texture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, Math.max(m1.c, m2.c), Math.max(m1.r, m2.r), 0, gl.RGB, gl.FLOAT, texels);

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

var sampler = gl.getUniformLocation(program, "usampler");
gl.uniform1i(sampler, 0);

顶点着色器:

#ifdef GL_ES 
    precision highp float; 
#endif 
attribute vec3 a_position;
attribute vec2 a_texcoord;
varying vec2   vTex;
void main(void)
{
gl_Position = vec4(a_position, 1.0);
vTex = a_texcoord;
}

片元着色器:

#ifdef GL_ES 
    precision highp float; 
#endif 
 
   // passed in from the vertex shader. 
    varying vec2      vTex;         // row, column to calculate 
    uniform sampler2D usampler;     
    uniform int       uLength;      
    uniform float     uStepS;       // increment across source texture 
    uniform float     uStepT;       // increment down source texture 
    uniform float     uOutRows;     
    uniform float     uOutCols;     
      
    // sum row r x col c 
    float sumrowcol(float row, float col) { 
        float sum = 0.;             // sum 
        float ss = 0.;              // column on source texture 
        float tt = 0.;              // row on source texture 
        float r = row*uStepT;       // moving texture coordinate 
        float c = col*uStepS;       // moving texture coordinate 
        for (int pos=0; pos<2048; ++pos) { 
            if(pos >= uLength) break; // stop when we multiple a row by a column 
            float m1 = texture2D(usampler,vec2(r,ss)).r; 
            float m2 = texture2D(usampler,vec2(tt,c)).g; 
            sum += (m1*m2); 
            ss += uStepS; 
            tt += uStepT; 
        } 
        return sum; 
    } 
      
   float shift_right (float v, float amt) { 
       v = floor(v) + 0.5; 
       return floor(v / exp2(amt)); 
   }
      
   float shift_left (float v, float amt) { 
       return floor(v * exp2(amt) + 0.5); 
   }
      
   float mask_last (float v, float bits) {
       return mod(v, shift_left(1.0, bits)); 
   }
      
   float extract_bits (float num, float from, float to) { 
       from = floor(from + 0.5); to = floor(to + 0.5); 
       return mask_last(shift_right(num, from), to - from); 
   }
      
   vec4 encode_float (float val) { 
       if (val == 0.0) return vec4(0, 0, 0, 0); 
       float sign = val > 0.0 ? 0.0 : 1.0; 
       val = abs(val); 
       float exponent = floor(log2(val)); 
       float biased_exponent = exponent + 127.0; 
       float fraction = ((val / exp2(exponent)) - 1.0) * 8388608.0; 
       float t = biased_exponent / 2.0; 
       float last_bit_of_biased_exponent = fract(t) * 2.0; 
       float remaining_bits_of_biased_exponent = floor(t); 
       float byte4 = extract_bits(fraction, 0.0, 8.0) / 255.0; 
       float byte3 = extract_bits(fraction, 8.0, 16.0) / 255.0; 
       float byte2 = (last_bit_of_biased_exponent * 128.0 + extract_bits(fraction, 16.0, 23.0)) / 255.0; 
       float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0; 
       return vec4(byte1, byte2, byte3, byte4); 
    } 
    void main(void) { 
          
        // get the implied row and column from .s and .t of passed texel 
        float col = floor((vTex.s*uOutRows)); 
        float row = floor((vTex.t*uOutCols));    
 
        // sum row x col for the passed pixel 
        float v = sumrowcol(row,col); 
 
        gl_FragColor = encode_float(v); 
    }

你是使用桌面OpenGL还是WebGL?你使用哪一个会改变答案。 - Nicol Bolas
矩阵需要多大?此外,请记住,将纹理传递到顶点着色器需要 GPU 的所谓 TVF(纹理顶点提取)功能。 - hidefromkgb
2
你可以使用 uniform float m[25] 来表示一个 5x5 的矩阵。 - vallentin
@Nicol Bolas 我正在使用WebGL。 - MadalinaCi
显示剩余2条评论
2个回答

1
这是你需要做的事情:
  1. 创建一个最大可能大小的2D纹理,因为重新分配纹理比重新分配RAM难得多。
  2. 根据每个像素的通道数设置纹理类型为GL_REDGL_RGGL_RGBGL_RGBA。通道类型应该是GL_FLOAT
  3. 将新创建的纹理传递给着色器,然后在算法需要时每次更新它。
  4. 帮助更新2D纹理中任意矩形区域的函数是glTexSubImage2D
  5. 还要注意最好将放大模式设置为GL_NEAREST,因为除了texelFetch族之外的任何纹理采样函数都会近似邻近颜色,这很可能不是你想要的。
关于示例代码...哪种语言对您更可取?

我更喜欢JavaScript。谢谢你的帮助。我也尝试了类似于你说的方法,但可能是因为我在某个地方做错了什么,因为当我尝试获取存储在纹理中的元素时,它们不是我预期的那样。 - MadalinaCi
@MadalinaCi,我可以看一下你的代码吗?我几乎确定所有问题的根源都是某些纹理参数设置不正确。 - hidefromkgb
这个问题被标记为“WebGL”,这意味着你的大部分答案都不适用。 - gman
@gman,WebGL没有适当的值吗?它有。好的,它们的名称不同(确切地说是gl.R32Fgl.RG32Fgl.RGB32Fgl.RGBA32F),但这就是我承诺帮助纠正代码的原因。WebGL在着色器中也有纹理采样器,以及texSubimage。那么,哪些部分是错误的呢? - hidefromkgb
这是WebGL2而不是WebGL。WebGL没有任何gl.XX32F。WebGL也没有textureFetch,只有WebGL2。 - gman
显示剩余3条评论

1

纹理就是一个由1到4个通道的2D数据数组。对于矩阵,您可能需要一个浮点纹理。浮点纹理支持是可选的,但很常见。

ext = gl.getExtension("OES_texture_float");
if (!ext) {
   // tell the user they can't run your app

所以,你把你的数据放在纹理中。
const width = 5;
const height = 5;
const data = new Float32Array(width * height);
data[0] = ???
data[1] = ???

现在制作一张纹理并上传它。
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
const level = 0;
gl.texImage2D(gl.TEXTURE_2D, level, gl.LUMINANCE, width, height 0,
              gl.LUMINANCE, gl.FLOAT, data);
// set the filtering so values don't get mixed
gl.texParameteri(gl.TEXTURE2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

LUMINANCE 是一种单通道纹理。

在着色器中读取它时,您需要知道其尺寸,在WebGL1中,您需要自己传递尺寸。

uniform vec2 dataTextureSize;
uniform sampler2D dataTexture;

然后你可以像这样获取任何元素。
float row = ??
float col = ??
vec2 uv = (vec2(col, row) + .5) / dataTextureSize;
float value = texture2D(dataTexture, uv).r;  // just the red channel

WebGL通常不允许您选择写入位置(这需要计算着色器,而这需要几个版本)。因此,您需要构造应用程序,以便按顺序处理每个目标行和列,或者使用一些技巧,例如使用gl.POINT并设置gl_Position来选择特定的输出像素。

您可以在此处查看示例

如果您使用WebGL2,则不需要扩展,可以使用texelFetch从纹理中获取特定值。

float value = texelFetch(dataTexture, ivec2(col, row));

也许更适合使用gl.R32F作为您的内部格式

const level = 0;
gl.texImage2D(gl.TEXTURE_2D, level, gl.R32F, width, height 0,
              gl.RED, gl.FLOAT, data);

不是textureFetch,而是texelFetch。看看答案错了多容易? - hidefromkgb
感谢您的编辑。我并不是想否定您的答案,但是一个提出这种问题的人可能无法在OpenGL和WebGL之间进行翻译,因此似乎任何答案都需要用WebGL术语来解释。 - gman
@gman,我更新了问题并附上了部分代码。你可以看一下告诉我哪里出错了吗? - MadalinaCi

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