在运行时在同一模型上使用不同的着色器

时间:2013-07-04 05:04:44

标签: opengl rendering shader pipeline scenegraph

我已经问过类似但有点不清楚的问题here,但这一次我将非常具体而且非常重要。

假设我有一个抓住电源的演员。他开始使用bloom着色器发光,10秒后恢复正常,再次附加默认着色器。问题基本归结为:

如何在运行时在同一模型上使用不同的着色器?

请考虑以下非常简单的示例:

默认着色器:

attribute vec4 Position;
uniform mat4 ModelViewProjMatrix;

void main(void)
{
    gl_Position = ModelViewProjMatrix * Position;
}

RendererGLES20中的渲染代码为:

void RendererGLES20::render(Model * model)
{
    glUniformMatrix4fv(mvpUniform, 1, 0, &mvpMatrix);
    GLuint positionSlot = glGetAttribLocation(_program, "Position");
    glEnableVertexAttribArray(positionSlot);

    // interleaved data, But for now we are ONLY using the positions, ignoring texture, normals and colours.
    const GLvoid* pCoords = &(model->vertexArray[0].Position[0]);
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);

    glDrawArrays(GL_TRIANGLES, 0, model->vertexCount);

    glDisableVertexAttribArray(positionSlot);
}

够简单!现在想象一下,演员得到了一些动力,然后应用了疯狂的着色器:

Crazy Shader:

attribute vec4 Position;
attribute vec4 SourceColor;
attribute vec2 Texture;
attribute vec4 Normal;
attribute vec2 tempAttrib0;
attribute vec2 tempAttrib1;

// A bunch of varying but we don't need to worry about these for now                                           
varying vec4 .........;
varying .........;

uniform mat4 MVPMatrix;
uniform vec2 BloomAmount;
uniform vec2 BloomQuality;
uniform vec2 BloomSize;
uniform vec2 RippleSize;
uniform vec2 RippleAmmount;
uniform vec2 RippleLocation;
uniform vec2 deltaTime;
uniform vec2 RippleMaxIterations;

void main(void)
{
    // Some crazy voodoo source code here...
    // .........
    gl_Position = ..............;
}

您可以清楚地看到,为了将此着色器附加到模型,我需要将实际的渲染器源代码修改为以下内容:

void RendererGLES20::render(Model * model)
{
    glUniformMatrix4fv(mvpUniform, 1, 0, ....);
    glUniformMatrix4fv(bloomAmountUniform, 1, 0, ....);
    glUniformMatrix4fv(bloomQualityUniform, 1, 0, ....);
    glUniformMatrix4fv(bloomSizeUniform, 1, 0, ....);
    glUniformMatrix4fv(rippleSizeUniform, 1, 0, ....);
    glUniformMatrix4fv(rippleAmountUniform, 1, 0, ....);
    glUniformMatrix4fv(rippleLocationUniform, 1, 0, ....);
    glUniformMatrix4fv(rippleMaxIterationsUniform, 1, 0, ....);
    glUniformMatrix4fv(deltaTimeUniform, 1, 0, ....);

    GLuint positionSlot = glGetAttribLocation(_program, "Position");
    GLuint sourceColorSlot = glGetAttribLocation(_program, "SourceColor");
    GLuint textureSlot = glGetAttribLocation(_program, "Texture");
    GLuint normalSlot = glGetAttribLocation(_program, "Normal");
    GLuint tempAttrib0Slot = glGetAttribLocation(_program, "TempAttrib0");
    GLuint tempAttrib1Slot = glGetAttribLocation(_program, "TempAttrib1");

    glEnableVertexAttribArray(positionSlot);
    glEnableVertexAttribArray(sourceColorSlot);
    glEnableVertexAttribArray(textureSlot);
    glEnableVertexAttribArray(normalSlot);
    glEnableVertexAttribArray(tempAttrib0Slot);
    glEnableVertexAttribArray(tempAttrib1Slot);

    // interleaved data
    const GLvoid* pCoords = &(model->vertexArray[0].Position[0]);
    const GLvoid* sCoords = &(model->vertexArray[0].SourceColor[0]);
    const GLvoid* tCoords = &(model->vertexArray[0].Texture[0]);
    const GLvoid* nCoords = &(model->vertexArray[0].Normal[0]);
    const GLvoid* t0Coords = &(model->vertexArray[0].TempAttrib0[0]);
    const GLvoid* t1Coords = &(model->vertexArray[0].TempAttrib1[0]);

    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, stride, pCoords);
    glVertexAttribPointer(sourceColorSlot, 4, GL_FLOAT, GL_FALSE, stride, sCoords);
    glVertexAttribPointer(textureSlot, 2, GL_FLOAT, GL_FALSE, stride, tCoords);
    glVertexAttribPointer(normalSlot, 4, GL_FLOAT, GL_FALSE, stride, nCoords);
    glVertexAttribPointer(tempAttrib0Slot, 3, GL_FLOAT, GL_FALSE, stride, t0Coords);
    glVertexAttribPointer(tempAttrib1Slot, 2, GL_FLOAT, GL_FALSE, stride, t1Coords);

    glDrawArrays(GL_TRIANGLES, 0, model->vertexCount);

    glDisableVertexAttribArray(positionSlot);
    glDisableVertexAttribArray(sourceColorSlot);
    glDisableVertexAttribArray(textureSlot);
    glDisableVertexAttribArray(normalSlot);
    glDisableVertexAttribArray(tempAttrib0Slot);
    glDisableVertexAttribArray(tempAttrib1Slot);
}

