1. Resource
1.1 Resource目录
ResourceCache负责资源缓存。它的成员resourceDirs_保存了资源目录列表。成员resourceGroup_保存已加载的资源,这是一个从资源类型到ResourceGroup的映射,ResourceGroup的成员resources_保存了资源名字到Resource的映射,所以这是一个两级映射。
在Engine::InitializeResourceCache()中得到资源目录。
调用GetParameter(),指定EP_RESOURCE_PREFIX_PATHS得到资源路径的主目录部分,指定EP_RESOURCE_PATHS得到路径的子目录部分。
遍历路径所有子目录。如果主目录加上子目录得到的全路径是存在的,就调用ResourceCache::AddResourceDir()加入资源目录列表。这里加入列表中的目录有:
1.2 File
File是文件类的抽象,实现Deserializer和Serailizer指定的读写接口。
ResourceCache::GetFile()负责加载指定文件名的File实例。
- 创建File实例,在构造函数中打开文件。
- 调用File::SetName()设置文件名。
1.3 加载Resource
Resource是各种资源的抽象,如Image、Shader等。
ResourceCache::GetResource
调用T::GetTypeStatic()得到T的StringHash值,以它为参数调用第二个ResourceCache::GetResource()函数。
在第二个GetResource()中,
- 调用SanitateResourceName()规范资源的路径。
- 调用Context::CreateObject()得到模板类T的实例。
- 调用GetFile()得到指定文件名的File实例。
- 调用Resource::Load()。其中调用虚拟函数Resource::BeginLoad()和Resource::EndLoad(),从File实例中加载资源。
BeginLoad()和EndLoad()是留给Resource派生类实现的两个虚拟函数。
2.Resource之Shader
2.1 Shader资源文件
Shader是一种Resource,它从Shader资源文件加载。这里使用的资源文件是Urho3D自带的Shaders/GLSL/Basic.glsl。这个名字包括如下几个部分:
- ”Basic”是资源的名字部分,
- Shader/GLSL是Graphics::shaderPath_指定的路径部分,
- .glsl是Graphis::shaderExtension_指定的扩展名部分。
如下是是Basic.glsl文本的节选。
// Basic.glsl
#include "Uniforms.glsl"
#ifdef VERTEXCOLOR
varying vec4 vColor;
#endif
void VS()
{
#ifdef VERTEXCOLOR
vColor = iColor;
#endif
}
void PS()
{
#ifdef VERTEXCOLOR
diffColor *= vColor;
#endif
}
2.2 Shader::BeginLoad()
Shader只需要实现BeginLoad()。
- 调用ProcessSource()。依次读入File中的行合并成Shader的文本。如果这一行是以#include开头,则调用ResourceCahce::GetFile()读入相应的文件,并展开到文本的对应位置。比如Basic.glsl中的#include “Uniforms.glsl”。
- 文本中包括vertex shader和pixel shader两部分。
- Vertex shader的main()函数是文本中的VS()函数,pixel shader的main()函数则是PS()函数。注释掉不需要的PS/VS,并把需要的VS/PS改回main,就分别可以得到vertex shader和pixel shader的源代码。
- 转换后得到的文本保存在Shader的成员vsSourceCode_和psSourceCode_中。
3. Resource之Texture
3.1 Texture概念
Texture是一种Resource。Texture有多种,Texture2D、Texture3D、TextureCube等。Texture的成员target_是texture的渲染目标,目标类型是TextureUsage指定的枚举值。
这里以Texture2D为例说明texture的加载,它是由Image和XMLFile等其他reousrce组合而成的Resource。Texture2D实现了Resource的两个虚拟函数BeginLoad()和EndLoad()。
3.2 Texture::BeginLoad()
Texture2D::BeginLoad()的工作如下:
- 创建Image实例。
- 调用Resource::Load()加载image。其中调用Image::BeginLoad()加载Image。
- 调用Deserailizer::ReadFileID()得到文件类型标志,根据类型读取图像数据。这里读取的是.png文件,文件头是“PNG ”。
- 调用外部库的函数stbi_load_from_memory()读取数据。
- 然后调用ResourceCache::GetTempResource
,这是一个类似于ResourceCache::GetResource 的调用。内部调用XMLFile::BeginLoad()。
在XMLFile::BeginLoad()中调用xml_document::load_buffer()读取XML文档。如果XML文件包含#include关键字,则又要调用ResourceCache::GetTempResource
3.3 Texture::EndLoad()
Texture2D::EndLoad()的工作如下:
- 调用SetParameters()。LoadMetadataFromXML()解析xml document。
- 遍历xml document,设置Texture2D的属性,如SetAddressMode()、SetBorderColor()等。
- 调用Image::SetData(),设置成员image_。
- 调用SetSize(),后者又调用Create()初始化。调用glGenTextures()创建 texture句柄,调用glTexParameteri()设置texture的参数。
- 调用另一个SetData()函数,参数是image的成员data_,这是图像的原始数据。其中调用调用glActiveTexture(GL_TEXTURE0)激活GL_TEXTURE0,调用glBindTexture()绑定texture,就是之前刚用glGenTextures()创建的句柄。
- 调用glTexImage2D()将image中的数据写入绑定的texture。
3.4 Texture绑定
绑定texture时,需要将glGenTextures()生成的texture handle,和Shader文件中定义的uniform sampler变量绑定到同一个texture unit上。
Texture unit根据用途定好了位置,就是上图中枚举类型TextureUnit规定的所有值。
哪个sampler绑定到哪个texture unit也是定好的,Grahpics的成员textureUnits_中,就保存了从sampler变量名到texture unit的映射。这个映射在Graphics::SetTextureUnitMappings()中硬编码。
有必要进一步说明的是sampler变量的名字。比如上图中的sDiffMap,包括两个部分,一是前缀的s,这是变量类型,表示sampler。二是DiffMap,这个才是保存在textureUnits_中的变量名。
首先是sampler变量端的绑定。
在ShaderProgram::Link()中,调用glGetProgramiv()得到所有uniform变量,并遍历。
- 调用glGetUniformLocation()得到变量位置。
- 如果这是个sampler变量,调用Graphics::GetTextureUit(),从Graphics::textureUnits_中查询相应texture unit。调用glUniform1iv()将sampler变量绑定到该texture unit。
然后是texture handle端的绑定。
渲染阶段会调用Graphics::SetTexture()。在Graphics::SetTexture()中,调用glActiveTexture()和glBindTexture()激活和绑定texture。
4. Resource之Font
4.1 Font定义
Font也是一种Reource,而真正意义上的字体是FontFace。Font是FontFace的容器。Font::GetFace()可以得到FontFace的实例。
FontFace的成员textures_是一个texture数组,每个texture是一些字符的图像集合。每个FontGlyph记录一个字符的信息,成员page_表示该字符在哪个texture中,另一些成员x_、y_、texWidth_、texHeight则进一步指出在该texture中的位置和大小。
FontFace的成员glyphMapping_是字符的编码到FontGlyph的映射。这样从字符编码就能找到它的图像了。
Urho3D支持两种字体:Freetype和bitmap。它们需要以不同的方式加载,FontFaceFreeType和FontFaceBitmap相应地提供FontFace::Load()的不同实现。FonFaceFreetype加载时使用外部库FT_Library,也就是FreeTypeLibrary的成员library_。
4.2 Font加载
Font::BeginLoad()将字体数据从文件加载到成员fontData_中,同时根据字体类型设置成员fontType_。字体类型是根据文件扩展名来判断的。
4.3 Font使用
使用字体时,调用Font::GetFace()。
- 根据fontType_值决定调用GetFaceFreeType()还是GetFaceBitmap()。
- 如果是GetFaceFreeType(),则创建FontFaceFreeType实例,调用FontFreeType::Load()。
FontFaceFreeType::Load()的工作如下:
- 调用FreeTypeLibrary::GetLibrary()得到外部库FT_Library句柄。
- 调用FT_XXX库系列函数,从数据中得到所有支持的字符编码,保存在一个本地数组charCodes[]中。数据来自来自Font::fontData_。
- 创建Image实例。
- 遍历charCodes[],调用LoadCharGlyph(),加载字符图像。
- 先调用FT_Load_Char()得到该字符在文件中的FontGlyph数据,其中包括图像在文件中的位置;再计算该字符在image中的位置和大小;再将字符图像读入image。将从字符编码到FontGlyph数据的映射保存到FontFace的成员glyphMapping_。
- 调用LoadFaceTexture()。 从image实例创建Texture2D实例,并保存在FontFace的成员textures_。
4.4 Text与Font
Text用于显示一段文字,它的成员text_是文本内容,成员printText_用字符编码数组表示text_。成员font_是文本的字体。GlyphLocation可以记录一个字符的位置,和引用的FontGlyph,这是绘制一个字符需要的信息。
Text的成员pageGlyphLocations_保存text_中的所有字符的GlyphLocaition。这是一个两层的数组,外层数组的索引对应于FontGlyph的成员page_,也就是字符对应的texture。把相同page_的字符安排在一起的原因是渲染效率,因为这样可以避免在不同的texture之间来回切换。
Text是一个UIElement,它实现UIELement的接口函数GetBatches(),以便渲染。(关于UIElement,请参见UI子系统)
- 调用UpdateCharLocation()。依次遍历成员printText_中的所有字符,调用FontFace::GetGlyph()查找其FontGlyph信息,按page_值分类保存在pageGlyphLocations_中。
- 创建UIBatch,并调用ConstructBatch()设置UIBatch。后者遍历pageGlyphLocaions_,对每个字符调用UIBatch::AddQuad()加入一组绘制的顶点VertexBuffer。
5. Resource之Model
5.1 建模文件布局
下图是建模文件布局的概要描述。
- 一组VertexBuffer,
- 一组IndexBuffer,
- 以Geometry lodlevel分层的二维Geometry数组。每个Geometry实例指定一组参数如下:
- type 图形类型
- vbref 对vertex buffer的引用
- ibref 对indexBuffer的引用
- indexStart, indexCount, 在引用的indexBuffer中的起始位置和长度。
enum PrimitiveType
{
TRIANGLE_LIST = 0,
LINE_LIST,
POINT_LIST,
TRIANGLE_STRIP,
LINE_STRIP,
TRIANGLE_FAN
};
5.2 Model
Model用于从建模文件中加载模型数据,它的成员vertexBuffers_是一组顶点数据,indexBuffers_是一组顶点索引数据。
Geometry是一次绘制的数据集合,它的成员vertexBuffers和indexBuffer_分别引用Model中的VertexBuffer和IndexBuffer。Model的成员gemetries_是一个按照geometry lodlevel分层的Geometry二维数组。
5.3 Model::BeginLoad()
Model::BeginLoad()的工作如下:
- 根据读取的vertexBuffer数目,依次创建VertexBuffer,并调用Deserializer::Read()读入数据。调用VertexBuffer::Unlock()时,调用glBufferData()绑定数据。
- 根据读取的IndexBuffer数组,依次创建IndexBuffer,并调用Deserializer::Read()读入数据。调用VertexBuffer::Unlock()时,调用glBufferData()绑定数据。
- 根据读取Geometry数据,创建Geometry二维数组,读入type、vref、iref等字段。
5.4 Model::EndLoad()
Model::EndLoad()的工作如下:
- 根据VertexBuffer和IndexBuffer的引用(也就是vref、iref字段的值),调用Geometry::SetVertexBuffer()、Geometry::SetIndexBuffer()、和Geometry::SetDrawRange()设置Geometry。
相关链接
Urho3D 1.7.1 源代码分析 (一)
Urho3D 1.7.1 源代码分析 (二)
Urho3D 1.7.1 源代码分析 (三)
Urho3D 1.7.1 源代码分析 (四)
Urho3D 1.7.1 源代码分析 (五)