Screen Space Reflection Optimization

Started by
1 comment, last by Styves 9 years, 11 months ago

Hi all,

I'm interested working on screen space reflection because it's the future.

Here the actual code I have, learned from a book :


struct PS_INPUT
{
  float4 Position : SV_POSITION;
  float3 Normal   : NORMAL;
  float4 Tangent  : TANGENT;
  float2 TexCoord : TEXCOORD0;
  float4 PosVS    : TEXCOORD1;
};

cbuffer PROJECTION_CBUFFER : register( b0 )
{
  float4x4 Projection;
  float4 PerspectiveValues;
};

cbuffer SSR_CBUFFER : register( b1 )
{
  float ViewAngleThreshold;
  float EdgeDistThreshold;
  float DepthBias;
  float ReflectionScale;
};

Texture2D DiffuseMap : register( t0 );
Texture2D DepthMap : register( t1 );
SamplerState LinearSampler : register( s0 );
SamplerState PointSampler : register( s1 );

float ConvertZToLinearDepth( in float DepthValue )
{
  return PerspectiveValues.z / (DepthValue - PerspectiveValues.w);
}

float3 PosFromDepth( in float2 uv, in float LinearDepth )
{
  return float3( PerspectiveValues.xy * uv * LinearDepth, LinearDepth );
}

static const float PixelSize = 2.0f / 720.0f;
static const int NumSteps = 1280;

float4 main( in PS_INPUT Input ) : SV_TARGET
{
  // Normalize the normal.
  float3 NormalVS = normalize( Input.Normal );
  
  // Compute the camera to pixel direction.
  float3 EyeToPixel = normalize( Input.PosVS.xyz );
  
  // Compute the reflected view direction.
  float3 ReflectVS = reflect( EyeToPixel, NormalVS );
  
  // The initial reflection color for the pixel.
  float4 ReflectColor = float4( 0.0f, 0.0f, 0.0f, 0.0f );
  
  // Check the angle using threshold.
  if( ReflectVS.z >= ViewAngleThreshold )
  {
    // Fade the reflection as the view angles gets close to the threshold.
    float ViewAngleThresholdInv = 1.0f - ViewAngleThreshold;
    float ViewAngleFade = clamp( 3.0f * (ReflectVS.z - ViewAngleThreshold) / ViewAngleThresholdInv, 0.0f, 1.0f );
    
    // Transform the View Space Reflection to clip-space.
    float3 PosReflectVS = Input.PosVS.xyz + ReflectVS;
    float3 PosReflectCS = mul( float4( PosReflectVS, 1.0f ), Projection ).xyz / PosReflectVS.z;
    float3 ReflectCS = PosReflectCS - Input.Position.xyz;
    
    // Resize Screen Space Reflection to an appropriate length.
    float ReflectScale = PixelSize / length( ReflectCS.xy );
    ReflectCS *= ReflectScale;
    
    // Compute the first sampling position in screen-space.
    float2 SampPos = float2( 0.5f, -0.5f ) * (Input.Position.xy + ReflectCS.xy) + 0.5f;
    
    // Find each iteration step in screen-space.
    float2 Step = ReflectCS.xy * float2( 0.5f, -0.5f );
    
    // Build a plane laying on the reflection vector.
    float4 RayPlane;
    float3 Right = cross( EyeToPixel, ReflectVS );
    RayPlane.xyz = normalize( cross( ReflectVS, Right ) );
    RayPlane.w = dot( RayPlane.xyz, Input.PosVS.xyz );
    
    // Iterate over the texture searching for intersection.
    for( int CurStep = 0; CurStep < NumSteps; ++CurStep )
    {
      // Get the current depth.
      float CurDepth = DepthMap.SampleLevel( PointSampler, SampPos, 0.0f ).x;
      
      // Reconstruct the position from depth.
      float CurDepthLin = ConvertZToLinearDepth( CurDepth );
      float3 CurPos = PosFromDepth( Input.Position.xy + ReflectCS.xy * ((float)CurStep + 1.0f), CurDepthLin );
      
      // The intersection happens between two positions on the oposite sides of the plane.
      if( RayPlane.w >= dot( RayPlane.xyz, CurPos ) + DepthBias )
      {
        // Compute the actual position on the ray for the given depth value.
        float3 FinalPosVS = Input.PosVS.xyz + (ReflectVS / abs(ReflectVS.z)) * abs(CurDepthLin - Input.PosVS.z + DepthBias);
        float2 FinalPosCS = FinalPosVS.xy / PerspectiveValues.xy / FinalPosVS.z;
        SampPos = float2( 0.5f, -0.5f ) * FinalPosCS.xy + 0.5f;
        
        // Get the value at the current screen space location.
        ReflectColor.rgb = DiffuseMap.SampleLevel( LinearSampler, SampPos, 0.0f ).rgb;
        
        // Fade out samples as they get close to the texture edges.
        float EdgeFade = clamp( distance( SampPos, float2( 0.5f, 0.5f ) ) * 2.0f - EdgeDistThreshold, 0.0f, 1.0f );
        
        // Compute the fade value.
        ReflectColor.a = min( ViewAngleFade, 1.0f - EdgeFade * EdgeFade );
        
        // Apply the reflection scale.
        ReflectColor.a *= ReflectionScale;
        
        // Advance past the final iteration to break the loop.
        CurStep = NumSteps;
      }
      
      // Advance to the next sample.
      SampPos += Step;
    }
  }
  
  // Return the reflect color.
  return ReflectColor;
}

This part is because it's fixed-size for test :


static const float PixelSize = 2.0f / 720.0f;
static const int NumSteps = 1280;

The problem is that cost very a lot.

Does a way exists to have a quality value ?

Thanks

Advertisement

Well 1200 samples per pixel is pretty much. Is there a way to early out the loop? I mean, when you find an intersection, can you quit the loop?

[edit] seems that your loop has an early out condition.

I think that using a down scaled depth buffer might help to accelerate the intersection finding. Ie. you can discard easily bigger blocks of data where the intersection doesn't happen for sure.

Cheers!

A possible improvement is to swap from a fixed step march and instead use a depth hierarchy that you can traverse as you get further along your ray. You can use lower resolution depths for longer rays/distant steps and get away with some pretty good speed increases. You can also use the hierarchy for detecting invalid tiles on the screen that clearly wouldn't have an intersection (quad-tree style), allowing you to skip portions of the screen that a pixel will never hit.

This topic is closed to new replies.

Advertisement