Skip to content

Commit

Permalink
feat(VR): enable SSR (#502)
Browse files Browse the repository at this point in the history
  • Loading branch information
alandtse authored Sep 26, 2024
1 parent 008eb38 commit 1808780
Show file tree
Hide file tree
Showing 9 changed files with 623 additions and 39 deletions.
46 changes: 40 additions & 6 deletions package/Shaders/Common/FrameBuffer.hlsli
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#ifndef __FRAMEBUFFER_DEPENDENCY_HLSL__
#define __FRAMEBUFFER_DEPENDENCY_HLSL__

cbuffer PerFrame : register(b12)
{
#if !defined(VR)
Expand Down Expand Up @@ -47,26 +50,39 @@ cbuffer PerFrame : register(b12)
#endif // !VR
}

float2 GetDynamicResolutionAdjustedScreenPosition(float2 screenPosition)
float2 GetDynamicResolutionAdjustedScreenPosition(float2 screenPosition, uint stereo = 1)
{
float2 screenPositionDR = DynamicResolutionParams1.xy * screenPosition;
float2 minValue = 0;
float2 maxValue = float2(DynamicResolutionParams2.z, DynamicResolutionParams1.y);
#if defined(VR)
bool isRight = screenPosition.x >= 0.5;
float minFactor = isRight ? 1 : 0;
minValue.x = 0.5 * (DynamicResolutionParams2.z * minFactor);
float maxFactor = isRight ? 2 : 1;
maxValue.x = 0.5 * (DynamicResolutionParams2.z * maxFactor);
// VR sometimes will clamp to stereouv
if (stereo) {
bool isRight = screenPosition.x >= 0.5;
float minFactor = isRight ? 1 : 0;
minValue.x = 0.5 * (DynamicResolutionParams2.z * minFactor);
float maxFactor = isRight ? 2 : 1;
maxValue.x = 0.5 * (DynamicResolutionParams2.z * maxFactor);
}
#endif
return clamp(screenPositionDR, minValue, maxValue);
}

float3 GetDynamicResolutionAdjustedScreenPosition(float3 screenPositionDR, uint stereo = 1)
{
return float3(GetDynamicResolutionAdjustedScreenPosition(screenPositionDR.xy, stereo), screenPositionDR.z);
}

float2 GetDynamicResolutionUnadjustedScreenPosition(float2 screenPositionDR)
{
return screenPositionDR * DynamicResolutionParams2.xy;
}

float3 GetDynamicResolutionUnadjustedScreenPosition(float3 screenPositionDR)
{
return float3(GetDynamicResolutionUnadjustedScreenPosition(screenPositionDR.xy), screenPositionDR.z);
}

float2 GetPreviousDynamicResolutionAdjustedScreenPosition(float2 screenPosition)
{
float2 screenPositionDR = DynamicResolutionParams1.zw * screenPosition;
Expand Down Expand Up @@ -99,3 +115,21 @@ float2 ViewToUV(float3 x, bool is_position = true, uint a_eyeIndex = 0)
float4 uv = mul(CameraProj[a_eyeIndex], newPosition);
return (uv.xy / uv.w) * float2(0.5f, -0.5f) + 0.5f;
}

/**
* @brief Checks if the UV coordinates are outside the frame, considering dynamic resolution if specified.
*
* This function is used to determine whether the provided UV coordinates lie outside the valid range of [0,1].
* If dynamic resolution is enabled, it adjusts the range according to dynamic resolution parameters.
*
* @param[in] uv The UV coordinates to check.
* @param[in] dynamicres Optional flag indicating whether dynamic resolution is applied. Default is false.
* @return True if the UV coordinates are outside the frame, false otherwise.
*/
bool isOutsideFrame(float2 uv, bool dynamicres = false)
{
float2 max = dynamicres ? DynamicResolutionParams1.xy : float2(1, 1);
return any(uv < float2(0, 0) || uv > max.xy);
}

#endif //__FRAMEBUFFER_DEPENDENCY_HLSL__
222 changes: 221 additions & 1 deletion package/Shaders/Common/VR.hlsli
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#ifndef __VR_DEPENDENCY_HLSL__
#define __VR_DEPENDENCY_HLSL__
#ifdef VR
# ifndef COMPUTESHADER
# include "Common\Constants.hlsli"
# include "Common\FrameBuffer.hlsli"
# endif
cbuffer VRValues : register(b13)
{
float AlphaTestRefRS : packoffset(c0);
Expand All @@ -25,13 +29,26 @@ float2 ConvertToStereoUV(float2 uv, uint a_eyeIndex, uint a_invertY = 0)
{
#ifdef VR
// convert [0,1] to eye specific [0,.5] and [.5, 1] dependent on a_eyeIndex
uv.x = saturate(uv.x);
uv.x = (uv.x + (float)a_eyeIndex) / 2;
if (a_invertY)
uv.y = 1 - uv.y;
#endif
return uv;
}

float3 ConvertToStereoUV(float3 uv, uint a_eyeIndex, uint a_invertY = 0)
{
uv.xy = ConvertToStereoUV(uv.xy, a_eyeIndex, a_invertY);
return uv;
}

float4 ConvertToStereoUV(float4 uv, uint a_eyeIndex, uint a_invertY = 0)
{
uv.xy = ConvertToStereoUV(uv.xy, a_eyeIndex, a_invertY);
return uv;
}

/**
Converts from eye specific uv to general uv [0,1].
In VR, texture buffers include the left and right eye in the same buffer.
Expand All @@ -53,6 +70,18 @@ float2 ConvertFromStereoUV(float2 uv, uint a_eyeIndex, uint a_invertY = 0)
return uv;
}

float3 ConvertFromStereoUV(float3 uv, uint a_eyeIndex, uint a_invertY = 0)
{
uv.xy = ConvertFromStereoUV(uv.xy, a_eyeIndex, a_invertY);
return uv;
}

float4 ConvertFromStereoUV(float4 uv, uint a_eyeIndex, uint a_invertY = 0)
{
uv.xy = ConvertFromStereoUV(uv.xy, a_eyeIndex, a_invertY);
return uv;
}

/**
Converts to the eye specific screenposition [0,Resolution].
In VR, texture buffers include the left and right eye in the same buffer. Flat only has a single camera for the entire width.
Expand All @@ -66,7 +95,22 @@ This returns the adjusted value
float2 ConvertToStereoSP(float2 screenPosition, uint a_eyeIndex, float2 a_resolution)
{
screenPosition.x /= a_resolution.x;
return ConvertToStereoUV(screenPosition, a_eyeIndex) * a_resolution;
float2 stereoUV = ConvertToStereoUV(screenPosition, a_eyeIndex);
return stereoUV * a_resolution;
}

float3 ConvertToStereoSP(float3 screenPosition, uint a_eyeIndex, float2 a_resolution)
{
float2 xy = screenPosition.xy / a_resolution;
xy = ConvertToStereoUV(xy, a_eyeIndex);
return float3(xy * a_resolution, screenPosition.z);
}

float4 ConvertToStereoSP(float4 screenPosition, uint a_eyeIndex, float2 a_resolution)
{
float2 xy = screenPosition.xy / a_resolution;
xy = ConvertToStereoUV(xy, a_eyeIndex);
return float4(xy * a_resolution, screenPosition.zw);
}

#ifdef PSHADER
Expand Down Expand Up @@ -102,6 +146,182 @@ uint GetEyeIndexFromTexCoord(float2 texCoord)
return 0;
}

/**
* @brief Converts mono UV coordinates from one eye to the corresponding mono UV coordinates of the other eye.
*
* This function is used to transition UV coordinates from one eye's perspective to the other eye in a stereo rendering setup.
* It works by converting the mono UV to clip space, transforming it into view space, and then reprojecting it into the other eye's
* clip space before converting back to UV coordinates. It also supports dynamic resolution.
*
* @param[in] monoUV The UV coordinates and depth value (Z component) for the current eye, in the range [0,1].
* @param[in] eyeIndex Index of the source/current eye (0 or 1).
* @param[in] dynamicres Optional flag indicating whether dynamic resolution is applied. Default is false.
* @return UV coordinates adjusted to the other eye, with depth.
*/
float3 ConvertMonoUVToOtherEye(float3 monoUV, uint eyeIndex, bool dynamicres = false)
{
// Convert from dynamic res to true UV space
if (dynamicres)
monoUV.xy *= DynamicResolutionParams2.xy;

// Step 1: Convert UV to Clip Space
float4 clipPos = float4(monoUV.xy * float2(2, -2) - float2(1, -1), monoUV.z, 1);

// Step 2: Convert Clip Space to View Space for the current eye
float4 viewPosCurrentEye = mul(CameraProjInverse[eyeIndex], clipPos);
viewPosCurrentEye /= viewPosCurrentEye.w;

// Step 3: Convert View Space to Clip Space for the other eye
float4 clipPosOtherEye = mul(CameraProj[1 - eyeIndex], viewPosCurrentEye);
clipPosOtherEye /= clipPosOtherEye.w;

// Step 4: Convert Clip Space to UV
float3 monoUVOtherEye = float3((clipPosOtherEye.xy * 0.5f) + 0.5f, clipPosOtherEye.z);

// Convert back to dynamic res space if necessary
if (dynamicres)
monoUVOtherEye.xy *= DynamicResolutionParams1.xy;

return monoUVOtherEye;
}

/**
* @brief Adjusts UV coordinates for VR stereo rendering when transitioning between eyes or handling boundary conditions.
*
* This function is used in raymarching to check the next UV coordinate. It checks if the current UV coordinates are outside
* the frame. If so, it transitions the UV coordinates to the other eye and adjusts them if they are within the frame of the other eye.
* If the UV coordinates are outside the frame of both eyes, it returns the adjusted UV coordinates for the current eye.
*
* The function ensures that the UV coordinates are correctly adjusted for stereo rendering, taking into account boundary conditions
* and preserving accurate reflections.
* Based on concepts from https://cuteloong.github.io/publications/scssr24/
* Wu, X., Xu, Y., & Wang, L. (2024). Stereo-consistent Screen Space Reflection. Computer Graphics Forum, 43(4).
*
* We do not have a backface depth so we may be ray marching even though the ray is in an object.
* @param[in] monoUV Current UV coordinates with depth information, [0-1]. Must not be dynamic resolution adjusted.
* @param[in] eyeIndex Index of the current eye (0 or 1).
* @param[out] fromOtherEye Boolean indicating if the result UV coordinates are from the other eye.
*
* @return Adjusted UV coordinates for stereo rendering, [0-1]. Must be dynamic resolution adjusted later.
*/
float3 ConvertStereoRayMarchUV(float3 monoUV, uint eyeIndex, out bool fromOtherEye)
{
fromOtherEye = false;
float3 resultUV = monoUV;
#ifdef VR
// Check if the UV coordinates are outside the frame
if (isOutsideFrame(resultUV.xy, false)) {
// Transition to the other eye
float3 otherEyeUV = ConvertMonoUVToOtherEye(resultUV, eyeIndex);

// Check if the other eye's UV coordinates are within the frame
if (!isOutsideFrame(otherEyeUV.xy, false)) {
resultUV = ConvertToStereoUV(otherEyeUV, 1 - eyeIndex);
fromOtherEye = true; // Indicate that the result is from the other eye
}
} else {
resultUV = ConvertToStereoUV(resultUV, eyeIndex);
}
#endif
return resultUV;
}

/**
* @brief Converts stereo UV coordinates from one eye to the corresponding stereo UV coordinates of the other eye.
*
* This function is used to transition UV coordinates from one eye's perspective to the other eye in a stereo rendering setup.
* It works by converting the stereo UV to mono UV, then to clip space, transforming it into view space, and then reprojecting it into the other eye's
* clip space before converting back to stereo UV coordinates. It also supports dynamic resolution.
*
* @param[in] stereoUV The UV coordinates and depth value (Z component) for the current eye, in the range [0,1].
* @param[in] eyeIndex Index of the current eye (0 or 1).
* @param[in] dynamicres Optional flag indicating whether dynamic resolution is applied. Default is false.
* @return UV coordinates adjusted to the other eye, with depth.
*/
float3 ConvertStereoUVToOtherEyeStereoUV(float3 stereoUV, uint eyeIndex, bool dynamicres = false)
{
// Convert from dynamic res to true UV space
if (dynamicres)
stereoUV.xy *= DynamicResolutionParams2.xy;

stereoUV.xy = ConvertFromStereoUV(stereoUV.xy, eyeIndex, true); // for some reason, the uv.y needs to be inverted before conversion?
// Swap eyes
stereoUV.xyz = ConvertMonoUVToOtherEye(stereoUV.xyz, eyeIndex);

stereoUV.xy = ConvertToStereoUV(stereoUV.xy, 1 - eyeIndex, false);

// Convert back to dynamic res space if necessary
if (dynamicres)
stereoUV.xy *= DynamicResolutionParams1.xy;
return stereoUV;
}

/**
* @brief Checks if the color is non zero by testing if the color is greater than 0 by epsilon.
*
* This function check is a color is non black. It uses a small epsilon value to allow for
* floating point imprecision.
*
* For screen-space reflection (SSR), this acts as a mask and checks for an invalid reflection by
* checking if the reflection color is essentially black (close to zero).
*
* @param[in] ssrColor The color to check.
* @param[in] epsilon Small tolerance value used to determine if the color is close to zero.
* @return True if color is non zero, otherwise false.
*/
bool IsNonZeroColor(float4 ssrColor, float epsilon = 0.001)
{
return dot(ssrColor.xyz, ssrColor.xyz) > epsilon * epsilon;
}

/**
* @brief Blends color data from two eyes based on their UV coordinates and validity.
*
* This function checks the validity of the colors based on their UV coordinates and
* alpha values. It blends the colors while ensuring proper handling of transparency.
*
* @param uv1 UV coordinates for the first eye.
* @param color1 Color from the first eye.
* @param uv2 UV coordinates for the second eye.
* @param color2 Color from the second eye.
* @param dynamicres Whether the uvs have dynamic resolution applied
* @return Blended color, including the maximum alpha from both inputs.
*/
float4 BlendEyeColors(
float3 uv1,
float4 color1,
float3 uv2,
float4 color2,
bool dynamicres = false)
{
// Check validity for color1
bool validColor1 = IsNonZeroColor(color1) && !isOutsideFrame(uv1.xy, dynamicres);
// Check validity for color2
bool validColor2 = IsNonZeroColor(color2) && !isOutsideFrame(uv2.xy, dynamicres);

// Calculate alpha values
float alpha1 = validColor1 ? color1.a : 0.0f;
float alpha2 = validColor2 ? color2.a : 0.0f;

// Total alpha
float totalAlpha = alpha1 + alpha2;

// Blend based on higher alpha
float4 blendedColor = (validColor1 ? color1 * (alpha1 / max(totalAlpha, 1e-5)) : float4(0, 0, 0, 0)) +
(validColor2 ? color2 * (alpha2 / max(totalAlpha, 1e-5)) : float4(0, 0, 0, 0));

// Final alpha determination
blendedColor.a = max(color1.a, color2.a);

return blendedColor;
}

float4 BlendEyeColors(float2 uv1, float4 color1, float2 uv2, float4 color2, bool dynamicres = false)
{
return BlendEyeColors(float3(uv1, 0), color1, float3(uv2, 0), color2, dynamicres);
}

struct VR_OUTPUT
{
float4 VRPosition;
Expand Down
Loading

0 comments on commit 1808780

Please sign in to comment.