在 WebGL2 中渲染为 16 位无符号整数 2D 纹理

Render to 16bits unsigned integer 2D texture in WebGL2

如 WebGL 2 官方规范或文档 (look here) 中所述,gl.RGBA16UI 内部尺寸格式是一种颜色可渲染格式。这意味着我应该能够渲染到 RGBA16UI 纹理。

我可以轻松地用 UInt16Array 填充纹理然后使用它。但是我无法使用着色器将纹理渲染填充到其中。然后,当我在下一个着色器中对值进行采样时,我只会得到零。

有没有人已经成功地使用 WebGL 2 渲染到无符号整数纹理?我将不胜感激,我已经玩了几个星期的纹理格式,但我不知道我在这里做错了什么......而且我没有在关于这个主题的其他 SO 问题中发现我的错误。

您可以在下面的代码片段中找到完整的代码。左侧打印的 6 种颜色是显示填充有 UInt16Array 的 RGBA16UI 纹理的结果。在右侧,如果渲染到 RGBA16UI 纹理成功,我们应该得到相同的结果。

[编辑] 那只是一个愚蠢的错字。我更新了我的代码并接受了 gman 答案作为如何在 WebGL 2 中渲染为无符号整数纹理的两个功能示例。

// Position/UV data
var displayPlanePositions = new Float32Array([
  -1, 1,
  -1, -1,
  0, -1,

  -1, 1,
  0, -1,
  0, 1
]);
var displayPlaneRTTPositions = new Float32Array([
  0, 1,
  0, -1,
  1, -1,

  0, 1,
  1, -1,
  1, 1
]);
var effectPlanePositions = new Float32Array([
  -1, 1,
  -1, -1,
  1, -1,

  -1, 1,
  1, -1,
  1, 1
]);
var displayPlaneUVs = new Float32Array([
  0, 1,
  0, 0,
  1, 0,

  0, 1,
  1, 0,
  1, 1
]);
var displayPlaneRTTUVs = new Float32Array([
  0, 1,
  0, 0,
  1, 0,

  0, 1,
  1, 0,
  1, 1
]);

// Texture data
var pixelsRGBA16UI = new Uint16Array([
  65535, 0, 0, 65535,
  0, 65535, 0, 65535,
  0, 0, 65535, 65535,
  65535, 65535, 0, 65535,
  0, 65535, 65535, 65535,
  65535, 65535, 65535, 65535
]);

// Shaders
var displayPlane16UIVertexShaderSource =
 `#version 300 es

  in vec2 position;
  in vec2 uv;
  out vec2 vUV;

  void main()
  {
    vUV = uv;
    gl_Position = vec4(position, 1.0, 1.0);
  }`;
var displayPlane16UIFragmentShaderSource =
 `#version 300 es
  precision highp float;
  precision highp usampler2D;

  uniform usampler2D sampler;

  in vec2 vUV;
  out vec4 color;

  void main()
  {
    uvec4 samplerUIntColor = texture(sampler, vUV);
    vec4 samplerFloatColor = vec4(samplerUIntColor) / 65535.0;
    color = samplerFloatColor;
  }`;
var effectPlane16UIVertexShaderSource =
 `#version 300 es

  in vec2 position;

  void main(void) {
    gl_Position = vec4(position, 1.0, 1.0);
  }`;
var effectPlane16UIFragmentShaderSource =
 `#version 300 es
  precision highp float;

  out uvec4 color;

  void main(void) {
    if (gl_FragCoord.x == 0.5) {
      if (gl_FragCoord.y == 0.5) {
        color = uvec4(65535u, 0.0, 0.0, 65535u);
      } else {
        color = uvec4(65535u, 65535u, 0.0, 65535u);
      }
    } else if (gl_FragCoord.x == 1.5) {
      if (gl_FragCoord.y == 0.5) {
        color = uvec4(0.0, 65535u, 0.0, 65535u);
      } else {
        color = uvec4(0.0, 65535u, 65535u, 65535u);
      }
    } else {
      if (gl_FragCoord.y == 0.5) {
        color = uvec4(0.0, 0.0, 65535u, 65535u);
      } else {
        color = uvec4(65535u, 65535u, 65535u, 65535u);
      }
    }
  }`;
