Foliage Rendering – 树叶体积感渲染(一)
通过修改法线的方式,制作出有自然柔和的体积感,并且跟随光影方向变化的树叶。
树叶的做法无非是插片,但是不管怎么插片,在平行光源下都不自然不好看,阴影边缘硬邦邦的。
我们想要的是有体块感的树叶。有个很简单出效果的方式就是把树叶的法线映射成球形的,因为我们最终想要的体块感很接近一个球形。
修改完法线后,就能360度无死角的显示出柔和的体块感。
配上贴图,把光源转几个方向看看,很完美。
实现球体Normal除了用修改法线的方式,还可以直接在Shader里通过物体Pivot计算出来,详细可参考第二篇:Foliage Rendering – 树叶体积感渲染(二)
然后树叶渲染上我也用了一点小技巧,做法是用一张Mask贴图。
Mask贴图的通道G中的上下渐变是用来控制树叶摇摆的幅度,让根部不动。树叶的摇摆通过UV偏移实现,和做草地的方式差不多。可参考DynamicGrass的shader制作。
在通道R中画出树叶亮边的区域,然后在shader里把亮边的部分叠加进去,让树叶在受光面里边缘高亮起来,在背光面略微亮一点点。以此做出树叶在阳光下的通透光影效果。
最终效果:
Shader参考:
Shader “WalkingFat/TreeLeaves"
{
Properties
{
_MainTex ("Main Texture", 2D) = "white" {}
_MainColor ("Main Color", Color) = (1 ,1 ,1 ,1)
_ShadowColor ("Shadow Color", Color) = (1 ,1 ,1 ,1)
_MaskTex ("Mask Tex", 2D) = "white" {}
_EdgeLitRate ("Edge Light Rate", range(0,2))= 0.3
_Cutoff ("Cutoff", range(0,1))= 0.3
// shake with wind
_OffsetGradientStrength ("Offset Gradient Strength", range (0,1)) = 0.7
_ShakeWindspeed ("Shake Wind speed", float) = 0
_ShakeBending ("Shake Bending", float) = 0
_WindDirRate ("Wind Direction Rate", float) = 0.5
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100
Pass
{
Tags { "LightMode" = "ForwardBase" }
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
// make light work
#pragma multi_compile_fwdbase
#include "AutoLight.cginc"
// make shadow work
#pragma multi_compile_fwdbase_fullshadows
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 posWorld : TEXCOORD1;
float4 pos : SV_POSITION;
fixed4 diff : COLOR0; // diffuse lighting color
UNITY_FOG_COORDS(2)
LIGHTING_COORDS(3,4)
};
// proprety
sampler2D _MainTex, _MaskTex;
float4 _MainTex_ST, _MaskTex_ST;
float4 _MainColor, _ShadowColor;
float _OffsetGradientStrength, _ShakeBending, _EdgeLitRate, _ShakeWindspeed, _WindDirRate;
float _WindDirectionX, _WindDirectionZ, _WindStrength, _Cutoff;
void FastSinCos (float4 val, out float4 s, out float4 c)
{
val = val * 6.408849 - 3.1415927;
// powers for taylor series
float4 r5 = val * val;
float4 r6 = r5 * r5;
float4 r7 = r6 * r5;
float4 r8 = r6 * r5;
float4 r1 = r5 * val;
float4 r2 = r1 * r5;
float4 r3 = r2 * r5;
//Vectors for taylor's series expansion of sin and cos
float4 sin7 = {1, -0.16161616, 0.0083333, -0.00019841};
float4 cos8 = {-0.5, 0.041666666, -0.0013888889, 0.000024801587};
// sin
s = val + r1 * sin7.y + r2 * sin7.z + r3 * sin7.w;
// cos
c = 1 + r5 * cos8.x + r6 * cos8.y + r7 * cos8.z + r8 * cos8.w;
}
v2f vert (appdata v)
{
v2f o;
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// diffuse light
half3 worldNormal = UnityObjectToWorldNormal(v.normal);
o.diff = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
// get bend rate --------------------------
fixed4 grandientCol = tex2Dlod (_MaskTex, float4 (TRANSFORM_TEX(v.uv, _MaskTex), 0.0, 0.0));
float grandient = lerp (grandientCol.g, 1, 1 - _OffsetGradientStrength);
float xyzOffset = o.uv.y * grandient;
// waving force by wind ==========================================================
const float _WindSpeed = _ShakeWindspeed;
const float4 _waveXSize = float4 (0.048, 0.06, 0.24, 0.096);
const float4 _waveZSize = float4 (0.024, 0.08, 0.08, 0.2);
const float4 waveSpeed = float4 (1.2, 2, 1.6, 4.8);
float4 _waveXmove = float4 (0.024, 0.04, -0.12, 0.096);
float4 _waveZmove = float4 (0.006, 0.02, -0.02, 0.1);
float4 waves;
waves = v.vertex.x * _waveXSize;
waves += v.vertex.z * _waveZSize;
waves += _Time.x * waveSpeed * _WindSpeed + v.vertex.x + v.vertex.z;
float4 s, c;
waves = frac (waves);
FastSinCos (waves, s, c);
float waveAmount = v.uv.y * _ShakeBending;
s *= waveAmount;
s *= normalize(waveSpeed);
float fade = dot (s, 1.3);
float3 waveMove = float3 (0, 0, 0);
float windDirX = _WindDirectionX * _WindStrength;
float windDirZ = _WindDirectionZ * _WindStrength;
float windDirY = _WindStrength;
waveMove.x = dot (s, _waveXmove * windDirX);
waveMove.z = dot (s, _waveZmove * windDirZ);
waveMove.y = dot (s, _waveZmove * windDirY);
float3 windDirOffset = float3 (windDirX, windDirY, windDirZ) * _WindDirRate * xyzOffset;
float3 waveForce = -mul ((float3x3)unity_WorldToObject, waveMove).xyz * xyzOffset + windDirOffset;
v.vertex.xyz += waveForce;
o.pos = UnityObjectToClipPos(v.vertex);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
// using fog
UNITY_TRANSFER_FOG (o, o.pos);
TRANSFER_VERTEX_TO_FRAGMENT (o); // make light work
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 texCol = tex2D(_MainTex, i.uv);
fixed4 maskCol = tex2D(_MaskTex, i.uv);
fixed4 col = fixed4(texCol.rgb * _MainColor.rgb, 1);
// apply light and shadow
// float attenuation = LIGHT_ATTENUATION(i);
//fixed finalDiff = clamp (maskCol.r * _EdgeLitRate * (i.diff.r + 0.3) + i.diff.r, 0 , 1);
fixed4 shadowCol = lerp (_ShadowColor, fixed4 (1.4,1.4,1.4,1), i.diff.r);
fixed3 edgeCol = maskCol.r * _EdgeLitRate * (i.diff.r + 1) * fixed3(1,1,1);
// col *= 1 + maskCol.g * _EdgeLitRate;
clip (texCol.a * _Cutoff - 0.5);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return fixed4(col.rgb + edgeCol, col.a) * shadowCol;
// return fixed4(texCol.a,0,0,1);
}
ENDCG
}
Pass {
Name "ShadowCaster"
Tags {
"LightMode"="ShadowCaster"
}
Offset 1, 1
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma multi_compile_shadowcaster
#pragma multi_compile_fog
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 posWorld : TEXCOORD1;
float4 pos : SV_POSITION;
};
// proprety
sampler2D _MainTex, _MaskTex;
float4 _MainTex_ST, _MaskTex_ST;
float _OffsetGradientStrength, _ShakeBending, _EdgeLitRate, _ShakeWindspeed, _WindDirRate;
float _WindDirectionX, _WindDirectionZ, _WindStrength, _Cutoff;
void FastSinCos (float4 val, out float4 s, out float4 c)
{
val = val * 6.408849 - 3.1415927;
// powers for taylor series
float4 r5 = val * val;
float4 r6 = r5 * r5;
float4 r7 = r6 * r5;
float4 r8 = r6 * r5;
float4 r1 = r5 * val;
float4 r2 = r1 * r5;
float4 r3 = r2 * r5;
//Vectors for taylor's series expansion of sin and cos
float4 sin7 = {1, -0.16161616, 0.0083333, -0.00019841};
float4 cos8 = {-0.5, 0.041666666, -0.0013888889, 0.000024801587};
// sin
s = val + r1 * sin7.y + r2 * sin7.z + r3 * sin7.w;
// cos
c = 1 + r5 * cos8.x + r6 * cos8.y + r7 * cos8.z + r8 * cos8.w;
}
v2f vert (appdata v)
{
v2f o;
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// get bend rate --------------------------
fixed4 grandientCol = tex2Dlod (_MaskTex, float4 (TRANSFORM_TEX(v.uv, _MaskTex), 0.0, 0.0));
float grandient = lerp (grandientCol.g, 1, 1 - _OffsetGradientStrength);
float xyzOffset = o.uv.y * grandient;
// waving force by wind ==========================================================
const float _WindSpeed = _ShakeWindspeed;
const float4 _waveXSize = float4 (0.048, 0.06, 0.24, 0.096);
const float4 _waveZSize = float4 (0.024, 0.08, 0.08, 0.2);
const float4 waveSpeed = float4 (1.2, 2, 1.6, 4.8);
float4 _waveXmove = float4 (0.024, 0.04, -0.12, 0.096);
float4 _waveZmove = float4 (0.006, 0.02, -0.02, 0.1);
float4 waves;
waves = v.vertex.x * _waveXSize;
waves += v.vertex.z * _waveZSize;
waves += _Time.x * waveSpeed * _WindSpeed + v.vertex.x + v.vertex.z;
float4 s, c;
waves = frac (waves);
FastSinCos (waves, s, c);
float waveAmount = v.uv.y * _ShakeBending;
s *= waveAmount;
s *= normalize(waveSpeed);
float fade = dot (s, 1.3);
float3 waveMove = float3 (0, 0, 0);
float windDirX = _WindDirectionX * _WindStrength;
float windDirZ = _WindDirectionZ * _WindStrength;
float windDirY = _WindStrength;
waveMove.x = dot (s, _waveXmove * windDirX);
waveMove.z = dot (s, _waveZmove * windDirZ);
waveMove.y = dot (s, _waveZmove * windDirY);
float3 windDirOffset = float3 (windDirX, windDirY, windDirZ) * _WindDirRate * xyzOffset;
float3 waveForce = -mul ((float3x3)unity_WorldToObject, waveMove).xyz * xyzOffset + windDirOffset;
v.vertex.xyz += waveForce;
o.pos = UnityObjectToClipPos(v.vertex);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
TRANSFER_SHADOW_CASTER (o); // make shadow work
return o;
}
float4 frag(v2f i) : COLOR {
fixed4 texCol = tex2D(_MainTex, i.uv);
clip (texCol.a * _Cutoff - 0.5);
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
}
Fallback "VertexLit"
}