为什么我的顶点着色器忽略 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
,我没有问题。我做错了什么吗?
这是上面代码的实时简化版本(屏幕上应该有绿点但实际上没有):
如果你真的在使用 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 <------------------------
}
};
顺便说一句:如果 cells
是 Uint8Array
,那么您不需要 type: gl.UNSIGNED_BYTE
。 twgl 将根据数据为 Uint8Array
.
的事实将类型设置为 gl.UNSIGNED_BYTE
您使用的库未始终默认 undefined
属性。沿着 Uint8Array
和 Int8Array
数组类型的某些路径将 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>
我的顶点着色器具有以下属性:
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
,我没有问题。我做错了什么吗?
这是上面代码的实时简化版本(屏幕上应该有绿点但实际上没有):
如果你真的在使用 twgl 那么,就像它猜测 size = 3 位置,4 颜色,2 texcoords 一样,twgl 猜测使用 normalize: true
的问题你需要告诉它 normalize: false
来自 the docs
normalize
boolean --- normalize forvertexAttribPointer
. Default istrue
if type isInt8Array
orUint8Array
otherwisefalse
.
所以,
const arrays = {
position: {
data: new Float32Array(points.flat()),
size: 2
},
color: {
data: cells,
size: 1,
type: gl.UNSIGNED_BYTE,
normalize: false, // added <------------------------
}
};
顺便说一句:如果 cells
是 Uint8Array
,那么您不需要 type: gl.UNSIGNED_BYTE
。 twgl 将根据数据为 Uint8Array
.
gl.UNSIGNED_BYTE
您使用的库未始终默认 undefined
属性。沿着 Uint8Array
和 Int8Array
数组类型的某些路径将 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>