光照中的基础概念如下:
1.辐射度:在物体的单位大小上,在单位时间内,获取的光线能量。
2.出射度:光源出射光线的数量和方向。
3.物体光线距离:光线和物体相交时,物体表面的光线距离为d/cosQ,其中d表示光线之间的距离,cosQ表示光线与物体法线之间的夹角。
常见的光线如下:
环境光:用来模拟间接光照。
自发光:用来表示光线无需物体反射,直接进入摄像机内的光线。
高光反射:就是光线照射到物体上时,直接被反射到物体外部,它的颜色和密度是不会发生改变的,只是方向发生变化而已。
漫反射:就是光线照射到物体上时,直接折射到物体内部,并且进一步与物体内部其他颗粒进行相交,这时一部分光被物体吸收,另一部分光被散射到物体外部,此时散射到外部的光的颜色,密度和方向都将发生变化。
光照模型:在物体某一个方向上计算出射度的等式。常见的光照模型如下:
1.标准光照模型:用来描述光线照射物体表面上时,直接反射进入摄像机内的光线。它是一种经验模型,并不能表达所有的光照现象。
2.物理光照模型:
漫反射实现:兰伯特漫反射的计算公式为:
半兰伯特漫反射计算公式为:
顶点着色器实现兰伯特漫反射时往往存在背光面与向光面之间有锯齿问题,代码如下:
Shader "Custom/DiffuseVertexLevel" {
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 {
float4 pos : SV_POSITION;
float3 color : COLOR; // 不必须参数,可以用做自定义参数使用
};
// 顶点着色器:实现漫反射光照
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// 获取光源世界坐标系中的方向,并进行归一化处理
fixed3 world_light = normalize(_WorldSpaceLightPos0.xyz);
// 获取法线世界坐标系中的方向,并进行归一化处理
fixed3 world_normal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// 兰伯特漫反射计算公式为:光照颜色和强度变量(_LightColor0) * 漫反射系数(_Diffuse) * 光源方向(_WorldSpaceLightPos0)与法线方向(normal)的非负值点积
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(world_normal, world_light));
// 获取环境光的强度和颜色
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 最终颜色为:环境光和漫反射插值
o.color = ambient + diffuse;
return o;
}
// 片元着色器:直接将顶点着色器中获取漫反射和环境光插值后的结果返回
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
片元着色器实现兰伯特漫反射时往往会出现背光面明暗一致的问题,此时可以使用半兰伯特漫反射计算公式来解决这一问题,代码如下:
Shader "Custom/DiffusePixelLevel" {
Properties {
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { "LightModel" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
// 漫反射系数
fixed4 _Diffuse;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 world_normal : TEXCOORD0; // 纹理方向,此处作为自定义参数使用,用来存储世界坐标系中的法线坐标
};
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// 法线坐标由对象空间转换到世界空间
o.world_normal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) :SV_Target {
// 获取世界空间中法线方向,并进行归一化
fixed3 world_normal = normalize(i.world_normal);
// 获取世界空间中光照方向,并进行归一化
fixed3 world_light = normalize(_WorldSpaceLightPos0.xyz);
// 获取光照强度和颜色
fixed3 light_color = _LightColor0.rgb;
// 兰伯特计算漫反射公式为:光照强度和颜色(light_color) * 漫反射系数(_Diffuse) * 光照方向与法线方向点积的非负值
//fixed3 diffuse = light_color * _Diffuse.rgb * saturate(dot(world_normal, world_light));
// 半兰伯特漫反射计算公式为:光照颜色和强度变量(_LightColor0) * 漫反射系数(_Diffuse) * (光源方向(_WorldSpaceLightPos0)与法线方向(normal)点积 * 0.5 + 0.5)
fixed3 diffuse = light_color * _Diffuse.rgb * (dot(world_normal, world_light) * 0.5 + 0.5);
// 获取环境光的强度和颜色
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 获取环境光与漫反射的差值
fixed3 color = diffuse + ambient;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
高光反射实现:Phong高光反射计算公式如下:
Blinn-Phong高光反射计算公式如下:
顶点着色器实现Phong高光反射时往往会存在高光部分视觉变大的问题。代码如下:
Shader "Custom/SpecularVertexLevel" {
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 { "LightModel" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.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 = mul(UNITY_MATRIX_MVP, v.vertex);
// 获取世界坐标系法线方向,并进行归一化处理
fixed3 world_normal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// 获取光照世界空间方向,并进行归一化处理
fixed3 world_light = normalize(_WorldSpaceLightPos0.xyz);
// 获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 获取漫反射大小:颜色强度和颜色 * 漫反射系数 * 法线方向和光线方向的点积
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(world_normal, world_light));
// 获取反射方向,并进行归一化处理
fixed3 reflect_dir = normalize(reflect(-world_light, world_normal));
// 获取视角世界空间中的方向,并进行归一化处理
fixed3 view_dir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
// 获取高光反射大小:颜色强度和颜色 * 高光反射系数 * 视觉大小和反射方向的点积的幂
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflect_dir, view_dir)), _Gloss);
// 混合颜色,并交给片元着色器处理
o.color = ambient + diffuse + specular;
return o;
}
// 片元着色器处理函数
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
片元着色器实现Phong高光反射时往往高光部分显示的比较平滑;而实现Blinn-Phong高光反射时往往会存在高光部分会变的更大更亮问题。代码如下:
Shader "Custom/SpecularPixelLevel" {
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 { "LightModel" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "lighting.cginc"
// 关联材质属性检视面板变量
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
// 顶点着色器输入数据结构体
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
// 片元着色器输入数据结构体
struct v2f {
float4 pos : SV_POSITION;
fixed3 world_normal : TEXCOORD0;
fixed3 world_pos : TEXCOORD1;
};
// 顶点着色器处理函数
v2f vert(a2v v) {
v2f o;
// 获取齐次空间坐标
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// 获取世界空间法线坐标
o.world_normal = mul(v.normal, (float3x3)unity_WorldToObject);
// 获取世界空间顶点坐标
o.world_pos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
// 片元着色器处理函数
fixed4 frag(v2f i) : SV_Target {
// 获取归一化法线
fixed3 world_normal = normalize(i.world_normal);
// 获取世界空间光线方向
fixed3 world_light = normalize(_WorldSpaceLightPos0.xyz);
// 获取漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(world_normal, world_light));
// 获取反射方向
fixed3 reflect_dir = normalize(reflect(-world_light, world_normal));
// 获取视角方向
fixed3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.world_pos.xyz);
// 获取高光反射
float specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflect_dir, view_dir)), _Gloss);
// Blinn-Phong模型高光反射获取方式
fixed3 half_dir = normalize(world_light + view_dir);
//float specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(world_normal, half_dir)), _Gloss);
// 获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 获取h混合色
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
unity内置光照相关函数:内部已经处理了不同的光源和矩阵变换等,方便开发者快速开发,常见的光照相关的内置函数如下:
其中内置的这些光照函数是没有归一化的,所以需要使用normalize函数进行特殊处理下。