Add VSM shader changes (#2971)

This commit is contained in:
Ben Doherty
2020-08-19 10:56:48 -07:00
committed by GitHub
parent 331d7c3e00
commit 3e5d9fa0b4
14 changed files with 143 additions and 28 deletions

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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:

View File

@@ -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);

View File

@@ -3,6 +3,7 @@ struct Light {
vec3 l;
float attenuation;
float NoL;
vec3 worldPosition;
bool castsShadows;
bool contactShadows;
uint shadowIndex;

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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);
}