1
votes

I am in the process of refactoring my engine for deferred rendering and am trying to implement proper lighting in OpenGL 2.1 / GLSL 120. So far, I believe I have implemented an ambient component based on an ambient light's color as well as the diffuse components of directional, point, and spot lights. However, I became a little stumped when it came to the specular components of these light types. Here is my current code for the second (lighting) pass of the deferred shader:

light_pass.vs

#version 120

attribute vec2 a_position;
uniform mat4 u_projection;
uniform mat4 u_view;

void main()
{
    gl_Position = u_projection * u_view * vec4(a_position, 0.0, 1.0);
}

light_pass.fs

#version 120

const int AMBIENT = 0;
const int DIRECTIONAL = 1;
const int POINT = 2;
const int SPOT = 3;

struct Light
{
    int type;
    vec3 color;
    vec3 direction;
    vec3 position;
    float radius;
    float inner_angle;
    float outer_angle;
};

uniform vec2 u_screen_size;
uniform Light u_light;
uniform sampler2D u_position;
uniform sampler2D u_normal;
uniform sampler2D u_diffuse;

vec2 calc_texcoord()
{
    return gl_FragCoord.xy / u_screen_size;
}

vec4 calc_ambient_light()
{
    return vec4(u_light.color, 1.0);
}

vec4 calc_directional_light(vec3 position, vec3 normal)
{
    vec3 light_dir = normalize(-u_light.direction);
    float contribution = max(dot(normal, light_dir), 0.0);
    return vec4(contribution * u_light.color, 1.0);
}

vec4 calc_point_light(vec3 position, vec3 normal)
{
    float dist = length(u_light.position - position);
    float att = clamp(1.0 - (dist*dist)/(u_light.radius*u_light.radius), 0.0, 1.0); att *= att;
    vec3 light_dir = normalize(u_light.position - position);
    float contribution = max(0.0, dot(normal, light_dir));
    return vec4(contribution * u_light.color, 1.0) * att;
}

vec4 calc_spot_light(vec3 position, vec3 normal)
{
    float dist = length(u_light.position - position);
    float att = clamp(1.0 - (dist*dist)/(u_light.radius*u_light.radius), 0.0, 1.0); att *= att;
    vec3 light_dir = normalize(u_light.position - position);
    float contribution = max(0.0, dot(normal, light_dir));
    vec3 spot_dir = normalize(u_light.direction);
    float angle = acos(dot(light_dir, spot_dir));
    float angle_factor = 1 - smoothstep(u_light.inner_angle, u_light.outer_angle, angle);
    return vec4(contribution * u_light.color, 1.0) * angle_factor * att;
}

void main()
{
    vec2 texcoord = calc_texcoord();
    vec3 position = texture2D(u_position, texcoord).xyz;
    vec3 normal = texture2D(u_normal, texcoord).xyz;
    vec3 diffuse = texture2D(u_diffuse, texcoord).xyz;
    //normal = normalize(normal);
    vec4 result = vec4(0, 0, 0, 0);

    int type = u_light.type;
    if(type == AMBIENT)
        result = calc_ambient_light();
    else if(type == DIRECTIONAL)
        result = calc_directional_light(position, normal);
    else if(type == POINT)
        result = calc_point_light(position, normal);
    else if(type == SPOT)
        result = calc_spot_light(position, normal);

    gl_FragColor = vec4(diffuse, 1.0) * result;//vec4(texcoord, 0, 1);
}

And here are the properties I have currently use in my light class (ignore the shadow properties) to define each type of light:

light.py

from enum import Enum
from pyorama.core.entity import Entity

class LightTypes(Enum):
    AMBIENT = 0
    DIRECTIONAL = 1
    POINT = 2
    SPOT = 3

class AmbientLight(Entity):
    def __init__(self, color):
        self.color = color
        self.cast_shadow = False
        super(AmbientLight, self).__init__()

class DirectionalLight(Entity):
    def __init__(self, color, direction):
        self.color = color
        self.direction = direction
        self.cast_shadow = False
        super(DirectionalLight, self).__init__()

class PointLight(Entity):
    def __init__(self, color, position, radius):
        self.color = color
        self.position = position
        self.radius = radius
        self.cast_shadow = False
        super(PointLight, self).__init__()

class SpotLight(Entity):
    def __init__(self, color, position, radius, direction, inner_angle, outer_angle):
        self.color = color
        self.position = position
        self.radius = radius
        self.direction = direction
        self.inner_angle = inner_angle
        self.outer_angle = outer_angle
        self.cast_shadow = False
        super(SpotLight, self).__init__()

Now currently, I do not have a defined material class and am treating the texture associated with my meshes as the source for a model's diffuse component. My biggest question with lighting is understanding which properties belong to the light and which ones belong to the material.

If I look at a typical .mtl (material) file for a 3d model, I see that at the most basic level, there were Ka, Kd, Ks, and Ns properties, corresponding to an ambient rgb value, diffuse rgb value, specular rgb value, and a mysterious specular exponent float. Does the light have these same components as well? How are the light and the material components combined together? Lots of the tutorials online using OpenGL 2.1 use the fixed function terminology and the glLightandglMaterial*` calls, which is further adding to my confusion.

Also, if I have a textured model, aren't these colors defined by the color of the texture rather than separate material properties? Currently, I do not have a material class and am simply assuming the diffuse color is the color of the texture at an interpolated point sampled in my fragment shader. What list of properties should a Material class have? Are there any missing/incorrect properties in my current Lighting class? I am not particularly interested in any code, just an explanation of what light and material properties I need to combine to get the final color output in a basic Blinn-Phong lighting model with ambient + diffuse + specular components. Any help would be greatly appreciated.

1

1 Answers

1
votes

I assume we are not talking about BRDF lighting,as PBR shading models may contain may contain dozens of material properties.In your case we deal with a standard ADS shading model where:

A- Ambient color

D- Diffuse color

S- Specular color.

It is common though to use the same color for both Diffuse and specular term,unless you are after some really fancy light effects.

The lighting model is also called Blinn-Phong,and boils down to the following pseudo equation:

Shaded Pixel = Ambient Term + Diffuse Term + Specular Term

Now,of course,you don't just place the values passed with those uniforms into that oversimplified equation.Ambient color comes usually as is,while the final diffuse and specular terms terms are calculated based on the vector products evaluation between light,view directions. But ADS are the basic properties of any light type.In addition to these,some light types have extra properties.For example, Spot light has cone angle and Radius,and even falloff feather properties to control light cone.It is also common to have light intensity property as a scalar, to multiply diffuse, as well as specular terms to strengthen the light intensity.

Regarding the material properties,you have got the standard ones:

MA - Ambient reflectivity

MD - Diffuse reflectivity

MS - Specular reflectivity

Shininess - (Scalar), Specular shininess factor

These you would pass into your program as a part of material's uniforms.The first 3 are combined in the final shading formula,where those are multiplied with their counterparts from the lights props (see at the top).Shininess factor is used to modify the amount of specular effect on the surface.It is important to note that it is up to you using MA,MD,MS ,or not.Basically,if you have a diffuse color property of a material,it is enough.Shininess though is quite important prop as it controls the specular spread amount on the surface Of course you're free to make up and use additional props both for lights and for materials.Here is a good read regarding ADS shading.