Unity Shader - Simple Toon Shading - 简单卡通渲染

news/2024/7/8 4:26:29

文章目录

  • 最终效果 - Final Effect
  • 无光照,只有纹理与主色调
    • Shader
  • 加描边 - Outline
    • GIF
    • Shader
  • 添加光影 - RecieveShadow
    • 自身接收阴影
    • Shader
  • 调整阴影 - Adjusting Shadow Params
    • Shader
  • 无透视法线挤出描边
    • Shader
    • 整体运行效果
  • 高光 - Specular
    • Shader
  • 边缘光 - Rim
    • Shader
  • 控制边缘光在背光时才显示 - Rim Show At Back To the Lighting
    • Shader
  • Project
  • References

使用的是Unity内置管线,后面有时间再学习:LWRP(URP),还有HDRP,学习任务有点多,一步一步来

本来想弄个资源来学习:后处理实现:深度+法线描边的
但现在有个适合的资源,还是先处理一下简单的卡通渲染效果吧
而且弄好后,后面做其他的效果也有个比较好的模型来做实验

最终效果 - Final Effect

刀的法线是有问题的,可能是建模的同学法线没处理好
子模型、Mask纹理都还不够细分,否则某些部位的光影可以控制得很完美
在这里插入图片描述
下面一步步来显示

无光照,只有纹理与主色调

在这里插入图片描述
可以看到纹理中部分的边缘信息也话上去了,如:白色丝绸的边缘,有还超短裙上的黑边条纹。
这些一般不是高级超模的几何体模型,都会画在纹理上。

Shader

