/* * 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); }