WebGL2将Uint16Array渲染到画布上作为图像

3

我正在尝试使用WebGL2纹理在浏览器中将Uint16Array渲染成图像。我有一个工作正常的例子,使用Uint8Array,但是升级到16位时遇到了困难,因为WebGL具有陡峭的学习曲线。

工作正常的8位示例(与下面的代码片段相同):Uint8Array http://jsfiddle.net/njxvftc9/2/ 不工作的16位尝试:Uint16Arrayhttp://jsfiddle.net/njxvftc9/3/

// image data
var w = 128;
var h = 128;
var size = w * h * 4;
var img = new Uint8Array(size); // need Uint16Array
for (var i = 0; i < img.length; i += 4) {
    img[i + 0] = 255; // r
    img[i + 1] = i/64; // g
    img[i + 2] = 0; // b
    img[i + 3] = 255; // a
}

// program
var canvas = document.getElementById('cv');
var gl = canvas.getContext('webgl2');

var program = gl.createProgram();
//var color_buffer_float_16ui = gl.getExtension('EXT_color_buffer_float'); // add for 16-bit

// texture
var tex = gl.createTexture(); // create empty texture
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texImage2D(
    gl.TEXTURE_2D, // target
    0, // mip level
    gl.RGBA, // internal format -> gl.RGBA16UI
    w, h, // width and height
    0, // border
    gl.RGBA, //format -> gm.RGBA_INTEGER
    gl.UNSIGNED_BYTE, // type -> gl.UNSIGNED_SHORT
    img // texture data
);

// buffer
var buffer = gl.createBuffer();
var bufferData =  new Float32Array([
    -1, -1,
    1, -1,
    1, 1,               
    1, 1,
    -1, 1,
    -1, -1
]);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, bufferData, gl.STATIC_DRAW);

// shaders
program.vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(program.vs,
                "attribute vec4 vertex;\n" + // incoming pixel input?
                "varying vec2 pixelCoordinate;\n" + // variable used to pass position to fragment shader
                "void main(){\n" +
                " gl_Position = vertex;\n" + // set pixel output position to incoming position (pass through)
                " pixelCoordinate = vertex.xy*0.5+0.5;\n" + // set coordinate for fragment shader
                "}\n");

program.fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(program.fs,
                "precision highp float;\n" + // ?
                "uniform sampler2D tex;\n" + // ?
                "varying vec2 pixelCoordinate;\n" + // receive pixel position from vertex shader
                "void main(){\n" +
                " gl_FragColor = texture2D(tex, pixelCoordinate);\n" + // lookup color in texture image at coordinate position and set color to
                "}\n");

gl.compileShader(program.vs);
gl.compileShader(program.fs);

gl.attachShader(program,program.vs);
gl.attachShader(program,program.fs);

gl.deleteShader(program.vs);
gl.deleteShader(program.fs);

// program
gl.bindAttribLocation(program, 0, "vertex");
gl.linkProgram(program);
gl.useProgram(program);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 6); // execute program
<canvas id="cv" width="100" height="100"></canvas>

我尝试过许多其他技术,参考了规范,在着色器中尝试将其转换为浮点数,并尝试组合在此处看到的方法:Render to 16bits unsigned integer 2D texture in WebGL2,但没有成功。

我更喜欢原生js,但愿意使用twgl或three.js等库,只要输入是Uint16Array,则着色器可以输出任何格式(如float等)。

有没有更多webgl2经验的人可以指引我方向或提供一个有效的例子?我的代码里是否有简单的错误,我是否缺少更大的概念,这是否可能?非常感谢任何帮助!


我一直在寻找这个,并取得了一些进展。考虑到有关代码片段的链接,我可能会等待它被制作成代码片段后再回答。 - whydoubt
@gman 谢谢你的代码片段推荐 - 我之前不知道它们!我根据你的要求添加了一个代码片段。你对这段代码有什么想法吗? - Robot
1个回答

4

如果您想将16位数据放入纹理中,则需要选择一个16位纹理内部格式。查看内部格式列表,您的RGBA选项为:

gl.RGBA16I    // 16bit integers
gl.RGBA16UI   // 16bit unsigned integers
gl.RGBA16F    // 16bit half floats

看起来你选择了gl.RGBA16UI

为了在着色器中访问该纹理,需要添加以下行:

uniform sampler2D tex;

必须更改这个

uniform usampler2D tex;

那个语法仅在GLSL 3.00 ES中可用,因此您需要在两个着色器的顶部添加#version 300 es
切换到#version 300 es还意味着其他语法变化,如在顶点着色器中attribute变为invarying变为out,在片元着色器中varying变为ingl_FragColor消失,您必须声明自己的输出,例如out vec4 fooColor;。此外,texture2D变成了texture
您不能在整数纹理上使用过滤,因此这两行代码:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

需要更改为

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

当你从纹理中获取数据时,它将是无符号整数,因此这一行代码

gl_FragColor = texture2D(tex, pixelCoordinate);

