标准光照模型只关心直接光照,也就是直接从光源发射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。基本方法是,把进入摄像机内的光线分为4各部分,每个部分使用一种方法计算贡献度。
自发光部分:用于描述给给定一个方向时,一个表面本身会向该方向发射多少的辐射量,如果没有使用全局光照技术,这些自发光表面并不会真的照亮周围的物体,而是他本身看起来更亮了。
高光反射部分:用于描述当光线从光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
漫反射部分:用于描述光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
环境光部分:用于描述其他所有的间接光照。
环境光
标准光照模型,使用一种称为环境光的部分来近似模拟间接光照,环境光计算非常简单,通常是一个全局变量,就是场景中所有的物体都使用这个环境光。下面等式给出了计算环境光的部分:
自发光
光线可以直接由光源发射进入摄像机,而不需要经过任何物体的反射。标准光照模型使用自发光计算这部分的贡献度,计算直接使用了材质的自发光颜色:
实时渲染中,通常自发光的表面往往不会照亮周围的表面,也就是说,一个物体不会被当作光源。使用全新的全局光照系统可以模拟这类自发光对周围物体的影响。
漫反射
漫反射光照是用于对那些被物体表面随机散射到各个方向的服辐射度进行建模。漫反射中,视角位置不重要,因为反射是完全随机,因此可以认为在任何反射上的分部都一样,但是入射角度很重要。漫反射复合兰伯特定律:反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比,因此漫反射的计算如下:
高光反射
这里的是一种经验模型,并不完全符合真实世界的高光反射想想,用于极端那些沿着完全镜面反射方向被反射的光线,可以让物体看起来有光泽,比如金属材质。计算高光反射需要知道的信息比较多,表面法线,视角方向,光源方向,反射方向,下图给出这些矢量
在这四个矢量中,实际上只需要知道其中3个,第四个可以求出
这样可以利用Phong模型来计算高光反射部分
和Phong模型相比,Blinn提出了一个简单的修改方法得到类似结果。基本思想是避免计算反射方向r,Blinn模型引入了一个新的矢量h,通过对v和l取平均后在归一化得到的
然后使用n和h之间的夹角计算,而不是v和r之间的夹角。
Blinn模型公式
硬件实现时,如果摄像机和光远距离模型足够远,Blinn模型会快Phong模型,因为此时可以认为v和l都是定值,h是一个常量,但是v和l不是定值时候,Phong会快一些。两种模型都是经验模型,也就是说,不应该认为Blinn模型是对正确的Phong模型近似,实际上,一些情况下,Blinn模型更符合实验结果。
逐像素还是逐顶点
计算光照模型可以在片元着色器计算,叫做逐像素光照,也可以在顶点着色器计算,称为逐顶点光照。逐像素光照,会以每个像素为基础,得到他的法线,然后进行光照模型计算,这种在面片之间对顶点法线插值的技术称为Phong着色,不同于之前的Phong光照模型。
逐顶点光照,称为高洛德着色,在逐顶点光照中,我们为每个顶点上计算光照,然后在渲染图元内部进行线性插值,最后输出成像素颜色。由于顶点数目远远小于像素数目,因此逐顶点光照的计算量小于逐像素光照。但是由于逐顶点光照依赖线性插值得到像素光照,因此光照模型中有非线性的计算,逐顶点光照就会出问题。而且由于逐顶点光照会在渲染图元内部对顶点颜色插值,导致渲染图元内部的颜色总是暗于顶点处的最高颜色,会产生明显的棱角想想。
标准光照是一个经验模型,但是易用计算速度和得到的效果都比较好,但是有很多局限性,很多重要的物理现象无法用Blinn-Phong模型表现,例如菲涅尔反射,其次这个模型是各向同性,当我们固定视角和光源方向旋转这个表面,反射不会发生任何改变。
Unity中的环境光和自发光
标准光照模型 环境光和自发光最简单
在Unity中场景中的环境光可以在Lighting面板中控制。在Shader可以通过Unity内置变量UNITY_LIGHTM ODEL_AMBIENT得到环境光的颜色和强度。
大部分物体没有自发光,如果要计算,只需要在片元着色器输出最后的颜色之前,把材质的自发光颜色添加到输出颜色就可以。
Unity Shader实现漫反射光照模型
之前给出了基本光照模型中的漫反射部分计算公式
公式可以看出,计算漫反射需要知道4个参数 入射光线的颜色和强度,材质的漫反射系数,表面法线以及光源方向
防止点积为负数,使用max操作,cg除了提供这样的函数,还提供了saturate函数
函数 saturate(x) x用于操作的标量或者矢量,作用是把x截取在0-1范围内,如果x是矢量,会对每一个分量进行这样的操作。
逐顶点光照
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/Diffuse Vertex-Level"
{
Properties
{
//控制材质漫反射颜色
_Diffuse ("Diffuse", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
Tags {
"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float3 color : COLOR;
float4 pos : SV_POSITION;
};
v2f vert (a2v v)
{
v2f o;
//模型顶点从模型空间转换到投影空间
o.pos = UnityObjectToClipPos(v.vertex);
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//法线从模型空间转到世界空间
fixed3 worldNormal = normalize(mul(v.normal,(float3x3)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
}
}
}
为了得到并且控制材质的漫反射颜色,首先在Shader的属性语义块中声明了一个Color类型的属性,初始设为白色
Properties
{
//控制材质漫反射颜色
_Diffuse ("Diffuse", Color) = (1,1,1,1)
}
然后在SubShader语义块中定义了一个Pass语义块,因为顶点片段着色器需要写在Pass语义块,而非SubShader语义块中,而且Pass的第一行指明了该Pass的光照模式
Tags {
"LightMode"="ForwardBase"}
LightMode标签是Pass标签的一种,用于定义该Pass在Unity的光照流水线的角色,只有正确定义了LightMode,才能得到一些Unity内置的光照变量,下面的_LightColor0
然后使用CGPROGRAM和ENDCG包围Cg代码片,为了使用Unity内置一些变量,后面的_LightColor0,还要包含进Unity的内置文件
#include "Lighting.cginc"
为了在Shader中使用属性语义块声明属性,定义一个和该属性类型匹配的变量,接下来定义了顶点着色器的输入输出结构体
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float3 color : COLOR;
float4 pos : SV_POSITION;
};
为了访问顶点法线,需要在a2v定义一个normal变量,通过使用NORMAL语义告诉Unity把模型顶点的发现信息存储在normal中,为了把在顶点着色器中计算得到的光照
颜色传递给片元着色器,需要在v2f定义一个color表面朗,并且不是必须使用COLOR语义,一些资料会使用TEXCOORD0语义。
接下来就是顶点着色器:
v2f vert (a2v v)
{
v2f o;
//模型顶点从模型空间转换到投影空间
o.pos = UnityObjectToClipPos(v.vertex);
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//法线从模型空间转到世界空间
fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));