我已经问过类似但有点不清楚的问题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);
}
您可以看到为了附加不同的着色器而需要编写的代码有多么不同。现在如果我想重新附加默认着色器怎么办? (这是着色器的附加和分离必须在运行时发生,例如:演员收集电源)。
任何想法如何能够高效,轻松地实现此功能,以允许模型在运行时更改着色器?我只是期待一个很好的实现/想法。你们怎么处理上述问题?
答案 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代码路径。