var displayPlaneRTT16UIVertexShaderSource =
 `#version 300 es

  in vec2 position;
  in vec2 uv;
  out vec2 vUV;

  void main(void) {
    vUV = uv;
    gl_Position = vec4(position, 1.0, 1.0);
  }`;
var displayPlaneRTT16UIFragmentShaderSource =
 `#version 300 es
  precision highp float;
  precision highp usampler2D;

  uniform usampler2D sampler;

  in vec2 vUV;
  out vec4 color;

  void main(void) {
    uvec4 samplerUIntColor = texture(sampler, vUV);
    vec4 samplerFloatColor = vec4(samplerUIntColor) / 65535.0;
    color = samplerFloatColor;
  }`;
  
// Create and get context
var canvas16UI = document.getElementById('webgl2-opti-tests-16ui');
var gl16UI = canvas16UI.getContext('webgl2');
canvas16UI.width = 512;
canvas16UI.height = 512;

// Get extension
var color_buffer_float_16ui = gl16UI.getExtension('EXT_color_buffer_float');

// Clear canvas
gl16UI.clearColor(0.0, 0.0, 0.0, 1.0);
gl16UI.clear(gl16UI.COLOR_BUFFER_BIT);

var log;


/* DISPLAY PLANE */

// Create Program
var displayPlane16UIVertexShader = gl16UI.createShader(gl16UI.VERTEX_SHADER);
var displayPlane16UIFragmentShader = gl16UI.createShader(gl16UI.FRAGMENT_SHADER);
var displayPlane16UIProgram = gl16UI.createProgram();
gl16UI.shaderSource(displayPlane16UIVertexShader, displayPlane16UIVertexShaderSource);
gl16UI.compileShader(displayPlane16UIVertexShader);
gl16UI.attachShader(displayPlane16UIProgram, displayPlane16UIVertexShader);
gl16UI.shaderSource(displayPlane16UIFragmentShader, displayPlane16UIFragmentShaderSource);
gl16UI.compileShader(displayPlane16UIFragmentShader);
gl16UI.attachShader(displayPlane16UIProgram, displayPlane16UIFragmentShader);
gl16UI.linkProgram(displayPlane16UIProgram);
log = gl16UI.getProgramInfoLog(displayPlane16UIProgram);
if (log) {
    console.log(log);
}

log = gl16UI.getShaderInfoLog(displayPlane16UIVertexShader);
if (log) {
    console.log("Vertex Shader", log);
}

log = gl16UI.getShaderInfoLog(displayPlane16UIFragmentShader);
if (log) {
    console.log("Fragment Shader", log);
}
gl16UI.useProgram(displayPlane16UIProgram);

// Get attribute locations
var displayPlane16UIPositionAttributeLocation = gl16UI.getAttribLocation(displayPlane16UIProgram, "position");
var displayPlane16UIUVAttributeLocation = gl16UI.getAttribLocation(displayPlane16UIProgram, "uv");

// Get uniform locations
var displayPlane16UISamplerLocation = gl16UI.getUniformLocation(displayPlane16UIProgram, "sampler");

// Create and bind VAO
var displayPlane16UIVAO = gl16UI.createVertexArray();
gl16UI.bindVertexArray(displayPlane16UIVAO);

// Create and bind Position Buffer
var displayPlane16UIPositionBuffer = gl16UI.createBuffer();
gl16UI.bindBuffer(gl16UI.ARRAY_BUFFER, displayPlane16UIPositionBuffer);
gl16UI.bufferData(gl16UI.ARRAY_BUFFER, displayPlanePositions, gl16UI.STATIC_DRAW);
gl16UI.enableVertexAttribArray(displayPlane16UIPositionAttributeLocation);
gl16UI.vertexAttribPointer(displayPlane16UIPositionAttributeLocation, 2, gl16UI.FLOAT, false, 0, 0);

// Create and bind UV Buffer
var displayPlane16UIUVBuffer = gl16UI.createBuffer();
gl16UI.bindBuffer(gl16UI.ARRAY_BUFFER, displayPlane16UIUVBuffer);
gl16UI.bufferData(gl16UI.ARRAY_BUFFER, displayPlaneUVs, gl16UI.STATIC_DRAW);
gl16UI.enableVertexAttribArray(displayPlane16UIUVAttributeLocation);
gl16UI.vertexAttribPointer(displayPlane16UIUVAttributeLocation, 2, gl16UI.FLOAT, true, 0, 0);

