Skip to content

Commit 79569c6

Browse files
qgu-unityEvergreen
authored andcommitted
[Backport] [6000.5] Fix Quad Views post-processing UV mapping for on-tile and regular PP
1 parent 440f10d commit 79569c6

9 files changed

Lines changed: 271 additions & 26 deletions

File tree

Packages/com.unity.render-pipelines.core/Runtime/XR/XRLayout.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ public class XRLayout
1010
{
1111
readonly List<(Camera, XRPass)> m_ActivePasses = new List<(Camera, XRPass)>();
1212

13+
/// <summary>
14+
/// State container for Quad View rendering, used to cache data across render passes.
15+
/// </summary>
16+
public QuadViewState quadView;
17+
18+
/// <summary>
19+
/// Contains cached state for Quad View XR rendering.
20+
/// Quad View is an XR rendering mode where peripheral (outer) and foveal (inner) views are rendered separately.
21+
/// </summary>
22+
public struct QuadViewState
23+
{
24+
/// <summary>
25+
/// Cached vignette center from the peripheral (outer) view pass.
26+
/// Used by the inner view pass to ensure vignette effect is consistent across both views.
27+
/// </summary>
28+
public Vector4 cachedPeripheralVignetteCenter;
29+
}
30+
1331
/// <summary>
1432
/// Configure the layout to render from the specified camera by generating passes from the the connected XR device.
1533
/// </summary>
@@ -76,6 +94,7 @@ internal void Clear()
7694
}
7795

7896
m_ActivePasses.Clear();
97+
quadView = default;
7998
}
8099

81100
internal void LogDebugInfo()

Packages/com.unity.render-pipelines.core/Runtime/XR/XRLayoutStack.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public XRLayout New()
1515
return layout;
1616
}
1717

18+
public bool hasLayout => m_Stack.Count > 0;
19+
1820
public XRLayout top => m_Stack.Peek();
1921

2022
public void Release()

Packages/com.unity.render-pipelines.core/Runtime/XR/XRPass.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public struct XRPassCreateInfo
2626
internal bool hasMotionVectorPass;
2727
internal bool spaceWarpRightHandedNDC;
2828
internal bool isLastCameraPass;
29+
internal Vector4 uvScales;
30+
internal Vector4 uvOffsets;
2931

3032
#if ENABLE_VR && ENABLE_XR_MODULE
3133
internal UnityEngine.XR.XRDisplaySubsystem.XRRenderPass xrSdkRenderPass;
@@ -54,6 +56,8 @@ public XRPass()
5456
m_OcclusionMesh = new XROcclusionMesh(this);
5557
m_VisibleMesh = new XRVisibleMesh(this);
5658
isLastCameraPass = true; // default to last camera pass when creating from default constructor
59+
uvScales = Vector4.one;
60+
uvOffsets = Vector4.zero;
5761
}
5862

5963
/// <summary>
@@ -137,6 +141,32 @@ public bool supportsFoveatedRendering
137141
/// </summary>
138142
public bool isLastCameraPass { get; private set; }
139143

144+
/// <summary>
145+
/// The scale factors used to map the current view's UV coordinates to the
146+
/// peripheral (outset) view's UV space during post-processing.
147+
/// xy = left eye (scale x, scale y), zw = right eye (scale x, scale y).
148+
/// </summary>
149+
/// <remarks>
150+
/// Used in Quad View foveated rendering where the high-resolution inner patch
151+
/// occupies a specific sub-region of the physical eye buffer.
152+
/// Formula: mappedUV = uv * uvScales + uvOffsets
153+
/// Only applicable to the inner pass; outer pass uses (1,1,1,1).
154+
/// </remarks>
155+
public Vector4 uvScales { get; private set; }
156+
157+
/// <summary>
158+
/// The translation offsets used to map the current view's UV coordinates to the
159+
/// peripheral (outset) view's UV space during post-processing.
160+
/// xy = left eye (offset x, offset y), zw = right eye (offset x, offset y).
161+
/// </summary>
162+
/// <remarks>
163+
/// Used in Quad View foveated rendering where the high-resolution inner patch
164+
/// occupies a specific sub-region of the physical eye buffer.
165+
/// Formula: mappedUV = uv * uvScales + uvOffsets
166+
/// Only applicable to the inner pass; outer pass uses (0,0,0,0).
167+
/// </remarks>
168+
public Vector4 uvOffsets { get; private set; }
169+
140170
/// <summary>
141171
/// Index of the pass inside the frame.
142172
/// </summary>
@@ -558,6 +588,8 @@ public void InitBase(XRPassCreateInfo createInfo)
558588
occlusionMeshScale = createInfo.occlusionMeshScale;
559589
foveatedRenderingInfo = createInfo.foveatedRenderingInfo;
560590
isLastCameraPass = createInfo.isLastCameraPass;
591+
uvScales = createInfo.uvScales;
592+
uvOffsets = createInfo.uvOffsets;
561593
}
562594

