Reflection Probeを適用するシェーダの書き方です。
ここではReflection Probeの仕組みは割愛し、シェーダ内での取り扱いについてのみ書きます。
Reflection Probeについては別の記事で書きます(17.07.10 書きました -> 関連からリンク)。
キューブマップを取得する
まずはReflection Probeによってレンダリングされたキューブマップを取得してみます。
Unityではunity_SpecCube0というキューブマップが定義されており、次のように使われることが決まっています。
- Reflection Probeの影響範囲内である場合にはReflection Probeによるキューブマップが入ってくる
- それ以外の場合にはLighting WindowのEnvironment Reflectionで設定したキューブマップが入ってくる
上記を踏まえてシェーダを書いてみます。
Shader "ReflectionProbe" { SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float4 normal: NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float3 worldPos : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 pos : TEXCOORD2; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { half3 worldViewDir = normalize(_WorldSpaceCameraPos - i.worldPos); half3 reflDir = reflect(-worldViewDir, i.worldNormal); // unity_SpecCube0はUnityで定義されているキューブマップ half4 refColor = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflDir, 0); // Reflection ProbeがHDR設定だった時に必要な処理 refColor.rgb = DecodeHDR(refColor, unity_SpecCube0_HDR); return refColor; } ENDCG } } }
unity_SpecCube0をサンプリングし、HDRのデコードをしているだけです。
結果は次のようになります。
(Gameビューだと見づらかったのでSceneビューで)
赤と緑のCubeがSphereに正常に映り込んでいることが確認できます。
Box Projection
次にBox Projectionを適用します。
Shader "ReflectionProbe" { SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float4 normal: NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float3 worldPos : TEXCOORD0; float3 worldNormal : TEXCOORD1; }; // Box Projectionを考慮した反射ベクトルを取得 float3 boxProjection(float3 normalizedDir, float3 worldPosition, float4 probePosition, float3 boxMin, float3 boxMax) { // GraphicsSettingsのReflection Probes Box Projectionが有効な場合のみtrue #if UNITY_SPECCUBE_BOX_PROJECTION // Box Projectionが有効な場合はprobePosition.w > 0となる if (probePosition.w > 0) { float3 magnitudes = ((normalizedDir > 0 ? boxMax : boxMin) - worldPosition) / normalizedDir; float magnitude = min(min(magnitudes.x, magnitudes.y), magnitudes.z); normalizedDir = normalizedDir * magnitude + (worldPosition - probePosition); } #endif return normalizedDir; } v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { half3 worldViewDir = normalize(_WorldSpaceCameraPos - i.worldPos); half3 reflDir = reflect(-worldViewDir, i.worldNormal); // 反射方向へのベクトルをBox Projectionにより変換する reflDir = boxProjection(reflDir, i.worldPos, unity_SpecCube0_ProbePosition, unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax); half4 refColor = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflDir, 0); refColor.rgb = DecodeHDR(refColor, unity_SpecCube0_HDR); return refColor; } ENDCG } } }
Box Projectionの仕組みの説明はここでは割愛します(別記事書くかも)。
さてBox Projectionを有効化するには下記2つの設定が必要です。
- Graphics SettingsのReflection Probes Box Projectionにチェックを入れる
- Reflection ProbeのBox Projectionにチェックを入れる
コメントにも書いてありますが、上記1.2.が有効化されているかどうかは次のように取得するというUnityの決まりがあります。
- チェックが入っていたらUNITY_SPECCUBE_BOX_PROJECTIONがtrueとなる
- チェックが入っていたらunity_SpecCube0_ProbePosition.wが1となる
上記のソースコードはこれらを用いてBox Projectionが無効な場合と処理を分岐しています。
結果は次のようになります。
Box Projectionが適用され、近くにあるオブジェクトもちゃんと映り込みました。
ブレンドを考慮する
最後にReflection Probe間のブレンドを適用してみます。
Shader "ReflectionProbe" { SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float4 normal: NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float3 worldPos : TEXCOORD0; float3 worldNormal : TEXCOORD1; }; // Box Projectionを考慮した反射ベクトルを取得 float3 boxProjection(float3 normalizedDir, float3 worldPosition, float4 probePosition, float3 boxMin, float3 boxMax) { #if UNITY_SPECCUBE_BOX_PROJECTION if (probePosition.w > 0) { float3 magnitudes = ((normalizedDir > 0 ? boxMax : boxMin) - worldPosition) / normalizedDir; float magnitude = min(min(magnitudes.x, magnitudes.y), magnitudes.z); normalizedDir = normalizedDir* magnitude + (worldPosition - probePosition); } #endif return normalizedDir; } v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { half3 worldViewDir = normalize(_WorldSpaceCameraPos - i.worldPos); half3 reflDir = reflect(-worldViewDir, i.worldNormal); half3 reflDir0 = boxProjection(reflDir, i.worldPos, unity_SpecCube0_ProbePosition, unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax); half3 reflDir1 = boxProjection(reflDir, i.worldPos, unity_SpecCube1_ProbePosition, unity_SpecCube1_BoxMin, unity_SpecCube1_BoxMax); half4 refColor0 = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflDir0, 0); refColor0.rgb = DecodeHDR(refColor0, unity_SpecCube0_HDR); // SpecCube1のサンプラはSpecCube0のものを使う half4 refColor1 = UNITY_SAMPLE_TEXCUBE_SAMPLER_LOD(unity_SpecCube1, unity_SpecCube0, reflDir1, 0); refColor1.rgb = DecodeHDR(refColor1, unity_SpecCube1_HDR); // unity_SpecCube0_BoxMin.w にブレンド率が入ってくる return lerp(refColor1, refColor0, unity_SpecCube0_BoxMin.w); } ENDCG } } }
2個目のキューブマップはunity_SpecCube1に入ってきます。
ただしこれにサンプラは含まれないため、
UNITY_SAMPLE_TEXCUBE_LODの代わりにNITY_SAMPLE_TEXCUBE_SAMPLER_LODを使用して、
サンプラとしてunity_SpecCube0を渡してサンプリングします。
またブレンド率はunity_SpecCube0_BoxMin.wに入ってくるという決まりがあるため、
これを使って二つのキューブマップの色を補間します。
結果は次のようになります。
正常にブレンドされました。