CustomPass using different camera: how to render MotionVectors, and depth with Tessellated objects?

First, I wanted to thank @antoinel_unity for the extremely helpful CustomPass examples repository. It’s been very useful as I’ve been learning the CustomPass and HDRP APIs.

I’m currently using 2019.3.9f1 with HDRP 7.3.1.

I’ve been trying to work on an effect extending from the repository’s depth capture example, where I’d like to capture the depth buffer and motion vectors for a separate camera that’s not being rendered to screen. I’m running into the following roadblocks:

  • When a MeshRenderer is using tessellation, such as HDRP’s LayeredLitTessellation Material, the Frame Debug window shows that object being is being rendered during the CustomPass, using the DepthOnly pass, but the depth texture is unchanged. The INSTANCING_ON keyword is set, and settings in the Frame Debug window seem similar to those in the standard DepthPrepassDeferredForDecals pass, which of course correctly captures the depth.
    Note that using a Lit override material when setting up the RendererListDesc will cause the object to be rendered correctly, though the depth will not match that of the true tessellated version. Additionally, I can reproduce the same behaviour in the repository’s depth capture example.
  • I’m trying to render MotionVectors from the same camera, but calling HDUtils.DrawRendererList doesn’t cause any objects to be rendered (even using Lit material, irrespective of tessellation).
    MeshRenderers have Motion Vectors set to Per Object Motion.

I’ll try to summarize the relevant code snippets here.

For the camera setup, I ensure the depthTextureMode is set, though this doesn’t seem to make a difference.

_bakingCamera.depthTextureMode |= DepthTextureMode.Depth | DepthTextureMode.MotionVectors;
For the depth pass, setup is the same as the repository’s example:

_bakingCamera.TryGetCullingParameters(out ScriptableCullingParameters cullingParams);
cullingParams.cullingOptions = CullingOptions.ShadowCasters;
cullingParams.cullingMask = (uint)_layerMask.value;
cullingResult = renderContext.Cull(ref cullingParams);

The same is true to the capture during Execute:

var heightRendererListDesc = new RendererListDesc(_depthShaderTags, cullingResult, _bakingCamera)
{
    rendererConfiguration = PerObjectData.None,
    renderQueueRange = RenderQueueRange.all,
    sortingCriteria = SortingCriteria.BackToFront,
    excludeObjectMotionVectors = false,
    stateBlock = new RenderStateBlock(RenderStateMask.Raster) { rasterState = new RasterState(depthClip: false) },
};
CoreUtils.SetRenderTarget(cmd, _depthBakeBuffer, ClearFlag.Depth);
HDUtils.DrawRendererList(renderContext, cmd, RendererList.Create(heightRendererListDesc));

For the motion vectors pass, I tried to use the same GraphicsFormat (RG16) that it seems Unity normally uses:

motionVectorsBuffer = RTHandles.Alloc(_depthCaptureTexture.width, _depthCaptureTexture.height, colorFormat: GraphicsFormat.R16G16_SFloat, depthBufferBits: DepthBits.None, dimension: TextureDimension.Tex2D, name: "Motion Vectors");

_motionVectorsShaderTags = new ShaderTagId[2]
{
    new ShaderTagId("MotionVectors"),
    new ShaderTagId("Motion Vectors"),
};

Then, during Execute:

var motionVectorRendererListDesc = new RendererListDesc(_motionVectorsShaderTags, cullingResult, _bakingCamera)
{
    rendererConfiguration = PerObjectData.MotionVectors,
    renderQueueRange = RenderQueueRange.all,
    sortingCriteria = SortingCriteria.BackToFront,
    excludeObjectMotionVectors = false,
};

CoreUtils.SetRenderTarget(cmd, _motionVectorsBuffer, ClearFlag.All);
RendererList motionVectorsRendererList = RendererList.Create(motionVectorRendererListDesc);
HDUtils.DrawRendererList(renderContext, cmd, motionVectorsRendererList);

