Hello! I have been trying to implement a shader effect for quite some time now, but I am currently stuck and need your help.
I have been following this tutorial which demonstrates the effect I’m going for
The tutorial is however for the built in render pipeline, and I’m trying to convert it for usage with the URP.
For easy reference, here is the unmodified shader ( https://github.com/Broxxar/NoMansScanner/blob/master/Assets/Scanner Effect/ScannerEffect.shader )
Shader "Hidden/ScannerEffect"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_DetailTex("Texture", 2D) = "white" {}
_ScanDistance("Scan Distance", float) = 0
_ScanWidth("Scan Width", float) = 10
_LeadSharp("Leading Edge Sharpness", float) = 10
_LeadColor("Leading Edge Color", Color) = (1, 1, 1, 0)
_MidColor("Mid Color", Color) = (1, 1, 1, 0)
_TrailColor("Trail Color", Color) = (1, 1, 1, 0)
_HBarColor("Horizontal Bar Color", Color) = (0.5, 0.5, 0.5, 0)
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct VertIn
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 ray : TEXCOORD1;
};
struct VertOut
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float2 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2;
};
float4 _MainTex_TexelSize;
float4 _CameraWS;
VertOut vert(VertIn v)
{
VertOut o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv.xy;
o.uv_depth = v.uv.xy;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv.y = 1 - o.uv.y;
#endif
o.interpolatedRay = v.ray;
return o;
}
sampler2D _MainTex;
sampler2D _DetailTex;
sampler2D_float _CameraDepthTexture;
float4 _WorldSpaceScannerPos;
float _ScanDistance;
float _ScanWidth;
float _LeadSharp;
float4 _LeadColor;
float4 _MidColor;
float4 _TrailColor;
float4 _HBarColor;
float4 horizBars(float2 p)
{
return 1 - saturate(round(abs(frac(p.y * 100) * 2)));
}
float4 horizTex(float2 p)
{
return tex2D(_DetailTex, float2(p.x * 30, p.y * 40));
}
half4 frag (VertOut i) : SV_Target
{
half4 col = tex2D(_MainTex, i.uv);
float rawDepth = DecodeFloatRG(tex2D(_CameraDepthTexture, i.uv_depth));
float linearDepth = Linear01Depth(rawDepth);
float4 wsDir = linearDepth * i.interpolatedRay;
float3 wsPos = _WorldSpaceCameraPos + wsDir;
half4 scannerCol = half4(0, 0, 0, 0);
float dist = distance(wsPos, _WorldSpaceScannerPos);
if (dist < _ScanDistance && dist > _ScanDistance - _ScanWidth && linearDepth < 1)
{
float diff = 1 - (_ScanDistance - dist) / (_ScanWidth);
half4 edge = lerp(_MidColor, _LeadColor, pow(diff, _LeadSharp));
scannerCol = lerp(_TrailColor, edge, diff) + horizBars(i.uv) * _HBarColor;
scannerCol *= diff;
}
return col + scannerCol;
}
ENDCG
}
}
}
And here is the logic script for giving the shader the essential data using built in renderer. (https://github.com/Broxxar/NoMansScanner/blob/master/Assets/Scanner Effect/ScannerEffectDemo.cs)
[ImageEffectOpaque]
void OnRenderImage(RenderTexture src, RenderTexture dst)
{
EffectMaterial.SetVector("_WorldSpaceScannerPos",
ScannerOrigin.position);
EffectMaterial.SetFloat("_ScanDistance", ScanDistance);
RaycastCornerBlit(src, dst, EffectMaterial);
}
void RaycastCornerBlit(RenderTexture source,
RenderTexture dest,
Material mat)
{
// Compute Frustum Corners
float camFar = _camera.farClipPlane;
float camFov = _camera.fieldOfView;
float camAspect = _camera.aspect;
float fovWHalf = camFov * 0.5f;
Vector3 toRight = _camera.transform.right
* Mathf.Tan(fovWHalf
* Mathf.Deg2Rad)
* camAspect;
Vector3 toTop = _camera.transform.up
* Mathf.Tan(fovWHalf
* Mathf.Deg2Rad);
Vector3 topLeft = (_camera.transform.forward - toRight + toTop);
float camScale = topLeft.magnitude * camFar;
topLeft.Normalize();
topLeft *= camScale;
Vector3 topRight = (_camera.transform.forward + toRight + toTop);
topRight.Normalize();
topRight *= camScale;
Vector3 bottomRight = (_camera.transform.forward + toRight - toTop);
bottomRight.Normalize();
bottomRight *= camScale;
Vector3 bottomLeft = (_camera.transform.forward - toRight - toTop);
bottomLeft.Normalize();
bottomLeft *= camScale;
// Custom Blit, encoding Frustum Corners as
// additional Texture Coordinates
RenderTexture.active = dest;
mat.SetTexture("_MainTex", source);
GL.PushMatrix();
GL.LoadOrtho();
mat.SetPass(0);
GL.Begin(GL.QUADS);
GL.MultiTexCoord2(0, 0.0f, 0.0f);
GL.MultiTexCoord(1, bottomLeft);
GL.Vertex3(0.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 0.0f);
GL.MultiTexCoord(1, bottomRight);
GL.Vertex3(1.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 1.0f);
GL.MultiTexCoord(1, topRight);
GL.Vertex3(1.0f, 1.0f, 0.0f);
GL.MultiTexCoord2(0, 0.0f, 1.0f);
GL.MultiTexCoord(1, topLeft);
GL.Vertex3(0.0f, 1.0f, 0.0f);
GL.End();
GL.PopMatrix();
}
The first issue I encountered was that there are no “OnRenderImage” callback in the URP so I tried to implement a custom [ScriptableRendererFeature] and [ScriptableRenderPass]
I have “Depth Texture” and “Opaque Texture” enabled in the “UniversalRenderPipelineAsset” options.
This is my current implementation in the Execute function of the [ScriptableRenderPass]
public override void Execute(ScriptableRenderContext context,
ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);
Camera _camera = renderingData.cameraData.camera;
cmd.SetGlobalVector("_WorldSpaceScannerPos",
_camera.transform.position);
// Compute Frustum Corners
float camFar = _camera.farClipPlane;
float camFov = _camera.fieldOfView;
float camAspect = _camera.aspect;
float fovWHalf = camFov * 0.5f;
Vector3 toRight = _camera.transform.right
* Mathf.Tan(fovWHalf
* Mathf.Deg2Rad)
* camAspect;
Vector3 toTop = _camera.transform.up
* Mathf.Tan(fovWHalf
* Mathf.Deg2Rad);
Vector3 topLeft = (_camera.transform.forward - toRight + toTop);
float camScale = topLeft.magnitude * camFar;
topLeft.Normalize();
topLeft *= camScale;
Vector3 topRight = (_camera.transform.forward + toRight + toTop);
topRight.Normalize();
topRight *= camScale;
Vector3 bottomRight = (_camera.transform.forward + toRight - toTop);
bottomRight.Normalize();
bottomRight *= camScale;
Vector3 bottomLeft = (_camera.transform.forward - toRight - toTop);
bottomLeft.Normalize();
bottomLeft *= camScale;
RenderTextureDescriptor opaqueDesc =
renderingData.cameraData.cameraTargetDescriptor;
opaqueDesc.depthBufferBits = 16;
cmd.GetTemporaryRT(m_TemporaryColorTexture.id,
opaqueDesc,
filterMode);
Blit(cmd, source, m_TemporaryColorTexture.id,
blitMaterial,
blitShaderPassIndex);
// Custom Blit, encoding Frustum Corners
// as additional Texture Coordinates
cmd.SetRenderTarget(m_TemporaryColorTexture.id);
cmd.SetGlobalTexture("_MainTex", source);
GL.PushMatrix();
GL.LoadOrtho();
blitMaterial.SetPass(0);
GL.Begin(GL.QUADS);
GL.MultiTexCoord2(0, 0.0f, 0.0f);
GL.MultiTexCoord(1, bottomLeft);
GL.Vertex3(0.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 0.0f);
GL.MultiTexCoord(1, bottomRight);
GL.Vertex3(1.0f, 0.0f, 0.0f);
GL.MultiTexCoord2(0, 1.0f, 1.0f);
GL.MultiTexCoord(1, topRight);
GL.Vertex3(1.0f, 1.0f, 0.0f);
GL.MultiTexCoord2(0, 0.0f, 1.0f);
GL.MultiTexCoord(1, topLeft);
GL.Vertex3(0.0f, 1.0f, 0.0f);
GL.End();
GL.PopMatrix();
Blit(cmd, m_TemporaryColorTexture.id, source);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
It seems like, the “TEXCOORD1” in the VertIn struct that “ray” get assigned to in the shader is not correct, which leads me to believe that I do something wrong in the Execute function…
The shader is drawing the scan effect, however only when _WorldSpaceScannerPos is exactly that of the cameras, which leads me to believe that the pixel to world position is incorrect. We can see that the effect expands from the lower left corner of the view which also suggests that something fishy is going on…
brisktornammonite