紧接上一篇: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的语法我会额外开一个分类版块进行详细讲解学习。