【Unity SRP】ランバート拡散反射モデルの実装
はじめに
前回実装した不透明オブジェクトに陰影をつけていきます。
s-dango.hatenablog.jp
最終的な表示画面
頻出する語句
ベクトル・・・・・・・向きと大きさを持つ量(速度や力など)
ベクトルの内積・・・・2つのベクトルにの計算によって得られる値
ベクトルの正規化・・・ベクトルの方向を維持しつつ大きさを1にすること
法線・・・・・・・・・3Dモデルにおいて、表面の一点に対して垂直なベクトル
陰影・・・・・・・・・光が当たらずにできる暗い部分(物体の裏側など、光が十分に届かずにできる陰と、他の物体に遮られてできる影がある)
目次
反射モデルとは
3Dグラフィックスにおいて、現実世界の光の挙動を表現するための計算式のことです。
モデリングされた物体のサーフェス(表面)の一点に陰影つけるために使用します。
拡散反射光
物体の陰を表現することができます。
特徴
- 光源から向きが離れていくほど暗くなっていく
- 視点によって色や大きさは変化しない
ランバート反射モデル
拡散反射光の挙動を表現するための計算モデルの一つです。
サーフェスの「法線ベクトル」と「光源方向ベクトル」との内積で算出することができます。
ランバート反射モデルの式
・・・正規化法線ベクトル
・・・光源ベクトル
・・・拡散反射光の色
・・・入射光の輝度
最終的に正規化法線ベクトルと光源ベクトルのなす角θが算出され、この値がサーフェスの輝度となります。
が 0.0 になればなるほど、サーフェスの輝度が 1.0 に近づいていきます。
実行サンプル
左がライティング無し、右がランバート反射を適用したモデルです。
ライトの実装
ランバート拡散反射を実装するためには、まずライトの方向ベクトルを取得する必要があります。
以下の実装では、Hierarchy内で一番明るいディレクショナルライトをメインライトとして、方向ベクトルをシェーダー側に渡しています。
前回のC#コードに追記する形になります。
※コードはURPの実装を参考にしています。
void RenderSingleCamera(ScriptableRenderContext context, Camera camera) { ScriptableCullingParameters cullParams; if (camera.TryGetCullingParameters(out cullParams) == false) { return; } CommandBuffer cmd = CommandBufferPool.Get(); ProfilingSampler sampler = new ProfilingSampler($"{nameof(BasicRenderPipeline)}.{nameof(RenderSingleCamera)}.{camera.name}"); using (new ProfilingScope(cmd, sampler)) { context.ExecuteCommandBuffer(cmd); cmd.Clear(); // カメラプロパティ設定 context.SetupCameraProperties(camera, false); // カリング設定 CullingResults cullResults = context.Cull(ref cullParams); // ライト情報の設定 <- ここでライトのセットアップを行っています SetupLights(context, cullResults); // 不透明オブジェクト描画 DrawObjectsOpaque(context, camera, cullResults); // Skybox描画 context.DrawSkybox(camera); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); context.Submit(); }
void SetupLights(ScriptableRenderContext context, CullingResults cullResults) { CommandBuffer cmd = CommandBufferPool.Get(); ProfilingSampler sampler = new ProfilingSampler("Setup Light Contents"); using (new ProfilingScope(cmd, sampler)) { NativeArray<VisibleLight> visibleLights = cullResults.visibleLights; // メインライト検索 int lightIndex = -1; float brightestLightIntensity = 0f; for (int i = 0; i < visibleLights.Length; i++) { VisibleLight visibleLight = visibleLights[i]; Light light = visibleLight.light; if (light == null) { break; } if (visibleLight.lightType == LightType.Directional) { // 最も明るいディレクショナルライトを使用 if (light.intensity > brightestLightIntensity) { brightestLightIntensity = light.intensity; lightIndex = i; } } } // シェーダーに渡すライトデータの作成 if (lightIndex < 0) { return; } VisibleLight mainVisibleLight = visibleLights[lightIndex]; Vector4 dir = -mainVisibleLight.localToWorldMatrix.GetColumn(2); Vector4 lightPos = new Vector4(dir.x, dir.y, dir.z, 0); cmd.SetGlobalVector("_MainLightPos", lightPos); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); }
シェーダーの実装
シェーダー側では、C#コード側で登録した _MainLightsPos
プロパティを使用して、ランバート拡散反射を実装しています。
前回のシェーダーコードに追記する形になります。
※コードはURPの実装を参考にしています。
Shader "Custom/Basic" { Properties { [MainTexture] _BaseMap("Base Map", 2D) = "white" {} [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1) _Cutoff("Alpha Cutout", Range(0.0, 1.0)) = 0.5 // ObsoleteProperties [HideInInspector] _MainTex("Texture", 2D) = "white" {} [HideInInspector] _Color("Color", Color) = (1, 1, 1, 1) } SubShader { Tags { "RenderType" = "Opaque" } LOD 100 Pass { Name "BasicPass" Tags { "LightMode" = "BasicPass" } HLSLPROGRAM #pragma exclude_renderers gles gles3 glcore #pragma target 4.5 #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct Varyings { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float3 normal : TEXCOORD1; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); float4 _BaseMap_ST; float4 _BaseColor; float _Cutoff; float4x4 unity_ObjectToWorld; float4x4 unity_MatrixVP; float4 _MainLightPos; // C#側で登録したプロパティを使用しています float3 TransformObjectToWorld(float3 positionOS) { return mul(unity_ObjectToWorld, float4(positionOS, 1.0)).xyz; } float4 TransformWorldToHClip(float3 positionWS) { return mul(unity_MatrixVP, float4(positionWS, 1.0)); } float3 TransformObjectToWorldNormal(float3 normalOS) { return mul(normalOS, (float3x3)unity_ObjectToWorld); } Varyings vert(Attributes i) { Varyings o = (Varyings)0; float3 positionWS = TransformObjectToWorld(i.positionOS.xyz); float4 positionCS = TransformWorldToHClip(positionWS); o.vertex = positionCS; o.uv = TRANSFORM_TEX(i.uv, _BaseMap); o.normal = TransformObjectToWorldNormal(i.normal); return o; } half4 frag(Varyings i) : SV_Target { float2 uv = i.uv; float4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv); float3 color = texColor.rgb * _BaseColor.rgb; half alpha = texColor.a * _BaseColor.a; // 正規化法線ベクトル・ライト方向ベクトルを使用して、ランバート拡散反射を実装しています half3 lightDir = normalize(_MainLightPos.xyz); half3 normal = normalize(i.normal); half dotNL = max(0, dot(normal, lightDir)); color.rgb *= dotNL; return half4(color, alpha); } ENDHLSL } } }
正規化法線ベクトル・ライト方向ベクトルを内積して、その算出結果(0.0~1.0)をサーフェスの輝度としています。
half3 lightDir = normalize(_MainLightPos.xyz); half3 normal = normalize(i.normal); half dotNL = max(0, dot(normal, lightDir)); color.rgb *= dotNL;
最終結果
シェーダーを設定したマテリアルをオブジェクトに適用すると、以下のような表示になると思います。
おわりに
今回は反射モデルの基本である、ランバート拡散反射モデルをSRPで実装しました。
ここまでみていただいてありがとうございます。
なにか間違い等あればご指摘いただけると嬉しいです。