Description
The Torch was a group project during my 2nd year at Game Technology & Producing at Saxion. A 3D adventure puzzle game using a self made C++ OpenGL engine. Darkness is everywhere, but with the power of "The Torch" you can move orbs of light from one source to another, making a pathway to the next area.
There is one bug in the game. If you watch the whole intro without skipping then the statues will be invisible to you. Press enter during the intro to skip it and this won't happen.
My contribution
- Deferred rendering
- Lighting (dynamic) (with bounding spheres)
- Shadow maps (dynamic)
- Fog
- Transform class
- Level loading through XML parsing (from Unity exported scenes)
- Player controls
- Object spawning
- Switch object
- Bugfixing and additions in other code
Code snippets
Code for deferred rendering. A bit dirty but it works. Each pointlight has a bounding sphere and through stencil tests only the lit geometry is rendered for lighting calculations. Shadows are dynamic and fade out when far away. Objects can be toggled to cast shadows or not.
void SceneManager::geometryPass(){
glDepthFunc(GL_LESS);
glDisable(GL_DEPTH_CLAMP);
_gBuffer.StartFrame();
glm::mat4 id;
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
geometryShader->use();
_gBuffer.BindForGeomPass(); // set draw buffer to gbuffer
glDepthMask(GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clean up buffer
glEnable(GL_DEPTH_TEST); // enable depth testing
if( _camera ) _camera->render( geometryShader ); // set perspective and view matrices
for ( auto j = _objects.begin(); j != _objects.end(); ++j ) {
GameObject * object = (GameObject*) *j;
object->render( geometryShader ); // render data into buffer
}
if(_terrain != NULL){
terrainShader->use();
if( _camera ) _camera->render( terrainShader );
_terrain->render(terrainShader);
}
glDepthMask(GL_FALSE);
glEnable(GL_DEPTH_CLAMP);
glDepthFunc(GL_LEQUAL);
}
void SceneManager::pointLightPass(){
glm::mat4 id;
for (unsigned int i = 0 ; i < _pointLights.size(); i++) { bool castShadow = false; float camDistance = glm::length(_pointLights[i]->getPosition() - _camera->getPosition());
if(_pointLights[i]->_castShadow && camDistance < 20 ){ castShadow = true; shadowPass(_pointLights[i]); // shadows glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _gBuffer.m_fbo); // dirty } stencilPass(_pointLights[i]); pointLightShader->use();
_gBuffer.BindForLightPass(pointLightShader); // give textures
glStencilFunc(GL_NOTEQUAL, 0, 0xFF);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE);
glEnable(GL_CULL_FACE);
glCullFace( GL_FRONT );
glViewport(0,0,windowX,windowY);
pointLightShader->setUniform(pointLightShader->gScreenSize, glm::vec2(windowX,windowY));
pointLightShader->setUniform(pointLightShader->cameraPos, _camera->getPosition());
if(_camera)
_camera->render(pointLightShader);
pointLightShader->setPointLightElement(0,_pointLights[i]);
if(castShadow){
pointLightShader->setCubeMap(pointLightShader->shadowMap, 20, _shadowMapFBO.m_shadowMap);
pointLightShader->setUniform(pointLightShader->useShadow,1.0f);
float shadowFadeFactor = 0;
if(camDistance >= 15){
shadowFadeFactor = 1-(20-camDistance)/5;
}
pointLightShader->setUniform(pointLightShader->shadowFadeFactor,shadowFadeFactor);
}
else{
pointLightShader->setUniform(pointLightShader->useShadow,0.0f);
}
_pointLights[i]->renderBoundingSphere(pointLightShader);
glCullFace(GL_BACK);
glDisable(GL_BLEND);
}
}
void SceneManager::shadowPass(PointLight * light){
glDisable(GL_BLEND);
glm::mat4 id;
glViewport(0,0,shadowRes,shadowRes);
shadowShader->use();
glEnable(GL_CULL_FACE);
glCullFace( GL_BACK );
glEnable( GL_DEPTH_TEST ); // must be enabled after use program
glDepthMask(GL_TRUE);
glClearColor(FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX);
_shadowMapFBO.BindForWriting();
float shadowFOV = 90.0f/180.f*M_PI; // 90 degrees to cover each side
glm::mat4 depthProjectionMatrix = glm::perspective( shadowFOV, 1.f, 0.1f, 100.0f );
shadowShader->setPointLightElement(0,light);
shadowShader->setUniform( shadowShader->projection, depthProjectionMatrix );
for (int k = 0 ; k < 6 ; k++) { _shadowMapFBO.WriteCubeFace(gCameraDirections[k].CubemapFace); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glm::mat4 depthViewMatrix = glm::lookAt( light->getPosition(), light->getPosition()+gCameraDirections[k].Target, gCameraDirections[k].Up );
shadowShader->setUniform( shadowShader->view, depthViewMatrix );
for ( auto j = _objects.begin(); j != _objects.end(); ++j ) {
GameObject * object = (GameObject*) *j;
if(object->castShadows){
object->render( shadowShader );
}
}
}
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
}
void SceneManager::stencilPass(PointLight * pointLight){
glViewport(0,0,windowX,windowY);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
stencilShader->use();
glDrawBuffer(GL_NONE);
glEnable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glClear(GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_ALWAYS, 0, 0);
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_INCR_WRAP, GL_KEEP);
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_DECR_WRAP, GL_KEEP);
if(_camera)
_camera->render(stencilShader);
pointLight->renderBoundingSphere(stencilShader);
}
void SceneManager::dirLightPass(){
glm::mat4 id;
for (unsigned int i = 0 ; i < _directionalLights.size(); i++) { dirLightShader->use();
_gBuffer.BindForLightPass(dirLightShader); // give textures
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE);
glCullFace( GL_FRONT );
glViewport(0,0,windowX,windowY);
dirLightShader->setUniform(dirLightShader->gScreenSize, glm::vec2(windowX,windowY));
dirLightShader->setUniform(dirLightShader->cameraPos, _camera->getPosition());
dirLightShader->setUniform(dirLightShader->view,id);
dirLightShader->setUniform(dirLightShader->projection,id);
dirLightShader->setDirLightElement(0,_directionalLights[i]);
screenQuad->render(dirLightShader,id);
}
glDisable(GL_BLEND);
glCullFace(GL_BACK);
}
void SceneManager::fogPass(){
glm::mat4 id;
fogShader->use();
_gBuffer.BindForLightPass(fogShader); // give textures
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
glCullFace( GL_FRONT );
glViewport(0,0,windowX,windowY);
fogShader->setUniform(fogShader->gScreenSize, glm::vec2(windowX,windowY));
fogShader->setUniform(fogShader->cameraPos, _camera->getPosition());
fogShader->setUniform(fogShader->view,id);
fogShader->setUniform(fogShader->projection,id);
screenQuad->render(fogShader,id);
glDisable(GL_BLEND);
glCullFace(GL_BACK);
}
void SceneManager::showTextures(){
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
_gBuffer.ShowBuffer();
GLsizei HalfWidth = (GLsizei)(windowX / 2.0f);
GLsizei HalfHeight = (GLsizei)(windowY / 2.0f);
_gBuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_POSITION);
glBlitFramebuffer(0, 0, windowX, windowY,
0, 0, HalfWidth, HalfHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
_gBuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_DIFFUSE);
glBlitFramebuffer(0, 0, windowX, windowY,
0, HalfHeight, HalfWidth, windowY, GL_COLOR_BUFFER_BIT, GL_LINEAR);
_gBuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_NORMAL);
glBlitFramebuffer(0, 0, windowX, windowY,
HalfWidth, HalfHeight, windowX, windowY, GL_COLOR_BUFFER_BIT, GL_LINEAR);
}
void SceneManager::finalPass(){
_gBuffer.BindForFinalPass();
glBlitFramebuffer(0, 0, windowX, windowY,
0, 0, windowX, windowY, GL_COLOR_BUFFER_BIT, GL_LINEAR);
}
// render pipeline
void SceneManager::render( sf::RenderWindow * window )
{
if(_camera != NULL){
geometryPass();
glEnable(GL_STENCIL_TEST);
pointLightPass();
glDisable(GL_STENCIL_TEST);
dirLightPass();
//showTextures();
if(showFog && levelNumber != 0){
fogPass();
}
finalPass();
}
window->display();
if(levelToLoad != ""){
std::cout << "loading level" << std::endl;
loadLevel(levelToLoad,levelNumber);
}
}
Transform class
#include "Transform.hpp"
# define M_PI 3.14159265358979323846 /* pi */
Transform::Transform()
: parentTransform(NULL)
{
}
Transform::~Transform()
{
//dtor
}
// return position
glm::vec3 Transform::getPosition(){
glm::mat4 trans = getWorldTransform();
return glm::vec3 (trans[3]);
}
glm::vec3 Transform::getScale(){
return glm::vec3 (scaleMatrix[0][0],scaleMatrix[1][1],scaleMatrix[2][2]);
}
// set position TODO
void Transform::setPosition(glm::vec3 newPosition){
glm::vec4 temp = glm::vec4( newPosition, 1.0f );
if(parentTransform)
temp = glm::inverse(parentTransform->getWorldTransform()) * glm::vec4( newPosition, 1.0f );
translationMatrix[3] = temp;
}
void Transform::setRotation(glm::vec3 newRotation){
eulerRotation = newRotation;
while (eulerRotation.x >= 180) eulerRotation.x -= 360;
while (eulerRotation.x < -180) eulerRotation.x += 360; while (eulerRotation.y >= 180) eulerRotation.y -= 360;
while (eulerRotation.y < -180) eulerRotation.y += 360; while (eulerRotation.z >= 180) eulerRotation.z -= 360;
while (eulerRotation.z < -180) eulerRotation.z += 360; rotationMatrix = glm::eulerAngleYXZ(eulerRotation.y*M_PI/180, eulerRotation.x*M_PI/180, eulerRotation.z*M_PI/180); } void Transform::setScale(glm::vec3 newScale){ scaleMatrix = glm::scale( glm::mat4(), newScale ); } // calculate world transform with parents, maybe not efficient doing this each time you want world transform? glm::mat4 Transform::getWorldTransform(){ glm::mat4 tempTransform = getLocalTransform(); if(parentTransform){ tempTransform = parentTransform->getWorldTransform() * tempTransform;
}
return tempTransform;
}
// translate transform, update children
void Transform::translate(glm::vec3 translation, bool bWorldSpace){
if (!bWorldSpace){
glm::vec4 rotatedTranslation = rotationMatrix * glm::vec4(translation,1);
translationMatrix = glm::translate(translationMatrix, glm::vec3(rotatedTranslation)); // world space
}
else{
translationMatrix = glm::translate(translationMatrix, translation);
}
}
void Transform::scale(glm::vec3 scaleVector){
scaleMatrix = glm::scale( scaleMatrix, scaleVector );
}
void Transform::eulerRotate(glm::vec3 deltaRotation, bool bWorldSpace){
eulerRotation += deltaRotation;
while (eulerRotation.x >= 180) eulerRotation.x -= 360;
while (eulerRotation.x < -180) eulerRotation.x += 360; while (eulerRotation.y >= 180) eulerRotation.y -= 360;
while (eulerRotation.y < -180) eulerRotation.y += 360; while (eulerRotation.z >= 180) eulerRotation.z -= 360;
while (eulerRotation.z < -180) eulerRotation.z += 360; if(bWorldSpace) rotationMatrix = glm::eulerAngleYXZ(eulerRotation.y*M_PI/180, eulerRotation.x*M_PI/180, eulerRotation.z*M_PI/180); else{ glm::mat4 tempMatrix = (glm::mat4) glm::eulerAngleYXZ(deltaRotation.y*M_PI/180, deltaRotation.x*M_PI/180, deltaRotation.z*M_PI/180); rotationMatrix = rotationMatrix * tempMatrix; } } // add a child transform to this transform void Transform::addChild(Transform* child){ childTransforms.push_back(child); child->setParent(this);
}
void Transform::removeChild( Transform * child ){
std::vector<Transform*>::iterator myIterator = std::find(childTransforms.begin(), childTransforms.end(), child);
if (myIterator != childTransforms.end())
{
Transform* toRemove = (Transform*) *myIterator;
toRemove->removeParent();
childTransforms.erase( myIterator );
}
}
glm::mat4 Transform::getLocalTransform(){
return translationMatrix * rotationMatrix * scaleMatrix;
}
// called by addChild when this transform is parented to a parent transform
void Transform::setParent(Transform* parent){
parentTransform = parent;
setScale(getScale()/parent->getScale());
}
void Transform::removeParent(){
setScale(getScale()*parentTransform->getScale());
parentTransform = NULL;
}
glm::vec3 Transform::eulerToDirection(glm::vec3 angle){
glm::vec3 dir;
float yaw = angle.y*M_PI/180;
float pitch = angle.x*M_PI/180;
dir.x = sin(yaw);
dir.y = (sin(pitch)*cos(yaw));
dir.z = -(cos(pitch)*cos(yaw));
return dir;
}