实现逻辑 Runtime Terrain GPU Driven Terrain 以一张高度图为Source直接在GPU端进行视锥剔除,LOD计算以及T junction Terrain进行了分块,由n个Section构成Component,n个Component构成地形,Component是碰撞更新单位,Section是 LOD与剔除单位,在最高LOD下,section是一块由两个三角面拼成的方形,方块数以2^n增长 SolveSectionBound SolveSectionLOD DoFrustumCull Result BuildInstanceData indirectDraw DrawVertex 构建高度图 对2D spline采样退化成Polygon并传入GPU,由GPU计算perpixel SDF,并通过SDF绘制不同种类spline的高度图,最 终降得到的三张高度图进行blend,得到用于地形的最终高度图 mountainHegihtMap spline(bezierCurve) 退化 polygon GPU SDF heightMap lakeHegihtMap riverHegihtMap Code terrain.SolveBound(cs, cmd); terrain.SolveLOD(cs, cmd, cameraPos, screenMultiple); terrain.BuildDrawElements(cs, cmd, _frustumPlanes); terrain.SetupTerrainConstant(terrain.terrainMaterial); terrain.SetupTerrainTexture(terrain.terrainMaterial); terrain.SetupTerrainBuffer(terrain.terrainMaterial); terrain.DrawTerrain(terrain.terrainMaterial, cmd, isWireFrameMode); Result terrainHeightMap 构建分区信息以及湿度 通过上一步得到的各种类spline的高度图,分批进行区域求解,分区规则如下 高山可以切断矮山的区域 河流可以切断山面的区域,切断的所有区域共享河的湿度 湖可以切断山面的区域,切断的所有区域共享湖的湿度 lakeHeightMap riverHeightMap lakeArea riverArea mountainHeightMap unionSet mountainArea unionSetWithMark unionSetWithMark Code public void BuildHumidityAndArea(TerrainBuilder terrainBuilder, TerrainEcoSystemConfig ecoConfig) { // init float[] humidityCache = new float[UnionSet.DataArray.Length]; NativeArray<uint> markBuffer = new NativeArray<uint>(UnionSet.DataArray.Length, Allocator.TempJob); BuildUnionSetGlobalParam.Builder = terrainBuilder; BuildUnionSetGlobalParam.TargetUnionSet = UnionSet; CleanUp(); // mountain pass BuildMountainUnionSet(markBuffer, terrainBuilder); BuildMarkBuffer(markBuffer); SolveRiverHumidity(humidityCache, terrainBuilder); // river pass UnionSet.Reset(); BuildRiverUnionSet(markBuffer, terrainBuilder); BuildMarkBuffer(markBuffer); SolveLakeHumidity(humidityCache, terrainBuilder); // lake pass UnionSet.Reset(); BuildLakeUnionSet(markBuffer, terrainBuilder); // final pass BuildIndexDicAndAreaMember(); SolveFinalHumidity(humidityCache, terrainBuilder); RefreshAreaEcologicalIndex(ecoConfig); // clean up markBuffer.Dispose(); } Result 植被生成 利用上一步形成的区域以湿度信息,通过区域高度决定每个区域所属的生态,并为所有区域生成植物 植物拥有排斥圈(即在一定距离内不得生成该植物),生态具有植物的生成密度,用来控制该区域内植物的生成数量 为了解决排斥圈问题,会在初始化时使用possion disk法生成一系列采样点,然后在生产植被时,从这些点中拣选想 生成的植被 possionDiskSample edgeCull terrainSDF areaCull areaData randomAreaPlant PossionDiskSample 性能热点 SDF构建 使用GPU进行Polygon的SDF构建,一条spline需要PerPixel跑一遍全图SDF 并查集构建 为了提取区域信息,需要通过回读下来的高度图构建并查集来分出区域信息,mountain,river,lake一共需要跑三 遍并查集构建 PossionDiskSample 对于每一个生态的每一种植物,需要烘焙一组点用于后续的生成,由于只需要在初始化的时候做一次,所以开销相 对并不严重 CollisonUpdate 每次刷新地形之后,需要对地形的碰撞进行更新 离屏RT 目前必须的离屏RT有9张,包括 3张高度RT 1张SDF 1张湿度信息 1张最终高度图 1张最终Normal 1张mountain normal 1张烘焙的网格贴图 实机Profile 环境: RTX 3060 10850K 分辨率: 1080P 高度图分辨率: 1024 x 1024 打包设置: il2cpp,development build,release,incremental GC,D3D11 测试场景: 上文截图中的场景 修改地形时出现的性能尖峰 第一个尖峰是由GPU构建SDF造成的,在两帧后,回读完毕,CPU开始更新碰撞,湿度,植被信息,此时出现第二 个尖峰 第一个尖峰 第二个尖峰 更新碰撞 54ms 计算区域信息 157ms 生成植物 60ms