【问题标题】:Bind multiple Uniform Buffer Objects绑定多个统一缓冲区对象
【发布时间】:2017-06-19 22:56:26
【问题描述】:

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

它们看起来是个好主意,不必将通用制服附加到每个程序(例如每个被渲染对象通用的投影和视图矩阵)。

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

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

我在这里有一个工作的 sn-p

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

我做错了什么? 谢谢

【问题讨论】:

    标签: glsl webgl2


    【解决方案1】:

    所以,你做错的第一件事是你没有打电话给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 东西总是在缓冲区索引 0 上,而 perModel 东西在索引 1 上,因此我可以在初始化时设置它们的程序部分(对 gl.uniformBlockBinding 的调用) .

    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. projectionviewviewProjection 等共享矩阵
    2. 每个模型矩阵,如 worldworldInverseTransform
    3. 每个灯光信息,如lightPositionlightColor
    4. 有 2 盏灯,所以第 4 块类似于第 3 块
    5. 环境颜色、镜面反射等材质数据。

    我并不是说这是完美的设置。我真的不知道。但是制作一种称为“材料”的东西并在多个模型之间共享该材料是很常见的,所以这就像一个 perMaterial 块,它不同于 perModel 块。共享照明信息也很常见。我不知道理想的设置是什么,只是指出perSceneperModel 对于相当常见的情况可能不够用。

    另一件事,这条线

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

    没有意义。 ELEMENT_ARRAY_BUFFER 是 VAO 状态的一部分。

    【讨论】:

    • 哦,jsfiddle 的第 41 行被注释掉了。如果我取消注释,您应该会看到该错误。或者如果我是一个好孩子并叫“getUniformBlockIndex”,我会看到一个白色方块而不是黄色方块? :)
    • 已更新,因为 WebGL2 中没有错误。我忘了你需要打电话给uniformBlockBinding
    • 那么在我的代码中添加这个的最佳位置在哪里?这应该在for循环内的渲染循环上吧?因为我们想为每个对象/程序更新相同的统一缓冲区...?
    • 不,因为它在答案中说它是每个程序,所以你只需要在初始化时调用 uniformBlockBinding 来告诉程序每个统一缓冲区块使用哪个统一块索引。
    • 还有一个与统一块对象无关的问题
    【解决方案2】:

    如gman所说,获取uniform块的索引,然后绑定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);
      }
    

    };

    【讨论】:

    • 不确定它是否是我的 GPU,但即使添加此更改我也无济于事.. jsfiddle.net/m9qchtdb/11 Chrome 上没有抛出错误,但 Firefox 记录统一块的缓冲区小于 UNIFORM_BLOCK_DATA_SIZE。 :/ 你能在你的机器上运行这个评论的 jsfiddle 吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多