会最终变成类似于以下内容
out vec4 fooColor;  // named this fooColor to make it clear you pick the name

void main() {
  uvec4 unsignedIntValues = texture(tex, pixelCoordinate);
  vec4 floatValues0To65535 = vec4(unsignedIntValues);  
  vec4 colorValues0To1 = floatValues0To65535 / 65535.0;
  fooColor = colorValues0To1;
}

当然,您可以将那个数学式子简化为一行。
  fooColor = vec4(texture(tex, pixelCoordinate)) / 65535.0;

例子:

// image data
var w = 128;
var h = 128;
var size = w * h * 4;
var img = new Uint16Array(size); // need Uint16Array
for (var i = 0; i < img.length; i += 4) {
    img[i + 0] = 65535; // r
    img[i + 1] = i/64 * 256; // g
    img[i + 2] = 0; // b
    img[i + 3] = 65535; // a
}

// program
var canvas = document.getElementById('cv');
var gl = canvas.getContext('webgl2');

var program = gl.createProgram();
//var color_buffer_float_16ui = gl.getExtension('EXT_color_buffer_float'); // add for 16-bit

// texture
var tex = gl.createTexture(); // create empty texture
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
    gl.TEXTURE_2D, // target
    0, // mip level
    gl.RGBA16UI, // internal format -> gl.RGBA16UI
    w, h, // width and height
    0, // border
    gl.RGBA_INTEGER, //format -> gm.RGBA_INTEGER
    gl.UNSIGNED_SHORT, // type -> gl.UNSIGNED_SHORT
    img // texture data
);

// buffer
var buffer = gl.createBuffer();
var bufferData =  new Float32Array([
    -1, -1,
    1, -1,
    1, 1,               
    1, 1,
    -1, 1,
    -1, -1
]);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, bufferData, gl.STATIC_DRAW);

// shaders
program.vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(program.vs, `#version 300 es
  in vec4 vertex; // incoming pixel input?
  out vec2 pixelCoordinate; // variable used to pass position to fragment shader
  void main(){
     gl_Position = vertex;  // set pixel output position to incoming position (pass through)
     pixelCoordinate = vertex.xy*0.5+0.5; // set coordinate for fragment shader
  }
`);

program.fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(program.fs, `#version 300 es
  precision highp float; // ?
  uniform highp usampler2D tex; // ?
  in vec2 pixelCoordinate; // receive pixel position from vertex shader
  out vec4 fooColor;
  void main() {
     uvec4 unsignedIntValues = texture(tex, pixelCoordinate);
     vec4 floatValues0To65535 = vec4(unsignedIntValues);
     vec4 colorValues0To1 = floatValues0To65535 / 65535.0;
     fooColor = colorValues0To1;
  }
`);

gl.compileShader(program.vs);
checkCompileError(program.vs);
gl.compileShader(program.fs);
checkCompileError(program.fs);

function checkCompileError(s) {
  if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
   console.error(gl.getShaderInfoLog(s));
  }
}

gl.attachShader(program,program.vs);
gl.attachShader(program,program.fs);

gl.deleteShader(program.vs);
gl.deleteShader(program.fs);

// program
gl.bindAttribLocation(program, 0, "vertex");
gl.linkProgram(program);
gl.useProgram(program);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 6); // execute program
<canvas id="cv" width="100" height="100"></canvas>


有一件事情可以帮助我(显然也能帮到你,根据你的代码),那就是检查编译器错误。我觉得这是一个很好的提示,因为它是一种非常有用的调试技巧。 - whydoubt
1
@gman 谢谢,你的答案详细程度让我大开眼界 - 从学习的角度来看非常有帮助。这真的解决了我对 v1 和 v2 着色器语法之间混淆的困惑,我自己永远不可能发现这些兼容性问题。再次感谢! - Robot
@FEA5T,你可能已经通过检查JavaScript控制台得到了一些线索。它应该至少告诉你你的纹理是不能使用的。只更改纹理运行你的片段代码会告诉你关于NEAREST问题的信息。修复这个问题后,会出现有关采样器不匹配(sampler2D vs usampler2D)的错误,因此你至少已经得到了一些提示去寻找问题所在。 - gman
@gman 我在控制台上操作时没有记录编译错误。WebGL 的一个难点就是缺乏调试能力。比如,我现在正试图从 RGBA16UI 转换到 R16UI,但我找不到任何关于 uvec4 texture(...) 查找调用中包含哪 4 个点的信息。你知道为什么单通道查找调用会返回 uvec4 吗? - Robot
1
@A.Mort 从位置数据中生成纹理坐标。这个方法仅在我们知道位置数据是一个四方形,其范围为每个方向上的 -1 到 1 所以 * 0.5 + 0.5 使值从 0 到 1,这正好是我们所需的参考纹理来绘制图像。一般情况下,您需要提供纹理坐标作为单独的属性,但对于这种特殊情况则不需要。 - gman
显示剩余5条评论

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