Skip to content

Instantly share code, notes, and snippets.

@Myonmu
Created March 24, 2025 12:54
Show Gist options
  • Select an option

  • Save Myonmu/03db0062f9f202dff018182804cb170c to your computer and use it in GitHub Desktop.

Select an option

Save Myonmu/03db0062f9f202dff018182804cb170c to your computer and use it in GitHub Desktop.
URP Copy Normals
Shader "Custom/BlitShader"
{
SubShader
{
Tags
{
"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline"
}
Pass
{
Name "Blit"
ZTest Always
ZWrite[_ZWrite]
Cull Off
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
TEXTURE2D(_SourceTex);
SAMPLER(sampler_SourceTex);
float4 frag(Varyings input) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
return SAMPLE_TEXTURE2D(_SourceTex,sampler_SourceTex, input.texcoord);
}
ENDHLSL
}
}
}
using UnityEngine;
using UnityEngine.Rendering.Universal;
namespace Rendering.Dithering
{
public class CopyNormalsFeature: ScriptableRendererFeature
{
public RenderPassEvent injectionPoint = RenderPassEvent.AfterRenderingDeferredLights;
public Vector2Int size= new(200,200);
private CopyNormalsPass _pass ;
private bool _requiresReset;
public override void Create()
{
var copyDepthPS = Shader.Find("Custom/BlitShader");
_pass = new CopyNormalsPass(injectionPoint, copyDepthPS, size);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
_pass.renderPassEvent = injectionPoint;
renderer.EnqueuePass(_pass);
}
}
}
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
namespace Rendering.Dithering
{
public class CopyNormalsPass: ScriptableRenderPass
{
private readonly Material _material;
private static int _shaderVarId;
private RTHandle _destination;
/// <summary>
/// Creates a new <c>CopyDepthPass</c> instance.
/// </summary>
/// <param name="evt">The <c>RenderPassEvent</c> to use.</param>
/// <param name="shader">The <c>Shader</c> to use for copying the depth.</param>
/// <param name="customPassName">An optional custom profiling name to disambiguate multiple copy passes.</param>
/// <seealso cref="RenderPassEvent"/>
public CopyNormalsPass(RenderPassEvent evt, Shader shader, Vector2Int size, string customPassName = null)
{
profilingSampler = customPassName != null
? new ProfilingSampler(customPassName)
: new ProfilingSampler(nameof(CopyNormalsPass));
_material = shader != null ? CoreUtils.CreateEngineMaterial(shader) : null;
renderPassEvent = evt;
_shaderVarId = Shader.PropertyToID( "_CameraNormalsTexture");
_destination = RTHandles.Alloc(size.x, size.y);
}
private class PassData
{
internal TextureHandle source;
internal TextureHandle destination;
internal UniversalCameraData cameraData;
internal Material material;
// The size of the camera target changes during the frame so we must make a copy of it here to preserve its record-time value.
internal Vector2Int cameraTargetSizeCopy;
}
private static void ExecutePass(RasterCommandBuffer cmd, PassData passData, RTHandle source,
RTHandle destination)
{
var mat = passData.material;
if (mat == null)
{
Debug.LogErrorFormat(
"Missing {0}. Copy Depth render pass will not execute. Check for missing reference in the renderer resources.",
mat);
return;
}
var viewportScale = source.useScaling
? new Vector2(source.rtHandleProperties.rtHandleScale.x, source.rtHandleProperties.rtHandleScale.y)
: Vector2.one;
// We y-flip if
// 1) we are blitting from render texture to back buffer(UV starts at bottom) and
// 2) renderTexture starts UV at top
var yflip = passData.cameraData.IsHandleYFlipped(source) !=
passData.cameraData.IsHandleYFlipped(destination);
var scaleBias = yflip
? new Vector4(viewportScale.x, -viewportScale.y, 0, viewportScale.y)
: new Vector4(viewportScale.x, viewportScale.y, 0, 0);
cmd.SetViewport(new Rect(0, 0, passData.cameraTargetSizeCopy.x, passData.cameraTargetSizeCopy.y));
mat.SetTexture(Shader.PropertyToID("_SourceTex"), source);
Blitter.BlitTexture(cmd, source, scaleBias, mat, 0);
}
/// <inheritdoc/>
public override void OnCameraCleanup(CommandBuffer cmd)
{
if (cmd == null)
throw new ArgumentNullException(nameof(cmd));
}
/// <summary>
/// Sets up the Copy Depth pass for RenderGraph execution
/// </summary>
/// <param name="renderGraph">The current RenderGraph used for recording and execution of a frame.</param>
/// <param name="destination"><c>TextureHandle</c> of the destination it will copy to.</param>
/// <param name="source"><c>TextureHandle</c> of the source it will copy from.</param>
/// <param name="cameraData">Camera settings for the current frame.</param>
/// <param name="bindAsCameraDepth">If this is true, the destination texture is bound as _CameraDepthTexture after the copy pass</param>
/// <param name="passName">The pass name used for debug and identifying the pass.</param>
public void Render(RenderGraph renderGraph, TextureHandle destination, TextureHandle source,
UniversalCameraData cameraData, bool bindAsCameraDepth = false,
string passName = "Copy Normals")
{
// TODO RENDERGRAPH: should call the equivalent of Setup() to initialise everything correctly
using (var builder =
renderGraph.AddRasterRenderPass<PassData>(passName, out var passData, profilingSampler))
{
passData.material = _material;
passData.cameraData = cameraData;
passData.cameraTargetSizeCopy = new Vector2Int(cameraData.cameraTargetDescriptor.width,
cameraData.cameraTargetDescriptor.height);
// Writes depth as "grayscale color" output
passData.destination = destination;
builder.SetRenderAttachment(destination, 0);
passData.source = source;
builder.UseTexture(source);
if (bindAsCameraDepth && destination.IsValid())
builder.SetGlobalTextureAfterPass(destination, _shaderVarId);
// TODO RENDERGRAPH: culling? force culling off for testing
builder.AllowPassCulling(false);
builder.AllowGlobalStateModification(true);
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
{
ExecutePass(context.cmd, data, data.source, data.destination);
});
}
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
var resourceData = frameData.Get<UniversalResourceData>();
var cameraData = frameData.Get<UniversalCameraData>();
var src = resourceData.gBuffer[2];
var dest = renderGraph.ImportTexture(_destination);
Render(renderGraph, dest, src, cameraData, true);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment