赞
踩
具体做法就是添加一个预编译指令:
#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的语法我会额外开一个分类版块进行详细讲解学习。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。