入门图形学:光照模型(五)

版权声明: https://blog.csdn.net/yinhun2012/article/details/81870248

        紧接上一篇:https://blog.csdn.net/yinhun2012/article/details/81061129

        之前我们详细的学习了怎么使用unity buildin的标准光照模型,给我们的shader指定这个标准光照模型,然后观察呈现出什么样的效果。

        具体做法就是添加一个预编译指令:

        #pragma surface surf Standard    就可以为我们的surf函数指定Standard光照模型了

        这次我们就来替换这个标准光照模型函数,替换成我们自己去实现的函数,比如这样:

        #pragma surface surf YangLightModel  意思就是说给我们的surf函数指定一个名为YangLightModel的光照模型。

        在开始写这个shader之前,首先让我们观察Lighting.cginc和UnityPBSLighting.cginc中的代码:

        ps:我这里我就不全部贴过来了,只拷一些具有代表性的,如下:

        

struct SurfaceOutput {
    fixed3 Albedo;
    fixed3 Normal;
    fixed3 Emission;
    half Specular;
    fixed Gloss;
    fixed Alpha;
};

        这里我来说明一下,首先就是这个SurfaceOutput的结构体,这个结构体定义了光照模型函数所需要的参数数据 (比如反射率、法线、放射等参数) , 然后就是UnityLight和UnityGI,分别如下(请打开UnityLightingCommon.cginc):

      

struct UnityLight
{
    half3 color;
    half3 dir;
    half  ndotl; // Deprecated: Ndotl is now calculated on the fly and is no longer stored. Do not used it.
};

struct UnityIndirect
{
    half3 diffuse;
    half3 specular;
};

struct UnityGI
{
    UnityLight light;
    UnityIndirect indirect;
};

        一目了然,UnityLight中包含光线颜色,朝向和ndotl计算好的数值,ndotl的意义我在光照模型(二)讲到了,是计算diffuse所需要的参数, 不过unity并不储存这个值了,得由我们自己去计算,UnityGI包含了一个Light结构体和一个Indirect结构体,Indirect中包含了漫反射和镜面反射颜色。     

        接下来就是这些结构体在光照模型函数中的使用了,如下:


inline fixed4 UnityLambertLight (SurfaceOutput s, UnityLight light)
{
    fixed diff = max (0, dot (s.Normal, light.dir));

    fixed4 c;
    c.rgb = s.Albedo * light.color * diff;
    c.a = s.Alpha;
    return c;
}

inline fixed4 LightingLambert (SurfaceOutput s, UnityGI gi)
{
    fixed4 c;
    c = UnityLambertLight (s, gi.light);

    #ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
        c.rgb += s.Albedo * gi.indirect.diffuse;
    #endif

    return c;
}

        这里展示的是Unity自带的Lambert光照模型,Lambert光照模型接收到来自Unity光照系统计算出的SurfaceOutput和UnityGI结构数据,然后算出基本的diffuse颜色,再加上GI自带的diffuse颜色,然后将最终的颜色rgb值返回给使用这个光照模型的surf函数,比如如下:

         #pragma surface surf Lambert   也就是说使用Lambert光照模型的surf表面着色函数

         接下来我们需要做的就是按照这个LightingLambert光照模型函数来仿写我们自己的光照模型函数,如下:

  

