【问题标题】:WebGL2 render Uint16Array to canvas as an imageWebGL2 将 Uint16Array 作为图像渲染到画布
【发布时间】:2019-08-28 20:00:38
【问题描述】:

我正在尝试使用 webgl2 纹理将Uint16Array 渲染到浏览器中的图像。我有一个Uint8Array 的工作示例小提琴,并且正在努力升级到 16 位,因为 webgl 的学习曲线很陡峭。

工作 8 位小提琴(与下面的 sn-p 相同): Uint8Array http://jsfiddle.net/njxvftc9/2/ 不工作的 16 位尝试: Uint16Array http://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
&lt;canvas id="cv" width="100" height="100"&gt;&lt;/canvas&gt;

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

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

任何在 webgl2 方面有更多经验的人都可以为我指明正确的方向或在此处提供一个有效的示例小提琴吗?我的代码中是否有一个简单的错误,我是否错过了一个更大的概念,这甚至可能吗?非常感谢任何帮助!

【问题讨论】:

  • 我一直在看这个,并且取得了一些不错的进展。在考虑了关于 sn-ps 的链接之后,我可能会推迟回答,直到它变成一个 sn-p。
  • @gman 感谢 sn-p 的推荐 - 我不知道他们!我根据您的要求添加了一个 sn-p。你对代码有什么想法吗?

标签: javascript glsl webgl


【解决方案1】:

如果要将 16 位数据放入纹理中,则需要选择 16 位纹理内部格式。看看the list of internal formats,你对 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 也意味着other syntax changes。 attribute 在顶点着色器中变为 in,varying 在顶点着色器中变为 out,在片段着色器中变为 in。 gl_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
&lt;canvas id="cv" width="100" height="100"&gt;&lt;/canvas&gt;

【讨论】:

  • 帮助我(显然是你,根据你的代码)解决这类问题的一件事是检查编译器错误。只是想指出这是一件好事,因为它是一种非常有用的调试技术。
  • 您在答案中提到的代码更改都是正确的,但是您的 sn-p 仍然不能正常工作。你需要在for循环中设置img[i + 1] = i*4;和img[i + 3] = 65535;。
  • 奇怪,我最初有这些更改,但一定没有选择“保存”。感谢您指出。至于检查编译器错误,我同意 100%。我只是假设提问者正在寻找一个 minimal 示例,但如果是我,我会离开检查(就像我上面所做的那样)
  • @gman 谢谢,我对您回答的详细程度感到震惊 - 从学习的角度来看非常有帮助。这确实消除了我在 v1 和 v2 着色器语法之间的混淆,而且我自己永远不会发现这些兼容性问题。再次感谢!
  • @A.Mort 它从位置数据生成纹理坐标。这只有效,因为我们知道位置数据是一个四边形,在每个方向上从 -1 到 1,所以 * 0.5 + 0.5 使值从 0 到 1,这是我们需要参考纹理来绘制图像的值。在一般情况下,您会提供纹理坐标作为单独的属性,但对于这种特殊情况,它们不是必需的。
猜你喜欢
  • 2020-07-09
  • 2018-05-05
  • 1970-01-01
  • 2014-01-24
  • 2017-04-26
  • 2021-11-09
  • 1970-01-01
  • 1970-01-01
  • 2021-06-30
相关资源
最近更新 更多