高真实感复杂光照环境光照场景模拟-软件设计方案
前言:
上一次笔者对高真实感的复杂光照进行了系统的需求设计和分析,给出了相应的用例图和类图进行说明。这次将对软件设计方案进行详细的阐述。
一、软件设计方案
1.软件架构
本项目是基于UE4的,采用的是“MVC”架构。将整个工作流程划分成了三个层次,分别是Model,View,Controller。
Model主要指的是Actor,Mesh,Material,Level,Resource等各种数据元素的内存表示。
Controller可以是各种算法,包括渲染算法,AI寻路,物理模拟等等。
View指的游戏的UI,可以是手柄的输入,也可以是与玩家直接交互的载体。

本项目主要是在移动渲染管线中实现延迟渲染,并且在Shader上有一定的表现能力(海水,风场等)。在Model层主要是GBuffer,VertexBuffer,ZBuffer,Texture,Lighting以及各种细化的RHI资源。Controller层则是实现延迟渲染算法,主动提交切换Render Target的Command给Render Thread,放入CmdList。在之后异步执行RHICommandList中的命令,对所有的渲染操作全部渲染到指定的GBuffer中,对ZBuffer不符合条件的片段,不予渲染,实现延迟渲染。View层则更注重相应Shader的表现。

2.设计模式
本项目主要是使用类模式中的模板方法模式 + 命令模式。
模板方法模式最大的特点是可以直接继承已有的父类特征,不需要书写过多的代码就可以实现相应的效果。缺点就是耦合性太强,如果父类做出了某些改变,子类也要相应变化。但是在UE4已经经过了很久的演变,并且它的架构已经十分成熟了,不存在太大的变化。而且继承自UOjbect,不仅可以提供诸如反射,GC,元数据,序列化等特性。还可以实现父类已定义好的接口,实现类接口的统一化。
选择命令模式则更出于对UE4的MVC架构以及三大Thread工作的机制的考虑。UE4的三大Thread:Game Thread,Render Thread,RHI Thread,为了不干涉各自的模块工作。采用的是异步工作方式,类似Windons下的事件机制。例如:当Render Thread有某个渲染Task,会生成很多的RHI Command,但Render Thread并不会直接跨模块的去执行RHI Command,而是将这些命令放入到RHI Command队列中。同时在需要执行的时候下发一个Task,然RHI Thread依次执行RHI队列中所有的Command。这样做的做法有很多的优点,显而易见的有两点:
1)模块之间的划分十分清晰并且耦合性很低
2)十分符合“开闭原则”,我们不需要去重写Thread的工作方式,只需要实现自己的命令类,在合适的地方放入相应的队列即可。

3.接口API

二、视图描述
1.依赖视图
依赖视图展现了软件模块之间的依赖关系。比如一个软件模块A调用了另一个软件模块B,那么我们说软件模块A直接依赖软件模块B。如果一个软件模块依赖另一个软件模块产生的数据,那么这两个软件模块也具有一定的依赖关系。 依赖视图在项目计划中有比较典型的应用。比如它能帮助我们找到没有依赖关系的软件模块或子系统,以便独立开发和测试,同时进一步根据依赖关系确定开发和测试软件模块的先后次序。 依赖视图在项目的变更和维护中也很有价值。比如它能有效帮助我们理清一个软件模块的变更对其他软件模块带来影响范围。

Game Play 和 Game Render模块聚合形成总的Game Engine模块。Game Render又依赖于下面的RHI资源模块。
2.泛化视图
泛化视图展现了软件模块之间的一般化或具体化的关系,典型的例子就是面向对象分析和设计方法中类之间的继承关系。值得注意的是,采用对象组合替代继承关系,并不会改变类之间的泛化特征。因此泛化是指软件模块之间的一般化或具体化的关系,不能局限于继承概念的应用。泛化视图有助于描述软件的抽象层次,从而便于软件的扩展和维护。比如通过对象组合或继承很容易形成新的软件模块与原有的软件架构兼容。

3.实现视图
执行视图展示了系统运行时的时序结构特点,比如流程图、时序图等。执行视图中的每一个执行实体,一般称为组件(Component),都是不同于其他组件的执行实体。如果有相同或相似的执行实体那么就把它们合并成一个。 执行实体可以最终分解到软件的基本元素和软件的基本结构,因而与软件代码具有比较直接的映射关系。在设计与实现过程中,我们一般将执行视图转换为伪代码之后,再进一步转换为实现代码。


文件目录:
- Binaries:存放编译生成的结果二进制文件。该目录可以gitignore,反正每次都会生成。
- Config:配置文件。
- Content:平常最常用到,所有的资源和蓝图等都放在该目录里。
- DerivedDataCache:“DDC”,存储着引擎针对平台特化后的资源版本。比如同一个图片,针对不同的平台有不同的适合格式,这个时候就可以在不动原始的uasset的基础上,比较轻易的再生成不同格式资源版本。gitignore。
- Intermediate:中间文件(gitignore),存放着一些临时生成的文件。有:
- Build的中间文件,.obj和预编译头等
- UHT预处理生成的.generated.h/.cpp文件
- VS.vcxproj项目文件,可通过.uproject文件生成编译生成的Shader文件。
- AssetRegistryCache:Asset Registry系统的缓存文件,Asset Registry可以简单理解为一个索引了所有uasset资源头信息的注册表。CachedAssetRegistry.bin文件也是如此。
- Saved:存储自动保存文件,其他配置文件,日志文件,引擎崩溃日志,硬件信息,烘培信息数据等。gitignore
- Source:代码文件。
4.执行视图
执行视图展示了系统运行时的时序结构特点,比如流程图、时序图等。执行视图中的每一个执行实体,一般称为组件(Component),都是不同于其他组件的执行实体。如果有相同或相似的执行实体那么就把它们合并成一个。 执行实体可以最终分解到软件的基本元素和软件的基本结构,因而与软件代码具有比较直接的映射关系。在设计与实现过程中,我们一般将执行视图转换为伪代码之后,再进一步转换为实现代码。

