GameMachine可以单独编译DirectX11渲染模块:
Froser:自制游戏引擎 - GameMachine - 渲染流水线 (DirectX11)zhuanlan.zhihu.com默认情况下,GameMachine自身是实现了OpenGL的渲染的。
流水线是指图元从被构造到被渲染所要经历的过程。每一个阶段在运行时(渲染时)都有严格的顺序,但是对它们进行设置并不一定要遵循上面的顺序。
和之前文章所说的DirectX11一样,OpenGL 3.3以上的版本也有一个固定的流水线:
![e078008416164fda5fff99f4c989f19f.png](https://i-blog.csdnimg.cn/blog_migrate/4b0343566616217eb6a037f5cedad710.png)
接下来,我们大致参照GameMachine源码来讲解各个着色器设置阶段。我们将对比DirectX11的流水线来进行介绍。
1. Vertex Specification
类似于DirectX11的IA阶段。我们需要准备好VAO(Vertex Array Object)、VBO(Vertex Buffer Object),进行布局的定义,并且准备好缓存。
GMModelDataProxy
类是用于将这些顶点数据传输给GPU的代理类,对DirectX11和OpenGL有各自的实现。我们节选一段OpenGL下的代码:
void GMGLModelDataProxy::transfer()
{
D(d);
D_BASE(db, GMModelDataProxy);
if (d->inited)
return;
prepareParentModel();
GMModel* model = getModel();
if (!model->isNeedTransfer())
return;
prepareTangentSpace();
GMModelBufferData bufferData;
Vector<GMVertex> packedVertices;
// 把数据打入顶点数组
packVertices(packedVertices);
GMsize_t verticeCount = 0;
GLuint vao;
GMGLBeginGetErrorsAndCheck();
glGenVertexArrays(1, &vao);
bufferData.arrayId = vao;
glBindVertexArray(vao);
GLuint vbo;
glGenBuffers(1, &vbo);
bufferData.vertexBufferId = vbo;
GLenum usage = model->getUsageHint() == GMUsageHint::StaticDraw ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW;
glBindBuffer(GL_ARRAY_BUFFER, bufferData.vertexBufferId);
glBufferData(GL_ARRAY_BUFFER, sizeof(GMVertex) * packedVertices.size(), packedVertices.data(), usage);
glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Position), GMVertex::PositionDimension, GL_FLOAT, GL_FALSE, sizeof(GMVertex), 0);
glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Normal), GMVertex::NormalDimension, GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(3));
glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Texcoord), GMVertex::TexcoordDimension, GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(6));
glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Tangent), GMVertex::TangentDimension, GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(8));
glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Bitangent), GMVertex::BitangentDimension, GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(11));
glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Lightmap), GMVertex::LightmapDimension, GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(14));
glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Color), GMVertex::ColorDimension, GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(16));
if (glVertexAttribIPointer)
glVertexAttribIPointer(gmVertexIndex(GMVertexDataType::BoneIds),GMVertex::BoneIDsDimension, GL_INT, sizeof(GMVertex), BIT32_OFFSET(20));
else // 可能某些ES版本不支持
glVertexAttribPointer(gmVertexIndex(GMVertexDataType::BoneIds), GMVertex::BoneIDsDimension, GL_INT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(20));
glVertexAttribPointer(gmVertexIndex(GMVertexDataType::Weights), GMVertex::WeightsDimension, GL_FLOAT, GL_FALSE, sizeof(GMVertex), BIT32_OFFSET(24));
GM_FOREACH_ENUM_CLASS(type, GMVertexDataType::Position, GMVertexDataType::EndOfVertexDataType)
{
glEnableVertexAttribArray(gmVertexIndex(type));
}
if (model->getDrawMode() == GMModelDrawMode::Index)
{
Vector<GMuint32> packedIndices;
// 把数据打入顶点数组
packIndices(packedIndices);
glGenBuffers(1, &bufferData.indexBufferId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferData.indexBufferId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GMuint32) * packedIndices.size(), packedIndices.data(), GL_STATIC_DRAW);
verticeCount = packedIndices.size();
}
else
{
verticeCount = packedVertices.size();
}
glBindVertexArray(0);
GMGLEndGetErrorsAndCheck();
for (auto& part : model->getParts())
{
part->clear();
}
GMModelBuffer* modelBuffer = new GMModelBuffer();
modelBuffer->setData(bufferData);
model->setVerticesCount(verticeCount);
model->setModelBuffer(modelBuffer);
modelBuffer->releaseRef();
d->inited = true;
model->doNotTransferAnymore();
}
可以看到,上述代码生成了VAO、VBO,并且定义了顶点布局,并且通过glBind*函数进行了绑定。
2. Vertex Shader (VS)
同DirectX11 VS阶段。
3. Tessellation
类似DirectX11 Hull Shader阶段。
4. Geometry Shader
类似DirectX11 GS阶段。
5. Vertex Post-Processing
顶点的后处理主要做两件事情:
- 变换反馈(Transform Feedback),将VS阶段输出的顶点坐标反馈回来,即获取VS阶段变换后的坐标。这个可以用来实现一些丰富的粒子效果。不过GameMachine没有使用此阶段。
- 裁剪。通过设置裁剪距离gl_ClipDistance,OpenGL会对不在距离范围内的顶点进行裁剪。
6. Primitive Assembly
图元装配用于处理裁减面(Cull Face)等。
7. Rasterization
光栅化阶段同DirectX11光栅化阶段。
8. Fragment Shader
同DirectX11 PS阶段。
9. Per-Sample Processing
同DirectX11 OM阶段,即进行深度测试、模板测试、混合等。
GameMachine遵循以上流水线对流程进行抽象。例如,在OpenGL的绘制流程中:
void GMGLTechnique::draw(GMModel* model)
{
D(d);
GMGLBeginGetErrorsAndCheck();
glBindVertexArray(model->getModelBuffer()->getMeshBuffer().arrayId);
prepareStencil(*d->engine);
prepareScreenInfo(getShaderProgram());
beforeDraw(model);
startDraw(model);
afterDraw(model);
glBindVertexArray(0);
GMGLEndGetErrorsAndCheck();
}
void GMGLTechnique_3D::beforeDraw(GMModel* model)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// 材质
prepareMaterial(model->getShader());
// 应用Shader
prepareShaderAttributes(model);
// 设置光照模型
GMIlluminationModel illuminationModel = prepareIlluminationModel(model);
// 纹理
prepareTextures(model, illuminationModel);
}
我们设置了VAO、模板、Shader的uniform参数等,在DirectX11的渲染中我们做的事情也是类似的:
void GMDx11Technique::draw(GMModel* model)
{
prepareScreenInfo();
prepareBuffer(model);
prepareLights();
prepareMaterials(model);
prepareRasterizer(model);
prepareBlend(model);
prepareDepthStencil(model);
prepareTextures(model);
prepareDebug(model);
passAllAndDraw(model);
}
总的来说,绘制过程一部分是OpenGL/DirectX的API,另外一部分是对着色器的变量设置。只要理解了渲染流水线,就可以写出适应于不同渲染API的足够抽象的代码。