// Create and bind texture to display
var displayPlane16UITexture = gl16UI.createTexture();
gl16UI.activeTexture(gl16UI.TEXTURE0 + 0);
gl16UI.bindTexture(gl16UI.TEXTURE_2D, displayPlane16UITexture);

gl16UI.pixelStorei(gl16UI.UNPACK_ALIGNMENT, 1);
gl16UI.texImage2D(gl16UI.TEXTURE_2D, 0, gl16UI.RGBA16UI, 3, 2, 0, gl16UI.RGBA_INTEGER, gl16UI.UNSIGNED_SHORT, pixelsRGBA16UI);

gl16UI.texParameteri(gl16UI.TEXTURE_2D, gl16UI.TEXTURE_MIN_FILTER, gl16UI.NEAREST);
gl16UI.texParameteri(gl16UI.TEXTURE_2D, gl16UI.TEXTURE_MAG_FILTER, gl16UI.NEAREST);
gl16UI.texParameteri(gl16UI.TEXTURE_2D, gl16UI.TEXTURE_WRAP_S, gl16UI.CLAMP_TO_EDGE);
gl16UI.texParameteri(gl16UI.TEXTURE_2D, gl16UI.TEXTURE_WRAP_T, gl16UI.CLAMP_TO_EDGE);

// Bind uniforms
gl16UI.uniform1i(displayPlane16UISamplerLocation, 0);

// Execute program
gl16UI.viewport(0, 0, gl16UI.canvas.width, gl16UI.canvas.height);
gl16UI.drawArrays(gl16UI.TRIANGLES, 0, 6);


/* EFFECT PLANE */

// Create and link Program
var effectPlane16UIVertexShader = gl16UI.createShader(gl16UI.VERTEX_SHADER);
var effectPlane16UIFragmentShader = gl16UI.createShader(gl16UI.FRAGMENT_SHADER);
var effectPlane16UIProgram = gl16UI.createProgram();
gl16UI.shaderSource(effectPlane16UIVertexShader, effectPlane16UIVertexShaderSource);
gl16UI.compileShader(effectPlane16UIVertexShader);
gl16UI.attachShader(effectPlane16UIProgram, effectPlane16UIVertexShader);
gl16UI.shaderSource(effectPlane16UIFragmentShader, effectPlane16UIFragmentShaderSource);
gl16UI.compileShader(effectPlane16UIFragmentShader);
gl16UI.attachShader(effectPlane16UIProgram, effectPlane16UIFragmentShader);
gl16UI.linkProgram(effectPlane16UIProgram);
log = gl16UI.getProgramInfoLog(effectPlane16UIProgram);
if (log) {
    console.log(log);
}

log = gl16UI.getShaderInfoLog(effectPlane16UIVertexShader);
if (log) {
    console.log("VERTEX SHADER", log);
}

log = gl16UI.getShaderInfoLog(effectPlane16UIFragmentShader);
if (log) {
    console.log("FRAGMENT SHADER", log);
}
gl16UI.useProgram(effectPlane16UIProgram);

// Get attribute locations
var effectPlane16UIPositionAttributeLocation = gl16UI.getAttribLocation(effectPlane16UIProgram, "position");

// Create and bind VAO
var effectPlane16UIVAO = gl16UI.createVertexArray();
gl16UI.bindVertexArray(effectPlane16UIVAO);

// Create and bind Position Buffer
var effectPlane16UIPositionBuffer = gl16UI.createBuffer();
gl16UI.bindBuffer(gl16UI.ARRAY_BUFFER, effectPlane16UIPositionBuffer);
gl16UI.bufferData(gl16UI.ARRAY_BUFFER, effectPlanePositions, gl16UI.STATIC_DRAW);
gl16UI.enableVertexAttribArray(effectPlane16UIPositionAttributeLocation);
gl16UI.vertexAttribPointer(effectPlane16UIPositionAttributeLocation, 2, gl16UI.FLOAT, false, 0, 0);

// Create and bind target texture
var effectPlane16UITexture = gl16UI.createTexture();
gl16UI.bindTexture(gl16UI.TEXTURE_2D, effectPlane16UITexture);

