Question

I am implementing Pre-Pass Lighting algorithm in OpenGL for my master dissertation project, after implementing a Deferred renderer as well. The Deferred renderer works perfectly and I based the implementation of PPL on it. I got a very weird artifact after the lighting pass of the algorithm: the data contained in the L-buffer, where I accumulate the contributions of the lights in the scene, is correct, but results to be slightly off in respect to the geometry so when I apply it to the scene in the material pass the result it's clearly visible! (I can't post the image here but here it's a link to see it http://postimage.org/image/kxhlbnl9v/)

It looks like the light map cube is somehow computed with an offset (different in every axes) from the geometry. I checked the shaders and C++ code many times, I do not understand where this problem comes from. I am running out of ideas. Below there is the code for the 3 passes of the algorithm that are called in sequence. The code is experimental for now so I know it's not well designed at this stage. I also add the shaders I use in every stage to write to G-buffer, L-buffer and framebuffer in order.

C++ CODE:

// Draw geometry to g buffer
void GLPrePassLightingRendererV2::GeometryStage()
{
  // Set GL states
  glFrontFace(GL_CCW);
  glCullFace(GL_BACK);
  glEnable(GL_CULL_FACE);
  glDepthFunc(GL_LEQUAL);
  glDisable(GL_BLEND);
  glEnable(GL_DEPTH_TEST);
  glDepthMask(GL_TRUE);

  // Bind G-Buffer for geometry pass
  mGBuffer->BindForWriting();

  // Bind geometry stage shaders
  mTargetRenderSystem->BindShader(mGeometryStageVS);
  mTargetRenderSystem->BindShader(mGeometryStageFS);

  // Clear the framebuffer
  mTargetRenderSystem->ClearFrameBuffer(FBT_COLOUR | FBT_DEPTH);

  // Iterate over all the Renderables in the previously built RenderQueue
  RenderableList* visibles = mSceneManager->GetRenderQueue()->GetRenderables();

  // Set shader params here
  //[...]

  // Get the transformation info from the node the renderable is attached to
  for (RenderableList::iterator it = visibles->begin(); it != visibles->end(); ++it)
  {
    Renderable* renderable = *it;
    Material* mat = renderable->GetMaterial();

    mGeometryStageVS->Update();
    mGeometryStageFS->Update();

    // Render the object
    RenderOperation rop;
    renderable->GetRenderOperation(rop);
    mTargetRenderSystem->Render(rop);
  }

  // Only the geometry pass will write to the depth buffer
  glDepthMask(GL_FALSE);
  glDisable(GL_DEPTH_TEST);
}

// Accumulate lights contribs in L-buffer using G-buffer
void GLPrePassLightingRendererV2::LightingStage()
{
  // Enable additive blending for lights
  glEnable(GL_BLEND);
  glBlendEquation(GL_FUNC_ADD);
  glBlendFunc(GL_ONE, GL_ONE);
  //glCullFace(GL_FRONT);

  // Bind shader for light stage
  mTargetRenderSystem->BindShader(mLightStageVS);
  mTargetRenderSystem->BindShader(mLightStageFS);

  // Bind G-Buffer for reading and L-Buffer for writing for lighting pass
  mGBuffer->BindForReading();
  mLBuffer->BindForWriting();

  mTargetRenderSystem->ClearFrameBuffer(FBT_COLOUR);

  // Set shader params
  // [...]

  // Get all the lights in frustum, not by renderable
  const LightList& lights = mSceneManager->GetLightsInFrustum();

  // For each light in the frustum
  LightList::const_iterator front_light_it;
  for (LightList::const_iterator lit = lights.begin(); lit != lights.end(); ++lit)
  {
    // Send per light parameters to the shader
    Light* l = (*lit);
    SetLight(*l);

    // Calculate bounding sphere for light and scale accordingly to instensity
    float lightSphereScale = GetPointLightSphereScale(l->GetColor(), l->GetDiffuseIntensity());

    // TODO: Render a sphere for each point light, a full screen quad for each directional
    worldMtx.Identity();
    worldMtx.SetScale(lightSphereScale, lightSphereScale, lightSphereScale);
    worldMtx.SetTranslation(l->GetPosition());

    mLightStageVS->SetParameterValue("gWorldMtx", (float*)&worldMtx);

    mLightStageVS->Update();
    mLightStageFS->Update();
    static MeshInstance* sphere = mSceneManager->CreateMeshInstance("LightSphere", MBT_LIGHT_SPHERE);

    RenderOperation rop;
    sphere->GetSubMeshInstance(0)->GetRenderOperation(rop);
    mTargetRenderSystem->Render(rop);
  }

  // Disable additive blending
  glDisable(GL_BLEND);
}

