【Unity SRP】鏡面反射の実装
はじめに
前回は不透明オブジェクトに拡散反射を実装しました。
今回は前回のモデルに鏡面反射を実装していきます。
目次
反射モデルとは
3Dグラフィックスにおいて、現実世界の光の挙動を表現するための計算式のことです。
モデリングされた物体のサーフェス(表面)の一点に陰影つけるために使用します。
鏡面反射光
物体の光沢やハイライトを表現することができます。
特徴
- 物体の色の影響を受けず、光源と同じ色のまま反射する
- 視点によって見える位置や大きさが変化する
- 正反射方向に近づけば近づくほど、光が強くなる
フォン反射モデル
鏡面反射の光の挙動を表現するためのモデルです。
「光源の正反射ベクトル」と「視点ベクトル」との内積で算出することができます。
フォン反射モデルの式
・・・正規化法線ベクトル
・・・光源ベクトル
・・・正反射ベクトル
・・・視点ベクトル
・・・鏡面反射係数(鏡面反射の反射率)
・・・鏡面反射成分(光源の明るさ)
・・・粗さ係数(物体の光沢度)
この視点方向と正反射方向が一致するときに明るさが最大になります。
光沢度を表す$a$の値が大きくなるにつれて、光沢の明るさも減少していきます。
実行サンプル
左:鏡面反射なし、右:フォン反射モデル
カメラ情報の設定
今回はシェーダー側でカメラの方向ベクトルを使用するため、C#側からシェーダーへカメラベクトルを渡す必要があります。
前回のコードにシェーダー側へ渡すための処理を追加していきます。
※コードは URP の実装を参考にしています。
/// <summary> /// 単一カメラごとのレンダリング /// </summary> /// <param name="context"></param> /// <param name="camera"></param> 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); // カメラの情報をシェーダー側に渡す SetPerCameraShaderVariables(cmd, camera); // ライト情報の設定 SetupLights(context, cullResults); // 不透明オブジェクト描画 DrawObjectsOpaque(context, camera, cullResults); // Skybox描画 context.DrawSkybox(camera); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); context.Submit(); } /// <summary> /// シェーダーに渡すカメラ情報の設定 /// </summary> /// <param name="camera"></param> void SetPerCameraShaderVariables(CommandBuffer cmd, Camera camera) { cmd.SetGlobalVector("_WorldSpaceCameraPos", camera.transform.position); }
SetPerCameraShaderVariables
でカメラデータを設定しています。
今回はカメラの位置情報のみシェーダー側に渡しています。
cmd.SetGlobalVector("_WorldSpaceCameraPos", camera.transform.position);
シェーダーの実装
シェーダーではC#側で設定した _WorldSpaceCameraPos
を使用して鏡面反射を実装しています。
前回のコードに鏡面反射の処理を追加します。
※コードは URP の実装を参考にしています。
Shader "Custom/Demo02_Specular_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 _SpecularBrightness("Specular Brightness", Float) = 2.0 // 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; float3 viewDir : TEXCOORD2; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); float4 _BaseMap_ST; float4 _BaseColor; float _Cutoff; float4x4 unity_ObjectToWorld; float4x4 unity_MatrixVP; float4 _MainLightPos; half4 _MainLightColor; float4 _WorldSpaceCameraPos; float _SpecularBrightness; 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); } float3 GetWorldSpaceViewDir(float3 positionWS) { return _WorldSpaceCameraPos.xyz - positionWS; } 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); o.viewDir = GetWorldSpaceViewDir(positionWS); 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 = saturate(dot(normal, lightDir)); // 鏡面反射(フォン反射モデル) half3 viewDir = normalize(i.viewDir); half3 r = 2.0 * normal * dot(normal, lightDir) - lightDir; half specular = pow(saturate(dot(r, viewDir)), _SpecularBrightness); color.rgb *= dotNL + specular; return half4(color, alpha); } ENDHLSL } } }
光源方向の正反射ベクトルと視点ベクトルの内積を算出したのち、光沢度を表す _SpecularBrightness
でべき乗することで、鏡面反射を表現しています。
そして、最終的に拡散反射光と合わせることで最終的なサーフェスの色としています。
// 鏡面反射(フォン反射モデル) half3 viewDir = normalize(i.viewDir); half3 r = 2.0 * normal * dot(normal, lightDir) - lightDir; half specular = pow(saturate(dot(r, viewDir)), _SpecularBrightness); color.rgb *= dotNL + specular;
ブリン-フォン反射モデル
フォン反射モデルは正反射ベクトルを計算するのに複数の内積や乗算を行うため、処理負荷を減らすために考え出されたモデルです。
「光源ベクトル」と「視点ベクトル」の中間ベクトルである「ハーフベクトル」と「法線ベクトル」との内積で算出することができます。
ブリン-フォン反射モデルの式
・・・正規化法線ベクトル
・・・光源ベクトル
・・・視点ベクトル
・・・ハーフベクトル
・・・粗さ係数(物体の光沢度)
正反射ベクトルを求めるフォン反射モデルとは違い、精度は落ちてしまいますが、その代わりに処理負荷が軽減されます。
以下は実装部分になります。
フォン反射モデルのコードがブリン-フォン反射モデルに置き換わっています。
// 鏡面反射(ブリン・フォン反射モデル) half3 viewDir = normalize(i.viewDir); half3 halfLV = normalize(lightDir + viewDir); half specular = pow(saturate(dot(normal, halfLV)), _SpecularBrightness); color.rgb *= dotNL + specular;
実行サンプル
左:鏡面反射無し 中央:ブリン-フォン反射モデル 右:フォン反射モデル
おわりに
今回は反射モデルの基本である、鏡面反射モデルをSRPで実装しました。
ここまでみていただいてありがとうございます。
【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で実装しました。
ここまでみていただいてありがとうございます。
なにか間違い等あればご指摘いただけると嬉しいです。
【Unity SRP】不透明オブジェクトを描画する
はじめに
Unityのレンダリングパイプラインについての勉強メモです。
今回はSRPで不透明オブジェクトを描画していきます。
ちなみにSRPとは Scriptable Render Pipeline の訳です。(長いのでSRPで統一します)
環境
Unity2020.3.15f2
com.unity.render-pipelines.core@10.5.1
目次
SRPの導入
SRPは以下の方法で導入できます。
- 新規でプロジェクトを作成する場合はテンプレートから「URP」または「HDRP」を選択
- 既にプロジェクトを作成している場合は「Windows/Package Manager」から「Core RP Library」をインポート
パイプラインの作成
不透明オブジェクトを描画するためのパイプラインを作成します。
独自パイプラインを作成するためには以下のクラスが必要になります。
- 「RenderPipelineAsset」を継承したクラス
- 「RenderPipeline」を継承したクラス
以下が実際のソースコードです。
ソースコード自体はUnityのURPの実装を参考にしています。
BasicRenderPipelineAsset.cs
using UnityEngine; using UnityEngine.Rendering; public class BasicRenderPipelineAsset : RenderPipelineAsset { #if UNITY_EDITOR [UnityEditor.MenuItem( "Project/Create/Render Pipeline Asset/Basic" )] public static void CreateAsset() { BasicRenderPipelineAsset instance = CreateInstance<BasicRenderPipelineAsset>(); UnityEditor.AssetDatabase.CreateAsset( instance, "Assets/Rendering/BasicRenderPipeline/BasicPipelineAsset.asset" ); } #endif protected override RenderPipeline CreatePipeline() { return new BasicRenderPipeline(); } }
BasicRenderPipeline.cs
using UnityEngine; using UnityEngine.Rendering; using Unity.Collections; public class BasicRenderPipeline : RenderPipeline { protected override void Render( ScriptableRenderContext context, Camera[] cameras ) { for( int i = 0; i < cameras.Length; i++ ) { RenderSingleCamera(context, cameras[i]); } } /// <summary> /// 単一カメラごとのレンダリング /// </summary> /// <param name="context"></param> /// <param name="camera"></param> 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); // 不透明オブジェクト描画 DrawObjectsOpaque(context, camera, cullResults); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); context.Submit(); } /// <summary> /// 不透明オブジェクト描画 /// </summary> /// <param name="context"></param> /// <param name="camera"></param> /// <param name="cullResults"></param> void DrawObjectsOpaque(ScriptableRenderContext context, Camera camera, CullingResults cullResults) { CommandBuffer cmd = CommandBufferPool.Get(); ProfilingSampler sampler = new ProfilingSampler(nameof(DrawObjectsOpaque)); using (new ProfilingScope(cmd, sampler)) { context.ExecuteCommandBuffer(cmd); cmd.Clear(); // ソート設定 SortingSettings sortingSettings = new SortingSettings(camera); sortingSettings.criteria = SortingCriteria.CommonOpaque; // 実行するシェーダーパスを設定 ShaderTagId shaderTagId = new ShaderTagId("OpaquePass"); // OpaquePass のみ描画するように設定 DrawingSettings drawingSettings = new DrawingSettings(shaderTagId, sortingSettings); // RenderQueue が Opaque の場合のみ描画 FilteringSettings filteringSettings = new FilteringSettings(RenderQueueRange.opaque); // 不透明オブジェクトを描画する context.DrawRenderers(cullResults, ref drawingSettings, ref filteringSettings); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } }
RenderPipelineを継承したクラスは「Render」関数を定義する必要があります。
この関数はレンダリングのエントリポイントとなり、
レンダリングのコンテキストとカメラの配列を引数として受け取ります。
以下がRender関数内の最小構成となり、ここにカメラごとの処理を追加していきます。
protected override void Render(ScriptableRenderContext context, Camera[] cameras) { foreach (var camera in cameras) { } }
CommandBufferの作成
レンダリングコンテキストにコマンドを発行するために、新しくコマンドバッファを作成します。
コマンドバッファとは、レンダリングコマンドリストを保持しており、レンダリングパイプラインの様々なタイミングでコマンドを実行できる機能です。
https://docs.unity3d.com/ja/2018.4/Manual/GraphicsCommandBuffers.html
カメラ設定
以下でカメラプロパティ(ビュー行列、プロジェクション行列、シェーダーのグローバル変数など)を設定しています。
context.SetupCameraProperties(camera);
カリング処理
カリングとは、画面に映さないポリゴンをピクセルパイプラインに渡す前に破棄するプロセスのことです。
以下の処理では context.Cull にカリングパラメータを渡すことで、カリング処理の結果を受け取っています。
ScriptableCullingParameters cullingParameters; if (camera.TryGetCullingParameters(false, out cullingParameters) == false) { continue; } CullingResults cullResults = context.Cull(ref cullingParameters);
フィルタリング処理
描画するオブジェクトのフィルタリングを以下の2つで設定しています。
- ShaderTagId
- RenderQueueRange
ShadertagId
シェーダー側の「LightMode」タグで指定することで、指定されたパスが実行されるようになります。
今回は「BasicPass」を指定しているので、シェーダー側で { "LightMode" = "BasicPass" }
を指定したパスのみが実行されることになります。
ShaderTagId shaderTagId = new ShaderTagId("BasicPass");
RenderQueueRange
オブジェクトが描画されるタイミングをキューの範囲で指定します。
今回は不透明オブジェクトを描画するため、RenderQueueを「opaque」に設定しています。
FilteringSettings filteringSettings = new FilteringSettings(RenderQueueRange.opaque);
最後にコンテキストの Submit 関数を呼び出してパイプラインの処理は完了です。
「Submit」関数は「ExecuteCommandBuffer」にてキューに登録された全てのレンダリングコマンドを実行しています。
context.Submit();
シェーダーの作成
パイプラインの作成が完了したので、次はシェーダー側で描画の中身を作成していきます。
コード自体はURPの Unlit.shader を参考にしています。
Basic.shader
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; }; struct Varyings { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); float4 _BaseMap_ST; float4 _BaseColor; float _Cutoff; float4x4 unity_ObjectToWorld; float4x4 unity_MatrixVP; 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)); } 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); 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; return half4(color, alpha); } ENDHLSL } } }
「LightMode」タグに「BasicPass」を指定することでパスが実行されるようになります。
Name "Basic" Tags { "LightMode" = "BasicPass" }
ここまでで全ての工程が完了しました。
一度Unityエディタに戻ってオブジェクトを作成します。
- 作成した「BasicRenderPipelineAsset」を「Edit/Project Settings/Graphics/Scriptable Render Pipeline Settings」に登録
- マテリアルを作成して、先ほど作成したシェーダーを指定
- オブジェクトを作成して、作成したマテリアルを適用
これで画面に不透明オブジェクトが描画されることが確認できました。
ついでに...
コンテキストの「DrawSkybox」を呼び出すことで不透明オブジェクトの描画後にSkyboxを描画することができます。
// 不透明オブジェクト描画 DrawObjectsOpaque(context, camera, cullResults); // Skybox描画 context.DrawSkybox(camera);
なぜSkyboxを不透明オブジェクトの後に描画するのか
Skyboxは画面全体に描画されるため、不透明オブジェクトより先に描画してしまうと、後から不透明オブジェクトを描画するときにSkyboxで描画した部分を塗りつぶしてしまいます。
そのため、先に不透明オブジェクトを描画しておくことでSkyboxを描画するときに不透明オブジェクトで塗られた部分を無視するため、処理負荷が軽くなるためらしいです。
描画順番としては、「不透明オブジェクト > Skybox > 半透明オブジェクト」という順番がUnityの標準となっています。
まとめ
今回は「SRPで不透明オブジェクトの描画」を行いました。
ここまで見てくださってありがとうございます。
内容に間違い等ありましたら遠慮なくご指摘いただけますと幸いです。
Universal Render Pipeline Assetまとめ
Universal Render Pipeline Asset(以下URPアセット)の作成~各設定項目を簡単にまとめていきます。
詳しくは公式のUniversal Render Pipeline Assetにまとめられているので、こちらも参考にしてください。
目次
1.URPアセットとは
- URPの描画機能と品質の設定をおこなえる
- SRPのRenderPipelineAssetを継承したスクリプト可能なオブジェクト
2.URPアセットの作成手順
Projectウィンドウの「Create」->「Rendering」->「Universal Render Pipeline」->「Pipeline Asset (Forward Renderer)」を選択することで、アセットを作成できます。
「UniversalRenderPipelineAsset」「UniversalRenderPipelineAsset_Renderer」という名前のアセットが作成されます。
3.作成されるアセットについて
UniversalRenderPipelineAsset
- Universal Render Pipelineの描画機能と品質の設定をおこなえます
- SRPのRenderPipelineAssetを継承したスクリプト可能なオブジェクトです
- 描画設定の項目に作成したアセットを割り当てることで、組み込みのレンダーパイプラインからURPに切り替わります
UniversalRenderPipelineAsset_Renderer
- UniversalRenderPipelineAssetに登録して使用します
- ScriptableRendererDataを継承したForwardRendererDataクラスであり、URPのフォワードレンダリングパスを実行するためのアセットです
- 作成されるレンダラーはデフォルトのForwardRendererです
URPアセットの設定項目
General
Renderer List
フィールド | m_RendererDataList : ScriptableRendererData[] |
デフォルト | ForwardRenderer |
説明 |
|
Depth Texture
フィールド | m_RequireDepthTexture : bool |
デフォルト | false |
説明 |
|
Opaque Texture
フィールド | m_RequireOpaqueTexture : bool |
デフォルト | false |
説明 |
Opaque Downsampling
フィールド | m_OpaqueDownsampling : DownSampling(enum) |
デフォルト | _2xBilinear |
説明 |
|
Downsampling
名称 | 説明 | 方式 |
---|---|---|
None | ダウンサンプリングを行わず、カメラと同じ解像度でコピーされたテクスチャが作成されます | |
_2xBilinear | 1/2の解像度でコピーされたテクスチャが作成されます | バイリニアフィルタリング |
_4xBox | 1/4の解像度でコピーされたテクスチャが作成されます | ボックスフィルタリング |
_4xBilinear | 1/4の解像度でコピーされたテクスチャが作成されます | バイリニアフィルタリング |
Terrain Holes
フィールド | m_SupportsTerrainHoles : bool |
デフォルト | true |
説明 |
|
Quality
HDR
フィールド | m_SupportsHDR : bool |
デフォルト | false |
説明 |
Anti Aliasing(MSAA)
フィールド | m_MSAA : MsaaQuality(enum) |
デフォルト | Disabled |
説明 |
|
Render Scale
フィールド | m_RenderScale : float |
デフォルト | 1.0 |
説明 |
Lighting
Main Light
フィールド | m_MainLightRenderingMode : LightRenderingMode(enum) |
デフォルト | PerPixel |
説明 |
|
Cast Shadows
フィールド | m_MainLightShadowsSupported : bool |
デフォルト | true |
説明 |
|
Shadow Resolution
フィールド | m_MainLightShadowmapResolution : ShadowResolution(enum) |
デフォルト | _2048 |
説明 |
|
Additional Lights
フィールド | m_AdditionalLightsRenderingMode : LightRenderingMode(enum) |
デフォルト | PerPixel |
説明 |
|
LightRenderingMode
名称 | 説明 |
---|---|
Double | 追加ライトの計算を無効にします |
Per Vertex | 頂点ごとに計算をおこないます |
Per Pixel | ピクセルごとに計算をおこないます |
Per Object Limit
フィールド | m_AdditionalLightsPerObjectLimit : int |
デフォルト | 4 |
説明 |
|
Cast Shadows
フィールド | m_AdditionalLightShadowsSupported : bool |
デフォルト | false |
説明 |
|
Shadow Resolution
フィールド | m_AdditionalLightsShadowmapResolution : ShadowResolution(enum) |
デフォルト | false |
説明 |
|
Shadows
Distance
フィールド | m_ShadowDistance : float |
デフォルト | 50 |
説明 |
|
Cascades
フィールド | m_ShadowCascades : ShadowCascadesOption(enum) |
デフォルト | NoCascades |
説明 |
|
ShadowCascadesOption
名称 | 説明 |
---|---|
NoCascades | 一つのシャドウマップを使用します(距離に応じてシャドウマップが切り替わることはありません) |
Two Cascades | 2つのシャドウマップを使用します |
Four Cascades | 4つのシャドウマップを使用します |
Depth Bias
フィールド | m_ShadowDepthBias : float |
デフォルト | 1.0 |
説明 |
|
Normal Bias
フィールド | m_ShadowNormalBia : float |
デフォルト | 1.0 |
説明 |
|
Soft Shadows
フィールド | m_SoftShadowsSupported : bool |
デフォルト | false |
説明 |
|
Post-processing
Grading Mode
フィールド | m_ColorGradingMode : ColorGradingMode(enum) |
デフォルト | LowDynamicRange |
説明 |
ColorGradingMode
名称 | 説明 |
---|---|
LowDynamicRange |
|
HighDynamicRange |
|
LUT Size
フィールド | m_ColorGradingLutSize : int |
デフォルト | 32 |
説明 |
|
Advanced
SRP Batcher
フィールド | m_UseSRPBatcher : bool |
デフォルト | true |
説明 |
|
Dynamic Batching
フィールド | m_SupportsDynamicBatching : bool |
デフォルト | false |
説明 |
|
Mixed Lighting
フィールド | m_MixedLightingSupported : bool |
デフォルト | true |
説明 |
|
Debug Level
フィールド | m_DebugLevel : PipelineDebugLevel(enum) |
デフォルト | Disabled |
説明 |
|
PipelineDebugLevel
名称 | 説明 |
---|---|
Disabled | ログの記録を無効にします |
Profiling |
|
Shader Variant Log Level
フィールド | m_ShaderVariantLogLevel : ShaderVariantLogLevel(enum) |
デフォルト | Disabled |
説明 |
|
ShaderVariantLogLevel
名称 | 説明 |
---|---|
Disabled | ログの記録を無効にします |
OnlyUniversalRPShaders | すべてのURPシェーダの情報をログに記録します |
AllShaders | ビルド内のすべてのシェーダの情報をログに記録します |
まとめ
URPアセットの各設定項目を簡単にまとめました。
URPの設定は従来のBuilt-in Render Pipelineの時とは違い、設定項目がアセットにまとめられているのでかなり扱いやすくなっていました。
これからもURPについて調べたことや気づいた点などあればまとめていくのでよろしくお願いします。
また本記事の内容にどこか間違い等あればご指摘いただけると嬉しいです。
参考記事
- 【Unity Manual】Universal Render Pipeline Asset
- 【凹みTips】Unity の Universal Render Pipeline のレンダリング周りについて勉強してみた
- 【染井吉野ゲームズ】Unityシェーダープログラミングの教科書4 SRP[1]URP/Litシェーダー解説編
URPを覚えよう ~特徴からプロジェクト作成まで~
この記事はURPを勉強し始めた筆者のメモ書きみたいなものです
はじめに
最近始動したプロジェクトでURPを使用することになりました。
実は前々から興味はあれど手をつけずにいたので、この機会にちゃんと勉強します。
1.Universal Render Pipline ( URP ) って何?
Universal Render Pipeline、通称URPは(長いのでURPで統一します)、Unity従来のレンダリングパイプラインである「Built-in」に変わる新しい機能として提供されているレンダリングパイプラインです。
URPはオープンソースとなっており、現在もGitHubにて開発が進められています。
- UnityデフォルトのパイプラインであるBuilt-in Render Piplineの後継として採用されることが決まっている新しいレンダーパイプラインの事
- 今まではLighht Weight Render Pipline、通称LWRPとして配布されていたが、unity2018以降、URPに改名
2.レンダリングパイプラインとは
レンダリングパイプラインとは、ゲーム空間上に配置したオブジェクトが最終的に画面に表示されるまでに行われる作業の工程の事です。
この工程で行われる各処理の事をPASSといい、パイプラインに渡されてきたオブジェクトは各PASSを通り、最終的に画面上に絵として表示されます。
PASSによって行われる処理の例
- カリング処理
- 陰影処理
- ラスタライズ
- ポストプロセス など
以下は、レンダリングパイプラインの作業工程のレ例図です。
【CEDEC2020 Unityの次の標準レンダリングパイプライン Universal Render Pipeline は何がどう変わるのか】より引用
そして、このパイプラインの各PASSをカスタマイズできるようになったのが、次に説明するScriptable Render Piplineになります
3.Scriptable Render Pipline ( SRP ) とURP
Scriptable Render Pipeline、通称SRPは(長いのでSRPで統一します)、はUnity5から試験的に導入されることとなり、Unity2018から正式版として組み込まれることになりました。
前項で触れた、レンダリングパイプラインのPASSを自由に組み替えられるパッケージとなります。
ただ、SRP単体では何も表示されない状態なので、自分で全て組み立てる必要があります。
Unity公式マニュアルの説明によると、完全にカスタマイズ可能とのことです。
SRP自体は「Package Manager」または開発中のGitHubからインポートすることで使用可能となります。
リスト一番上の「Core RP Library」がSRPのパッケージです。(ちなみに一つ下が本記事で説明している「URP」パッケージ)
具体的な導入や使用方法は、また別の記事でやろうと思います。
興味がある方は勉強してみてもいいかもしれません。
SRPは自由度の高いパッケージですが、正直パイプラインを1から全て作成しようと思うと、かなりの時間と手間が必要になってきます。
そこでUnityさんが、最初から誰でも使えるように組み立ててくれた2つのパイプラインがあります。
その一つがこの記事で説明していくUniversal Render Pipeline ( URP ) です。
4.URPの特徴
以下にURPの特徴やURPでできることをまとめていきます
特徴
- URPはモバイルからハイエンドなコンソールゲームまで、幅広い表現を可能としている
- 「シングルパス・フォワードレンダリング」というレンダリング方式を採用しており、ドローコール数を抑える事が可能
- カメラごとに「2D」「3D」のレンダラーを分けて使用することが可能(それぞれ実行されるパスが異なる)
URPで使用できるシェーダ
URPではビルトインに変わる標準シェーダとして以下が提供用意されています
- Lit : 物理ベースのシェーダ
- Simple Lit : 非物理ベースのシェーダ
- Baked Lit : 動的なライティングを行わないシェーダ
ビルトインから用意されているシェーダでは、以下が使用可能となっています
- Unlit : ライトの影響を受けないシェーダ
URPのデメリット
- ビルトインの標準シェーダであった「Surface」が使用できないため、アセットストアなどからインポートしてきたアセットはURPに対応させる必要がある
- 「Shadow Mask」「Point Light Shadow」などの影の表現が使えない
- 「SSR」「Auto Exposure」「SSAO」などの一部のポストプロセスが使えないため、自作する必要がある 下2つについては、近いうちに実装されていく予定らしいのです。
どうしても使いたい場合、今はまだ自作する必要があります。
URPの導入方法
新規プロジェクトを作成する場合
新規でプロジェクトを作成する場合は、作成画面の「Universal Render Pipline」を選択します。
既存プロジェクトに導入する場合
既存のプロジェクトに導入する場合は、PackageManagerを使用します。
パッケージのインポート後、Projectウィンドウから「Create」->「Rendering」->「Universal Render Pipeline」->「Pipline Asset(Forward Renderer)」を選択してアセットを作成します。
この時点で、Pipline Assetとそのアセットで使用するレンダラー用アセットの2つが作成されます。
作成したPipeline Assetを、「Project Settings」->「Graphics」の「Scriptable Render Pipleline Settings」項目に設定します。
シェーダとマテリアルについて
【2.URPで使用できるシェーダ】でも説明しましたが、URPではBuilt-inで使用されていた「Surface Shader」が使えません。
なのでマテリアルの設定にはURP用のシェーダを設定する必要があります。(Standardを使用するとUnityユーザーさんおなじみピンクモデルが表示されます)
URPは使うべきか
近いうちにBuilt-inから完全に置き換わる事を考えると、これから新規で動くプロジェクトはURPを使用したほうがよさそう
(公式によると、Unity2021を目標にBuilt-inの上位互換となる予定らしい)
適している開発
- これから新しく動くプロジェクト
- モバイル・コンソール・PCまで幅広く展開したい人
- フォトグラフィックな表現を必要としない開発
- アニメ調など、独自の表現を行いたい人
- 「Shader Graph」「VFX Graph」といったアーティスト向けツールを使用したい人
5.まとめ
URPとは
- SRPをベースとした拡張可能なレンダリングパイプライン
- 高速で、幅広い表現が可能
URPで何が変わるのか
- パフォーマンスの向上
- レンダラーを使った、少し変わった表現が可能になる
URPを拡張するには
- アセットストアで探す
- 頑張って自作
以上となります。
今回はURPとは何なのか~URPの導入までを書きました。
間違っている箇所等があれば、コメント欄にて指摘していただけると嬉しいです。
今後も調べた事や勉強したことなどを記事にしていくので、よろしくお願いします。
参考サイト
【Unity】Unityの次の標準レンダリングパイプライン Universal Render Pipeline は何がどう変わるのか
https://learning.unity3d.jp/4966/
【Unity】軽量レンダーパイプラインからユニバーサルレンダーパイプラインへ
https://blogs.unity3d.com/jp/2019/09/20/how-the-lightweight-render-pipeline-is-evolving/
【Unity】スクリプタブルレンダーパイプラインの概要
https://blogs.unity3d.com/jp/2018/01/31/srp-overview/
【Unity】Universal Render Pipeline overview
https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@10.3/manual/index.html
【npaka 様】Unityのレンダーパイプライン
https://note.com/npaka/n/n5c7c11512999
参考書籍
【染井吉野ゲームズ 様】Unityシェーダープログラミングの教科書4 SRP[1]UniversalRP/Litシェーダー解説編