EVSM improvements (#9758)
- refactoring/clecanup to make some changes easier - VSM mipmap generation was mistakenly disable when blur radius was 0 - analytic variance was disabled because the math only worked for VSM. Fixed the math. - better handling of large blurs when using fp32 - implement EVSM equivalent of receiver plane normal bias - use correct EVSM clearing color - mipmapping with point lights works much better (no seams) - min variance is computed automatically - custom high precision mipmaping shader for VSM
This commit is contained in:
@@ -2121,7 +2121,7 @@ public class View {
|
||||
*/
|
||||
public boolean highPrecision = false;
|
||||
/**
|
||||
* VSM minimum variance scale, must be positive.
|
||||
* @deprecated has no effect.
|
||||
*/
|
||||
public float minVarianceScale = 0.5f;
|
||||
/**
|
||||
|
||||
@@ -372,7 +372,7 @@ public:
|
||||
} vsm;
|
||||
|
||||
/**
|
||||
* Light bulb radius used for soft shadows. Currently this is only used when DPCF or PCSS is
|
||||
* Light bulb radius used for soft shadows. Currently, this is only used when DPCF or PCSS is
|
||||
* enabled. (2cm by default).
|
||||
*/
|
||||
float shadowBulbRadius = 0.02f;
|
||||
|
||||
@@ -735,7 +735,7 @@ struct VsmShadowOptions {
|
||||
bool highPrecision = false;
|
||||
|
||||
/**
|
||||
* VSM minimum variance scale, must be positive.
|
||||
* @deprecated has no effect.
|
||||
*/
|
||||
float minVarianceScale = 0.5f;
|
||||
|
||||
|
||||
@@ -3809,15 +3809,13 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
auto const& inDesc = resources.getDescriptor(data.in);
|
||||
auto width = inDesc.width;
|
||||
assert_invariant(width == inDesc.height);
|
||||
int const dim = width >> (level + 1);
|
||||
uint32_t const dim = std::max(1u, width >> (level + 1));
|
||||
|
||||
auto& material = getPostProcessMaterial("vsmMipmap");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
|
||||
// When generating shadow map mip levels, we want to preserve the 1 texel border.
|
||||
// (note clearing never respects the scissor in Filament)
|
||||
auto const pipeline = getPipelineState(ma);
|
||||
backend::Viewport const scissor = { 1u, 1u, dim - 2u, dim - 2u };
|
||||
backend::Viewport const scissor = { 0, 0, dim, dim };
|
||||
|
||||
FMaterialInstance* const mi = getMaterialInstance(ma);
|
||||
mi->setParameter("color", in, SamplerParams{
|
||||
|
||||
@@ -62,6 +62,8 @@ ShadowMap::ShadowMap(FEngine& engine) noexcept
|
||||
: mPerShadowMapUniforms(engine),
|
||||
mShadowType(ShadowType::DIRECTIONAL),
|
||||
mHasVisibleShadows(false),
|
||||
mVsm(false),
|
||||
mReservedBit(false),
|
||||
mFace(0) {
|
||||
Entity entities[2];
|
||||
engine.getEntityManager().create(2, entities);
|
||||
@@ -85,7 +87,7 @@ void ShadowMap::terminate(FEngine& engine) {
|
||||
|
||||
ShadowMap::~ShadowMap() = default;
|
||||
|
||||
void ShadowMap::initialize(size_t const lightIndex, ShadowType const shadowType,
|
||||
void ShadowMap::initialize(size_t const lightIndex, ShadowType const shadowType, bool const vsm,
|
||||
uint16_t const shadowIndex, uint8_t const face,
|
||||
LightManager::ShadowOptions const* options) {
|
||||
mLightIndex = lightIndex;
|
||||
@@ -93,6 +95,7 @@ void ShadowMap::initialize(size_t const lightIndex, ShadowType const shadowType,
|
||||
mOptions = options;
|
||||
mShadowType = shadowType;
|
||||
mFace = face;
|
||||
mVsm = vsm;
|
||||
}
|
||||
|
||||
mat4f ShadowMap::getDirectionalLightViewMatrix(float3 direction, float3 up,
|
||||
@@ -1181,9 +1184,8 @@ float2 ShadowMap::texelSizeWorldSpace(mat4f const& S, uint16_t const shadowDimen
|
||||
return s;
|
||||
}
|
||||
|
||||
template<typename Casters, typename Receivers>
|
||||
void ShadowMap::visitScene(const FScene& scene, uint32_t const visibleLayers,
|
||||
Casters casters, Receivers receivers) noexcept {
|
||||
template<typename Visitor>
|
||||
void ShadowMap::visitScene(const FScene& scene, uint32_t const visibleLayers, Visitor visitor) noexcept {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
|
||||
using State = FRenderableManager::Visibility;
|
||||
@@ -1196,14 +1198,11 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t const visibleLayers,
|
||||
size_t const c = soa.size();
|
||||
for (size_t i = 0; i < c; i++) {
|
||||
if (layers[i] & visibleLayers) {
|
||||
const Aabb aabb{ worldAABBCenter[i] - worldAABBExtent[i],
|
||||
worldAABBCenter[i] + worldAABBExtent[i] };
|
||||
if (visibility[i].castShadows) {
|
||||
casters(aabb, visibleMasks[i]);
|
||||
}
|
||||
if (visibility[i].receiveShadows) {
|
||||
receivers(aabb, visibleMasks[i]);
|
||||
}
|
||||
Aabb const aabb{
|
||||
worldAABBCenter[i] - worldAABBExtent[i],
|
||||
worldAABBCenter[i] + worldAABBExtent[i]
|
||||
};
|
||||
visitor(aabb, visibleMasks[i], visibility[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1222,13 +1221,15 @@ ShadowMap::SceneInfo::SceneInfo(
|
||||
wsShadowCastersVolume = {};
|
||||
wsShadowReceiversVolume = {};
|
||||
visitScene(scene, visibleLayers,
|
||||
[&](Aabb caster, Culler::result_type) {
|
||||
wsShadowCastersVolume.min = min(wsShadowCastersVolume.min, caster.min);
|
||||
wsShadowCastersVolume.max = max(wsShadowCastersVolume.max, caster.max);
|
||||
},
|
||||
[&](Aabb receiver, Culler::result_type) {
|
||||
wsShadowReceiversVolume.min = min(wsShadowReceiversVolume.min, receiver.min);
|
||||
wsShadowReceiversVolume.max = max(wsShadowReceiversVolume.max, receiver.max);
|
||||
[this](Aabb const& aabb, Culler::result_type, FRenderableManager::Visibility const visibility) {
|
||||
if (visibility.castShadows) {
|
||||
wsShadowCastersVolume.min = min(wsShadowCastersVolume.min, aabb.min);
|
||||
wsShadowCastersVolume.max = max(wsShadowCastersVolume.max, aabb.max);
|
||||
}
|
||||
if (visibility.receiveShadows) {
|
||||
wsShadowReceiversVolume.min = min(wsShadowReceiversVolume.min, aabb.min);
|
||||
wsShadowReceiversVolume.max = max(wsShadowReceiversVolume.max, aabb.max);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1242,18 +1243,20 @@ void ShadowMap::updateSceneInfoDirectional(mat4f const& Mv, FScene const& scene,
|
||||
sceneInfo.lsCastersNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
|
||||
sceneInfo.lsReceiversNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
|
||||
visitScene(scene, sceneInfo.visibleLayers,
|
||||
[&](Aabb caster, Culler::result_type) {
|
||||
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
|
||||
sceneInfo.lsCastersNearFar.x = max(sceneInfo.lsCastersNearFar.x, r.max.z);
|
||||
sceneInfo.lsCastersNearFar.y = min(sceneInfo.lsCastersNearFar.y, r.min.z);
|
||||
},
|
||||
[&](Aabb receiver, Culler::result_type const vis) {
|
||||
// account only for objects that are visible by the camera
|
||||
auto mask = 1u << VISIBLE_RENDERABLE_BIT;
|
||||
if ((vis & mask) == mask) {
|
||||
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, receiver);
|
||||
sceneInfo.lsReceiversNearFar.x = max(sceneInfo.lsReceiversNearFar.x, r.max.z);
|
||||
sceneInfo.lsReceiversNearFar.y = min(sceneInfo.lsReceiversNearFar.y, r.min.z);
|
||||
[&](Aabb const& aabb, Culler::result_type const vis, FRenderableManager::Visibility const visibility) {
|
||||
if (visibility.castShadows) {
|
||||
auto const r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, aabb);
|
||||
sceneInfo.lsCastersNearFar.x = max(sceneInfo.lsCastersNearFar.x, r.max.z);
|
||||
sceneInfo.lsCastersNearFar.y = min(sceneInfo.lsCastersNearFar.y, r.min.z);
|
||||
}
|
||||
if (visibility.castShadows) {
|
||||
// account only for objects that are visible by the camera
|
||||
constexpr auto mask = 1u << VISIBLE_RENDERABLE_BIT;
|
||||
if ((vis & mask) == mask) {
|
||||
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, aabb);
|
||||
sceneInfo.lsReceiversNearFar.x = max(sceneInfo.lsReceiversNearFar.x, r.max.z);
|
||||
sceneInfo.lsReceiversNearFar.y = min(sceneInfo.lsReceiversNearFar.y, r.min.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1268,15 +1271,15 @@ void ShadowMap::updateSceneInfoSpot(mat4f const& Mv, FScene const& scene,
|
||||
sceneInfo.lsCastersNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
|
||||
// account only for objects that are visible by both the camera and the light
|
||||
visitScene(scene, sceneInfo.visibleLayers,
|
||||
[&](Aabb caster, Culler::result_type const vis) {
|
||||
auto mask = VISIBLE_DYN_SHADOW_RENDERABLE;
|
||||
if ((vis & mask) == mask) {
|
||||
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
|
||||
sceneInfo.lsCastersNearFar.x = std::max(sceneInfo.lsCastersNearFar.x, r.max.z); // near
|
||||
sceneInfo.lsCastersNearFar.y = std::min(sceneInfo.lsCastersNearFar.y, r.min.z); // far
|
||||
[&](Aabb const& aabb, Culler::result_type const vis, FRenderableManager::Visibility const visibility) {
|
||||
if (visibility.castShadows) {
|
||||
constexpr auto mask = VISIBLE_DYN_SHADOW_RENDERABLE;
|
||||
if ((vis & mask) == mask) {
|
||||
auto const r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, aabb);
|
||||
sceneInfo.lsCastersNearFar.x = std::max(sceneInfo.lsCastersNearFar.x, r.max.z); // near
|
||||
sceneInfo.lsCastersNearFar.y = std::min(sceneInfo.lsCastersNearFar.y, r.min.z); // far
|
||||
}
|
||||
}
|
||||
},
|
||||
[&](Aabb receiver, Culler::result_type) {
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1286,47 +1289,56 @@ void ShadowMap::setAllocation(uint8_t const layer, backend::Viewport viewport) n
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getViewport() const noexcept {
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp).
|
||||
// This is used for calculating the light space; in particular, if the shadowmap is in a 2D atlas, this
|
||||
// returns the valid area of the shadowmap. By definition all values must be integer.
|
||||
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture, which should only happen for
|
||||
// directional lights when "focus shadow casters" is used, or when shadowFar is smaller than the camera far.
|
||||
// For directional lights, the border is filed with "fully lit".
|
||||
//
|
||||
// For point-lights, we also use a 1-texel border, but for a different reason; the border is filled with data
|
||||
// so that bilinear (PCF) filtering works properly.
|
||||
//
|
||||
// Spot-light a treated like point lights (the border will be filled with "fully-lit" anyways)
|
||||
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
return { mOffset.x + border, mOffset.y + border, dim - 2u * border, dim - 2u * border };
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getScissor() const noexcept {
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp), so we
|
||||
// don't scissor the border, so it gets filled with correct neighboring texels.
|
||||
// This is used while rendering the shadowmap.
|
||||
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
switch (mShadowType) {
|
||||
case ShadowType::DIRECTIONAL:
|
||||
return { mOffset.x + border, mOffset.y + border, dim - 2u * border, dim - 2u * border };
|
||||
switch (mShadowType) { // NOLINT(*-multiway-paths-covered)
|
||||
case ShadowType::DIRECTIONAL: {
|
||||
// Don't render anything into the border; it's already filled with "fully lit".
|
||||
return {mOffset.x + border, mOffset.y + border, dim - 2u * border, dim - 2u * border};
|
||||
}
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT:
|
||||
return { mOffset.x, mOffset.y, dim, dim };
|
||||
case ShadowType::POINT: {
|
||||
// Render into the border (which will get the adjacent faces texels)
|
||||
return {mOffset.x, mOffset.y, dim, dim};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float4 ShadowMap::getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept {
|
||||
float border; // shadowmap border in texels
|
||||
switch (mShadowType) {
|
||||
// This is used to clamp the texture coordinates when sampling the shadowmap
|
||||
|
||||
float border = 0; // shadowmap border in texels
|
||||
switch (mShadowType) { // NOLINT(*-multiway-paths-covered)
|
||||
case ShadowType::DIRECTIONAL:
|
||||
// For directional lights, we need to allow the sampling to reach the border, it
|
||||
// happens when "focus shadow casters" is used for instance.
|
||||
// For directional lights, we need to allow the sampling to reach the center of border texels,
|
||||
// but no further, so we don't read into the adjacent texture in the 2D atlas (taking bilinear filtering
|
||||
// into account).
|
||||
border = 0.5f;
|
||||
break;
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT:
|
||||
// For spot and point light, this is equal to the viewport. i.e. the valid
|
||||
// texels are inside the viewport (w/ 1-texel border), the border will be used
|
||||
// for bilinear filtering.
|
||||
// For point-light, the border is only needed for bilinear filtering (of the other faces)
|
||||
border = 1.0f;
|
||||
break;
|
||||
}
|
||||
@@ -1355,8 +1367,8 @@ void ShadowMap::prepareCamera(Transaction const& transaction,
|
||||
}
|
||||
|
||||
void ShadowMap::prepareViewport(Transaction const& transaction,
|
||||
backend::Viewport const& viewport) noexcept {
|
||||
ShadowMapDescriptorSet::prepareViewport(transaction, viewport);
|
||||
backend::Viewport const& physicalViewport, backend::Viewport const& logicalViewport) noexcept {
|
||||
ShadowMapDescriptorSet::prepareViewport(transaction, physicalViewport, logicalViewport);
|
||||
}
|
||||
|
||||
void ShadowMap::prepareTime(Transaction const& transaction,
|
||||
@@ -1370,8 +1382,8 @@ void ShadowMap::prepareMaterialGlobals(Transaction const& transaction,
|
||||
}
|
||||
|
||||
void ShadowMap::prepareShadowMapping(Transaction const& transaction,
|
||||
bool const highPrecision) noexcept {
|
||||
ShadowMapDescriptorSet::prepareShadowMapping(transaction, highPrecision);
|
||||
float const vsmExponent, float const vsmMaxMoment) noexcept {
|
||||
ShadowMapDescriptorSet::prepareShadowMapping(transaction, vsmExponent, vsmMaxMoment);
|
||||
}
|
||||
|
||||
ShadowMapDescriptorSet::Transaction ShadowMap::open(DriverApi& driver) noexcept {
|
||||
|
||||
@@ -128,7 +128,7 @@ public:
|
||||
static math::mat4f getPointLightViewMatrix(backend::TextureCubemapFace face,
|
||||
math::float3 position) noexcept;
|
||||
|
||||
void initialize(size_t lightIndex, ShadowType shadowType, uint16_t shadowIndex, uint8_t face,
|
||||
void initialize(size_t lightIndex, ShadowType shadowType, bool vsm, uint16_t shadowIndex, uint8_t face,
|
||||
LightManager::ShadowOptions const* options);
|
||||
|
||||
struct ShaderParameters {
|
||||
@@ -173,7 +173,10 @@ public:
|
||||
static void updateSceneInfoSpot(const math::mat4f& Mv, FScene const& scene,
|
||||
SceneInfo& sceneInfo);
|
||||
|
||||
LightManager::ShadowOptions const* getShadowOptions() const noexcept { return mOptions; }
|
||||
LightManager::ShadowOptions const& getShadowOptions() const noexcept {
|
||||
assert_invariant(mOptions);
|
||||
return *mOptions;
|
||||
}
|
||||
size_t getLightIndex() const { return mLightIndex; }
|
||||
uint16_t getShadowIndex() const { return mShadowIndex; }
|
||||
void setAllocation(uint8_t layer, backend::Viewport viewport) noexcept;
|
||||
@@ -193,13 +196,13 @@ public:
|
||||
static void prepareCamera(Transaction const& transaction,
|
||||
FEngine const& engine, const CameraInfo& cameraInfo) noexcept;
|
||||
static void prepareViewport(Transaction const& transaction,
|
||||
backend::Viewport const& viewport) noexcept;
|
||||
backend::Viewport const& physicalViewport, backend::Viewport const& logicalViewport) noexcept;
|
||||
static void prepareTime(Transaction const& transaction,
|
||||
FEngine const& engine, math::float4 const& userTime) noexcept;
|
||||
static void prepareMaterialGlobals(Transaction const& transaction,
|
||||
std::array<math::float4, 4> const& materialGlobals) noexcept;
|
||||
static void prepareShadowMapping(Transaction const& transaction,
|
||||
bool highPrecision) noexcept;
|
||||
float vsmExponent, float vsmMaxMoment) noexcept;
|
||||
static ShadowMapDescriptorSet::Transaction open(backend::DriverApi& driver) noexcept;
|
||||
void commit(Transaction& transaction, FEngine& engine, backend::DriverApi& driver) const noexcept;
|
||||
void bind(backend::DriverApi& driver) const noexcept;
|
||||
@@ -273,9 +276,8 @@ private:
|
||||
static inline math::float4 computeBoundingSphere(
|
||||
math::float3 const* vertices, size_t count) noexcept;
|
||||
|
||||
template<typename Casters, typename Receivers>
|
||||
static void visitScene(FScene const& scene, uint32_t visibleLayers,
|
||||
Casters casters, Receivers receivers) noexcept;
|
||||
template<typename Visitor>
|
||||
static void visitScene(FScene const& scene, uint32_t visibleLayers, Visitor visitor) noexcept;
|
||||
|
||||
static inline Aabb compute2DBounds(const math::mat4f& lightView,
|
||||
math::float3 const* wsVertices, size_t count) noexcept;
|
||||
@@ -349,9 +351,11 @@ private:
|
||||
uint8_t mLayer = 0; // our layer in the shadowMap texture // 1
|
||||
ShadowType mShadowType : 2; // :2
|
||||
bool mHasVisibleShadows : 1; // :1
|
||||
bool mVsm : 1; // :1
|
||||
UTILS_UNUSED bool mReservedBit : 1; // :1
|
||||
uint8_t mFace : 3; // :3
|
||||
math::ushort2 mOffset{}; // 4
|
||||
UTILS_UNUSED uint8_t reserved[4]; // 4
|
||||
UTILS_UNUSED uint8_t reserved[4] = {}; // 4
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -151,12 +151,14 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(
|
||||
|
||||
mDirectionalShadowMapCount = builder.mDirectionalShadowMapCount;
|
||||
mSpotShadowMapCount = builder.mSpotShadowMapCount;
|
||||
const bool vsm = view.hasVSM();
|
||||
|
||||
for (auto const& entry : builder.mShadowMaps) {
|
||||
auto& shadowMap = getShadowMap(entry.shadowIndex);
|
||||
shadowMap.initialize(
|
||||
entry.lightIndex,
|
||||
entry.shadowType,
|
||||
vsm,
|
||||
entry.shadowIndex,
|
||||
entry.face,
|
||||
entry.options);
|
||||
@@ -230,15 +232,12 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
FView& view, CameraInfo const& mainCameraInfo,
|
||||
float4 const& userTime) noexcept {
|
||||
|
||||
const float moment2 = std::numeric_limits<half>::max();
|
||||
const float moment1 = std::sqrt(moment2);
|
||||
const float4 vsmClearColor{ moment1, moment2, -moment1, moment2 };
|
||||
|
||||
FScene* scene = view.getScene();
|
||||
assert_invariant(scene);
|
||||
|
||||
// make a copy here, because it's a very small structure
|
||||
const TextureAtlasRequirements textureRequirements = mTextureAtlasRequirements;
|
||||
VsmShadowOptions const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
TextureAtlasRequirements const textureRequirements = mTextureAtlasRequirements;
|
||||
assert_invariant(textureRequirements.layers <= CONFIG_MAX_SHADOW_LAYERS);
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
@@ -258,8 +257,6 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
utils::FixedCapacityVector<ShadowPass> passList;
|
||||
};
|
||||
|
||||
VsmShadowOptions const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
|
||||
auto& prepareShadowPass = fg.addPass<PrepareShadowPassData>("Prepare Shadow Pass",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.passList.reserve(getMaxShadowMapCount());
|
||||
@@ -392,16 +389,18 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
|
||||
auto transaction = ShadowMap::open(driver);
|
||||
ShadowMap::prepareCamera(transaction, engine, cameraInfo);
|
||||
ShadowMap::prepareViewport(transaction, shadowMap.getViewport());
|
||||
ShadowMap::prepareViewport(transaction,
|
||||
{ 0, 0, textureRequirements.size, textureRequirements.size },
|
||||
shadowMap.getViewport());
|
||||
ShadowMap::prepareTime(transaction, engine, userTime);
|
||||
ShadowMap::prepareMaterialGlobals(transaction, view.getMaterialGlobals());
|
||||
ShadowMap::prepareShadowMapping(transaction,
|
||||
vsmShadowOptions.highPrecision);
|
||||
getWrapExponentEVSM(vsmShadowOptions, shadowMap.getShadowOptions()),
|
||||
getMaxMomentEVSM(vsmShadowOptions));
|
||||
shadowMap.commit(transaction, engine, driver);
|
||||
|
||||
// updatePrimitivesLod must be run before RenderPass::appendCommands.
|
||||
FView::updatePrimitivesLod(scene->getRenderableData(),
|
||||
engine, cameraInfo, entry.range);
|
||||
FView::updatePrimitivesLod(scene->getRenderableData(), engine, cameraInfo, entry.range);
|
||||
|
||||
// generate and sort the commands for rendering the shadow map
|
||||
|
||||
@@ -430,10 +429,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
entry.executor = pass.getExecutor();
|
||||
|
||||
if (!view.hasVSM()) {
|
||||
auto const* options = shadowMap.getShadowOptions();
|
||||
auto const& options = shadowMap.getShadowOptions();
|
||||
PolygonOffset const polygonOffset = { // handle reversed Z
|
||||
.slope = -options->polygonOffsetSlope,
|
||||
.constant = -options->polygonOffsetConstant
|
||||
.slope = -options.polygonOffsetSlope,
|
||||
.constant = -options.polygonOffsetConstant
|
||||
};
|
||||
entry.executor.overridePolygonOffset(&polygonOffset);
|
||||
}
|
||||
@@ -465,10 +464,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
auto const& entry = *first;
|
||||
|
||||
const uint8_t layer = entry.shadowMap->getLayer();
|
||||
const auto* options = entry.shadowMap->getShadowOptions();
|
||||
const auto& options = entry.shadowMap->getShadowOptions();
|
||||
const auto msaaSamples = textureRequirements.msaaSamples;
|
||||
const bool blur = entry.shadowMap->hasVisibleShadows() &&
|
||||
view.hasVSM() && options->vsm.blurWidth > 0.0f;
|
||||
view.hasVSM() && options.vsm.blurWidth > 0.0f;
|
||||
|
||||
auto last = first;
|
||||
// loop over each shadow pass to find its layer range
|
||||
@@ -508,10 +507,9 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
|
||||
renderTargetDesc.attachments.color[0] = data.output;
|
||||
renderTargetDesc.attachments.depth = depth;
|
||||
renderTargetDesc.clearFlags =
|
||||
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH;
|
||||
renderTargetDesc.clearFlags = TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH;
|
||||
// we need to clear the shadow map with the max EVSM moments
|
||||
renderTargetDesc.clearColor = vsmClearColor;
|
||||
renderTargetDesc.clearColor = textureRequirements.clearColor;
|
||||
renderTargetDesc.samples = msaaSamples;
|
||||
|
||||
if (UTILS_UNLIKELY(blur)) {
|
||||
@@ -530,16 +528,14 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
.attachments = {
|
||||
.color = { data.tempBlurSrc },
|
||||
.depth = depth },
|
||||
.clearColor = vsmClearColor,
|
||||
.clearColor = textureRequirements.clearColor,
|
||||
.samples = msaaSamples,
|
||||
.clearFlags = TargetBufferFlags::COLOR
|
||||
| TargetBufferFlags::DEPTH
|
||||
.clearFlags = TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// the shadowmap layer
|
||||
data.output = builder.write(data.output,
|
||||
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
renderTargetDesc.attachments.depth = data.output;
|
||||
renderTargetDesc.clearFlags = TargetBufferFlags::DEPTH;
|
||||
}
|
||||
@@ -594,27 +590,27 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
// the whole layer. Blurring should happen per shadowmap, not for the whole
|
||||
// layer.
|
||||
|
||||
const float blurWidth = options->vsm.blurWidth;
|
||||
// FIXME: this Gaussian blur is not precise enough for EVSM
|
||||
|
||||
const float blurWidth = options.vsm.blurWidth;
|
||||
if (blurWidth > 0.0f) {
|
||||
const float sigma = (blurWidth + 1.0f) / 6.0f;
|
||||
size_t kernelWidth = std::ceil((blurWidth - 5.0f) / 4.0f);
|
||||
size_t kernelWidth = size_t(std::ceil((blurWidth - 5.0f) / 4.0f));
|
||||
kernelWidth = kernelWidth * 4 + 5;
|
||||
ppm.gaussianBlurPass(fg,
|
||||
shadowPass->tempBlurSrc,
|
||||
shadowPass->output,
|
||||
false, kernelWidth, sigma);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: mipmapping here is broken because it'll access texels from adjacent
|
||||
// shadow maps.
|
||||
|
||||
// If the shadow texture has more than one level, mipmapping was requested, either directly
|
||||
// or indirectly via anisotropic filtering.
|
||||
// So generate the mipmaps for each layer
|
||||
if (textureRequirements.levels > 1) {
|
||||
for (size_t level = 0; level < textureRequirements.levels - 1; level++) {
|
||||
ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level, vsmClearColor);
|
||||
}
|
||||
// If the shadow texture has more than one level, mipmapping was requested, either directly
|
||||
// or indirectly via anisotropic filtering.
|
||||
// So generate the mipmaps for each layer
|
||||
if (UTILS_UNLIKELY(textureRequirements.levels > 1)) {
|
||||
auto& ppm = engine.getPostProcessManager();
|
||||
for (size_t level = 0; level < textureRequirements.levels - 1; level++) {
|
||||
ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level, textureRequirements.clearColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -626,7 +622,8 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept {
|
||||
|
||||
FScene* scene = view.getScene();
|
||||
FScene const* const scene = view.getScene();
|
||||
auto const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
auto& lcm = engine.getLightManager();
|
||||
|
||||
FLightManager::Instance const directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
|
||||
@@ -650,7 +647,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
};
|
||||
|
||||
bool hasVisibleShadows = false;
|
||||
utils::Slice<ShadowMap> cascadedShadowMaps = getCascadedShadowMap();
|
||||
utils::Slice<ShadowMap> const cascadedShadowMaps = getCascadedShadowMap();
|
||||
if (!cascadedShadowMaps.empty()) {
|
||||
// Even if we have more than one cascade, we cull directional shadow casters against the
|
||||
// entire camera frustum, as if we only had a single cascade.
|
||||
@@ -750,6 +747,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
||||
s.shadows[shadowIndex].normalBias = wsTexelSize * normalBias;
|
||||
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
||||
s.shadows[shadowIndex].vsmExponent = getWrapExponentEVSM(vsmShadowOptions, options);
|
||||
s.shadows[shadowIndex].bulbRadiusLs =
|
||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius / length(wsTexelSize);
|
||||
|
||||
@@ -801,10 +799,8 @@ void ShadowMapManager::updateSpotVisibilityMasks(
|
||||
const bool visSpotShadowRenderable = v.castShadows && inVisibleLayer &&
|
||||
(!v.culling || (mask & VISIBLE_DYN_SHADOW_RENDERABLE));
|
||||
|
||||
using Type = Culler::result_type;
|
||||
|
||||
visibleMask[i] &= ~Type(VISIBLE_DYN_SHADOW_RENDERABLE);
|
||||
visibleMask[i] |= Type(visSpotShadowRenderable << VISIBLE_DYN_SHADOW_RENDERABLE_BIT);
|
||||
visibleMask[i] &= ~Culler::result_type(VISIBLE_DYN_SHADOW_RENDERABLE);
|
||||
visibleMask[i] |= Culler::result_type(visSpotShadowRenderable << VISIBLE_DYN_SHADOW_RENDERABLE_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,13 +809,14 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo const& sceneInfo) noexcept {
|
||||
|
||||
const size_t lightIndex = shadowMap.getLightIndex();
|
||||
FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions();
|
||||
FLightManager::ShadowOptions const& options = shadowMap.getShadowOptions();
|
||||
auto const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
|
||||
// update the shadow map frustum/camera
|
||||
const ShadowMap::ShadowMapInfo shadowMapInfo{
|
||||
.atlasDimension = mTextureAtlasRequirements.size,
|
||||
.textureDimension = uint16_t(options->mapSize),
|
||||
.shadowDimension = uint16_t(options->mapSize - 2u),
|
||||
.textureDimension = uint16_t(options.mapSize),
|
||||
.shadowDimension = uint16_t(options.mapSize - 2u),
|
||||
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
|
||||
engine.getBackend() == Backend::VULKAN ||
|
||||
engine.getBackend() == Backend::WEBGPU,
|
||||
@@ -834,7 +831,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
const float2 wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options.normalBias;
|
||||
|
||||
auto& s = mShadowUb.edit();
|
||||
const double n = shadowMap.getCamera().getNear();
|
||||
@@ -845,9 +842,10 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
||||
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
||||
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
||||
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
||||
s.shadows[shadowIndex].vsmExponent = getWrapExponentEVSM(vsmShadowOptions, options);
|
||||
s.shadows[shadowIndex].bulbRadiusLs =
|
||||
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius
|
||||
/ length(wsTexelSizeAtOneMeter);
|
||||
|
||||
}
|
||||
@@ -905,13 +903,14 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
|
||||
const uint8_t face = shadowMap.getFace();
|
||||
const size_t lightIndex = shadowMap.getLightIndex();
|
||||
FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions();
|
||||
FLightManager::ShadowOptions const& options = shadowMap.getShadowOptions();
|
||||
auto const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
|
||||
// update the shadow map frustum/camera
|
||||
const ShadowMap::ShadowMapInfo shadowMapInfo{
|
||||
.atlasDimension = mTextureAtlasRequirements.size,
|
||||
.textureDimension = uint16_t(options->mapSize),
|
||||
.shadowDimension = uint16_t(options->mapSize), // point-lights don't have a border
|
||||
.textureDimension = uint16_t(options.mapSize),
|
||||
.shadowDimension = uint16_t(options.mapSize), // point-lights don't have a border
|
||||
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
|
||||
engine.getBackend() == Backend::VULKAN ||
|
||||
engine.getBackend() == Backend::WEBGPU,
|
||||
@@ -926,7 +925,7 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
const float2 wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options.normalBias;
|
||||
|
||||
auto& s = mShadowUb.edit();
|
||||
const double n = shadowMap.getCamera().getNear();
|
||||
@@ -937,9 +936,10 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
||||
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
||||
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
||||
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
||||
s.shadows[shadowIndex].vsmExponent = getWrapExponentEVSM(vsmShadowOptions, options);
|
||||
s.shadows[shadowIndex].bulbRadiusLs =
|
||||
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius
|
||||
/ length(wsTexelSizeAtOneMeter);
|
||||
}
|
||||
}
|
||||
@@ -995,7 +995,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
|
||||
lightData.data<FScene::SHADOW_INFO>());
|
||||
|
||||
ShadowTechnique shadowTechnique{};
|
||||
utils::Slice<const ShadowMap> spotShadowMaps = getSpotShadowMaps();
|
||||
utils::Slice<const ShadowMap> const spotShadowMaps = getSpotShadowMaps();
|
||||
if (!spotShadowMaps.empty()) {
|
||||
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
||||
for (ShadowMap const& shadowMap : spotShadowMaps) {
|
||||
@@ -1035,14 +1035,14 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
for (ShadowMap const& shadowMap : getCascadedShadowMap()) {
|
||||
// Shadow map size should be the same for all cascades.
|
||||
auto const& options = shadowMap.getShadowOptions();
|
||||
maxDimension = std::max(maxDimension, options->mapSize);
|
||||
elvsm = elvsm || options->vsm.elvsm;
|
||||
maxDimension = std::max(maxDimension, options.mapSize);
|
||||
elvsm = elvsm || options.vsm.elvsm;
|
||||
}
|
||||
|
||||
for (ShadowMap const& shadowMap : getSpotShadowMaps()) {
|
||||
auto const& options = shadowMap.getShadowOptions();
|
||||
maxDimension = std::max(maxDimension, options->mapSize);
|
||||
elvsm = elvsm || options->vsm.elvsm;
|
||||
maxDimension = std::max(maxDimension, options.mapSize);
|
||||
elvsm = elvsm || options.vsm.elvsm;
|
||||
}
|
||||
|
||||
uint8_t layersNeeded = 0;
|
||||
@@ -1052,7 +1052,7 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
ShadowMap* pShadowMap) mutable {
|
||||
// Allocate shadowmap from our Atlas Allocator
|
||||
auto const& options = pShadowMap->getShadowOptions();
|
||||
auto allocation = allocator.allocate(options->mapSize);
|
||||
auto allocation = allocator.allocate(options.mapSize);
|
||||
assert_invariant(allocation.isValid());
|
||||
assert_invariant(!allocation.viewport.empty());
|
||||
pShadowMap->setAllocation(allocation.layer, allocation.viewport);
|
||||
@@ -1088,6 +1088,7 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
msaaSamples = 1;
|
||||
}
|
||||
|
||||
float4 clearColor{};
|
||||
TextureFormat format = TextureFormat::DEPTH16;
|
||||
if (view.hasVSM()) {
|
||||
if (vsmShadowOptions.highPrecision) {
|
||||
@@ -1095,6 +1096,9 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
} else {
|
||||
format = elvsm ? TextureFormat::RGBA16F : TextureFormat::RG16F;
|
||||
}
|
||||
const float maxMoment2 = getMaxMomentEVSM(vsmShadowOptions);
|
||||
const float maxMoment1 = std::sqrt(maxMoment2);
|
||||
clearColor = { maxMoment1, maxMoment2, 0, 0 };
|
||||
}
|
||||
|
||||
mSoftShadowOptions = view.getSoftShadowOptions();
|
||||
@@ -1116,7 +1120,8 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
layersNeeded,
|
||||
mipLevels,
|
||||
msaaSamples,
|
||||
format
|
||||
format,
|
||||
clearColor
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -47,9 +47,12 @@
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
#include <math/half.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
@@ -145,6 +148,28 @@ public:
|
||||
// for debugging only
|
||||
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
|
||||
|
||||
static float getMaxMomentEVSM(VsmShadowOptions const& vsmShadowOptions) noexcept {
|
||||
return vsmShadowOptions.highPrecision ?
|
||||
std::numeric_limits<float>::max() :
|
||||
float(std::numeric_limits<math::half>::max());
|
||||
}
|
||||
|
||||
static float getMaxWrapExponentEVSM(VsmShadowOptions const& vsmShadowOptions) noexcept {
|
||||
constexpr float low = 5.2f; // ~ std::log(std::numeric_limits<math::half>::max()) * 0.5f;
|
||||
constexpr float high = 40.0f; // ~ std::log(std::numeric_limits<float>::max()) * 0.5f;
|
||||
return vsmShadowOptions.highPrecision ? high : low;
|
||||
}
|
||||
|
||||
static float getWrapExponentEVSM(
|
||||
VsmShadowOptions const& vsmShadowOptions,
|
||||
LightManager::ShadowOptions const& options) noexcept {
|
||||
constexpr float ABSOLUTE_FILTER_LIMIT = 42.0f;
|
||||
float const targetExponent = getMaxWrapExponentEVSM(vsmShadowOptions);
|
||||
float const effectiveFilterRadius = std::max(1.0f, options.vsm.blurWidth);
|
||||
float const filterCeiling = ABSOLUTE_FILTER_LIMIT / effectiveFilterRadius;
|
||||
return std::min(targetExponent, filterCeiling);
|
||||
}
|
||||
|
||||
private:
|
||||
explicit ShadowMapManager(FEngine& engine);
|
||||
|
||||
@@ -216,6 +241,7 @@ private:
|
||||
uint8_t levels = 0;
|
||||
uint8_t msaaSamples = 1;
|
||||
backend::TextureFormat format = backend::TextureFormat::DEPTH16;
|
||||
math::float4 clearColor{};
|
||||
} mTextureAtlasRequirements;
|
||||
|
||||
SoftShadowOptions mSoftShadowOptions;
|
||||
|
||||
@@ -93,6 +93,10 @@ public:
|
||||
mHasScissor = true;
|
||||
}
|
||||
|
||||
void setScissor(backend::Viewport const& viewport) noexcept {
|
||||
setScissor(viewport.left, viewport.bottom, viewport.width, viewport.height);
|
||||
}
|
||||
|
||||
void unsetScissor() noexcept {
|
||||
constexpr uint32_t maxvalu = std::numeric_limits<int32_t>::max();
|
||||
mScissorRect = { 0, 0, maxvalu, maxvalu };
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <ratio>
|
||||
@@ -1131,8 +1132,6 @@ void FView::prepareShadowMapping() const noexcept {
|
||||
uniforms = mShadowMapManager->getShadowMappingUniforms();
|
||||
}
|
||||
|
||||
constexpr float low = 5.54f; // ~ std::log(std::numeric_limits<math::half>::max()) * 0.5f;
|
||||
constexpr float high = 42.0f; // ~ std::log(std::numeric_limits<float>::max()) * 0.5f;
|
||||
constexpr uint32_t SHADOW_SAMPLING_RUNTIME_PCF = 0u;
|
||||
constexpr uint32_t SHADOW_SAMPLING_RUNTIME_EVSM = 1u;
|
||||
constexpr uint32_t SHADOW_SAMPLING_RUNTIME_DPCF = 2u;
|
||||
@@ -1148,8 +1147,8 @@ void FView::prepareShadowMapping() const noexcept {
|
||||
break;
|
||||
case ShadowType::VSM:
|
||||
s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_EVSM;
|
||||
s.vsmExponent = mVsmShadowOptions.highPrecision ? high : low;
|
||||
s.vsmDepthScale = mVsmShadowOptions.minVarianceScale * 0.01f * s.vsmExponent;
|
||||
s.vsmExponent = 0; // this is only used when rendering the shadowmap, not when using it
|
||||
s.vsmMaxMoment = ShadowMapManager::getMaxMomentEVSM(mVsmShadowOptions);
|
||||
s.vsmLightBleedReduction = mVsmShadowOptions.lightBleedReduction;
|
||||
break;
|
||||
case ShadowType::DPCF:
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <math/half.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -74,8 +76,8 @@ void ShadowMapDescriptorSet::prepareLodBias(Transaction const& transaction, floa
|
||||
}
|
||||
|
||||
void ShadowMapDescriptorSet::prepareViewport(Transaction const& transaction,
|
||||
backend::Viewport const& viewport) noexcept {
|
||||
PerViewDescriptorSetUtils::prepareViewport(edit(transaction), viewport, viewport);
|
||||
backend::Viewport const& physicalViewport, backend::Viewport const& logicalViewport) noexcept {
|
||||
PerViewDescriptorSetUtils::prepareViewport(edit(transaction), physicalViewport, logicalViewport);
|
||||
// TODO: offset calculation is now different
|
||||
}
|
||||
|
||||
@@ -90,11 +92,10 @@ void ShadowMapDescriptorSet::prepareMaterialGlobals(Transaction const& transacti
|
||||
}
|
||||
|
||||
void ShadowMapDescriptorSet::prepareShadowMapping(Transaction const& transaction,
|
||||
bool const highPrecision) noexcept {
|
||||
float const vsmExponent, float const vsmMaxMoment) noexcept {
|
||||
auto& s = edit(transaction);
|
||||
constexpr float low = 5.54f; // ~ std::log(std::numeric_limits<math::half>::max()) * 0.5f;
|
||||
constexpr float high = 42.0f; // ~ std::log(std::numeric_limits<float>::max()) * 0.5f;
|
||||
s.vsmExponent = highPrecision ? high : low;
|
||||
s.vsmExponent = vsmExponent;
|
||||
s.vsmMaxMoment = vsmMaxMoment;
|
||||
}
|
||||
|
||||
ShadowMapDescriptorSet::Transaction ShadowMapDescriptorSet::open(DriverApi& driver) noexcept {
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
float bias) noexcept;
|
||||
|
||||
static void prepareViewport(Transaction const& transaction,
|
||||
backend::Viewport const& viewport) noexcept;
|
||||
backend::Viewport const& physicalViewport, backend::Viewport const& logicalViewport) noexcept;
|
||||
|
||||
static void prepareTime(Transaction const& transaction,
|
||||
FEngine const& engine, math::float4 const& userTime) noexcept;
|
||||
@@ -75,7 +75,7 @@ public:
|
||||
std::array<math::float4, 4> const& materialGlobals) noexcept;
|
||||
|
||||
static void prepareShadowMapping(Transaction const& transaction,
|
||||
bool highPrecision) noexcept;
|
||||
float vsmExponent, float vsmMaxMoment) noexcept;
|
||||
|
||||
static Transaction open(backend::DriverApi& driver) noexcept;
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ material {
|
||||
{
|
||||
name : color,
|
||||
target : color,
|
||||
type : float4
|
||||
type : float4,
|
||||
precision : high
|
||||
}
|
||||
],
|
||||
domain : postprocess,
|
||||
@@ -30,8 +31,29 @@ material {
|
||||
|
||||
fragment {
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp vec2 uv = gl_FragCoord.xy * materialParams.uvscale;
|
||||
postProcess.color = textureLod(materialParams_color,
|
||||
vec3(uv, materialParams.layer), 0.0);
|
||||
// For EVSM mipmapping, we use a custom box filter (instead of the GPU's bilinear filter), because GPUs often
|
||||
// use a lower precision for texture interpolation and EVSM moments are very sensitive to that.
|
||||
|
||||
highp ivec2 destCoord = ivec2(gl_FragCoord.xy);
|
||||
highp ivec3 srcCoord = ivec3(destCoord * 2, materialParams.layer);
|
||||
|
||||
#if defined(TARGET_WEBGPU_ENVIRONMENT)
|
||||
// it looks like WebGPU doesn't support texelFetchOffset
|
||||
highp vec4 m0 = texelFetch(materialParams_color, ivec3(srcCoord.xy + ivec2(0, 0), srcCoord.z), 0);
|
||||
highp vec4 m1 = texelFetch(materialParams_color, ivec3(srcCoord.xy + ivec2(1, 0), srcCoord.z), 0);
|
||||
highp vec4 m2 = texelFetch(materialParams_color, ivec3(srcCoord.xy + ivec2(0, 1), srcCoord.z), 0);
|
||||
highp vec4 m3 = texelFetch(materialParams_color, ivec3(srcCoord.xy + ivec2(1, 1), srcCoord.z), 0);
|
||||
#else
|
||||
highp vec4 m0 = texelFetchOffset(materialParams_color, srcCoord, 0, ivec2(0, 0));
|
||||
highp vec4 m1 = texelFetchOffset(materialParams_color, srcCoord, 0, ivec2(1, 0));
|
||||
highp vec4 m2 = texelFetchOffset(materialParams_color, srcCoord, 0, ivec2(0, 1));
|
||||
highp vec4 m3 = texelFetchOffset(materialParams_color, srcCoord, 0, ivec2(1, 1));
|
||||
#endif
|
||||
|
||||
// Make sure to not overflow the moments when averaging them
|
||||
postProcess.color = (m0 * 0.25) +
|
||||
(m1 * 0.25) +
|
||||
(m2 * 0.25) +
|
||||
(m3 * 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
// VSM shadows [variant: VSM]
|
||||
// --------------------------------------------------------------------------------------------
|
||||
float vsmExponent;
|
||||
float vsmDepthScale;
|
||||
float vsmMaxMoment;
|
||||
float vsmLightBleedReduction;
|
||||
uint32_t shadowSamplingType; // 0: vsm, 1: dpcf
|
||||
|
||||
@@ -302,9 +302,9 @@ struct ShadowUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
float bulbRadiusLs; // 4
|
||||
float nearOverFarMinusNear; // 4
|
||||
math::float2 normalBias; // 4
|
||||
bool elvsm; // 4
|
||||
uint32_t layer; // 4
|
||||
uint32_t reserved1; // 4
|
||||
bool elvsm; // 4 // could be 1 bit
|
||||
uint32_t layer; // 4 // could be 8 bits
|
||||
float vsmExponent; // 4 // could be fp16
|
||||
uint32_t reserved2; // 4
|
||||
};
|
||||
ShadowData shadows[CONFIG_MAX_SHADOWMAPS];
|
||||
|
||||
@@ -175,7 +175,7 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
|
||||
// VSM shadows [variant: VSM]
|
||||
// ------------------------------------------------------------------------------------
|
||||
{ "vsmExponent", 0, Type::FLOAT },
|
||||
{ "vsmDepthScale", 0, Type::FLOAT },
|
||||
{ "vsmMaxMoment", 0, Type::FLOAT, Precision::HIGH },
|
||||
{ "vsmLightBleedReduction", 0, Type::FLOAT },
|
||||
{ "shadowSamplingType", 0, Type::UINT },
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ layout(location = 0) out highp uvec2 outPicking;
|
||||
// note: VARIANT_HAS_VSM and VARIANT_HAS_PICKING are mutually exclusive
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
highp vec2 computeDepthMomentsVSM(const highp float depth);
|
||||
highp vec4 computeDepthMomentsVSM(const highp float depth);
|
||||
|
||||
void main() {
|
||||
filament_lodBias = frameUniforms.lodBias;
|
||||
@@ -53,9 +53,7 @@ void main() {
|
||||
// we always compute the "negative" side of ELVSM because the cost is small, and this allows
|
||||
// EVSM/ELVSM choice to be done on the CPU side more easily.
|
||||
highp float depth = vertex_worldPosition.w;
|
||||
depth = exp(frameUniforms.vsmExponent * depth);
|
||||
fragColor.xy = computeDepthMomentsVSM(depth);
|
||||
fragColor.zw = computeDepthMomentsVSM(-1.0 / depth); // requires at least RGBA16F
|
||||
fragColor = computeDepthMomentsVSM(depth);
|
||||
#elif defined(VARIANT_HAS_PICKING)
|
||||
#if FILAMENT_EFFECTIVE_VERSION == 100
|
||||
outPicking.a = mod(float(object_uniforms_objectId / 65536), 256.0) / 255.0;
|
||||
@@ -74,22 +72,30 @@ void main() {
|
||||
#endif
|
||||
}
|
||||
|
||||
highp vec2 computeDepthMomentsVSM(const highp float depth) {
|
||||
// computes the moments
|
||||
#if MATERIAL_FEATURE_LEVEL > 0
|
||||
|
||||
highp vec4 computeDepthMomentsVSM(const highp float depth) {
|
||||
// See GPU Gems 3
|
||||
// https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-8-summed-area-variance-shadow-maps
|
||||
highp vec2 moments;
|
||||
// computes the first two moments
|
||||
float c = frameUniforms.vsmExponent;
|
||||
highp float MAX_MOMENT = frameUniforms.vsmMaxMoment;
|
||||
|
||||
// the first moment is just the depth (average)
|
||||
moments.x = depth;
|
||||
// wrap depth for EVSM
|
||||
highp float z = exp(c * depth);
|
||||
|
||||
// compute the 2nd moment over the pixel extents.
|
||||
moments.y = depth * depth;
|
||||
// compute EVSM moments
|
||||
highp vec2 m1 = vec2(z, -1.0 / z);
|
||||
highp vec2 m2 = m1 * m1;
|
||||
|
||||
// the local linear approximation is not correct with a warped depth
|
||||
//highp float dx = dFdx(depth);
|
||||
//highp float dy = dFdy(depth);
|
||||
//moments.y += 0.25 * (dx * dx + dy * dy);
|
||||
// compute analytic variance (2nd moment), taking into account the change in depth accross the texel
|
||||
highp float dzdx = dFdx(depth);
|
||||
highp float dzdy = dFdy(depth);
|
||||
highp float linearVariance = 0.25 * (dzdx * dzdx + dzdy * dzdy);
|
||||
highp vec2 analyticVariance = c * c * m2 * linearVariance;
|
||||
m2 = min(m2 + analyticVariance, MAX_MOMENT);
|
||||
|
||||
return moments;
|
||||
return vec4(m1.x, m2.x, m1.y, m2.y);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -335,6 +335,91 @@ float ShadowSample_PCSS(const bool DIRECTIONAL,
|
||||
return 1.0 - percentageOccluded;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// VSM
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
float chebyshevUpperBound(const highp vec2 moments, const highp float depth,
|
||||
const highp float minVariance, const highp float lbrAmount) {
|
||||
// Fast path: if the receiver is fully in front of the caster
|
||||
if (depth <= moments.x) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Calculate variance with our dynamically injected floor
|
||||
highp float variance = max(moments.y - (moments.x * moments.x), minVariance);
|
||||
|
||||
// Standard Chebyshev inequality
|
||||
highp float d = depth - moments.x;
|
||||
highp float p_max = variance / (variance + d * d);
|
||||
|
||||
// Apply Light Bleeding Reduction (LBR)
|
||||
return saturate((p_max - lbrAmount) / (1.0 - lbrAmount));
|
||||
}
|
||||
|
||||
float evaluateEVSM(const bool ELVSM, float c,
|
||||
const highp vec4 moments, const highp float zReceiver,
|
||||
const highp vec2 dzduv, const highp vec2 texelSize) {
|
||||
const highp float EPSILON_MULTIPLIER = 0.002; // could be 0.00001 in fp32
|
||||
float lbrAmount = frameUniforms.vsmLightBleedReduction;
|
||||
|
||||
// Scale the UV-space gradient down to a single shadow map texel footprint.
|
||||
highp vec2 texel_dzduv = dzduv * texelSize;
|
||||
|
||||
// squared magnitude of the linear depth gradient across a single shadow map texel footprint
|
||||
highp float dz2 = dot(texel_dzduv, texel_dzduv);
|
||||
|
||||
// remap depth to [-1, 1]
|
||||
highp float depth = zReceiver * 2.0 - 1.0;
|
||||
|
||||
// positive wrap
|
||||
highp float pw = exp(c * depth);
|
||||
highp float epsilon = EPSILON_MULTIPLIER * (pw * pw);
|
||||
// Dynamic variance for the positive side (derivative of wraped depth w.r.t. light-space depth via Chain Rule)
|
||||
highp float dpwdz = 2.0 * c * pw;
|
||||
highp float pMinVariance = epsilon + 0.25 * (dpwdz * dpwdz) * dz2;
|
||||
float p = chebyshevUpperBound(moments.xy, pw, pMinVariance, lbrAmount);
|
||||
|
||||
// negative wrap
|
||||
if (ELVSM) {
|
||||
highp float nw = -1.0 / pw;
|
||||
highp float epsilon = EPSILON_MULTIPLIER * (nw * nw);
|
||||
// Dynamic variance for the negative side (derivative of wraped depth w.r.t. light-space depth via Chain Rule)
|
||||
highp float dnwdz = 2.0 * c * nw;
|
||||
highp float nMinVariance = epsilon + 0.25 * (dnwdz * dnwdz) * dz2;
|
||||
float n = chebyshevUpperBound(moments.zw, nw, nMinVariance, lbrAmount);
|
||||
p = min(p, n);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
float ShadowSample_VSM(const bool DIRECTIONAL, const highp sampler2DArray shadowMap,
|
||||
const highp vec4 scissorNormalized,
|
||||
const uint layer, const int index,
|
||||
const highp vec4 shadowPosition, const highp float zLight) {
|
||||
|
||||
bool ELVSM = shadowUniforms.shadows[index].elvsm;
|
||||
float c = shadowUniforms.shadows[index].vsmExponent;
|
||||
highp vec2 texelSize = vec2(1.0) / vec2(textureSize(shadowMap, 0)); // TODO: put this in a uniform
|
||||
|
||||
// note: shadowPosition.z is in linear light-space normalized to [0, 1]
|
||||
// see: ShadowMap::computeVsmLightSpaceMatrix() in ShadowMap.cpp
|
||||
// see: computeLightSpacePosition() in common_shadowing.fs
|
||||
highp vec3 position = vec3(shadowPosition.xy * (1.0 / shadowPosition.w), shadowPosition.z);
|
||||
|
||||
// plane receiver bias to reduce shadow acnee on the received plane (before clamp)
|
||||
highp vec2 dzduv = computeReceiverPlaneDepthBias(position);
|
||||
|
||||
// clamp uv to border
|
||||
position.xy = clamp(position.xy, scissorNormalized.xy, scissorNormalized.zw);
|
||||
|
||||
// Read the shadow map with all available filtering
|
||||
highp vec4 moments = texture(shadowMap, vec3(position.xy, layer));
|
||||
|
||||
return evaluateEVSM(ELVSM, c, moments, position.z, dzduv, texelSize);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Screen-space Contact Shadows
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -409,70 +494,6 @@ float screenSpaceContactShadow(vec3 lightDirection) {
|
||||
return occlusion;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// VSM
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
float linstep(const float min, const float max, const float v) {
|
||||
// we could use smoothstep() too
|
||||
return clamp((v - min) / (max - min), 0.0, 1.0);
|
||||
}
|
||||
|
||||
float reduceLightBleed(const float pMax, const float amount) {
|
||||
// Remove the [0, amount] tail and linearly rescale (amount, 1].
|
||||
return linstep(amount, 1.0, pMax);
|
||||
}
|
||||
|
||||
float chebyshevUpperBound(const highp vec2 moments, const highp float mean,
|
||||
const highp float minVariance, const float lightBleedReduction) {
|
||||
// Donnelly and Lauritzen 2006, "Variance Shadow Maps"
|
||||
|
||||
highp float variance = moments.y - (moments.x * moments.x);
|
||||
variance = max(variance, minVariance);
|
||||
|
||||
highp float d = mean - moments.x;
|
||||
float pMax = variance / (variance + d * d);
|
||||
|
||||
pMax = reduceLightBleed(pMax, lightBleedReduction);
|
||||
|
||||
return mean <= moments.x ? 1.0 : pMax;
|
||||
}
|
||||
|
||||
float evaluateShadowVSM(const highp vec2 moments, const highp float depth) {
|
||||
highp float depthScale = frameUniforms.vsmDepthScale * depth;
|
||||
highp float minVariance = depthScale * depthScale;
|
||||
return chebyshevUpperBound(moments, depth, minVariance, frameUniforms.vsmLightBleedReduction);
|
||||
}
|
||||
|
||||
float ShadowSample_VSM(const bool ELVSM, const highp sampler2DArray shadowMap,
|
||||
const highp vec4 scissorNormalized,
|
||||
const uint layer, const highp vec4 shadowPosition) {
|
||||
|
||||
// note: shadowPosition.z is in linear light-space normalized to [0, 1]
|
||||
// see: ShadowMap::computeVsmLightSpaceMatrix() in ShadowMap.cpp
|
||||
// see: computeLightSpacePosition() in common_shadowing.fs
|
||||
highp vec3 position = vec3(shadowPosition.xy * (1.0 / shadowPosition.w), shadowPosition.z);
|
||||
|
||||
// Note: we don't need to clamp to `scissorNormalized` in the VSM case because this is only
|
||||
// needed when the shadow casters and receivers are different, which is never the case with VSM
|
||||
// (see ShadowMap.cpp).
|
||||
|
||||
// Read the shadow map with all available filtering
|
||||
highp vec4 moments = texture(shadowMap, vec3(position.xy, layer));
|
||||
highp float depth = position.z;
|
||||
|
||||
// EVSM depth warping
|
||||
depth = depth * 2.0 - 1.0;
|
||||
depth = frameUniforms.vsmExponent * depth;
|
||||
|
||||
depth = exp(depth);
|
||||
float p = evaluateShadowVSM(moments.xy, depth);
|
||||
if (ELVSM) {
|
||||
p = min(p, evaluateShadowVSM(moments.zw, -1.0 / depth));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Shadow sampling dispatch
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -529,6 +550,9 @@ float shadow(const bool DIRECTIONAL,
|
||||
} else if (CONFIG_SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW) {
|
||||
return ShadowSample_PCF_Low(shadowMap, scissorNormalized, layer, shadowPosition);
|
||||
}
|
||||
|
||||
// should not happen
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Shadow requiring a sampler2D sampler (VSM, DPCF and PCSS)
|
||||
@@ -539,9 +563,8 @@ float shadow(const bool DIRECTIONAL,
|
||||
uint layer = shadowUniforms.shadows[index].layer;
|
||||
// This conditional is resolved at compile time
|
||||
if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_EVSM) {
|
||||
bool elvsm = shadowUniforms.shadows[index].elvsm;
|
||||
return ShadowSample_VSM(elvsm, shadowMap, scissorNormalized, layer,
|
||||
shadowPosition);
|
||||
return ShadowSample_VSM(DIRECTIONAL, shadowMap, scissorNormalized, layer, index,
|
||||
shadowPosition, zLight);
|
||||
}
|
||||
|
||||
if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_DPCF) {
|
||||
@@ -558,8 +581,8 @@ float shadow(const bool DIRECTIONAL,
|
||||
// This is here mostly for debugging at this point.
|
||||
// Note: In this codepath, the normal bias is not applied because we're in the VSM variant.
|
||||
// (see: get{Cascade|Spot}LightSpacePosition)
|
||||
return ShadowSample_PCF(shadowMap, scissorNormalized,
|
||||
layer, shadowPosition);
|
||||
return ShadowSample_PCF(shadowMap, scissorNormalized, layer,
|
||||
shadowPosition);
|
||||
}
|
||||
|
||||
// should not happen
|
||||
|
||||
@@ -10,7 +10,7 @@ struct ShadowData {
|
||||
highp vec2 normalBias;
|
||||
bool elvsm;
|
||||
mediump uint layer;
|
||||
mediump uint reserved1;
|
||||
mediump float vsmExponent;
|
||||
mediump uint reserved2;
|
||||
};
|
||||
#endif
|
||||
|
||||
2
web/filament-js/filament.d.ts
vendored
2
web/filament-js/filament.d.ts
vendored
@@ -1747,7 +1747,7 @@ export interface View$VsmShadowOptions {
|
||||
*/
|
||||
highPrecision?: boolean;
|
||||
/**
|
||||
* VSM minimum variance scale, must be positive.
|
||||
* @deprecated has no effect.
|
||||
*/
|
||||
minVarianceScale?: number;
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user