绑定多个统一缓冲区对象

时间:2017-06-19 11:17:34

标签: glsl webgl2

使用WebGL 2,我们现在可以使用Uniform Buffer Objects。

它们看起来像一个好主意,不必将常见的制服附加到每个程序(如每个被渲染对象共有的投影和视图矩阵)。

我创建了一个帮助器类,每次我想绑定一个统一的缓冲区对象时都会调用它。

class UniformBuffer {
    constructor(gl, data, boundLocation = 0) {
        this.boundLocation = boundLocation;

        this.data = new Float32Array(data);

        this.buffer = gl.createBuffer();
        gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
        gl.bufferData(gl.UNIFORM_BUFFER, this.data, gl.DYNAMIC_DRAW);
        gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
    }

    update(gl, data, offset = 0) {
        this.data.set(data, offset);

        gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
        gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.data, 0, null);
        gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
    }
};

想要创建像这样的统一缓冲区的想法

const perScene = new UniformBuffer(gl, [
    ...vec4.create(),
    ...vec4.create(),
], 0); // and bind it to bind location 0?

const perObject = new UniformBuffer(gl, [
    ...vec4.create(),
], 1); // and bind it to bind location 1?

在我的渲染循环中,我通过调用

更新“perScene”制服
perScene.update(gl, [
    ...vec4.fromValues(1, 0, 0, 1),
], 4); // giving an offset to update only the 2nd color.

然后我会查看场景中的所有对象,我的想法是像这样更新perObject统一缓冲区

for (let i = 0; i < objects.length; i++) {
    perObject.update(gl, [
       ...vec4.fromValues(0, 0, 1, 1),
    ]);
} 

我正在谈论vec4只是为了让示例更容易,但我们的想法是在perScene和perObject上的(对象和普通矩阵)上有矩阵(投影和视图)。

在我的着色器中,我将它们声明为

uniform perScene {
    vec4 color1;
    vec4 color2;
};


uniform perModel {
    vec4 color3;
};

我这里有一个工作片段

class UniformBuffer {
    constructor(gl, data, boundLocation = 0) {
        this.boundLocation = boundLocation;

        this.data = new Float32Array(data);

        this.buffer = gl.createBuffer();
        gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
        gl.bufferData(gl.UNIFORM_BUFFER, this.data, gl.DYNAMIC_DRAW);
        gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
    }

    update(gl, data, offset = 0) {
        this.data.set(data, offset);

        gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
        gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.data, 0, null);
        gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
    }
};

const vertex = `#version 300 es

uniform perScene {
	vec4 color1;
    vec4 color2;
};

uniform perModel {
	vec4 color3;
};

in vec3 a_position;
out vec3 v_color;

void main() {
	gl_Position = vec4(a_position, 1.0);
	v_color = color1.rgb + color2.rgb; // WORKS
    // v_color = color1.rgb + color2.rgb + color3.rgb; // DOESNT WORK
}
`;

const fragment = `#version 300 es
precision highp float;
precision highp int;

in vec3 v_color;
out vec4 outColor;

void main() {
	outColor = vec4(v_color, 1.0);
}
`;

const geometry = {
    positions: [-0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0],
    indices: [0, 2, 1, 1, 2, 3],
};

const renderList = [];

// STEP 1 (create canvas)
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl2");
if (!gl) {
    console.log('no webgl2 buddy');
}

// STEP 2 (create program)
const v = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(v, vertex);
gl.compileShader(v);

const f = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(f, fragment);
gl.compileShader(f);

const program = gl.createProgram();
gl.attachShader(program, v);
gl.attachShader(program, f);
gl.linkProgram(program);

// STEP 3 (create VAO)
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const colorUniformLocation = gl.getUniformLocation(program, 'color');

const positionsBuffer = gl.createBuffer();
const indicesBuffer = gl.createBuffer();

const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

// position & indices
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(geometry.positions), gl.STATIC_DRAW);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(geometry.indices), gl.STATIC_DRAW);

// STEP 4 (create UBO)

// bound to location 0
const perScene = new UniformBuffer(gl, [
    ...vec4.create(), // color 1
    ...vec4.create(), // color 2
], 0);

// bound to location 1 ?
const perModel = new UniformBuffer(gl, [
    ...vec4.create(), // color 3
], 3);

