Relief/Parallax Occlusion Mapping - How do you calculate the depth offset?

Started by
0 comments, last by CarlML 7 years, 4 months ago

Hi, I have implemented relief mapping and am trying to calculate the correct world space depth offset, resulting from the view vector going into texture space. How would one do that?

Below is what the shader code looks like, it is similar to parallax occlusion mapping with an added binary search part, making it relief mapping. The last lines in the pixel shader is where I currently estimate the resulting depth based on the size of my model but it is not exact and generalized so I need to find a better way.

Vertex shader:


void VS_RenderGeometryDisplace(float4 position : POSITION0,
                               float2 texCoord : TEXCOORD0,
                               float3 normal : NORMAL0,
                               float3 binormal : BINORMAL0,
                               float3 tangent : TANGENT0,


                               out float4 outPosition : POSITION0,
                               out float3 outTexCoord : TEXCOORD0,
                               out float3 outNormal : TEXCOORD1,
                               out float3 outViewVec : TEXCOORD2,
                               out float3 outLightDir : TEXCOORD3,
                               out float3 outWorldPos : TEXCOORD4,
                               out float3x3 tangentToWorldSpace : TEXCOORD5)
{
    outPosition = mul(position, worldViewProjMatrix);
    outTexCoord.xy = texCoord;
    outTexCoord.z = outPosition.z / cameraFarPlane; //depth stored in texCoord
    outNormal = mul(float4(normal,0.0f),worldMatrix).xyz;

    tangentToWorldSpace[0] = mul(float4(tangent, 0.0f), worldMatrix).xyz;
    tangentToWorldSpace[1] = mul(float4(binormal, 0.0f), worldMatrix).xyz;
    tangentToWorldSpace[2] = mul(float4(normal, 0.0f), worldMatrix).xyz;

    float3x3 worldToTangentSpace = transpose(tangentToWorldSpace);
    float3 worldPos = mul(position, worldMatrix).xyz;
    float3 viewVec = worldPos - cameraPosWorld;
    outViewVec = mul(viewVec, worldToTangentSpace);

    outLightDir = mul(-lightDir, worldToTangentSpace);

    outWorldPos = worldPos;
}

Pixel shader:


void PS_RenderGeometryDisplace(float3 texCoord : TEXCOORD0,
                               float3 inNormal : TEXCOORD1,
                               float3 inViewVec : TEXCOORD2,
                               float3 inLightDir : TEXCOORD3,
                               float3 inWorldPos : TEXCOORD4,
                               float3x3 tangentToWorldSpace : TEXCOORD5,

                               out float4 color0_normal : COLOR0,
                               out float4 color1_depth : COLOR1,
                               out float4 color2_diffuse : COLOR2,
                               out float4 color3_illum : COLOR3)
{
    float3 camToPix = normalize(inWorldPos - cameraPosWorld);
    float viewDot = dot(inNormal, camToPix);
    viewDot *= viewDot; //add some bias and make it a positive number

    float2 tex = texCoord.xy;

    float fParallaxLimit = -length(inViewVec.xy) / inViewVec.z;
    fParallaxLimit *= (0.2f + 0.8f * viewDot) * 0.85f; //Scale of displacement

    float2 vOffsetDir = normalize(inViewVec.xy);
    float2 vMaxOffset = vOffsetDir * fParallaxLimit;

    int nMinSamples = 8;
    int nMaxSamples = 14;
    int nNumSamples = (int)lerp( (float)nMaxSamples, (float)nMinSamples, viewDot ); //Use more samples when viewing the surface at an angle

    float fStepSize = 1.0f / (float)nNumSamples;
    float2 texOffset = fStepSize * vMaxOffset;

    float2 dx = ddx(tex);
    float2 dy = ddy(tex);

    float fCurrRayHeight = 1.0f;
    float2 vCurrOffset = float2(0.0f, 0.0f);
    float2 vLastOffset = float2(0.0f, 0.0f);
    float fLastSampledHeight = 1.0f;
    float fCurrSampledHeight = 1.0f;
    int nCurrSample = 0;

    //Parallax occlusion mapping / Relief mapping
    //Raymarch from surface into texture space to find first spot that is above ray depth
    while (nCurrSample < nNumSamples)
    {
        fCurrSampledHeight = tex2Dgrad(DiffuseMapSampler, tex + vCurrOffset, dx, dy).a;
        if (fCurrSampledHeight >= fCurrRayHeight)
        {
            nCurrSample = nNumSamples + 1;
        }
        else
        {
            nCurrSample++;
            fCurrRayHeight -= fStepSize;
            vLastOffset = vCurrOffset;
            vCurrOffset += texOffset;
            fLastSampledHeight = fCurrSampledHeight;
        }
    }

    //Binary search
    float lastRayHeight = fCurrRayHeight + fStepSize;
    for (int i=0; i < 8; i++)
    {
        float midRayHeight = (lastRayHeight + fCurrRayHeight) * 0.5f;
        float2 midOffset = (vCurrOffset + vLastOffset) * 0.5f;
        fCurrSampledHeight = tex2Dgrad(DiffuseMapSampler, tex + midOffset, dx, dy).a;
        if (fCurrSampledHeight > midRayHeight)
        {
            vCurrOffset = midOffset;
            fCurrRayHeight = midRayHeight;
        }
        else
        {
            vLastOffset = midOffset;
            lastRayHeight = midRayHeight;
            if (i == 7) vCurrOffset = midOffset;
        }
    }

    tex += vCurrOffset; //New texture coordinates

    //This is the new world position I'm trying to calculate accurately
    float3 newWorldPos = inWorldPos + camToPix * (1.0f - fCurrSampledHeight) * (5.5f + 0.5f * viewDot);

    float3 camPos = mul(float4(newWorldPos,1.0f), viewProjMatrix).xyz;
    color1_depth = camPos.z / cameraFarPlane;

    ...

Suggestions on how to calculate the offset world position, the depth or about the code in general are welcome.

Advertisement

So I figured out a solution. I do a ray-plane intersection using the view ray and a plane that is moved down along the surface normal according to the trace depth multiplied with a user defined "reliefDepth" value that should correpond to the observable maximum world space displacement.


float offsetDepth = (reliefDepth * 0.2f + (reliefDepth * 0.8f) * viewDot) * (1.0f - fCurrSampledHeight);
float3 bottomPos = inWorldPos - inNormal * offsetDepth;
float d1 = dot(inNormal, bottomPos - inWorldPos);
float d2 = dot(camToPix, inNormal);
float3 newWorldPos = inWorldPos;
if (d2) newWorldPos += camToPix * (d1 / d2);

The "reliefDepth" variable is adjusted in the same way that the "fParallaxLimit" is, according to view angle. Those two values need to correspond for a good result.

This topic is closed to new replies.

Advertisement