KaaPexei 发表于 2023-1-20 19:29

【unity】卡通渲染--实现篇1(初步光照与描边)

【前言】



https://www.zhihu.com/video/1599628297838735360
本人在阅读完unity-chan源代码之后,对于卡通渲染有一定的初步了解基础上,开始尝试自己去做做二次元的渲染,期间跑过导出MMD模型,以及还有模型的骨骼修复,以及贴图的处理,还有相关mixamo进行动作的绑定,来达到能够有动作的效果。因为做二次元需要看到动态,一味做静态的效果那还不如画画?!
当然,正文主要介绍二次元的渲染相关内容,关于一堆杂事(例如模型处理,怎么弄动作)便不再赘述。
【正文】

光照渲染:

首先,我们先不介绍渲染,我们先看看色彩效果,当我们环境变暗的时候,我们的颜色需要怎么变化呢?需要往饱和度提升,以及还有明度下降来变化,围绕这一点,我们便建立了修改贴图的基础,毕竟明暗不是通过简单的调整V(明度)来实现,还有S(饱和度)来完善效果:



84899204

在进行修改贴图需要记住该美学原理,当然还有一些需要看的也要看看各位的功底,本人也只能帮到这里了。
首先,我们进行光照的模型搭建,本人主要选取半兰伯特模型进行光照效果呈现,通过(NOL+1)/2来建立基础的光照模型:



半兰伯特模型



半兰伯特模型的效果

完成这一步之后,我们需要通过光照的进一步处理来进行二分色调整,在这里,我主要使用了如下文章进行二分色处理:
主要方式是通过smoothstep来进行兰伯特模型的校正,主要汇入颜色的远端位置,以及还有调控兰伯特模型的参数,来达到ramp参数制作(当然常见方式是通过ramp图,可以蹲第2篇),接着把这个参数进行阴影色和基础色的lerp过渡来达到效果:



参数介绍



https://www.zhihu.com/video/1599618937737756672
完成这一步之后,我们需要进行光照阴影处理,在这里和unity-chan一样,通过step函数来调整阴影的位移,通过feather来调整整体软边效果,同时我也加入阴影修正,来进行整体阴影的明暗处理:



阴影算法

为了避免和之前的光照运算的ramp冲突,我在这里通过mix对阴影和ramp进行混合,来得到阴影色和主颜色的混合效果:



进行min混合



阴影开启,以及记得最后的软阴影的宏



https://www.zhihu.com/video/1599621980193480704
接着如上参数完成之后,本人将其汇入到lerp中进行阴影色和基础色的过渡,暗处为阴影色,亮处为基础色,阴影色通过PS进行效果处理,将暗度调低,以及饱和度调高,来制作阴影色:



效果对比图

完成完这一步之后,便已经初步搭建好整体的光照模型,后续往里面加什么SDF,或者什么深度边缘光,什么NPR都容易很多,以及还需要对头发进行处理也方便多了(当然等待第二期,我也就做了4小时呜呜呜),在这里文章便主要通过半兰伯特模型去初步做出来一个简单的光照效果。
描边:

在这里,和bulidin的forwardadd不一样的是,在URP中主要通过Tags来定义forward和SRPUnlit,所以在这里需要把pass的顺序写对来,先写forward用于光照渲染,然后再写用于描边的SRPUnlit:



第一个pass写forward



描边pass

描边的方法也挺简单,首先记得需要进行前向剔除,避免覆盖导致错误效果出现:



前向剔除



默认剔除的错误效果

接着主要通过摄像机离物体中心点的距离来进行smoothstep插值,来规避线条的过宽问题:



代码图

完成这一步后,把结果带入到法线外扩,得到描边效果:



进行法线外扩



https://www.zhihu.com/video/1599623676051275776
完成这一步之后,我们还需要在裁剪空间进行深度的位移,通过输出顶点在裁剪空间Z轴的位置,来调整该pass输出的深度远近,以此进行线条的深浅设计:



代码图



https://www.zhihu.com/video/1599625180195766272
在片元着色器,本人由于还没有进行顶点色处理,所以暂时输出自定义的颜色:



顶点色处理

其他:

接着由于需要进行不同组件之间的阴影处理,所以我们需要在里面多写入两个pass,用来表示深度以及阴影投射,直接复制litshader即可:



进行阴影效果搭建



阴影投射

【结论】

其实也挺简单,光照主要制作了阴影色和基础色,通过半兰伯特模型,以及平行光阴影进行min混合,来达到整体的光照效果,在描边方面通过摄像机与物体中心的远近来进行smoothstep来实现,并且汇入裁剪空间Z坐标调整描边的深浅细节效果。



效果图

【源代码】

