Kyle Hayward's

Portfolio

Dual-Paraboloid Mapping

Description

This is a series of tutorials developed for my blog that covers using dual-paraboloid mapping for reflections and shadows [link1 link2 link3]. It was developed in C# and XNA. The method uses paraboloid mapping for environment reflections and omnidirectional shadows for point lighting. It captures the world in two textures(instead of six as with cube mapping) using a paraboloid warp. This allows for dynamic scenes, but trades resolution for speed. More information on the camera animation can be found here.

Features

  • Dynamic reflections and shadows

  • Variance shadow mapping

  • Requires less memory than cube maps

  • Fast to update

Screen Shots

Video

Source Snippet

////////////////////////////////////////////////////////////////////////////////////////////
//
// Texture & Shadow + Build the Dual-Paraboloid map
//
////////////////////////////////////////////////////////////////////////////////////////////
OutputVS_DP BuildDP_VS(float3 posL : POSITION0, float3 normalL : NORMAL0, float2 tex0: TEXCOORD0)
{
    // Zero out our output.
    OutputVS_DP outVS = (OutputVS_DP)0;
    
    // Pass on texture coordinates to be interpolated in rasterization.
    outVS.tex0 = posL;
    outVS.pos = mul(float4(posL, 1.0f), World).xyz;
    
    //Render with the Dual-Paraboloid distortion
    
    // Transform to homogeneous clip space.
    outVS.posH = mul(float4(posL, 1.0f), WorldViewProj);
    
    outVS.posH.z = outVS.posH.z * Direction;
    
    float L = length( outVS.posH.xyz );                     // determine the distance between (0,0,0) and the vertex
    outVS.posH = outVS.posH / L;                            // divide the vertex position by the distance 
    
    outVS.z = outVS.posH.z;                                 // remember which hemisphere the vertex is in
    outVS.posH.z = outVS.posH.z + 1;                        // add the reflected vector to find the normal vector

    outVS.posH.xy = outVS.posH.xy / outVS.posH.z;           // divide xy coords by the new z-value

    outVS.posH.z = (L - NearPlane) / (FarPlane - NearPlane);// scale the depth to [0, 1]
    outVS.posH.w = 1;                                       // set w to 1 so there is no w divide

    // Done--return the output.
    return outVS;
}

float4 BuildDP_PS(OutputVS_DP input) : COLOR
{
    clip(input.z);
    
    float4 texColor = texCUBE(EnvTex, input.tex0);
    
    //transform to paraboloid basis
    float3 pos = mul(float4(input.pos, 1.0f), LightView);
    
    float L = length(pos);
    float alpha = .5f + pos.z / LightAttenuation;
    
    //calculate the mapping for the front and back hemispheres
    float3 P0 =  CalcDPMDistortFront(pos, L, LightAttenuation);
    float3 P1 =  CalcDPMDistortBack(pos, L, LightAttenuation);
    
    //Decide which hemisphere we're in based on alpha, and return the moments
    float3 moments = CalcDPMVSMMoments(ShadowFrontS, ShadowBackS, P0, P1, alpha);
    float shadow =   CalcVSMShadow(moments.xy, moments.z);
    
    float lit_factor = (moments.z <= moments[0]);
    texColor.xyz *= max(lit_factor, shadow + .2f);
    
    return texColor;
}

Downloads

Binary: XNA 3.0 Redist and .Net 3.5 Framework is required to be installed in order to run the demo.

Source: XNA Game Studio 3.0 and DirectX is required to be installed in order to build and run the source.