前言
光学中,我们是用辐射度来量化光。
光照按照不同的散射方向分为两种形式:漫反射(diffuse)和高光反射(specular)。
高光反射描述物体是如何反射光线的,漫反射则表示有多少光线会被折射、吸收和散射出表面。根据入射光线的数量和方向,我们可以计算出射光线的数量和方向,通常使用出射度描述它。辐射度和出射度之间是线性关系的,它们之间的比值就是材质的漫反射和高光反射属性。
接下来将会介绍Unity内的两种光照方式实现的原理以及代码实现:
内容
BRDF 模型
早期的游戏引擎一般只有一个光照模型,BRDF 模型,即标准光照模型(Bidirectional Reflectance Distribution Function),又称 Phong 模型。
它的基本方法是,把进入到摄像机内的光线分为 4 部分,每部分使用一种方法来计算它的贡献度。
- 自发光 描述当给定一个方向时,一个表面本身会向该方向发射多少辐射量。注意,如果没有使用全局光照,这些自发光的表面并不会照亮周围的物体,只是他本体看起来更亮而已。
- 高光反射(金属之类的) 描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
- 漫反射 该表面会向四周散射多少辐射量
- 环境光 描述其他所有的间接光照(就是其他物体的发射的光线)。
兰伯特定律
发射光线的强度与表面法线和光源方向之间的夹角余弦值成正比。
漫发射的计算公式
C = (c * m) * max( 0 , n * l )。
小写 c 为光源颜色,m 为漫反射颜色,n 是表面法线,l 是指向光源的单位矢量。需要注意应该防止发现和光源点乘的结果为负值(避免物体被从后面来的光源照亮),所以用 max 函数限制其为正数。
高光反射的计算公式
- r = 2(n * l)n - l
- C = (c * m) * (max(0,v * r)) ^ gloss
n,l 代表意义与漫反射公式相同,r 为光的反射方向矢量,m 为高光反射颜色,v 为 视角方向矢量,gloss 为材质的光泽度,gloss 越大亮点就越小。
Blinn 模型的高光反射计算公式
与上述 Phong 模型不同的是,Blinn 模型引入了一个新矢量 h,通过对 v 和 l 的取平均后再归一化得到:h = (v + l) / | v + l |。
公式为:C = (c * m) * (max ( 0, n * h ))^gloss
逐像素与逐顶点光照
在片元着色器中计算,称为逐像素光照。在顶点着色器中计算,称为逐顶点光照(高罗德着色)。逐顶点光照是在每个顶点上计算光照,然后在渲染图元内部进行线性插值,最后输出成像素颜色。由于顶点数目小于像素数目,所以其计算量小于逐像素光照,因此在阴影交界处会出现锯齿,精细度不如逐像素光照。
Unity 的环境光和自发光
环境光可以通过 Shader 的内置变量 UNITY_LIGHTMODEL_AMBIENT 访问
自发光只需要在片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色上就行。
漫反射和高光反射的 Shader 实现
漫反射光照模型(逐顶点)
Shader "Unity Shader Book/Chapter6/Diffuse Vertex-Level" { Properties { _Diffuse("Diffuse",Color) = (1.0,1.0,1.0,1.0) } SubShader { Tags { "LightMode"="ForwardBase" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Diffuse; struct appdata { float4 vertex : POSITION; //顶点在模型空间的坐标 float3 normal : NORMAL; //法线 } ; struct v2f { fixed3 color : COLOR; //输出颜色 float4 pos : SV_POSITION; //输出位置 } ; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); //模型空间转换到裁剪空间 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //环境光 fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); //法线方向n fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); //光源位置 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 } } }
高光反射模型(逐顶点)
Shader "Unity Shader Book/Chapter6/SpecularVertexLevel" { Properties { _Diffuse("Diffuse",Color) = (1.0,1.0,1.0,1.0) _Specular("Specular",Color) = (1.0,1.0,1.0,1.0) _Gloss("Gloss",Range(8.0,256)) = 20 //镜面反射面积大小 } SubShader { Tags { "LightMode"="ForwardBase" } //小心,如果没有这个光源会反向 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Diffuse; fixed4 _Specular; float _Gloss; struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; } ; struct v2f { float3 color: COLOR; float4 pos : SV_POSITION; } ; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); //等同于 "o.pos = mul(UNITY_MATRIX_MVP,v.vertex);" fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); //或使用 UnityObjectToWorldNormal(v.normal) fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir)); fixed3 reflectDir = normalize(reflect(-worldLightDir,worldNormal)); //反射方向矢量r fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld,v.vertex).xyz); //视线方向 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 } } }