三、核心数据结构
Camera:
enum Camera_Movement { FORWARD, BACKWARD, LEFT, RIGHT }; const float YAW = -90.f; const float PITCH = 0.f; const float SPEED = 2.5f; const float SENSITIVITY = 0.1f; const float ZOOM = 45.f; class Camera { private: void update_camera_vectors(); public: glm::vec3 position; glm::vec3 front; glm::vec3 up; glm::vec3 right; glm::vec3 world_up; float yaw, pitch; float movement_speed, mouse_sensitivity; float zoom; Camera(glm::vec3 posi = glm::vec3(0.f, 0.f, 0.f), glm::vec3 u = glm::vec3(0.f, 1.f, 0.f), float y = YAW, float p = PITCH) : position(p), world_up(u), yaw(y), pitch(p), front(glm::vec3(0.f, 0.f, -1.f)), movement_speed(SPEED), mouse_sensitivity(SENSITIVITY), zoom(ZOOM) { update_camera_vectors(); } Camera(float pos_x, float pos_y, float pos_z, float up_x, float up_y, float up_z, float y, float p) :position(glm::vec3(pos_x, pos_y, pos_z)), world_up(glm::vec3(up_x, up_y, up_z)), yaw(y), pitch(p), front(glm::vec3(0.f, 0.f, -1.f)), movement_speed(SPEED), mouse_sensitivity(SENSITIVITY), zoom(ZOOM) { update_camera_vectors(); } void process_mouse_scroll(float yoffset); void process_mouse_movement(float xoffset, float yoffset, GLboolean constrain_pitch = true); void process_keyboard(Camera_Movement direction, float deltatime); glm::mat4 get_view_matrix(); inline glm::vec3 get_position() { return position; } };
Shader
class Shader { public: unsigned int program_id; float row; public: Shader(const char*, const char*, const char* = nullptr); ~Shader(); void check_compile_error(unsigned int, const char*); void check_link_error(unsigned int, const char*); inline void use() { glUseProgram(program_id); } inline void set_value_int_1(const char* name, int value) { glUniform1i(glGetUniformLocation(program_id, name), value); } inline void set_value_float_1(const char* name, float value) { glUniform1f(glGetUniformLocation(program_id, name), value); } inline void set_value_float_1(const string& str, float value) { glUniform1f(glGetUniformLocation(program_id, str.c_str()), value); } inline void set_value_bool_1(const char* name, bool value) { glUniform1i(glGetUniformLocation(program_id, name), (int)value); } inline void set_value_mat4(const char* name, glm::mat4 &uniform) { glUniformMatrix4fv(glGetUniformLocation(program_id, name), 1, GL_FALSE, &uniform[0][0]); } inline void set_value_mat3(const char* name, glm::mat3 &uniform) { glUniformMatrix3fv(glGetUniformLocation(program_id, name), 1, GL_FALSE, &uniform[0][0]); } inline void set_value_mat2(const char* name, glm::mat2 &uniform) { glUniformMatrix2fv(glGetUniformLocation(program_id, name), 1, GL_FALSE, &uniform[0][0]); } inline void set_value_vec3(const char* name, glm::vec3 &uniform) { glUniform3fv(glGetUniformLocation(program_id, name), 1, &uniform.x); } inline void set_value_vec3(const string& name, glm::vec3 &uniform) { glUniform3fv(glGetUniformLocation(program_id, name.c_str()), 1, &uniform.x); } inline void set_value_vec3(const char* name, float v1, float v2, float v3) { glUniform3f(glGetUniformLocation(program_id, name), v1, v2, v3); } inline void set_value_vec2(const char* name, glm::vec2 &uniform) { glUniform2fv(glGetUniformLocation(program_id, name), 1, &uniform.x); } inline void set_value_vec2(const char* name, float x, float y) { glUniform2f(glGetUniformLocation(program_id, name), x, y); } };
四、运行环境
基本环境:
Android移动端
支持OpenglES
推荐环境:
内存:4GB
CPU:高通骁龙636以上
五、技术选型
本项目是在UE4的源码环境下,对移动管线进行修改,是延迟渲染在移动端成为可能。
主语言使用的是C++,Shader语言采用的是HLSL,在UE4和VS2019的框架下开发该项目。
六、核心工作机制
延迟渲染的核心工作机制有以下三点:
1.生成自定义命令类型,该命令完成下列工作:请求相应的RHI Resource(g-buffer),对RHI Resource进行跟踪和绑定,Push到Render Thread Task队列中
2.生成相应的渲染命令,在执行RHI Command队列中的命令时。令所有的命令先跟z-buffer对比,并且如果不符合条件,予以丢弃。
3.对g-buffer进行像素采样,并结合光照,生成相应的RHI Command。
海水和风场等效果实现的核心工作机制则更取决于HLSL Shader的编写。
参考引用
[1] https://gitee.com/mengning997/se/blob/master/ppt/软件科学基础概论.pptx
[2] https://zhuanlan.zhihu.com/p/24170697
浙公网安备 33010602011771号