您可以看到为了附加不同的着色器而需要编写的代码有多么不同。现在如果我想重新附加默认着色器怎么办? (这是着色器的附加和分离必须在运行时发生,例如:演员收集电源)。

任何想法如何能够高效,轻松地实现此功能,以允许模型在运行时更改着色器?我只是期待一个很好的实现/想法。你们怎么处理上述问题?

2 个答案:

答案 0 :(得分:1)

在渲染对象之前,您可以使用预期的着色器程序调用{​​{1}}(specifications here)。您可能想要使用已有的glUseProgram(program)变量。

然后,您可以根据您使用的着色器更改您设置的变量(制服/数组)。

我不确定“附加和分离着色器”,但为了回答您的效率问题,大多数人倾向于根据着色器对其“模型”进行分组,以尽量减少对_program的调用。这也意味着您只需每帧设置glUseProgram()一次制服,而不是每个使用该着色器的模型设置一次。

修改

这是一个示例(基于您的示例),它允许您在运行时使用枚举选择着色器

bloomQualityUniform

答案 1 :(得分:0)

在我们详细介绍之前,首先让我们解决一些心理障碍:OpenGL不是一个场景图:你不会将它作为一个场景,它会渲染整个模型等。说实话,OpenGL是用操作系统提供的纸张画的美化铅笔。

你应该认为OpenGL是某种程序控制的绘图工具,因为它就是这样。在您继续阅读之前,我建议您打开自己喜欢的图像处理程序(Photoshop,GIMP,Krita等),并尝试画出漂亮的图片。也许您复制一些图层,在其上应用一些滤镜,将其覆盖在原始图层上以获得所需的效果,等等。

这就是你应该考虑编写OpenGL的方式,特别是在进行着色器效果时。

现在让我们打破这个:

  

假设我有一个抓住电源的演员。

为此你需要一个演员模型和一些动画。这是由艺术家使用Blender等工具完成的。

  

他开始使用bloom shader

发光

发光通常只是一个额外的通道,它覆盖在原始模型上。让你的Photoshop模型回归你的脑海。首先,使用照明着色器绘制模型。假设你有一个 Model 类和一个 PhongTechniquq 类,它来自 Technique 类,它提供了一个接口来提供一个模型来绘制:

class Model;
class ModelState;

class Technique {
    drawModel(Model const *Model, ModelState const *state, /*...*/);
};

/* technique that renders models using a phong illumination model */
class PhongTechnique {
    drawModel(Model const *Model, ModelState const *state, /*...*/);
}

然后对于Bloom效果我们有另一个技术类

/* technique that renders models using a bloom */
class BloomTechnique {
    drawModel(Model const *Model, ModelState const *state, /*...*/);
}
  

并在10秒后恢复正常,再次附加默认着色器。

所以在你的游戏动画循环中,你会遇到你的模型。其中附有一些动画数据。

class AnimationElement {
    float timeStart();
    float timeStop();
    float X(float T);
}

class Model {
    vector<AnimationElement> animation_elements;
    ModelState animate(float T);
}

在模型状态下,我们有一些使用效果的标志。所以在你的整体绘图功能

drawscene(float T)
{
    PhongTechnique phong;
    BloomTechnique bloom;

    foreach(m in models) {
        ModelState mstate = m.animate(T);

        if(mstate.phong_pass)
            phong.drawModel(m, mstate, ...);

        if(mstate.bloom_pass)
            bloom.drawModel(m, mstate, ...);

    }
}

现在,在不同的Technique类实现中,您可以切换到右边的着色器,设置顶点属性数据等,然后渲染模型。或者确切地说:您将填写绘图批次列表,稍后您将重新排序以优化绘图过程。

如果你想看一个真正的游戏引擎:Id Software确实发布了他们的Doom3和Doom3-BFG引擎的完整源代码,后者有一个现代的OpenGL-3代码路径。