// Combine L-buffer and material information per object
void GLPrePassLightingRendererV2::MaterialStage()
{
  // Set some GL states
  glDepthMask(GL_TRUE);
  glEnable(GL_DEPTH_TEST);
  //glCullFace(GL_BACK);

  // Bind material stage shaders (TODO: actually every object will bind its own matarial, if not a default one is used)
  mTargetRenderSystem->BindShader(mMaterialStageVS);
  mTargetRenderSystem->BindShader(mMaterialStageFS);

  // Bind L-Buffer for reading
  mLBuffer->BindForReading();

  mTargetRenderSystem->ClearFrameBuffer(FBT_COLOUR | FBT_DEPTH, Math::ColourValue::WHITE);

  // Iterate over all the Renderables in the previously built RenderQueue
  RenderableList* visibles = mSceneManager->GetRenderQueue()->GetRenderables();

  // Set shader params here
  // [...]

  // Get the transformation info from the node the renderable is attached to
  for (RenderableList::iterator it = visibles->begin(); it != visibles->end(); ++it)
  {
    Renderable* renderable = *it;
    Material* mat = renderable->GetMaterial();


    // Set texture units
    if (mat)
    {
      for (unsigned short i = 0; i < mat->GetTextureUnitCount(); ++i)
      {
        const TextureUnit* unit = mat->GetTextureUnit(i);
        GLTexture* t = static_cast<GLTexture*>(unit->GetTexture());
        glActiveTexture(GL_TEXTURE1); // This is needed because the first texture map slot is hold by the LBuffer!
        glBindTexture(GL_TEXTURE_2D, t->GetGLId());
      }
    }

    mMaterialStageVS->Update();
    mMaterialStageFS->Update();

    // Render the object
    RenderOperation rop;
    renderable->GetRenderOperation(rop);
    mTargetRenderSystem->Render(rop);
  }
}

NVIDIA CG Shaders:

// Vertex shader for Deferred Rendering geometry stage.

float4x4 gWorldMtx;
float4x4 gViewMtx;
float4x4 gProjectionMtx;

struct a2v
{
    float3 position : POSITION;
    float3 normal   : NORMAL;
    float2 texCoord : TEXCOORD0;
};

struct v2f
{
    float4 position     : POSITION;
    float3 normal       : TEXCOORD0;
    float3 wPosition    : TEXCOORD1;
    float2 texCoord     : TEXCOORD2;
};


v2f PPL_geometry_stage_vs(a2v IN)
{
    v2f OUT;

    // Transform to world space
    OUT.wPosition = mul(gWorldMtx, float4(IN.position, 1.0f)).xyz;
    OUT.normal = mul(gWorldMtx, float4(IN.normal, 0.0f)).xyz;

    // Transform to homogeneous clip space
    OUT.position = mul(gViewMtx, float4(OUT.wPosition, 1.0f));
    OUT.position = mul(gProjectionMtx, OUT.position);

    OUT.texCoord = IN.texCoord;

    return OUT;
}

// Fragment shader for Pre-pass Lighing geometry stage.

struct f2a
{
    float4 position : COLOR0;
    float4 normal   : COLOR1;
};

f2a PPL_geometry_stage_fs(v2f IN)
{
    f2a OUT;

    OUT.position = float4(IN.wPosition, 1.0f);
    OUT.normal = float4(normalize(IN.normal), 1.0f);

    return OUT;
}

// Vertex shader for Pre-pass lighing light stage.

float4x4 gWorldMtx;
float4x4 gViewMtx;
float4x4 gProjectionMtx;

struct a2v
{
    float3 position : POSITION;
};

struct v2f
{
    float4 position : POSITION;
    float4 lightPos : TEXCOORD0;
};

v2f PPL_light_stage_vs(a2v IN)
{
    v2f OUT;

    float4x4 wv   = mul(gWorldMtx, gViewMtx);
    float4x4 wvp  = mul(gViewMtx, gProjectionMtx);
    wvp           = mul(wvp, gWorldMtx);

    // Only transforms position to world space
    OUT.position  = mul(wvp, float4(IN.position, 1.0f));

    // Copy light position to calculate fragment coordinate
    OUT.lightPos = OUT.position;

    return OUT;  
}