Shader "Custom/YangSurfaceShader" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
		_Color ("Color", Color) = (1,1,1,1)
		_Specular("Specular",Range(1,10)) = 5
	}

	SubShader{
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM

		#pragma surface surf YangLightModel

		#include "UnityPBSLighting.cginc"
		#include "UnityCG.cginc"
		#include "Lighting.cginc"

		#pragma target 3.0

		sampler2D _MainTex;
		float4 _Color;
		half _Specular;

		struct Input {
			float2 uv_MainTex;
		};

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

		inline float4 LightingYangLightModel(SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, float atten)
		{
			/*首先计算当前环境光*/
			float3 unity_buildin_ambient_light_color = UNITY_LIGHTMODEL_AMBIENT.xyz;
			/*然后计算当前场景light的颜色值rgb*/
			float3 unity_buildin_light_color = _LightColor0.rgb;
			/*计算漫反射diffuse颜色*/
			float3 diffuse = s.Albedo * unity_buildin_light_color * max(dot(s.Normal,lightDir),0);
			/*计算光源方向和视线方向的中和向量的单位向量H*/
			float3 h_dir_or_call_VPlusL_dir = normalize(lightDir + viewDir);
			/*计算反射specular颜色*/
			float3 specular = unity_buildin_light_color * pow(max(dot(s.Normal,h_dir_or_call_VPlusL_dir),0),s.Specular);

			/*定义一个颜色合并所有的颜色并返回给使用这个光照模型的surf函数*/
			float4 col;
			col.xyz = diffuse + unity_buildin_ambient_light_color + specular;
			col.w = s.Alpha;	
			return col;	
		}	
		ENDCG
	}
	FallBack "Diffuse"
}

         这里我要讲一下这个CG shader具体的含义,如下:

         一.Properties字段

              相信_MainTex,_Color,_Specular大家应该能一眼看懂,无非就是主纹理贴图,放射颜色值和镜面反射高亮幂参数。

         二.surf函数以及Input结构体

              从这里开始就让人迷惑了,void surf (Input IN, inout SurfaceOutput o),这个函数后面的SurfaceOutput参数我们聊过,无非就是让我们自己填充SurfaceOutput这个结构数据,然后提供给光照模型去使用。

              那么前面一个Input结构体是什么意思呢?这个其实是Unity SurfaceShader编译surf函数时必须的一个形参结构体,目的就是为了规范化封装储存surf函数计算需要的数据,比如uv_MainTex就储存了主纹理_MainTex采样顶点uv,传递给surf使用(这里要特别提醒的是,unity的shader编译器,已经将Input.uv_MainTex根据字符串匹配绑定为主纹理采样顶点uv的字段,这个名称是不能变的,修改该字符串将得不到编译绑定效果)当然我们可以定义多个变量去储存更多内容,比如如下:

                

struct Input
{
    float2 uv_MainTex;
    float2 uv_BumpMap;
    float3 viewDir;
};

              我们把主纹理,法线贴图,视线方向向量绑定封装进Input,然后给surf函数使用,surf函数利用Input结构数据来计算光照模型需要的数据,比如如下:

fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;

               上面代码的意义就是说根据uv_MainTex去计算主纹理_MainTex的采样数据(tex2D为纹理采样函数,cg语法篇章我会讲解),然后将采样后的颜色乘上我们定义的主颜色值,得到最终主颜色值。

           三.inline float4 LightingYangLightModel(SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, float atten)

               首先,这个光照函数为什么要定义成这样呢?

              ①.首先这个光照函数前面要加上Lighting前缀,这个是Unity shader编译器的规范,为了识别这是一个光照函数,才能进行后续的处理。

               ②.(SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, float atten)形参列表,除了SurfaceOutput这个我们知道外,其他的形参都是什么意思呢?虽然从形参名称我们能够知道lightDir就是光源方向向量,viewDir就是视线方向向量,atten代表某个衰减值。但是实际上是不是能起到如形参名一样的效果呢?比如lightDir和viewDir就是计算diffuse和specular颜色的关键参数(光照模型二和三中是我们自己计算的,这里直接提供的话就方便很多),我们直接在LightingYangLightModel中使用lightDir和viewDir去计算diffuse和specular,可以看到效果果然如同自己亲自计算这两个向量能达到的效果。也就是说这一系列形参列表的实际传递参数确实如其所命名。

               这个到底是为什么呢?还是以标准光照模型为例,如下:

                

inline half4 LightingStandard (SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
{
    s.Normal = normalize(s.Normal);

    half oneMinusReflectivity;
    half3 specColor;
    s.Albedo = DiffuseAndSpecularFromMetallic (s.Albedo, s.Metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);

    // shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
    // this is necessary to handle transparency in physically correct way - only diffuse component gets affected by alpha
    half outputAlpha;
    s.Albedo = PreMultiplyAlpha (s.Albedo, s.Alpha, oneMinusReflectivity, /*out*/ outputAlpha);

    half4 c = UNITY_BRDF_PBS (s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);
    c.a = outputAlpha;
    return c;
}

        同时我们来打开UnityPBSLighting.cginc和Lighting.cginc这两个文件,可以看到很多各种光照模型的函数定义和形参列表,实际上我们定义的LightingYangLightModel无非就是其中一个重载函数而已,Unity Shader编译器跟我们的重载函数,编译调用时传递相应的数据。

        最后LightingYangLightModel中那些计算公式的意义,在光照模型二和三中都有相当详细的讲解,我就不赘述了,只做了简单的注释。

        接下来我们看下这个cg shader的具体表现效果,如下图:

        以上,就是自定义Surface着色器的通用CG shader写法,cg的语法我会额外开一个分类版块进行详细讲解学习。

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/81870248