Urho3D 1.7.1 源代码分析(三)

1. Application

这里的分析主要参考Urho3D的两个例子HelloWorld和StaticScene,前者使用UI组件,用于分析UI子系统,后者显示模型,用于分析Renderer子系统。

1.1 URHO3D_DEFINE_APPLICATION_MAIN()

URHO3D_DEFINE_APPLICATION_MAIN()宏用于创建唯一的application实例。下面是Urho3D的例子HelloWorld。

URHO3D_DEFINE_APPLICATION_MAIN(HelloWorld)

展开后的代码如下:

int RunApplication() 
{ 
    Urho3D::SharedPtr context(new Urho3D::Context()); 
    Urho3D::SharedPtr application(new HelloWorld(context)); 
    return application->Run(); 
} 

int main(int argc, char** argv) 
{ 
    Urho3D::ParseArguments(argc, argv); 
    return RunApplication(); 
}

HelloWorld从Application派生,这里创建HelloWorld的实例,并调用Application::Run()启动它。在创建application之前,先创建公共的Context实例,以便传给Application,并在之后再传给其他类。

1.2 定制Application

Application定义了一组虚拟函数Setup()、Start()和Stop()。使用者可以在自己的Applicatio类中定制自己的实现。一个典型的做法是在Start()中创建UI组件和模型组件。这样UI子系统和Renderer系统可以分别对它们进行操作。

下图是main()的调用步骤。

  • 创建Context实例。
  • 创建使用者的Application类实例。
  • 在Application::Run()中调用使用者实现的Setup()、Start()和Stop()
  • 在Application()中创建Engine实例,在Application::Run()调用Engine::Initialize()初始化,在调用Engine::RunFrame()驱动它。Engine组合了对UI和Renderer及其他子系统的功能。

2. Engine

2.1 Engine::Engine()

在构造函数Engine()中,初始化UI、FileSystem、Log、ResourceCache等子系统并向Context注册。在UI的构造函数UI()中,调用RegisterUILibrary()。

2.2 Engine::Initialize()

Engine::Initialize()的工作如下:

  • 创建Graphics子系统和Renderer子系统,向Context注册。

  • 调用Graphics::SetMode(),这将触发UI子系统和Renderer子系统的初始化。

    • 调用SDL_CreateWindow()创建用于绘制的窗口。
    • 发送E_SCREENMODE事件。UI在UI::HandleScreenMode()中处理该事件,调用Renderer::Initialize()。Renderer在Renderer::HandleScreenMode()中处理该事件,调用Renderer::Initialize()。
    • 调用CheckFeatureSupport(),检查支持哪些OpenGL扩展选项。
  • 设置Renderer。

2.3 Engine::runFrame()

Engine::RunFrame()依次调用Engine::Update()进行渲染前的准备工作,然后调用Engine::Render()渲染。

对于UI子系统,

  • 发送的E_POSTUPDATE事件,由UI::HandlePostUpdate()处理,后者调用UI::Update()。
  • 发送的E_RENDERUPDATE事件,由UI::HandleRenderUpdate()处理,其中调用UI::RenderUpdate()。
  • 在Engine::Render()中,调用UI::Render()渲染UI组件。

对于Renderer子系统,

  • 发送的E_RENDERUPDATE事件,由Renderer::HandleRenderUpdate()处理,其中调用Renderer::Update()。
  • 在Engine::Render()中,调用Renderer::Render()渲染模型。

Engine中还包括Graphics的处理,Graphics负责绘制图像前的准备工作。

在Graphics::BeginFrame()中,

  • 如调用glEnable()设置绘制选项。
  • 调用glViewPort()设置绘制视口。
  • 发送E_BEGINRENDERING事件。

在Graphics::EndFrame()中,

  • 发送E_ENDRENDERING事件。

3. Graphics子系统

Graphics包括与显示编程接口有关的操作。如下列出了Graphics的部分成员函数。

