OpenGL tutorial 6 (lighting)
Simulate the real world
the Final output color is calculated by multiplying the object color by the light source color.
if the object RGB color is (1.0, 0.5, 0.0) and light source color is (1.0, 0.0, 0.0), the Final output color will be (1.0, 0.0, 0.0)
For more reality
lets simulate the color of the light hitting the object, so next we'll want to simulate the intensity of that light.
the higher angle between a surface and a source of light is, the less intense color is on that surface
you can clearly see the gradient of the intensity along the curve of the sphere
To do this, we need the position of the light, and the slope of the object surface (for the angle)
the traditional way of calculating the light intensity is to represent the slope of the surface by a normal vector.
Normals are unit vectors (aka vectors of length one)
it help us calculate how light should act on a certain object.
type of normals
these can be either perpendicular to the surface of one triangle (called face normals) or arranged in a different way such as being perpendicular to the plane created by all the adjacent vertices(called vertex normals)
first option create the flat shading, second option create smooth shading
we can choose depends on our mesh
set normal vector (Vertex data)
// Vertices coordinates
GLfloat vertices[] =
{ // COORDINATES / COLORS / TexCoord / NORMALS //
-0.5f, 0.0f, 0.5f, 0.83f, 0.70f, 0.44f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, // Bottom side
-0.5f, 0.0f, -0.5f, 0.83f, 0.70f, 0.44f, 0.0f, 5.0f, 0.0f, -1.0f, 0.0f, // Bottom side
0.5f, 0.0f, -0.5f, 0.83f, 0.70f, 0.44f, 5.0f, 5.0f, 0.0f, -1.0f, 0.0f, // Bottom side
0.5f, 0.0f, 0.5f, 0.83f, 0.70f, 0.44f, 5.0f, 0.0f, 0.0f, -1.0f, 0.0f, // Bottom side
-0.5f, 0.0f, 0.5f, 0.83f, 0.70f, 0.44f, 0.0f, 0.0f, -0.8f, 0.5f, 0.0f, // Left Side
-0.5f, 0.0f, -0.5f, 0.83f, 0.70f, 0.44f, 5.0f, 0.0f, -0.8f, 0.5f, 0.0f, // Left Side
0.0f, 0.8f, 0.0f, 0.92f, 0.86f, 0.76f, 2.5f, 5.0f, -0.8f, 0.5f, 0.0f, // Left Side
-0.5f, 0.0f, -0.5f, 0.83f, 0.70f, 0.44f, 5.0f, 0.0f, 0.0f, 0.5f, -0.8f, // Non-facing side
0.5f, 0.0f, -0.5f, 0.83f, 0.70f, 0.44f, 0.0f, 0.0f, 0.0f, 0.5f, -0.8f, // Non-facing side
0.0f, 0.8f, 0.0f, 0.92f, 0.86f, 0.76f, 2.5f, 5.0f, 0.0f, 0.5f, -0.8f, // Non-facing side
0.5f, 0.0f, -0.5f, 0.83f, 0.70f, 0.44f, 0.0f, 0.0f, 0.8f, 0.5f, 0.0f, // Right side
0.5f, 0.0f, 0.5f, 0.83f, 0.70f, 0.44f, 5.0f, 0.0f, 0.8f, 0.5f, 0.0f, // Right side
0.0f, 0.8f, 0.0f, 0.92f, 0.86f, 0.76f, 2.5f, 5.0f, 0.8f, 0.5f, 0.0f, // Right side
0.5f, 0.0f, 0.5f, 0.83f, 0.70f, 0.44f, 5.0f, 0.0f, 0.0f, 0.5f, 0.8f, // Facing side
-0.5f, 0.0f, 0.5f, 0.83f, 0.70f, 0.44f, 0.0f, 0.0f, 0.0f, 0.5f, 0.8f, // Facing side
0.0f, 0.8f, 0.0f, 0.92f, 0.86f, 0.76f, 2.5f, 5.0f, 0.0f, 0.5f, 0.8f // Facing side
};
// Indices for vertices order
GLuint indices[] =
{
0, 1, 2, // Bottom side
0, 2, 3, // Bottom side
4, 6, 5, // Left side
7, 9, 8, // Non-facing side
10, 12, 11, // Right side
13, 15, 14 // Facing side
};
The normals will be different on each side of the pyramid.
some of your normals are not unit length, it does not matter since we can just normalize them in the shader later
if you change vertex data(Vertices[]), or indices
you have to check the glVertexAttribPointer(it will be in your VAO.cpp), glDrawElements function
And if you add a new data in vertex data (like normal or texture), you have to include the data in the vertex shader file.
in order that to export the data to fragment shader, we declare the data to the outputs
set light position and light color
//light.vert
#version 440 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 camMatrix;
void main()
{
gl_Position = camMatrix * model * vec4(aPos, 1.0);
}
the light source is denoted by the cube.
as we do the last post, in window, the object is appeared by calculation with multiplying the camera coordinates and object coordinates
To set the light color, we'll want to export it to both the light fragment shader, and the shader of our object
In the light one we'll simply use it as the color of the object, while in the object shader multifly the texture and light
//light.frag
#version 440 core
out vec4 FragColor;
uniform vec4 lightColor;
void main()
{
FragColor = lightColor;
}
the lightColor is uniform variable, so we can change it in our main.cpp
(i'll not decribe the lightsource vertex or index, find out in https://github.com/VictorGordan/opengl-tutorials)
//Main.cpp
...
lightShader.Activate();
glUniformMatrix4fv(glGetUniformLocation(lightShader.ID, "model"), 1, GL_FALSE, glm::value_ptr(lightModel));
glUniform4f(glGetUniformLocation(lightShader.ID, "lightColor"), lightColor.x, lightColor.y, lightColor.z, lightColor.w);
....
While() {
...
// Tells OpenGL which Shader Program we want to use
lightShader.Activate();
// Export the camMatrix to the Vertex Shader of the light cube
camera.Matrix(lightShader, "camMatrix");
// Bind the VAO so OpenGL knows to use it
lightVAO.Bind();
// Draw primitives, number of indices, datatype of indices, index of indices
glDrawElements(GL_TRIANGLES, sizeof(lightIndices) / sizeof(int), GL_UNSIGNED_INT, 0);
...
}
set fragment shader 1
the main object color setted by multiflying the texture and light color
//default.frag
#version 440 core
out vec4 FragColor;
in vec3 color;
in vec2 texCoord;
uniform sampler2D tex0;
uniform vec4 lightColor;
void main()
{
FragColor = texture(tex0, texCoord) * lightColor;
}
set the vertex shader
we'll include the normal layout and also export it to the fragment shader, along with the curren position(crntPos).
use these things to calculate the direction of light.
//default.vert
#version 440 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTex;
layout (location = 3) in vec3 aNormal;
out vec3 color;
out vec2 texCoord;
out vec3 Normal;
out vec3 crntPos;
uniform mat4 camMatrix;
uniform mat4 model;
void main()
{
crntPos = vec3(model * vec4(aPos, 1.0f));
gl_Position = camMatrix * vec4(crntPos, 1.0);
color = aColor;
texCoord = aTex;
Normal = aNormal;
}
set the ambient, diffuse, specular light
Ambient lighting: even when it is dark there is usually still some light somewhere in the world (the moon, a distant light)
so objects are almost never completely dark.
To simulate this we use an ambient lighting constant that always gives the object some color.
Diffuse lighting: simulates the directional impact a light object has on an object.
This is the most visually significant component of the lighting model.
The more a part of an object faces the light source, the brighter it becomes.
Specular lighting: simulates the bright spot of a light that appears on shiny objects.
Specular highlights are more inclined to the color of the light than the color of the object.
first, i will set the diffuse light.
for the consistency purposes i'll also import the model matrix for the main object.
also, it'll set in the fragment shader too.
we declare the normal vector and current position of object, and also the light source position.
in the main function, we normalize the normal vector
and then get the lightDirection vector by simply subtracting the crntPos from the lightPos and normalizing it
(note that, the lightDirection represent the direction from the object towards the light source.)
now we have the two vectors
the lighting that we are implementing now is called diffuse lighting (diffuse)
the larger the angle between the two vectors the less intensity the light has.
we can calculate this with dot product, and the intensity cant minous, so we doing the max function by 0
//default.frag
#version 440 core
out vec4 FragColor;
in vec3 color;
in vec2 texCoord;
in vec3 Normal;
in vec3 crntPos;
uniform sampler2D tex0;
uniform vec4 lightColor;
uniform vec3 lightPos;
void main()
{
vec3 normal = normalize(Normal);
vec3 lightDirection = normalize(lightPos - crntPos);
float diffuse = max(dot(normal, lightDirection), 0.0f);
FragColor = texture(tex0, texCoord) * lightColor * diffuse;
}
set the diffuse, specular light
i just set the ambient light is 0.2f
but for the specular, we have to know the view direction and reflection direction.
the view direction can be seen via camera position vector - object position vector
(note that, view direction represent the direction from the object towards to camera postion)
and the reflection direction can be seen via reflet function with
-(light direction(light position - object position)) and normal vector
#version 330 core
// Outputs colors in RGBA
out vec4 FragColor;
// Imports the color from the Vertex Shader
in vec3 color;
// Imports the texture coordinates from the Vertex Shader
in vec2 texCoord;
// Imports the normal from the Vertex Shader
in vec3 Normal;
// Imports the current position from the Vertex Shader
in vec3 crntPos;
// Gets the Texture Unit from the main function
uniform sampler2D tex0;
// Gets the color of the light from the main function
uniform vec4 lightColor;
// Gets the position of the light from the main function
uniform vec3 lightPos;
// Gets the position of the camera from the main function
uniform vec3 camPos;
void main()
{
// ambient lighting
float ambient = 0.20f;
// diffuse lighting
vec3 normal = normalize(Normal);
vec3 lightDirection = normalize(lightPos - crntPos);
float diffuse = max(dot(normal, lightDirection), 0.0f);
// specular lighting
float specularLight = 0.50f;
vec3 viewDirection = normalize(camPos - crntPos);
vec3 reflectionDirection = reflect(-lightDirection, normal);
float specAmount = pow(max(dot(viewDirection, reflectionDirection), 0.0f), 8);
float specular = specAmount * specularLight;
// outputs final color
FragColor = texture(tex0, texCoord) * lightColor * (diffuse + ambient + specular);
}
as you can see, the final color is combined with diffuse, ambient and specular.
specular map
in the default image, we can see the area that shouldn't reflect much light such as the cracks in the wood, still some what do so. And the areas that should reflect a lot of light such as the smooth tops of the wood, don't really do so.
this is the image texture and specular map texture.
the specular maps are black and white textures that tell the program where specular light should appear and how strong it should be
set specular map code
This map is consist with black and white texture, so it appears as a single channel image.
we can use the GL_RED format.
//main.cpp
Texture planksSpec("Resources/planksSpec.png", GL_TEXTURE_2D, 1, GL_RED, GL_UNSIGNED_BYTE);
planksSpec.texUnit(shaderProgram, "tex1", 1);
while()
{
...
planksSpec.Bind();
...
}
in main.cpp, we have to set the specular map texture only one channel (.r)
//default.frag
#version 330 core
// Outputs colors in RGBA
out vec4 FragColor;
// Imports the color from the Vertex Shader
in vec3 color;
// Imports the texture coordinates from the Vertex Shader
in vec2 texCoord;
// Imports the normal from the Vertex Shader
in vec3 Normal;
// Imports the current position from the Vertex Shader
in vec3 crntPos;
// Gets the Texture Units from the main function
uniform sampler2D tex0;
uniform sampler2D tex1;
// Gets the color of the light from the main function
uniform vec4 lightColor;
// Gets the position of the light from the main function
uniform vec3 lightPos;
// Gets the position of the camera from the main function
uniform vec3 camPos;
void main()
{
// ambient lighting
float ambient = 0.20f;
// diffuse lighting
vec3 normal = normalize(Normal);
vec3 lightDirection = normalize(lightPos - crntPos);
float diffuse = max(dot(normal, lightDirection), 0.0f);
// specular lighting
float specularLight = 0.50f;
vec3 viewDirection = normalize(camPos - crntPos);
vec3 reflectionDirection = reflect(-lightDirection, normal);
float specAmount = pow(max(dot(viewDirection, reflectionDirection), 0.0f), 16);
float specular = specAmount * specularLight;
// outputs final color
FragColor = (texture(tex0, texCoord) * (diffuse + ambient) + texture(tex1, texCoord).r * specular) * lightColor;
}