-
准备工作
- 按照草地所在的位置 划分不同的cell , 每个cell 记录 cell里面草的位置
- 根据cell 列表 有序的生成一个草的位置列表,并将这个位置列表保存到ComputeBuffer
// 将所有排序完之后的草放入到cell 列表中 传入shader Vector3[] allGrassPosSortByCell = new Vector3[m_grassCnt]; int offset = 0; for (int i = 0; i < m_cellToPosList.Length; i++) { for (int j = 0; j < m_cellToPosList[i].Count; j++) { allGrassPosSortByCell[offset] = m_cellToPosList[i][j]; offset ++; } }
-
更新流程
-
将摄像机的可视距离调整到 草的可视距离范围
-
取得摄像机的视锥体, 将每个cell 转换成AABB包围盒,计算包围盒是否在视锥体内,粗略的剔除不在视野内的cell
-
以每个cell为单位,计算当前cell 处于 位置列表的偏移值,将偏移值传递到ComputeShader
for (var i = 0; i < m_visibleCellIdList.Count; i++) { var cellId = m_visibleCellIdList[i]; // 计算偏移起点 int memoryOffset = 0; for (var j = 0; j < cellId; j++) { memoryOffset += m_cellToPosList[j].Count; } cullingComputeShader.SetInt("_StartOffset", memoryOffset); var checkCnt = m_cellToPosList[cellId].Count; cullingComputeShader.Dispatch(0, Mathf.CeilToInt(checkCnt / 64f),1,1); }
-
ComputeShader 计算 当前草是否在视野内,将在视野内的草的id添加到列表里
[numthreads(64,1,1)] void CSMain(uint3 id : SV_DispatchThreadID){ // 将世界坐标做换成裁剪坐标 float4 absPosCS = abs(mul(_VPMatrix, float4(_GrassAllPosBuffer[id.x + _StartOffset], 1.0))); //_GrassVisibleIDBuffer.Append(id.x + _StartOffset); // 判断坐标在npc下的裁剪情况 if(absPosCS.z / <= absPosCS.w && absPosCS.y <= absPosCS.w * 1.5 && absPosCS.x <= absPosCS.w * 1.1 && absPosCS.w <= _MaxDistance){ _GrassVisibleIDBuffer.Append(id.x + _StartOffset); } }
-
获取ComputeShader计算好的信息,更新ArgsBuff里的绘制次数,使用DrawMeshInstancedIndirect 批量绘制 草地
// 获取当前草地的个数 ComputeBuffer.CopyCount(m_grassVisibleIDBuffer, m_grassRenderArgsBuffer, 4); // 绘制草地 Graphics.DrawMeshInstancedIndirect(m_grassMesh, 0, instanceMaterial, m_renderBounds, m_grassRenderArgsBuffer);
-
裁剪后的效果
-
渲染流程
- Instanced 的 shader中 会带有当前实例化的id, 由上面经过Cpu 跟ComputeShader计算 可以得出 当前可见草的id, 跟位置列表,综上就可以得出每个草所在的世界坐标,
Varyings vert(Attributes input , uint instanceID : SV_InstanceId){ float3 perGrassPivotPosWS = _GrassAllPosBuffer[_GrassVisibleIDBuffer[instanceID]].xyz ;}
- 因为我们用的草是只一个面 所以它需要时刻对着摄像机,也就是billboard.
- 直接在顶点着色器中计算当前的颜色, 根据 当前草的高度插值计算一下当前的颜色 可以形成一种从草根到草尖 一种由暗到明的效果,最后加上漫反射跟高光反射的效果
渲染后的效果
- Instanced 的 shader中 会带有当前实例化的id, 由上面经过Cpu 跟ComputeShader计算 可以得出 当前可见草的id, 跟位置列表,综上就可以得出每个草所在的世界坐标,
-
草的摇摆
草的摇摆也其实就正常的顶点偏移 ,就是有点不知道3个分向风的参数控制是为啥 可能这样摇摆起来看起来更加自然float wind = 0; wind += sin(_Time.y * _WindFrequency1 + perGrassPivotPosWS.x * _WindDir1.x + perGrassPivotPosWS.z * _WindDir1.y) * _WindIntensity1; wind += sin(_Time.y * _WindFrequency2 + perGrassPivotPosWS.x * _WindDir2.x + perGrassPivotPosWS.z * _WindDir2.y) * _WindIntensity2; wind += sin(_Time.y * _WindFrequency3 + perGrassPivotPosWS.x * _WindDir3.x + perGrassPivotPosWS.z * _WindDir3.y) * _WindIntensity3; wind *= input.positionOS.y; float3 windOffset = cameraTransformRightWS * wind; positionWS += windOffset;
-
行走的轨迹
- 一个很有趣的做法,使用拖尾组件 可以形成角色的行动轨迹,然后会缓慢消失的效果
- 使用renderfeature将行走的轨迹 绘制下来 传递到草的shader , 根据轨迹进行插值将草压低