I’m using (DX11) LookAtLH or LookTo functions to get my view matrix. When setting Camera Direction so that the y component is zero I have the correct reflexion (picture left). When changing the Dir.y to negative or positive values I have picture on middle or right. In my calculation this corresponds to a rotation on the x axis (e.g. parallel to screen). I’m not using arcball but keyboard input to modify the rotationx/y params.

Roty -= 0.005f*iRotate;//iRotate =0 or 1 if keyboard left/right arrow pressed
//iMove 0/1 is up/down arrow pressed
Rotx += 0.005f*iLift;//iLift 0/1 if pageup/pagedown pressed
m_WorldCamera.m_Dir = XMVector3Transform(XMVectorSet(1, 0, 0, 0), XMMatrixRotationY(Roty)*XMMatrixRotationX(Rotx));
m_WorldCamera.m_Eye += m_WorldCamera.m_Dir*(float)iMove 
XMVECTOR DEye = m_WorldCamera.m_Eye+XMVectorSet(0, -25, 0, 0);
gView = XMMatrixLookToLH(DEye, m_WorldCamera.m_Dir, XMVectorSet(0, 1, 0, 0));

My SSR normal map is calculated in the shader as NSSR = Mesh.Normal*(float3x3)(ObjectWorldMat*CameraView).

Is there something important I missed to be able to get the correct reflexion whatever the camera rotationX angle?

enter image description here

On the left you can see the generated textures used in my SSR shader.

  1. Bumpped normals in view space (txNormal2);

  2. unmodified mesh normals in view space used for reflexion calculation (txNormal1);

  3. Layers of the scene (txdiffuse1). In the topright part 1/2 screen resolution for main scene and other parts 1/4 resolution for layers containing occluded objects.

  4. Depth buffer for main scene full res, not used in the SSR shader;

  5. Depth buffer (txDepth1) for layers texture (txdiffuse1).

Using layers at 1/2 and 1/4 resolution gives the shader below for 3 layers including the main scene. The resulting color is blended to the main target. Note that doing everything in full screen resolution gives me the same problem.

static const float2 TpDecal[4] = {{0,0}, {0.25,0}, {0,0.25} , {0.25,0.25}  };
#define STEP 2.5*0.02
#define ITER 30
#define STEP2 0.5f
#define BUMP 0.075f

float4 PS_PostDeferredReflex3(PS_INPUTQUAD Input) : SV_TARGET
    //the screen quad mesh used for rendering has regular 0..1 texture coordinates.
    //As we use 1/2 resolution from the topright quad of txDiffuse1
    //we modify Input.Tex.xy as UV to sample topright quad.
    float2 UV = mad(Input.Tex, 0.5, float2(0.5,0));
    //sample 1/2 resolution Depth
    float D = txDepth1.SampleLevel(sPoint, UV, 0).r;
    if ( D > 0.99999 ) return float4(0,0,0,0);
    //sample full screen resolution mesh normals
    float2 NN = txNormal1.SampleLevel(sPoint, Input.Tex, 0);
    if ( NN.r == 0 ) return float4(0,0,0,0);
    //the line below uses precalculated value from the inverse projection matrix (only _11, __22,_34 and _43 values are necessary) to obtain Pos in view space with a mad op.
    float3 PosV = float3(mad(Input.Tex.xy, float2(  0.707243978977203, -0.397824734449387),float2( -0.353621989488602,  0.198912367224693)), 1)/(-0.998999953269959*D+1);
//  line above equivalent to 
//  float4 PosV = mul(float4(mad(Input.Tex.xy, float2( 2, -2),float2( -1,  1)), D, 1), InvProj);
//  PosV/=PosV.w;
    NN = mad(NN,2,-1);
    //normals are stored in view space as RG format.
    //We retrieve B value below with Pithagorean theorem.
    float3 VSDir = normalize(reflect(normalize(PosV),float3(NN.rg, 1-sqrt(dot(NN.rg, NN.rg)))));