// Fragment shader for Pre-pass lighing light stage.

// Light structures
struct BaseLight
{
    float3 color;
    float ambientIntensity;
    float diffuseIntensity;
};

struct DirectionalLight
{
    struct BaseLight base;
    float3 direction;
};

struct Attenuation
{
    float constant;
    float linearr;
    float quadratic;
};

struct PointLight
{
    struct BaseLight base;
    float3 position;
    Attenuation atten;
};

struct SpotLight
{
    struct PointLight base;
    float3 direction;
    float cutoff;
};

// G-Buffer textures
sampler2D gPositionMap  : TEXUNIT0;
sampler2D gNormalMap    : TEXUNIT1;

// Light variables
float3 gEyePosition;
DirectionalLight gDirectionalLight;
PointLight gPointLight;
SpotLight gSpotLight;
int gLightType;
float gSpecularPower;

float4 PPL_light_stage_point_light_fs(v2f IN) : COLOR0
{
    // Get fragment coordinate, from NDC space [-1, 1] to [0, 1].
    float2 fragcoord = ((IN.lightPos.xy / IN.lightPos.w) + 1.0f) / 2.0f;

    // Calculate lighting with G-Buffer textures
    float3 position = tex2D(gPositionMap, fragcoord).xyz;
    float3 normal = tex2D(gNormalMap, fragcoord).xyz;
    normal = normalize(normal);

    // Attenuation
    float3 lightDirection = position - gPointLight.position;
    float dist = length(lightDirection);
    float att = gPointLight.atten.constant + gPointLight.atten.linearr * dist + gPointLight.atten.quadratic * dist * dist;

    // NL
    lightDirection = normalize(lightDirection);
    float NL = dot(normal, -lightDirection);

    // Specular (Blinn-Phong)
    float specular = 0.0f;
    //if (NL > 0)
    //{
    //  float3 vertexToEye = normalize(gEyePosition - position);
    //  float3 lightReflect = normalize(reflect(lightDirection, normal));
    //  specular = pow(saturate(dot(vertexToEye, lightReflect)), gSpecularPower);
    //}

    // Apply attenuation to NL
    NL = NL / min(1.0, att);

    float3 lightColor = gPointLight.base.color * gPointLight.base.diffuseIntensity;
    return float4(lightColor.r, lightColor.g, lightColor.b, 1.0f) * NL;
}

// Vertex shader for Pre-pass lighing material stage.

float4x4 gWorldMtx;
float4x4 gViewMtx;
float4x4 gProjectionMtx;

struct a2v
{
    float3 position : POSITION;
    float3 normal   : NORMAL;
    float2 texcoord : TEXCOORD0;
};

struct v2f
{
    float4 position : POSITION;
    float2 texcoord : TEXCOORD0;
    float3 normal   : TEXCOORD1;
    float4 projPos  : TEXCOORD2;
};

v2f PPL_material_stage_vs(a2v IN)
{
    v2f OUT;

    float4x4 wv   = mul(gWorldMtx, gViewMtx);
    float4x4 wvp  = mul(gViewMtx, gProjectionMtx);
    wvp           = mul(wvp, gWorldMtx);

    // Only transforms position to world space
    OUT.position  = mul(wvp, float4(IN.position, 1.0f));

    // Normal (It's not necessary, but i have to see if it influences the execution)
    OUT.normal    = mul(gWorldMtx, float4(IN.normal, 0.0f)).xyz;

    // Copy texture coordinates
    OUT.texcoord = IN.texcoord;

    // Copy projected position to get the fragment coordinate
    OUT.projPos = OUT.position;

    return OUT;
}

// Fragment shader for Pre-pass lighing material stage.

// L-buffer texture
sampler2D gLightMap : TEXUNIT0;
// Object's material specific textures
sampler2D gColorMap : TEXUNIT1;

float4 PPL_material_stage_fs(v2f IN) : COLOR0
{
    float2 fragcoord = ((IN.projPos.xy / IN.projPos.w) + 1.0f) / 2.0f;

    // Get all light contributions for this pixel
    float4 light = tex2D(gLightMap, fragcoord);
    float3 combined = saturate(light.rgb);// + light.aaa);

    // Get material albedo from texture map
    float4 diffuse = tex2D(gColorMap, IN.texcoord);

    return float4(combined, 1.0f) * diffuse;
}

Any suggestions?

Was it helpful?

Solution

You may want to use the WPOS register (VPOS in HLSL) instead of calculating the screen locations.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top