gl16UI.pixelStorei(gl16UI.UNPACK_ALIGNMENT, 1);
gl16UI.texImage2D(gl16UI.TEXTURE_2D, 0, gl16UI.RGBA16UI, 3, 2, 0, gl16UI.RGBA_INTEGER, gl16UI.UNSIGNED_SHORT, null);

gl16UI.texParameteri(gl16UI.TEXTURE_2D, gl16UI.TEXTURE_MIN_FILTER, gl16UI.NEAREST);
gl16UI.texParameteri(gl16UI.TEXTURE_2D, gl16UI.TEXTURE_MAG_FILTER, gl16UI.NEAREST);
gl16UI.texParameteri(gl16UI.TEXTURE_2D, gl16UI.TEXTURE_WRAP_S, gl16UI.CLAMP_TO_EDGE);
gl16UI.texParameteri(gl16UI.TEXTURE_2D, gl16UI.TEXTURE_WRAP_T, gl16UI.CLAMP_TO_EDGE);

// Create and bind the framebuffer
var framebuffer = gl16UI.createFramebuffer();
gl16UI.bindFramebuffer(gl16UI.FRAMEBUFFER, framebuffer);

// Attach the texture as the first color attachment
gl16UI.framebufferTexture2D(gl16UI.FRAMEBUFFER, gl16UI.COLOR_ATTACHMENT0, gl16UI.TEXTURE_2D, effectPlane16UITexture, 0);
console.log("Render to RGBA16UI Texture:", gl16UI.checkFramebufferStatus(gl16UI.FRAMEBUFFER) === 36053 ? "FRAMEBUFFER_COMPLETE" : "FRAMEBUFFER_INCOMPLETE");

// Execute program
gl16UI.viewport(0, 0, 3, 2);
gl16UI.drawArrays(gl16UI.TRIANGLES, 0, 6);

// Unbind framebuffer
gl16UI.bindFramebuffer(gl16UI.FRAMEBUFFER, null);


/* DISPLAY PLANE RTT */

// Create Program
var displayPlaneRTT16UIVertexShader = gl16UI.createShader(gl16UI.VERTEX_SHADER);
var displayPlaneRTT16UIFragmentShader = gl16UI.createShader(gl16UI.FRAGMENT_SHADER);
var displayPlaneRTT16UIProgram = gl16UI.createProgram();
gl16UI.shaderSource(displayPlaneRTT16UIVertexShader, displayPlaneRTT16UIVertexShaderSource);
gl16UI.compileShader(displayPlaneRTT16UIVertexShader);
gl16UI.attachShader(displayPlaneRTT16UIProgram, displayPlaneRTT16UIVertexShader);
gl16UI.shaderSource(displayPlaneRTT16UIFragmentShader, displayPlaneRTT16UIFragmentShaderSource);
gl16UI.compileShader(displayPlaneRTT16UIFragmentShader);
gl16UI.attachShader(displayPlaneRTT16UIProgram, displayPlaneRTT16UIFragmentShader);
gl16UI.linkProgram(displayPlaneRTT16UIProgram);
log = gl16UI.getProgramInfoLog(displayPlaneRTT16UIProgram);
if (log) {
    console.log(log);
}

log = gl16UI.getShaderInfoLog(displayPlaneRTT16UIVertexShader);
if (log) {
    console.log("VERTEX SHADER", log);
}

log = gl16UI.getShaderInfoLog(displayPlaneRTT16UIFragmentShader);
if (log) {
    console.log("FRAGMENT SHADER", log);
}
gl16UI.useProgram(displayPlaneRTT16UIProgram);

// Get attribute locations
var displayPlaneRTT16UIPositionAttributeLocation = gl16UI.getAttribLocation(displayPlaneRTT16UIProgram, "position");
var displayPlaneRTT16UIUVAttributeLocation = gl16UI.getAttribLocation(displayPlaneRTT16UIProgram, "uv");

// Get uniform locations
var displayPlaneRTT16UISamplerLocation = gl16UI.getUniformLocation(displayPlaneRTT16UIProgram, "sampler");

// Create and bind VAO
var displayPlaneRTT16UIVAO = gl16UI.createVertexArray();
gl16UI.bindVertexArray(displayPlaneRTT16UIVAO);