函数 功能 调用的函数
SetMode() 创建窗口 SDL_CreateWindow()
Clear() 清除背景 glClearColor()、glClearDepth()、glClearStencil()、glClear()
SetViewport() 设置视口 glViewport()
SetBlendMode() 设置混合模式 glBlendFunc()、glBlendEquation()
SetColorWrite() 设置颜色mask glColorMask()
SetCullMode() 设置剔除模式 glCullFace()
SetDepthTest() 设置深度测试 glDepthFunc()
SetScissorTest() 设置裁剪测试 glScissor()
SetStencilTest() 设置模板测试 glStencilFunc()、glStencilOp()
SetShaders() 设置Shader、Program glCreateShader()、glShaderSource()、glCompileShader()、
glCreateProgram()、glAttachShader()、glLinkProgram、glUseProgram()
SetShaderParameter() 设置Shader的uniform变量 glUniform1fv()、glUniformMatrix3fv() ...
SetVBO() 绑定顶点buffer glBindBuffer()
PrepareDraw() 设置顶点数据 glVertexAttribPointer()
Draw() 绘制 glDrawArrays()、glDrawElements()、glDrawElementsInstanced()

3.1 Shader & Program

3.1.1 Shader & Program概念

Shader是从资源文件加载的Resource,而ShaderVariation是从Shader生成的、OpenGL意义上的Shader。ShaderVariation的成员owner_指向引用的Shader实例。另外一个成员type_指出这是一个vertex shader还是一个pixel/fragment shader。

ShaderProgram类代表program。在构造函数中,它引用一个vertex ShaderVariation和一个pixel ShaderVariation。

ShaderProgram的成员vertexAttributes_保存了program的vertex attribute到其位置的映射关系。Vertex attribute的信息包括两部分,一是semantic,二是序号。所谓的semantic由数组ShaderVariation::elementSemanticNames[]指定:

const char* ShaderVariation::elementSemanticNames[] =
{
     "POS",
     "NORMAL",
     "TEXCOORD",
};

如下是一个vertex attribute的例子。

attribute vec4 iPos;
attribute vec3 iNormal;
attribute vec4 iCubeTexCoord1;

通过检查attribute的名字是否包含elementSemanticNames[]中的semantic字符串,判断是那种semantic。如iPos包含POS,所以对应的semantic是POS。

Attribute可能附加了数字,如iCubeTexCoord1附加了1,这个数字就是序号。如果没有附加数字,则序号为-1。如iCubeTexCoord1的序号是1,iPos的序号是-1。

ShaderParameter保存了uniform变量的信息,type_是从属shader的类型,name_是变量名,location_是变量位置。ShaderProgram的成员shaderParameters_则保存了一般uniform变量(不包括sampler变量)的名称到ShaderParameter的映射关系。

3.1.2 创建Shader

ShaderVariation::Create()负责创建opengl意义上的shader。

  • 调用Shader::GetSourceCode()得到Shader源代码。
  • 源代码中使用了宏,以便指定其中某些代码有效或无效。使用ShaderVariantion::SetDefines()可以设置这些宏。下面的DIFFMAP是宏的一个例子。如果定义了DIFFMAP,则往代码中加入#define DIFFMAP,就是下面红色的一行。
#define DIFFMAP

void VS()
{
     #ifdef DIFFMAP
         vTexCoord = iTexCoord;
     #endif
}

指定的宏不同,则从一个Shader实例创建的ShaderVariation不同。如果宏相同,就要避免重复创建ShaderVariation实例了。Shader把从宏到ShaderVariation实例的映射保存在成员vsVariantions_和psVariantions_中。

调用Shader::GetVariation()可以得到与指定宏对应的ShaderVariation,它的参数就是包含宏的字符串。

  • NormalDefines()将字符串标准化,也就是把字符全部转成大写,将包含的多个宏重新按顺序排列。
  • 如果对应的ShaderVariation还不存在,则创建ShaderVariation实例,并设置宏。后面调用ShaderVariation::Create()时就可以使用了。
  • 如果ShaderVariation已经存在,则返回就好了。

3.1.3 创建Program

ShaderProgram::Link()创建Program,分三步。

一是调用glCreateProgram()和glAttachShader()创建program。

