目录
1、Shader控制一棵草的渲染
2、草地的动态交互
3、使用GPUInstancing渲染大面积的草
4、对大面积草地进行区域剔除和显示等级设置
大家好,我是阿赵。
这里继续讲大面积草地渲染的第二个部分,草地动态交互。这里主要有风吹效果和球体碰撞效果2种。
一、风吹效果
Unity使用shader控制草的渲染和动画
风吹动草的效果,主要还是使用顶点程序来控制顶点的偏移
回顾一下之前的基础草的shader的顶点程序
v2f vert (appdata v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.centerPos = mul(unity_ObjectToWorld, float4(float3(0, 0, 0), 1)).xyz;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
float hVal = smoothstep(_hmin, _hmax, o.worldPos.y - o.centerPos.y);
float vVal = smoothstep(_vmin, _vmax, distance(o.worldPos.xz, o.centerPos.xz));
float hvVal = hVal * vVal;
o.hvVal = float3(hVal, vVal, hvVal);
float hVertexOffset = hvVal * _hOffset;
float2 vVertexOffset = (o.worldPos.xz - o.centerPos.xz)*hvVal*_vOffset;
o.pos = UnityObjectToClipPos(v.vertex+float3(vVertexOffset.x, hVertexOffset, vVertexOffset.y));
return o;
}
在这个顶点程序里面,已经使用顶点偏移来实现了草的受重力弯曲的形态,所以现在只需要对它进行一些小修改,加入一个风力的影响,就可以实现左右摇摆的效果了。
这里我添加一个风的方向,还有一个Sin曲线来模拟风的左右摇摆:
float2 wind = _windOffset * hVal*_SinTime.w;
float2 windPosXZOffset = vVertexOffset + wind;
o.pos = UnityObjectToClipPos(v.vertex + float3(windPosXZOffset.x, hVertexOffset, windPosXZOffset.y));
通过调节windOffset,可以让草摆动的幅度产生变化
修改之后的完整shader是这样的:
Shader "azhao/GrassWind"
{
Properties
{
_MainTex("MainTex", 2D) = "white" {}
_hmin("hmin", Range(0 , 1)) = 0
_hmax("hmax", Range(0 , 1)) = 1
_hOffset("hOffset", Range(-1 , 1)) = 0
_vmin("vmin", Range(0 , 1)) = 0
_vmax("vmax", Range(0 , 1)) = 1
_vOffset("vOffset", Range(-5 , 5)) = 0
_topCol("topCol", Color) = (0,1,0,0)
_windOffset("windOffset", Vector) = (0,0,0,0)
_bottomCol("bottomCol", Color) = (0,0,0,0)
}
SubShader
{
Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" }
Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityShaderVariables.cginc"
#pragma target 3.0
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 centerPos : TEXCOORD1;
float3 worldPos : TEXCOORD2;
float3 hvVal : TEXCOORD3;
};
uniform float _hmin;
uniform float _hmax;
uniform float _vmin;
uniform float _vmax;
uniform float _vOffset;
uniform float2 _windOffset;
uniform float _hOffset;
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform float4 _topCol;
uniform float4 _bottomCol;
v2f vert (appdata v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.centerPos = mul(unity_ObjectToWorld, float4(float3(0, 0, 0), 1)).xyz;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
float hVal = smoothstep(_hmin, _hmax, o.worldPos.y - o.centerPos.y);
float vVal = smoothstep(_vmin, _vmax, distance(o.worldPos.xz, o.centerPos.xz));
float hvVal = hVal * vVal;
o.hvVal = float3(hVal, vVal, hvVal);
float hVertexOffset = hvVal * _hOffset;
float2 vVertexOffset = (o.worldPos.xz - o.centerPos.xz)*hvVal*_vOffset;
float2 wind = _windOffset * hVal*_SinTime.w;
float2 windPosXZOffset = vVertexOffset + wind;
o.pos = UnityObjectToClipPos(v.vertex+float3(windPosXZOffset.x, hVertexOffset, windPosXZOffset.y));
return o;
}
half4 frag (v2f i) : SV_Target
{
// sample the texture
half4 col = tex2D(_MainTex, i.uv);
half3 finalCol = col.rgb * _topCol.rgb*i.hvVal.z + col.rgb;
finalCol = clamp(finalCol*i.hvVal.x + _bottomCol * (1 - i.hvVal.x)*finalCol, half3(0, 0, 0), half3(1, 1, 1));
half alpha = col.a;
clip(alpha - 0.5);
return half4(finalCol,alpha);
}
ENDCG
}
//为了产生影子,加多一个pass
Pass {
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 centerPos : TEXCOORD1;
float3 worldPos : TEXCOORD2;
float3 hvVal : TEXCOORD3;
};
uniform float _hmin;
uniform float _hmax;
uniform float _vmin;
uniform float _vmax;
uniform float _vOffset;
uniform float2 _windOffset;
uniform float _hOffset;
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform float4 _topCol;
uniform float4 _bottomCol;
v2f vert(appdata v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.centerPos = mul(unity_ObjectToWorld, float4(float3(0, 0, 0), 1)).xyz;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
float hVal = smoothstep(_hmin, _hmax, o.worldPos.y - o.centerPos.y);
float vVal = smoothstep(_vmin, _vmax, distance(o.worldPos.xz, o.centerPos.xz));
float hvVal = hVal * vVal;
o.hvVal = float3(hVal, vVal, hvVal);
float hVertexOffset = hvVal * _hOffset;
float2 vVertexOffset = (o.worldPos.xz - o.centerPos.xz)*hvVal*_vOffset;
float2 wind = _windOffset * hVal*_SinTime.w;
float2 windPosXZOffset = vVertexOffset + wind;
o.pos = UnityObjectToClipPos(v.vertex + float3(windPosXZOffset.x, hVertexOffset, windPosXZOffset.y));
return o;
}
half4 frag(v2f i) : SV_Target
{
// sample the texture
half4 col = tex2D(_MainTex, i.uv);
clip(col.a - 0.5);
return col;
}
ENDCG
}
}
}
二、和球的交互
unity草地互动
这里说的和球交互,是因为我不想花太多的时间去放一个角色模型进去并控制,所以我就简单的用一个球来代替。其实是模拟了一个人在草丛里面行走时,对草的交互。
既然是草被人挤开的动画,那么还是使用顶点动画来做了
这里需要定义一下球对草地的影响范围
假设球的坐标可以输入到shader里面,那么求出草的顶点和球坐标的距离,就可以过滤出离球比较近的一圈才会受到球的影响。
在刚才的顶点程序上面,再添加一点代码:
float roleDis = (1 - distance(o.worldPos.xz, rolePos.xz));
float2 roleNor = (o.worldPos.xz - rolePos.xz)*step(0, roleDis)*(roleDis*_roleMul);
float2 rolePosXZOffset = vVertexOffset + wind * (1 - roleNor) + roleNor * hVal;
float rolePosYOffset = hVertexOffset - saturate(roleDis*_roleHOffset);
o.pos = UnityObjectToClipPos(v.vertex+float3(rolePosXZOffset.x, rolePosYOffset, rolePosXZOffset.y));
return o;
需要注意:
1、rolePos不需要在shader的Properties里面声明,它是一个全局的变量,在C#代码里面,使用Shader.SetGlobalVector(“rolePos”, role.transform.position);来给它赋值
2、这里模拟的是一个角色的影响,如果是多个角色在草地里面移动,可以把rolePos全局变量变成数组,具体的实现可以自己试试。
这时候顶点的控制就受到了3个方面的影响:
1、重力
2、风力
3、球体的碰撞
完整的Shader:
Shader "azhao/GrassWindBall"
{
Properties
{
_MainTex("MainTex", 2D) = "white" {}
_hmin("hmin", Range(0 , 1)) = 0
_hmax("hmax", Range(0 , 1)) = 1
_hOffset("hOffset", Range(-1 , 1)) = 0
_vmin("vmin", Range(0 , 1)) = 0
_vmax("vmax", Range(0 , 1)) = 1
_vOffset("vOffset", Range(-5 , 5)) = 0
_topCol("topCol", Color) = (0,1,0,0)
_windOffset("windOffset", Vector) = (0,0,0,0)
_bottomCol("bottomCol", Color) = (0,0,0,0)
_roleMul("roleMul", Range(0 , 10)) = 0
_roleHOffset("roleHOffset", Range(0 , 10)) = 0
}
SubShader
{
Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" }
Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 centerPos : TEXCOORD1;
float3 worldPos : TEXCOORD2;
float3 hvVal : TEXCOORD3;
};
uniform float _hmin;
uniform float _hmax;
uniform float _vmin;
uniform float _vmax;
uniform float _vOffset;
uniform float2 _windOffset;
uniform float3 rolePos;
uniform float _roleMul;
uniform float _hOffset;
uniform float _roleHOffset;
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform float4 _topCol;
uniform float4 _bottomCol;
SamplerState sampler_MainTex;
v2f vert (appdata v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.centerPos = mul(unity_ObjectToWorld, float4(float3(0, 0, 0), 1)).xyz;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
float hVal = smoothstep(_hmin, _hmax, o.worldPos.y - o.centerPos.y);
float vVal = smoothstep(_vmin, _vmax, distance(o.worldPos.xz, o.centerPos.xz));
float hvVal = hVal * vVal;
o.hvVal = float3(hVal, vVal, hvVal);
float hVertexOffset = hvVal * _hOffset;
float2 vVertexOffset = (o.worldPos.xz - o.centerPos.xz)*hvVal*_vOffset;
float2 wind = _windOffset * hVal*_SinTime.w;
float roleDis = (1 - distance(o.worldPos.xz, rolePos.xz));
float2 roleNor = (o.worldPos.xz - rolePos.xz)*step(0, roleDis)*(roleDis*_roleMul);
float2 rolePosXZOffset = vVertexOffset + wind * (1 - roleNor) + roleNor * hVal;
float rolePosYOffset = hVertexOffset - saturate(roleDis*_roleHOffset);
o.pos = UnityObjectToClipPos(v.vertex+float3(rolePosXZOffset.x, rolePosYOffset, rolePosXZOffset.y));
return o;
}
half4 frag (v2f i) : SV_Target
{
// sample the texture
half4 col = tex2D(_MainTex, i.uv);
half3 finalCol = col.rgb * _topCol.rgb*i.hvVal.z + col.rgb;
finalCol = clamp(finalCol*i.hvVal.x + _bottomCol * (1 - i.hvVal.x)*finalCol, half3(0, 0, 0), half3(1, 1, 1));
half alpha = col.a;
clip(alpha - 0.5);
return half4(finalCol,alpha);
}
ENDCG
}
//为了产生影子,加多一个pass
Pass {
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 centerPos : TEXCOORD1;
float3 worldPos : TEXCOORD2;
float3 hvVal : TEXCOORD3;
};
uniform float _hmin;
uniform float _hmax;
uniform float _vmin;
uniform float _vmax;
uniform float _vOffset;
uniform float2 _windOffset;
uniform float3 rolePos;
uniform float _roleMul;
uniform float _hOffset;
uniform float _roleHOffset;
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform float4 _topCol;
uniform float4 _bottomCol;
SamplerState sampler_MainTex;
v2f vert(appdata v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.centerPos = mul(unity_ObjectToWorld, float4(float3(0, 0, 0), 1)).xyz;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
float hVal = smoothstep(_hmin, _hmax, o.worldPos.y - o.centerPos.y);
float vVal = smoothstep(_vmin, _vmax, distance(o.worldPos.xz, o.centerPos.xz));
float hvVal = hVal * vVal;
o.hvVal = float3(hVal, vVal, hvVal);
float hVertexOffset = hvVal * _hOffset;
float2 vVertexOffset = (o.worldPos.xz - o.centerPos.xz)*hvVal*_vOffset;
float2 wind = _windOffset * hVal*_SinTime.w;
float roleDis = (1 - distance(o.worldPos.xz, rolePos.xz));
float2 roleNor = (o.worldPos.xz - rolePos.xz)*step(0, roleDis)*(roleDis*_roleMul);
float2 rolePosXZOffset = vVertexOffset + wind * (1 - roleNor) + roleNor * hVal;
float rolePosYOffset = hVertexOffset - saturate(roleDis*_roleHOffset);
o.pos = UnityObjectToClipPos(v.vertex + float3(rolePosXZOffset.x, rolePosYOffset, rolePosXZOffset.y));
return o;
}
half4 frag(v2f i) : SV_Target
{
// sample the texture
half4 col = tex2D(_MainTex, i.uv);
clip(col.a - 0.5);
return col;
}
ENDCG
}
}
}