【问题标题】:How to load more than one shader program in WebGL render function如何在 WebGL 渲染函数中加载多个着色器程序
【发布时间】:2017-08-07 09:20:17
【问题描述】:

任何人都可以帮助我解决以下问题,我很困惑,不胜感激。我会尽量简洁。

我有一个简单的 WebGL 页面,它呈现两个对象。我可以通过单击按钮加载一个或另一个着色器程序,以更改这两个对象的渲染方式。但是,我想用第一个着色器程序渲染一个对象,用第二个着色器程序渲染另一个对象。

这是我的绘图函数(每次用户拖动鼠标时调用):

WebGLViewer.prototype.DrawGLScene = function()
{
    this.SetupWebGLContextViewport();

    this.SetForProgram();

    this.Draw3DObjects();
}

因此,调用了三个函数。他们在这里:

首先设置投影和相机:

WebGLViewer.prototype.SetupWebGLContextViewport = function()
{
    this.m_WebGLContext.viewport(0, 0, this.m_WebGLContext.viewportWidth, this.m_WebGLContext.viewportHeight);
    this.m_WebGLContext.clear(this.m_WebGLContext.COLOR_BUFFER_BIT | this.m_WebGLContext.DEPTH_BUFFER_BIT);

    if(this.m_projection == PROJECTION.ORTHOGRAPHIC)
    {
        mat4.ortho(this.m_orthoXMin, this.m_orthoXMax, this.m_orthoYMin, this.m_orthoYMax, 0.1, 100, this.m_pMatrix);
    }
    else
    {
        mat4.perspective(45, this.m_WebGLContext.viewportWidth / this.m_WebGLContext.viewportHeight, 0.1, 100.0, this.m_pMatrix);
    }

    mat4.identity(this.m_mvMatrix);

    this.m_mvMatrix = mat4.lookAt(this.m_eye, this.m_centre, this.m_up);
}

其次,选择要使用的着色器程序(如果只有一个对象,则使用程序 0,否则使用程序 1 - 我通过单击按钮一次添加一个对象) - (我应该添加 m_arrProgramSettingCallbacks 是一个数组设置顶点和片段着色器统一变量值的两个函数):

WebGLViewer.prototype.SetForProgram = function()
{
    if(this.m_numObjects.length == 1)
    {
        this.UseShaderProgram(0,false);
    }
    else
    {
        this.UseShaderProgram(1,false);
    }

    this.m_WebGLContext.uniformMatrix4fv(this.m_shaderProgram.m_pMatrixUniform, false, this.m_pMatrix);
    this.m_WebGLContext.uniformMatrix4fv(this.m_shaderProgram.m_mvMatrixUniform, false, this.m_mvMatrix);

    var normalMatrix = mat3.create();
    mat4.toInverseMat3(this.m_mvMatrix, normalMatrix);
    mat3.transpose(normalMatrix);
    this.m_WebGLContext.uniformMatrix3fv(this.m_shaderProgram.m_nMatrixUniform, false, normalMatrix);

    this.m_WebGLContext.useProgram(this.m_shaderProgram);

    var context = this.m_nShaderProgramID == 0 ? this : this.m_callingObject;
    this.m_arrProgramSettingCallbacks[this.m_nShaderProgramID].call(context);
}

您可以看到,上面的函数调用了另一个名为 UseShaderProgram 的函数,它简单地将全局变量 m_nShaderProgramID 和 m_shaderProgram 设置如下(我将 false 作为第二个参数传递,因此不会发生重绘):

WebGLViewer.prototype.UseShaderProgram = function(nShaderProgramID, bRedraw)
{
    this.EnableVertexAttribArray(nShaderProgramID);

    this.m_nShaderProgramID = nShaderProgramID;
    this.m_shaderProgram = this.m_arrShaderPrograms[this.m_nShaderProgramID];

    if(bRedraw)
    {
        this.Draw();
    }
}

最后,第三个函数渲染场景:

