第 6 章 Unity の基本的なライティング
6.1 私たちは世界をどう見ているか
一般に、画像を生成するには実際の照明環境をシミュレートする必要があり、次の 3 つの物理現象を考慮する必要があります。
- まず、光線が発せられる
光源(light source)
のは - 次に、光線はシーン内のいくつかのオブジェクトと交差し、一部の光線は吸収され、一部は他の方向に散乱されます。
- 最後に、カメラは光を吸収して画像を生成します。
光源
光学では辐照度(irradiance)
光を定量化するために使用しますが、平行光の場合、その放射輝度は光の方向に垂直な単位面積を単位時間当たりに通過するエネルギーを計算することで求められます。
放射照度は表面に当たる光線間の距離 d/cos に反比例するため、光源方向と表面法線 n の内積を使用して取得できる cos に正比例します。
吸収と散乱
光源から光が放射された後、光はいくつかのオブジェクトと交差します。交差の結果は と の 2 つになり散射(scattering)
ます吸收(absorption)
。散乱は光の方向のみを変更し、光の密度や色は変更しませんが、吸収は光の密度と色のみを変更しますが、光の方向は変更しません。
光が物体の表面で散乱した後、2つの方向があり、1つは物体の内部に散乱するこの現象を または といい、もう1つは外部に折射(refraction)
散乱透射(transmission)
するこの現象を といいます反射(reflection)
。不透明なオブジェクトの場合、オブジェクトの内部に屈折した光線は内部の粒子と交差し続け、その一部は最終的にオブジェクトの表面から再放出され、その他はオブジェクトに吸収されます。オブジェクトの表面から再放出される光線は、入射光線とは異なる方向分布と色を持ちます。
これら 2 つの異なる散乱方向を区別するために、照明モデルの異なる部分を使用してそれらを計算します。その部分は高光反射(specular)
オブジェクトの表面が光をどのように反射するかを表し、漫反射(diffuse)
部分は光がどれだけ屈折、吸収、散乱されるかを表します。表面の。通常、入射光線の数と方向に従って、出射度(exitance)
それを説明するために使用されます。放射照度と発光の間には線形関係があり、それらの比率がマテリアルの拡散反射と鏡面反射の特性になります。
着色
着色(shading)
材料特性と光源情報に基づいて、特定の視線方向に沿った出力度を計算する式を使用するプロセスを指し、通常、この式を と呼びます光照模型(Lighting Model)
。
BRDF
BRDF (双方向反射率分布関数) は、不透明な表面で光がどのように反射されるかを定義する 4 つの実数変数の関数を指します。BRDF は、入射光の方向と放射照度が与えられると、特定の出射方向における光のエネルギー分布を与えることができます。この章に含まれる BRDF はすべて、実際のシーンの理想化され単純化されたモデルです。つまり、物体と光の間の相互作用を真に反映することはできません。これらの照明モデルは経験的モデルと呼ばれます。それにもかかわらず、これらの経験的モデルはリアルタイム レンダリングで長年使用されてきました。場合によっては、光とオブジェクトの間の相互作用をより現実的にシミュレートしたいと考えることがありますが、これは実際に起こります基于物理的 BRDF 模型
。これらのより複雑な照明モデルについては後ほど説明します。
6.2 標準照明モデル
ライティング モデルには多くの種類がありますが、初期のゲーム エンジンでは 1 つのライティング モデル、つまり標準ライティング モデルのみがよく使用されていました。その基本的な方法は、カメラに入射する光を 4 つの部分に分割し、各部分がその寄与を計算する方法を使用することです。
自发光(emissive)
、これは、方向が与えられたときに、サーフェス自体がその方向にどれだけの放射輝度を放出するかを表します。グローバル イルミネーションがないと、これらの自己発光サーフェスは実際に周囲のオブジェクトを照らすのではなく、自分自身をより明るく見せるだけです。高光反射(specular)
これは、光源からモデルの表面に光が当たったときに、完全な鏡面反射の方向に表面によってどれだけの放射線が散乱されるかを記述するために使用されます。漫反射(diffuse)
、光源からの光がモデルの表面に当たったときに、モデルの表面によって各方向にどれだけの放射線が散乱されるかを記述するために使用されます。环境光(ambient)
, このセクションは、他のすべての間接照明について説明するために使用されます。
環境光
標準の照明モデルは直接照明の記述に重点を置いていますが、現実世界ではオブジェクトは间接光照
(間接光) によって照らされることもあります。間接照明とは、複数のオブジェクトの間で反射し、最終的にカメラに入る光を指します。
標準照明モデルでは、間接照明を近似するためにアンビエント ライトと呼ばれるものを使用します。環境光の計算は非常に簡単で、通常はグローバル変数です。つまり、シーン内のすべてのオブジェクトがこの環境光を使用します。
自発光
光を光源から直接カメラに放射することもできます。標準の照明モデルは自己照明を使用してこのパーツの寄与を計算します。その計算も非常に簡単です。つまり、マテリアルの自己照明色が直接使用されます。通常のリアルタイム レンダリングでは、自己
照明surface は周囲の surface を照らしません。つまり、オブジェクトは光源として扱われません。Unity 5 で導入された全局光照系统
、このような自己発光オブジェクトが周囲のオブジェクトに及ぼす影響をシミュレートすることが可能です。
拡散反射
拡散照明は、オブジェクトの表面によって全方向にランダムに散乱される放射輝度をモデル化するために使用されます。拡散反射では、反射が完全にランダムであるため、視野角の位置は重要ではありません。すべての反射 すべての方向の分布は同じです。
ただし、入射光線の角度が重要であり、拡散照明が続きます兰伯特定律(Lambert's law)
。反射光線の強度は、表面法線と光源の方向の間の角度の余弦に比例します。
ここで、n^ は表面法線、l^ は光源を指す単位ベクトル、Mdiffuse はマテリアルの拡散色、Clight は光源の色です。なお、光源の法線と方向の点積が負の値にならないようにするために、最大値を0で切った関数を使います。オブジェクトが背後から光源によって照らされるのを防ぐことができます。
鏡面反射
ここでの鏡面反射は経験的なモデルであり、現実世界の鏡面現象に正確に対応するものではありません。鏡面反射を計算するには、表面法線 n、視野角方向 v、光源方向 l、反射方向 r など、多くの情報を知る必要があります。
これら 4 つのベクトルのうち、実際に知っていればよいのは 3 つだけで、4 番目のベクトルである反射方向は他の情報から計算できます。このようにして、それを使用してハイライト反射の部分を計算
できPhong 模型
ます
。 Mgloss は光泽度(gloss)
、光沢とも呼ばれるマテリアルです。ハイライト領域の「明るいスポット」の幅を制御します。Mgloss が大きいほど、ハイライトは小さくなります。Mspecular はマテリアルの鏡面カラーで、鏡面反射のマテリアルの強度とカラーを制御するために使用されます。Clight は光源の色と強度です。同様に、v * r の結果が負にならないようにする必要もあります。
上記の Phong モデルと比較して、Blinn は同様の効果を得る簡単な修正方法を提案しました。その基本的な考え方は、反射方向の計算を避けることです。この目的のために、Blinn 模型
新しいベクトル h が導入されます。これは、v と l を平均して正規化することによって取得されます。
次に、v と r の間の角度の代わりに、n と h の間の角度を使用します。
ハードウェア実装では、カメラと光源がモデルから十分に離れている場合、Blinn モデルは Phong モデルよりも高速になります。このとき、vもlも固定値とみなせるので、hは定数となります。ただし、v または l が一定でない場合は、Phong モデルの方が高速になる可能性があります。
これら 2 つの照明モデルは経験的なモデルである必要があり、現実世界の鏡面反射現象と完全に一致しているわけではありません。
頂点ごとまたはピクセルごと
上記の照明モデルを実装する場合、通常は 2 つのオプションがあります。
- フラグメント シェーダ ( とも呼ばれます) で計算されます
逐像素光照(per-pixel lighting)
。ピクセルごとのライティングでは、各ピクセルに基づいて法線を取得し、ライティング モデルを計算します。パッチ間の頂点法線を補間するこの手法は と呼ばれPhong 着色(Phong shading)
、フォン補間または法線補間シェーディング手法としても知られています。 - 頂点シェーダで計算され、 と呼ばれ
逐顶点光照(per-vertex lighting)
、 とも呼ばれます高洛德着色(Gouraud shading)
。頂点ごとのライティングでは、各頂点のライティングを計算し、レンダリング プリミティブ内で線形補間を実行し、最後にピクセル カラーを出力します。 - 多くの場合、頂点の数はピクセル数よりもはるかに少ないため、頂点ごとのライティングの計算量はピクセルごとのライティングの計算量よりも少なくなります。ただし、頂点ごとのライティングは線形補間に依存してピクセル ライティングを取得するため、ライティング モデルに非線形計算がある場合 (鏡面反射を計算する場合など)、頂点ごとのライティングでは問題が発生する可能性があります。
6.3 Unity の環境光と自己照明
Unity シーンのアンビエント ライトは、Unity の公式マニュアルを直接参照して、特定のライト設定でwindow -> Rending -> Ligthing
設定できます。また、Unity で自己照明を実現するのは非常に簡単です。自己照明の色を設定するだけです。フラグメント シェーダが最終的な色を出力する前のマテリアルを出力カラーに追加します。
6.4 Unity Shaderで拡散反射照明モデルを実現する
頂点ごとのライティング
Shader "Chapter 6/Diffuse Vertex Level"
{
Properties
{
// 定义漫反射颜色,默认为白色
_diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
// 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
Tags {
"LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
#include "UnityLightingCommon.cginc"
fixed4 _diffuse;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert (a2v v)
{
v2f o;
// 坐标转换
o.vertex = UnityObjectToClipPos(v.vertex);
// 获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 转换法线向量至世界坐标系
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// 场景中只有一个光源且是平行光时,光源方向可以由 _WorldSpaceLightPos0 得到,当有多个光源时无法这样使用
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// saturate 函数用于将结果截取至 [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"
}
ピクセルごとの照明
Shader "Chapter 6/Diffuse Frag Level"
{
Properties
{
// 定义漫反射颜色,默认为白色
_diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
// 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
Tags {
"LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
#include "UnityLightingCommon.cginc"
fixed4 _diffuse;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
v2f vert (a2v v)
{
// 顶点着色器只负责进行坐标转换
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 颜色计算移动至片元着色器中进行
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _diffuse.rgb * saturate(dot(i.worldNormal, worldLight));
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
ハーフランバートモデル
頂点ごとに使用するか、ピクセルごとに使用するかに関係なく、問題が発生します。光が届かない領域では、モデルの外観は通常、明暗の変化がなく完全に黒くなり、モデルの逆光部分は飛行機のように見え、モデルの詳細は失われます。このため、半兰伯特(Half Lambert)
照明モデルという改善手法が提案されています。
元の Lambert モデルと比較すると、半 Lambert 照明モデルでは、合計の内積が負になるのを防ぐために max 演算を使用せず、結果を 1 倍にスケーリングし、次のサイズを追加していることがわかります。オフセット。ほとんどの場合、合計の値は 0.5 です。つまり、式は次のとおりです。
このようにして、n*l の結果範囲を [−1, 1] から [0, 1] までマッピングできます。つまり、モデルのバックライト付きサーフェスの場合、元のランバート照明モデルの内積の結果は同じ値、つまり値 0 にマッピングされますが、半ランバート モデルではバックライト付きサーフェスがマッピングされます。明暗の変化もあり、異なるドット積の結果が異なる値にマッピングされます。セミランバートには物理的な根拠はなく、視覚的な強化技術にすぎないことに注意してください。
以下は、セミランバート モデルの頂点ごとの実装です。
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
Shader "Chapter 6/Diffuse Half Lambert"
{
Properties
{
_diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
Tags {
"LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
fixed4 _diffuse;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert (a2v v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// 半兰伯特模型算法
fixed3 halfLambert = dot(worldNormal, worldLight) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _diffuse.rgb * halfLambert;
o.color = ambient + diffuse;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color, 1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
以下は、頂点ごとの拡散ライティング、ピクセルごとの拡散ライティング、およびハーフ ランバート ライティングの比較です。
6.5 Unity Shaderは鏡面反射照明モデルを実装します
頂点ごとのライティング
Shader "Chapter 6/Specular Vertex Level"
{
Properties
{
// 定义漫反射颜色,默认为白色
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
// 高光反射颜色
_Specular ("Specular", Color) = (1, 1, 1, 1)
// 用于控制高光区域大小
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
Pass
{
// 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
Tags {
"LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
#include "UnityLightingCommon.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert (a2v v)
{
v2f o;
// 坐标转换
o.vertex = 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));
/*高光反射*/
// 函数 reflect 可直接根据入射光角度与法线向量计算出反射方向
fixed3 reflectDir = normalize(reflect(-worldLight, worldNormal));
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
}
}
Fallback "Specular"
}
ピクセルごとの照明
Shader "Chapter 6/Specular Frag Level"
{
Properties
{
// 定义漫反射颜色,默认为白色
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
// 高光反射颜色
_Specular ("Specular", Color) = (1, 1, 1, 1)
// 用于控制高光区域大小
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
Pass
{
// 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
Tags {
"LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
#include "UnityLightingCommon.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 worldNormal : TEXCOORD0;
fixed3 worldPos : TEXCOORD1;
};
v2f vert (a2v v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
/*漫反射*/
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLight));
/*高光反射*/
// 函数 reflect 可直接根据入射光角度与法线向量计算出反射方向
fixed3 reflectDir = normalize(reflect(-worldLight, i.worldNormal));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
// 叠加环境光、漫反射、高光反射,作为返回结果
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
ブリン・フォン照明モデルのピクセルごとの実現
Shader "Chapter 6/Specular Blinn-Phong"
{
Properties
{
// 定义漫反射颜色,默认为白色
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
// 高光反射颜色
_Specular ("Specular", Color) = (1, 1, 1, 1)
// 用于控制高光区域大小
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
Pass
{
// 只有设置正确的光照模式,才能得到 Unity 的内置光照变量,比如 _LightColor0
Tags {
"LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 为了使用 Unity 内置的一些变量(比如 _LightColor0),必须包含相应 Unity 内置文件
#include "UnityLightingCommon.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 worldNormal : TEXCOORD0;
fixed3 worldPos : TEXCOORD1;
};
v2f vert (a2v v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
/*漫反射*/
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(i.worldNormal, worldLight));
/*高光反射*/
// 函数 reflect 可直接根据入射光角度与法线向量计算出反射方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Blinn-Phong 模型算法
fixed3 halfDir = normalize(worldLight + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(halfDir, i.worldNormal)), _Gloss);
// 叠加环境光、漫反射、高光反射,作为返回结果
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
以下は、上記 3 つの実装の比較です (左から右へ)。
- ピクセルごとにライティングを処理すると、頂点ごとよりも滑らかなハイライトが得られます。
- ブリンフォン照明モデルの鏡面部分はより大きく明るく見えます。実際のレンダリングでは、ほとんどの場合、Blinn-Phong 照明モデルを選択します。
6.6 Unity 組み込み関数の使用
UnityCG.cginc
詳細については関数のソースコードを参照してください。ここでは省略します。