/*
* Copyright (C) 2017-2019 Christopher J. Howard
*
* This file is part of Antkeeper Source Code.
*
* Antkeeper Source Code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper Source Code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper Source Code. If not, see .
*/
#include "shadow-map-render-pass.hpp"
#include "resources/resource-manager.hpp"
ShadowMapRenderPass::ShadowMapRenderPass(ResourceManager* resourceManager):
resourceManager(resourceManager),
shader(nullptr),
croppedShadowMapViewports(nullptr),
viewCamera(nullptr),
splitViewFrustum(nullptr),
cropMatrices(nullptr),
tileMatrices(nullptr)
{}
bool ShadowMapRenderPass::load(const RenderContext* renderContext)
{
// Set maximum number of bones for skinned meshes
maxBoneCount = 64;
// Create split view frustum
splitViewFrustum = new SplitViewFrustum(4);
splitViewFrustum->setSplitSchemeWeight(0.6f);
// Determine resolution of shadow maps
shadowMapResolution = 4096;
croppedShadowMapResolution = shadowMapResolution >> 1;
// Allocate viewports
croppedShadowMapViewports = new Vector4[splitViewFrustum->getSubfrustumCount()];
// Setup viewports
for (int i = 0; i < splitViewFrustum->getSubfrustumCount(); ++i)
{
int x = i % 2;
int y = i / 2;
Vector4* viewport = &croppedShadowMapViewports[i];
(*viewport)[0] = static_cast(x * croppedShadowMapResolution);
(*viewport)[1] = static_cast(y * croppedShadowMapResolution);
(*viewport)[2] = static_cast(croppedShadowMapResolution);
(*viewport)[3] = static_cast(croppedShadowMapResolution);
}
// Allocate matrices
cropMatrices = new Matrix4[splitViewFrustum->getSubfrustumCount()];
tileMatrices = new Matrix4[splitViewFrustum->getSubfrustumCount()];
// Setup tile matrices
Matrix4 tileScale = glm::scale(Vector3(0.5f, 0.5f, 1.0f));
for (int i = 0; i < splitViewFrustum->getSubfrustumCount(); ++i)
{
float x = static_cast(i % 2) * 0.5f;
float y = static_cast(i / 2) * 0.5f;
tileMatrices[i] = glm::translate(Vector3(x, y, 0.0f)) * tileScale;
}
// Setup permutation values
unskinnedPermutation = 0;
skinnedPermutation = 1;
// Load shader
shader = resourceManager->load("depth-pass.glsl");
// Generate unskinned and skinned permutations
if (!shader->generatePermutation(unskinnedPermutation) || !shader->generatePermutation(skinnedPermutation))
{
std::cerr << std::string("ShadowMapRenderPass: failed to generate shader permutation.") << std::endl;
return false;
}
// Allocate bone palette parameter
matrixPaletteParam = new ShaderMatrix4(maxBoneCount);
// Connect shader variables
modelViewProjectionParam.connect(shader->getInput("modelViewProjectionMatrix"));
matrixPaletteParam->connect(shader->getInput("matrixPalette"));
if (!modelViewProjectionParam.isConnected() ||
!matrixPaletteParam->isConnected())
{
std::cerr << std::string("ShadowMapRenderPass: one or more shader variables were not connected to shader inputs.") << std::endl;
return false;
}
return true;
}
void ShadowMapRenderPass::unload()
{
modelViewProjectionParam.disconnect();
matrixPaletteParam->disconnect();
delete matrixPaletteParam;
shader->deleteAllPermutations();
delete[] croppedShadowMapViewports;
croppedShadowMapViewports = nullptr;
delete splitViewFrustum;
splitViewFrustum = nullptr;
delete[] cropMatrices;
cropMatrices = nullptr;
delete[] tileMatrices;
tileMatrices = nullptr;
}
void ShadowMapRenderPass::render(RenderContext* renderContext)
{
// Bind framebuffer and setup viewport
glBindFramebuffer(GL_FRAMEBUFFER, renderTarget->framebuffer);
glViewport(0, 0, renderTarget->width, renderTarget->height);
// Enable depth testing
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);
// Clear the framebuffer depth
glClear(GL_DEPTH_BUFFER_BIT);
// Draw back faces only
glDisable(GL_CULL_FACE);
glCullFace(GL_FRONT);
// Disable alpha blending
glDisable(GL_BLEND);
//const Camera& lightCamera = *(renderContext->camera);
std::list* operations = renderContext->queue->getOperations();
GLuint boundVAO = 0;
const Matrix4& viewCameraView = viewCamera->getViewTween()->getSubstate();
const Matrix4& viewCameraProjection = viewCamera->getProjectionTween()->getSubstate();
const Matrix4& lightCameraViewProjection = lightCamera->getViewProjectionTween()->getSubstate();
splitViewFrustum->setMatrices(viewCameraView, viewCameraProjection);
// Sort operations
operations->sort(RenderOpCompare());
std::uint32_t permutation = 0xDEADBEEF;
std::uint32_t noShadowCastingFlag = 4;
// For each frustum split
for (int i = 0; i < splitViewFrustum->getSubfrustumCount(); ++i)
{
// Calculate crop matrix
{
const ViewFrustum& subfrustum = splitViewFrustum->getSubfrustum(i);
// Create AABB containing the subfrustum corners
AABB subfrustumBounds(subfrustum.getCorner(0), subfrustum.getCorner(0));
for (std::size_t j = 1; j < 8; ++j)
{
subfrustumBounds.add(subfrustum.getCorner(j));
}
// Transform subfrustum bounds into light's clip space
AABB croppingBounds = subfrustumBounds.transformed(lightCameraViewProjection);
Vector3 cropMax = croppingBounds.getMax();
Vector3 cropMin = croppingBounds.getMin();
// Calculate scale
Vector3 scale;
scale.x = 2.0f / (cropMax.x - cropMin.x);
scale.y = 2.0f / (cropMax.y - cropMin.y);
scale.z = 1.0f / (cropMax.z - cropMin.z);
// Quantize scale
float scaleQuantizer = 64.0f;
scale.x = 1.0f / std::ceil(1.0f / scale.x * scaleQuantizer) * scaleQuantizer;
scale.y = 1.0f / std::ceil(1.0f / scale.y * scaleQuantizer) * scaleQuantizer;
// Calculate offset
Vector3 offset;
offset.x = (cropMax.x + cropMin.x) * scale.x * -0.5f;
offset.y = (cropMax.y + cropMin.y) * scale.y * -0.5f;
offset.z = -cropMin.z * scale.z;
// Quantize offset
float halfTextureSize = static_cast(croppedShadowMapResolution) * 0.5f;
offset.x = std::ceil(offset.x * halfTextureSize) / halfTextureSize;
offset.y = std::ceil(offset.y * halfTextureSize) / halfTextureSize;
cropMatrices[i] = glm::translate(offset) * glm::scale(scale);
}
Matrix4 croppedViewProjection = cropMatrices[i] * lightCameraViewProjection;
// Activate viewport for corresponding cropped shadow map
const Vector4& viewport = croppedShadowMapViewports[i];
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
// Render operations
for (const RenderOperation& operation: *operations)
{
// Skip operations with no materials and materials with no shadows
if (operation.material == nullptr || (operation.material->getFlags() & noShadowCastingFlag))
{
continue;
}
// TODO: Perform culling for subfrustums
// Select permutation
std::uint32_t targetPermutation = (operation.pose != nullptr) ? skinnedPermutation : unskinnedPermutation;
if (permutation != targetPermutation)
{
permutation = targetPermutation;
shader->activate(permutation);
}
// Pass matrix palette
if (operation.pose != nullptr)
{
matrixPaletteParam->getConnectedInput()->upload(0, operation.pose->getMatrixPalette(), operation.pose->getSkeleton()->getBoneCount());
}
const Matrix4& modelMatrix = operation.transform;
Matrix4 modelViewProjectionMatrix = croppedViewProjection * modelMatrix;
modelViewProjectionParam.setValue(modelViewProjectionMatrix);
modelViewProjectionParam.upload();
if (boundVAO != operation.vao)
{
glBindVertexArray(operation.vao);
boundVAO = operation.vao;
}
glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset);
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
bool ShadowMapRenderPass::RenderOpCompare::operator()(const RenderOperation& opA, const RenderOperation& opB) const
{
// If A is rigged
if (opA.pose != nullptr)
{
// And B is rigged
if (opB.pose != nullptr)
{
// Sort by VAO ID
return (opA.vao <= opB.vao);
}
else
{
// Render A first
return true;
}
}
// Sort by VAO ID
return (opA.vao <= opB.vao);
}