【《Unity 2018 Shaders and Effects Cookbook》提炼总结】(七)卡通效果和 Phong模型

1.卡通效果shader

        游戏中最常用的效果之一是toonshader,也被称为celluloid(CEL)shading。这是一种非真实感渲染技术,可以让3D模型显得平坦。许多游戏使用它来表示图形是手绘而不是3D建模的错觉。我们可以在下图种看到使用toon Shader(左)和Standard Shader(右)。

       

         要想达到这种效果单纯的使用surface 函数不是不可能实现的,但它将非常昂贵且耗时。事实上,surface 函数仅适用于材质的属性,而不适用于实际的照明条件。由于toon shading需要我们改变光线反射的方式,我们需要创建自定义光照模型。

         a.创建一个shader命名为ToonShader。

         b. 创建一个material命名为ToonShaderMat。

         c.此外我们需要一个称为ramp map 的附加纹理。该纹理将用于指示何时根据收到的阴影使用某些颜色。选中我们的纹理后在Inspector 面板,改变纹理的WrapMode 为Clamp。如果你想要两种颜色之间的边缘变得清晰,Filter Mode也应该设为Point。

     

  

       e.在属性块添加 _RampTex属性并移走_Glossiness,_Metallic,_Color属性,并在CGPROGRAM部分添加移除相关变量

     _RampTex("Ramp",2D) = "white" {}

     sampler2D _RampTex;

       h.更改#pragma指令,使其指向名为LightingToon()的函数:

     #pragma surface surf Toon

       i.添加一个函数并命名为LightingToon:

      fixed4 LightingToon(SurfaceOutput s, fixed3 lightDir, fixed atten)
        {
            //First calculate the dot product of the light direction and the surface's normal
            half NdotL = dot(s.Normal, lightDir);
            //Remap NdotL to the value on the ramp map
            NdotL = tex2D(_RampTex, fixed2(NdotL, 0.5));

            //Next,set what color should be returned
            half4 color;
            color.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
            color.a = s.Alpha;

            //Return the calculated color
            return color;
        }

      j.在surf函数种添加如下部分

      void surf(Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
        }

     k.保存脚本回到场景效果图如下:

    源码如下:

Shader "Custom/ToonShader" {
	Properties {
		_RampTex("Ramp",2D) = "white" {}
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
	
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200

		CGPROGRAM
       #pragma surface surf Toon

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;
		sampler2D _RampTex;
		
		struct Input {
			float2 uv_MainTex;
		};

		fixed4 LightingToon(SurfaceOutput s, fixed3 lightDir, fixed atten)
		{
			//First calculate the dot product of the light direction and the surface's normal
			half NdotL = dot(s.Normal, lightDir);
			//Remap NdotL to the value on the ramp map
			NdotL = tex2D(_RampTex, fixed2(NdotL, 0.5));

			//Next,set what color should be returned
			half4 color;
			color.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
			color.a = s.Alpha;

			//Return the calculated color
			return color;
		}
	   
		void surf(Input IN, inout SurfaceOutput o) {
			o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
		}

		ENDCG
	}
	FallBack "Diffuse"
}

    提示:

            可以通过Window|Lighting|Settings来改变Environment|Environmental Lighting|Intensity Multiplier值为0 来调试该效果。

它是如何运行的

        Toon shading的主要特征是光线的渲染方式;表面没有均匀的阴影。为了达到这个效果,我们需要一个渐变纹理图。其目的是将Lambertian光照强度重新映射到另一个值。使用没有渐变的渐变映射,我们可以强制照明步骤逐步渲染。下图显示了如何使用渐变纹理图来校正光强度:

