Brad Davis.85992747 years agoHistory
404 lines · 16.4 KB · C++
RawDownload
////  LightStage.cpp//  render-utils/src////  Created by Zach Pomerantz on 1/14/2015.//  Copyright 2015 High Fidelity, Inc.//  Copyright 2024 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "LightStage.h" #include <cmath> #include "ViewFrustum.h" template <>std::string render::PointerStage<graphics::Light, graphics::LightPointer, LightFrame>::_name { "LIGHT_STAGE" }; // The bias matrix goes from homogeneous coordinates to UV coords (see http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/#basic-shader)const glm::mat4 LightStage::Shadow::_biasMatrix {    0.5, 0.0, 0.0, 0.0,    0.0, 0.5, 0.0, 0.0,    0.0, 0.0, 0.5, 0.0,    0.5, 0.5, 0.5, 1.0 };const int LightStage::Shadow::MAP_SIZE = 1024; LightStage::LightStage() {    // Add off lights    const LightPointer ambientOffLight { std::make_shared<graphics::Light>() };    ambientOffLight->setAmbientIntensity(0.0f);    ambientOffLight->setColor(graphics::Vec3(0.0));    ambientOffLight->setIntensity(0.0f);    ambientOffLight->setType(graphics::Light::Type::AMBIENT);    _ambientOffLightId = addElement(ambientOffLight);     const LightPointer pointOffLight { std::make_shared<graphics::Light>() };    pointOffLight->setAmbientIntensity(0.0f);    pointOffLight->setColor(graphics::Vec3(0.0));    pointOffLight->setIntensity(0.0f);    pointOffLight->setType(graphics::Light::Type::POINT);    _pointOffLightId = addElement(pointOffLight);     const LightPointer spotOffLight { std::make_shared<graphics::Light>() };    spotOffLight->setAmbientIntensity(0.0f);    spotOffLight->setColor(graphics::Vec3(0.0));    spotOffLight->setIntensity(0.0f);    spotOffLight->setType(graphics::Light::Type::SPOT);    _spotOffLightId = addElement(spotOffLight);     const LightPointer sunOffLight { std::make_shared<graphics::Light>() };    sunOffLight->setAmbientIntensity(0.0f);    sunOffLight->setColor(graphics::Vec3(0.0));    sunOffLight->setIntensity(0.0f);    sunOffLight->setType(graphics::Light::Type::SUN);    _sunOffLightId = addElement(sunOffLight);     // Set default light to the off ambient light (until changed)    _defaultLightId = _ambientOffLightId;} LightStage::Shadow::Schema::Schema() {    ShadowTransform defaultTransform = {};    defaultTransform.reprojection = mat4();    defaultTransform.fixedBias = 0.005f;    std::fill(cascades, cascades + SHADOW_CASCADE_MAX_COUNT, defaultTransform);    invMapSize = 1.0f / MAP_SIZE;    cascadeCount = 1;    invCascadeBlendWidth = 1.0f / 0.2f;    invFalloffDistance = 1.0f / 2.0f;    maxDistance = 20.0f;} LightStage::Shadow::Cascade::Cascade() :    _frustum{ std::make_shared<ViewFrustum>() },    _minDistance{ 0.0f },    _maxDistance{ 20.0f } {} const glm::mat4& LightStage::Shadow::Cascade::getView() const {    return _frustum->getView();} const glm::mat4& LightStage::Shadow::Cascade::getProjection() const {    return _frustum->getProjection();} float LightStage::Shadow::Cascade::computeFarDistance(const ViewFrustum& viewFrustum, const Transform& shadowViewInverse,                                                      float left, float right, float bottom, float top, float viewMaxShadowDistance) const {    // Far distance should be extended to the intersection of the infinitely extruded shadow frustum    // with the view frustum side planes. To do so, we generate 10 triangles in shadow space which are the result of    // tesselating the side and far faces of the view frustum and clip them with the 4 side planes of the    // shadow frustum. The resulting clipped triangle vertices with the farthest Z gives the desired    // shadow frustum far distance.    std::array<Triangle, 10> viewFrustumTriangles;    Plane shadowClipPlanes[4] = {        Plane(glm::vec3(0.0f, -1.0f, 0.0f), glm::vec3(0.0f, top, 0.0f)),        Plane(glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, bottom, 0.0f)),        Plane(glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(left, 0.0f, 0.0f)),        Plane(glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(right, 0.0f, 0.0f))    };     viewFrustum.tesselateSidesAndFar(shadowViewInverse, viewFrustumTriangles.data(), viewMaxShadowDistance);     static const int MAX_TRIANGLE_COUNT = 16;    auto far = 0.0f;     for (auto& triangle : viewFrustumTriangles) {        Triangle clippedTriangles[MAX_TRIANGLE_COUNT];        auto clippedTriangleCount = clipTriangleWithPlanes(triangle, shadowClipPlanes, 4, clippedTriangles, MAX_TRIANGLE_COUNT);         for (auto i = 0; i < clippedTriangleCount; i++) {            const auto& clippedTriangle = clippedTriangles[i];            far = glm::max(far, -clippedTriangle.v0.z);            far = glm::max(far, -clippedTriangle.v1.z);            far = glm::max(far, -clippedTriangle.v2.z);        }    }     return far;} LightStage::Shadow::Shadow(graphics::LightPointer light, unsigned int cascadeCount) :    _light{ light } {    cascadeCount = std::min(cascadeCount, (unsigned int)SHADOW_CASCADE_MAX_COUNT);    Schema schema;    schema.cascadeCount = cascadeCount;    _schemaBuffer = std::make_shared<gpu::Buffer>(gpu::Buffer::UniformBuffer, sizeof(Schema), (const gpu::Byte*) &schema);     // Create shadow cascade texture array    auto depthFormat = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH);  // Depth32 texel format    map = gpu::TexturePointer(gpu::Texture::createRenderBufferArray(depthFormat, MAP_SIZE, MAP_SIZE, cascadeCount));    Sampler::Desc samplerDesc;    samplerDesc._borderColor = glm::vec4(1.0f);    samplerDesc._wrapModeU = Sampler::WRAP_BORDER;    samplerDesc._wrapModeV = Sampler::WRAP_BORDER;    samplerDesc._filter = Sampler::FILTER_MIN_MAG_LINEAR;    samplerDesc._comparisonFunc = (uint8_t)ComparisonFunction::LESS;     map->setSampler(Sampler(samplerDesc));     _cascades.resize(cascadeCount);     for (uint cascadeIndex=0; cascadeIndex < cascadeCount; cascadeIndex++) {        auto& cascade = _cascades[cascadeIndex];        std::string name = "Shadowmap Cascade ";        name += '0' + cascadeIndex;        cascade.framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(name));        cascade.framebuffer->setDepthBuffer(map, depthFormat, cascadeIndex);    }     if (light) {        setMaxDistance(light->getShadowsMaxDistance());    }} void LightStage::Shadow::setLight(graphics::LightPointer light) {    _light = light;    if (light) {        setMaxDistance(light->getShadowsMaxDistance());    }} void LightStage::Shadow::setMaxDistance(float value) {    static const auto MINIMUM_MAXDISTANCE = 1e-3f;     value = std::max(MINIMUM_MAXDISTANCE, value);    if (value != _maxDistance) {        // This overlaping factor isn't really used directly for blending of shadow cascades. It's        // just there to be sure the cascades do overlap. The blending width used is relative        // to the UV space and is set in the Schema with invCascadeBlendWidth.        static const auto OVERLAP_FACTOR = 1.0f / 5.0f;         _maxDistance = value;         if (_cascades.size() == 1) {            _cascades.front().setMinDistance(0.0f);            _cascades.front().setMaxDistance(_maxDistance);        } else {            // Distribute the cascades along that distance            // TODO : these parameters should be exposed to the user as part of the light entity parameters, no?            static const auto LOW_MAX_DISTANCE = 2.0f;            static const auto MAX_RESOLUTION_LOSS = 0.6f; // Between 0 and 1, 0 giving tighter distributions             // The max cascade distance is computed by multiplying the previous cascade's max distance by a certain            // factor. There is a "user" factor that is computed from a desired max resolution loss in the shadow            // and an optimal one based on the global min and max shadow distance, all cascades considered. The final            // distance is a gradual blend between the two            const auto userDistanceScale = 1.0f / (1.0f - MAX_RESOLUTION_LOSS);            const auto optimalDistanceScale = powf(_maxDistance / LOW_MAX_DISTANCE, 1.0f / (_cascades.size() - 1));             float maxCascadeUserDistance = LOW_MAX_DISTANCE;            float maxCascadeOptimalDistance = LOW_MAX_DISTANCE;            float minCascadeDistance = 0.0f;             for (size_t cascadeIndex = 0; cascadeIndex < _cascades.size(); ++cascadeIndex) {                float blendFactor = cascadeIndex / float(_cascades.size() - 1);                float maxCascadeDistance;                 if (cascadeIndex == size_t(_cascades.size() - 1)) {                    maxCascadeDistance = _maxDistance;                } else {                    maxCascadeDistance = maxCascadeUserDistance + (maxCascadeOptimalDistance - maxCascadeUserDistance)*blendFactor*blendFactor;                }                 float shadowOverlapDistance = maxCascadeDistance * OVERLAP_FACTOR;                 _cascades[cascadeIndex].setMinDistance(minCascadeDistance);                _cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance);                 // Compute distances for next cascade                minCascadeDistance = maxCascadeDistance;                maxCascadeUserDistance = maxCascadeUserDistance * userDistanceScale;                maxCascadeOptimalDistance = maxCascadeOptimalDistance * optimalDistanceScale;                maxCascadeUserDistance = std::min(maxCascadeUserDistance, _maxDistance);            }        }         // Update the buffer        const auto& lastCascade = _cascades.back();        auto& schema = _schemaBuffer.edit<Schema>();        schema.maxDistance = _maxDistance;        schema.invFalloffDistance = 1.0f / (OVERLAP_FACTOR*lastCascade.getMaxDistance());    }} void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,                                            float nearDepth, float farDepth) {    assert(nearDepth < farDepth);    // Orient the keylight frustum    auto lightDirection = glm::normalize(_light->getDirection());    glm::quat orientation;    if (lightDirection == IDENTITY_UP) {        orientation = glm::quat(glm::mat3(-IDENTITY_RIGHT, IDENTITY_FORWARD, -IDENTITY_UP));    } else if (lightDirection == -IDENTITY_UP) {        orientation = glm::quat(glm::mat3(IDENTITY_RIGHT, IDENTITY_FORWARD, IDENTITY_UP));    } else {        auto side = glm::normalize(glm::cross(lightDirection, IDENTITY_UP));        auto up = glm::normalize(glm::cross(side, lightDirection));        orientation = glm::quat_cast(glm::mat3(side, up, -lightDirection));    }     // Position the keylight frustum    auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection;    for (auto& cascade : _cascades) {        cascade._frustum->setOrientation(orientation);        cascade._frustum->setPosition(position);    }} void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum,                                            float nearDepth, float farDepth) {    assert(nearDepth < farDepth);    assert(cascadeIndex < _cascades.size());     auto& cascade = _cascades[cascadeIndex];    const auto viewMinCascadeShadowDistance = std::max(viewFrustum.getNearClip(), cascade.getMinDistance());    const auto viewMaxCascadeShadowDistance = std::min(viewFrustum.getFarClip(), cascade.getMaxDistance());    const auto viewMaxShadowDistance = _cascades.back().getMaxDistance();     const Transform shadowView{ cascade._frustum->getView()};    const Transform shadowViewInverse{ shadowView.getInverseMatrix() };     auto nearCorners = viewFrustum.getCorners(viewMinCascadeShadowDistance);    auto farCorners = viewFrustum.getCorners(viewMaxCascadeShadowDistance);     vec3 min{ shadowViewInverse.transform(nearCorners.bottomLeft) };    vec3 max{ min };    // Expand keylight frustum  to fit view frustum    auto fitFrustum = [&min, &max, &shadowViewInverse](const vec3& viewCorner) {        const auto corner = shadowViewInverse.transform(viewCorner);         min.x = glm::min(min.x, corner.x);        min.y = glm::min(min.y, corner.y);        min.z = glm::min(min.z, corner.z);         max.x = glm::max(max.x, corner.x);        max.y = glm::max(max.y, corner.y);        max.z = glm::max(max.z, corner.z);    };    fitFrustum(nearCorners.bottomRight);    fitFrustum(nearCorners.topLeft);    fitFrustum(nearCorners.topRight);    fitFrustum(farCorners.bottomLeft);    fitFrustum(farCorners.bottomRight);    fitFrustum(farCorners.topLeft);    fitFrustum(farCorners.topRight);     // Re-adjust near and far shadow distance    auto near = glm::min(-max.z, nearDepth);    auto far = cascade.computeFarDistance(viewFrustum, shadowViewInverse, min.x, max.x, min.y, max.y, viewMaxShadowDistance);     glm::mat4 ortho = glm::ortho<float>(min.x, max.x, min.y, max.y, near, far);    cascade._frustum->setProjection(ortho);     // Calculate the frustum's internal state    cascade._frustum->calculate();     // Update the buffer    auto& schema = _schemaBuffer.edit<Schema>();    auto& schemaCascade = schema.cascades[cascadeIndex];    schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix();} void LightStage::Shadow::setKeylightCascadeBias(unsigned int cascadeIndex, float constantBias, float slopeBias) {    auto& schema = _schemaBuffer.edit<Schema>();    auto& schemaCascade = schema.cascades[cascadeIndex];    schemaCascade.fixedBias = constantBias;    schemaCascade.slopeBias = slopeBias;} void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) {    assert(cascadeIndex < _cascades.size());    const Transform view{ shadowFrustum.getView() };    const Transform viewInverse{ view.getInverseMatrix() };    auto& cascade = _cascades[cascadeIndex];     *cascade._frustum = shadowFrustum;    // Update the buffer    auto& schema = _schemaBuffer.edit<Schema>();    auto& schemaCascade = schema.cascades[cascadeIndex];    schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix();} LightStage::Index LightStage::addElement(const LightPointer& light, const bool shouldSetAsDefault) {    Index lightId;     auto found = _elementMap.find(light);    if (found == _elementMap.end()) {        lightId = _elements.newElement(light);        // Avoid failing to allocate a light, just pass        if (lightId != INVALID_INDEX) {             // Allocate the matching Desc to the light            if (lightId >= (Index) _descs.size()) {                _descs.emplace_back(Desc());            } else {                assert(_descs[lightId].shadowId == INVALID_INDEX);                _descs[lightId] = Desc();            }             // Insert the light and its index in the reverese map            _elementMap[light] = lightId;             updateLightArrayBuffer(lightId);        }    } else {        lightId = (*found).second;    }     if (shouldSetAsDefault) {        _defaultLightId = lightId;    }     return lightId;} LightStage::LightPointer LightStage::removeElement(Index index) {    LightPointer removedLight = _elements.freeElement(index);    if (removedLight) {        _elementMap.erase(removedLight);        _descs[index] = Desc();    }    assert(_descs.size() <= (size_t)index || _descs[index].shadowId == INVALID_INDEX);    return removedLight;} LightStage::LightPointer LightStage::getCurrentKeyLight(const LightFrame& frame) const {    Index keyLightId { _defaultLightId };    if (!frame._sunLights.empty()) {        keyLightId = frame._sunLights.front();    }    return _elements.get(keyLightId);} LightStage::LightPointer LightStage::getCurrentAmbientLight(const LightFrame& frame) const {    Index keyLightId { _defaultLightId };    if (!frame._ambientLights.empty()) {        keyLightId = frame._ambientLights.front();    }    return _elements.get(keyLightId);} void LightStage::updateLightArrayBuffer(Index lightId) {    auto lightSize = sizeof(graphics::Light::LightSchema);    if (!_lightArrayBuffer) {        _lightArrayBuffer = std::make_shared<gpu::Buffer>(gpu::Buffer::UniformBuffer, lightSize);    }     if (lightId > (Index)_lightArrayBuffer->getNumTypedElements<graphics::Light::LightSchema>()) {        _lightArrayBuffer->resize(lightSize * (lightId + 10));    }     // lightArray is big enough so we can remap    auto light = _elements._elements[lightId];    if (light) {        const auto& lightSchema = light->getLightSchemaBuffer().get();        _lightArrayBuffer->setSubData<graphics::Light::LightSchema>(lightId, lightSchema);    } else {        // this should not happen ?    }}