// STEP 5 (add instances)
for (let i = 0; i < 1; i++) {
    renderList.push({
        id: i,
        vao: vao,
        program: program,
        color: [0, 1, 1],
    });
}

// STEP 6 (draw)
gl.clearColor(0, 0, 0, 0);

gl.enable(gl.DEPTH_TEST);

gl.viewport(0, 0, canvas.width, canvas.height);

perScene.update(gl, [
    ...vec4.fromValues(1, 0, 0, 1),
    ...vec4.fromValues(0, 1, 0, 1),
]);

for (let i = 0; i < renderList.length; i++) {
    const current = renderList[i];
    gl.useProgram(current.program);
    gl.bindVertexArray(current.vao);

    // update perObject
    perModel.update(gl, [
        ...vec4.fromValues(0, 0, 1, 1),
    ]);

    gl.drawElements(gl.TRIANGLES, geometry.indices.length, gl.UNSIGNED_SHORT, 0);

    // unbind
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}

console.log('compiled!');
canvas {
    background-color: black;
}
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>

我不应该看到白色方块,因为所有颜色加起来都会产生vec4(1.0, 1.0, 1.0, 1.0)吗? (jsfiddle第41行)

我做错了什么? 感谢

2 个答案:

答案 0 :(得分:5)

所以,你做错的第一件事就是你没有打电话给gl.getUniformBlockIndex。就像制服一样,你必须查询位置,或者在这种情况下查询每个块的索引。

第二件事是块制服是一个级别的间接,你需要拨打gl.uniformBlockBinding(program, uniformBlockIndex, uniformBufferIndex);

uniformBlockIndex是您从gl.getUniformBlockIndex获得的索引。 uniformBufferIndex类似于纹理单元。有N个统一缓冲区索引。您可以选择从0MAX_UNIFORM_BUFFER_BINDINGS - 1的任何缓冲区索引。

如果你有一个使用块A,B的程序和另一个使用A和C的程序,这个间接会有所帮助。在这种情况下,块A可能在2个程序中有不同的索引,但你可以从同一个uniformBufferIndex中提取它的值

请注意,此状态是每个程序状态,因此如果您计划始终对同一个统一块使用相同的统一缓冲区索引,则可以在初始化时设置它。

要拼出更多。你有一个着色器程序。它有州

var someProgram = {
  uniforms: {
    projectionMatrix: [1, 0, 0, 0, 0, ... ],  // etc
  },
  uniformBlockIndcies[  // one per uniform block
    0, 
    0,
    0,
  ],
  ...
}

接下来,您有统一的缓冲区索引,它们是全局状态

glState = {
  textureUnits: [ ... ],
  uniformBuffers: [ null, null, null ..., ], 
};

告诉程序每个统一缓冲区块,哪个统一缓冲区索引与gl.uniformBlockBinding一起使用。然后使用gl.bindBufferBasegl.bindBufferRange将缓冲区绑定到该索引。

这与告诉程序使用哪个纹理单元然后将纹理绑定到该单元非常相似。当你这样做时,在初始时或渲染时间真的取决于你。在我看来,我似乎更有可能在初始时决定我的perScene东西总是在索引1的缓冲区索引0和perModel的东西,因此我可以设置它们的程序部分(调用gl.uniformBlockBinding)at初始时间。

class UniformBuffer {
    constructor(gl, data, boundLocation = 0) {
        this.boundLocation = boundLocation;

        this.data = new Float32Array(data);

        this.buffer = gl.createBuffer();
        gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
        gl.bufferData(gl.UNIFORM_BUFFER, this.data, gl.DYNAMIC_DRAW);
        gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
    }

    update(gl, data, offset = 0) {
        this.data.set(data, offset);

        gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
        gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.data, 0, null);
        gl.bindBuffer(gl.UNIFORM_BUFFER, null);
        gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
    }
};

const vertex = `#version 300 es

uniform perScene {
	vec4 color1;
    vec4 color2;
};

uniform perModel {
	vec4 color3;
};

in vec3 a_position;
out vec3 v_color;

void main() {
	gl_Position = vec4(a_position, 1.0);
	v_color = color1.rgb + color2.rgb + color3.rgb; 
}
`;