The cullingResult is the same as for the depth pass, though no objects are rendered, even the ones that appear in the depth pass.

Any help or advice would be greatly appreciated!

An update on my investigations on capturing motion vectors from a different camera in a CustomPass: it seems that the MOTIONVECTORS pass is disabled for a normal MeshRenderer, even if Motion Vectors is set to Per Object Motion and the object is moving (eg. by setting transform.position each frame). Enabling ‘Add Precomputed Velocity’ (even though we have so such velocity to supply) in the MeshRenderer’s Material seems to enable the pass again, and HDUtils.DrawRendererList does indeed perform a pass for such objects if using the MotionVectors shader tags in the RendererListDesc.

However, it seems as though the per object motion vector data is not being set in this pass. Particularly, the following shader properties show up in the pass, but the entries are all zero: unity_MotionVectorsParams, _WorldSpaceCameraPos, and unity_MatrixPreviousM. Now, _WorldSpaceCameraPos can be set at the command buffer level, which is also how we’d need to set _PrevViewProjMatrix and _NonJitteredViewProjMatrix (in my case, I know that the different camera doesn’t move, so we can re-use its current view-projection matrix). As a result, no motion is rendered in this pass.

Does anyone know why unity_MotionVectorsParams and unity_MatrixPreviousM would not be set, even though rendererConfiguration = PerObjectData.MotionVectors is being set in the RendererListDesc?

Thanks for reading, and any input would be greatly appreciated!

Yes, I think this is ignored in SRPs because we allocate our own buffers and the camera doesn’t own them.

Otherwise, I don’t think you’re missing anything to render your motion vectors. I made a quick test custom pass and it worked for me (it’s here if you want to take a look: https://github.com/alelievr/HDRP-Custom-Passes/blob/master/README.md#render-object-motion-vectors)

Thank you for putting this together! I’ve installed Unity 2020.3.6f1 and HDRP 10.4 and can confirm that motion vectors are indeed captured. Downgrading the example to 2019.3.9f1 with HDRP 7.3.1 doesn’t work; objects are not picked up by the DrawRendererList call. Nonetheless, I should be able to update to 2020.3.

However, I have run into issues trying to bake motion vectors for a different (non-rendered) Camera. The main problem is that CustomPassUtils.RenderFromCamera won’t work for motion vectors. From trial and error, it seems that the ShaderTagId used in the RendererListDesc for CoreUtils.DrawRendererList is necessary; otherwise, the pass seems to cycle from frame to frame between rendering all moving objects, and all other objects.

Unfortunately, DrawRendererList doesn’t work nicely when trying to bake from another camera. I’ve found that HDRenderPipeline.OverrideCameraRendering will let me set up the baking camera parameters. However, that struct is currently internal. Also, it causes the baking camera’s aspect ratio to be changed to match the main camera, which is incorrect for my use case (the baking camera’s position, aspect ratio, and field of view are specified ahead of time).

Making a custom version of the HDRP package to work around the current limitations of HDRenderPipeline.OverrideCameraRendering was thankfully straightforward, and allows me to successfully bake motion vectors. Perhaps some API changes could allow for a little more flexibility when using a CustomPass to bake to another camera.

Thank you again for your help. With it, I’m able to move forward with my project.

1 Like

Yes, we plan to expose this class in the future (with a fix for the aspect ratio) to make it easier to override camera settings inside the frame.

This is an useful info, thanks

Update:

I’m creating the PR to expose the OverrideCameraRendering API, you can check it out here: Expose override camera rendering api by alelievr · Pull Request #5016 · Unity-Technologies/Graphics · GitHub

It includes the fix for aspect ratio as well as 4 new functions to render directly the objects into a render texture instead of using RTHandles.

1 Like

Nice! What HDRP version this PR will get into?

For now we’re targetting HDRP 12

hello, when i use built-in pipeline and turn on TAA in unity 2020

it also have a motion vector pass, could be seen on frame debugger.

Does unity write this built-in-motion-vector-pass in c++ code?