软光栅化渲染器学习笔记(一):简单几何图元
参考TinyRenderer项目,GAMES101和众多大佬的文章梳理出的软光栅化渲染器开发要点,仅作学习与记录。
接入SDL2实现创建窗口和绘制像素:
使用SDL2可以简单快捷地搭建窗口框架,我们只需要一个绘制像素点的基础功能即可,其它SDL2提供的绘制功能均不使用:
12345678910111213141516171819202122232425262728293031323334353637const int width = 600;const int height = 400;void DrawPixel(SDL_Renderer* renderer, int x, int y, Vector3f color) { SDL_SetRenderDrawColor(renderer, (Uint8)(color.x * 255), (Uint8)(color.y * 255), (Uint8)(color.z * 255), SDL_ALPHA_OPAQUE); SDL_Point point; point.x = x; point.y = y; SDL_RenderD ...
Assimp与模型渲染的故事:模型加载,骨骼蒙皮动画
想要开发一个好用的渲染引擎,加载/编辑模型和使用骨骼动画的功能是必不可少的,由于市面上的模型格式过多并且标准也互不统一,所以想要自己写出读取模型的算法是十分困难的,因此可以借助好用的模型加载库来完成这个工作,Assimp就是一个开源且支持格式较多的模型加载库,官方声称Assimp可以加载的模型格式有40种之多,但是实际上在加载一些模型格式时会时常出现错误,经本人测试部分fbx,部分3ds和所有obj模型能够成功加载的,由于fbx拥有骨骼动画的功能,所以我基本以使用fbx为主(但想要加载全部fbx模型还是只能用FBX SDK完成)。
Assimp架构
Assimp库使用的架构是典型的树状结构,而这个架构在模型加载中也是比较好用的,因为它足够清晰,也便于理清模型当中部件的父子关系,下图简单地呈现了Assimp的架构:
Struct
Content
aiScene
mRootNode,mMeshes,mMaterials,mAnimations,mTextures,mLights,mCameras
aiNode
mTransformation,mParent,mChild ...
从零开始的 Vulkan(附一):多Subpass实现延迟渲染
对于拥有大量光照的场景,经典的前向渲染(Forward Shading)由于其逐片元的计算通常会产生大量的开销,因此出现了许多用于解决这一问题的算法,常见的有延迟渲染(Deferred Shading)和Forward+渲染,在这一节中我们主要来研究使用Vulkan的Subpass技术来简化延迟渲染的实现(这也是Subpass的主要用途之一)。
原理
延迟渲染的主要优化方案是不使用传统光栅化的将每个图形片元的位置计算出来,再逐片元依次计算光照,取而代之的,只保留屏幕空间中所有需要显示的像素及其片元信息(通常称作G-Buffer),再依次为每个像素计算光照。延迟渲染的最大优点是在拥有极大量光源的场景中能大大减少计算量,最大缺点便是无法绘制任何透明物体(因为颜色混合操作的不可行)。
藉由这样的思路,我们得知需要实现延迟渲染至少要经过两个Pass,而在Vulkan中,这两个Pass可以放在一个RenderPass中由两个Subpass实现,第一个Subpass负责将需要的片元数据输出(获取G-Buffer),第二个Pass则负责进行逐片元的光照计算。
输出着色器
在第一个Subpass中,我 ...
从零开始的 Vulkan(六):同步与渲染循环
同步依赖
对于资源读写的同步一直是包括Vulkan在内的现代图形学API的重要议题,而在Vulkan中,隐式执行的同步操作非常有限,大部分的同步操作是交由用户手动操作,因此对于同步粒度的要求也上了一个台阶,在本节中,我将依据VkSpec上的描述详细探讨Vulkan中同步的细节。
执行依赖和内存依赖
Vulkan的许多同步是以操作集(Operation set) 为单位进行的,两个操作集之间的同步确保了第一个操作集的完成执行在第二个操作集的初始化之前,而为了实现这种操作集之间的同步,就需要定义不同的同步范围(Operation Scopes)。VkSpec向我们解释了同步操作大概的执行模式:
让Ops1(一号操作)和Ops2(二号操作)位于不同的操作集中。
定义一个Sync(同步)命令。
让Scope1st(一号范围)和Scope2st(二号范围)成为Sync的同步范围。
ScopedOps1(一号范围中操作)是Ops1和Scope1st的交集。
ScopedOps2(二号范围中操作)是Ops2和Scope2st的交集。
依次提交Ops1,Sync,Ops2到执行队列中,这会导致在Sc ...
从零开始的 Vulkan(五):描述符与渲染通道
描述符池与描述符集
创建描述符池
在上一节我们创建管线的时候,指定了管线布局需要应用的描述符集,但若要在绘制时真正使用管线布局中的描述符,还需要将描述符集实际创建出来再进行绑定。创建描述符的过程和创建命令缓冲区类似,需要先创建描述符池,再从描述符池中分配出描述符集。
创建描述符池的方法如下所示:
12345auto descriptorPoolInfo = vk::DescriptorPoolCreateInfo() .setMaxSets(maxSetsCount) .setPoolSizeCount(typeCount.size()) .setPPoolSizes(typeCount.data());vkInfo->device.createDescriptorPool(&descriptorPoolInfo, 0, &vkInfo->descPool);
从一个描述符池中可以分配出多个描述符集,而maxSets就指定了可以分配出描述符集的最大数量。poolSizeCount和pPoolSizes指定了描述符池中允许的不同描述符类型的数量。
在V ...
从零开始的 Vulkan(四):光栅化与渲染管线
和图像零距离接触:图形渲染管线
如今已经是脱离固定管线的新时代了,GPU也不再是一个无可控制的黑盒,我们可以根据自己的需求去定制硬件需要做的任务,当下主流的渲染方式主要是光栅化和光线追踪,Vulkan作为次世代图形API,也十分贴心地给我们准备了所有需要用的可编程管线:计算管线,图形管线,光线追踪管线。
这一节我们主要探讨光栅化在Vulkan中的实现方法,因此只会讲解图形管线的具体使用。
光栅化渲染流水线简述
GPU的光栅化一般由以下几个步骤组成(以下的部分概念来自DX12龙书):
输入装配器(Input Assembler,IA) 阶段会从显卡存储中读取几何数据(顶点和索引),再将它们依据图元拓扑(Primitive Topology)装配为几何图元(Geometric Primitive),送入后续的阶段。
待图元被装配完毕后,其顶点就会被送入顶点着色器阶段(Vertex Shader Stage),顶点着色器对数据的处理是逐顶点的,在这一阶段我们要完成顶点的坐标变换和数据传递,使所有顶点处于齐次裁剪空间中,由硬件完成透视除法(齐次除法)后,所有的顶点都位于规范化设备坐标(Nor ...
从零开始的 Vulkan(三):资源与内存管理
在之前使用Vulkan API的过程中,VkAllocationCallbacks的身影频繁出现,但是我并未对它作出解释,它实际上是使用Vulkan进行CPU内存管理的一个工具,这里我们就来好好研究一下。
内存管理
Vulkan中的内存分为两种:主机内存和设备内存。
主机内存
主机内存是使用Vulkan过程中需要的一种设备不可见的存储,VkAllocationCallbacks就是Vulkan提供一种用于手动管理主级内存分配的重要工具,尽管这并不是必要的,当我们不进行指定时,Vulkan就会使用默认的分配方式。 VkAllocationCallbacks的定义如下:
12345678typedef struct VkAllocationCallbacks { void* pUserData; PFN_vkAllocationFunction pfnAllocation; PFN_vkReallocationFunction pfnReallocation; PFN_vkFreeFunction pfnFree; PFN_vkInternalAllocationNot ...
从零开始的 Vulkan(二):交换链与命令缓冲区
一切为了渲染:交换链的创建
在此,我们将完成以下三件事:
为Win32平台上的Vulkan应用创建一个前置对象表面(Surface)。
创建一个符合我们需求的用于渲染和呈递图像的交换链。
创建交换链图像视图,它将用于对应我们在交换链上设置的图像。
前置对象表面surface
一般情况,不同平台开发的应用程序都有其各自的执行逻辑,例如在Win32下,一个窗口程序的创建和运行就需要窗口进程,窗口句柄,实例句柄和各种消息循环。为了标记一个Win32程序,我们需要得到对应的hWnd和hInstance,然后就可以将Vulkan和Win32程序系绑在一起。
以下宏确保了Vulkan对于Win32平台的正确识别:
1#define VK_USE_PLATFORM_WIN32_KHR
Khronos为了实现跨平台的目标,为不同平台创造了一个统一的抽象层,名为surface,它是将Vulkan和具体设备显示连接起来的一个桥梁,这里我展示了在win32平台下创建surface的方法,其它平台也是类似的。
123456const auto surfaceInfo = vk::Win32SurfaceC ...
从零开始的 Vulkan(一):设备与调试
前言
今年上半年Khronos正式推出了Vulkan1.3标准,借此机会正好让我这个已经两年没有碰过图形学的人重温一下“十分简约”的Vulkan标准,从零开始记录用Vulkan封装渲染引擎的过程。
介于我的大部分源码是在Vulkan1.1版本下写成的,且使用hpp版本,所以会有很多新特性无法顾及,以及代码会显得较为臃肿,若有不当之处还请见谅。
开发环境:ViusalStudio2022 C++11 Win32 Vulkan1.3.216.0
一切的开始:创建Vulkan实例和设备
在此,我们将完成三件事:
创建Vulkan实例(VkInstance),并将必要的层与实例扩展添加上去。
借用Vulkan实例创建一个用于验证层反馈错误的debugMessenger。
找到可用的GPU物理设备,并依托物理设备创建出统管全局的逻辑设备(VkDevice),同时添加需要的附加特性和设备扩展。
vkInfo结构体
由于Vulkan在使用过程中会有大量的结构体和对象冗余,所以我用了一个大结构体来容纳这些东西,这个结构体在后面的过程会经常用到:
12345678910111213141516171 ...