相关

        有许多不同的方法可以实现Toon shading效果。使用不同的渐变会对模型的外观产生巨大的变化,因此我们应该试验以找到最佳的。渐变纹理的另一种替代方法是捕捉光强度NdotL,以便它只能假设从0到1等距采样一定数量的值。

       

	half4 LightingCustomLambert(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
	{
		half NdotL = dot(s.Normal, lightDir);
		//Snap instead
		half cel = floor(NdotL * _CelShadingLevels) / (_CelShadingLevels - 0.5);
		//Next,set what color should be returned
		half4 color;
		color.rgb = s.Albedo * _LightColor0.rgb * (cel * atten);
		color.a = s.Alpha;
		//Return the calculated color

		return color;
	}

          要捕捉一个数字,我们首先将NdotL乘以_CelShadingLevels变量,通过floor函数将结果四舍五入为整数,然后将其除以。这种舍入由floor函数完成,它将有效地从一个数字中删除小数点。通过这样做,cel 数量被迫承担从0到1的_CelShadingLevels等距值之一。这消除了对渐变纹理的需要并使所有颜色步骤具有相同的大小。如果我们要用此方法,我们要添加_CelShadingLevels属性。

  2.Phong 模型

        物体表面的镜面反射仅仅描述了它的闪亮程度。这些类型的效果通常在shader世界被称为视图相关效果,这是因为,为了在shader中实现逼真的高光效果,我们需要包括相机的方向或永和面向物体的面。最基本且性能最友好的Specular类型使Phong  Specular效果。

         a.创建一个shader命名为Phong。

         b.创建一个material命名为PhongMat。

         c.双击打开shader,删除所有当前属性及其定义,然后将以下属性添加到shader并在SubShader{}模块添加相关变量:

         Properties {
        _MainTint("Diffuse Tint",Color) = (1,1,1,1)
        _MainTex("Base (RGB)",2D) = "white"{}
        _SpecularColor("Specular Color",Color) = (1,1,1,1)
        _SpecPower("Specular Power",Range(0,30)) = 1
         }

         e.现在,我们必须添加我们的自定义光照模型,以便我们可以计算自己的Phong Specular.将以下代码添加到Subshader {}函数  

      fixed4 LightingPhong(surfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
        {
            //Reflection
            float NdotL = dot(s.Normal, lightDir);
            float3 reflectionVector = normalize(2.0 * s.Normal * NdotL - lightDir);

            // Specular
            float spec = pow(max(0, dot(reflectionVector, viewDir)), _SpecPower);

            //Final effect
            fixed4 c;
            c.rgb = (s.Albedo * _LightColor0.rgb * max(0, NdotL) * atten) + (_LightColor0.rgb * finalSpec);
            c.a = s.Alpha;
            return c;

        }

         h.接下来,我们必须告诉CGPROGRAM块它需要使用我们的自定义Lighting函数而不是其中的内置函数。我们通过将#pragma语句更改为以下内容来完成操作

            CGPROGRAM
            #pragma surface surf Phong

        i.最后,我们把surf函数更新为以下代码:

        void surf (Input IN, inout SurfaceOutput o) {
            half4 c = tex2D(_MainTex, IN.uv_MainTex) * _MainTint;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }

        j.保存代码。回到场景,效果图如下

 

       k.尝试更改Specular 属性并观察效果。

       源码如下:
  

Shader "Custom/Phong" {
	Properties {
		_MainTint("Diffuse Tint",Color) = (1,1,1,1)
		_MainTex("Base (RGB)",2D) = "white"{}
		_SpecularColor("Specular Color",Color) = (1,1,1,1)
		_SpecPower("Specular Power",Range(0,30)) = 1
	}
		SubShader{
			Tags { "RenderType" = "Opaque" }
			LOD 200

			CGPROGRAM
			#pragma surface surf Phong

			// Use shader model 3.0 target, to get nicer looking lighting
			#pragma target 3.0
		float4 _SpecularColor;
	 	sampler2D _MainTex;
		float4 _MainTint;
		float _SpecPower;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D(_MainTex, IN.uv_MainTex) * _MainTint;
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		fixed4 LightingPhong(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
		{
			//Reflection
			float NdotL = dot(s.Normal, lightDir);
			float3 reflectionVector = normalize(2.0 * s.Normal * NdotL - lightDir);

			// Specular
			float spec = pow(max(0, dot(reflectionVector, viewDir)), _SpecPower);
			float3 finalSpec = _SpecularColor.rgb * spec;

			//Final effect
			fixed4 c;
			c.rgb = (s.Albedo * _LightColor0.rgb * max(0, NdotL) * atten) + (_LightColor0.rgb * finalSpec);
			c.a = s.Alpha;
			return c;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

它是如何运行的:

       

     在我们的例子中,我们使用的是Specular shader,因此我们需要具有依赖于视图的Lighting函数结构,我们必须写下列内容:

CGPROGRAM
#pragma surface surf Phong
fixed4 LightingPhong(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
    //...
}
      这告诉shader我们要创建自己的视图相关shader。始终确保Lighting函数声明和#pragma语句中Lighting函数名称相同,否则Unity 将无法找到我们的光照模型。

    在Phong模型中起作用的组件如下入所示。我们有光方向L(加上它的反射R)和法线方向N。它们之前在Lambertian模型中都有遇到过。除了V,它是视图方向:

Phong模型假设反射表面的最终光强度由两个分量给出,即漫反射颜色和镜面反射值。如下所示:

                                                                    I = D + S

漫反射分量D和之前的Lambertian模型保持不变:

                                                                    D = N * L

镜面反射分量S定义如下:

                                                                    S= (R*L)P

这里,P是shader 中定义为_SpecPower的Specular power。唯一未知的参数是R,它是根据N的L的反射。在向量代数中,这可以计算如下。

                                                                   R = 2N * (N * L)- L

这正是以下计算的内容:

       float3 reflectionVector = normalize(2.0 * s.Normal *NdotL - lightDir);

这有使法线向光线弯曲的效果;作为一个顶点,法线指向远离光线,有关更直观的表示,请看下图

     以上均基于Unity2018.1.0f源码可见我的GitHub:

    https://github.com/xiaoshuivv/ShadersUnity2018.1.0f.git

猜你喜欢

转载自blog.csdn.net/qq_39218906/article/details/86735974
今日推荐