WebGLViewer.prototype.Draw3DObjects = function()
{
    this.m_WebGLContext.activeTexture(this.m_WebGLContext.TEXTURE0);

    for(var n3DObject = 0; n3DObject < this.m_arrVertexPositionBuffers.length; n3DObject++)
    {
        this.m_preRenderObjectCallback.call(this.m_callingObject, n3DObject);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ARRAY_BUFFER, this.m_arrVertexPositionBuffers[n3DObject]);
        this.m_WebGLContext.vertexAttribPointer(this.m_shaderProgram.m_vertexPositionAttribute, this.m_arrVertexPositionBuffers[n3DObject].itemSize, this.m_WebGLContext.FLOAT, false, 0, 0);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ARRAY_BUFFER, this.m_arrVertexNormalBuffers[n3DObject]);
        this.m_WebGLContext.vertexAttribPointer(this.m_shaderProgram.m_vertexNormalAttribute, this.m_arrVertexNormalBuffers[n3DObject].itemSize, this.m_WebGLContext.FLOAT, false, 0, 0);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ARRAY_BUFFER, this.m_arrVertexColourBuffers[n3DObject]);
        this.m_WebGLContext.vertexAttribPointer(this.m_shaderProgram.m_vertexColourAttribute, this.m_arrVertexColourBuffers[n3DObject].itemSize, this.m_WebGLContext.FLOAT, false, 0, 0);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ARRAY_BUFFER, this.m_arrVertexTextureCoordBuffers[n3DObject]);
        this.m_WebGLContext.vertexAttribPointer(this.m_shaderProgram.m_textureCoordAttribute, this.m_arrVertexTextureCoordBuffers[n3DObject].itemSize, this.m_WebGLContext.FLOAT, false, 0, 0);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ELEMENT_ARRAY_BUFFER, this.m_arrVertexIndicesBuffers[n3DObject]);
        this.m_WebGLContext.drawElements(this.m_WebGLContext.TRIANGLES, this.m_arrVertexIndicesBuffers[n3DObject].numItems, this.m_WebGLContext.UNSIGNED_SHORT, 0);
   }
}

因此,我们可以看到,在任何给定的渲染代码运行中,仅使用一个着色器程序,具体取决于是否有一个或两个对象。

这一切都很完美。

希望每个对象使用一个着色器程序,我基本上将 SetForProgram 调用放在 Draw3DObjects 函数中,如下所示:

WebGLViewer.prototype.Draw3DObjects = function()
{
    this.m_WebGLContext.activeTexture(this.m_WebGLContext.TEXTURE0);

    for(var n3DObject = 0; n3DObject < this.m_arrVertexPositionBuffers.length; n3DObject++)
    {
        this.UseShaderProgram(n3DObject,false);

        this.m_WebGLContext.uniformMatrix4fv(this.m_shaderProgram.m_pMatrixUniform, false, this.m_pMatrix);
        this.m_WebGLContext.uniformMatrix4fv(this.m_shaderProgram.m_mvMatrixUniform, false, this.m_mvMatrix);

        var normalMatrix = mat3.create();
        mat4.toInverseMat3(this.m_mvMatrix, normalMatrix);
        mat3.transpose(normalMatrix);
        this.m_WebGLContext.uniformMatrix3fv(this.m_shaderProgram.m_nMatrixUniform, false, normalMatrix);

        this.m_WebGLContext.useProgram(this.m_shaderProgram);

        var context = this.m_nShaderProgramID == 0 ? this : this.m_callingObject;
        this.m_arrProgramSettingCallbacks[this.m_nShaderProgramID].call(context);

        this.m_preRenderObjectCallback.call(this.m_callingObject, n3DObject);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ARRAY_BUFFER, this.m_arrVertexPositionBuffers[n3DObject]);
        this.m_WebGLContext.vertexAttribPointer(this.m_shaderProgram.m_vertexPositionAttribute, this.m_arrVertexPositionBuffers[n3DObject].itemSize, this.m_WebGLContext.FLOAT, false, 0, 0);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ARRAY_BUFFER, this.m_arrVertexNormalBuffers[n3DObject]);
        this.m_WebGLContext.vertexAttribPointer(this.m_shaderProgram.m_vertexNormalAttribute, this.m_arrVertexNormalBuffers[n3DObject].itemSize, this.m_WebGLContext.FLOAT, false, 0, 0);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ARRAY_BUFFER, this.m_arrVertexColourBuffers[n3DObject]);
        this.m_WebGLContext.vertexAttribPointer(this.m_shaderProgram.m_vertexColourAttribute, this.m_arrVertexColourBuffers[n3DObject].itemSize, this.m_WebGLContext.FLOAT, false, 0, 0);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ARRAY_BUFFER, this.m_arrVertexTextureCoordBuffers[n3DObject]);
        this.m_WebGLContext.vertexAttribPointer(this.m_shaderProgram.m_textureCoordAttribute, this.m_arrVertexTextureCoordBuffers[n3DObject].itemSize, this.m_WebGLContext.FLOAT, false, 0, 0);

        this.m_WebGLContext.bindBuffer(this.m_WebGLContext.ELEMENT_ARRAY_BUFFER, this.m_arrVertexIndicesBuffers[n3DObject]);
        this.m_WebGLContext.drawElements(this.m_WebGLContext.TRIANGLES, this.m_arrVertexIndicesBuffers[n3DObject].numItems, this.m_WebGLContext.UNSIGNED_SHORT, 0);
   }
}

