开放世界记录

4.1k words

简介

开始尝试做开放世界游戏,记录用的插件和要考虑的东西

地形切割

world-streamer-2

  • 提供对于Unity Terrain组件进行切割,并生成多级Low网格
    Image text
    • 在设定区域内使用Terrain,区域外则使用Mesh渲染处理后的Low网格
  • 地形会切割成多个场景,提供场景流式加载
    • 场景的异步加载
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      IEnumerator LoadAsync()
      {
      // yield return new WaitForSeconds(0.5f);

      //Debug.Log("scenesToLoad " + scenesToLoad.Count);
      int[] sceneLoadIndexes = new int[scenesToLoad.Count];
      for (int i = 0; i < scenesToLoad.Count; i++)
      {
      int sceneID = SceneManager.sceneCount;
      SceneSplit split = scenesToLoad[i];
      AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(split.sceneName, LoadSceneMode.Additive);


      asyncOperation.completed += (operation) =>
      {

      SceneLoadComplete(sceneID, split);
      OnOperationDone(operation);

      };


      asyncOperations.Add(asyncOperation);
      yield return null;
      }
      scenesToLoad.Clear();
      //Debug.Log("Load finished " + asyncOperations.Count);
      operationStarted = false;
      }

      IEnumerator UnloadAsync()
      {
      yield return null;

      //Debug.Log(asyncOperations.Count);
      for (int i = 0; i < scenesToUnload.Count; i++)
      {
      AsyncOperation asyncOperation = SceneManager.UnloadSceneAsync(scenesToUnload[i]);
      asyncOperation.completed += OnOperationDone;
      asyncOperations.Add(asyncOperation);

      yield return null;
      }
      scenesToUnload.Clear();


      //Debug.Log("Unload finished " + asyncOperations.Count);
      operationStarted = false;
      }
  • 浮点数错误修复系统,根据设置,在移动指定数量格子后,将整体位置移动到原点,不会产生,浮点数过大导致的浮点数精度问题

worldstreamer

地形贴图混合

Virtual Texture

地形和Mesh混合

图形引擎实战:移动端URP地形贴图融合与HBAO实现 - 搜狐畅游引擎部的文章 - 知乎

地形LOD的poping问题

为了避免两个LOD级别之间的突变,可以使用简单的线性插值,通过线性插值因子从0-1的变换,实现LOD的变换

CDLOD
基于使用在顶点着色器中置换的固定网格网格来直接从高度图数据源渲染地形

Continuous Distance-Dependent Level of Detail for Rendering Heightmaps (CDLOD) - 生鱼片的文章 - 知乎
ShaderToy

CDLOD过渡算法
1
2
3
4
5
6
7

const float2 g_gridDim = float2( 64, 64 );
float2 morphVertex( float2 gridPos, float2 vertex, float morphK )
{
float2 fracPart = frac( gridPos.xy * g_gridDim.xy * 0.5 ) * 2.0 / g_gridDim.xy;
return vertex.xy - fracPart * g_quadScale.xy * morphK;
}

PCA压缩

地表贴图压缩:7通道2采样→4通道1采样 - profhua的文章 - 知乎
使用PCA,对贴图进行压缩
网易GDC2023中提到将 PBR 所需要的 diffuse(rgb)、normal(xyz)、ao、roughness、metallic,总共九个通道,通过 PCA 压缩成 一张具有4通道的贴图
【GDC2023干货分享】90帧高质量写实手游的程序实现与美术设计 - 网易游学的文章 - 知乎

Image text
Image text

大片草地

草地编辑器
Image text
插件存在的问题

  • 剔除操作是对所有存储在显存中的草数据,对做一遍视锥体裁剪,开销比较大
    • 优化方式 1:UnityURP-MobileDrawMeshInstancedIndirectExample 里用到的裁剪方式,将草根据位置划分为一个个AABB,CPU计算AABB的裁剪结果,然后传递给GPU,ComputeShader只需要计算通过视锥裁剪的草数据,而不是全部数据
    • 优化方法 2:作者后续更新,通过四叉树对空间进行划分成AABB,然后进行剔除,和方式1一样
  • 交互方式上,只是根据传递的角色位置,顶点进行相应偏移,不适用于多个移动物体
    • 优化方式:UnityURP-MobileDrawMeshInstancedIndirectExample 中,将角色运动轨迹用LineRenderer保存,将这个结果存为一张RT图,保存法线信息,采样这张RT图可以得到相应路线结果,可以设置相关参数控制草地从被压扁到恢复的时间
  • 没有写入深度,会导致渲染时结果被覆盖

细节记录

草地和地形贴图混合
Image text

大气渲染

altos
可以提供动态日夜切换,指定太阳、月亮贴图,提供体积云,并有次表面散射效果
细节记录

Image text
问题:云的结果把草的结果覆盖了
原因:草地编辑器没有写入深度法线信息,需要添加对应的Pass
解决后效果

Image text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Pass
{
Name "DepthNormals"
Tags{"LightMode" = "DepthNormals"}

ZWrite On
Cull[_Cull]

HLSLPROGRAM
#pragma exclude_renderers gles gles3 glcore
#pragma target 4.5

#pragma vertex vert
#pragma fragment DepthNormalsFragment

//因为是自己计算生成的Mesh,不需要考虑法线贴图的采样以及透明度的混合
// -------------------------------------
// Material Keywords
// #pragma shader_feature_local _NORMALMAP
// #pragma shader_feature_local _PARALLAXMAP
// #pragma shader_feature_local _ _DETAIL_MULX2 _DETAIL_SCALED
// #pragma shader_feature_local_fragment _ALPHATEST_ON
// #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A

//--------------------------------------
// GPU Instancing
// #pragma multi_compile_instancing
// #pragma multi_compile _ DOTS_INSTANCING_ON

#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
// #include "Packages/com.unity.render-pipelines.universal/Shaders/LitDepthNormalsPass.hlsl"


float4 DepthNormalsFragment(v2f input) : SV_TARGET
{
half3 normalWS = input.normalWS;
return half4(NormalizeNormalPerPixel(normalWS), 0.0);
}

ENDHLSL
}

全局光照

使用天空盒或固定颜色实现的环境光不能有效的适应周边环境和动态光源,可以通过预辐射度全局光照实现

PRTGI 细节记录
Image text
Image text

Unity自带的实时全局光照,目前仅支持Buildin,SRP计划在Unity2024后支持
Image text
Image text

阴影

用的Unity自带的级联阴影,四级感觉够了,八级感觉没必要,根本看不到

动物生态

计划用行为树简单的实现,主要包括以下行为

  • 行为模拟,食物搜索、躲避捕食者
  • 领地模拟,动物在领地之间会产生相互竞争和冲突
  • 群体行为,模拟动物群体间的领导、集体防御行为
  • 环境交互,对环境的交互性,包括地形、季节、气候的处理,饮水、觅食

Boids算法,可以实现集群行为.将个体行为分为规避、结盟、凝聚共同作用。
Vector3 direction = separation+ alignment + (cohesion - boid.position).normalized;

Boids