为什么我的顶点着色器忽略 Uint8Array 数据而不是 Float32Array 数据?

Why does my vertex shader ignore Uint8Array data but not Float32Array data?

我的顶点着色器具有以下属性:

attribute float a_color;

我有一个仅由 0 和 1 组成的数组缓冲区(WebAssembly 的内存),我通过创建 Uint8Array 来创建它的视图。但是我的顶点着色器忽略了它(没有错误,但似乎将所有内容都视为 0)。我正在使用 twgl。这是我的代码:

顶点着色器:

attribute vec2 a_position;
attribute float a_color;
uniform vec2 u_resolution;
uniform float u_point_width;
varying vec4 v_color;

vec2 normalize_coords(vec2 position) {
   float x = position[0];
   float y = position[1];
   float resx = u_resolution[0];
   float resy = u_resolution[1];
   return vec2(2.0 * x / resx - 1.0, 2.0 * y / resy - 1.0);
}

void main() {
   gl_PointSize = u_point_width;
   vec2 coords = normalize_coords(a_position);
   gl_Position = vec4(coords * vec2(1, -1), 0, 1);
   v_color = vec4(0, a_color ,0,1);
}

片段着色器:

precision highp float;

varying vec4 v_color;
void main() {
  gl_FragColor = v_color;
}

Javascript:

  const canvas = document.getElementById("c");

  const CELL_SIZE = 10;

  const gl = canvas.getContext("webgl");
  const programInfo = twgl.createProgramInfo(gl, [
    "vertex-shader",
    "fragment-shader"
  ]);

  twgl.setDefaults({ attribPrefix: "a_" });

  twgl.resizeCanvasToDisplaySize(gl.canvas, window.devicePixelRatio);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  const universe = Universe.new(
    Math.ceil(gl.canvas.width / CELL_SIZE / 2),
    Math.ceil(gl.canvas.height / CELL_SIZE / 2)
  );
  const width = universe.width();
  const height = universe.height();

  let points = [];

  for (let i = 0; i < width; i++) {
    for (let j = 0; j < height; j++) {
      points.push([i * CELL_SIZE * 2, j * CELL_SIZE * 2]);
    }
  }

  const cellsPtr = universe.cells();
  // Webassembly memory (only 0's and 1's)
  const cells = new Uint8Array(memory.buffer, cellsPtr, width * height);

  const arrays = {
    position: {
      data: new Float32Array(points.flat()),
      size: 2
    },
    color: {
      data: cells,
      size: 1,
      type: gl.UNSIGNED_BYTE
    }
  };

  const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

  const uniforms = {
    u_resolution: [gl.canvas.width, gl.canvas.height],
    u_point_width: CELL_SIZE
  };

  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.useProgram(programInfo.program);

  twgl.setUniforms(programInfo, uniforms);

  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

  twgl.drawBufferInfo(gl, bufferInfo, gl.POINTS);

  function renderLoop(time) {
    twgl.resizeCanvasToDisplaySize(gl.canvas, window.devicePixelRatio);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    universe.tick();
    const cellsPtr = universe.cells();
    const cells = new Uint8Array(
      memory.buffer,
      cellsPtr,
      width * height
    );

    const uniforms = {
      u_resolution: [gl.canvas.width, gl.canvas.height],
      u_point_width: CELL_SIZE
    };

    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.useProgram(programInfo.program);
    twgl.setUniforms(programInfo, uniforms);

    twgl.setAttribInfoBufferFromArray(gl, bufferInfo.attribs.a_color, {
      data: cells,
      size: 1,
      type: gl.UNSIGNED_BYTE
    }); // Dynamically update buffer with new colors

    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

    twgl.drawBufferInfo(gl, bufferInfo, gl.POINTS);
    requestAnimationFrame(renderLoop);
  }

  requestAnimationFrame(renderLoop);
};

如果我手动将cells转换为Float32Array,我没有问题。我做错了什么吗?

这是上面代码的实时简化版本(屏幕上应该有绿点但实际上没有):

https://codesandbox.io/s/silly-jackson-ut3wm

如果你真的在使用 twgl 那么,就像它猜测 size = 3 位置,4 颜色,2 texcoords 一样,twgl 猜测使用 normalize: true 的问题你需要告诉它 normalize: false

来自 the docs

normalize boolean --- normalize for vertexAttribPointer. Default is true if type is Int8Array or Uint8Array otherwise false.

所以,

  const arrays = {
    position: {
      data: new Float32Array(points.flat()),
      size: 2
    },
    color: {
      data: cells,
      size: 1,
      type: gl.UNSIGNED_BYTE,
      normalize: false,       // added <------------------------
    }
  };

顺便说一句:如果 cellsUint8Array,那么您不需要 type: gl.UNSIGNED_BYTE。 twgl 将根据数据为 Uint8Array.

的事实将类型设置为 gl.UNSIGNED_BYTE

