从宏观上讲渲染包含了两大部分:决定一个像素的可见性,决定一个像素的光照计算
而光照模型就是用于决定在一个像素上决定怎样的渲染光照计算。
一、我们是如何看到这个世界的
1.光源
实时渲染中,我们通常把光源当成一个没有体积的点,用l来表示它的方向
辐照度:量化光的方法,一个光源发出了多少光
在计算光照模型时,我们需要知道一个物体的辐照度
对于平行光辐照度通过计算垂直于l的单位时间内穿过的能量来得到。
但物体表面往往时和l不垂直的,那么
如何计算辐照度:光源方向l和法线n之间的夹角的余弦值
注意:这里的默认方向的模都为1
辐照度和照射到物体表面时光线之间的距离成反比,因此辐照度与
成正比。
可以使用光源方向l和表面法线n的点积得到。这也是用点积来计算辐照度的由来。
2.吸收和散射
光线由光源发射出来后,会与物体相交,相交的结果有两种:散射和吸收
散射:只改变方向,不改变颜色和密度
吸收:只改变密度和颜色,但不改变光线的方向
散射后的两种方向:
- 1.散射到物体内部,这种现象被称为折射或投射
- 2.散射到外部,这种现象被称为反射
对于不透明物体,折射进入物体内部的光线还会继续与内部的颗粒进行交互。其中一些光线最后会重新发射出物体表面,而另一些则被物体吸收。那些从物体表面重新发射出的光线将具有和入射光线不同的方向分布和颜色。
为了区分两种不同的散射,光照模型中用了两种不同的部分来计算:
- 高光反射:表示物体表面是如何反射光线的
- 漫反射:表示有多少光线会被折射、吸收和散射出表面
出射度:根据入射光线的数量和方向,计算出出射光线的数量和方向
辐照度和出射度之间是满足线性关系的,而他们之间的比值就是材质的漫反射和高光反射属性
3.着色
定义:着色(shading)是指根据材质属性(如漫反射属性等)、光源信息(如光源方向、辐照度等),使用一个等式去计算沿着某一个观察方向的出射度的过程。
我们把这个等式称为光照模型。不同的光照模型有不同的目的。
如:一些用于描述粗糙的物体表面,一些用于描述金属表面
4.BRDF光照模型
通俗的来讲,当给定入射光线的方向和辐照度后,BRDF可以给出在某个出射方向上的能量分布。
本章涉及的BRDF是对真实场景进行理想化和简化后的模型,并不能真实的反应物体与光线的交互。这写光照模型被称为经验模型。
图形学第一定律:如果它看起来是对的,那么它就是对的。
二、标准光照模型
1975年,著名学者裴祥风提出了标准光照模型背后的基本理念。标准光照模型只关心直接光照,也就是那些直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。
该方法将光线分为4部分:
自发光(emissive):。用于表述当给定一个方向时,一个表面本身会向该方向发射出多少辐射度。注意,如果没有使用全局光照技术,这些自发光是并不会真的照亮周围物体的,而是它本身看起来更亮了而已。
高光反射(specular):。用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
漫反射(diffuse):。用于描述当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
环境光(ambient):。用于描述其他所有间接光照。
1.环境光
间接光照是指,光线通常会在多个物体间反射,最后进入摄像机,也就是说,光线在进入摄像机前,经过了不只一次光照。
在标准光照模型中,使用了一种被称为环境光的部分来近似模拟间接光照。环境光的计算非常简单,它是一个全局变量,即场景中的所有物体都使用这个环境光。
2.自发光
自发光也可以直接由光源发射进摄像机,而不需要经过任何物体的反射。标准光照模型使用自发光来计算这个部分的贡献度。其计算很简答,直接使用该材质的自发光颜色
通常在实时渲染中,自发光表面并不会照亮周围表面,这个物体并不会被当成一个光源。
只有引入全局光照系统后才可以模拟这类自发光物体对周围环境的影响
3.漫反射
漫反射用于对那些被物体表面随机散射到各个方向的辐射度进行建模。
漫反射中视角不重要,入射光线角度很重要
兰伯特定律:反射光线的强度与表面法线和光源方向之间的夹角的余弦值成正比。
材质的漫反射颜色
光源颜色
防止物体被从后面来的光源照亮,所以将负值截去
4.高光反射
用于计算沿着完全镜面防溺水方向被反射的光线,这可以让物体看起来是有光泽的,如金属材质
需要知道的变量:法线方向、视角方向、光源方向、反射方向等
其中反射方向可以由得到
phong模型计算高光反射:
:材质的光泽度,也称反光度。用于控制高光区域的“亮点”有多宽,
越大,两点越小
:是材质的高光颜色。用于控制材质对于高光反射的强度和颜色。
:光源颜色和强度
Blinn-Phong模型:
基本思想是为避免计算。因此引入一个新的矢量
,它可以通过对
和
qupingjun后再归一化得到
Blinn-Phong模型公式:
在硬件实现中,如果摄像机和光源距离模型足够远的话,Blinn模型会快于Phong,因为和
都是定值,所以
也是一个常量。这两种模型都是经验模型,不存在谁更正确一些。
5.逐像素还是逐顶点
我们在哪里计算这些模型呢?
一般有两种选择:
- 在片元着色器中计算,也被称为逐像素光照
- 在顶点着色器中计算,也被称为逐顶点光照
逐像素光照:
以每一个像素为基础,得到它的法线(可以对顶点法线插值得到,也可以从法线纹理中采样得到),然后进行光照模型计算。这种在面片之间对顶点法线进行插值的技术被称为Phong着色,也被称为Phong插值或法线着色技术,与之前讲的Phong光照模型不同
逐顶点光照:
又称高洛德着色(Gouraud)。对每个顶点上进行光照,在渲染图元中进行线性插值,最后输出成像素颜色。由于顶点数目小于像素数目,因此顶点光照计算小于像素光照。但顶点光照更依赖于线性插值来得到像素光照,所以当光照模型中有非线性的计算时(如高光反射),逐顶点光照就会出错。逐顶点光照会在渲染图元内部对顶点进行插值,导致渲染图元内部颜色总处于暗于顶点处的最高颜色值,会导致产生明显的棱角现象。
6.总结
- 标准光照模型是经验模型
- Blinn-Phong简化了计算在某些情况下计算更快
- Blinn-Phong无法表现很多重要的物理现象,如菲涅尔反射
- Blinn-Phong是各向同性的,即当我们固定视角和光源方向旋转这个表现时,反射不会发生任何改变
- 有些表面是各向异性的反射性质,如拉丝金属、毛发等
三、unity中的环境光和自发光
在标准光照模型中计算环境光和自发光
环境光:
可以在light中控制
只需要在shader中通过内置变量
UNITY_LIGHTMODEL_AMBIENT
来控制环境光的颜色和强度信息
自发光:
在片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色上即可
四、在unityshader中实现漫反射光照模型
漫反射基本光照公式:
我们需要四个参数:
- 入射光线颜色和强度
- 材质漫反射系数
- 表面法线
- 光源方向
为了防止点积结果负值,使用max操作,CG提供了这一函数。在本例子中,使用CG的另一函数可以达到同一目的:saturate
函数:saturate()
参数:x:为用于操作的矢量或标量,可以是float、float2、float3等类型
描述:把x截取在[0,1]范围内,如果x是一个矢量,那么会对它的每一个分量进行这样的操作
1.实践:逐顶点光照
全部代码如下:
Shader "Shader/DiffuseVertexLevel"
{
Properties
{
[HDR]_Diffuse ("Diffues", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode"="ForwardBase"}//定义光照模式 只有正确定义LightMode,我们才能获得一些unity的内置变量
CGPROGRAM//包围CG代码片
#pragma vertex vert
#pragma fragment frag//定义顶点着色器和像素着色器名字
#include "UnityCG.cginc"
#include "Lighting.cginc"//为了使用unity内置的一些变量,还需要包含进unity的内置文件UnityCG.cginc
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;//为了访问顶点的法线,在appdata中定义normal变量
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 color : COLOR;//为了把顶点着色器中得到的光照颜色传递给片元着色器
};
fixed4 _Diffuse;//声明变量
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal,unity_WorldToObject));
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
}
}
Fallback "Diffuse"
}
效果如下:
2.实践:逐像素光照
全部代码如下:
Shader "Shader/DiffusePixelLevel"
{
Properties
{
[HDR]_Diffuse ("Diffues", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode"="ForwardBase"}//定义光照模式 只有正确定义LightMode,我们才能获得一些unity的内置变量
CGPROGRAM//包围CG代码片
#pragma vertex vert
#pragma fragment frag//定义顶点着色器和像素着色器名字
#include "UnityCG.cginc"
#include "Lighting.cginc"//为了使用unity内置的一些变量,还需要包含进unity的内置文件UnityCG.cginc
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;//为了访问顶点的法线,在appdata中定义normal变量
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
fixed4 _Diffuse;//声明变量
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal,unity_WorldToObject));
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.半兰伯特模型
对于模型的背光面,原兰伯特中点积结果将映射到同一个值。
而半兰伯特,则在背光面也可以有明暗变化,不同的点积结果会映射到不同的值上
半兰伯特无任何物理依据,仅仅是视觉加强技术。
五、在unityshader中实现高光反射光照模型
基本同理