绑定多个统一缓冲区对象 [英] Bind multiple Uniform Buffer Objects

查看:135
本文介绍了绑定多个统一缓冲区对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用WebGL 2,我们现在可以使用统一缓冲区对象.

With WebGL 2 we now can play with Uniform Buffer Objects.

它们看起来是个好主意,不必在每个程序中都附加通用的制服(例如,对于要渲染的每个对象通用的投影和视图矩阵).

They look like a great idea, not having to attach common uniforms to every single program (like projection and view matrices that are common to every object being rendered).

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

I created an helper class which I call every time I want to bind a uniform buffer object.

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);
    }
};

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

The idea if to create the uniform buffers like this

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?

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

In my render loop, I then update the "perScene" uniforms by calling

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

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

Then I'll look through all the objects in the scene and my idea is to update the perObject uniform buffer like this

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

我在谈论vec4只是为了简化示例,但想法是在perScene上具有矩阵(投影和视图),在perObject上具有矩阵(对象和法线).

I'm talking about vec4 just to make the example easier, but the idea is to have matrices (projection and view) on the perScene, and (object and normal matrices) on the perObject.

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

In my shader I have them declared as

uniform perScene {
    vec4 color1;
    vec4 color2;
};


uniform perModel {
    vec4 color3;
};

我在这里有一个有效的代码段

I have a working snippet here

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行)

Shouldn't I be seeing a white square since all colours added up result in a vec4(1.0, 1.0, 1.0, 1.0)? (jsfiddle line 41)

我做错了什么? 谢谢

推荐答案

因此,您做错的第一件事是您没有呼叫gl.getUniformBlockIndex.就像制服一样,您必须查询位置或在这种情况下每个块的索引.

So, the first thing you're doing wrong is you're not calling gl.getUniformBlockIndex. Just like uniform you have to query the location or in this case the index of each block.

第二件事是,块状制服是间接一级的,您需要调用gl.uniformBlockBinding(program, uniformBlockIndex, uniformBufferIndex);

The second thing is block uniforms are indirected one level and you need to call gl.uniformBlockBinding(program, uniformBlockIndex, uniformBufferIndex);

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

uniformBlockIndex is the index you got from gl.getUniformBlockIndex. uniformBufferIndex similar to a texture unit. There are N uniform buffer indices. You can choose any buffer index from 0 to MAX_UNIFORM_BUFFER_BINDINGS - 1.

如果您有一个使用块A,B的程序而另一个使用块A和C的程序,则此间接方法会有所帮助.在这种情况下,块A在两个程序中可能具有不同的索引,但是您可以从相同的UniformBufferIndex中提取其值

This indirection helps if you have one program that uses blocks A, B and another that uses A and C. In this case block A might have a different index in the 2 programs but you have it pull its values from the same uniformBufferIndex.

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

Note that this state is per program state so can probably set it at init time if you plan to always use the same uniform buffer index for the same uniform block.

进一步说明.您有一个着色器程序.它有状态

To spell it out even more. You have a shader program. It has state

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

接下来,您将获得全局状态的统一缓冲区索引

Next you have uniform buffer indices which are global state

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

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

You tell the program for each uniform buffer block, which uniform buffer index to use with gl.uniformBlockBinding. You then bind a buffer to that index with gl.bindBufferBase or gl.bindBufferRange.

非常类似于告诉程序要使用哪个纹理单元,然后将纹理绑定到该单元.当您执行此操作时,实际上是在初始化时间还是在渲染时间取决于您.在我看来,似乎更有可能在初始化时决定我的perScene填充始终位于缓冲区索引0上,而perModel填充始终位于索引1上,因此我可以在初始化时将它们设置为程序部分(对gl.uniformBlockBinding的调用)

It's very similar to telling a program which texture unit to use and then binding a texture to that unit. When you do this, at init time or render time is really up to you. In my mind it seems more likely I could decide at init time that my perScene stuff is always on buffer index 0 and perModel stuff at index 1 and therefore I could set them up the program parts (the calls to gl.uniformBlockBinding) at init time.

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>

此示例中,有5个统一块.

  1. projectionviewviewProjection
  2. 之类的共享矩阵
  3. 每个模型矩阵,例如worldworldInverseTransform
  4. 每个灯光信息,例如lightPositionlightColor.
  5. 有2个灯,所以第4个区块与第3个相似
  6. 诸如环境颜色,镜面反射性等材料数据.
  1. the shared matrices like projection and view and viewProjection
  2. the per model matrices like world and worldInverseTransform
  3. the per light info like lightPosition and lightColor.
  4. There are 2 lights so the 4th block is similar to the 3rd
  5. the material data like ambient color, specularity, etc..

我并不是说这是完美的设置.我真的不知道.但是制作一个称为材料"的东西并在多个模型中共享该材料是很常见的,就像perMaterial块与perModel块不同.共享照明信息也很常见.我不知道理想的设置是什么,只是指出perSceneperModel在相当常见的情况下可能还不够.

I'm not saying that's the perfect setup. I really have no idea. But it's pretty common to make something called a "material" and share that material among more than one model so that's like a perMaterial block which is different from a perModel block. It's also common to share lighting info. I don't know what the ideal setup is, just pointing out that perScene and perModel might not be enough for fairly common situations.

另一件事,这行

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

没有任何意义. ELEMENT_ARRAY_BUFFER是VAO状态的一部分.

makes no sense. ELEMENT_ARRAY_BUFFER is part of the VAO state.

这篇关于绑定多个统一缓冲区对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