简易二次元角色渲染风格(一)

Last modified date

弱化光影,无描边,有轻微立体感的清爽二次元渲染方式。

首先看看最终效果(角色模型动画来自于网络):

  • 按住鼠标左键可旋转角色
  • 点击左上角按钮可切换视角和角色

日系二次元的渲染风格以自发光为主,特别是皮肤要白皙干净。模型贴图部分不要带强烈的阴影,衣服只要画出固有色和褶皱部分。皮肤需要根据立体结构有一点明暗变化,特别是脖子和衣服遮盖的暗部。

先看看纯自发光的效果:

自发光效果已经看上去不错了。7分靠美术功底,3分靠shader添彩。

纯自发光缺乏立体感,我要加点类似diffuse的明暗变化,但是有没必要做实时光。于是我用一个cubemap实现这个效果。

  • cubemap一直朝向camera视角。
  • 贴图白色部分略偏右上角,可以造成光从右上角照过来的感觉,立体感更好点。
  • 贴图的黑白色当做lerp的系数“t”,另有一个cubecolor与固有色混合成更深一点的固有色“B”,与固有色“A”做计算。

日系风格不能脏,所以尽量少用黑色。日系水彩配色可以研究研究,从红黄暖色到蓝紫冷色,一定要干净通透。

现在立体感有了,还缺一点出彩的东西就是侧逆光。人像摄影的万能邪术侧逆光,啥玩意儿都能照出花儿来。

  • rim根据主平行光的角度变化。
  • rim宽度由一张横向黑白贴图控制。

脸部的侧逆光在某些角度不好看,我再加一张mask贴图来控制是否显示侧逆光。调整过程要反复测试,保证各个角度都尽量好看。

下图右边没有mask的,眼睛,鼻子,额头等地方的白色逆光很难看。左边加了mask就可以自由控制侧逆光位置。

这样大效果基本就ok了,我没有做的很细,主要是推销一下皮肤和衣服的shader做法。

这个demo里我还加了一些Post-processing的效果。

  • Bloom+HDR有曝光效果,配合shader里的Rim侧逆光,其本身就可以把颜色设高。
  • 后期矫色:把颜色调暖黄了一点,其实原本颜色也蛮好看的。

另外角色加了Dynamic Bone动态骨骼系统,让衣服和头发飘的自然点。

角色脚下的投影是RenderTexture做的,并且在shader里模糊了一下。

最后贴上shader代码:
Shader "WalkingFat/Toon1/Char02"
{
    Properties
    {
        _MainColor ("Main Color", Color) = (1.0, 1.0, 1.0, 1) // vertex color
        _MainTex ("Texture", 2D) = "white" {} // main texture

        _ToonCubeColor ("Toon Cube Color", Color) = (1.0, 1.0, 1.0, 1) 
        _ToonCube ("Toon Cube", Cube) = "" {} // cube map for toon light

        _RimColor("Rim Color", Color) = (0.5,0.5,0.5,1)
        _RimIntensity("Rim Intensity", Range(0.0, 100)) = 5.0
        _RimLightSampler("RimMask", 2D) = "white"{} 
        _RimMask ("Rim Mask", 2D) = "white" {} // main texture
    }

    SubShader
    {
        Tags
        { 
            "RenderType"="Opaque"
            "Queue" = "Geometry"
        }
        LOD 100

        Pass
        {
            Name "FORWARD"

            Tags
            {
                "LightMode" = "ForwardBase"
            }

            Cull Off
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 normal : NORMAL;
                float2 uv         : TEXCOORD0;
                float3 cubenormal : TEXCOORD1;
                float3 worldPos   : TEXCOORD2;
                float3 eyeDir     : TEXCOORD3;
                float3 lightDir   : TEXCOORD4; 
                UNITY_FOG_COORDS(5) // for fog
            };

            sampler2D _MainTex;
            float4 _MainColor;
            samplerCUBE _ToonCube;
            float4 _ToonCubeColor;
            float4 _MainTex_ST;
            float4 _RimColor;
            float _RimIntensity;
            sampler2D _RimLightSampler;
            sampler2D _RimMask;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.cubenormal = mul (UNITY_MATRIX_MV, float4(v.normal,0));  // get cube normal data  
//                cubenormal = mul (UNITY_MATRIX_MV, float4(v.normal,0));
                o.normal = mul(float4(v.normal,0), unity_WorldToObject).xyz; // get vertex normal in world space
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // get vertex pos in world space
                o.eyeDir.xyz = normalize( _WorldSpaceCameraPos.xyz - o.worldPos.xyz ).xyz;
                o.lightDir = WorldSpaceLightDir( v.vertex );
                UNITY_TRANSFER_FOG(o,o.vertex); // for fog
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv) * _MainColor; // get main texture color and blend with mainColor
                fixed4 cubeCol = texCUBE(_ToonCube, i.cubenormal); // get cube texture color
                fixed4 cubeAddCol = col * _ToonCubeColor;
                fixed4 finalCubeCol = lerp (cubeAddCol, col, cubeCol.r);
                // Rimlight
                float normalDotEye = dot( i.normal, i.eyeDir.xyz );
                float falloffU = clamp( 1.0 - abs( normalDotEye ), 0.02, 0.98 );

                float rimlightDot = saturate( 0.5 * ( dot( i.normal, i.lightDir ) + 1.5 ) );
                falloffU = saturate( rimlightDot * falloffU );
                falloffU = tex2D( _RimLightSampler, float2( falloffU, 0.25f ) ).r;
                float3 rimCol = falloffU * col * _RimColor * _RimIntensity;

                fixed4 rimMaskCol = tex2D(_RimMask, i.uv);
                rimCol *= rimMaskCol.r;

                fixed4 finalCol = fixed4(finalCubeCol + rimCol, col.a);

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, finalCol);
                return finalCol;
            }
            ENDCG
        }
    }
    Fallback "Unlit/Texture"
}

Share

This site is protected by wp-copyrightpro.com