563595
internal void AddView(XRView xrView)

Packages/com.unity.render-pipelines.core/Runtime/XR/XRSystem.cs

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@ public static XRLayout NewLayout()
321321
return s_Layout.New();
322322
}
323323

324+
/// <summary>
325+
/// Returns the current active XR layout, or null if none is active.
326+
/// Used by render passes to access cross-camera XR state like Quad View parameters.
327+
/// </summary>
328+
public static XRLayout currentLayout => s_Layout.hasLayout ? s_Layout.top : null;
329+
324330
/// <summary>
325331
/// Used by the render pipeline to complete the XR layout at the end of the frame.
326332
/// </summary>
@@ -430,15 +436,77 @@ void AddViewToPass(XRPass xrPass, XRDisplaySubsystem.XRRenderPass renderPass, in
430436
xrPass.AddView(BuildView(renderPass, renderParam));
431437
}
432438

433-
for (int renderPassIndex = 0; renderPassIndex < s_Display.GetRenderPassCount(); ++renderPassIndex)
439+
// Helper: extract frustum extents from projection matrix.
440+
// Returns Vector4(width, height, left, top) at unit depth.
441+
static Vector4 ExtractFrustumBoundsFromProjection(Matrix4x4 proj)
442+
{
443+
float width = 2.0f / proj[0, 0];
444+
float height = 2.0f / proj[1, 1];
445+
float left = (proj[0, 2] - 1.0f) / proj[0, 0];
446+
float top = (1 - proj[1, 2]) / proj[1, 1];
447+
448+
return new Vector4(width, height, left, top);
449+
}
450+
451+
Vector4 ExtractViewBounds(XRDisplaySubsystem.XRRenderPass renderPass, int renderParamIndex)
452+
{
453+
renderPass.GetRenderParameter(camera, renderParamIndex, out var renderParam);
454+
var proj = renderParam.projection;
455+
return ExtractFrustumBoundsFromProjection(proj);
456+
}
457+
458+
int renderPassCount = s_Display.GetRenderPassCount();
459+
460+
// Pre-calculate view bounds for quad view (2 passes × 2 views) using fixed-size stack variables
461+
// This avoids List allocations that would cause GC pressure every frame
462+
Vector4 pass0View0Bounds = default, pass0View1Bounds = default;
463+
Vector4 pass1View0Bounds = default, pass1View1Bounds = default;
464+
bool isQuadViewSetup = renderPassCount == 2;
465+
if (isQuadViewSetup)
466+
{
467+
s_Display.GetRenderPass(0, out var pass0);
468+
s_Display.GetRenderPass(1, out var pass1);
469+
if (pass0.GetRenderParameterCount() >= 2 && pass1.GetRenderParameterCount() >= 2)
470+
{
471+
pass0View0Bounds = ExtractViewBounds(pass0, 0);
472+
pass0View1Bounds = ExtractViewBounds(pass0, 1);
473+
pass1View0Bounds = ExtractViewBounds(pass1, 0);
474+
pass1View1Bounds = ExtractViewBounds(pass1, 1);
475+
}
476+
else
477+
{
478+
isQuadViewSetup = false;
479+
}
480+
}
481+
482+
for (int renderPassIndex = 0; renderPassIndex < renderPassCount; ++renderPassIndex)
434483
{
435484
s_Display.GetRenderPass(renderPassIndex, out var renderPass);
436485
s_Display.GetCullingParameters(camera, renderPass.cullingPassIndex, out var cullingParams);
437486

438487
int renderParameterCount = renderPass.GetRenderParameterCount();
488+
bool isLastPass = renderPassIndex == renderPassCount - 1;
489+
// This parameter makes sure we are in 2 pass quad view's second pass, which is the only case we need to apply special UV scale and offset.
490+
bool isQuadViewLastPass = isLastPass && isQuadViewSetup;
491+
Vector4 uvScales = Vector4.one;
492+
Vector4 uvOffsets = Vector4.zero;
493+
if (isQuadViewLastPass)
494+
{
495+
// Calculate UV scales and offsets from pre-computed view bounds
496+
uvScales.x = pass1View0Bounds.x / pass0View0Bounds.x;
497+
uvScales.y = pass1View0Bounds.y / pass0View0Bounds.y;
498+
uvScales.z = pass1View1Bounds.x / pass0View1Bounds.x;
499+
uvScales.w = pass1View1Bounds.y / pass0View1Bounds.y;
500+
501+
uvOffsets.x = (pass1View0Bounds.z - pass0View0Bounds.z) / pass0View0Bounds.x;
502+
uvOffsets.y = -(pass1View0Bounds.w - pass0View0Bounds.w) / pass0View0Bounds.y;
503+
uvOffsets.z = (pass1View1Bounds.z - pass0View1Bounds.z) / pass0View1Bounds.x;
504+
uvOffsets.w = -(pass1View1Bounds.w - pass0View1Bounds.w) / pass0View1Bounds.y;
505+
}
506+
439507
if (CanUseSinglePass(camera, renderPass))
440508
{
441-
var createInfo = BuildPass(renderPass, cullingParams, layout, renderPassIndex == s_Display.GetRenderPassCount() - 1);
509+
var createInfo = BuildPass(renderPass, cullingParams, layout, renderPassIndex == s_Display.GetRenderPassCount() - 1, uvScales, uvOffsets);
442510
var xrPass = s_PassAllocator(createInfo);
443511

444512
for (int renderParamIndex = 0; renderParamIndex < renderParameterCount; ++renderParamIndex)
@@ -452,7 +520,7 @@ void AddViewToPass(XRPass xrPass, XRDisplaySubsystem.XRRenderPass renderPass, in
452520
{
453521
for (int renderParamIndex = 0; renderParamIndex < renderParameterCount; ++renderParamIndex)
454522
{
455-
var createInfo = BuildPass(renderPass, cullingParams, layout, renderPassIndex == s_Display.GetRenderPassCount() - 1);
523+
var createInfo = BuildPass(renderPass, cullingParams, layout, renderPassIndex == s_Display.GetRenderPassCount() - 1, uvScales, uvOffsets);
456524
var xrPass = s_PassAllocator(createInfo);
457525
AddViewToPass(xrPass, renderPass, renderParamIndex);
458526
layout.AddPass(camera, xrPass);
@@ -515,7 +583,6 @@ static XRView BuildView(XRDisplaySubsystem.XRRenderPass renderPass, XRDisplaySub
515583
{
516584
// Convert viewport from normalized to screen space
517585
Rect viewport = renderParameter.viewport;
518-
519586
viewport.x *= renderPass.renderTargetScaledWidth;
520587
viewport.width *= renderPass.renderTargetScaledWidth;
521588
viewport.y *= renderPass.renderTargetScaledHeight;
@@ -542,8 +609,8 @@ private static RenderTextureDescriptor XrRenderTextureDescToUnityRenderTextureDe
542609
return rtDesc;
543610
}
544611

545-
static XRPassCreateInfo BuildPass(XRDisplaySubsystem.XRRenderPass xrRenderPass, ScriptableCullingParameters cullingParameters, XRLayout layout, bool isLastPass)
546-
{
612+
static XRPassCreateInfo BuildPass(XRDisplaySubsystem.XRRenderPass xrRenderPass, ScriptableCullingParameters cullingParameters, XRLayout layout, bool isLastPass, Vector4 uvScales, Vector4 uvOffsets)
613+
{
547614
XRPassCreateInfo passInfo = new XRPassCreateInfo
548615
{
549616
renderTarget = xrRenderPass.renderTarget,
@@ -562,7 +629,9 @@ static XRPassCreateInfo BuildPass(XRDisplaySubsystem.XRRenderPass xrRenderPass,
562629
copyDepth = xrRenderPass.shouldFillOutDepth,
563630
spaceWarpRightHandedNDC = xrRenderPass.spaceWarpRightHandedNDC,
564631
xrSdkRenderPass = xrRenderPass,
565-
isLastCameraPass = isLastPass
632+
isLastCameraPass = isLastPass,
633+
uvScales = uvScales,
634+
uvOffsets = uvOffsets
566635
};
567636

568637
return passInfo;

Packages/com.unity.render-pipelines.universal/Runtime/Passes/PostProcess/UberPostProcessPass.cs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using UnityEngine.Experimental.Rendering;
23
using UnityEngine.Rendering.RenderGraphModule;
34
using System.Runtime.CompilerServices; // AggressiveInlining
45

@@ -225,7 +226,7 @@ public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer
225226
if(data.chromaticAberration.IsActive())
226227
data.chromaticAberration.Apply(material);
227228

228-
data.vignette.Apply(material, cameraData.xr);
229+
data.vignette.Apply(material);
229230

230231
if(data.filmGrain.IsActive())
231232
data.filmGrain.Apply(material);
@@ -262,8 +263,14 @@ public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer
262263
RenderingUtils.SetupOffscreenUIViewportParams(material, ref cameraData.pixelRect, data.isFinalPass && cameraData.resolveFinalTarget);
263264
}
264265

265-
// Done with Uber, blit it
266266
#if ENABLE_VR && ENABLE_XR_MODULE
267+
// Setup XR UV remapping for Quad View (used by all screen-space effects)
268+
if (cameraData.xr != null && cameraData.xr.enabled && cameraData.xr.singlePassEnabled)
269+
{
270+
PostProcessUtils.SetupXRUVRemapping(material, cameraData.xr);
271+
}
272+
273+
// Done with Uber, blit it
267274
if (cameraData.xr.enabled && cameraData.xr.hasValidVisibleMesh)
268275
PostProcessUtils.ScaleViewportAndDrawVisibilityMesh(context, data.sourceTexture, data.destinationTexture, data.cameraData, material, data.isFinalPass);
269276
else
@@ -510,24 +517,51 @@ internal struct VignetteParams
510517
{
511518
public Vector4 vignetteParams1;
512519
public Vector4 vignetteParams2;
520+
#if ENABLE_VR && ENABLE_XR_MODULE
521+
public Vector4 vignetteXRCenter;
522+
public bool hasXRCenter;
523+
#endif
524+
513525

514526
[MethodImpl(MethodImplOptions.AggressiveInlining)]
515527
public void Setup(Vignette vignette, int width, int height, Experimental.Rendering.XRPass xrPass)
516528
{
517529
CalcVignetteParams(vignette, width, height, xrPass, out vignetteParams1, out vignetteParams2);
530+
#if ENABLE_VR && ENABLE_XR_MODULE
531+
hasXRCenter = false;
532+
if (xrPass != null && xrPass.enabled && xrPass.singlePassEnabled)
533+
{
534+
hasXRCenter = true;
535+
Vector2 center = vignetteParams2;
536+
var xrLayout = XRSystem.currentLayout;
537+
538+
if (xrLayout != null && xrPass.viewCount > 1 && xrPass.multipassId == 1 && xrPass.isLastCameraPass)
539+
{
540+
// Second pass (inner views): Reuse the cached peripheral vignette center
541+
// This ensures vignette is calculated in the outer UV space after remapping
542+
vignetteXRCenter = xrLayout.quadView.cachedPeripheralVignetteCenter;
543+
}
544+
else
545+
{
546+
// First pass (peripheral/outer views): Calculate and cache the vignette center
547+
vignetteXRCenter = xrPass.ApplyXRViewCenterOffset(center);
548+
if (xrLayout != null)
549+
xrLayout.quadView.cachedPeripheralVignetteCenter = vignetteXRCenter;
550+
}
551+
}
552+
#endif
518553
}
519554

520555
[MethodImpl(MethodImplOptions.AggressiveInlining)]
521-
public void Apply(Material material, Experimental.Rendering.XRPass xrPass)
556+
public void Apply(Material material)
522557
{
523558
material.SetVector(ShaderConstants._Vignette_Params1, vignetteParams1);
524559
material.SetVector(ShaderConstants._Vignette_Params2, vignetteParams2);
525560

526561
#if ENABLE_VR && ENABLE_XR_MODULE
527-
if (xrPass != null && xrPass.enabled && xrPass.singlePassEnabled)
562+
if (hasXRCenter)
528563
{
529-
Vector2 center = vignetteParams2;
530-
material.SetVector(ShaderConstants._Vignette_ParamsXR, xrPass.ApplyXRViewCenterOffset(center));
564+
material.SetVector(ShaderConstants._Vignette_ParamsXR, vignetteXRCenter);
531565
}
532566
#endif
533567
}
@@ -546,6 +580,11 @@ static public void CalcVignetteParams(Vignette vignette, int width, int height,
546580
// center since the version of the shader that is not single-pass will use the value in _Vignette_Params2
547581
center = xrPass.ApplyXRViewCenterOffset(center);
548582
}
583+
if (xrPass != null && xrPass.singlePassEnabled && xrPass.viewCount > 1 && xrPass.multipassId == 1 && xrPass.isLastCameraPass)
584+
{
585+
// In quad view we need to also apply the aspect ratio correction to the vignette as the UV remapping will cause it to be stretched/squashed if not corrected
586+
aspectRatio *= xrPass.uvScales.y / xrPass.uvScales.x;
587+
}
549588
#endif
550589

551590
vignetteParams1 = new Vector4(

Packages/com.unity.render-pipelines.universal/Runtime/PostProcessUtils.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,21 @@ internal static void SetupHDROutput(Material material, HDROutputUtils.HDRDisplay
271271
}
272272
#endregion
273273

274+
#if ENABLE_VR && ENABLE_XR_MODULE
275+
/// <summary>
276+
/// Configures UV remapping for Quad View multi-resolution rendering in XR.
277+
/// Sets shader parameters for screen-space effects to align with inner view coordinates.
278+
/// </summary>
279+
/// <param name="material">The material to configure.</param>
280+
/// <param name="xrPass">The XR pass containing UV scale/offset parameters.</param>
281+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
282+
internal static void SetupXRUVRemapping(Material material, XRPass xrPass)
283+
{
284+
material.SetVector(ShaderConstants._Quad_View_Uv_Remap_scalesXR, xrPass.uvScales);
285+
material.SetVector(ShaderConstants._Quad_View_Uv_Remap_offsetsXR, xrPass.uvOffsets);
286+
}
287+
#endif
288+
274289
#region Blit
275290

276291
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -395,6 +410,9 @@ static class ShaderConstants
395410
public static readonly int _BlueNoise_Texture = Shader.PropertyToID("_BlueNoise_Texture");
396411
public static readonly int _Dithering_Params = Shader.PropertyToID("_Dithering_Params");
397412

413+
public static readonly int _Quad_View_Uv_Remap_scalesXR = Shader.PropertyToID("_Quad_View_Uv_Remap_scalesXR");
414+
public static readonly int _Quad_View_Uv_Remap_offsetsXR = Shader.PropertyToID("_Quad_View_Uv_Remap_offsetsXR");
415+
398416
public static readonly int _SourceSize = Shader.PropertyToID("_SourceSize");
399417
}
400418
}

0 commit comments

Comments
 (0)