UnityのURPのVolumeにHeightFogを追加する
Unityで高さフォグを使おうと思ったがURPパッケージに入っていなかったので自分で作る。
使い方は、RenderFeatureとVolumeで設定すると使えるようになります。
こんな見た目になります。
Github
コードとサンプルはここに置いてあります。
https://github.com/jnhtt/height-fog-urp-volume
開発環境
以下のような環境で実装しました。
- MacBook Pro (16インチ,2021) M1 Pro/32GB
- Unity2022.3.13f1
- URP 14.0.9
技術的要素
- 高さフォグをVolumeに追加
- RenderFeature/RenderPassの追加
高さフォグをVolumeに追加
高さフォグの制御のためのパラメーターはVolume経由で行うようにしたいと思うのでVolumeComponentを継承したクラスを作成します。
fogHeightMin/Maxの値は用途に合わせて調整してください。
パラメーター | 意味 | 値の範囲 |
---|---|---|
fogColor | フォグの色 | rgb |
fogHeightMin | フォググラデの範囲の最小値、これより小さいとフォグ色で塗られる | floatで[-200,300] |
fogHeightMax | フォググラデの範囲の最大値、これより大きいとフォグはない | floatで[-200,300] |
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace FingerTip.Volume
{
public class HeightFog : VolumeComponent
{
public ColorParameter fogColor = new ColorParameter(Color.white);
public ClampedFloatParameter fogHeightMin = new ClampedFloatParameter(0, -200, 300);
public ClampedFloatParameter fogHeightMax = new ClampedFloatParameter(0, -200, 300);
public bool IsActive => fogHeightMin.value < fogHeightMax.value;
}
}
Volumeに追加すると高さフォグが使えるようになります。ただし、サポートしているのは、Global Volumeだけです。
もちろん、次に説明するRenderFeatureを追加する必要があります。
RenderFeature/RenderPassの追加
まずは、RenderFeatureを作成します。
メニューからRenderFeatureを作成するとRenderPassクラスを内包するコードを自動出力してくれます。
この自動出力されたコードを改造して実装します。
Projectビュー > 右クリック > Create > Rendering > URP RendererFeature
CustomRenderPassFeatureのファイル名をHeightFogなど高さフォグが分かる名前に変更します。
こんな感じのコードを作成します。
VolumeにHeightFogの機能を追加して、そこから_FogColorや_FogHeightMin/MaxのパラメーターをCustom/HeightFogシェーダーに渡しています。
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace FingerTip
{
using FingerTip.Volume;
public sealed class HeightFogRendererFeature : ScriptableRendererFeature
{
private sealed class HeightFogRenderPass : ScriptableRenderPass
{
private const string RenderPassName = nameof(HeightFogRenderPass);
private Material material;
private static readonly int FogColorId = Shader.PropertyToID("_FogColor");
private static readonly int FogHeightMinId = Shader.PropertyToID("_FogHeightMin");
private static readonly int FogHeightMaxId = Shader.PropertyToID("_FogHeightMax");
public HeightFogRenderPass()
{
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
var shaderName = "Custom/HeightFog";
var shader = Shader.Find(shaderName);
if (shader == null)
{
Debug.LogError($"Not found shader!{shaderName}");
return;
}
material = CoreUtils.CreateEngineMaterial(shader);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (renderingData.cameraData.camera.cameraType == CameraType.Preview)
{
return;
}
if (material == null || !renderingData.cameraData.postProcessEnabled)
{
return;
}
var volumeStack = VolumeManager.instance.stack;
if (volumeStack == null)
{
return;
}
var heightFog = volumeStack.GetComponent<HeightFog>();
if (heightFog == null || !heightFog.active || !heightFog.IsActive)
{
return;
}
material.SetColor(FogColorId, heightFog.fogColor.value);
material.SetFloat(FogHeightMinId, heightFog.fogHeightMin.value);
material.SetFloat(FogHeightMaxId, heightFog.fogHeightMax.value);
var cmd = CommandBufferPool.Get(RenderPassName);
Blit(cmd, ref renderingData, material);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public void Cleanup()
{
CoreUtils.Destroy(material);
}
}
private HeightFogRenderPass rendererPass;
public override void Create()
{
rendererPass = new HeightFogRenderPass();
}
protected override void Dispose(bool disposing)
{
rendererPass.Cleanup();
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(rendererPass);
}
}
}
次のコードは、高さフォグを実現するHeightFogシェーダーです。
ビルドの際には、Preloadに追加してビルドに含まれるようにする必要があります。
重要な箇所としては、_BlitTextureは組み込みのテクスチャでColorバッファを参照できます。
また、SampleSceneDepth関数でピクセルの深度値を取得できます。深度値を取得できるとComputeWorldSpacePosition関数でワールド座標を計算できます。
各ピクセルのワールド座標のy座標を高さとすると、どの高さからフォグの効果を描画するかを決めることができます。
フォグの計算式はあとで説明しますが、至って簡単な処理になっています。
Shader "Custom/HeightFog"
{
HLSLINCLUDE
#pragma exclude_renderers gles
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
float4 _FogColor;
float _FogHeightMin;
float _FogHeightMax;
float4 Frag(Varyings input) : SV_Target
{
float4 c = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_PointClamp, input.texcoord);
#if UNITY_REVERSED_Z
float depth = SampleSceneDepth(input.texcoord).x;
#else
float depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(input.texcoord).x);
#endif
float3 worldPos = ComputeWorldSpacePosition(input.texcoord, depth, UNITY_MATRIX_I_VP);
float rate = smoothstep(_FogHeightMin, _FogHeightMax, worldPos.y);
return lerp(_FogColor, c, rate);
}
ENDHLSL
SubShader
{
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline"}
LOD 100
ZTest Always
ZWrite Off
Cull Off
Pass
{
Name "HeightFog"
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
ENDHLSL
}
}
}
作成したRendererFeatureを UniversalRenderer Data に追加します。
さらにVolumeにHeightFogを追加して機能を有効にすることで使用可能です。
高さフォグだけでなくいろんな効果を追加できそうです。
ScreenSpaceで各ピクセルのワールド座標を取得できると様々な効果を実装できそうです。