如何在WebGL渲染函数中加载多个着色器程序

时间:2017-08-07 09:20:17

标签: glsl webgl

任何人都可以帮助我解决以下问题,我很困惑并且会感激任何建议。我会尝试尽可能简洁。

我有一个简单的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函数。

米奇。

1 个答案:

答案 0 :(得分:1)

你应该注意的一些事情。

  1. 统一地点不会在各个计划之间共享。

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

  2. 不保证属性位置相同

    除非您致电gl.bindAttribLocation(webgl1和webgl2)或使用layout (location = <loc>)(webgl2 glsl 300 es),​​否则每个程序的属性位置可能会有所不同。

  3. 所以,当你想要切换程序时,你需要启用和设置特定程序的属性(除非你使用上面的方法确保它们使用相同的属性位置),你需要为该特定程序设置所有制服,因为不共享制服。

    另请注意,gl.uniform???函数适用于当前程序,即使用gl.useProgram设置的函数。因此,为了在特定程序上设置制服,您必须先为该程序调用{​​{1}}。

    您应该在JavaScript控制台中看到使用错误的统一位置和错误的程序的错误。例如:

    &#13;
    &#13;
    gl.useProgram
    &#13;
    &#13;
    &#13;

    当我运行上面的代码时,我在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;
    }

    查看您的代码我看到了

       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,但此时为时已晚。