Sign in to follow this  
matt77hias

Spotlights, omni lights and directional lights

Recommended Posts

Is this a "clean" way (looks a bit like C-style OO) to handle different kinds of light sources in the same shader?

I use the following structure on the CPU:

enum LightInfoType {
    LightInfoType_SpotLight        = 0,
    LightInfoType_OmniLight        = 1,
    LightInfoType_DirectionalLight = 2
};

__declspec(align(16)) struct LightInfo final {
    XMFLOAT4 m_intensity;
    XMFLOAT4 m_position;            // not for directional light
    XMFLOAT3 m_direction;           // not for omni light
    LightInfoType m_type;
    float m_distance_falloff_start; // not for directional light
    float m_distance_falloff_end;   // not for directional light
    float m_cos_penumbra;           // only for spotlight
    float m_cos_umbra;              // only for spotlight
​};

static_assert(sizeof(LightInfo) == 64, "Light struct mismatch");

On the GPU, I use a large StructuredBuffer with structs of the same size and layout?

Edited by matt77hias

Share this post


Link to post
Share on other sites

Define clean way ? There is almost as many way to transfer information to a GPU as there is stars in the night sky ( at least a light polluted sky ).

 

Fat structures are usually a bad idea tho, because you will waste some bandwidth. It may be irrelevant if you read your light once in a tile deferred renderer, but could be an issue if it is a forward pass for example.

Share this post


Link to post
Share on other sites

Define clean way

I am not really a fan of "LightInfoType m_type " and the fact that omni lights and directional lights use only part of the allocated data whereas spotlights use everything.

The number of lights is not known in advance, neither are their types. So I would allocate a buffer of 1024 lights or so to start with on the GPU.

Fat structures are usually a bad idea tho, because you will waste some bandwidth.

Worst case: the maximum number of lights is used and dynamic. I do not use deferred shading yet (so this can be problematic for large scenes and resolutions).

Share this post


Link to post
Share on other sites

You can just have 3 structured buffers, one for each type of light ( but i guess the directional should be unique in a constant buffer, maybe two for a quick backlit bounce ? ).

 

You do not really have a choice for the allocation and have to pick your worst case anyway. Splitting the lights into separate bucket also allow the shader to be a little more efficient, pushing the type test outside of the loop, the compiler will appreciate. The result of summing lights is invariant from the order you process them anyway.

Share this post


Link to post
Share on other sites

 

maybe two

I could sort them on the CPU side.

Isn't allocating two buffers of 1024 more memory expensive?

 

Only if you use all of the data in your shader, you still gonna limit it to a numLight.

 

Let's compare the memory footprint of your 1024 light structur against a 1024 texture in BC7 ( for example + mips ).

Lights : 1024 * 64 = 64K

1024x1024 BC7 with mips = 1.3MB

 

You could store 21K lights in the equivalent of one texture, and your scene will have far more than one texture, and probably many even bigger than 1024 :)

 

 

 

maybe two

I could sort them on the CPU side.

Isn't allocating two buffers of 1024 more memory expensive?

 

Only if you use all of the data in your shader, you still gonna limit it to a numLight.

 

Let's compare the memory footprint of your 1024 light structur against a 1024 texture in BC7 ( for example + mips ).

Lights : 1024 * 64 = 64K

1024x1024 BC7 with mips = 1.3MB

 

You could store 21K lights in the equivalent of one texture, and your scene will have far more than one texture, and probably many even bigger than 1024 :)

 

 

Share this post


Link to post
Share on other sites

Ok, then I can use an OO approach and keep using my Light hierarchy without having to convert them to standard LightInfo structures before passing them to the GPU. I will then pass them as a Light dependent structure to a dedicated buffer (except for the directional and ambient light).

Share this post


Link to post
Share on other sites

Can I get some feedback (feel free to nitpick) on my shader (progression) below, which is beside some "basic" shaders, my first autonomously written shader.

I want to support Lambertian, Phong, Blinn-Phong and Modified Blinn-Phong BRDFs and ambient lights, directional lights, omni lights and spotlights light sources. I perform all shading operations in camera space (viewing along positive z-axis).

It is possible to add and use a texture for the specular contribution (Ks) as well. This is currently more difficult for the specular exponent (Ns), but could be added as well in the future.

I do not understand why I needed to declare and use a j for looping instead of declaring and using an i twice (warning).

//-----------------------------------------------------------------------------
// Transformations
//-----------------------------------------------------------------------------
cbuffer cb_transform : register(b0) {
    matrix model_to_world;
    matrix world_to_view;
    matrix world_to_view_inverse_transpose;
    matrix view_to_projection;
}