您使用的库未始终默认 undefined 属性。沿着 Uint8ArrayInt8Array 数组类型的某些路径将 normalized 设置为 true

标准化属性缓冲区数据

WebGLRenderingContext.vertexAttribPointer 中的规范化参数 if true 将对单位范围内的整数进行规范化。对于有符号 -1 到 1 和无符号 0 到 1。

这导致 color 属性设置为 1 / 255 当您使用 Uint8Array 类型的数组并且不 t define the value of 规范化`

您可以在顶点着色器中将该值缩放回 1。

示例顶点着色器

attribute float color;

//... code

varying vec4 colorOut;
void main() {

    // ... code

    colorOut = vec4(0, color * 255.0, 0, 1);  // scale the color by 255
}

例子

显示 normalized 如何更改属性数据。红色未归一化,绿色已归一化。请注意,这不使用任何库。

const GL_OPTIONS = {alpha: false, depth: false, premultpliedAlpha: false, preserveDrawingBufer: true};
const CELL_SIZE = 10, GRID_SIZE = 32, GRID_PX_WIDTH = GRID_SIZE * CELL_SIZE, CELLS = GRID_SIZE ** 2;
const GL_SETUP = {
    get context() { return  canvas.getContext("webgl", GL_OPTIONS) },
    get locations() { return ["A_pos", "A_green", "A_red", "U_res"] },  
    get vertex() { return `
attribute vec2 pos;
attribute float green;
attribute float red;
uniform vec2 res;
varying vec4 colorV;
void main() {
    gl_PointSize = ${(CELL_SIZE - 2).toFixed(1)};
    gl_Position = vec4(pos / res, 0, 1);
    colorV = vec4(red, green * 255.0, 0, 1);
}`;
    },
    get fragment() { return `
precision highp float;
varying vec4 colorV;
void main() {
    gl_FragColor = colorV;
}`;
    },
    get buffers() {
        return {
            pos: { buffer: GL_SETUP.grid },
            red: { size: 1, type: gl.UNSIGNED_BYTE, normalize: false, buffer: GL_SETUP.colorsRed },
            green: { size: 1, type: gl.UNSIGNED_BYTE, normalize: true, buffer: GL_SETUP.colorsGreen }
        };
    },
    get grid() {
        var i = CELLS, buf = new Float32Array(i * 2), idx;
        while (i--) {
            idx = i << 1;
            buf[idx++]  = (i % GRID_SIZE) * CELL_SIZE - GRID_PX_WIDTH / 2;
            buf[idx] = (i / GRID_SIZE | 0) * CELL_SIZE - GRID_PX_WIDTH / 2;
        }
        return buf;
    },
    get colorsRed() {
        var i = CELLS, buf = new Uint8Array(i);
        while(i--) { buf[i] = Math.random() < 0.5 && i < CELLS / 2 ? 1 : 0 }
        return buf; 
    },    
    get colorsGreen() {
        var i = CELLS, buf = new Uint8Array(i);
        while(i--) { buf[i] = Math.random() < 0.5 && i >= CELLS / 2 ? 1 : 0 }
        return buf; 
    },    
};

const gl = GL_SETUP.context;
const shader = initShader(gl, GL_SETUP);
const W = canvas.width = innerWidth, H = canvas.height = innerHeight;
gl.viewport(0, 0, W, H);
gl.uniform2fv(shader.locations.res, new Float32Array([W / 2 , -H / 2]));
gl.drawArrays(gl.POINTS, 0, CELLS);

function compileAndAttach(gl, program, src, type = gl.VERTEX_SHADER) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, src);
    gl.compileShader(shader);   
    gl.attachShader(program, shader);
}
function createShader(gl, settings) {
    const locations = {}, program = gl.createProgram();
    compileAndAttach(gl, program, settings.vertex);
    compileAndAttach(gl, program, settings.fragment, gl.FRAGMENT_SHADER);
    gl.linkProgram(program);
    gl.useProgram(program);
    for(const desc of settings.locations) {
        const [type, name] = desc.split("_");
        locations[name] = gl[`get${type==="A" ? "Attrib" : "Uniform"}Location`](program, name);
    }
    return {program, locations, gl};
}
function initShader(gl, settings) {
    const shader = createShader(gl, settings);
    for (const [name, data] of Object.entries(settings.buffers)) {
        const {use = gl.STATIC_DRAW, type = gl.FLOAT, buffer, size = 2, normalize = false} = data;
        gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
        gl.bufferData(gl.ARRAY_BUFFER, buffer, use);
        gl.enableVertexAttribArray(shader.locations[name]);
        gl.vertexAttribPointer(shader.locations[name], size, type, normalize, 0, 0);
    }
    return shader;
}
body { padding: 0px }
canvas {
  position: absolute;
  top: 0px;
  left: 0px;
}
<canvas id="canvas"></canvas>