Unity Shader 基础光照(build)
1.光学基础
1.1 光源:
光线由光源发出,在实时渲染中,我们通常把光源当成一个没有体积的点。用l代表光的方向,用辐照度来量化光的强度。辐照度的意思表示垂直于l的单位面积上单位时间穿过的能量。如果光于平面不垂直,则辐照度为 cos S/l,S为光线与平面法线的夹角。
1.2 吸收与散射:
光源与物体相交后通常会有两种结果:散射 和 吸收。
散射:只改变光的方向,不改变光的密度和颜色; 吸收: 只改变光的方向和密度,不改变光的方向。散射又分为折射和反射两种。
散射又分为两种,一种是直接被反射出来,另一种是经过内部折射或者内部吸收后翻出出来的光。我们把高光反射(specular)表示表面反射的光,漫反射(difuss)表示折射、吸收、散射出表面的光。
1.3 着色:
着色,指根据材质属性、光源信息、使用一个等式去计算沿着某个观察方向的初射度过程。我们把这个等式称为 光照模型(Lighting Model),不同的光照模型有不同的目的,例如一些描述粗糙物体表面,一些描述金属表面。
1.4 BRDF 光照模型
BRDF光照模型用来回答了当光线从某个方向照射到一个表面时,有多少光线被反射,反射的角度有哪些等问题。BRDF大多都是一个数学公式给定的。这些往往都是经验模型,你可以自己试一试,图形学第一定律“如果它看起来是对的,那么它就是对的”。
2.标准光照模型
标准光照模型的基本方法是,把进入摄像机内的光线分成4个部分。每一个部分使用一种方法来计算它的贡献度:
自发光(emissive),用于描述给定一个方向时,一个表面本身会向该方向发射多少辐射量。值得注意的是如果没有全局光照技术,这些自发光的表面并不会真的照亮周围的物体,而它本身看起来更亮了而已。
高光反射(specular),这部分用于描述当前光线从光源照射到模型表面时,该表面完全镜面反射散射多少辐射量。
漫反射(difuss),当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
环境光(ambient),描述其他所有的间接光线。
逐顶点还是逐像素?
通过基本光照模型,我们可以得到数学工作,那在哪开始计算这些光照模型呢?我们可以在片元着色器中计算,也叫做逐像素光照。或者是在顶点着色器中计算,也叫做逐顶点光照。
在逐像素光照中,我们会以每个像素为单位,得到它的法线,在片之间进行法线的差值计算,然后进行光照模型计算。逐顶点光照,我们会计算每个顶点的光照,然后会在渲染图元内部进行线性差值,最后输出成像素颜色。逐顶点光照计算量小于逐像素光照。
3.Unity中的环境光与自发光
在光照模型中,环境光和自发光是最简单的,Unity中可以通过Window->Rendering->Lighting->Environment 中进行设置环境光。而由于绝大多数的物体是没有自发光特性的,因此大部分不用设置。如果需要的话只需要在片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色上即可。
4.使用Unity Shader实现漫反射光照模型
在基本光照模型中漫反射的计算公式是:
Cdiffuse=(Clight*Mdiffuse)max(0,n*l);
其中:Clight表示入射光颜色和强度,Mdiffuse为材质漫反射系数,n为法线,l为入射方向。
1.逐顶点光照:
Shader "Untiy Book/Chapter6/DiffuseVertexLevel" { Properties { //控制漫反射颜色 _Diffuse("Diffuse",Color) = (1,1,1,1) } SubShader { Pass { //只有定义了正确的LigtMode,才能得到后续的一些光照变量 Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "Lighting.cginc" //为了使用在Properties定义的变量,要定义一个与之属性类型匹配的变量 fixed4 _Diffuse; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos:SV_POSITION; fixed3 color : COLOR; }; v2f vert (a2v v) { v2f o; // 顶点着色器基本任务 将模型空间转为裁剪空间 o.pos = UnityObjectToClipPos(v.vertex); // 得到环境光 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 将顶点的法线从模型空间转为世界空间 归一化处理,防止结果为负数 fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal)); // 得到光照强度和方向 fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); // 光的颜色强度*材质漫反射颜色 得到漫散射光照 saturate函数是Cg提供的可以班参数截取到0-1之间。 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb*saturate(dot(worldNormal,worldLight)); o.color = ambient+diffuse; return o; } fixed4 frag (v2f i) : SV_Target { return fixed4(i.color,1.0); } ENDCG } } Fallback "Diffuse" }
效果: 逐顶点的漫反射光照,对于细分程度高的物体可以有比较好的效果,对于细分程度低的模型,会看到背光面与向光面有一些齿轮。
2.逐像素光照:
Shader "Untiy Book/Chapter6/DiffusePixelLevel" { Properties { //控制漫反射颜色 _Diffuse("Diffuse",Color) = (1,1,1,1) } SubShader { Pass { //只有定义了正确的LigtMode,才能得到后续的一些光照变量 Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "Lighting.cginc" //为了使用在Properties定义的变量,要定义一个与之属性类型匹配的变量 fixed4 _Diffuse; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos:SV_POSITION; fixed3 worldNormal : TEXCOORD0; }; v2f vert (a2v v) { v2f o; // 顶点着色器基本任务 将模型空间转为裁剪空间 o.pos = UnityObjectToClipPos(v.vertex); // 顶点着色器不再计算,只需要将世界坐标法线给片元着色器 o.worldNormal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 将顶点的法线从模型空间转为世界空间 归一化处理,防止结果为负数 fixed3 worldNormal = normalize(i.worldNormal); // 得到光照强度和方向 fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); // 得到物体散射 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb*saturate(dot(worldNormal,worldLight)); fixed3 color = ambient+diffuse; return fixed4(color,1.0); } ENDCG } } Fallback "Diffuse" }
效果: 这样光线过渡就有了更加平滑的效果了。但问题又来了,在光线无法到达的区域,模型没有任何的明暗变化。
3.半兰伯特模型
这种模型是在开发半条命时提出的一种技术。其实也就是更改了光照模型的公式,如下:
Cdiffuse=(Clight*Mdiffuse)(alpha(n*l)+beta);
其中对n*l 乘以一个alpha倍再加上了 beta偏移,绝大多数情况下两个值都为0.5,这样保证了值不为0,且原有的0值处,现在为0.5了所以保证了背光面也能有阴影变化
Shader "Untiy Book/Chapter6/HalfLambert" { Properties { //控制漫反射颜色 _Diffuse("Diffuse",Color) = (1,1,1,1) } SubShader { Pass { //只有定义了正确的LigtMode,才能得到后续的一些光照变量 Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "Lighting.cginc" //为了使用在Properties定义的变量,要定义一个与之属性类型匹配的变量 fixed4 _Diffuse; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos:SV_POSITION; fixed3 worldNormal : TEXCOORD0; }; v2f vert (a2v v) { v2f o; // 顶点着色器基本任务 将模型空间转为裁剪空间 o.pos = UnityObjectToClipPos(v.vertex); // 顶点着色器不再计算,只需要将世界坐标法线给片元着色器 o.worldNormal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 将顶点的法线从模型空间转为世界空间 归一化处理,防止结果为负数 fixed3 worldNormal = normalize(i.worldNormal); // 得到光照强度和方向 fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); fixed halfLambert = dot(worldNormal,worldLight)*0.5 +0.5; // 得到物体散射 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert; fixed3 color = ambient+diffuse; return fixed4(color,1.0); } ENDCG } } Fallback "Diffuse" }
效果:
三种漫反射光照对比效果:
5.使用Unity Shader实现高光反射模型
高光反射的基本光照模型为:
Cspecular=(Clight*Mspecular)max(0,(v*r))gloss;
其中,入射光线的颜色和强度Clight,材质的高光反射系数Mspecular,视角方向v,反射方向r。其中r = l-2(n*l)n
1.逐顶点光照
Shader "Untiy Book/Chapter6/SpecularVertex" { Properties { _Diffuse ("Diffuse",Color) = (1,1,1,1) // 控制高光反射颜色 _Specular ("Specular",Color) = (1,1,1,1) // 控制高光反射大小 _Gloss("Gloss",Range(8.0,256)) = 20 } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" #include "UnityCG.cginc" fixed4 _Diffuse; fixed4 _Specular; float _Gloss; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; fixed3 color : COLOR; }; v2f vert (a2v v) { v2f o; // 基本功能 o.pos = UnityObjectToClipPos(v.vertex); // 漫反射部分 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal)); fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); fixed3 diffuse = _LightColor0 * _Diffuse.rgb *saturate(dot(worldNormal,worldLight)); // 高光反射部分 fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal)); fixed3 viewDir = normalize(UnityWorldSpaceViewDir(mul(unity_ObjectToWorld,v.vertex))); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss); // 环境+漫反射+高光反射 o.color = ambient + diffuse + specular; return o; } fixed4 frag (v2f i) : SV_Target { return fixed4(i.color,1.0); } ENDCG } } Fallback "Specular" }
这种方式得到的高光部分明显不平滑,因为高光反射部分的计算是非线性的,但是顶点着色器中计算光照部分是线性的。
2.逐像素光照
Shader "Untiy Book/Chapter6/SpecularPixel" { Properties { _Diffuse ("Diffuse",Color) = (1,1,1,1) // 控制高光反射颜色 _Specular ("Specular",Color) = (1,1,1,1) // 控制高光反射大小 _Gloss("Gloss",Range(8.0,256)) = 20 } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" #include "UnityCG.cginc" fixed4 _Diffuse; fixed4 _Specular; float _Gloss; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; }; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); // 只需计算世界空间下的法线坐标和顶点方向 o.worldNormal = UnityObjectToWorldNormal(v.vertex); o.worldPos = mul(unity_ObjectToWorld,v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // 环境光 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); // 漫反射 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight)); // 计算高光 fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz); fixed3 specular = _LightColor0.rbg * _Specular.rbg* pow(saturate(dot(reflectDir,viewDir)),_Gloss); return fixed4(ambient+diffuse+specular,1.0); } ENDCG } } Fallback "Specular" }
效果:我们看到了,光照部分更加平滑了。
3.Blinn-Phong 光照模型
该公式比较复杂,它是一个经验模型,并不完全符合真实世界中的光照现象。具体公式为:
Shader "Untiy Book/Chapter6/BlinnPhong" { Properties { _Diffuse ("Diffuse",Color) = (1,1,1,1) // 控制高光反射颜色 _Specular ("Specular",Color) = (1,1,1,1) // 控制高光反射大小 _Gloss("Gloss",Range(8.0,256)) = 20 } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" #include "UnityCG.cginc" fixed4 _Diffuse; fixed4 _Specular; float _Gloss; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; }; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); // 只需计算世界空间下的法线坐标和顶点方向 o.worldNormal = UnityObjectToWorldNormal(v.vertex); o.worldPos = mul(unity_ObjectToWorld,v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // 环境光 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); // 漫反射 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight)); // 计算高光 //fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz); // change fixed3 halfDir = normalize(worldLight+viewDir); fixed3 specular = _LightColor0.rbg * _Specular.rbg* pow(max(0,dot(worldNormal,halfDir)),_Gloss); return fixed4(ambient+diffuse+specular,1.0); } ENDCG } } Fallback "Specular" }
效果:我们可以看到高光反射部分看起来更大,更亮一些。实际渲染中我们更喜欢这种这种方式
全部对比:从左到右,从上到下