Foliage Rendering – 树叶体积感渲染(一)

Last modified date

通过修改法线的方式,制作出有自然柔和的体积感,并且跟随光影方向变化的树叶。

树叶的做法无非是插片,但是不管怎么插片,在平行光源下都不自然不好看,阴影边缘硬邦邦的。

我们想要的是有体块感的树叶。有个很简单出效果的方式就是把树叶的法线映射成球形的,因为我们最终想要的体块感很接近一个球形。

修改完法线后,就能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"
}

Share

This site is protected by wp-copyrightpro.com