I’m currently a student at the SAE Institute of Geneva, in the Game Programming section. For the computer graphic module, our class began to work on creating a basic clone of Minecraft, using the custom game engine made in C++ : Neko engine.
Our main goal was to implement basic functions, such as:
In this project, my goal was to implement the Light & Shadows.
Minecraft Like context
In the Minecraft Like game, the player roam across the proceduraly generated terrain. This terrain is composed of chunks and blocs. For each bloc, a shadow need to be displayed.
Light Structure
To start, we need a light source, I created a simple struct which contains all the needed information about the light source.
struct DirectionalLight
{
Vec3f position = Vec3f::one * 10.0f;
Vec3f direction = Vec3f(-1.0f, -1.0f, 0.0f);
Color3 color = Vec3f(1.0f, 1.0f, 1.0f);
float intensity = 2.0f;
float ambientStrength = 1.0f;
float diffuseStrength = 1.0f;
float specularStrength = 1.0f;
int specularPow = 32;
};
DirectionalLight directionalLight_;
I also created a vertex shader and a fragment shader to render the cubes with the Blinn-Phong lighting model. After some coding for the implementation of basic material structure, I got my first cube casted with a light !
Shadows
Then, I needed to display some shadows, and believe me it was not an easy task.
First of all, my current rendering process was the following:
To create a shadow I first need to create a depth buffer, with a depth shader, which should give me something like this :
Then, I have to change my rendering process to something like this:
The directional light is modeled to be infinitely far away, but we need to render the scene from the light perspective, all rays are parallel.
In the shadow calculation I integrated multiple technics to render the shadow correctly, and to remove undesired artefacts.
float ShadowCalculation(vec4 fragPosLightSpace)
{
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if(projCoords.x < 0.0 || projCoords.x > 1.0 ||projCoords.y < 0.0 || projCoords.y > 1.0)
return 0.0;
float closestDepth = texture(shadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
if(currentDepth > 1.0)
return 0.0;
vec3 normal = normalize(Normal);
vec3 lightDir = -light.direction;
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
for(int x = -1; x <= 1; ++x)
{
for(int y = -1; y <= 1; ++y)
{
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
if(projCoords.z > 1.0)
shadow = 0.0;
return shadow;
}
Before it gave me correct results, I had some strange complications, let’s check some of them :
Conclusion