//-----------------------------------------------------------------------------
// Materials
//-----------------------------------------------------------------------------
Texture2D diffuse_texture_map : register(t0);
sampler texture_sampler       : register(s0);

cbuffer cb_material : register(b1) {
    float3 Kd;       // The diffuse reflectivity of the material.
    float  dissolve; // The dissolve of the material.
    float3 Ks;       // The specular reflectivity of the material.
    float  Ns;       // The specular exponent of the material.
};

// Calculates the reflected direction of the given l about the given n.
float3 reflected_direction(float3 n, float3 l) {
    return reflect(-l, n);
}
// Calculates the half direction between the v (0,0,-1) and given l.
float3 half_direction(float3 l) {
    l.z -= 1.0f;
    return normalize(l);
}

// Calculates the Lambertian BRDF (independent of kd).
float lambertian_brdf(float3 n, float3 l) {
    return max(0.0f, dot(n, l));
}
// Calculates the Phong BRDF (independent of ks).
float phong_brdf(float3 n, float3 l) {
    const float3 r = reflected_direction(n, l);
    return max(0.0f, pow(-r.z, Ns) / dot(n, l));
}
// Calculates the Blinn-Phong BRDF (independent of ks).
float blinnphong_brdf(float3 n, float3 l) {
    const float3 h = half_direction(l);
    return max(0.0f, pow(dot(n, h), Ns) / dot(n, l));
}
// Calculates the Modified Blinn-Phong BRDF (independent of ks).
float modifiedblinnphong_brdf(float3 n, float3 l) {
    const float3 h = half_direction(l);
    return pow(max(0.0f, dot(n, h)), Ns);
}

//-----------------------------------------------------------------------------
// Lights
//-----------------------------------------------------------------------------
cbuffer cb_light : register(b2) {
    float3 Ia;            // The intensity of the ambient light. 
    uint   nb_omnilights; // The number of omni lights.
    float3 Id;            // The intensity of the directional light.
    uint   nb_spotlights; // The number of spotlights.
    float3 d;             // The direction of the directional light in camera space.
};

struct OmniLight {
    float4 p;                      // The position of the omni light in camera space.
    float3 I;                      // The intensity of the omni light.
    float  distance_falloff_start; // The distance at which intensity falloff starts.
    float  distance_falloff_end;   // The distance at which intensity falloff ends.
};

struct SpotLight {
    float4 p;                      // The position of the spotlight in camera space.
    float3 I;                      // The intensity of the spotlight.
    float  exponent_property;      // The exponent property of the spotlight.
    float3 d;                      // The direction of the spotlight in camera space.
    float  distance_falloff_start; // The distance at which intensity falloff starts.
    float  distance_falloff_end;   // The distance at which intensity falloff ends.
    float  cos_penumbra;           // The cosine of the penumbra angle at which intensity falloff starts.
    float  cos_umbra;              // The cosine of the umbra angle at which intensity falloff ends.
};

StructuredBuffer< OmniLight > omni_lights : register(b2);
StructuredBuffer< SpotLight > spot_lights : register(b3);

// Calculates the distance fall off at a given distance r.
float distance_falloff(float r, float r_start, float r_end) {
    return saturate((r_end - r) / (r_end - r_start));
}
// Calculates the angular fall off at a given angle theta.
float angular_falloff(float cos_theta, float cos_penumbra, float cos_umbra, float s_exp) {
    if (cos_theta >= cos_penumbra) {
        return 1.0f;
    }
    if (cos_theta <= cos_umbra) {
        return 0.0f;
    }
    return pow((cos_theta - cos_umbra) / (cos_penumbra - cos_umbra), s_exp);
}

// Calculates the maximum contribution of the given omni light on the given point.
float3 calculate_omnilight_max_contribution(OmniLight light, float4 p) {
    const float r  = distance(light.p, p);
    const float df = distance_falloff(r, light.distance_falloff_start, light.distance_falloff_end);
    return df * light.I;
}
// Calculates the maximum contribution of the given spotlight on the given point.
float3 calculate_spotlight_max_contribution(SpotLight light, float4 p, float3 l) {
    const float r  = distance(light.p, p);
    const float cos_theta = dot(light.d, -l);
    const float df = distance_falloff(r, light.distance_falloff_start, light.distance_falloff_end);
    const float af = angular_falloff(cos_theta, light.cos_penumbra, light.cos_umbra, light.exponent_property);
    return df * af * light.I;
}

