对马岛之魂草地生成

1.7k 词

分割地形

将地形划分为不同的块(tile) 每个tile有一套纹理 包含地形的高度 地形的材质 目的是能够区分哪些位置应该放哪些草 子部分的纹理由父图块采样得来,每一个tile有一个shader ,在tile的网格上获取一个位置添加一个偏移量来选择草面片的位置,然后Distance culling和frustum culling 然后在当前位置从贴图选择草的种类和高度,丢掉空草以及高度为0的草的地形最后Occlusion culling

terrian

不同草之间边缘部分链接

每个tile有一张512*512的纹理来映射草的类型(使用的艺术家创造的参数)作为8bit-index存入草的数组中 进行双线性插值是没有意义的(依然可以清楚的看到不同草的边界) 所以随机选择一种草的簇(clump)然后跟据四个簇的类型在中间位置进行加权

草片数据结构

grass_blade

Position 位置
Facing 叶面指向(2d向量)

Wind Strength at position 风强
Per-blade Hash 基于每个面片位置的哈希(驱动面片上所有参数包括动画)
GrassType 草的类型

Clump facing/color(聚集信息,通过面片所属的簇影响其他信息)

Height/Width/Tilt/Bend/Side Curve(控制叶片形状的各种参数)

Clump的影响

vornoi
根据之前的哈希来使网格顶点偏移,再根据voronoi图空间分割寻找最近邻点,通过这个距离来作为当前簇对其他簇的影响

Pipline

pipline
indirect draw args为图块绘制相关参数
pipline2
instancebuffer可以保存8个图块的草数据 每四个处理双缓冲buffer

Vertex Shader

每个图块是一个drawcall高低质量分开
vertex
对于每个顶点需要知道它沿面片长度的比值,面片的曲率不是稳定的所以顶点不会均匀分布(对马岛之魂中给了美术一个参数可以在高LOD与低LOD中转换,动态调顶点数量可能有很多问题)
tile
根据tile的划分,草的展开距离应该是原先的两倍(总数是一样的)便于由high lod tile 向low lod tile过度
2grass
一般所有的顶点用在一个面片上但如果草足够短的话,分为两个面片,

草的形状/Vertex Shader

三次Bezier curve

  • 位置易于计算
  • 法线容易确定
  • 根据控制点可以很好的控制形状 使草的动画变的很容易而且多样

bezier
推力(面片法线)大于0向上小于0向下
aa

  • 确定空间位置计算曲线(根据0-1确定沿面片的位置)
  • 找到orthogonal normal(面片宽度朝向)
  • 沿着草的朝向得到顶点的位置,根据顶点位置缩放
  • 曲线导数与orthogonal normal的叉积是表面法线朝向

受风场力的影响,风实际上是用户参数塑造的2d purlin噪声(实际上有一个更复杂的系统包含不同的噪声层在粒子系统中)
wind

然后就可以抖动了
shake
每个面片的散列偏移确保每个面片的运动是不同的,曲线的弧长不容易计算因为面片会为弧长设置动画

将草边缘法线弯曲,让草有一个更自然的圆形外观
noraml
改变草的朝向(面片顶点)使得草看起来更加厚
face face2

Pixel Shader

处理中远距离的镜面高光(尤其在雨中),草的动画会产生很多噪点
mirrorlight

贴图与材质
material

  • 光泽度
  • 与光泽度类似但沿着面片向下有宽度上的变化 颜色 间接颜色(纹理的v维度给出颜色因为随着叶片长度变化而变化 u维度由簇去控制而不是单独的)
  • 随叶片长度变化的恒定值 半透明在底部向叶尖减小
  • AO功能与上面的恒定值相同

为什么要输出AO值而不是输出SSAO?因为草不会存速度 草的无状态特性难以模拟 (目前解决方法是存上一帧的数据包括风的速度/方向,顶点位置,还要存一个位移缓冲防止玩家在草上走(玩家与草的互动)并且由于风的信息在shader中处理导致需要存储每个叶片的过程数据,性能受到限制)输出SSAO效果不佳 AO会变成一个斑点和一个亮的块

在有的地方能看到整个地图场景所以选择在地形上而不是底层的材质上渲染一个纹理
full

角色在隐藏草中的处理(补充)
草地阴影的特殊优化(补充)

后续优化

  • 艺术资产的放置(地图边缘的草生成)
  • 可以有更多的树叶类型或者小石头之类的生成
  • 每个tile的草的数量希望能与tile本身的大小分开
留言