Add VSM shader changes (#2971)
This commit is contained in:
@@ -347,7 +347,7 @@ backend::Handle<backend::HwProgram> FMaterial::getSurfaceProgramSlow(uint8_t var
|
||||
UibGenerator::getPerRenderableBonesUib().getName());
|
||||
}
|
||||
|
||||
addSamplerGroup(pb, BindingPoints::PER_VIEW, SibGenerator::getPerViewSib(), mSamplerBindings);
|
||||
addSamplerGroup(pb, BindingPoints::PER_VIEW, SibGenerator::getPerViewSib(variantKey), mSamplerBindings);
|
||||
addSamplerGroup(pb, BindingPoints::PER_MATERIAL_INSTANCE, mSamplerInterfaceBlock, mSamplerBindings);
|
||||
|
||||
return createAndCacheProgram(std::move(pb), variantKey);
|
||||
|
||||
@@ -29,8 +29,8 @@ class SamplerInterfaceBlock;
|
||||
|
||||
class SibGenerator {
|
||||
public:
|
||||
static SamplerInterfaceBlock const& getPerViewSib() noexcept;
|
||||
static SamplerInterfaceBlock const* getSib(uint8_t bindingPoint) noexcept;
|
||||
static SamplerInterfaceBlock const& getPerViewSib(uint8_t variantKey) noexcept;
|
||||
static SamplerInterfaceBlock const* getSib(uint8_t bindingPoint, uint8_t variantKey) noexcept;
|
||||
};
|
||||
|
||||
struct PerViewSib {
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace filament {
|
||||
// Variant 0 | VSM | FOG | DEP | SKN | SRE | DYN | DIR |
|
||||
// ...-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
// Reserved variants:
|
||||
// Vertex depth 0 0 1 X 0 0 0
|
||||
// Vertex depth X 0 1 X 0 0 0
|
||||
// Fragment depth X 0 1 0 0 0 0
|
||||
// Reserved X X 1 X X X X
|
||||
// Reserved X X 0 X 1 0 0
|
||||
@@ -132,6 +132,10 @@ namespace filament {
|
||||
static constexpr uint8_t filterVariantVertex(uint8_t variantKey) noexcept {
|
||||
// filter out vertex variants that are not needed. For e.g. fog doesn't affect the
|
||||
// vertex shader.
|
||||
if (variantKey & DEPTH) {
|
||||
// VSM affects the vertex shader, but only for DEPTH variants.
|
||||
return variantKey & (VERTEX_MASK | VSM);
|
||||
}
|
||||
return variantKey & VERTEX_MASK;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace filament {
|
||||
|
||||
void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib,
|
||||
const char* materialName) {
|
||||
// We assume material variant 0 here, which is sufficient for calculating the binding map.
|
||||
// The material variant currently only affects sampler formats (for VSM), not offsets.
|
||||
const uint8_t variantKey = 0;
|
||||
uint8_t offset = 0;
|
||||
size_t maxSamplerIndex = backend::MAX_SAMPLER_COUNT - 1;
|
||||
bool overflow = false;
|
||||
@@ -35,7 +38,7 @@ void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib,
|
||||
if (blockIndex == filament::BindingPoints::PER_MATERIAL_INSTANCE) {
|
||||
sib = perMaterialSib;
|
||||
} else {
|
||||
sib = filament::SibGenerator::getSib(blockIndex);
|
||||
sib = filament::SibGenerator::getSib(blockIndex, variantKey);
|
||||
}
|
||||
if (sib) {
|
||||
auto sibFields = sib->getSamplerInfoList();
|
||||
@@ -66,7 +69,7 @@ void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib,
|
||||
if (blockIndex == filament::BindingPoints::PER_MATERIAL_INSTANCE) {
|
||||
sib = perMaterialSib;
|
||||
} else {
|
||||
sib = filament::SibGenerator::getSib(blockIndex);
|
||||
sib = filament::SibGenerator::getSib(blockIndex, variantKey);
|
||||
}
|
||||
if (sib) {
|
||||
auto sibFields = sib->getSamplerInfoList();
|
||||
|
||||
@@ -76,7 +76,7 @@ SamplerInterfaceBlock::Builder::~Builder() noexcept = default;
|
||||
|
||||
SamplerInterfaceBlock::SamplerInterfaceBlock() = default;
|
||||
SamplerInterfaceBlock::SamplerInterfaceBlock(const SamplerInterfaceBlock& rhs) = default;
|
||||
SamplerInterfaceBlock::SamplerInterfaceBlock(SamplerInterfaceBlock&& rhs) noexcept /* = default */ {};
|
||||
SamplerInterfaceBlock::SamplerInterfaceBlock(SamplerInterfaceBlock&& rhs) noexcept = default;
|
||||
SamplerInterfaceBlock& SamplerInterfaceBlock::operator=(const SamplerInterfaceBlock& rhs) = default;
|
||||
SamplerInterfaceBlock& SamplerInterfaceBlock::operator=(SamplerInterfaceBlock&& rhs) /*noexcept*/ = default;
|
||||
SamplerInterfaceBlock::~SamplerInterfaceBlock() noexcept = default;
|
||||
|
||||
@@ -16,39 +16,61 @@
|
||||
|
||||
#include "private/filament/SibGenerator.h"
|
||||
|
||||
#include "private/filament/Variant.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <private/filament/SamplerInterfaceBlock.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
SamplerInterfaceBlock const& SibGenerator::getPerViewSib() noexcept {
|
||||
SamplerInterfaceBlock const& SibGenerator::getPerViewSib(uint8_t variantKey) noexcept {
|
||||
using Type = SamplerInterfaceBlock::Type;
|
||||
using Format = SamplerInterfaceBlock::Format;
|
||||
using Precision = SamplerInterfaceBlock::Precision;
|
||||
|
||||
// TODO: ideally we'd want this to be constexpr, this is a compile time structure
|
||||
static SamplerInterfaceBlock sib = SamplerInterfaceBlock::Builder()
|
||||
.name("Light")
|
||||
.add("shadowMap", Type::SAMPLER_2D_ARRAY, Format::SHADOW, Precision::MEDIUM)
|
||||
.add("records", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM)
|
||||
.add("froxels", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM)
|
||||
.add("iblDFG", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM)
|
||||
.add("iblSpecular", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM)
|
||||
.add("ssao", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM)
|
||||
.add("ssr", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM)
|
||||
.add("structure", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM)
|
||||
auto buildSib = [] (bool hasVsm) {
|
||||
auto builder = SamplerInterfaceBlock::Builder();
|
||||
|
||||
builder
|
||||
.name("Light");
|
||||
|
||||
if (hasVsm) {
|
||||
builder.add("shadowMap", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH);
|
||||
} else {
|
||||
builder.add("shadowMap", Type::SAMPLER_2D_ARRAY, Format::SHADOW, Precision::MEDIUM);
|
||||
}
|
||||
|
||||
return builder
|
||||
.add("records", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM)
|
||||
.add("froxels", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM)
|
||||
.add("iblDFG", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM)
|
||||
.add("iblSpecular", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM)
|
||||
.add("ssao", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM)
|
||||
.add("ssr", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM)
|
||||
.add("structure", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM)
|
||||
.build();
|
||||
};
|
||||
|
||||
assert(sib.getSize() == PerViewSib::SAMPLER_COUNT);
|
||||
// TODO: ideally we'd want these to be constexpr, these are compile time structures.
|
||||
|
||||
return sib;
|
||||
static SamplerInterfaceBlock sibPcf = buildSib(false);
|
||||
static SamplerInterfaceBlock sibVsm = buildSib(true);
|
||||
|
||||
// SamplerBindingMap relies the assumption that Sibs have the same names and offsets
|
||||
// regardless of variant.
|
||||
assert(sibPcf.getSize() == PerViewSib::SAMPLER_COUNT);
|
||||
assert(sibVsm.getSize() == PerViewSib::SAMPLER_COUNT);
|
||||
|
||||
Variant v(variantKey);
|
||||
|
||||
return v.hasVsm() ? sibVsm : sibPcf;
|
||||
}
|
||||
|
||||
SamplerInterfaceBlock const* SibGenerator::getSib(uint8_t bindingPoint) noexcept {
|
||||
SamplerInterfaceBlock const* SibGenerator::getSib(uint8_t bindingPoint, uint8_t variantKey) noexcept {
|
||||
switch (bindingPoint) {
|
||||
case BindingPoints::PER_VIEW:
|
||||
return &getPerViewSib();
|
||||
return &getPerViewSib(variantKey);
|
||||
case BindingPoints::PER_RENDERABLE:
|
||||
return nullptr;
|
||||
case BindingPoints::LIGHTS:
|
||||
|
||||
@@ -193,6 +193,7 @@ std::string ShaderGenerator::createVertexProgram(filament::backend::ShaderModel
|
||||
cg.generateDefine(vs, "HAS_SHADOWING", litVariants && variant.hasShadowReceiver());
|
||||
cg.generateDefine(vs, "HAS_SHADOW_MULTIPLIER", material.hasShadowMultiplier);
|
||||
cg.generateDefine(vs, "HAS_SKINNING_OR_MORPHING", variant.hasSkinningOrMorphing());
|
||||
cg.generateDefine(vs, "HAS_VSM", variant.hasVsm());
|
||||
cg.generateDefine(vs, getShadingDefine(material.shading), true);
|
||||
generateMaterialDefines(vs, cg, mProperties, mDefines);
|
||||
|
||||
@@ -420,7 +421,7 @@ std::string ShaderGenerator::createFragmentProgram(filament::backend::ShaderMode
|
||||
cg.generateSeparator(fs);
|
||||
cg.generateSamplers(fs,
|
||||
material.samplerBindings.getBlockOffset(BindingPoints::PER_VIEW),
|
||||
SibGenerator::getPerViewSib());
|
||||
SibGenerator::getPerViewSib(variantKey));
|
||||
cg.generateSamplers(fs,
|
||||
material.samplerBindings.getBlockOffset(BindingPoints::PER_MATERIAL_INSTANCE),
|
||||
material.sib);
|
||||
|
||||
@@ -3,6 +3,7 @@ struct Light {
|
||||
vec3 l;
|
||||
float attenuation;
|
||||
float NoL;
|
||||
vec3 worldPosition;
|
||||
bool castsShadows;
|
||||
bool contactShadows;
|
||||
uint shadowIndex;
|
||||
|
||||
@@ -13,4 +13,22 @@ void main() {
|
||||
discard;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAS_VSM)
|
||||
// Since we're rendering from the perspective of the light, frameUniforms.cameraPosition is the
|
||||
// light position, in world space.
|
||||
// We use "distance to the light" as the depth metric, which works for both directional and spot
|
||||
// lights.
|
||||
highp float depth = length(frameUniforms.cameraPosition.xyz - vertex_worldPosition);
|
||||
|
||||
highp float dx = dFdx(depth);
|
||||
highp float dy = dFdy(depth);
|
||||
|
||||
// Output the first and second depth moments.
|
||||
// The first moment is mean depth.
|
||||
// The second moment is mean depth squared.
|
||||
// These values are retrieved when sampling the shadow map to compute variance.
|
||||
highp float bias = 0.25 * (dx * dx + dy * dy);
|
||||
fragColor = vec4(depth, depth * depth + bias, 0.0, 0.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3,15 +3,27 @@
|
||||
void materialVertex(inout MaterialVertexInputs m) { }
|
||||
|
||||
void main() {
|
||||
#if defined(VERTEX_DOMAIN_DEVICE)
|
||||
gl_Position = getPosition();
|
||||
#else
|
||||
|
||||
// World position is used to compute gl_Position, except for vertices already in the device domain.
|
||||
// Regardless of vertex domain, if VSM is turned on, then we need to compute world position to pass
|
||||
// to the fragment shader.
|
||||
#if !defined(VERTEX_DOMAIN_DEVICE) || defined(HAS_VSM)
|
||||
// Run initMaterialVertex to compute material.worldPosition.
|
||||
MaterialVertexInputs material;
|
||||
initMaterialVertex(material);
|
||||
materialVertex(material);
|
||||
#endif
|
||||
|
||||
#if defined(VERTEX_DOMAIN_DEVICE)
|
||||
gl_Position = getPosition();
|
||||
#else
|
||||
gl_Position = getClipFromWorldMatrix() * getWorldPosition(material);
|
||||
#endif
|
||||
|
||||
#if defined(HAS_VSM)
|
||||
vertex_worldPosition = material.worldPosition.xyz;
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_VULKAN_ENVIRONMENT)
|
||||
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
|
||||
gl_Position.y = -gl_Position.y;
|
||||
|
||||
@@ -45,7 +45,12 @@ void evaluateDirectionalLight(const MaterialInputs material,
|
||||
bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1u);
|
||||
if (hasDirectionalShadows && cascadeHasVisibleShadows) {
|
||||
uint layer = cascade;
|
||||
#if defined(HAS_VSM)
|
||||
highp float fragDepth = length(vertex_worldPosition - frameUniforms.lightPosition.xyz);
|
||||
visibility = shadowVsm(light_shadowMap, layer, getCascadeLightSpacePosition(cascade), fragDepth);
|
||||
#else
|
||||
visibility = shadow(light_shadowMap, layer, getCascadeLightSpacePosition(cascade));
|
||||
#endif
|
||||
}
|
||||
if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) {
|
||||
if (objectUniforms.screenSpaceContactShadows != 0u) {
|
||||
|
||||
@@ -130,6 +130,7 @@ Light getLight(const uint index) {
|
||||
light.l = normalize(posToLight);
|
||||
light.attenuation = getDistanceAttenuation(posToLight, positionFalloff.w);
|
||||
light.NoL = saturate(dot(shading_normal, light.l));
|
||||
light.worldPosition = positionFalloff.xyz;
|
||||
|
||||
uint type = floatBitsToUint(scaleOffsetShadowType.w);
|
||||
if (type == LIGHT_TYPE_SPOT) {
|
||||
@@ -170,8 +171,14 @@ void evaluatePunctualLights(const PixelParams pixel, inout vec3 color) {
|
||||
#if defined(HAS_SHADOWING)
|
||||
if (light.NoL > 0.0){
|
||||
if (light.castsShadows) {
|
||||
#if defined(HAS_VSM)
|
||||
highp float fragDepth = length(vertex_worldPosition - light.worldPosition);
|
||||
visibility = shadowVsm(light_shadowMap, light.shadowLayer,
|
||||
getSpotLightSpacePosition(light.shadowIndex), fragDepth);
|
||||
#else
|
||||
visibility = shadow(light_shadowMap, light.shadowLayer,
|
||||
getSpotLightSpacePosition(light.shadowIndex));
|
||||
getSpotLightSpacePosition(light.shadowIndex));
|
||||
#endif
|
||||
}
|
||||
if (light.contactShadows && visibility > 0.0) {
|
||||
if (objectUniforms.screenSpaceContactShadows != 0u) {
|
||||
|
||||
@@ -45,7 +45,11 @@ vec4 evaluateMaterial(const MaterialInputs material) {
|
||||
if ((frameUniforms.directionalShadows & 1u) != 0u) {
|
||||
uint cascade = getShadowCascade();
|
||||
uint layer = cascade;
|
||||
#if defined(HAS_VSM)
|
||||
// TODO: VSM shadow multiplier
|
||||
#else
|
||||
visibility = shadow(light_shadowMap, layer, getCascadeLightSpacePosition(cascade));
|
||||
#endif
|
||||
}
|
||||
if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) {
|
||||
if (objectUniforms.screenSpaceContactShadows != 0u) {
|
||||
|
||||
@@ -306,6 +306,32 @@ float screenSpaceContactShadow(vec3 lightDirection) {
|
||||
return occlusion;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// VSM
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
highp float linstep(const highp float a, const highp float b, const highp float v) {
|
||||
return clamp((v - a) / (b - a), 0.0, 1.0);
|
||||
}
|
||||
|
||||
highp float reduceLightBleed(const highp float pMax, const highp float amount) {
|
||||
return linstep(amount, 1.0, pMax);
|
||||
}
|
||||
|
||||
highp float chebyshevUpperBound(const highp vec2 moments, const highp float mean,
|
||||
const highp float minVariance, const highp float lightBleedReduction) {
|
||||
// Donnelly and Lauritzen 2006, "Variance Shadow Maps"
|
||||
|
||||
highp float variance = moments.y - (moments.x * moments.x);
|
||||
variance = max(variance, minVariance);
|
||||
|
||||
const highp float d = mean - moments.x;
|
||||
highp float pMax = variance / (variance + d * d);
|
||||
pMax = reduceLightBleed(pMax, lightBleedReduction);
|
||||
|
||||
return mean <= moments.x ? 1.0 : pMax;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Shadow sampling dispatch
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -327,3 +353,15 @@ float shadow(const lowp sampler2DArrayShadow shadowMap, const uint layer, const
|
||||
return ShadowSample_PCF_High(shadowMap, layer, size, shadowPosition);
|
||||
#endif
|
||||
}
|
||||
|
||||
float shadowVsm(const highp sampler2DArray shadowMap, const uint layer, const highp vec3 shadowPosition,
|
||||
const highp float fragDepth) {
|
||||
const highp vec2 moments = texture(shadowMap, vec3(shadowPosition.xy, layer)).xy;
|
||||
|
||||
// TODO: bias and lightBleedReduction should be uniforms
|
||||
const float bias = 0.01;
|
||||
const float lightBleedReduction = 0.2;
|
||||
|
||||
const float minVariance = bias * 0.01;
|
||||
return chebyshevUpperBound(moments, fragDepth, minVariance, lightBleedReduction);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user