【UnityShader】ライティングの基礎シェーダー #115
はじめに
今回は複数ライトに対応した基本的なシェーダーについて勉強します。
シェーダー
Shader "Day3/Practice1" { Properties { _MainTex ("Texture", 2D) = "white" {} _SpecularPow ("Speclar Pow", float) = 5 } SubShader { Tags { "RenderType"="Opaque"} LOD 100 CGINCLUDE sampler2D _MainTex; float4 _MainTex_ST; // ライトの色を取得する half4 _LightColor0; #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; half3 normal : NORMAL; }; ENDCG Pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM struct v2f { float2 uv : TEXCOORD0; half3 normal : NORMAL; float3 viewDir : TEXCOORD1; float3 lightDir : TEXCOORD2; float4 vertex : SV_POSITION; }; half _SpecularPow; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); float4 worldPos = mul(unity_ObjectToWorld, v.vertex); o.viewDir = normalize(_WorldSpaceCameraPos - worldPos.xyz); o.uv = TRANSFORM_TEX(v.uv, _MainTex); float isDirectional = step(1, _WorldSpaceLightPos0.w); o.lightDir = normalize(_WorldSpaceLightPos0.xyz - (worldPos.xyz * isDirectional)); o.normal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 texCol = tex2D(_MainTex, i.uv); // 拡散反射 float3 diffuse = saturate(dot(i.normal, i.lightDir)) * _LightColor0; // 鏡面反射 float3 reflectVec = reflect(-i.lightDir, i.normal); float specular = pow(saturate(dot(reflectVec, i.viewDir)), _SpecularPow); // 環境光 float3 ambient = ShadeSH9(float4(i.normal, 1)); return fixed4(ambient, 1); fixed4 col = fixed4(texCol.rgb * (ambient + diffuse) + specular, texCol.a); return col; } ENDCG } Pass { Tags { "LightMode"="ForwardAdd" } Blend One One CGPROGRAM struct v2f { float2 uv : TEXCOORD0; half3 normal : NORMAL; float3 lightDir : TEXCOORD1; float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); float4 worldPos = mul(unity_ObjectToWorld, v.vertex); float isDirectional = step(1, _WorldSpaceLightPos0.w); o.lightDir = normalize(_WorldSpaceLightPos0.xyz - (worldPos.xyz * isDirectional)); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.normal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 texCol = tex2D(_MainTex, i.uv); float3 lightCol = saturate(dot(i.normal, i.lightDir)) * _LightColor0; return fixed4(texCol.rgb * lightCol, 1); } ENDCG } } }
拡散反射光
非金属表面付近で起きる光の反射のうち、界面で発生する鏡面反射を除いた成分のことである
拡散反射 - Wikipedia:より引用
つまり、光があたっている部分を明るく、あたっていない部分は暗くなるものになります。
以下の画像のdiffuse reflection
がそれにあたります。
拡散反射 - Wikipedia:より引用
求め方
光が一番当たる部分は、光の向きとオブジェクトの法線が逆ベクトルに
当たらない部分は垂直、もしくはそれ以上と考えれば求められそうです。
以下の画像のようにイメージして頂ければわかりやすいかと思います。
つまり、光の向きと法線の内積を求めることで光の強さを計算することができます。
それをコードに起こしたものが以下になります。
// vertex float isDirectional = step(1, _WorldSpaceLightPos0.w); o.lightDir = normalize(_WorldSpaceLightPos0.xyz - (worldPos.xyz * isDirectional)); // fragment float3 diffuse = saturate(dot(i.normal, i.lightDir)) * _LightColor0;
_WorldSpaceLightPos0
ディレクショナルライトとその他のライトで取得できるものが異なります。
ライト | 値 |
---|---|
ディレクショナルライト | float4(ワールド空間の向き, 0) |
その他のライト | float4(ワールド空間の位置, 1) |
その他のライトの場合は、位置が取得できるので向きベクトルへと変換しています。
鏡面反射光
二物質の界面において発生する反射である。鏡面反射では反射の法則が成り立ち、入射角と反射角を等しくする。
鏡面反射 - Wikipedia:より引用
つまり、どの程度光を反射させるかを示すものになります。
求め方
まずは、光が入ってくるベクトルの反射ベクトルを求めます。
そのベクトルと視線のベクトルの内積を求めることで、反射の強さを求めることができそうです。
以下の画像のようにイメージして頂ければわかりやすいかと思います。
コードに起こしたものが以下になります。
// vertex o.viewDir = normalize(_WorldSpaceCameraPos - worldPos.xyz); // fragment float3 reflectVec = reflect(-i.lightDir, i.normal); float specular = pow(saturate(dot(reflectVec, i.viewDir)), _SpecularPow);
reflect
はhlslに定義済の反射ベクトルを返すものになります。
また、計算結果にpow
をしているのは物体によって反射率が異なるからになります。
環境光
物体間や物体内部における光の反射が生み出す間接光を簡易的に表現したい場合に用いる。
とのこと。
求め方
0.3f辺りを加算する手法もありますが、今回はShadeSH9
を使用します。
コードに起こしたものが以下になります。
float3 ambient = ShadeSH9(float4(i.normal, 1));
ShadeSH9
引数に法線を入れることで、環境光を取得するものになります。
wは1.0とコメントに記載してあります。
定義は以下となります。
// normal should be normalized, w=1.0 // output in active color space half3 ShadeSH9 (half4 normal) { // Linear + constant polynomial terms half3 res = SHEvalLinearL0L1 (normal); // Quadratic polynomials res += SHEvalLinearL2 (normal); # ifdef UNITY_COLORSPACE_GAMMA res = LinearToGammaSpace (res); # endif return res; }
深堀り
ShadeSH9の中身を深堀りしてみましたが、結論から述べると理解できませんでした。
// normal should be normalized, w=1.0 half3 SHEvalLinearL0L1 (half4 normal) { half3 x; // Linear (L1) + constant (L0) polynomial terms x.r = dot(unity_SHAr,normal); x.g = dot(unity_SHAg,normal); x.b = dot(unity_SHAb,normal); return x; } // normal should be normalized, w=1.0 half3 SHEvalLinearL2 (half4 normal) { half3 x1, x2; // 4 of the quadratic (L2) polynomials half4 vB = normal.xyzz * normal.yzzx; x1.r = dot(unity_SHBr,vB); x1.g = dot(unity_SHBg,vB); x1.b = dot(unity_SHBb,vB); // Final (5th) quadratic (L2) polynomial half vC = normal.x*normal.x - normal.y*normal.y; x2 = unity_SHC.rgb * vC; return x1 + x2; }
unity_SH
系は、球面調和関数によって求められたGIのようで、それと法線情報をごにょごにょして環境光を求めているっぽいです。
使わなかったもの
UNITY_LIGHTMODEL_AMBIENT
は公式にLegacy variableとあるので、使用しませんでした。
UNITY_LIGHTMODEL_AMBIENT
をそのまま返したものは以下になります。
各光の描画結果
拡散反射光
鏡面反射光
環境光
上記3つを合わせたもの
他のライトの実装
基本的には、拡散反射光と同様に法線とライトの向きの内積によって求めます。
float3 lightCol = saturate(dot(i.normal, i.lightDir)) * _LightColor0;
また、Unityにライトのパスを追加していることを伝えるためにForwardAdd
をTagsに追加します。
今回は、色を追加するのでBlendはOne One
にしています。
Tags { "LightMode"="ForwardAdd" } Blend One One
他のライトを追加した描画結果
ポイントライトを追加しました。