// jave.lin 2019.08.25
Shader "Test/Toon" {
    Properties {
        _MainTex ("MainTex", 2D) = "white" {}
        _MainColor ("MainColor", Color) = (1,1,1,1)
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass { // solid
            Name "Solid"
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct v2f {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _MainColor;
            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                fixed4 col = tex2D(_MainTex, i.uv);
                col.rgb *= _MainColor;
                return col;
            }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

加描边 - Outline

在这里插入图片描述
加上顶点按法线方向挤出后的背面绘制来描边的效果。
比之前没有描边的好很多,最明显的是,大腿之间的相同颜色的线条、头发与天空盒的线条

GIF

在这里插入图片描述
上面使用的描边方式比较简单:

  • 两个pass
  • 第一个绘制本体
  • 第二个将顶点想法线方向挤出,再绘制本体的背面

具体还有很多种描边,这里只简单介绍这种

Shader

// jave.lin 2019.08.25
Shader "Test/Toon" {
    Properties {
        _MainTex ("MainTex", 2D) = "white" {}
        _MainColor ("MainColor", Color) = (1,1,1,1)
        _OutLineWidth ("OutlLineWidth", Range(0, 0.1)) = 0.002
        _OutLineColor ("OutLineColor", Color) = (0,0,0,1)
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass { // solid
            Name "Solid"
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct v2f {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _MainColor;
            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                fixed4 col = tex2D(_MainTex, i.uv);
                col.rgb *= _MainColor;
                return col;
            }
            ENDCG
        }
        Pass { // outline
            Name "Outline"
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            fixed _OutLineWidth;
            fixed4 _OutLineColor;
            float4 vert (float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
                return UnityObjectToClipPos(vertex + normal * _OutLineWidth);
            }
            fixed4 frag () : SV_Target { return _OutLineColor; }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

添加光影 - RecieveShadow

首先是阴影

自身接收阴影

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Shader

shader中添加了阴影的注释

// jave.lin 2019.08.25
Shader "Test/Toon" {
    Properties {
        _MainTex ("MainTex", 2D) = "white" {}
        _MainColor ("MainColor", Color) = (1,1,1,1)
        _OutLineWidth ("OutlLineWidth", Range(0, 0.1)) = 0.002
        _OutLineColor ("OutLineColor", Color) = (0,0,0,1)
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass { // solid
            Name "Solid"
            Tags { "LightMode"="ForwardBase" }                      // shadow需要,正向渲染光照基础pass
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"                              // shadow需要,宏UNITY_LIGHTING_COORDS需要
            #pragma multi_compile_fwdbase_fullshadows               // shadow需要
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };
            struct v2f {
                // 变量名必须为pos,因为光影宏:TRANSFER_VERTEX_TO_FRAGMENT中有些嵌套宏有固化这个变量名称来处理
                float4 pos : SV_POSITION;                           // shadow需要
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                // UNITY_LIGHTING_COORDS(3,4)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define LIGHTING_COORDS(idx1, idx2) DECLARE_LIGHT_COORDS(idx1) SHADOW_COORDS(idx2)
                // #   define DECLARE_LIGHT_COORDS(idx) unityShadowCoord4 _LightCoord : TEXCOORD##idx;
                // DECLARE_LIGHT_COORDS(idx1)会声明,float[2~4] _LightCoord : TEXCOORD##idx;光源坐标的变量
                // SHADOW_COORDS(idx1) float[3~4] _ShadowCoord : TEXCOORD##idx1;阴影坐标变量
                
                // 所以可以理解该宏是:声明光源、阴影纹理采样坐标的
                LIGHTING_COORDS(3,4)                                // shadow需要
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _MainColor;
            v2f vert (appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                // TRANSFER_VERTEX_TO_FRAGMENT(a)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define TRANSFER_VERTEX_TO_FRAGMENT(a) COMPUTE_LIGHT_COORDS(a) TRANSFER_SHADOW(a)
                // #   define COMPUTE_LIGHT_COORDS(a) a._LightCoord = mul(unity_WorldToLight, mul(unity_ObjectToWorld, v.vertex)).xy;
                // #define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_WorldToShadow[0], mul(unity_ObjectToWorld,v.vertex));

                // 所以可以理解该宏是:
                // - 将光源坐标通过unity_WorldToLight矩阵变换到光源空间下
                // - 将阴影坐标通过unity_WorldToShadow[](或其他矩阵,依不同光源类型)矩阵变换到阴影空间(其实就是对应的光源空间)下
                // - 注意阴影坐标转回也会根据算法类型来计算,如:使用ScreenSpace Shadow来处理会将阴影坐标转为屏幕空间坐标即可
                TRANSFER_VERTEX_TO_FRAGMENT(o)                      // shadow需要
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                i.worldNormal = normalize(i.worldNormal);

                //viewDir后面高光用
                //float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                // 光衰减atten
                // 有采样光源空间深度图,将光源空间下的坐标与深度图比较是否在于深度图
                // 大于返回光影数据值作为系数衰减,否则返回1.0系数
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * UNITY_LIGHTMODEL_AMBIENT.a;

                atten = atten * 0.5 + 0.5;
                // diffuse
                fixed LdotN = dot(lightDir, i.worldNormal);
                fixed halfLambert = LdotN * 0.5 + 0.5;
                fixed3 diffuse = tex2D(_MainTex, i.uv).rgb * _MainColor * halfLambert * atten;
                return fixed4(ambient + diffuse, 1);
            }
            ENDCG
        }
        Pass { // outline
            Name "Outline"
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            fixed _OutLineWidth;
            fixed4 _OutLineColor;
            float4 vert (float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
                return UnityObjectToClipPos(vertex + normal * _OutLineWidth);
            }
            fixed4 frag () : SV_Target { return _OutLineColor; }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

调整阴影 - Adjusting Shadow Params

当然了,这个光影效果,不是我们卡通渲染需要的。
卡通渲染的光影过渡是比较硬的,我们可以使用一张1D的纹理过渡图来处理阴影的过渡
该纹理尺寸一般只要:256x1就够了
但是我为了纹理方便查看,我就使用了256x16

下面使用GIMP来绘制(我的游戏本上没有安装PS,因为PS都要收费,破解的又不想安装在游戏本中,因为一般破解程序都有木马,所以我就使用了免费、开源的GIMP,但是肯定没有PS好用,T^T)

我用填充工具随便填个渐变色图

  • 皮肤的在这里插入图片描述
  • 非皮肤在这里插入图片描述
    在这里插入图片描述
    效果不是很理想
    特别在头发,脸部的光影

其实这些可以使用额外的纹理来mask或是系数控制

在做光影时,发现模型制作不是很规范
(胸部部分竟然做到了衣服的子模型里)
所以导致胸部的部分光影不对
硬是要解决就是用mask texture来处理,但没必要了,以后再找找看有没更简单的模型,方便测试的
在这里插入图片描述

Shader

只有ambient+diffuse的光影
思路:

  • 使用diffuse的LdotN系数来控制对GradientTex阴影梯度纹理采样
  • GradientTex纹理主要是控制阴影梯度的,可给外部提供灵活的控制方式

当然这只是其一一种方式
也可以使用:

  • ShadowColor 阴影颜色
  • GradientGrayTex 阴影亮度剔除

然后:

fixed g = tex2D(GradientGrayTex, LdotN).r;
// g *= atten // 阴影衰减
// g *= specular // 高光
fixed3 shadow = ShadowColor * g;
fixed3 combined = ambient + diffuse + specular;

combined = lerp(combined, shadow, _ShadowItensity);

下面shader没有高光的阴影,一般卡通渲染的高光比较少,或是没有高光

// jave.lin 2019.08.25
Shader "Test/Toon" {
    Properties {
        [NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
        _MainColor ("MainColor", Color) = (1,1,1,1)
        _OutLineWidth ("OutlLineWidth", Range(0, 0.1)) = 0.002
        _OutLineColor ("OutLineColor", Color) = (0,0,0,1)
        [NoScaleOffset] _GradientTex ("GradientTex", 2D) = "white" {}       // 用于阴影梯度采样纹理,暂时就叫这个名词吧
        _GradientIntensity ("GradientIntensity", Range(0,1)) = 1            // 阴影梯度采样纹理强度
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass { // solid
            Name "Solid"
            Tags { "LightMode"="ForwardBase" }                      // shadow需要,正向渲染光照基础pass
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"                              // shadow需要,宏UNITY_LIGHTING_COORDS需要
            #pragma multi_compile_fwdbase_fullshadows               // shadow需要
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };
            struct v2f {
                // 变量为必须要pos,因为光影宏:TRANSFER_VERTEX_TO_FRAGMENT中有些嵌套宏有固化这个变量名称来处理
                float4 pos : SV_POSITION;                           // shadow需要
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                // UNITY_LIGHTING_COORDS(3,4)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define LIGHTING_COORDS(idx1, idx2) DECLARE_LIGHT_COORDS(idx1) SHADOW_COORDS(idx2)
                // #   define DECLARE_LIGHT_COORDS(idx) unityShadowCoord4 _LightCoord : TEXCOORD##idx;
                // DECLARE_LIGHT_COORDS(idx1)会声明,float[2~4] _LightCoord : TEXCOORD##idx;光源坐标的变量
                // SHADOW_COORDS(idx1) float[3~4] _ShadowCoord : TEXCOORD##idx1;阴影坐标变量
                
                // 所以可以理解该宏是:声明光源、阴影纹理采样坐标的
                LIGHTING_COORDS(3,4)                                // shadow需要
            };
            sampler2D _MainTex;
            fixed4 _MainColor;
            sampler2D _GradientTex;
            fixed _GradientIntensity;
            v2f vert (appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                // TRANSFER_VERTEX_TO_FRAGMENT(a)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define TRANSFER_VERTEX_TO_FRAGMENT(a) COMPUTE_LIGHT_COORDS(a) TRANSFER_SHADOW(a)
                // #   define COMPUTE_LIGHT_COORDS(a) a._LightCoord = mul(unity_WorldToLight, mul(unity_ObjectToWorld, v.vertex)).xy;
                // #define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_WorldToShadow[0], mul(unity_ObjectToWorld,v.vertex));

                // 所以可以理解该宏是:
                // - 将光源坐标通过unity_WorldToLight矩阵变换到光源空间下
                // - 将阴影坐标通过unity_WorldToShadow[](或其他矩阵,依不同光源类型)矩阵变换到阴影空间(其实就是对应的光源空间)下
                // - 注意阴影坐标转回也会根据算法类型来计算,如:使用ScreenSpace Shadow来处理会将阴影坐标转为屏幕空间坐标即可
                TRANSFER_VERTEX_TO_FRAGMENT(o)                      // shadow需要
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                i.worldNormal = normalize(i.worldNormal);

                //viewDir后面高光用
                //float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                // 光衰减atten
                // 有采样光源空间深度图,将光源空间下的坐标与深度图比较是否在于深度图
                // 大于返回光影数据值作为系数衰减,否则返回1.0系数
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

                // ambient
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * UNITY_LIGHTMODEL_AMBIENT.a;

                atten = atten * 0.5 + 0.5;
                // diffuse
                fixed LdotN = dot(lightDir, i.worldNormal);
                fixed halfLambert = LdotN * 0.5 + 0.5;                                  // 使用半lambert,背光不用太黑
                fixed lightShadowCoef = halfLambert * atten;                            // 乘上光影系数,应用上自身阴影
                fixed3 diffuse = tex2D(_MainTex, i.uv).rgb * _MainColor;
                fixed3 gradient = tex2D(_GradientTex, float2(lightShadowCoef, 0)).rgb;  // 使用光影系数采样梯度纹理
                diffuse = lerp(diffuse, diffuse * gradient, _GradientIntensity);        // 阴影强弱插值
                return fixed4(ambient + diffuse, 1);
            }
            ENDCG
        }
        Pass { // outline
            Name "Outline"
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            fixed _OutLineWidth;
            fixed4 _OutLineColor;
            float4 vert (float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
                // projection space
                float4 pos = UnityObjectToClipPos(vertex);
                // to view space normal
                fixed3 vNormal = mul((float3x3)UNITY_MATRIX_IT_MV, normal);
				fixed2 offset = TransformViewToProjection(vNormal.xy);
                // 因为在vertex post-processing会有perspective divide,所以我们先乘上pos.w以抵消透视
                // 这样无论多远多近都可以按恒定的描边边宽来显示
                pos.xy += offset * _OutLineWidth * pos.w;
                return pos;
            }
            fixed4 frag () : SV_Target { return _OutLineColor; }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

无透视法线挤出描边

然后再改了改描边,不需要透视
不然近距离镜头时,描边会变粗,如下图
在这里插入图片描述
下面是无透视的描边
在这里插入图片描述

Shader

看vs即可

            float4 vert (float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
                // projection space
                float4 pos = UnityObjectToClipPos(vertex);
                // to view space normal
                fixed3 vNormal = mul((float3x3)UNITY_MATRIX_IT_MV, normal);
				fixed2 offset = TransformViewToProjection(vNormal.xy);
                // 因为在vertex post-processing会有perspective divide,所以我们先乘上pos.w以抵消透视
                // 这样无论多远多近都可以按恒定的描边边宽来显示
                pos.xy += offset * _OutLineWidth * pos.w;
                return pos;
            }

整体运行效果

在这里插入图片描述
在这里插入图片描述

最后我将头发颜色调整为红色,风格也挺搭的

调整材质的MainColor参数为红色即可,效果如下:
在这里插入图片描述

高光 - Specular

下面是我们正常高光
但是太平滑了,我们需要硬边过渡
在这里插入图片描述
因为需要硬边过渡,我就简单粗暴的添加一个SpecularThreshold来过滤掉一些比较小的高光值
效果如下:
在这里插入图片描述
最终高光在没个子模型的材质参数再调整一下
在这里插入图片描述
没添加高光前的对比
在这里插入图片描述
添加一个自转,不是镜头转了,方便看光影
在这里插入图片描述
描边小一些,头发黄色,角度换一下,那把刀好帅
在这里插入图片描述
头发那些高光不太理想,一般需要手绘纹理的光影mask来处理就会好很多。或是头发高模法线图也可以

还有头发、大腿、两部的高光过渡太平滑了,我们将其参数调整一下,效果会更好
在这里插入图片描述
在这里插入图片描述
在此基础上,如果对头发、衣服黄金色纹理,纽扣,铠甲金属,如果再细分一下纹理分通道来控制高光系数纹理图的话,可以制作得非常好的效果,但没有资源。

Shader

// jave.lin 2019.08.25
Shader "Test/Toon" {
    Properties {
        [NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
        _MainColor ("MainColor", Color) = (1,1,1,1)
        _OutLineWidth ("OutlLineWidth", Range(0, 0.1)) = 0.002
        _OutLineColor ("OutLineColor", Color) = (0,0,0,1)
        [NoScaleOffset] _GradientTex ("GradientTex", 2D) = "white" {}       // 用于阴影梯度采样纹理,暂时就叫这个名词吧
        _GradientIntensity ("GradientIntensity", Range(0,1)) = 1            // 阴影梯度采样纹理强度
        _SpecularPower ("SpecularPower", Range(1,100)) = 80                 // 高光平滑度
        _SpecularIntensity ("SpecularItensity", Range(0,1)) = 1             // 高光强度
        _SpecularThreshold ("SpecularThreshold", Range(0,1)) = 0.3          // 高光阈值
        _SpecularBrightness ("SpecularBrightness", Range(0,1)) = 0.1        // 高光添加的亮度量
        _SpecularValue ("SpecularValue", Range(0,1)) = 1                    // 高光值
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass { // solid
            Name "Solid"
            Tags { "LightMode"="ForwardBase" }                      // shadow需要,正向渲染光照基础pass
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"                              // shadow需要,宏UNITY_LIGHTING_COORDS需要
            #pragma multi_compile_fwdbase_fullshadows               // shadow需要
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                half3 normal : NORMAL;
            };
            struct v2f {
                // 变量名必须为pos,因为光影宏:TRANSFER_VERTEX_TO_FRAGMENT中有些嵌套宏有固化这个变量名称来处理
                float4 pos : SV_POSITION;                           // shadow需要
                float2 uv : TEXCOORD0;
                half3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                // UNITY_LIGHTING_COORDS(3,4)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define LIGHTING_COORDS(idx1, idx2) DECLARE_LIGHT_COORDS(idx1) SHADOW_COORDS(idx2)
                // #   define DECLARE_LIGHT_COORDS(idx) unityShadowCoord4 _LightCoord : TEXCOORD##idx;
                // DECLARE_LIGHT_COORDS(idx1)会声明,float[2~4] _LightCoord : TEXCOORD##idx;光源坐标的变量
                // SHADOW_COORDS(idx1) float[3~4] _ShadowCoord : TEXCOORD##idx1;阴影坐标变量
                
                // 所以可以理解该宏是:声明光源、阴影纹理采样坐标的
                UNITY_LIGHTING_COORDS(3,4)                                // shadow需要
            };
            sampler2D _MainTex;
            fixed4 _MainColor;
            sampler2D _GradientTex;
            fixed _GradientIntensity;
            fixed _SpecularPower;
            fixed _SpecularIntensity;
            fixed _SpecularThreshold;
            fixed _SpecularBrightness;
            fixed _SpecularValue;
            v2f vert (appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                // TRANSFER_VERTEX_TO_FRAGMENT(a)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define TRANSFER_VERTEX_TO_FRAGMENT(a) COMPUTE_LIGHT_COORDS(a) TRANSFER_SHADOW(a)
                // #   define COMPUTE_LIGHT_COORDS(a) a._LightCoord = mul(unity_WorldToLight, mul(unity_ObjectToWorld, v.vertex)).xy;
                // #define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_WorldToShadow[0], mul(unity_ObjectToWorld,v.vertex));

                // 所以可以理解该宏是:
                // - 将世界空间光源坐标通过unity_WorldToLight矩阵变换到光源空间下
                // - 将世界空间阴影坐标通过unity_WorldToShadow[](或其他矩阵,依不同光源类型)矩阵变换到阴影空间(其实就是对应的光源空间)下
                // - 注意阴影坐标转回也会根据算法类型来计算,如:使用ScreenSpace Shadow来处理会将阴影坐标转为屏幕空间坐标即可
                TRANSFER_VERTEX_TO_FRAGMENT(o)                      // shadow需要
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                i.worldNormal = normalize(i.worldNormal);

                //viewDir后面高光用
                half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
                half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                // 光衰减atten
                // 采样光源空间深度图,将光源空间下的坐标与深度图比较
                // 大于深度图的返回光影数据值作为系数衰减,小于深度图的则返回1.0系数
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
                atten = atten * 0.5 + 0.5;

                // ambient
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
                
                fixed LdotN = dot(lightDir, i.worldNormal);
                fixed halfLambert = LdotN * 0.5 + 0.5;                                      // 使用半lambert,背光不用太黑
                fixed lightShadowCoef = halfLambert * atten;                                // 乘上光影系数,应用上自身阴影
                
                // diffuse
                fixed3 diffuse = tex2D(_MainTex, i.uv).rgb * _MainColor;
                fixed3 dGradient = tex2D(_GradientTex, float2(lightShadowCoef, 0)).rgb;     // 使用光影系数采样梯度纹理
                //return fixed4(dGradient,1);
                diffuse = lerp(diffuse,  diffuse * dGradient, _GradientIntensity);          // 漫反射光影强弱插值

                // specular
                half3 hDir = normalize(viewDir + lightDir);
                fixed HdotN = max(0, dot(hDir, i.worldNormal));
                fixed specular = pow(HdotN, _SpecularPower) * _SpecularIntensity;
                specular *= atten;                                                          // 阴影衰减对高光有些影响
                specular = step(_SpecularThreshold, specular) * _SpecularValue;             // 大于阈值的才有效
                //return specular;

                // 高光这儿,我们是源diffuse的高光部分叠加,还有高光亮度叠加
                return fixed4(ambient + diffuse + diffuse * specular + specular * _SpecularBrightness, 1);
            }
            ENDCG
        }
        Pass { // outline
            Name "Outline"
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            fixed _OutLineWidth;
            fixed4 _OutLineColor;
            float4 vert (float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
                // projection space
                float4 pos = UnityObjectToClipPos(vertex);
                // to view space normal
                fixed3 vNormal = mul((float3x3)UNITY_MATRIX_IT_MV, normal);
				fixed2 offset = TransformViewToProjection(vNormal.xy);
                // 因为在vertex post-processing会有perspective divide,所以我们先乘上pos.w以抵消透视
                // 这样无论多远多近都可以按恒定的描边边宽来显示
                pos.xy += offset * _OutLineWidth * pos.w;
                return pos;
            }
            fixed4 frag () : SV_Target { return _OutLineColor; }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

边缘光 - Rim

一般卡通渲染也是不需要边缘光的,下面我们就下一丢丢的边缘光好了,不多
在这里插入图片描述
合成后
在这里插入图片描述

Shader

// jave.lin 2019.08.25
Shader "Test/Toon" {
    Properties {
        [NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
        _MainColor ("MainColor", Color) = (1,1,1,1)
        _OutLineWidth ("OutlLineWidth", Range(0, 0.1)) = 0.002
        _OutLineColor ("OutLineColor", Color) = (0,0,0,1)
        [NoScaleOffset] _GradientTex ("GradientTex", 2D) = "white" {}       // 用于阴影梯度采样纹理,暂时就叫这个名词吧
        _GradientIntensity ("GradientIntensity", Range(0,1)) = 1            // 阴影梯度采样纹理强度
        _SpecularPower ("SpecularPower", Range(1,100)) = 80                 // 高光平滑度
        _SpecularIntensity ("SpecularItensity", Range(0,1)) = 1             // 高光强度
        _SpecularThreshold ("SpecularThreshold", Range(0,1)) = 0.3          // 高光阈值
        _SpecularBrightness ("SpecularBrightness", Range(0,1)) = 0.1        // 高光添加的亮度量
        _SpecularValue ("SpecularValue", Range(0,1)) = 1                    // 高光值
        _RimIntensity ("RimIntensity", Range(0,5)) = 1                      // 边缘光
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass { // solid
            Name "Solid"
            Tags { "LightMode"="ForwardBase" }                      // shadow需要,正向渲染光照基础pass
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"                              // shadow需要,宏UNITY_LIGHTING_COORDS需要
            #pragma multi_compile_fwdbase_fullshadows               // shadow需要
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                half3 normal : NORMAL;
            };
            struct v2f {
                // 变量名必须为pos,因为光影宏:TRANSFER_VERTEX_TO_FRAGMENT中有些嵌套宏有固化这个变量名称来处理
                float4 pos : SV_POSITION;                           // shadow需要
                float2 uv : TEXCOORD0;
                half3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                // UNITY_LIGHTING_COORDS(3,4)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define LIGHTING_COORDS(idx1, idx2) DECLARE_LIGHT_COORDS(idx1) SHADOW_COORDS(idx2)
                // #   define DECLARE_LIGHT_COORDS(idx) unityShadowCoord4 _LightCoord : TEXCOORD##idx;
                // DECLARE_LIGHT_COORDS(idx1)会声明,float[2~4] _LightCoord : TEXCOORD##idx;光源坐标的变量
                // SHADOW_COORDS(idx1) float[3~4] _ShadowCoord : TEXCOORD##idx1;阴影坐标变量
                
                // 所以可以理解该宏是:声明光源、阴影纹理采样坐标的
                UNITY_LIGHTING_COORDS(3,4)                                // shadow需要
            };
            sampler2D _MainTex;
            fixed4 _MainColor;
            sampler2D _GradientTex;
            fixed _GradientIntensity;
            fixed _SpecularPower;
            fixed _SpecularIntensity;
            fixed _SpecularThreshold;
            fixed _SpecularBrightness;
            fixed _SpecularValue;
            fixed _RimIntensity;
            v2f vert (appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                // TRANSFER_VERTEX_TO_FRAGMENT(a)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define TRANSFER_VERTEX_TO_FRAGMENT(a) COMPUTE_LIGHT_COORDS(a) TRANSFER_SHADOW(a)
                // #   define COMPUTE_LIGHT_COORDS(a) a._LightCoord = mul(unity_WorldToLight, mul(unity_ObjectToWorld, v.vertex)).xy;
                // #define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_WorldToShadow[0], mul(unity_ObjectToWorld,v.vertex));

                // 所以可以理解该宏是:
                // - 将世界空间光源坐标通过unity_WorldToLight矩阵变换到光源空间下
                // - 将世界空间阴影坐标通过unity_WorldToShadow[](或其他矩阵,依不同光源类型)矩阵变换到阴影空间(其实就是对应的光源空间)下
                // - 注意阴影坐标转回也会根据算法类型来计算,如:使用ScreenSpace Shadow来处理会将阴影坐标转为屏幕空间坐标即可
                TRANSFER_VERTEX_TO_FRAGMENT(o)                      // shadow需要
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                i.worldNormal = normalize(i.worldNormal);

                //viewDir后面高光用
                half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
                half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                // 光衰减atten
                // 采样光源空间深度图,将光源空间下的坐标与深度图比较
                // 大于深度图的返回光影数据值作为系数衰减,小于深度图的则返回1.0系数
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
                atten = atten * 0.5 + 0.5;

                // ambient
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
                
                fixed LdotN = dot(lightDir, i.worldNormal);
                fixed halfLambert = LdotN * 0.5 + 0.5;                                      // 使用半lambert,背光不用太黑
                fixed lightShadowCoef = halfLambert * atten;                                // 乘上光影系数,应用上自身阴影
                
                // diffuse
                fixed3 diffuse = tex2D(_MainTex, i.uv).rgb * _MainColor;
                fixed3 dGradient = tex2D(_GradientTex, float2(lightShadowCoef, 0)).rgb;     // 使用光影系数采样梯度纹理
                //return fixed4(dGradient,1);
                diffuse = lerp(diffuse,  diffuse * dGradient, _GradientIntensity);          // 漫反射光影强弱插值

                // specular
                half3 hDir = normalize(viewDir + lightDir);
                fixed HdotN = max(0, dot(hDir, i.worldNormal));
                fixed specular = pow(HdotN, _SpecularPower) * _SpecularIntensity;
                specular *= atten;                                                          // 阴影衰减对高光有些影响
                specular = step(_SpecularThreshold, specular) * _SpecularValue;             // 大于阈值的才有效
                //return specular;

                // rim
                fixed rimFactor = (1 - dot(viewDir, i.worldNormal)) * _RimIntensity;        // 边缘光
                rimFactor *= atten;                                                         // 应用上光影衰减系数
                rimFactor = step(_SpecularThreshold, rimFactor) * _SpecularValue;           // 阈值使用高光的
                //return rimFactor;

                specular = max(specular, rimFactor);                                        // 这里简单处理:高光与边缘光哪个亮取哪个

                // 高光这儿,我们是源diffuse的高光部分叠加,还有高光亮度叠加
                return fixed4(
                    ambient + 
                    diffuse + 
                    diffuse * specular + specular * _SpecularBrightness
                    , 1);
            }
            ENDCG
        }
        Pass { // outline
            Name "Outline"
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            fixed _OutLineWidth;
            fixed4 _OutLineColor;
            float4 vert (float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
                // projection space
                float4 pos = UnityObjectToClipPos(vertex);
                // to view space normal
                fixed3 vNormal = mul((float3x3)UNITY_MATRIX_IT_MV, normal);
				fixed2 offset = TransformViewToProjection(vNormal.xy);
                // 因为在vertex post-processing会有perspective divide,所以我们先乘上pos.w以抵消透视
                // 这样无论多远多近都可以按恒定的描边边宽来显示
                pos.xy += offset * _OutLineWidth * pos.w;
                return pos;
            }
            fixed4 frag () : SV_Target { return _OutLineColor; }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

控制边缘光在背光时才显示 - Rim Show At Back To the Lighting

在这里插入图片描述

	[Toggle] _RimShowAtBackToLight ("RimShowAtBackToLight", Float) = 0  // 边缘光是否被光是才显示
...
	rimFactor = lerp(rimFactor, rimFactor * max(0, dot(-lightDir, viewDir)), _RimShowAtBackToLight);   // 视线越背光,边缘光应该越亮

在这里插入图片描述
Shader Properties中可调整
在这里插入图片描述

Shader

// jave.lin 2019.08.25
Shader "Test/Toon" {
    Properties {
        [NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
        _MainColor ("MainColor", Color) = (1,1,1,1)
        _OutLineWidth ("OutlLineWidth", Range(0, 0.1)) = 0.002
        _OutLineColor ("OutLineColor", Color) = (0,0,0,1)
        [NoScaleOffset] _GradientTex ("GradientTex", 2D) = "white" {}       // 用于阴影梯度采样纹理,暂时就叫这个名词吧
        _GradientIntensity ("GradientIntensity", Range(0,1)) = 1            // 阴影梯度采样纹理强度
        _SpecularPower ("SpecularPower", Range(1,100)) = 80                 // 高光平滑度
        _SpecularIntensity ("SpecularItensity", Range(0,1)) = 1             // 高光强度
        _SpecularThreshold ("SpecularThreshold", Range(0,1)) = 0.3          // 高光阈值
        _SpecularBrightness ("SpecularBrightness", Range(0,1)) = 0.1        // 高光添加的亮度量
        _SpecularValue ("SpecularValue", Range(0,1)) = 1                    // 高光值
        _RimIntensity ("RimIntensity", Range(0,5)) = 1                      // 边缘光
        [Toggle] _RimShowAtBackToLight ("RimShowAtBackToLight", Float) = 0  // 边缘光是否被光是才显示
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass { // solid
            Name "Solid"
            Tags { "LightMode"="ForwardBase" }                      // shadow需要,正向渲染光照基础pass
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"                              // shadow需要,宏UNITY_LIGHTING_COORDS需要
            #pragma multi_compile_fwdbase_fullshadows               // shadow需要
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                half3 normal : NORMAL;
            };
            struct v2f {
                // 变量名必须为pos,因为光影宏:TRANSFER_VERTEX_TO_FRAGMENT中有些嵌套宏有固化这个变量名称来处理
                float4 pos : SV_POSITION;                           // shadow需要
                float2 uv : TEXCOORD0;
                half3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                // UNITY_LIGHTING_COORDS(3,4)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define LIGHTING_COORDS(idx1, idx2) DECLARE_LIGHT_COORDS(idx1) SHADOW_COORDS(idx2)
                // #   define DECLARE_LIGHT_COORDS(idx) unityShadowCoord4 _LightCoord : TEXCOORD##idx;
                // DECLARE_LIGHT_COORDS(idx1)会声明,float[2~4] _LightCoord : TEXCOORD##idx;光源坐标的变量
                // SHADOW_COORDS(idx1) float[3~4] _ShadowCoord : TEXCOORD##idx1;阴影坐标变量
                
                // 所以可以理解该宏是:声明光源、阴影纹理采样坐标的
                UNITY_LIGHTING_COORDS(3,4)                                // shadow需要
            };
            sampler2D _MainTex;
            fixed4 _MainColor;
            sampler2D _GradientTex;
            fixed _GradientIntensity;
            fixed _SpecularPower;
            fixed _SpecularIntensity;
            fixed _SpecularThreshold;
            fixed _SpecularBrightness;
            fixed _SpecularValue;
            fixed _RimIntensity;
            fixed _RimShowAtBackToLight; 
            v2f vert (appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                // TRANSFER_VERTEX_TO_FRAGMENT(a)宏在:AutoLight.cginc可以看到,所以需要 include "AutoLight.cginc"
                // #define TRANSFER_VERTEX_TO_FRAGMENT(a) COMPUTE_LIGHT_COORDS(a) TRANSFER_SHADOW(a)
                // #   define COMPUTE_LIGHT_COORDS(a) a._LightCoord = mul(unity_WorldToLight, mul(unity_ObjectToWorld, v.vertex)).xy;
                // #define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_WorldToShadow[0], mul(unity_ObjectToWorld,v.vertex));

                // 所以可以理解该宏是:
                // - 将世界空间光源坐标通过unity_WorldToLight矩阵变换到光源空间下
                // - 将世界空间阴影坐标通过unity_WorldToShadow[](或其他矩阵,依不同光源类型)矩阵变换到阴影空间(其实就是对应的光源空间)下
                // - 注意阴影坐标转回也会根据算法类型来计算,如:使用ScreenSpace Shadow来处理会将阴影坐标转为屏幕空间坐标即可
                TRANSFER_VERTEX_TO_FRAGMENT(o)                      // shadow需要
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                i.worldNormal = normalize(i.worldNormal);

                //viewDir后面高光用
                half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
                half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                // 光衰减atten
                // 采样光源空间深度图,将光源空间下的坐标与深度图比较
                // 大于深度图的返回光影数据值作为系数衰减,小于深度图的则返回1.0系数
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
                atten = atten * 0.5 + 0.5;

                // ambient
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
                
                fixed LdotN = dot(lightDir, i.worldNormal);
                fixed halfLambert = LdotN * 0.5 + 0.5;                                      // 使用半lambert,背光不用太黑
                fixed lightShadowCoef = halfLambert * atten;                                // 乘上光影系数,应用上自身阴影
                
                // diffuse
                fixed3 diffuse = tex2D(_MainTex, i.uv).rgb * _MainColor;
                fixed3 dGradient = tex2D(_GradientTex, float2(lightShadowCoef, 0)).rgb;     // 使用光影系数采样梯度纹理
                //return fixed4(dGradient,1);
                diffuse = lerp(diffuse,  diffuse * dGradient, _GradientIntensity);          // 漫反射光影强弱插值

                // specular
                half3 hDir = normalize(viewDir + lightDir);
                fixed HdotN = max(0, dot(hDir, i.worldNormal));
                fixed specular = pow(HdotN, _SpecularPower) * _SpecularIntensity;
                specular *= atten;                                                          // 阴影衰减对高光有些影响
                specular = step(_SpecularThreshold, specular) * _SpecularValue;             // 大于阈值的才有效
                //return specular;

                // rim
                fixed rimFactor = (1 - dot(viewDir, i.worldNormal)) * _RimIntensity;        // 边缘光
                rimFactor = lerp(rimFactor, rimFactor * max(0, dot(-lightDir, viewDir)), _RimShowAtBackToLight);   // 视线越背光,边缘光应该越亮
                rimFactor *= atten;                                                         // 应用上光影衰减系数
                rimFactor = step(_SpecularThreshold, rimFactor) * _SpecularValue;           // 阈值使用高光的
                //return rimFactor;

                specular = max(specular, rimFactor);                                        // 这里简单处理:高光与边缘光哪个亮取哪个

                // 高光这儿,我们是源diffuse的高光部分叠加,还有高光亮度叠加
                return fixed4(
                    ambient + 
                    diffuse + 
                    diffuse * specular + specular * _SpecularBrightness
                    , 1);
            }
            ENDCG
        }
        Pass { // outline
            Name "Outline"
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest       // 最快速精度
            fixed _OutLineWidth;
            fixed4 _OutLineColor;
            float4 vert (float4 vertex : POSITION, float3 normal : NORMAL) : SV_POSITION {
                // projection space
                float4 pos = UnityObjectToClipPos(vertex);
                // to view space normal
                fixed3 vNormal = mul((float3x3)UNITY_MATRIX_IT_MV, normal);
				fixed2 offset = TransformViewToProjection(vNormal.xy);
                // 因为在vertex post-processing会有perspective divide,所以我们先乘上pos.w以抵消透视
                // 这样无论多远多近都可以按恒定的描边边宽来显示
                pos.xy += offset * _OutLineWidth * pos.w;
                return pos;
            }
            fixed4 frag () : SV_Target { return _OutLineColor; }
            ENDCG
        }
    }
    Fallback "Diffuse"
}

Project

TestNPR_ToonShading_卡通渲染_ambient_diffuse_specular_rim

References

收集了一些资料,后面进一步了解卡通渲染再去看看,上面的是之前理解的很少一部分内容总结写出来的。

  • 【NPR】卡通渲染
  • 卡通渲染及其相关技术总结
  • 风格化角色渲染实践

http://www.niftyadmin.cn/n/2525426.html

相关文章

javascript(十五) 错误处理技术

为什么80%的码农都做不了架构师?>>> 错误处理技术 常见的异常EvalError, RangeError ,ReferenceError ,SyntaxError ,TypeError ,URIError 其他就是语法规则try {}catch…

给定一个存放整数的数组,重新排列数组使得数组左边为奇数,右边为偶数

给定一个存放整数的数组,重新排列数组使得数组左边为奇数,右边为偶数 分类: c/c 数据结构算法2011-07-16 22:40 594人阅读 评论(0) 收藏 举报[cpp] view plaincopyprint? /* 给定一个存放整数的数组,重新排列数组使得数组左边为…

开源大数据周刊-第8期

阿里云E-Mapreduce动态 E-Mapreduce团队 1.3.2版本(已经发布): Master HA功能1.4版本(正在研发): 用户执行计划及集群运行状态自定义报警集群整体运行情况的仪表盘集群的一些专家建议,例如:扩容…

Unity - RenderWithShader, SetReplacementShader, ResetReplacementShader 测试

文章目录API 简介DemoShaderCSharp ScriptsScene - 场景内容运行效果详细解析SetReplacementShader与ResetReplacementShaderRenderWithShader坑点ProjectUnity - RenderWithShader, SetReplacementShader, ResetReplacementShader 测试 最不喜欢介绍API,但可以参考…

强制类型转换运算符

强制类型转换运算符 C有四种强制类型转换符,分别是dynamic_cast,const_cast,static_cast,reinterpret_cast。其中dynamic_cast与运行时类型转换密切相关,在这里我们介绍dynamic_cast。dynamic_cast强制转换运算符 该转…

Ex2010-17 Linked Mailbox in Exchange Server

Series 1http://www.msexchange.org/articles-tutorials/exchange-server-2007/planning-architecture/deploying-exchange-resource-forest-part1.htmlSeries 2http://msexchangeteam.in/linked-mailbox-in-exchange-server-2013-part-1-2/转载于:https://blog.51cto.com/1050…

Unity Shader PostProcessing - 3 - 深度+法线来边缘检测

最近事情太多,学习时间断断续续,终于挤出时间,将深度法线边缘检测的基础学习完 前一篇:Unity Shader PostProcessing - 2 - 边缘检测,是基于图像像素来边缘检测的。 在平面像素边缘检测会遇到很多像素颜色问题导致检…

boost bind 用法

boost::bind()用来将一个函数或函数对象绑定某些参数&#xff0c;返回值是一个函数对象。 它提供一个任意的函数对象(仿函数)、函数、函数指针、成员函数指针。 上代码&#xff1a; #include <boost/bind.hpp> #include <iostream> #include <vector> #…