# GPUDrivenTerrain **Repository Path**: alienity/GPUDrivenTerrain ## Basic Information - **Project Name**: GPUDrivenTerrain - **Description**: 在看了狗哥老司机和MaxwellGeng等大佬关于GPUDriven的实现,就参考Ubisoft实现一个简单的GPUDriven的Terrain的绘制 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2021-03-30 - **Last Updated**: 2023-12-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简介 在看了[狗哥老司机](https://zhuanlan.zhihu.com/p/352850047)和[MaxwellGeng](https://zhuanlan.zhihu.com/p/58311222)等大佬关于GPUDriven的实现,就参考Ubisoft实现一个简单的GPUDriven的Terrain的绘制 因为地形绘制时需要的数据不需要一直更新,所以只要在一开始传入地形的HeightMap和NormalMap之后,剩下的绘制操作基本不太需要传入其他大量的数据,非常适合GPUDriven。 使用GPUDriven的好处是可以在很少的几次DrawCall就可以绘制出整个地形场景,而且可以剔除掉不需要的面片,减少绘制的压力。 做GPUDrivenTerrain需要注意 - Hiz的生成,地表mesh的拆分与剔除 - 不同mip的mesh的临接的接缝处理 - 在GPU上实现的地形数据结构,以及灯光的处理(我没有去实现) # 实现流程 1. 使用一个64*64大小的mesh作为Instance的对象,可以通过四叉树把当前的地形切分,分成3级 2. 使用上一帧的depth计算出的Hiz和上一帧的ViewProjection的Matrix对当前帧的做一次Cull 3. 用第2步剪裁的结果绘制出深度,并生成当前深度的Hiz 4. 通过第3步生成的Hiz和当前帧的ViewProjection的Matrix再次对第2步剪裁后剩下的部分再做一次Cull 5. 对第4步得到的新的深度重新计算Hiz,作为本帧最终的Hiz,同时也作为下一帧的输入Hiz 6. 通过剔除操作得到需要绘制的mesh的ID和 我们在Unity上实现的时候,场景跟地形分开绘制,具体实现代码如下 ```c // 0、绘制得到Opaque的深度图 m_DepthPrepass.Setup(cameraTargetDescriptor, new RenderTargetIdentifier(m_DepthRenderTarget)); EnqueuePass(m_DepthPrepass); // 1、根据上一帧的Hiz和VP先剔除掉有可能被遮挡的地块,在Opaque的深度图上继续绘制深度 m_TerrainDepthPrepass.Setup(new RenderTargetIdentifier(m_DepthRenderTarget), m_HizRenderTarget, _VPPrevFrame); EnqueuePass(m_TerrainDepthPrepass); // 2、使用新的depth计算Hiz m_HizPass.Setup(new RenderTargetIdentifier(m_DepthRenderTarget), m_HizRenderTarget); EnqueuePass(m_HizPass); // 3、使用新的Hiz做剔除,绘制剩下的其实还存在的小块 m_TerrainDepthPrepass.Setup(new RenderTargetIdentifier(m_DepthRenderTarget), m_HizRenderTarget, _VPPrevFrame); EnqueuePass(m_TerrainDepthPrepass); // 4、计算当前帧的depth计算Hiz,以便下一帧使用 m_HizPass.Setup(new RenderTargetIdentifier(m_DepthRenderTarget), m_HizRenderTarget); EnqueuePass(m_HizPass); // 正常绘制场景 EnqueuePass(m_RenderOpaqueForwardPass); // 正常绘制地形 m_GPUTerrainPass.Setup(BuiltinRenderTextureType.CameraTarget, BuiltinRenderTextureType.CameraTarget); EnqueuePass(m_GPUTerrainPass); ``` 对于绘制阴影也是跟上面同样的剔除方法,只不过光源作为相机的位置和方向而已,跟正常的shadowmap绘制没有太大区别。 ![Game相机绘制的GPUDriven地形](https://img-blog.csdnimg.cn/20210408011636352.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlbmd5aWJpbmc=,size_16,color_FFFFFF,t_70#pic_center)![真实绘制的mesh以及不同的LOD](https://img-blog.csdnimg.cn/20210408011740579.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlbmd5aWJpbmc=,size_16,color_FFFFFF,t_70#pic_center) ### 接缝处理 参考狗哥老司机和Ubisoft介绍的,通过对mesh进行退化,可以防止不同mip等级的mesh之间相连接的时候会出现缝隙,通过把在小mip的一个点移动到已知的点的位置上,而这个移动的距离就存在mesh的color属性中。 使用的mesh是直接狗哥老司机的项目中的在顶点的color上设置了偏移值的mesh,右边是存到了alpha通道上,所以看不见 ![使用的](https://img-blog.csdnimg.cn/20210408012217469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlbmd5aWJpbmc=,size_16,color_FFFFFF,t_70#pic_center) 在实际绘制的时候,如下图所示,不同mip的mesh连接到一块儿的时候如果不做偏移的处理是这样的,小的mip会有一个顶点在大的mip边上,这样在根据heightmap对顶点的位置做偏移的时候,就会导致该边上多出来的这个顶点凸出去,而且产生缝隙 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210408012653473.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlbmd5aWJpbmc=,size_16,color_FFFFFF,t_70#pic_center) 处理的方法就是提前在在不同mip临接的边上,对低mip的mesh多出来的这个顶点做退化处理,这点跟Ubisoft提到的用greedy的方法预填充场景对象的mesh方法一样,要对mesh重合或者不同mip临接的顶点做退化处理,其实就是把这个顶点偏移一定的距离,使其与mesh内部的顶点重合。 ![不同mip连接的地方退化的mesh顶点](https://img-blog.csdnimg.cn/20210408011922705.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Rlbmd5aWJpbmc=,size_16,color_FFFFFF,t_70#pic_center) 做了顶点偏移处理后的mesh如上图所示,不同mip的mesh相接的地方就不会出现接缝 # 引用 [1] [https://zhuanlan.zhihu.com/p/335325149](https://zhuanlan.zhihu.com/p/335325149) [2] [https://zhuanlan.zhihu.com/p/352850047](https://zhuanlan.zhihu.com/p/352850047)