Question

I'm currently creating a 3D scene, and I was wondering what would be the best way to go about creating a point light that follows the camera. So far my code is as follows:

Vertex shader:

// Materials
uniform vec3 materialAmbient;
uniform vec3 materialDiffuse;
uniform vec3 materialSpecular;
uniform float materialShininess;

uniform float att_quadratic = 0.1;

// Lights
struct AMBIENT
{   
    vec3 color;
};
struct DIRECTIONAL
{   
    vec3 direction;
    vec3 diffuse;
};
struct POINT
{   int on;
    int frag;
    vec3 position;
    vec3 diffuse;
    vec3 specular;
};

uniform AMBIENT lightAmbient;
uniform DIRECTIONAL lightDir;
uniform POINT lightPoint1, lightPoint2, lightPoint3;

layout (location = 0) in vec3 aVertex;
layout (location = 2) in vec3 aNormal;
layout (location = 3) in vec2 aTexCoord;

// Output (sent to Fragment Shader)
out vec4 color;
out vec4 position;
out vec4 normal;
out vec2 texCoord;
out float gravelFactor;     // gravelFactor is 1 within the gravel circle and 0 outside the circle

vec4 compAmbient(vec3 material, AMBIENT light)
{
    return vec4(material * light.color, 1);
}

vec4 compDirectional(vec3 material, DIRECTIONAL light)
{
    vec3 L = normalize(mat3(matrixView) * light.direction).xyz;
    float NdotL = dot(normal.xyz, L);
    if (NdotL > 0)
        return vec4(light.diffuse * material * NdotL, 1);
    else
        return vec4(0, 0, 0, 1);
}

vec4 compPoint(vec3 materialDiffuse, vec3 materialSpecular, float materialShininess, POINT light)
{
    vec4 result = vec4(0, 0, 0, 1);

    // diffuse
    vec3 L = normalize(matrixView * vec4(light.position, 1) - position).xyz;
    float NdotL = dot(L, normal.xyz);
    if (NdotL > 0)
        result += vec4(light.diffuse * materialDiffuse, 1) * NdotL;

    // specular
    vec3 V = normalize(-position.xyz);
    vec3 R = reflect(-L, normal.xyz);
    float RdotV = dot(R, V);
    if (NdotL > 0 && RdotV > 0)
        result += vec4(light.specular * materialSpecular * pow(RdotV, materialShininess), 1);

    //attentuation
    float dist = length(matrixView * vec4(light.position, 1) - position); 
    float att = 1 / (att_quadratic * dist * dist); 

    return result * att;
}

void main(void) 
{
    // calculate position & normal
    position = matrixModelView * vec4(aVertex, 1.0);
    gl_Position = matrixProjection * position;
    normal = vec4(normalize(mat3(matrixModelView) * aNormal), 1);

    // calculate texture coordinate
    texCoord = aTexCoord;

    // calculate the colour
    color = vec4(0, 0, 0, 0);

    // ambient light
    color += compAmbient(materialAmbient, lightAmbient);

    // directional lights
    color += compDirectional(materialDiffuse, lightDir);

    // point lights
    if (lightPoint1.on == 1 && lightPoint1.frag == 0)
        color += compPoint(materialDiffuse, materialSpecular, materialShininess, lightPoint1);
    if (lightPoint2.on == 1 && lightPoint2.frag == 0)
        color += compPoint(materialDiffuse, materialSpecular, materialShininess, lightPoint2);
    if (lightPoint3.on == 1 && lightPoint3.frag == 0)
        color += compPoint(materialDiffuse, materialSpecular, materialShininess, lightPoint3);

    // calculation of the gravelFactor:
    // 0 outside the 8-unit radius from the center
    // 1 within 7 units from the centre
    // between 0 and 1 at the border zone
    gravelFactor = clamp(-length(aVertex.xz) + 8, 0, 1);
}

Fragment Shader:

// input variables
in vec4 color;
in vec4 position;
in vec4 normal;
in vec2 texCoord;
in float gravelFactor;

// output variable
out vec4 outColor;

// uniforms - material parameters
uniform vec3 materialAmbient;
uniform vec3 materialDiffuse;
uniform vec3 materialSpecular;
uniform float materialShininess;

uniform float att_quadratic = 0.1;

// This uniform variable may be used to take different actions for the terrain and not-terrain
uniform int terrain;

// view matrix (needed for lighting)
uniform mat4 matrixView;