const fragment = `#version 300 es
precision highp float;
precision highp int;

in vec3 v_color;
out vec4 outColor;

void main() {
	outColor = vec4(v_color, 1.0);
}
`;

const geometry = {
    positions: [-0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0],
    indices: [0, 2, 1, 1, 2, 3],
};

const renderList = [];

// STEP 1 (create canvas)
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl2");
if (!gl) {
    console.log('no webgl2 buddy');
}

// STEP 2 (create program)
const v = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(v, vertex);
gl.compileShader(v);

const f = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(f, fragment);
gl.compileShader(f);

const program = gl.createProgram();
gl.attachShader(program, v);
gl.attachShader(program, f);
gl.linkProgram(program);

// STEP 3 (create VAO)
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const colorUniformLocation = gl.getUniformLocation(program, 'color');

const positionsBuffer = gl.createBuffer();
const indicesBuffer = gl.createBuffer();

const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

// position & indices
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(geometry.positions), gl.STATIC_DRAW);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(geometry.indices), gl.STATIC_DRAW);

// STEP 4 (create UBO)

// bound to location 0
const perScene = new UniformBuffer(gl, [
    ...vec4.create(), // color 1
    ...vec4.create(), // color 2
], 0);

// bound to location 1 ?
const perModel = new UniformBuffer(gl, [
    ...vec4.create(), // color 3
], 1);

gl.uniformBlockBinding(program, gl.getUniformBlockIndex(program, "perScene"), perScene.boundLocation);
gl.uniformBlockBinding(program, gl.getUniformBlockIndex(program, "perModel"), perModel.boundLocation);


// STEP 5 (add instances)
for (let i = 0; i < 1; i++) {
    renderList.push({
        id: i,
        vao: vao,
        program: program,
        color: [0, 1, 1],
    });
}

// STEP 6 (draw)
gl.clearColor(0, 0, 0, 0);

gl.enable(gl.DEPTH_TEST);

gl.viewport(0, 0, canvas.width, canvas.height);

perScene.update(gl, [
    ...vec4.fromValues(1, 0, 0, 1),
    ...vec4.fromValues(0, 1, 0, 1),
]);

for (let i = 0; i < renderList.length; i++) {
    const current = renderList[i];
    gl.useProgram(current.program);
    gl.bindVertexArray(current.vao);

    // update perObject
    perModel.update(gl, [
        ...vec4.fromValues(0, 0, 1, 1),
    ]);

    gl.drawElements(gl.TRIANGLES, geometry.indices.length, gl.UNSIGNED_SHORT, 0);

    // unbind
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}

console.log('compiled!');
canvas {
    background-color: black;
}
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>

this example中有5个统一的块。

  1. 共享矩阵,例如projectionview以及viewProjection
  2. 每个模型矩阵,例如worldworldInverseTransform
  3. 每个灯光信息,例如lightPositionlightColor
  4. 有2个灯,所以第4个块类似于第3个
  5. 材料数据,如环境色,镜面反射等。
  6. 我不是说这是完美的设置。我真的不知道。但是制作一种称为“材料”的东西并在多个模型中共享该材料是很常见的,这就像perMaterial块不同于perModel块。共享照明信息也很常见。我不知道理想的设置是什么,只是指出perSceneperModel可能不足以应对相当常见的情况。

    另一件事,这一行

        // unbind
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    

    毫无意义。 ELEMENT_ARRAY_BUFFER是VAO州的一部分。

答案 1 :(得分:0)

正如gman所说,获取统一块的索引,然后将其与gl.bindBufferBase

绑定

您更新的课程应如下所示:

class UniformBuffer {
  constructor(gl, data, program, uniformName, targetIndex = 0) {

      this.data = new Float32Array(data);
      const boundLocation = gl.getUniformBlockIndex(program, uniformName);

      this.buffer = gl.createBuffer();
      gl.bindBufferBase(gl.UNIFORM_BUFFER, boundLocation, this.buffer);

      gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
      gl.bufferData(gl.UNIFORM_BUFFER, this.data, gl.DYNAMIC_DRAW);
      gl.bindBuffer(gl.UNIFORM_BUFFER, null);
  }

  update(gl, data, offset = 0) {
      this.data.set(data, offset);

      gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
      gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.data, 0, null);
      gl.bindBuffer(gl.UNIFORM_BUFFER, null);
  }

};