Shader學習筆記 03 - 草

草對象

  1. 線性排列

image(圖片取自gpu gem1)html

頂點最少、須要對應紋理、法線等貼圖。效果很差。固定視角;遠處;或者加上廣告牌(billboard)效果(eg:遊戲漫漫長夜,若是不繞着草轉不必定能發現)可採用。git

  1. 交叉多邊形

image
image

頂點少、須要對應紋理、法線等貼圖。github

  1. 自定義模型

image

頂點多,更靈活,效果更好。c#

  1. C#程序或幾何着色器(geometry shader)生成

參考博客,就是程序生成頂點,而後鏈接面片生成草模型,幾何着色器須要dx10,目前手機應該不支持。app

批量草 //todo

生成
  1. GPU instance
  2. mesh 合併,通常須要程序式生成草體,也就是須要保存草的分佈信息,或者使用隨機生成。
排列

https://caseymuratori.com/blog_0011ide

優化 //TODO

chunk分塊;lod;函數

動畫

  • 草的擺動通常只須要根據風向移動頂點xz值,但移動幅度過大便會拉長模型,這時候便須要計算出y的偏移,從而保持草的長度相對不變。
  • 須要使用草的世界座標影響偏移值,若是沒有,批量草的擺動便會同樣。
底部剛性
  1. 使用uv的y值或者頂點的y值
  2. 比較複雜的是使用頂點顏色
xz偏移
  1. 使用三角函數
// 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;
  1. 使用噪聲
float noise = fbmNoise(worldPos.xz+_Time.y*_WindSpeed);
float wind = (noise*2.0-1.0)*_WindStrength;
  1. unity內置

文件TerrainEngine.cginc函數TerrainWaveGrass。核心原理也是使用正弦函數。post

y偏移

模型座標的中心通常不在底部,草根的位置能夠估算,也能夠在外部經過bounds.size獲取實際尺寸獲得準確的草根。優化

  1. 經過xz偏移量估算
示例vert:


動畫

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);}

  1. 經過勾股定理
示例vert:


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);}

  1. 經過三角函數
示例vert:
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);
  1. 經過旋轉矩陣
示例vert:


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);

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);
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);}

交互

障礙物推進草向物體外偏移

https://www.patreon.com/posts/19844414

// 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來實現碰撞搖擺不是很好的方案。

相關文章
相關標籤/搜索