// Create and bind Position Buffer
var displayPlaneRTT16UIPositionBuffer = gl16UI.createBuffer();
gl16UI.bindBuffer(gl16UI.ARRAY_BUFFER, displayPlaneRTT16UIPositionBuffer);
gl16UI.bufferData(gl16UI.ARRAY_BUFFER, displayPlaneRTTPositions, gl16UI.STATIC_DRAW);
gl16UI.enableVertexAttribArray(displayPlaneRTT16UIPositionAttributeLocation);
gl16UI.vertexAttribPointer(displayPlaneRTT16UIPositionAttributeLocation, 2, gl16UI.FLOAT, false, 0, 0);

// Create and bind UV Buffer
var displayPlaneRTT16UIUVBuffer = gl16UI.createBuffer();
gl16UI.bindBuffer(gl16UI.ARRAY_BUFFER, displayPlaneRTT16UIUVBuffer);
gl16UI.bufferData(gl16UI.ARRAY_BUFFER, displayPlaneRTTUVs, gl16UI.STATIC_DRAW);
gl16UI.enableVertexAttribArray(displayPlaneRTT16UIUVAttributeLocation);
gl16UI.vertexAttribPointer(displayPlaneRTT16UIUVAttributeLocation, 2, gl16UI.FLOAT, true, 0, 0);

// Bind texture to display
gl16UI.activeTexture(gl16UI.TEXTURE0 + 0);
gl16UI.bindTexture(gl16UI.TEXTURE_2D, effectPlane16UITexture);

// Bind uniforms
gl16UI.uniform1i(displayPlaneRTT16UISamplerLocation, 0);

// Execute program
gl16UI.viewport(0, 0, gl16UI.canvas.width, gl16UI.canvas.height);
gl16UI.drawArrays(gl16UI.TRIANGLES, 0, 6);
<!DOCTYPE html>

<html>
<head>
  <meta charset="UTF-8" />
  <title>RGBA16UI RenderTarget</title>
</head>

<body>
  <canvas id="webgl2-opti-tests-16ui" width="512" height="512" style="position: absolute; z-index: 1000;"></canvas>
</body>
</html>

感谢阅读。

抱歉,我发现很难阅读您的代码。也许 make an mcve next time?

无论如何,这是一个工作示例。当您只是尝试调试纹理或渲染时,最简单的方法是只绘制一个点。然后你不需要任何属性或顶点数组对象或缓冲区。由于只有一种纹理,因此您也不需要设置采样器制服。

function main() {
  const gl = document.querySelector("canvas").getContext("webgl2");
  if (!gl) {
    return alert("need webgl2");
  }
  
  const vs = `
  #version 300 es
  void main() {
    // use a point as it's easier
    gl_PointSize = 300.0;   // because the canvas is 300x150
    gl_Position = vec4(0, 0, 0, 1);
  }
  `;
  
  const uintFS = `
  #version 300 es
  precision highp float;
  out uvec4 color;
  void main() {
    // will fill texture with values from 0 to 30000
    // if the texture is 300x100 and we're rendering
    // to the entire texture
    color = uvec4(gl_FragCoord.xy, 0, 300) * 100u;
  }
  `;
  
  const uintToFloatFS = `
  #version 300 es
  precision highp float;
  uniform highp usampler2D tex;
  out vec4 color;
  void main() {
    uvec4 data = texture(tex, gl_PointCoord.xy);
    color = vec4(data) / 30000.0;
  }
  `;
  
  // compile shaders 
  const renderUintPrg = twgl.createProgram(gl, [vs, uintFS]);
  const uintToFloatPrg = twgl.createProgram(gl, [vs, uintToFloatFS]);

  // make an 300x150 RGBA16UI texture and attach to framebuffer
  const fbi = twgl.createFramebufferInfo(gl, [
    {internalFormat: gl.RGBA16UI, minMag: gl.NEAREST, },
  ], 300, 150);
  
  // bind framebuffer
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbi.framebuffer);
  
  gl.useProgram(renderUintPrg);
  
  gl.drawArrays(gl.POINTS, 0, 1);
  
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);

  gl.useProgram(uintToFloatPrg);
  
  gl.drawArrays(gl.POINTS, 0, 1);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>