(圖片取自gpu gem1)html
頂點最少、須要對應紋理、法線等貼圖。效果很差。固定視角;遠處;或者加上廣告牌(billboard)效果(eg:遊戲漫漫長夜,若是不繞着草轉不必定能發現)可採用。git
頂點少、須要對應紋理、法線等貼圖。github
頂點多,更靈活,效果更好。c#
參考博客,就是程序生成頂點,而後鏈接面片生成草模型,幾何着色器須要dx10,目前手機應該不支持。app
https://caseymuratori.com/blog_0011ide
chunk分塊;lod;函數
// 1. 直接使用正弦函數 float speed = _Time.x * _Speed; float x = sin(wpos.x + speed);// x偏移 float z = sin(wpos.z + speed);// z偏移 // 2. 來自 https://blogs.unity3d.com/2018/08/07/shader-graph-updates-and-sample-project/ float sine = sin(_Time.y*_WindSpeed)*0.1; sine = lerp(0.1, sine, min(sine,1)); // 使正弦波有一些隨機的小波動 float wind = Remap(_WindStrength*_SinTime,float2(-1,1),float2(-0.1,0.1)) + _WindStrength*sine;
float noise = fbmNoise(worldPos.xz+_Time.y*_WindSpeed); float wind = (noise*2.0-1.0)*_WindStrength;
文件TerrainEngine.cginc函數TerrainWaveGrass。核心原理也是使用正弦函數。post
模型座標的中心通常不在底部,草根的位置能夠估算,也能夠在外部經過bounds.size獲取實際尺寸獲得準確的草根。優化
float _Rigidness; float _WindSpeed; float _WindStrength; float _YOffsetRate; void vert(inout appdata_full v) { float4 worldPos = mul(unity_ObjectToWorld, v.vertex); float2 uv = v.texcoord; float offset = pow(uv.y,5);
動畫float2 dir = normalize(_WindDirection.xz); float noise = fbmNoise(worldPos.xz/_Rigidness+_Time.y*dir*_WindSpeed); float wind = (noise*2.0-1.0)*_WindStrength; worldPos.xz += wind*dir; float gravityForce = abs (wind*_YOffsetRate); worldPos.y -= gravityForce * offset; float4 swayPos = mul(unity_WorldToObject, worldPos); v.vertex.xyz = lerp(v.vertex.xyz, swayPos.xyz, offset);}float2 dir = normalize(_WindDirection.xz); float noise = fbmNoise(worldPos.xz/_Rigidness+_Time.y*dir*_WindSpeed); float wind = (noise*2.0-1.0)*_WindStrength; worldPos.xz += wind*dir; float gravityForce = abs (wind*_YOffsetRate); worldPos.y -= gravityForce * offset; float4 swayPos = mul(unity_WorldToObject, worldPos); v.vertex.xyz = lerp(v.vertex.xyz, swayPos.xyz, offset);
float _Rigidness; float _WindSpeed; float _WindStrength; float4 _RootPos; void vert(inout appdata_full v) { UNITY_INITIALIZE_OUTPUT(Input,o); float4 worldPos = mul(unity_ObjectToWorld, v.vertex); float2 uv = v.texcoord; float offset = pow(uv,5);
float2 dir = normalize(_WindDirection.xz); float noise = fbmNoise(worldPos.xz/_Rigidness+_Time.y*dir*_WindSpeed); float wind = (noise*2.0-1.0)*_WindStrength; worldPos.xz += wind*dir; float4 vertexAdj = v.vertex - _RootPos; float c = length(vertexAdj.xyz); float a = length(wind); worldPos.y -= (c - sqrt(c*c - a*a)); float4 swayPos = mul(unity_WorldToObject, worldPos); v.vertex.xyz = lerp(v.vertex.xyz, swayPos.xyz, offset);}float2 dir = normalize(_WindDirection.xz); float noise = fbmNoise(worldPos.xz/_Rigidness+_Time.y*dir*_WindSpeed); float wind = (noise*2.0-1.0)*_WindStrength; worldPos.xz += wind*dir; float4 vertexAdj = v.vertex - _RootPos; float c = length(vertexAdj.xyz); float a = length(wind); worldPos.y -= (c - sqrt(c*c - a*a)); float4 swayPos = mul(unity_WorldToObject, worldPos); v.vertex.xyz = lerp(v.vertex.xyz, swayPos.xyz, offset);
float _Rigidness; float _WindSpeed; float4 _RootPos; float _MaxAngle;float Remap(float x, float2 inMinMax, float2 outMinMax)
{
return (x - inMinMax.x)* (outMinMax.y - outMinMax.x)/(inMinMax.y - inMinMax.x) + outMinMax.x;
}float3 AngleRot(float4 vet, float angle, float2 dir)
{
float s,c;
sincos(angle,s,c);float3 rot = 0; float4 vertexAdj = vet - float4(0,-1,0,0); float vlen = length(vertexAdj.xyz); float xzL = vlen*s; rot.xz = dir*xzL; float yl = vlen - vlen*c; rot.y = -yl; return rot;
}
void vert(inout appdata_full v)
{
UNITY_INITIALIZE_OUTPUT(Input,o);
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
float2 uv = v.texcoord;
float offset = pow(uv,5);float2 dir = normalize(_WindDirection.xz); float noise = fbmNoise(worldPos.xz/_Rigidness+_Time.y*dir*_WindSpeed); float maxAngle = _MaxAngle*UNITY_PI/180; float angle = Remap(noise, float2(0,1),float2(-maxAngle,maxAngle)); worldPos.xyz += AngleRot(v.vertex, angle, dir); float4 swayPos = mul(unity_WorldToObject, worldPos); v.vertex.xyz = lerp(v.vertex.xyz, swayPos.xyz, offset);
}
float3 rot = 0; float4 vertexAdj = vet - float4(0,-1,0,0); float vlen = length(vertexAdj.xyz); float xzL = vlen*s; rot.xz = dir*xzL; float yl = vlen - vlen*c; rot.y = -yl; return rot;float2 dir = normalize(_WindDirection.xz); float noise = fbmNoise(worldPos.xz/_Rigidness+_Time.y*dir*_WindSpeed); float maxAngle = _MaxAngle*UNITY_PI/180; float angle = Remap(noise, float2(0,1),float2(-maxAngle,maxAngle)); worldPos.xyz += AngleRot(v.vertex, angle, dir); float4 swayPos = mul(unity_WorldToObject, worldPos); v.vertex.xyz = lerp(v.vertex.xyz, swayPos.xyz, offset);
float _Rigidness; float _WindSpeed; float _WindStrength; float4 _RootPos; float _MaxAngle;
// Construct a rotation matrix that rotates around the provided axis, sourced from:
// https://gist.github.com/keijiro/ee439d5e7388f3aafc5296005c8c3f33
float3x3 AngleAxis3x3(float angle, float3 axis)
{
float c, s;
sincos(angle, s, c);float t = 1 - c; float x = axis.x; float y = axis.y; float z = axis.z; return float3x3( t * x * x + c, t * x * y - s * z, t * x * z + s * y, t * x * y + s * z, t * y * y + c, t * y * z - s * x, t * x * z - s * y, t * y * z + s * x, t * z * z + c );
}
void vert(inout appdata_full v)
{
UNITY_INITIALIZE_OUTPUT(Input,o);
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
float2 uv = v.texcoord;
float offset = pow(uv,5);float t = 1 - c; float x = axis.x; float y = axis.y; float z = axis.z; return float3x3( t * x * x + c, t * x * y - s * z, t * x * z + s * y, t * x * y + s * z, t * y * y + c, t * y * z - s * x, t * x * z - s * y, t * y * z + s * x, t * z * z + c );float2 dir = normalize(_WindDirection.xz); float noise = fbmNoise(worldPos.xz/_Rigidness+_Time.y*dir*_WindSpeed); float maxAngle = _MaxAngle*UNITY_PI/180; float angle = Remap(noise, float2(0,1),float2(-maxAngle,maxAngle)); float3x3 bendRotationMatrix = AngleAxis3x3(angle, normalize(float3(dir.y,0,dir.x))); float3 vertexAdj = mul(bendRotationMatrix,v.vertex.xyz - _RootPos.xyz); vertexAdj += _RootPos.xyz; v.vertex.xyz = lerp(v.vertex.xyz, vertexAdj, offset);}float2 dir = normalize(_WindDirection.xz); float noise = fbmNoise(worldPos.xz/_Rigidness+_Time.y*dir*_WindSpeed); float maxAngle = _MaxAngle*UNITY_PI/180; float angle = Remap(noise, float2(0,1),float2(-maxAngle,maxAngle)); float3x3 bendRotationMatrix = AngleAxis3x3(angle, normalize(float3(dir.y,0,dir.x))); float3 vertexAdj = mul(bendRotationMatrix,v.vertex.xyz - _RootPos.xyz); vertexAdj += _RootPos.xyz; v.vertex.xyz = lerp(v.vertex.xyz, vertexAdj, offset);
// c#腳本,將障礙物座標,總障礙物數量賦值給shader變量 { public Transform[] obstacles; private Vector4[] obstaclePositions = new Vector4[10]; Update { for (int n = 0; n < obstacles.Length; n++) { obstaclePositions[n] = obstacles[n].position; } Shader.SetGlobalFloat("_ObstacleLength", obstacles.Length); Shader.SetGlobalVectorArray("_ObstaclePositions", obstaclePositions); } } // shader uniform float3 _ObstaclePositions[100]; uniform float _ObstacleLength; vert { float2 xzShift = 0; for (int i = 0; i < _ObstacleLength; i++){ float3 dis = distance(_ObstaclePositions[i], worldPos.xyz); // 頂點於障礙物座標中心距離 float3 radius = 1 - saturate(dis /_Radius); // 半徑內越接近障礙物中心值越大,中心爲1,半徑外爲0 float3 sphereDisp = worldPos - _ObstaclePositions[i]; // 障礙物中心指向頂點的向量 sphereDisp *= radius; // 偏移值衰減 xzShift += sphereDisp.xz; // 偏移值累加 } // 方法一、二、4,xzShift累加 // 方法3 float len = length(xzShift); if(len != 0) { float obsMaxAngle = _MaxAngle*UNITY_PI/180; float obsAngle = Remap(len, float2(0,_MaxGrassLength),float2(0,obsMaxAngle)); worldPos.xyz += AngleRot(v.vertex, obsAngle, normalize(xzShift)); } }
碰撞草體觸發草的搖晃,觸發器+動畫(或者在c#腳本控制頂點偏移)就是比較好的解決方案。
使用shader比較麻煩,難點在於確認觸發點所在草體的全部頂點,而且須要在腳本里根據時間不斷修正偏移。github.com/wachel/UnityInteractiveGrass,這個是找到以shader實現的交互草,裏面保存了草體的mesh,經過mesh獲取整個草體的頂點並佔用了Tangant屬性來與shader傳遞搖擺的信息。感受使用shader來實現碰撞搖擺不是很好的方案。