|
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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<float>(x * croppedShadowMapResolution);
|
|
(*viewport)[1] = static_cast<float>(y * croppedShadowMapResolution);
|
|
(*viewport)[2] = static_cast<float>(croppedShadowMapResolution);
|
|
(*viewport)[3] = static_cast<float>(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<float>(i % 2) * 0.5f;
|
|
float y = static_cast<float>(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<Shader>("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<RenderOperation>* 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<float>(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);
|
|
}
|
|
|