#ifdef BUMP // add some deviation in the ray based on the bumpy normals in txNormal2
    //not the origin of my problem
    NN = txNormal2.SampleLevel(sPoint, Input.Tex, 0);
    NN = mad(NN,2,-1);
    VSDir += VSDir*float3(NN.rg, 1-sqrt(dot(NN.rg, NN.rg)))*BUMP;

    float3 SSray = float3(UV, D);//set the start 1/2 resolution
    //line below calculate the end of ray in full screen
    float4 SSEnd = mul ( float4(PosV + VSDir*500, 1), Proj);
    SSEnd/= SSEnd.w;
    //adjust the result to half screen, topright panel (z component
    //not affected) and do the difference to SSray to give the 
    //maximum  length of the ray in screen space.
    SSEnd.xyz=mad(SSEnd.xyz, float3(0.5,0.5,1), float3(0.5,0,0))-SSray;
    float3 SSDir = SSEnd.xyz*STEP;//set the step for 1/2 res
    float2 SSDir2 = SSDir.xy*STEP2;//set the step UV for 1/4 res
    float2 TpRay =  Input.Tex*0.25;//set the start UV for 1/4 res
    float2 DZH;
    float3 EndRay;
    float3 MinRay;
    float3 MaxRay;
    float2 EndRay2;

    float3 T;
    float3 ZBufferVal;
    int i=0;
    while (i<ITER)
        EndRay = SSray;
        EndRay2 = TpRay;
        SSray+=SSDir;//increments 1/2 res
        TpRay+=SSDir2;//increments 1/4 res
        ZBufferVal.r = txDepth1.SampleLevel(sPoint, SSray.xy, 0);//sample 1/2 res
        ZBufferVal.g = txDepth1.SampleLevel(sPoint, TpRay.xy, 0)+0.00002f;//sample 1/4 layer 1, if Z.g=Z.r the offset ensure selection of the main higher res layer
        ZBufferVal.b = txDepth1.SampleLevel(sPoint, TpRay.xy+TpDecal[1], 0)+0.00002f;//sample 1/4 layer 2
        T = SSray.zzz-ZBufferVal;//select the final layer based on comparison of T.rgb and DZH.x
        DZH = float2(0.005, 0);//set a starting maximum value for DZH.x. The DZH.y component will contain the layer index (1-3)
            //this conditionnal assignment performs better than if statments
        DZH = (( T.r>=0 )&& (T.r<DZH.x))?float2(T.r,1):DZH;
        DZH = (( T.g>=0 )&& (T.g<DZH.x))?float2(T.g,2):DZH;
        DZH = (( T.b>=0 )&& (T.b<DZH.x))?float2(T.b,3):DZH;
        if ( DZH.y>0) //if layer encoutered by the ray
        if ( DZH.x < 0.0005) //if delta depth < 0.0005
            MinRay = EndRay;//refinement loop
            MaxRay = SSray;
            //if not main layer 1 (1/2 res) modify the MinMaxRay
            //UV coordinates according to the 1/4 res viewports
            //decals stored in TPDecal
                float2 dtp = TpDecal[DZH.y-2];
                MinRay.xy = EndRay2+dtp;
                MaxRay.xy = TpRay+dtp;
            for(int j = 0; j < 8; j++)//do refinement loop
                EndRay = (MinRay+MaxRay)*0.5f;
                T.r = txDepth1.SampleLevel(sPoint, EndRay.xy, 0);
                if ( EndRay.z > T.r) MaxRay = EndRay; else MinRay = EndRay;
            if (abs(EndRay.z-T.r)<0.000025) //output color if delta depth low enought with blend according to ray length done
                return float4(txDiffuse2.SampleLevel(sPoint, EndRay.xy, 0).rgb,1-clamp((EndRay.z-D)/SSEnd.z, 0, 1));
    return float4(0,0,0,0);

1 Answer 1


Finally the problem was in the normal used for SSR. Retrieving the z component from the stored xy format by the Pythagorean theorem works well if the sign of z can be fixed. Apparently when the camera rotate around the x axis (e.g. axis parallel to screen width in my case) the retrieved z value is not consistent and so it is better to store the normal in full xyz R10B10G10 format. Doing so solved my problem with good precision. Alternative packed format for SSR normal could be another answer. For instance Spheremap Transform with R16G16_UNORM format works well too with no impact compared to R10G10B10A2 considering the additional coding/decoding instructions. R16G16_FLOAT gave me small flickering in the mirrored pixels. R8G8 is not precise enought for this purpose with bad reflexion aspect.

coding spheremap normal
float2 NOut = mad(N.xy, 1.0/sqrt(N.z*8+8), 0.5);
float2 NIn = mad(txNormal.sample().rg, 4, -2);
float f = dot(NIn ,NIn ); 
float3 NOut = float3(NIn*sqrt(1-f*0.25), 1-f*0.5);

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .