- Home /
How do you get the world position from the screen position in a URP shader graph used in a ScriptableRenderPass?
I'm using a shader graph in the Universal Shader Pipeline (URP) to do some post-processing in a ScriptableRenderPass. How do I get the world coordinates of the screen position?
I have tried using the View Direction node, set to World:
And I have tried the Screen Position node:
Neither seems to be working because the colors change as I move the camera around, but I would expect the color to stay the same since the world position of the GameObject is not changing. Any suggestions?
Answer by Namey5 · Aug 21, 2020 at 04:18 AM
Something along the lines of this should work;
You will however probably need to provide the inverse-projection matrix yourself for two reasons;
Shader graph is barely functional at best, and currently just using the inverse-projection matrix will throw errors.
The matrices shader graph provides would probably already be API converted, meaning you would need to manually handle platform differences. As such, if you just pass in the raw inverse-projection matrix from your camera (Unity uses OpenGL convention) this should just work.
//One way to set this up for the scene globally
//You don't need this script on every object, just one place in the scene will do (some kind of singleton game-manager object, that kind of thing)
[ExecuteInEditMode]
public class MatrixSetup : MonoBehaviour
{
private void OnEnable ()
{
Camera.onPreCull += SetupMatrices;
}
private void OnDisable ()
{
Camera.onPreCull -= SetupMatrices;
}
private void SetupMatrices (Camera cam)
{
//Just make sure you have a custom matrix property on your material called _InvProjectionMatrix and use that instead
Shader.SetGlobalMatrix ("_InvProjectionMatrix", cam.projectionMatrix.inverse);
}
}
Thank you for your answer! Unfortunately, I tried and it is not quite working:
https://i.imgur.com/r1k25JH.gif
The cube is at (0,0,0) and the camera rotates around it, while the sphere and capsule are at (0,-20,80) and (30,-30,40). I would expect the cube to be half white and the sphere and capsule to be fully black, no matter what camera angle.
As you said, the Transformation $$anonymous$$atrix node's Inverse Projection setting causes a bunch of errors and is unusable, so I am injecting the inverse projection matrix in the ScriptableRenderPass just before the material is used.
That does look to be working properly, although it would appear the view-to-world transformation isn't happening (again, I would imagine a bug in shader graph). In that case, I would suggest also providing the cameraToWorld matrix in the same way you provide the inverse projection matrix and multiply the point by that rather than using the transform node.
The code that shader graph generates seems to be correct (besides just being ugly but I kinda expect that for autogenerated code):
SurfaceDescription SurfaceDescriptionFunction(SurfaceDescriptionInputs IN)
{
SurfaceDescription surface = (SurfaceDescription)0;
float4x4 _Property_5B6D371B_Out_0 = _InvProjection$$anonymous$$atrix;
float4 _ScreenPosition_854B73C5_Out_0 = float4(IN.ScreenPosition.xy / IN.ScreenPosition.w, 0, 0);
float4 _$$anonymous$$ultiply_29352153_Out_2;
Unity_$$anonymous$$ultiply_float(_ScreenPosition_854B73C5_Out_0, float4(2, 2, 2, 2), _$$anonymous$$ultiply_29352153_Out_2);
float4 _Subtract_666C8A4A_Out_2;
Unity_Subtract_float4(_$$anonymous$$ultiply_29352153_Out_2, float4(1, 1, 1, 1), _Subtract_666C8A4A_Out_2);
float _Split_D9227722_R_1 = _Subtract_666C8A4A_Out_2[0];
float _Split_D9227722_G_2 = _Subtract_666C8A4A_Out_2[1];
float _Split_D9227722_B_3 = _Subtract_666C8A4A_Out_2[2];
float _Split_D9227722_A_4 = _Subtract_666C8A4A_Out_2[3];
float4 _Combine_1ED2C973_RGBA_4;
float3 _Combine_1ED2C973_RGB_5;
float2 _Combine_1ED2C973_RG_6;
Unity_Combine_float(_Split_D9227722_R_1, _Split_D9227722_G_2, 1, 1, _Combine_1ED2C973_RGBA_4, _Combine_1ED2C973_RGB_5, _Combine_1ED2C973_RG_6);
float4 _$$anonymous$$ultiply_C6D92794_Out_2;
Unity_$$anonymous$$ultiply_float(_Property_5B6D371B_Out_0, _Combine_1ED2C973_RGBA_4, _$$anonymous$$ultiply_C6D92794_Out_2);
float _Split_E2ED4B73_R_1 = _$$anonymous$$ultiply_C6D92794_Out_2[0];
float _Split_E2ED4B73_G_2 = _$$anonymous$$ultiply_C6D92794_Out_2[1];
float _Split_E2ED4B73_B_3 = _$$anonymous$$ultiply_C6D92794_Out_2[2];
float _Split_E2ED4B73_A_4 = _$$anonymous$$ultiply_C6D92794_Out_2[3];
float4 _Divide_8C21FB04_Out_2;
Unity_Divide_float4(_$$anonymous$$ultiply_C6D92794_Out_2, (_Split_E2ED4B73_A_4.xxxx), _Divide_8C21FB04_Out_2);
float _SceneDepth_8757E40A_Out_1;
Unity_SceneDepth_Linear01_float(float4(IN.ScreenPosition.xy / IN.ScreenPosition.w, 0, 0), _SceneDepth_8757E40A_Out_1);
float4 _$$anonymous$$ultiply_43807CD1_Out_2;
Unity_$$anonymous$$ultiply_float(_Divide_8C21FB04_Out_2, (_SceneDepth_8757E40A_Out_1.xxxx), _$$anonymous$$ultiply_43807CD1_Out_2);
float3 _Transform_CCF2A2F9_Out_1 = mul(UNITY_$$anonymous$$ATRIX_I_V, float4((_$$anonymous$$ultiply_43807CD1_Out_2.xyz).xyz, 1)).xyz;
float _Split_35585B54_R_1 = _Transform_CCF2A2F9_Out_1[0];
float _Split_35585B54_G_2 = _Transform_CCF2A2F9_Out_1[1];
float _Split_35585B54_B_3 = _Transform_CCF2A2F9_Out_1[2];
float _Split_35585B54_A_4 = 0;
surface.Color = (_Split_35585B54_G_2.xxx);
surface.Alpha = 1;
surface.AlphaClipThreshold = 0;
return surface;
}
And the code:
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Project
{
[Serializable]
public class UnderwaterRendererSettings
{
public $$anonymous$$aterial material;
public RenderPassEvent renderPassEvent;
public bool disableInSceneView = true;
}
public class UnderwaterRenderPass : ScriptableRenderPass
{
private RenderTargetIdentifier renderTargetId;
private $$anonymous$$aterial material;
private RenderTargetHandle tempTexture;
public void Setup(RenderTargetIdentifier renderTargetId, $$anonymous$$aterial material)
{
this.renderTargetId = renderTargetId;
this.material = material;
}
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
cmd.GetTemporaryRT(tempTexture.id, cameraTextureDescriptor);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get("UnderwaterRenderPass");
Camera camera = renderingData.cameraData.camera;
$$anonymous$$atrix4x4 invProjection$$anonymous$$atrix = camera.projection$$anonymous$$atrix.inverse;
material.Set$$anonymous$$atrix("_InvProjection$$anonymous$$atrix", invProjection$$anonymous$$atrix);
cmd.Blit(renderTargetId, tempTexture.Identifier(), material, 0);
cmd.Blit(tempTexture.Identifier(), renderTargetId);
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
CommandBufferPool.Release(cmd);
}
public override void FrameCleanup(CommandBuffer cmd)
{
cmd.ReleaseTemporaryRT(tempTexture.id);
}
}
public class UnderwaterRendererFeature : ScriptableRendererFeature
{
public UnderwaterRendererSettings settings;
public UnderwaterRenderPass pass;
public override void Create()
{
pass = new UnderwaterRenderPass();
pass.renderPassEvent = settings.renderPassEvent;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.disableInSceneView && renderingData.cameraData.isSceneViewCamera)
return;
pass.Setup(renderer.cameraColorTarget, settings.material);
renderer.EnqueuePass(pass);
}
}
}
I'm not sure if I should inject more of the matrices like is suggested here: https://stackoverflow.com/a/58600831 ?