Shader "ToonShader"
{
    //【汇入:贴图】
    Properties
    {
      
      _MainTex ("主贴图", 2D) = "white" { }//贴图汇入窗口:代码中的名字,开放的名字,类型,初始值
      _MainShadowTex ("主阴影贴图", 2D) = "black" { }//贴图汇入窗口:代码中的名字,开放的名字,类型,初始值
      _NormalTex ("法线贴图", 2D) = "bump" { }//贴图汇入窗口:代码中的名字,开放的名字,类型,初始值
      _NormalScale ("法线影响程度", float) = 1.0
       _Clipping ("Alpha裁剪", Float) = 0 //alpha剔除
      _ClipScale ("裁剪强度", Range(0.0, 1.0)) = 1.0

      
      _ShadowSmooth ("阴影平滑", Range(0, 1)) = 0
      _ShadowRange ("阴影范围", Range(0, 1)) = 0
      _ShadowsAdd ("阴影强度", Range(0, 1)) = 1

      
       _ShadowOn ("阴影调整", Float) = 1 //alpha剔除
      //_ShadowMaskTex ("阴影遮罩贴图", 2D) = "white" { }//贴图汇入窗口:代码中的名字,开放的名字,类型,初始值
      _ShadowChange ("阴影修正", Range(-0.5, 0.5)) = 0
      _Shadow_Step ("阴影位移", Range(0, 1)) = 0
      _Shadow_Feather ("阴影平滑", Range(0, 1)) = 0
      
      
      _OutLineColor ("描边颜色", Color) = (1, 1, 1, 1)
      _Outline_Width ("描边宽度", Range(0, 1)) = 1
      _Farthest_Distance ("描边最远距离", float) = 1
      _Nearest_Distance ("描边最近距离", float) = 0
      _Offset_Z ("描边摄像机调整", Range(0, 1)) = 0

      
      _HairHighLightTex ("高光贴图", 2D) = "black" { }
      _HighLightColor ("高光颜色", Color) = (1, 1, 1, 1)
    }
    //【模型的处理】
    SubShader
    {

      pass
      {
            //定义一个使用的语言类型
            Tags { "LightMode" = "UniversalForward" }
            HLSLPROGRAM

            //汇入模型的一堆数据
            struct appdata
            {
                float4 vertex : POSITION;//顶点的数据(位置)
                float3 normal : NORMAL;//汇入法线数据
                float4 tangent : TANGENT;//切线
                float2 uv : TEXCOORD0;//模型的UV坐标

            };
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"//函数库:主要用于各种的空间变换
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"//从unity中取得我们的光照
            TEXTURE2D(_MainTex);SAMPLER(sampler_MainTex);//采样贴图
            TEXTURE2D(_NormalTex);SAMPLER(sampler_NormalTex);//采样贴图
            TEXTURE2D(_ShadowMaskTex);SAMPLER(sampler_ShadowMaskTex);//采样贴图
            TEXTURE2D(_MainShadowTex);SAMPLER(sampler_MainShadowTex);//采样贴图
            TEXTURE2D(_HairHighLightTex);SAMPLER(sampler_HairHighLightTex);//采样贴图
            float _NormalScale;//法线影响程度
            float _Clipping;
            float _ClipScale;
            float _ShadowChange;
            float _Shadow_Step;
            float _Shadow_Feather;
            float _ShadowOn;
            float _ShadowSmooth;
            float _ShadowRange;
            float _ShadowsAdd;
            float4 _HighLightColor;
            //定义出顶点着色器
            #pragma vertex vert
            //定义出片元着色器
            #pragma fragment frag

            //阴影宏
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
            #pragma multi_compile _ _SHADOWS_SOFT

            
            //模型处理
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : TEXCOORD1;
                float3 vertex_world : TEXCOORD2;
                float3 tangent : TEXCOORD3;
                float3 bitangent : TEXCOORD4;
            };

            real MainLightRealtimeShadowUTS(float4 shadowCoord, float4 positionCS)
            {
                #if !defined(MAIN_LIGHT_CALCULATE_SHADOWS)
                  return 1.0h;
                #endif
                ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
                half4 shadowParams = GetMainLightShadowParams();
                #if defined(UTS_USE_RAYTRACING_SHADOW)
                  float4 screenPos = ComputeScreenPos(positionCS / positionCS.w);
                  return SAMPLE_TEXTURE2D(_RaytracedHardShadow, sampler_RaytracedHardShadow, screenPos);
                #endif

                return SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture), shadowCoord, shadowSamplingData, shadowParams, false);
            }

            v2f vert(appdata v)//顶点着色器

            {
                v2f o;//初始化
                //物体,从模型空间->裁剪空间
                o.vertex = TransformObjectToHClip(v.vertex);
               
                o.vertex_world = TransformObjectToWorld(v.vertex.xyz);//完成了顶点从模型空间转换到世界空间的位置
                o.normal = normalize(TransformObjectToWorldNormal(v.normal));//法线
                o.tangent = normalize(TransformObjectToWorldDir(v.tangent));//切线
                o.bitangent = normalize(cross(o.normal, o.tangent)) * v.tangent.w * unity_WorldTransformParams.w;//切线
                o.uv = v.uv;//顶点UV到片元UV
                return o;//返回值

            }
            half4 frag(v2f i) : SV_TARGET
            {
                //基础色与法线
                float4 base_color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);//得到基础颜色
                float4 base_shadow_color = SAMPLE_TEXTURE2D(_MainShadowTex, sampler_MainShadowTex, i.uv);//得到基础颜色
                float4 height_light_color = SAMPLE_TEXTURE2D(_HairHighLightTex, sampler_HairHighLightTex, i.uv);//得到基础颜色
                float4 pack_normal = SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, i.uv);//采样法线贴图
                float3 unpack_normal = UnpackNormal(pack_normal);//得到法线的具体数据

                //阴影
                float4 SHADOW_COORDS = TransformWorldToShadowCoord(i.vertex_world);//阴影变量
                Light mainLight = GetMainLight(SHADOW_COORDS);//得到主光源的方向
                half shadow = 1;
                if (_ShadowOn)
                {
                  shadow = mainLight.shadowAttenuation;//得到实时阴影
                  shadow = saturate(saturate(shadow * 2) * 0.5 + 0.5 + _ShadowChange);//阴影修正
                  shadow = saturate((shadow - (_Shadow_Step - _Shadow_Feather)) / (_Shadow_Feather + 0.0001));
                }

                float3 N = normalize(_NormalScale * unpack_normal.x * i.tangent + _NormalScale * unpack_normal.y * i.bitangent + unpack_normal.z * i.normal);//法线
                float3 L = mainLight.direction;//光照
                float3 V = normalize(_WorldSpaceCameraPos - i.vertex_world);//视角方向
                float3 H = normalize(V + L);//得到我们的半程向量

                //漫反射
                float NoL = max(0.0f, dot(N, L));//法线和光照的点乘
                float diffuse_var = (NoL + 1) / 2;//半兰伯特模型
                half ramp = saturate(smoothstep(0, _ShadowSmooth, diffuse_var - _ShadowRange) + (1 - _ShadowsAdd));

                float3 diffuse = mainLight.color.rgb * lerp(base_shadow_color, base_color, min(ramp, shadow));//漫反射的光

                if (_Clipping > 0)
                {
                  clip(base_color.a - 0.01 - (1 - _ClipScale));
                }

                return float4(diffuse , 1.0f);
            }

            ENDHLSL

            //定义一个使用的语言类型

      }

      Pass
      {
            Name "Outline"
            Tags { "LightMode" = "SRPDefaultUnlit" }
            Cull Front
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma target 2.0
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            float _Outline_Width;
            float _Farthest_Distance;
            float _Nearest_Distance;
            float _Offset_Z;
            float4 _OutLineColor;

            struct appdata
            {
                float4 vertex : POSITION;//顶点的数据(位置)
                float3 normal : NORMAL;//汇入法线数据
                float4 tangent : TANGENT;//切线
                float2 uv : TEXCOORD0;//模型的UV坐标

            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            v2f vert(appdata v)//顶点着色器

            {
                v2f o;//初始化
                //物体,从模型空间->裁剪空间
                float4 objPos = mul(unity_ObjectToWorld, float4(0, 0, 0, 1));//模型空间的(0,0,0)点转换到世界空间
                float Set_Outline_Width = (_Outline_Width * 0.01 * smoothstep(_Farthest_Distance, _Nearest_Distance, distance(objPos.rgb, _WorldSpaceCameraPos))).r;//描边宽度 * 基于摄像机远距进行smoothstep操作避免过宽问题的出现

                //v.2.0.7.5
                float4 _ClipCameraPos = mul(UNITY_MATRIX_VP, float4(_WorldSpaceCameraPos.xyz, 1));//摄像机裁剪空间坐标
                //v.2.0.4.3 baked Normal Texture for Outline
                o.vertex = TransformObjectToHClip(float4(v.vertex.xyz + v.normal * Set_Outline_Width, 1));//基于法线进行外扩
                //v.2.0.7.5
                o.vertex.z = o.vertex.z - 0.01 * _Offset_Z * _ClipCameraPos.z;//裁剪空间的Z轴进行偏移

                return o;//返回值

            }
            half4 frag(v2f i) : SV_TARGET
            {

                return _OutLineColor;
            }


            ENDHLSL
      }

      //【pass:深度】
      Pass
      {
            Name "DepthOnly"
            Tags { "LightMode" = "DepthOnly" }

            ZWrite On
            ColorMask 0
            Cull off

            HLSLPROGRAM
            // Required to compile gles 2.0 with standard srp library

            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment

            #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
      }
      pass
      {
            Tags { "LightMode" = "ShadowCaster" }
            ColorMask 0

            HLSLPROGRAM

            #pragma target 3.5
            //是否剔除的shader feature
            #pragma shader_feature _ _SHADOWS_CLIP _SHADOWS_DITHER
            //GPU需要CPU给它的数组数据
            #pragma multi_compile_instancing
            //设置LOD
            #pragma multi_compile _ LOD_FADE_CROSSFADE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"//函数库:主要用于各种的空间变换
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"//从unity中取得我们的光照
            #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
            #pragma vertex ShadowPassVertex
            #pragma fragment ShadowPassFragment
            ENDHLSL
      }
    }


    //【输出结果】

}
页: [1]
查看完整版本: 【unity】卡通渲染--实现篇1(初步光照与描边)