光照模型的概念目前还不明晰,因为笔者也是一个初学者,所以请小心对待笔者介绍的内容。笔者认为光照模型是规定光照算法的模型,比如说前面提到的Lambert光照模型,规定了材质表面的光线的表达式为
环境光+散射光+反射高光+放射光
我们通过一组小实验来说明如何自定义光照模型。
第1.1步:新建一个shader,两个material
其中最后一个是采用了标准的diffuse
MEasyNormalMapping(Material)采用了EasyNormalMapping(shader)。
第1.2步:
▼代码开始 Shader "Custom/EasyNormalMapping" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf CustomDiffuse //还记得吗?这一句的语法是:#pragma surface 光线处理函数 光照模型 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; fixed4 _Color; //这个surf非常简单,就是取出了主纹理罢了 void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Alpha = c.a; } /*这里有很多注意事项: a:下面的LightingCustomDiffuse是CUstomDiffuse这个光照模型的计算光照的实现。 b:声明此光照计算实现函数时,名称必须要按照:Lighting<光照模型名称>,此处即LightingCustomDiffuse c:光照模型首先经过surf方法得到表面颜色,然后利用LightingCustomDiffuse计算光线,最后呈现在屏幕上 d:这里采用了一个典型的形参列表:(表面颜色 SurfaceOutput s, 光线的来源方向 fixed3 lightDir, 光衰减的系数 fixed atten) 请注意 lightDir是指从表面上的研究点到光源的方向,也就是指向光源方向,而不是入射方向(是入射方向的反方向) e:返回一个四维的小数,其实是指返回的光线 (其它的形参列表可以看:http://blog.csdn.net/henger_/article/details/52689080) */ inline float4 LightingCustomDiffuse(SurfaceOutput s, fixed3 lightDir, fixed atten) { //都做了些什么呢?首先是表面法向量和光线方向的点乘,此数目越靠近1,散射越明亮(是不是很符合常理!) float difLight = max(0, dot(s.Normal, lightDir)); //col表示结果的颜色 float4 col; //这里_LightColor0是由Unity根据场景中的光源得到的光源的光色,具体来由不清楚,简单说就是光源的颜色。 //这个公式表明:散射光色 = 表面的反射系数 * 光源 * 散射强度 * 衰减系数(有许多博文说这里要乘以2) col.rgb = s.Albedo*_LightColor0.rgb*(difLight*atten); col.a = s.Alpha; return col; } ENDCG } FallBack "Diffuse" } ▲代码结束
第1.3步:我们为两个材料都选上石头的纹理图,最后可以在预览图中看出,它们的效果完全一样。
总结:我们已经自定义了一个光照模型,这个光照模型和标准内置的diffuse模型效果一样,因为我们的实现原理是一样的。
本文参考了猫都能学会的Unity3D Shader入门指南(二)
第二组实验
halfLambert模型:
将上面的代码稍修改一下,得到HalfLambert模型,这是一个在低光线条件下增亮的做法,是将光强系数 * 0.5+0.5的做法,非常简单。
inline float4 LightingCustomDiffuse(SurfaceOutput s, fixed3 lightDir, fixed atten)
{
float difLight = max(0, dot(s.Normal, lightDir));
float4 col;
//只增加这一行
difLight = difLight * 0.5 + 0.5;
col.rgb = s.Albedo*_LightColor0.rgb*(difLight*atten);
col.a = s.Alpha;
return col;
}
下图中左边应用了HalfLambert模型。
第三组实验:实现积雪效果
第3.1步:新建一个Snow的shader,新建一个名为MSnow的材质。
第3.2步:Snow的代码如下:
▼代码开始 Shader "Custom/Snow" { Properties{ _MainTex("Albedo (RGB)", 2D) = "white" {} _Bump("Bump", 2D) = "bump"{} _Snow("Snow Level", Range(0,1)) = 0.2 _SnowColor("Snow Color", Color) = (1,1,1,1) _SnowDirection("Snow Direction", Vector) = (0,1,0) //注意U3D中的Y轴是反重力方向的,而不是Z轴。这和UE4不一样。 } SubShader{ Tags{ "RenderType" = "Opaque" } LOD 300 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; sampler2D _Bump; struct Input { float2 uv_MainTex; float2 uv_Bump; float3 worldNormal; INTERNAL_DATA /*什么意思?这表示当前点在世界中的法方向,我们采用;INTERNAL_DATA来标记这个特殊的输入*/ //更为详细的参考:https://docs.unity3d.com/Manual/SL-SurfaceShaders.html }; float _Snow; float4 _SnowColor; float4 _SnowDirection; void surf(Input IN, inout SurfaceOutput o) { half4 c = tex2D(_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump)); //解释:输入IN表示输入的点(包含一个特殊的输入,就是世界坐标下的法方向) //WorldNormalVector(IN, o.Normal)表示IN点以及它的法向量在世界坐标下的向量表达, //和雪的方向的点击,和阈值作比较 //插值函数:lerp(a,b,c)表示(b-a)*c+a if ( dot( WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) > lerp(1, -1, _Snow)) { o.Albedo = _SnowColor.rgb;//取雪色 } else { o.Albedo = c.rgb;//取原色 } o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } ▲代码结束
第3.3步:注意配上法向量和主纹理。
效果:
第四组实验:本组实验修改了顶点,也就是对模型进行了顶点的修改。此前的操作都是在贴图上进行的,顶点没有发生过改动。本组实验强调修改顶点。
第4.1步:在刚刚的Snow(Shader)代码中稍微修改,如下:
▼代码开始 // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' Shader "Custom/Snow" { Properties{ _MainTex("Albedo (RGB)", 2D) = "white" {} _Bump("Bump", 2D) = "bump"{} _Snow("Snow Level", Range(0,1)) = 0.2 _SnowColor("Snow Color", Color) = (1,1,1,1) _SnowDirection("Snow Direction", Vector) = (0,1,0) //注意U3D中的Y轴是反重力方向的,而不是Z轴。这和UE4不一样。 _SnowDepth("Snow Depth", Range(0,0.3))=0.1 //改动1 } SubShader{ Tags{ "RenderType" = "Opaque" } LOD 300 CGPROGRAM #pragma surface surf Lambert vertex:vert //改动2:在末尾添加vertex:Fun表示顶点模型将修改,修改的方法为Fun sampler2D _MainTex; sampler2D _Bump; struct Input { float2 uv_MainTex; float2 uv_Bump; float3 worldNormal; INTERNAL_DATA /*什么意思?这表示当前点在世界中的法方向,我们采用;INTERNAL_DATA来标记这个特殊的输入*/ //更为详细的参考:https://docs.unity3d.com/Manual/SL-SurfaceShaders.html }; float _Snow; float4 _SnowColor; float4 _SnowDirection; float _SnowDepth; //改动3 void surf(Input IN, inout SurfaceOutput o) { half4 c = tex2D(_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump)); //解释:输入IN表示输入的点(包含一个特殊的输入,就是世界坐标下的法方向) //WorldNormalVector(IN, o.Normal)表示IN点以及它的法向量在世界坐标下的向量表达, //和雪的方向的点击,和阈值作比较 //插值函数:lerp(a,b,c)表示(b-a)*c+a if ( dot( WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) > lerp(1, -1, _Snow)) { o.Albedo = _SnowColor.rgb;//取雪色 } else { o.Albedo = c.rgb;//取原色 } o.Alpha = c.a; } //改动4:vert函数处理顶点,对于每一个顶点v,进行如下修改。inout appdata_full v是顶点的固有写法。 void vert(inout appdata_full v) { /*需求:我们希望vert函数将“法方向和雪方向近似相同的所有顶点都抬高一些” 数学做法: 1:将雪方向(也就是010)转换到物体坐标系中; 这是什么意思?就是以物体来看,雪的方向是什么,如果物体是倒置的(脑子朝下的),那么物体看来的雪方向应该是0,-1,0, 如果物体时脑子靠一侧的,那么物体看的雪的方向将会是水平方向的(它是这样认为的),例如1,0,0, 如何将世界里的雪方向以物体的视角来看待呢?数学上只需要 M(world2object的转换矩阵) * 雪的世界方向 = 雪的物体系下的方向 【语法上】 1.a:unity_ObjectToWorld是内置的一个矩阵,表示此物体到世界的转换矩阵(也就是UE4中的Transform) 1.b:transpose(P)表示将P转置 1.c:mul(P,v)表示矩阵P左乘向量v即Pv 2:上述得到的物体看待的雪方向记为sn,那么sn和此顶点的法方向的点乘,点乘值越大,表示越是容易积雪的情况 如果大于阈值,那么顶点的xyz在积雪方向上增加一定的量,此处的积雪方向是指法+雪的方向 */ //写法A: float4 sn = mul(transpose(unity_ObjectToWorld), _SnowDirection); //写法B:sn = _SnowDirection; /*写法A是正确的,写法B是错误的,它没有将雪的方向转换为物体看待的描述*/ if (dot(v.normal, sn.xyz) >= lerp(1, -1, (_Snow * 2) / 3)) { v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth; } } ENDCG } FallBack "Diffuse" } ▲代码结束
效果可以看出:下面的球中,容易积雪的顶点被抬高了许多。
appdata_full拥有如下成员:
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
fixed4 color : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
以上顶点的信息中,前四个分别是:点的位置,点的正切方向(也就是定点的正切空间的指向,正切空间就是表面指向外的方向,也就是物理世界中严格的概念“法线方向”),点的法线方向(规定的法线方向,以正切空间为坐标系),纹理信息,以及最后的Color表示顶点的颜色。
——2017年7月28日16:21:45 小江村儿的文杰 zouwj5@qq.com