当只有一个对象时工作正常,但是当我添加第二个时程序停止响应。

好吧,对不起所有的代码。感谢任何可以建议我在 OpenGL 渲染循环中为每个对象使用 useProgram 函数的人。

米奇。

【问题讨论】:

    标签: glsl webgl


    【解决方案1】:

    一些你应该注意的事情。

    1. 统一的位置不会跨程序共享。

      如果您有 2 个程序都有一个名为 u_matrix 的制服,您需要为每个程序查找单独的位置对象

    2. 属性位置不保证相同

      除非您调用 gl.bindAttribLocation(webgl1 和 webgl2)或使用 layout (location = &lt;loc&gt;)(webgl2 glsl 300 es),​​否则每个程序的属性位置可能/将会不同。

    因此,简而言之,当您想要切换程序时,您需要为该特定程序启用和设置属性(除非您使用上述方法来确保它们使用相同的属性位置),并且您需要为该特定程序设置所有制服,因为制服不共享。

    还要记住gl.uniform??? 函数适用于当前程序,即您使用gl.useProgram 设置的程序。因此,要为特定程序设置制服,您必须首先为该程序调用 gl.useProgram

    您应该会在 JavaScript 控制台中看到错误,因为您在错误的程序中使用了错误的统一位置。示例:

    const gl = document.createElement("canvas").getContext("webgl");
    
    const vs = createShader(gl, gl.VERTEX_SHADER, `
    void main() { 
      gl_Position = vec4(0,0,0,1); 
      gl_PointSize = 10.;
    }
    `);
    const fs = createShader(gl, gl.FRAGMENT_SHADER, `
    precision mediump float;
    uniform vec4 color;
    void main() {
      gl_FragColor = color;
    }
    `);
    
    // create 2 programs using the exact same shaders
    const prg1 = createProgram(gl, vs, fs);
    const prg2 = createProgram(gl, vs, fs);
    
    // look up the location of 'color' on prg1
    const colorLoc = gl.getUniformLocation(prg1, "color");
    // use that location with prg2  BAD!!
    gl.useProgram(prg2);
    gl.uniform4fv(colorLoc, [1, 0, 0, 1]);  // SHOULD GET ERROR
    
    
    function createProgram(gl, vs, fs) {
      const p = gl.createProgram();
      gl.attachShader(p, vs);
      gl.attachShader(p, fs);
      gl.linkProgram(p);
      // TODO: check for errors;
      return p;
    }
    
    function createShader(gl, type, src) {
      const s = gl.createShader(type);
      gl.shaderSource(s, src);
      gl.compileShader(s);
      // TODO: should check for errors
      return s;
    }

    当我运行上面的代码时,我在 JavaScript 控制台中看到了这个

       WebGL: INVALID_OPERATION: uniform4fv: location is not from current program
    

    看你的代码我看到了这个

        this.UseShaderProgram(n3DObject,false);
    
        this.m_WebGLContext.uniformMatrix4fv(this.m_shaderProgram.m_pMatrixUniform, false, this.m_pMatrix);
        this.m_WebGLContext.uniformMatrix4fv(this.m_shaderProgram.m_mvMatrixUniform, false, this.m_mvMatrix);
    
        var normalMatrix = mat3.create();
        mat4.toInverseMat3(this.m_mvMatrix, normalMatrix);
        mat3.transpose(normalMatrix);
        this.m_WebGLContext.uniformMatrix3fv(this.m_shaderProgram.m_nMatrixUniform, false, normalMatrix);
    
        this.m_WebGLContext.useProgram(this.m_shaderProgram);
    

    代码调用this.UseShaderProgram,但不是调用useProgram。然后,它会在之前发布的任何程序上设置制服。最后它调用了useProgram,但为时已晚。

    【讨论】:

    • 非常感谢。您的彻底回答让我想到了一些电话的顺序,过了一会儿我看到了错误,现在一切正常。感谢您抽出宝贵时间,非常感谢。米奇,
    猜你喜欢
    • 1970-01-01
    • 2015-07-28
    • 1970-01-01
    • 1970-01-01
    • 2015-12-26
    • 1970-01-01
    • 1970-01-01
    • 2020-01-06
    • 1970-01-01
    相关资源
    最近更新 更多