二是得到vertex attribute。

  • 调用glGetProgramiv(GL_ACTIVE_ATTRIBUTES)得到所有vertex attribute,并遍历。
  • 调用glActiveAttrib()得到vertex attribute。调用glGetAttribLocation()得到attribute的位置。根据vertex attribute的名字查询ShaderVariation::elementSemanticNames[]数组,得到其semantic。将attribute的信息(samantic, 序号)到位置的映射关系保存在成员vertexAttributes_中。

三是得到uniform变量,包括sampler变量和一般的uniform变量。区分这两者办法是看前缀字符。sampler变量以s开头,如sDiffMap、sDiffCubeMap等。一般变量以c开头,如cModel、cViewProj等。

  • 调用glGetProgramiv(GL_ACTIVE_UNIFORMS)得到所有vertex attribute,并遍历。
  • 对于sampler变量,找到与变量对应的texture unit,调用glUniform1iv()将uniform变量绑定到texture unit上。
  • 对于一般变量,将变量名和ShaderParameter的映射关系保存在成员shaderParameters_中。

3.1.4 Graphics与ShaderProgram

Graphics的成员impl_是一个GraphicsImpl实例,后者分担Graphics的部分功能。

GraphicsImpl的成员shaderProgram_持有唯一的ShaderProgram实例。

3.2 VertexBuffer

3.2.1 Vertex Buffer概念

GPUObject保存OpenGL对象的句柄。它的成员object_是一个union类型GPUObjectHandle,后者的成员name_保存的就是句柄值,如glGenBuffers()得到的buffer的句柄值。

VertexBuffer从GPUObject派生的,它保存一组顶点。VertexBuffer的每个顶点可以包括不同semantic的顶点数据,semantic由枚举类型VertexElementSemantic规定,如位置、法线向量等。

enum VertexElementSemantic
{
    SEM_POSITION = 0,
    SEM_NORMAL,
    SEM_TEXCOORD,
};

VertexElement负责保存这些semantic信息。VertexBuffer的成员elements_就是一组VertexElement实例,如果VertexBuffer中有3种semantic数据,则elements_中就有3个VertexElement实例。

Vertexbuffer中数据的排列方式是每个顶点的数据放在一起,然后所有顶点排列成一个数组。

VertexElement除了semantic_之外的其他成员:

  • type_是数据类型,可以通过查询数组ELEMENT_TYPESIZE[]得到它的字段大小。
URHO3D_API const unsigned ELEMENT_TYPESIZES[] =
{
    sizeof(int),
    sizeof(float),
}
  • offset_是该semantic在vertexbuffer的数据中的偏移位置。

3.2.2 VertexBuffer::SetSize()

有两层VertexBuffer::SetSize()。

  • 顶层SetSize()的参数是一个位掩码,指定有哪些vertex element。调用GetElements(),在数组LEGACY_VERTEXELEMENTS[]查找每个位掩码对应的VertexElement,并保存在VertexBuffer的成员elements_中。注意这时的VertexElement只有数据类型和semantic,而offset_等其他成员没有设置。
extern URHO3D_API const VertexElement LEGACY_VERTEXELEMENTS[] =
{
    VertexElement(TYPE_VECTOR3, SEM_POSITION, 0, false),     // Position
    VertexElement(TYPE_VECTOR3, SEM_NORMAL, 0, false),       // Normal
    VertexElement(TYPE_UBYTE4_NORM, SEM_COLOR, 0, false),    // Color
}
  • 调用下层VertexBuffer::SetSize()。其中,
    • 调用UpdateOffsets()。遍历elements_,设置VertexElement的成员offset_。
    • 调用Create()。其中调用glGenBuffers()生成buffer,调用glBindBuffer()绑定buffer,调用glBufferData()写入顶点数据,不过这时顶点数据为空。

3.2.3 VertexBuffer::SetData()

VertexBuffer::SetData()的工作是:

调用glBindBuffer()绑定Buffer,调用glBufferData()写入顶点数据,这里顶点数据不为空。

相关链接
Urho3D 1.7.1 源代码分析 (一)
Urho3D 1.7.1 源代码分析 (二)
Urho3D 1.7.1 源代码分析 (三)
Urho3D 1.7.1 源代码分析 (四)
Urho3D 1.7.1 源代码分析 (五)

你可能感兴趣的:(Urho3D 1.7.1 源代码分析(三))