Hello, I tested a way to raycast the scene from the camera without collider, using camera depth texture.
I play with Unity 6000 in URP and rendergraph with a render feature.
I would like to have advises on my work, to know if I do things right.
The render feature :
using UnityEngine.Rendering.Universal;
using UnityEngine;
using System;
public class DepthQueryRenderFeature : ScriptableRendererFeature
{
[SerializeField] private Shader shader;
private Material material;
private DepthQueryRenderPass depthQueryRenderPass;
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (!renderingData.cameraData.isSceneViewCamera && !renderingData.cameraData.isPreviewCamera)
{
renderer.EnqueuePass(depthQueryRenderPass);
depthQueryRenderPass.CameraNear = renderingData.cameraData.camera.nearClipPlane;
depthQueryRenderPass.CameraFar = renderingData.cameraData.camera.farClipPlane;
}
}
/// -0.5,-0.5 is viewport bottom left
public bool Query(Vector2 position, Action<float> callback)
{
if (depthQueryRenderPass != null && depthQueryRenderPass.QueryCallback == null)
{
depthQueryRenderPass.QueryPosition = position;
depthQueryRenderPass.QueryCallback = callback;
depthQueryRenderPass.QueryPending = true;
return true;
}
return false;
}
public override void Create()
{
if (shader == null)
{
return;
}
material = new Material(shader);
depthQueryRenderPass = new DepthQueryRenderPass(material, 0);
depthQueryRenderPass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
}
}
The render pass :
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System;
using UnityEngine;
using UnityEngine.Rendering.RenderGraphModule.Util;
using Unity.Collections;
public class DepthQueryRenderPass : ScriptableRenderPass
{
private class PassData
{
internal TextureHandle depth;
}
private Material blitMaterial;
private int shaderPass;
BaseRenderFunc<PassData, UnsafeGraphContext> PassExecuter;
Action<AsyncGPUReadbackRequest> ReadbackCallback;
public bool QueryPending;
public Action<float> QueryCallback;
public Vector2 QueryPosition;
public float CameraNear;
public float CameraFar;
public DepthQueryRenderPass(Material blitMaterial, int shaderPass)
{
this.blitMaterial = blitMaterial;
this.shaderPass = shaderPass;
PassExecuter = ExecuteReadbackPass;
ReadbackCallback = Readback;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
if (QueryPending)
{
QueryPending = false;
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
//Readback texture
TextureHandle readBackTexture = UniversalRenderer.CreateRenderGraphTexture(
renderGraph
, new RenderTextureDescriptor(1, 1, RenderTextureFormat.RFloat, 0)
, "DepthQueryReadbackTexture"
, false
);
//Blit camera depth to readback texture
renderGraph.AddBlitPass(
new RenderGraphUtils.BlitMaterialParameters(
resourceData.cameraDepthTexture, readBackTexture
, new Vector2(1, 1), QueryPosition
, blitMaterial, 0
),
"DepthQueryBlit"
);
//Readback
using (var builder = renderGraph.AddUnsafePass<PassData>("DepthQueryReadback", out var passData))
{
ConfigureInput(ScriptableRenderPassInput.Depth);
passData.depth = readBackTexture;
builder.UseTexture(passData.depth, AccessFlags.Read);
builder.AllowPassCulling(false);
builder.SetRenderFunc(PassExecuter);
}
}
}
void ExecuteReadbackPass(PassData passData, UnsafeGraphContext context)
{
CommandBuffer unsafeCommandBuffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
var array = new NativeArray<float>(1, Allocator.Persistent);
unsafeCommandBuffer.RequestAsyncReadbackIntoNativeArray(
ref array,
passData.depth, 0, 0, 1, 0, 1, 0, 1, UnityEngine.Experimental.Rendering.GraphicsFormat.R32_SFloat
, ReadbackCallback
);
}
float LinearEyeDepth(float z)
{
var zpx = -1.0f + CameraFar / CameraNear;
var zpy = 1.0f;
var zpz = zpx / CameraFar;
var zpw = zpy / CameraFar;
return 1.0f / (zpz * z + zpw);
}
private void Readback(AsyncGPUReadbackRequest request)
{
if (!request.hasError)
{
var array = request.GetData<float>();
QueryCallback(LinearEyeDepth(array[0]));
if(!QueryPending) QueryCallback = null;
array.Dispose();
}
}
}
The shader :
Shader "Custom/DepthQuery"
{
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// The Blit.hlsl file provides the vertex shader (Vert),
// the input structure (Attributes), and the output structure (Varyings)
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
float4 Frag(Varyings IN) : SV_Target
{
#if UNITY_REVERSED_Z
float depth = SampleSceneDepth(IN.texcoord);
#else
// Adjust z to match NDC for OpenGL
float depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(IN.texcoord));
#endif
return depth;
}
ENDHLSL
SubShader
{
Cull Off ZWrite Off ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
ENDHLSL
}
}
}
The raycasting behaviour :
using System;
using UnityEngine;
public class Raycaster : MonoBehaviour
{
public Camera QueryCamera;
public DepthQueryRenderFeature DepthQueryFeature;
Action<float> queryCallback;
Ray queryRay;
float queryCosAngle;
private void Start()
{
queryCallback = QueryCallback;
}
void Update()
{
if (Input.GetMouseButtonUp(0))
{
//Get the world ray from camera origin
queryRay = QueryCamera.ScreenPointToRay(Input.mousePosition);
queryRay.origin = QueryCamera.transform.position; //use camera origin instead of near clip plane because the query will return distance from origin plane
//Compute the ratio between query distance (from origin plane) and real world distance from camera origin
queryCosAngle = Vector3.Dot(queryRay.direction, QueryCamera.transform.forward);
//Start depth query
DepthQueryFeature.Query(
new Vector2(Input.mousePosition.x / Screen.width - 0.5f, Input.mousePosition.y / Screen.height - 0.5f),
queryCallback
);
}
}
private void QueryCallback(float depth)
{
var worldPosition = queryRay.origin + queryRay.direction * depth / queryCosAngle;
CreateSphere(worldPosition);
}
private static void CreateSphere(Vector3 worldPosition)
{
var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.transform.position = worldPosition;
go.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
}
}