struct POINT
{   int on;
    int frag;
    vec3 position;
    vec3 diffuse;
    vec3 specular;
};

uniform POINT lightPoint1, lightPoint2, lightPoint3;

vec4 compPoint(vec3 materialDiffuse, vec3 materialSpecular, float materialShininess, POINT light)
{
    vec4 result = vec4(0, 0, 0, 1);

    // diffuse
    vec3 L = normalize(matrixView * vec4(light.position, 1) - position).xyz;
    float NdotL = dot(L, normal.xyz);
    if (NdotL > 0)
        result += vec4(light.diffuse * materialDiffuse, 1) * NdotL;

    // specular
    vec3 V = normalize(-position.xyz);
    vec3 R = reflect(-L, normal.xyz);
    float RdotV = dot(R, V);
    if (NdotL > 0 && RdotV > 0)
        result += vec4(light.specular * materialSpecular * pow(RdotV, materialShininess), 1);

    //attentuation
    float dist = length(matrixView * vec4(light.position, 1) - position); 
    float att = 1 / (att_quadratic * dist * dist); 

    return result * att;
}

// Texture Samplers
uniform sampler2D textureGrass;
uniform sampler2D textureGravel;
uniform sampler2D texture;
uniform sampler2D bumpmap;
uniform sampler2D textureNormal;

vec4 bump_normal = texture(textureNormal, texCoord.st) * 2 - 1; 

void main(void) 
{
    outColor = color;

    if (lightPoint1.on == 1 && lightPoint1.frag == 1)
        outColor += compPoint(materialDiffuse, materialSpecular, materialShininess, lightPoint1);
    if (lightPoint2.on == 1 && lightPoint2.frag == 1)
        outColor += compPoint(materialDiffuse, materialSpecular, materialShininess, lightPoint2);
    if (lightPoint3.on == 1 && lightPoint3.frag == 1)
        outColor += compPoint(materialDiffuse, materialSpecular, materialShininess, lightPoint3);

    if (terrain == 1)
    {
        // Rendering Terrain
        // Terrain is a mix of the Grass and Gravel texture
        outColor *= mix(texture(textureGrass, texCoord.st), texture(textureGravel, texCoord.st), gravelFactor);
    }
    else
    {
        outColor *= texture(texture, texCoord.st) + bump_normal;
    }
}

And the declaration of my point lights:

//setup point light1
glUniform1i(glGetUniformLocation(idProg, "lightPoint1.on"), 1);
glUniform1i(glGetUniformLocation(idProg, "lightPoint1.frag"), 1);
glUniform3f(glGetUniformLocation(idProg, "lightPoint1.position"), 0, 3.1, 0.0);
glUniform3f(glGetUniformLocation(idProg, "lightPoint1.diffuse"), 1.0, 0.0, 0.0);
glUniform3f(glGetUniformLocation(idProg, "lightPoint1.specular"),  1.0, 0.0, 0.0);

What would be the best way to approach this problem? Should the position of the light be changed within the main code or within the shaders? How could I do this?

Was it helpful?

Solution

I would definitely change the light position in your main code and then pass it to your shaders.

So, for instance, your code to change the position would look something like this:

//called whenever you redraw your scene
void render() 
{
    //or however you want to position the light relative to your camera
    glUniform3f
        (
        glGetUniformLocation(idProg, "lightPoint1.position"), 
        get_camera_pos_x(), 
        get_camera_pos_y(), 
        get_camera_pos_z()
        );
    glUniform3f
        (            
        glGetUniformLocation(idProg, "lightPoint1.direction"), 
        get_camera_dir_x(), 
        get_camera_dir_y(), 
        get_camera_dir_z()
        )
    //...rest of your drawing code...
}

The main advantage of doing it this way is you are eliminating redundant calculations. If you had your vertex shader updating the light position in response to your camera position, it would work but you would be repeating that calculation many times each frame. Remember, a vertex shader is executed on every vertex that you draw. There's no need to recalculate the light's position on each vertex if it's going to be the same each time.

Update per OP in comments: OP stated that he wanted to be able to change the direction of the light using the camera, like a flashlight. To do this, you will need to add an additional uniform to your light struct in the shader. I've called it "direction" (a normalized vec3) above. You can then calculate the camera's direction in your main code and pass it to the shader like normal. What you do with it in the shader is up to you, but this tutorial might help

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