//-----------------------------------------------------------------------------
// Shading
//-----------------------------------------------------------------------------

// Calculates the Lambertian shading.
float4 calculate_lambertian_shading(float4 p, float3 n, float2 tex) {

    float3 I_diffuse  = float3(0.0f, 0.0f, 0.0f);

    // Ambient light and directional light contribution
    float3 brdf = lambertian_brdf(n, -d);
    I_diffuse = Ia + brdf * Id;

    // Omni lights contribution
    for (uint i = 0; i < nb_omnilights; ++i) {
        const OmniLight light = omni_lights[i];
        const float3 l = normalize(light.p - p).xyz;
        const float3 I_light = calculate_omnilight_max_contribution(light, p);

        brdf = lambertian_brdf(n, l);
        I_diffuse += brdf * I_light;
     }

     // Spotlights contribution
     for (uint j = 0; j < nb_spotlights; ++j) {
         const SpotLight light = spot_lights[j];
         const float3 l = normalize(light.p - p).xyz;
         const float3 I_light = calculate_spotlight_max_contribution(light, p, l);

         brdf = lambertian_brdf(n, l);
         I_diffuse += brdf * I_light;
     }

     float4 I = float4(0.0f, 0.0f, 0.0f, dissolve);
     I.xyz = Kd * I_diffuse;
     I *= diffuse_texture_map.Sample(texture_sampler, tex);
     return I;
}
// Calculates the Phong BRDF shading.
float4 calculate_phong_shading(float4 p, float3 n, float2 tex) {

    float3 I_diffuse = float3(0.0f, 0.0f, 0.0f);
    float3 I_specular = float3(0.0f, 0.0f, 0.0f);

    // Ambient light and directional light contribution
    float3 brdf = lambertian_brdf(n, -d);
    I_diffuse = Ia + brdf * Id;

    // Omni lights contribution
    for (uint i = 0; i < nb_omnilights; ++i) {
        const OmniLight light = omni_lights[i];
        const float3 l = normalize(light.p - p).xyz;
        const float3 I_light = calculate_omnilight_max_contribution(light, p);

        brdf = lambertian_brdf(n, l);
        I_diffuse += brdf * I_light;

        brdf = phong_brdf(n, l);
        I_specular += brdf * I_light;
     }

    // Spotlights contribution
    for (uint j = 0; j < nb_spotlights; ++j) {
        const SpotLight light = spot_lights[j];
        const float3 l = normalize(light.p - p).xyz;
        const float3 I_light = calculate_spotlight_max_contribution(light, p, l);

        brdf = lambertian_brdf(n, l);
        I_diffuse += brdf * I_light;

        brdf = phong_brdf(n, l);
        I_specular += brdf * I_light;
    }

    float4 I = float4(0.0f, 0.0f, 0.0f, dissolve);
    I.xyz = Kd * I_diffuse;
    I *= diffuse_texture_map.Sample(texture_sampler, tex);
    I.xyz += Ks * I_specular;
    return I;
}
// Calculates the Blinn-Phong BRDF shading.
float4 calculate_blinnphong_shading(float4 p, float3 n, float2 tex) {
   // similiar but with other specular BRDF
}
// Calculates the Modified Blinn-Phong BRDF shading.
float4 calculate_modifiedblinnphong_shading(float4 p, float3 n, float2 tex) {
   // similiar but with other specular BRDF
}

//-----------------------------------------------------------------------------
// Input structures
//-----------------------------------------------------------------------------
struct VS_INPUT {
    float4 p   : POSITION;
    float3 n   : NORMAL;
    float2 tex : TEXCOORD0;
};

struct PS_INPUT {
    float4 p      : SV_POSITION;
    float4 p_view : POSITION;
    float3 n_view : NORMAL;
    float2 tex    : TEXCOORD0;
};

//-----------------------------------------------------------------------------
// Vertex Shader
//-----------------------------------------------------------------------------
PS_INPUT VS(VS_INPUT input) {
    PS_INPUT output = (PS_INPUT)0;
    output.p_view   = mul(input.p, model_to_world);
    output.p_view   = mul(output.p_view, world_to_view);
    output.p        = mul(output.p_view, view_to_projection);
    output.tex      = input.tex;
    output.n_view   = mul(input.n, (float3x3)world_to_view_inverse_transpose);
    return output;
}

//-----------------------------------------------------------------------------
// Pixel Shader
//-----------------------------------------------------------------------------
float4 PS(PS_INPUT input) : SV_Target {
    return calculate_lambertian_shading(input.p_view, input.n_view, input.tex);
}
Edited by matt77hias

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this