光线追踪系统--软件设计方案
一、软件结构特点
1.1、设计模式
对象模式+行为型模式:
整个系统中主要处理的是多个光线对象与多个物体对象间的关系。一个物体对象是由几何顶点数据与材质对象组合而成,因此光线对象与物体对象的联系实际上是光线与顶点数据以及材质对象的联系。
整个光线追踪处理的过程将不同的职责分配给了不同类和对象,并由它们的协同工作完成整个任务。通过光线产生着色器调用TraceRay()产生光线对象,然后由加速结构遍历对场景中的物体进行遍历并调用Intersection着色器求交,如果光线与物体有交点,则继续调用AnyHit着色器验证该相交点是否有效。如果该交点有效,则它会和已经找到的最近交点去比较并更新当前光线的最近交点。当整个场景和当前光线找不到新的交点后,则根据是否已经找到一个最近交点去调用接下来的流程,若没有找到则调用Miss着色器,否则调用ClosestHit着色器进行最终的着色。
对于每个着色器,我们可以单独修改替换其着色的算法而不会影响使用着色器的用户,因此属于行为型模式中的策略模式。
1.2、软件架构风格及其描述视图
整个光线追踪系统的渲染流水线天然就是管道-过滤器风格的结构。渲染流水线接收原始的顶点数据等输入,然后通过各个过滤器(即着色器)进行处理,最终得到渲染结果。
软件架构模型是通过一组关键视图来描述的,同一个软件架构,由于选取的视角和抽象层次不同可以得到不同的视图,这样一组关键视图搭配起来可以完整地描述一个逻辑自洽的软件架构模型。
本系统的一组关键视图如下:
- 分解视图
分解是的常见方法包括面向功能、面向特征、面向数据、面向并发、面向事件、面向对象等。
本系统采用面向功能的分解方法得到分解视图如下:
- 执行视图
系统的执行流程如下:
渲染流程图:
- 实现视图
项目的文件目录结构如下:
External DLLs目录存放外部的动态链接库。
Resource目录存放如模型数据、材质贴图等资源。
ImGui目录存放C++即时渲染UI组件,提供交互的UI界面。
D3DWindow目录存放初始化应用程序窗口以及初始化DirectX3D的代码。
Shaders目录存放渲染过程中所需要的着色器。
1.4、接口API
本光线系统本身是基于DirectX 3D 12这一套图形API进行开发。
UI界面使用C++的UI插件。
其他的接口API将在下面的核心数据结构中介绍。
二、核心数据结构
2.1、相机:
class Camera { protected: Float4x4 view;//观察矩阵 Float4x4 projection;//投影矩阵 Float4x4 viewProjection;//观察投影矩阵 Float4x4 world;//世界矩阵 Float3 position;//位置 Quaternion orientation;//四元数,控制相机旋转 float nearZ = 0.0f;//近平面 float farZ = 0.0f;//远平面 void CreateProjection();//创建投影矩阵 void WorldMatrixChanged(); public: //初始化 void Initialize(float nearZ, float farZ); //获取坐标变换矩阵、四元数、近平面、远平面 const Float4x4& ViewMatrix() const { return view; }; const Float4x4& ProjectionMatrix() const { return projection; }; const Float4x4& ViewProjectionMatrix() const { return viewProjection; }; const Float4x4& WorldMatrix() const { return world; }; const Float3& Position() const { return position; }; const Quaternion& Orientation() const { return orientation; }; float NearClip() const { return nearZ; }; float FarClip() const { return farZ; }; //获取相机各个方向的方向向量 Float3 Forward() const; Float3 Back() const; Float3 Up() const; Float3 Down() const; Float3 Right() const; Float3 Left() const; //设置相机各个参数 void SetLookAt(const Float3& eye, const Float3& lookAt, const Float3& up); void SetWorldMatrix(const Float4x4& newWorld); void SetPosition(const Float3& newPosition); void SetOrientation(const Quaternion& newOrientation); void SetNearClip(float newNearClip); void SetFarClip(float newFarClip); void SetProjection(const Float4x4& newProjection); };
2.2:材质:
struct Material { uint Albedo;//反射率 uint Normal;//法线 uint Roughness;//粗糙度、光滑度 uint Metallic;//金属参数 uint Opacity;//不透明度 uint Emissive;//自发光 };
2.3:光源:
struct SpotLight //锥型光源 { float3 Position;//光源位置 float AngularAttenuationX;//角度衰减 float3 Direction;//光源中心方向 float AngularAttenuationY; float3 Intensity;//光强 float Range;//光照范围 }; struct PointLight //点光源 { Float3 Position; Float3 Intensity; };
2.4:几何体信息:
struct GeometryInfo //几何体的存储信息 { uint VtxOffset;//物体顶点在整个顶点缓冲区中开始的位置 uint IdxOffset;//各个面的绘制顺序在整个索引缓冲区中开始的位置 uint MaterialIdx;//材质的索引 }; class Mesh //构成物体的mesh网格 { public: // 从文件中读取数据初始化mesh void InitFromAssimpMesh(const aiMesh& assimpMesh, float sceneScale, MeshVertex* dstVertices, uint8* dstIndices, IndexType indexType); // 根据数据创建特定类型的几何体 void InitBox(const Float3& dimensions, const Float3& position, const Quaternion& orientation, uint32 materialIdx, MeshVertex* dstVertices, uint16* dstIndices); void InitPlane(const Float2& dimensions, const Float3& position, const Quaternion& orientation, uint32 materialIdx, MeshVertex* dstVertices, uint16* dstIndices); void InitCommon(const MeshVertex* vertices, const uint8* indices, uint64 vbAddress, uint64 ibAddress, uint64 vtxOffset, uint64 idxOffset); // 访问几何体的数据:几何体的子物体和数量、顶点数量、顶点索引数量以及其存储位置 const Array<MeshPart>& MeshParts() const { return meshParts; } uint64 NumMeshParts() const { return meshParts.Size(); } uint32 NumVertices() const { return numVertices; } uint32 NumIndices() const { return numIndices; } uint32 VertexOffset() const { return vtxOffset; } uint32 IndexOffset() const { return idxOffset; } //返回面的绘制类型:如三角形、四边形等 IndexType IndexBufferType() const { return indexType; } DXGI_FORMAT IndexBufferFormat() const { return indexType == IndexType::Index32Bit ? DXGI_FORMAT_R32_UINT : DXGI_FORMAT_R16_UINT; } uint32 IndexSize() const { return indexType == IndexType::Index32Bit ? 4 : 2; } //访问顶点数组、索引数组 const MeshVertex* Vertices() const { return vertices; } const uint16* Indices() const { Assert_(indexType == IndexType::Index16Bit); return (const uint16*)indices; } const uint32* Indices32() const { Assert_(indexType == IndexType::Index32Bit); return (const uint32*)indices; } //访问顶点缓冲区、索引缓冲区 const D3D12_VERTEX_BUFFER_VIEW* VBView() const { return &vbView; } const D3D12_INDEX_BUFFER_VIEW* IBView() const { return &ibView; } //访问物体的包围盒 const Float3& AABBMin() const { return aabbMin; } const Float3& AABBMax() const { return aabbMax; } protected: Array<MeshPart> meshParts;//构成该物体的子物体 uint32 numVertices = 0;//顶点数量 uint32 numIndices = 0;//索引数量 uint32 vtxOffset = 0;//顶点存放位置 uint32 idxOffset = 0;//索引存放位置 IndexType indexType = IndexType::Index16Bit;//索引类型 const MeshVertex* vertices = nullptr;//顶点数组的指针 const uint8* indices = nullptr;//索引数组的指针 D3D12_VERTEX_BUFFER_VIEW vbView = { };//顶点缓冲区指针 D3D12_INDEX_BUFFER_VIEW ibView = { };//索引缓冲区指针 Float3 aabbMin;//内层包围盒 Float3 aabbMax;//外层包围盒 };
三、运行环境和技术选型
运行环境:Window10系统。
处理器:Intel i5九代cpu以上或i7四代cpu以上。
显卡:支持光线追踪的Nvidia系列,建议RTX20系列及RTX30系列显卡。
外部依赖库:
- assimp-vc140-mt.dll
- dxcompiler.dll
- dxil.dll
- WinPixEventRuntime.dll
四、工作机制
当启动系统后,系统首先初始化应用程序窗口,然后初始化UI,渲染默认的场景画面。渲染的机制是:从摄像机镜头位置到屏幕像素方向发射出一条光线然后追踪其路径,计算与场景中物体的最近有效交点计算该像素的颜色,然后呈现在屏幕的对应像素点上。该过程将由GPU进行并行计算,效率远高于CPU。根据用户设置的参数及硬件设备的不同,每帧画面的生成耗时将在6ms以上。在渲染过程中用户可以进行参数调整及镜头移动,同时会更新参数重新渲染画面。