| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- // ----------------------------------------------------------------
- // From Game Programming in C++ by Sanjay Madhav
- // Copyright (C) 2017 Sanjay Madhav. All rights reserved.
- //
- // Released under the BSD License
- // See LICENSE.txt for full details.
- // ----------------------------------------------------------------
- #include "Renderer.h"
- #include "Texture.h"
- #include "Mesh.h"
- #include <algorithm>
- #include "Shader.h"
- #include "VertexArray.h"
- #include "SpriteComponent.h"
- #include "MeshComponent.h"
- #include "UIScreen.h"
- #include "Game.h"
- #include <GL/glew.h>
- #include "SkeletalMeshComponent.h"
- #include "GBuffer.h"
- #include "PointLightComponent.h"
- Renderer::Renderer(Game* game)
- :mGame(game)
- ,mSpriteShader(nullptr)
- ,mMeshShader(nullptr)
- ,mSkinnedShader(nullptr)
- ,mMirrorBuffer(0)
- ,mMirrorTexture(nullptr)
- ,mGBuffer(nullptr)
- ,mGGlobalShader(nullptr)
- ,mGPointLightShader(nullptr)
- {
- }
- Renderer::~Renderer()
- {
- }
- bool Renderer::Initialize(float screenWidth, float screenHeight)
- {
- mScreenWidth = screenWidth;
- mScreenHeight = screenHeight;
- // Set OpenGL attributes
- // Use the core OpenGL profile
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
- // Specify version 3.3
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
- // Request a color buffer with 8-bits per RGBA channel
- SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
- SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
- SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
- SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
- SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
- // Enable double buffering
- SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
- // Force OpenGL to use hardware acceleration
- SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
- mWindow = SDL_CreateWindow("Game Programming in C++ (Chapter 13)", 100, 100,
- static_cast<int>(mScreenWidth), static_cast<int>(mScreenHeight), SDL_WINDOW_OPENGL);
- if (!mWindow)
- {
- SDL_Log("Failed to create window: %s", SDL_GetError());
- return false;
- }
- // Create an OpenGL context
- mContext = SDL_GL_CreateContext(mWindow);
- // Initialize GLEW
- glewExperimental = GL_TRUE;
- if (glewInit() != GLEW_OK)
- {
- SDL_Log("Failed to initialize GLEW.");
- return false;
- }
- // On some platforms, GLEW will emit a benign error code,
- // so clear it
- glGetError();
- // Make sure we can create/compile shaders
- if (!LoadShaders())
- {
- SDL_Log("Failed to load shaders.");
- return false;
- }
- // Create quad for drawing sprites
- CreateSpriteVerts();
- // Create render target for mirror
- if (!CreateMirrorTarget())
- {
- SDL_Log("Failed to create render target for mirror.");
- return false;
- }
-
- // Create G-buffer
- mGBuffer = new GBuffer();
- int width = static_cast<int>(mScreenWidth);
- int height = static_cast<int>(mScreenHeight);
- if (!mGBuffer->Create(width, height))
- {
- SDL_Log("Failed to create G-buffer.");
- return false;
- }
- // Load point light mesh
- mPointLightMesh = GetMesh("Assets/PointLight.gpmesh");
- return true;
- }
- void Renderer::Shutdown()
- {
- // Get rid of any render target textures, if they exist
- if (mMirrorTexture != nullptr)
- {
- glDeleteFramebuffers(1, &mMirrorBuffer);
- mMirrorTexture->Unload();
- delete mMirrorTexture;
- }
- // Get rid of G-buffer
- if (mGBuffer != nullptr)
- {
- mGBuffer->Destroy();
- delete mGBuffer;
- }
- // Delete point lights
- while (!mPointLights.empty())
- {
- delete mPointLights.back();
- }
- delete mSpriteVerts;
- mSpriteShader->Unload();
- delete mSpriteShader;
- mMeshShader->Unload();
- delete mMeshShader;
- SDL_GL_DeleteContext(mContext);
- SDL_DestroyWindow(mWindow);
- }
- void Renderer::UnloadData()
- {
- // Destroy textures
- for (auto i : mTextures)
- {
- i.second->Unload();
- delete i.second;
- }
- mTextures.clear();
- // Destroy meshes
- for (auto i : mMeshes)
- {
- i.second->Unload();
- delete i.second;
- }
- mMeshes.clear();
- }
- void Renderer::Draw()
- {
- // Draw to the mirror texture first
- Draw3DScene(mMirrorBuffer, mMirrorView, mProjection, 0.25f);
- // Draw the 3D scene to the G-buffer
- Draw3DScene(mGBuffer->GetBufferID(), mView, mProjection, 1.0f, false);
- // Set the frame buffer back to zero (screen's frame buffer)
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
- // Draw from the GBuffer
- DrawFromGBuffer();
-
- // Draw all sprite components
- // Disable depth buffering
- glDisable(GL_DEPTH_TEST);
- // Enable alpha blending on the color buffer
- glEnable(GL_BLEND);
- glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
- glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
- // Set shader/vao as active
- mSpriteShader->SetActive();
- mSpriteVerts->SetActive();
- for (auto sprite : mSprites)
- {
- if (sprite->GetVisible())
- {
- sprite->Draw(mSpriteShader);
- }
- }
-
- // Draw any UI screens
- for (auto ui : mGame->GetUIStack())
- {
- ui->Draw(mSpriteShader);
- }
- // Swap the buffers
- SDL_GL_SwapWindow(mWindow);
- }
- void Renderer::AddSprite(SpriteComponent* sprite)
- {
- // Find the insertion point in the sorted vector
- // (The first element with a higher draw order than me)
- int myDrawOrder = sprite->GetDrawOrder();
- auto iter = mSprites.begin();
- for (;
- iter != mSprites.end();
- ++iter)
- {
- if (myDrawOrder < (*iter)->GetDrawOrder())
- {
- break;
- }
- }
- // Inserts element before position of iterator
- mSprites.insert(iter, sprite);
- }
- void Renderer::RemoveSprite(SpriteComponent* sprite)
- {
- auto iter = std::find(mSprites.begin(), mSprites.end(), sprite);
- mSprites.erase(iter);
- }
- void Renderer::AddMeshComp(MeshComponent* mesh)
- {
- if (mesh->GetIsSkeletal())
- {
- SkeletalMeshComponent* sk = static_cast<SkeletalMeshComponent*>(mesh);
- mSkeletalMeshes.emplace_back(sk);
- }
- else
- {
- mMeshComps.emplace_back(mesh);
- }
- }
- void Renderer::RemoveMeshComp(MeshComponent* mesh)
- {
- if (mesh->GetIsSkeletal())
- {
- SkeletalMeshComponent* sk = static_cast<SkeletalMeshComponent*>(mesh);
- auto iter = std::find(mSkeletalMeshes.begin(), mSkeletalMeshes.end(), sk);
- mSkeletalMeshes.erase(iter);
- }
- else
- {
- auto iter = std::find(mMeshComps.begin(), mMeshComps.end(), mesh);
- mMeshComps.erase(iter);
- }
- }
- void Renderer::AddPointLight(PointLightComponent* light)
- {
- mPointLights.emplace_back(light);
- }
- void Renderer::RemovePointLight(PointLightComponent* light)
- {
- auto iter = std::find(mPointLights.begin(), mPointLights.end(), light);
- mPointLights.erase(iter);
- }
- Texture* Renderer::GetTexture(const std::string& fileName)
- {
- Texture* tex = nullptr;
- auto iter = mTextures.find(fileName);
- if (iter != mTextures.end())
- {
- tex = iter->second;
- }
- else
- {
- tex = new Texture();
- if (tex->Load(fileName))
- {
- mTextures.emplace(fileName, tex);
- }
- else
- {
- delete tex;
- tex = nullptr;
- }
- }
- return tex;
- }
- Mesh* Renderer::GetMesh(const std::string & fileName)
- {
- Mesh* m = nullptr;
- auto iter = mMeshes.find(fileName);
- if (iter != mMeshes.end())
- {
- m = iter->second;
- }
- else
- {
- m = new Mesh();
- if (m->Load(fileName, this))
- {
- mMeshes.emplace(fileName, m);
- }
- else
- {
- delete m;
- m = nullptr;
- }
- }
- return m;
- }
- void Renderer::Draw3DScene(unsigned int framebuffer, const Matrix4& view, const Matrix4& proj,
- float viewPortScale, bool lit)
- {
- // Set the current frame buffer
- glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
- // Set viewport size based on scale
- glViewport(0, 0,
- static_cast<int>(mScreenWidth * viewPortScale),
- static_cast<int>(mScreenHeight * viewPortScale)
- );
- // Clear color buffer/depth buffer
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- glDepthMask(GL_TRUE);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- // Draw mesh components
- // Enable depth buffering/disable alpha blend
- glEnable(GL_DEPTH_TEST);
- glDisable(GL_BLEND);
- // Set the mesh shader active
- mMeshShader->SetActive();
- // Update view-projection matrix
- mMeshShader->SetMatrixUniform("uViewProj", view * proj);
- // Update lighting uniforms
- if (lit)
- {
- SetLightUniforms(mMeshShader, view);
- }
- for (auto mc : mMeshComps)
- {
- if (mc->GetVisible())
- {
- mc->Draw(mMeshShader);
- }
- }
- // Draw any skinned meshes now
- mSkinnedShader->SetActive();
- // Update view-projection matrix
- mSkinnedShader->SetMatrixUniform("uViewProj", view * proj);
- // Update lighting uniforms
- if (lit)
- {
- SetLightUniforms(mSkinnedShader, view);
- }
- for (auto sk : mSkeletalMeshes)
- {
- if (sk->GetVisible())
- {
- sk->Draw(mSkinnedShader);
- }
- }
- }
- bool Renderer::CreateMirrorTarget()
- {
- int width = static_cast<int>(mScreenWidth) / 4;
- int height = static_cast<int>(mScreenHeight) / 4;
- // Generate a frame buffer for the mirror texture
- glGenFramebuffers(1, &mMirrorBuffer);
- glBindFramebuffer(GL_FRAMEBUFFER, mMirrorBuffer);
- // Create the texture we'll use for rendering
- mMirrorTexture = new Texture();
- mMirrorTexture->CreateForRendering(width, height, GL_RGB);
- // Add a depth buffer to this target
- GLuint depthBuffer;
- glGenRenderbuffers(1, &depthBuffer);
- glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer);
- glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer);
- // Attach mirror texture as the output target for the frame buffer
- glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mMirrorTexture->GetTextureID(), 0);
- // Set the list of buffers to draw to for this frame buffer
- GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0 };
- glDrawBuffers(1, drawBuffers);
- // Make sure everything worked
- if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
- {
- // If it didn't work, delete the framebuffer,
- // unload/delete the texture and return false
- glDeleteFramebuffers(1, &mMirrorBuffer);
- mMirrorTexture->Unload();
- delete mMirrorTexture;
- mMirrorTexture = nullptr;
- return false;
- }
- return true;
- }
- void Renderer::DrawFromGBuffer()
- {
- // Disable depth testing for the global lighting pass
- glDisable(GL_DEPTH_TEST);
- // Activate global G-buffer shader
- mGGlobalShader->SetActive();
- // Activate sprite verts quad
- mSpriteVerts->SetActive();
- // Set the G-buffer textures to sample
- mGBuffer->SetTexturesActive();
- // Set the lighting uniforms
- SetLightUniforms(mGGlobalShader, mView);
- // Draw the triangles
- glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
- // Copy depth buffer from G-buffer to default frame buffer
- glBindFramebuffer(GL_READ_FRAMEBUFFER, mGBuffer->GetBufferID());
- int width = static_cast<int>(mScreenWidth);
- int height = static_cast<int>(mScreenHeight);
- glBlitFramebuffer(0, 0, width, height,
- 0, 0, width, height,
- GL_DEPTH_BUFFER_BIT, GL_NEAREST);
- // Enable depth test, but disable writes to depth buffer
- glEnable(GL_DEPTH_TEST);
- glDepthMask(GL_FALSE);
- // Set the point light shader and mesh as active
- mGPointLightShader->SetActive();
- mPointLightMesh->GetVertexArray()->SetActive();
- // Set the view-projection matrix
- mGPointLightShader->SetMatrixUniform("uViewProj",
- mView * mProjection);
- // Set the G-buffer textures for sampling
- mGBuffer->SetTexturesActive();
- // The point light color should add to existing color
- glEnable(GL_BLEND);
- glBlendFunc(GL_ONE, GL_ONE);
- // Draw the point lights
- for (PointLightComponent* p : mPointLights)
- {
- p->Draw(mGPointLightShader, mPointLightMesh);
- }
- }
- bool Renderer::LoadShaders()
- {
- // Create sprite shader
- mSpriteShader = new Shader();
- if (!mSpriteShader->Load("Shaders/Sprite.vert", "Shaders/Sprite.frag"))
- {
- return false;
- }
- mSpriteShader->SetActive();
- // Set the view-projection matrix
- Matrix4 spriteViewProj = Matrix4::CreateSimpleViewProj(mScreenWidth, mScreenHeight);
- mSpriteShader->SetMatrixUniform("uViewProj", spriteViewProj);
- // Create basic mesh shader
- mMeshShader = new Shader();
- if (!mMeshShader->Load("Shaders/Phong.vert", "Shaders/GBufferWrite.frag"))
- {
- return false;
- }
- mMeshShader->SetActive();
- // Set the view-projection matrix
- mView = Matrix4::CreateLookAt(Vector3::Zero, Vector3::UnitX, Vector3::UnitZ);
- mProjection = Matrix4::CreatePerspectiveFOV(Math::ToRadians(70.0f),
- mScreenWidth, mScreenHeight, 10.0f, 10000.0f);
- mMeshShader->SetMatrixUniform("uViewProj", mView * mProjection);
- // Create skinned shader
- mSkinnedShader = new Shader();
- if (!mSkinnedShader->Load("Shaders/Skinned.vert", "Shaders/GBufferWrite.frag"))
- {
- return false;
- }
- mSkinnedShader->SetActive();
- mSkinnedShader->SetMatrixUniform("uViewProj", mView * mProjection);
-
- // Create shader for drawing from GBuffer (global lighting)
- mGGlobalShader = new Shader();
- if (!mGGlobalShader->Load("Shaders/GBufferGlobal.vert", "Shaders/GBufferGlobal.frag"))
- {
- return false;
- }
- // For the GBuffer, we need to associate each sampler with an index
- mGGlobalShader->SetActive();
- mGGlobalShader->SetIntUniform("uGDiffuse", 0);
- mGGlobalShader->SetIntUniform("uGNormal", 1);
- mGGlobalShader->SetIntUniform("uGWorldPos", 2);
- // The view projection is just the sprite one
- mGGlobalShader->SetMatrixUniform("uViewProj", spriteViewProj);
- // The world transform scales to the screen and flips y
- Matrix4 gbufferWorld = Matrix4::CreateScale(mScreenWidth, -mScreenHeight,
- 1.0f);
- mGGlobalShader->SetMatrixUniform("uWorldTransform", gbufferWorld);
-
- // Create a shader for point lights from GBuffer
- mGPointLightShader = new Shader();
- if (!mGPointLightShader->Load("Shaders/BasicMesh.vert",
- "Shaders/GBufferPointLight.frag"))
- {
- return false;
- }
- // Set sampler indices
- mGPointLightShader->SetActive();
- mGPointLightShader->SetIntUniform("uGDiffuse", 0);
- mGPointLightShader->SetIntUniform("uGNormal", 1);
- mGPointLightShader->SetIntUniform("uGWorldPos", 2);
- mGPointLightShader->SetVector2Uniform("uScreenDimensions",
- Vector2(mScreenWidth, mScreenHeight));
- return true;
- }
- void Renderer::CreateSpriteVerts()
- {
- float vertices[] = {
- -0.5f, 0.5f, 0.f, 0.f, 0.f, 0.0f, 0.f, 0.f, // top left
- 0.5f, 0.5f, 0.f, 0.f, 0.f, 0.0f, 1.f, 0.f, // top right
- 0.5f,-0.5f, 0.f, 0.f, 0.f, 0.0f, 1.f, 1.f, // bottom right
- -0.5f,-0.5f, 0.f, 0.f, 0.f, 0.0f, 0.f, 1.f // bottom left
- };
- unsigned int indices[] = {
- 0, 1, 2,
- 2, 3, 0
- };
- mSpriteVerts = new VertexArray(vertices, 4, VertexArray::PosNormTex, indices, 6);
- }
- void Renderer::SetLightUniforms(Shader* shader, const Matrix4& view)
- {
- // Camera position is from inverted view
- Matrix4 invView = view;
- invView.Invert();
- shader->SetVectorUniform("uCameraPos", invView.GetTranslation());
- // Ambient light
- shader->SetVectorUniform("uAmbientLight", mAmbientLight);
- // Directional light
- shader->SetVectorUniform("uDirLight.mDirection",
- mDirLight.mDirection);
- shader->SetVectorUniform("uDirLight.mDiffuseColor",
- mDirLight.mDiffuseColor);
- shader->SetVectorUniform("uDirLight.mSpecColor",
- mDirLight.mSpecColor);
- }
- Vector3 Renderer::Unproject(const Vector3& screenPoint) const
- {
- // Convert screenPoint to device coordinates (between -1 and +1)
- Vector3 deviceCoord = screenPoint;
- deviceCoord.x /= (mScreenWidth) * 0.5f;
- deviceCoord.y /= (mScreenHeight) * 0.5f;
- // Transform vector by unprojection matrix
- Matrix4 unprojection = mView * mProjection;
- unprojection.Invert();
- return Vector3::TransformWithPerspDiv(deviceCoord, unprojection);
- }
- void Renderer::GetScreenDirection(Vector3& outStart, Vector3& outDir) const
- {
- // Get start point (in center of screen on near plane)
- Vector3 screenPoint(0.0f, 0.0f, 0.0f);
- outStart = Unproject(screenPoint);
- // Get end point (in center of screen, between near and far)
- screenPoint.z = 0.9f;
- Vector3 end = Unproject(screenPoint);
- // Get direction vector
- outDir = end - outStart;
- outDir.Normalize();
- }
|