You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
10 KiB
263 lines
10 KiB
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.RenderGraphModule;
|
|
using UnityEngine.Rendering.Universal;
|
|
|
|
/// <summary>
|
|
/// Tilt-shift post-process effect for URP 17 (Unity 6).
|
|
///
|
|
/// Two modes:
|
|
/// ScreenSpace — blur based on vertical screen position (fast, no depth buffer needed).
|
|
/// DepthBased — Scheimpflug tilt-plane CoC using the depth buffer (physically accurate).
|
|
/// </summary>
|
|
public class TiltShiftFeature : ScriptableRendererFeature
|
|
{
|
|
public enum BlurMode
|
|
{
|
|
/// <summary>Blur based on vertical UV position. No depth buffer required.</summary>
|
|
ScreenSpace,
|
|
|
|
/// <summary>
|
|
/// Blur based on distance from a tilted focal plane in world space (Scheimpflug principle).
|
|
/// Requires the camera Depth Texture to be enabled.
|
|
/// </summary>
|
|
DepthBased
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class Settings
|
|
{
|
|
[Tooltip("ScreenSpace: blur by vertical screen position.\n" +
|
|
"DepthBased: physically-accurate Scheimpflug tilt-plane CoC (needs Depth Texture).")]
|
|
public BlurMode blurMode = BlurMode.ScreenSpace;
|
|
|
|
[Header("Screen-Space Mode")]
|
|
[Tooltip("Vertical offset of the sharp band from screen centre (UV units, + = up).")]
|
|
[Range(-0.4f, 0.4f)]
|
|
public float centerOffset = 0f;
|
|
|
|
[Tooltip("Height of the fully-sharp band as a fraction of screen height.")]
|
|
[Range(0.01f, 0.8f)]
|
|
public float focusWidth = 0.2f;
|
|
|
|
[Tooltip("Distance over which blur fades in at each edge of the focus band (UV units).")]
|
|
[Range(0.01f, 0.4f)]
|
|
public float falloffRange = 0.15f;
|
|
|
|
[Header("Depth-Based Mode (Scheimpflug)")]
|
|
[Tooltip("Depth of the focal plane centre (world units). " +
|
|
"Increase until the subject you want sharp is in focus.")]
|
|
[Range(0.1f, 1000f)]
|
|
public float focusDistance = 10f;
|
|
|
|
[Tooltip("How much the focal plane depth shifts per screen-height unit.\n" +
|
|
"Positive = top of screen focuses deeper (typical for bird's-eye view).\n" +
|
|
"Set to 0 for standard (flat) DoF.")]
|
|
[Range(-200f, 200f)]
|
|
public float tiltFactor = 30f;
|
|
|
|
[Tooltip("Half-width of the perfectly sharp zone around the focal plane (world units).")]
|
|
[Range(0f, 50f)]
|
|
public float focusBand = 1f;
|
|
|
|
[Tooltip("World-unit distance over which blur ramps from zero to maximum.")]
|
|
[Range(0.1f, 100f)]
|
|
public float depthFalloff = 5f;
|
|
|
|
[Header("Shared")]
|
|
[Tooltip("Maximum blur radius in pixels at the extremes.")]
|
|
[Range(1f, 64f)]
|
|
public float maxBlurRadius = 20f;
|
|
|
|
[Tooltip("Gaussian samples per side per pass. Higher = smoother, slower.")]
|
|
[Range(2, 20)]
|
|
public int sampleCount = 8;
|
|
|
|
public RenderPassEvent injectionPoint = RenderPassEvent.AfterRenderingPostProcessing;
|
|
}
|
|
|
|
public Settings settings = new Settings();
|
|
|
|
private TiltShiftPass renderPass;
|
|
private Material blitMaterial;
|
|
|
|
// ── Lifecycle ─────────────────────────────────────────────────────────────────
|
|
|
|
public override void Create()
|
|
{
|
|
var shader = Shader.Find("Custom/TiltShift");
|
|
if (shader == null)
|
|
{
|
|
Debug.LogWarning("[TiltShift] Shader 'Custom/TiltShift' not found.");
|
|
return;
|
|
}
|
|
|
|
blitMaterial = CoreUtils.CreateEngineMaterial(shader);
|
|
renderPass = new TiltShiftPass(blitMaterial, settings);
|
|
renderPass.renderPassEvent = settings.injectionPoint;
|
|
}
|
|
|
|
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
|
{
|
|
if (blitMaterial == null || renderPass == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var camType = renderingData.cameraData.cameraType;
|
|
if (camType == CameraType.Preview || camType == CameraType.Reflection)
|
|
{
|
|
return;
|
|
}
|
|
|
|
renderPass.UpdateSettings(settings);
|
|
renderPass.renderPassEvent = settings.injectionPoint;
|
|
renderer.EnqueuePass(renderPass);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
CoreUtils.Destroy(blitMaterial);
|
|
}
|
|
|
|
// ── Inner render pass ──────────────────────────────────────────────────────────
|
|
|
|
private sealed class TiltShiftPass : ScriptableRenderPass
|
|
{
|
|
// Shader property IDs
|
|
private static readonly int propMaxBlurRadius = Shader.PropertyToID("_TiltShift_MaxBlurRadius");
|
|
private static readonly int propSampleCount = Shader.PropertyToID("_TiltShift_SampleCount");
|
|
private static readonly int propCenterOffset = Shader.PropertyToID("_TiltShift_CenterOffset");
|
|
private static readonly int propFocusWidth = Shader.PropertyToID("_TiltShift_FocusWidth");
|
|
private static readonly int propFalloffRange = Shader.PropertyToID("_TiltShift_FalloffRange");
|
|
private static readonly int propFocusDistance = Shader.PropertyToID("_TiltShift_FocusDistance");
|
|
private static readonly int propTiltFactor = Shader.PropertyToID("_TiltShift_TiltFactor");
|
|
private static readonly int propFocusBand = Shader.PropertyToID("_TiltShift_FocusBand");
|
|
private static readonly int propDepthFalloff = Shader.PropertyToID("_TiltShift_DepthFalloff");
|
|
|
|
private const string depthModeKeyword = "DEPTH_MODE";
|
|
|
|
private readonly Material blitMaterial;
|
|
private Settings currentSettings;
|
|
|
|
// PassData carries what the render func needs at execution time.
|
|
private class PassData
|
|
{
|
|
public TextureHandle source;
|
|
public Material material;
|
|
public int passIndex;
|
|
}
|
|
|
|
public TiltShiftPass(Material mat, Settings passSettings)
|
|
{
|
|
blitMaterial = mat;
|
|
currentSettings = passSettings;
|
|
requiresIntermediateTexture = true;
|
|
}
|
|
|
|
public void UpdateSettings(Settings s)
|
|
{
|
|
currentSettings = s;
|
|
}
|
|
|
|
// ── RecordRenderGraph ──────────────────────────────────────────────────────
|
|
|
|
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
|
|
{
|
|
var resourceData = frameData.Get<UniversalResourceData>();
|
|
|
|
if (resourceData.isActiveTargetBackBuffer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var useDepth = currentSettings.blurMode == BlurMode.DepthBased;
|
|
|
|
// Validate depth texture availability.
|
|
if (useDepth && !resourceData.cameraDepthTexture.IsValid())
|
|
{
|
|
Debug.LogWarning("[TiltShift] DepthBased mode requires the camera Depth Texture. " +
|
|
"Enable it in the URP Renderer asset or Camera component.");
|
|
useDepth = false;
|
|
}
|
|
|
|
// Push settings to material.
|
|
blitMaterial.SetFloat(propMaxBlurRadius, currentSettings.maxBlurRadius);
|
|
blitMaterial.SetInt(propSampleCount, currentSettings.sampleCount);
|
|
blitMaterial.SetFloat(propCenterOffset, currentSettings.centerOffset);
|
|
blitMaterial.SetFloat(propFocusWidth, currentSettings.focusWidth);
|
|
blitMaterial.SetFloat(propFalloffRange, currentSettings.falloffRange);
|
|
blitMaterial.SetFloat(propFocusDistance, currentSettings.focusDistance);
|
|
blitMaterial.SetFloat(propTiltFactor, currentSettings.tiltFactor);
|
|
blitMaterial.SetFloat(propFocusBand, currentSettings.focusBand);
|
|
blitMaterial.SetFloat(propDepthFalloff, currentSettings.depthFalloff);
|
|
|
|
if (useDepth)
|
|
{
|
|
blitMaterial.EnableKeyword(depthModeKeyword);
|
|
}
|
|
else
|
|
{
|
|
blitMaterial.DisableKeyword(depthModeKeyword);
|
|
}
|
|
|
|
// Texture handles
|
|
var source = resourceData.activeColorTexture;
|
|
var depth = useDepth ? resourceData.cameraDepthTexture : TextureHandle.nullHandle;
|
|
|
|
var desc = renderGraph.GetTextureDesc(source);
|
|
desc.clearBuffer = false;
|
|
|
|
desc.name = "TiltShift_Temp";
|
|
var temp = renderGraph.CreateTexture(desc);
|
|
|
|
desc.name = "TiltShift_Final";
|
|
var final = renderGraph.CreateTexture(desc);
|
|
|
|
// Pass 0 — horizontal blur (source to temp)
|
|
AddBlurPass(renderGraph, "TiltShift_H", source, depth, temp, passIndex: 0, useDepth);
|
|
|
|
// Pass 1 — vertical blur (temp to final)
|
|
AddBlurPass(renderGraph, "TiltShift_V", temp, depth, final, passIndex: 1, useDepth);
|
|
|
|
// Redirect camera colour so subsequent passes use our result.
|
|
resourceData.cameraColor = final;
|
|
}
|
|
|
|
// ── Helper: record one blur pass ───────────────────────────────────────────
|
|
|
|
private void AddBlurPass(
|
|
RenderGraph renderGraph,
|
|
string passName,
|
|
TextureHandle source,
|
|
TextureHandle depth,
|
|
TextureHandle dest,
|
|
int passIndex,
|
|
bool useDepth)
|
|
{
|
|
using var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData);
|
|
|
|
passData.source = source;
|
|
passData.material = blitMaterial;
|
|
passData.passIndex = passIndex;
|
|
|
|
builder.UseTexture(source, AccessFlags.Read);
|
|
|
|
// Declare the depth dependency so the render graph orders this pass after CopyDepthPass.
|
|
// We do not call SetGlobalTexture here — URP's CopyDepthPass already exposes
|
|
// _CameraDepthTexture globally via SetGlobalTextureAfterPass.
|
|
if (useDepth && depth.IsValid())
|
|
{
|
|
builder.UseTexture(depth, AccessFlags.Read);
|
|
}
|
|
|
|
builder.SetRenderAttachment(dest, 0, AccessFlags.Write);
|
|
|
|
builder.SetRenderFunc((PassData data, RasterGraphContext ctx) =>
|
|
{
|
|
Blitter.BlitTexture(ctx.cmd, data.source, new Vector4(1, 1, 0, 0),
|
|
data.material, data.passIndex);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|