カスタム製の空の作成
HD レンダーパイプライン (HDRP) には、空とライティングパイプラインとの一貫性を維持しながら、独自のプロパティーとシェーダーを持つカスタム製の空を開発する、スカイシステムがあります。
自分で空を作成するためには、以下を処理するスクリプトを作ります。
スカイレンダラーの使用
上記の手順をすべて終了すると、新しいスカイが Visual Environment (Unity プロジェクトの Volumes をオーバライドします) の Sky Type ドロップダウン に自動的に表示されます。
スカイ設定
まず最初に、SkySettingsから継承した新しいクラスを作成します。この新しいクラスには、必要とする特定のスカイレンダラー専用の、あらゆるプロパティーが含まれます。
このクラスには以下を含める必要があります。
- SkyUniqueID 属性: これは特定のスカイに特有の整数でなければなりません。なおその他の SkySettings と抵触してはいけません。SkyType enum を使って、HDRP が既に使っている値を確認してください。
- GetHashCode: スカイシステムはこの機能を使って、いつスカイリフレクションのキューブマップを再レンダリングするかを決定します。
- GetSkyRendererType: スカイシステムはこの機能を使って、適切なレンダラーをインスタンス化します。
例えばこちらは、SkySettings の HDRI Sky の実装です。
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
[VolumeComponentMenu("Sky/New Sky")]
// SkyUniqueID はビルトイン HDRP SkyType enum の一部である必要はありません。
// これは、HDRPによってネイティブに使用される ID を追跡するためにのみ提供されます。
// すべての整数値を使用できます。
[SkyUniqueID(NEW_SKY_UNIQUE_ID)]
public class NewSky : SkySettings
{
const int NEW_SKY_UNIQUE_ID = 20382390;
[Tooltip("Specify the cubemap HDRP uses to render the sky.")]
public CubemapParameter hdriSky = new CubemapParameter(null);
public override Type GetSkyRendererType()
{
return typeof(NewSkyRenderer);
}
public override int GetHashCode()
{
int hash = base.GetHashCode();
unchecked
{
hash = hdriSky.value != null ? hash * 23 + hdriSky.GetHashCode() : hash;
}
return hash;
}
public override int GetHashCode(Camera camera)
{
// 空がカメラ設定に依存している場合に実装します (例えば位置など)
return GetHashCode();
}
}
スカイレンダラー
次に、クラスを作成して、実際に空を、ライティング用のキューブマップか、視覚的な背景のいずれかにレンダリングする必要があります。ここで、特定のレンダリング機能を実装します。
SkyRenderer は、SkyRenderer インターフェースを実装する必要があります。
public abstract class SkyRenderer
{
int m_LastFrameUpdate = -1;
/// <summary>
/// 開始時に呼び出されます。レンダラーが使用するリソースを作成します (シェーダー、マテリアルなど)。
/// </summary>
public abstract void Build();
/// <summary>
/// クリーンアップ時に呼び出されます。レンダラーが使ったリソースをリリースします。
/// </summary>
public abstract void Cleanup();
/// <summary>
/// HDRP は各フレームに 1 度この機能を呼び出します。SkyRenderer はユーザー定義の更新頻度と関係なく繰り返す必要がある場合に実装します。 (SkySettings UpdateMode を参照)。
/// </summary>
/// <returns>Update がスカイライティングに再度レンダリングが必要と判断した場合に、True。それ以外は False。</returns>
protected virtual bool Update(BuiltinSkyParameters builtinParams) { return false; }
/// <summary>
/// 空の実際のレンダリングを実装します。空を (ライティングの) キューブマップにレンダリングするとき、およびメインフレームのレンダリング中に HDRP がこれを呼び出します。
/// </summary>
/// <param name="builtinParams">空をレンダリングするために使うエンジンパラメーター。</param>
/// <param name="renderForCubemap">空をライティングのキューブマップにレンダリングしたい場合、True で渡します。このケースではスカイレンダラーに異なる実装が必要なときに役立ちます。</param>
/// <param name="renderSunDisk">スカイレンダラーが太陽のレンダリングをサポートする場合は、False に設定されているときにレンダーすべきではありません。</param>
public abstract void RenderSky(BuiltinSkyParameters builtinParams, bool renderForCubemap, bool renderSunDisk);
こちらはスカイレンダラーの簡単な実装の例です。
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
class NewSkyRenderer : SkyRenderer
{
public static readonly int _Cubemap = Shader.PropertyToID("_Cubemap");
public static readonly int _SkyParam = Shader.PropertyToID("_SkyParam");
public static readonly int _PixelCoordToViewDirWS = Shader.PropertyToID("_PixelCoordToViewDirWS");
Material m_NewSkyMaterial; // キューブマップをレンダーテクスチャにレンダリングします (キューブまたは 2D)
MaterialPropertyBlock m_PropertyBlock = new MaterialPropertyBlock();
private static int m_RenderCubemapID = 0; // FragBaking
private static int m_RenderFullscreenSkyID = 1; // FragRender
public override void Build()
{
m_NewSkyMaterial = CoreUtils.CreateEngineMaterial(GetNewSkyShader());
}
// シェーダーを取得するプロジェクト依存の方法。
Shader GetNewSkyShader()
{
// 実装してください
return null;
}
public override void Cleanup()
{
CoreUtils.Destroy(m_NewSkyMaterial);
}
protected override bool Update(BuiltinSkyParameters builtinParams)
{
return false;
}
public override void RenderSky(BuiltinSkyParameters builtinParams, bool renderForCubemap, bool renderSunDisk)
{
using (new ProfilingSample(builtinParams.commandBuffer, "Draw sky"))
{
var newSky = builtinParams.skySettings as NewSky;
int passID = renderForCubemap ? m_RenderCubemapID : m_RenderFullscreenSkyID;
float intensity = GetSkyIntensity(newSky, builtinParams.debugSettings);
float phi = -Mathf.Deg2Rad * newSky.rotation.value; // -rotation to match Legacy
m_PropertyBlock.SetTexture(_Cubemap, newSky.hdriSky.value);
m_PropertyBlock.SetVector(_SkyParam, new Vector4(intensity, 0.0f, Mathf.Cos(phi), Mathf.Sin(phi)));
m_PropertyBlock.SetMatrix(_PixelCoordToViewDirWS, builtinParams.pixelCoordToViewDirMatrix);
CoreUtils.DrawFullScreen(builtinParams.commandBuffer, m_NewSkyMaterial, m_PropertyBlock, passID);
}
}
}
注意
スカイレンダラーが重いデータ (事前計算されたテクスチャまたはそれに類似したものなど) を制御しなければならない場合、特別な対応が必要です。実際、レンダラーのインスタンスはカメラごとに存在するため、データがレンダラーのメンバーであれば、デフォルトでメモリー内でも複製されます。 スカイレンダラーにはそれぞれ大幅に異なるニーズがあるため、この種のデータ共有はレンダラーが制御するものであり、ユーザーが実装しなければなりません。
スカイレンダリングシェーダー
最後に、空用のシェーダーを実際に作成する必要があります。このシェーダーのコンテンツは、どのエフェクトを含めたいかによります。
例えばこちらは、スカイレンダラーの HDRI Sky 実装です。
Shader "Hidden/HDRP/Sky/NewSky"
{
HLSLINCLUDE
#pragma vertex Vert
#pragma editor_sync_compilation
#pragma target 4.5
#pragma only_renderers d3d11 playstation xboxone xboxseries vulkan metal switch
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Sky/SkyUtils.hlsl"
TEXTURECUBE(_Cubemap);
SAMPLER(sampler_Cubemap);
float4 _SkyParam; // x 露出、y 乗算、zw 回転 (cosPhi と sinPhi)
#define _Intensity _SkyParam.x
#define _CosPhi _SkyParam.z
#define _SinPhi _SkyParam.w
#define _CosSinPhi _SkyParam.zw
struct Attributes
{
uint vertexID : SV_VertexID;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 positionCS : SV_POSITION;
UNITY_VERTEX_OUTPUT_STEREO
};
Varyings Vert(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID, UNITY_RAW_FAR_CLIP_VALUE);
return output;
}
float3 RotationUp(float3 p, float2 cos_sin)
{
float3 rotDirX = float3(cos_sin.x, 0, -cos_sin.y);
float3 rotDirY = float3(cos_sin.y, 0, cos_sin.x);
return float3(dot(rotDirX, p), p.y, dot(rotDirY, p));
}
float4 GetColorWithRotation(float3 dir, float exposure, float2 cos_sin)
{
dir = RotationUp(dir, cos_sin);
float3 skyColor = SAMPLE_TEXTURECUBE_LOD(_Cubemap, sampler_Cubemap, dir, 0).rgb * _Intensity * exposure;
skyColor = ClampToFloat16Max(skyColor);
return float4(skyColor, 1.0);
}
float4 RenderSky(Varyings input, float exposure)
{
float3 viewDirWS = GetSkyViewDirWS(input.positionCS.xy);
// 反転してシーンを指します
float3 dir = -viewDirWS;
return GetColorWithRotation(dir, exposure, _CosSinPhi);
}
float4 FragBaking(Varyings input) : SV_Target
{
return RenderSky(input, 1.0);
}
float4 FragRender(Varyings input) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
return RenderSky(input, GetCurrentExposureMultiplier());
}
ENDHLSL
SubShader
{
// 通常の新しい空
// キューブマップ用
Pass
{
ZWrite Off
ZTest Always
Blend Off
Cull Off
HLSLPROGRAM
#pragma fragment FragBaking
ENDHLSL
}
// フルスクリーンの空用
Pass
{
ZWrite Off
ZTest LEqual
Blend Off
Cull Off
HLSLPROGRAM
#pragma fragment FragRender
ENDHLSL
}
}
Fallback Off
}
ノート: NewSky の例は、2 つのパスを使っています。1 つは背景で空をレンダリングするための深度テストを使い (そうすることでジオメトリが正しく遮蔽するようにするため)、もう 1 つは深度テストは用いずに空をリフレクションキューブマップにレンダリングします。