Compare commits
15 Commits
bjd/comman
...
bjd/debugg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18dab53920 | ||
|
|
b754b81e6f | ||
|
|
65bb66f90c | ||
|
|
a0af84d034 | ||
|
|
3cdaf3feeb | ||
|
|
da7d1b6fa9 | ||
|
|
3129226839 | ||
|
|
6935acafff | ||
|
|
d8103f4fd6 | ||
|
|
09a13f4015 | ||
|
|
e888023102 | ||
|
|
f62f4736f0 | ||
|
|
a378272d56 | ||
|
|
3d61938fcf | ||
|
|
a9f3937da6 |
@@ -9,6 +9,7 @@ A new header is inserted each time a *tag* is created.
|
|||||||
- gltfio: recompute bounding boxes with morph targets
|
- gltfio: recompute bounding boxes with morph targets
|
||||||
- engine: add missing getters on `MaterialInstance`
|
- engine: add missing getters on `MaterialInstance`
|
||||||
- WebGL: add missing `ColorGrading` JS bindings
|
- WebGL: add missing `ColorGrading` JS bindings
|
||||||
|
- engine: improvements/cleanup of Shadow mapping code [⚠️ **Recompile Materials**]
|
||||||
|
|
||||||
## v1.28.3
|
## v1.28.3
|
||||||
|
|
||||||
|
|||||||
@@ -290,14 +290,10 @@ void PerViewUniforms::prepareShadowMapping(bool highPrecision) noexcept {
|
|||||||
|
|
||||||
void PerViewUniforms::prepareShadowSampling(PerViewUib& uniforms,
|
void PerViewUniforms::prepareShadowSampling(PerViewUib& uniforms,
|
||||||
ShadowMappingUniforms const& shadowMappingUniforms) noexcept {
|
ShadowMappingUniforms const& shadowMappingUniforms) noexcept {
|
||||||
uniforms.lightFromWorldMatrix = shadowMappingUniforms.lightFromWorldMatrix;
|
|
||||||
uniforms.cascadeSplits = shadowMappingUniforms.cascadeSplits;
|
uniforms.cascadeSplits = shadowMappingUniforms.cascadeSplits;
|
||||||
uniforms.shadowBulbRadiusLs = shadowMappingUniforms.shadowBulbRadiusLs;
|
|
||||||
uniforms.shadowBias = shadowMappingUniforms.shadowBias;
|
|
||||||
uniforms.ssContactShadowDistance = shadowMappingUniforms.ssContactShadowDistance;
|
uniforms.ssContactShadowDistance = shadowMappingUniforms.ssContactShadowDistance;
|
||||||
uniforms.directionalShadows = shadowMappingUniforms.directionalShadows;
|
uniforms.directionalShadows = shadowMappingUniforms.directionalShadows;
|
||||||
uniforms.cascades = shadowMappingUniforms.cascades;
|
uniforms.cascades = shadowMappingUniforms.cascades;
|
||||||
uniforms.cascades |= uint32_t(shadowMappingUniforms.elvsm) << 31u;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerViewUniforms::prepareShadowVSM(Handle<HwTexture> texture,
|
void PerViewUniforms::prepareShadowVSM(Handle<HwTexture> texture,
|
||||||
|
|||||||
@@ -97,8 +97,10 @@ void ShadowMapManager::setDirectionalShadowMap(size_t lightIndex,
|
|||||||
LightManager::ShadowOptions const* options) noexcept {
|
LightManager::ShadowOptions const* options) noexcept {
|
||||||
assert_invariant(options->shadowCascades <= CONFIG_MAX_SHADOW_CASCADES);
|
assert_invariant(options->shadowCascades <= CONFIG_MAX_SHADOW_CASCADES);
|
||||||
for (size_t c = 0; c < options->shadowCascades; c++) {
|
for (size_t c = 0; c < options->shadowCascades; c++) {
|
||||||
auto* pShadowMap = getCascadeShadowMap(c);
|
const size_t i = c;
|
||||||
pShadowMap->initialize(lightIndex, ShadowType::DIRECTIONAL, c, 0, options);
|
assert_invariant(i < CONFIG_MAX_SHADOW_CASCADES);
|
||||||
|
auto* pShadowMap = getCascadeShadowMap(i);
|
||||||
|
pShadowMap->initialize(lightIndex, ShadowType::DIRECTIONAL, i, 0, options);
|
||||||
mCascadeShadowMaps.push_back(pShadowMap);
|
mCascadeShadowMaps.push_back(pShadowMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,17 +109,19 @@ void ShadowMapManager::addShadowMap(size_t lightIndex, bool spotlight,
|
|||||||
LightManager::ShadowOptions const* options) noexcept {
|
LightManager::ShadowOptions const* options) noexcept {
|
||||||
if (spotlight) {
|
if (spotlight) {
|
||||||
const size_t c = mSpotShadowMaps.size();
|
const size_t c = mSpotShadowMaps.size();
|
||||||
assert_invariant(c < CONFIG_MAX_SHADOWMAPS);
|
const size_t i = c + CONFIG_MAX_SHADOW_CASCADES;
|
||||||
auto* pShadowMap = getPointOrSpotShadowMap(c);
|
assert_invariant(i < CONFIG_MAX_SHADOWMAPS);
|
||||||
pShadowMap->initialize(lightIndex, ShadowType::SPOT, c, 0, options);
|
auto* pShadowMap = getPointOrSpotShadowMap(i);
|
||||||
|
pShadowMap->initialize(lightIndex, ShadowType::SPOT, i, 0, options);
|
||||||
mSpotShadowMaps.push_back(pShadowMap);
|
mSpotShadowMaps.push_back(pShadowMap);
|
||||||
} else {
|
} else {
|
||||||
// point-light, generate 6 independent shadowmaps
|
// point-light, generate 6 independent shadowmaps
|
||||||
for (size_t face = 0; face < 6; face++) {
|
for (size_t face = 0; face < 6; face++) {
|
||||||
const size_t c = mSpotShadowMaps.size();
|
const size_t c = mSpotShadowMaps.size();
|
||||||
assert_invariant(c < CONFIG_MAX_SHADOWMAPS);
|
const size_t i = c + CONFIG_MAX_SHADOW_CASCADES;
|
||||||
auto* pShadowMap = getPointOrSpotShadowMap(c);
|
assert_invariant(i < CONFIG_MAX_SHADOWMAPS);
|
||||||
pShadowMap->initialize(lightIndex, ShadowType::POINT, c, face, options);
|
auto* pShadowMap = getPointOrSpotShadowMap(i);
|
||||||
|
pShadowMap->initialize(lightIndex, ShadowType::POINT, i, face, options);
|
||||||
mSpotShadowMaps.push_back(pShadowMap);
|
mSpotShadowMaps.push_back(pShadowMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,7 +459,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
|||||||
// entire camera frustum, as if we only had a single cascade.
|
// entire camera frustum, as if we only had a single cascade.
|
||||||
ShadowMap& shadowMap = *mCascadeShadowMaps[0];
|
ShadowMap& shadowMap = *mCascadeShadowMaps[0];
|
||||||
|
|
||||||
auto shaderParameters = shadowMap.updateDirectional(mEngine,
|
shadowMap.updateDirectional(mEngine,
|
||||||
lightData, 0, cameraInfo, shadowMapInfo, *scene, sceneInfo);
|
lightData, 0, cameraInfo, shadowMapInfo, *scene, sceneInfo);
|
||||||
|
|
||||||
hasVisibleShadows = shadowMap.hasVisibleShadows();
|
hasVisibleShadows = shadowMap.hasVisibleShadows();
|
||||||
@@ -464,18 +468,6 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
|||||||
Frustum const& frustum = shadowMap.getCamera().getCullingFrustum();
|
Frustum const& frustum = shadowMap.getCamera().getCullingFrustum();
|
||||||
FView::cullRenderables(engine.getJobSystem(), renderableData, frustum,
|
FView::cullRenderables(engine.getJobSystem(), renderableData, frustum,
|
||||||
VISIBLE_DIR_SHADOW_RENDERABLE_BIT);
|
VISIBLE_DIR_SHADOW_RENDERABLE_BIT);
|
||||||
|
|
||||||
// Set shadowBias, using the first directional cascade.
|
|
||||||
// when computing the required bias we need a half-texel size, so we multiply by 0.5 here.
|
|
||||||
// note: normalBias is set to zero for VSM
|
|
||||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : 0.5f * lcm.getShadowNormalBias(0);
|
|
||||||
// Texel size is constant for directional light (although that's not true when LISPSM
|
|
||||||
// is used, but in that case we're pretending it is).
|
|
||||||
const float wsTexelSize = shaderParameters.texelSizeAtOneMeterWs;
|
|
||||||
mShadowMappingUniforms.shadowBias = normalBias * wsTexelSize;
|
|
||||||
mShadowMappingUniforms.shadowBulbRadiusLs =
|
|
||||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius / wsTexelSize;
|
|
||||||
mShadowMappingUniforms.elvsm = options.vsm.elvsm;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,6 +520,10 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
|||||||
|
|
||||||
mShadowMappingUniforms.cascadeSplits = wsSplitPositionUniform;
|
mShadowMappingUniforms.cascadeSplits = wsSplitPositionUniform;
|
||||||
|
|
||||||
|
// when computing the required bias we need a half-texel size, so we multiply by 0.5 here.
|
||||||
|
// note: normalBias is set to zero for VSM
|
||||||
|
const float normalBias = shadowMapInfo.vsm ? 0.0f : 0.5f * lcm.getShadowNormalBias(0);
|
||||||
|
|
||||||
for (size_t i = 0, c = mCascadeShadowMaps.size(); i < c; i++) {
|
for (size_t i = 0, c = mCascadeShadowMaps.size(); i < c; i++) {
|
||||||
assert_invariant(mCascadeShadowMaps[i]);
|
assert_invariant(mCascadeShadowMaps[i]);
|
||||||
|
|
||||||
@@ -541,7 +537,22 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
|||||||
lightData, 0, cameraInfo, shadowMapInfo, *scene, sceneInfo);
|
lightData, 0, cameraInfo, shadowMapInfo, *scene, sceneInfo);
|
||||||
|
|
||||||
if (shadowMap.hasVisibleShadows()) {
|
if (shadowMap.hasVisibleShadows()) {
|
||||||
mShadowMappingUniforms.lightFromWorldMatrix[i] = shaderParameters.lightSpace;
|
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||||
|
assert_invariant(shadowIndex == i);
|
||||||
|
|
||||||
|
// Texel size is constant for directional light (although that's not true when LISPSM
|
||||||
|
// is used, but in that case we're pretending it is).
|
||||||
|
const float wsTexelSize = shaderParameters.texelSizeAtOneMeterWs;
|
||||||
|
|
||||||
|
auto& s = mShadowUb.edit();
|
||||||
|
s.shadows[shadowIndex].layer = shadowMap.getLayer();
|
||||||
|
s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace;
|
||||||
|
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSize;
|
||||||
|
s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSize;
|
||||||
|
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
||||||
|
s.shadows[shadowIndex].bulbRadiusLs =
|
||||||
|
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius / wsTexelSize;
|
||||||
|
|
||||||
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
||||||
cascadeHasVisibleShadows |= 0x1u << i;
|
cascadeHasVisibleShadows |= 0x1u << i;
|
||||||
}
|
}
|
||||||
@@ -669,6 +680,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap,
|
|||||||
auto& s = mShadowUb.edit();
|
auto& s = mShadowUb.edit();
|
||||||
const double n = shadowMap.getCamera().getNear();
|
const double n = shadowMap.getCamera().getNear();
|
||||||
const double f = shadowMap.getCamera().getCullingFar();
|
const double f = shadowMap.getCamera().getCullingFar();
|
||||||
|
s.shadows[shadowIndex].layer = shadowMap.getLayer();
|
||||||
s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace;
|
s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace;
|
||||||
s.shadows[shadowIndex].direction = direction;
|
s.shadows[shadowIndex].direction = direction;
|
||||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||||
@@ -679,6 +691,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap,
|
|||||||
s.shadows[shadowIndex].bulbRadiusLs =
|
s.shadows[shadowIndex].bulbRadiusLs =
|
||||||
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
||||||
/ wsTexelSizeAtOneMeter;
|
/ wsTexelSizeAtOneMeter;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,7 +753,6 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
|||||||
|
|
||||||
|
|
||||||
// and if we need to generate it, update all the UBO data
|
// and if we need to generate it, update all the UBO data
|
||||||
// Note: this below is done for all six faces even if it sets identical values each time
|
|
||||||
if (shadowMap.hasVisibleShadows()) {
|
if (shadowMap.hasVisibleShadows()) {
|
||||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||||
const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||||
@@ -750,7 +762,7 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
|||||||
auto& s = mShadowUb.edit();
|
auto& s = mShadowUb.edit();
|
||||||
const double n = shadowMap.getCamera().getNear();
|
const double n = shadowMap.getCamera().getNear();
|
||||||
const double f = shadowMap.getCamera().getCullingFar();
|
const double f = shadowMap.getCamera().getCullingFar();
|
||||||
|
s.shadows[shadowIndex].layer = shadowMap.getLayer();
|
||||||
s.shadows[shadowIndex].lightFromWorldMatrix = {}; // no texture matrix for point lights
|
s.shadows[shadowIndex].lightFromWorldMatrix = {}; // no texture matrix for point lights
|
||||||
s.shadows[shadowIndex].direction = {}; // no direction of point lights
|
s.shadows[shadowIndex].direction = {}; // no direction of point lights
|
||||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||||
@@ -779,14 +791,12 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
|
|||||||
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
||||||
for (auto const* pShadowMap : mSpotShadowMaps) {
|
for (auto const* pShadowMap : mSpotShadowMaps) {
|
||||||
const size_t lightIndex = pShadowMap->getLightIndex();
|
const size_t lightIndex = pShadowMap->getLightIndex();
|
||||||
|
// gather the per-light (not per shadow map) information. For point lights we will
|
||||||
// FIXME: currently we have one slot per shadowmap in the UBO, but we now have up to
|
// "see" 6 shadowmaps (one per face), we must use the first face one, the shader
|
||||||
// 6 shadowmap per light. So for now, we only write the data of the face 0,
|
// knows how to find the entry for other faces (they're guaranteed to be sequential).
|
||||||
// and the shader will figure out where to find the other face (layer+face)
|
|
||||||
if (pShadowMap->getFace() == 0) {
|
if (pShadowMap->getFace() == 0) {
|
||||||
shadowInfo[lightIndex].castsShadows = true; // FIXME: is that set correctly?
|
shadowInfo[lightIndex].castsShadows = true; // FIXME: is that set correctly?
|
||||||
shadowInfo[lightIndex].index = pShadowMap->getShadowIndex();
|
shadowInfo[lightIndex].index = pShadowMap->getShadowIndex();
|
||||||
shadowInfo[lightIndex].layer = pShadowMap->getLayer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,14 +45,10 @@ class FrameGraph;
|
|||||||
class RenderPass;
|
class RenderPass;
|
||||||
|
|
||||||
struct ShadowMappingUniforms {
|
struct ShadowMappingUniforms {
|
||||||
std::array<math::mat4f, CONFIG_MAX_SHADOW_CASCADES> lightFromWorldMatrix;
|
|
||||||
math::float4 cascadeSplits;
|
math::float4 cascadeSplits;
|
||||||
float shadowBulbRadiusLs;
|
|
||||||
float shadowBias;
|
|
||||||
float ssContactShadowDistance;
|
float ssContactShadowDistance;
|
||||||
uint32_t directionalShadows;
|
uint32_t directionalShadows;
|
||||||
uint32_t cascades;
|
uint32_t cascades;
|
||||||
bool elvsm;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ShadowMapManager {
|
class ShadowMapManager {
|
||||||
@@ -105,7 +101,7 @@ public:
|
|||||||
ShadowMap* getPointOrSpotShadowMap(size_t index) noexcept {
|
ShadowMap* getPointOrSpotShadowMap(size_t index) noexcept {
|
||||||
assert_invariant(index < CONFIG_MAX_SHADOWMAPS);
|
assert_invariant(index < CONFIG_MAX_SHADOWMAPS);
|
||||||
return std::launder(reinterpret_cast<ShadowMap*>(
|
return std::launder(reinterpret_cast<ShadowMap*>(
|
||||||
&mShadowMapCache[CONFIG_MAX_SHADOW_CASCADES + index]));
|
&mShadowMapCache[index]));
|
||||||
}
|
}
|
||||||
|
|
||||||
ShadowMap const* getPointOrSpotShadowMap(size_t spot) const noexcept {
|
ShadowMap const* getPointOrSpotShadowMap(size_t spot) const noexcept {
|
||||||
@@ -215,13 +211,13 @@ private:
|
|||||||
|
|
||||||
utils::FixedCapacityVector<ShadowMap*> mSpotShadowMaps{
|
utils::FixedCapacityVector<ShadowMap*> mSpotShadowMaps{
|
||||||
utils::FixedCapacityVector<ShadowMap*>::with_capacity(
|
utils::FixedCapacityVector<ShadowMap*>::with_capacity(
|
||||||
CONFIG_MAX_SHADOWMAPS) };
|
CONFIG_MAX_SHADOWMAPS - CONFIG_MAX_SHADOW_CASCADES) };
|
||||||
|
|
||||||
// inline storage for all our ShadowMap objects, we can't easily use a std::array<> directly.
|
// inline storage for all our ShadowMap objects, we can't easily use a std::array<> directly.
|
||||||
// because ShadowMap doesn't have a default ctor, and we avoid out-of-line allocations.
|
// because ShadowMap doesn't have a default ctor, and we avoid out-of-line allocations.
|
||||||
// Each ShadowMap is currently 40 bytes (total of 2.5KB for 64 shadow maps)
|
// Each ShadowMap is currently 40 bytes (total of 2.5KB for 64 shadow maps)
|
||||||
using ShadowMapStorage = std::aligned_storage<sizeof(ShadowMap), alignof(ShadowMap)>::type;
|
using ShadowMapStorage = std::aligned_storage<sizeof(ShadowMap), alignof(ShadowMap)>::type;
|
||||||
std::array<ShadowMapStorage, CONFIG_MAX_SHADOW_CASCADES + CONFIG_MAX_SHADOWMAPS> mShadowMapCache;
|
std::array<ShadowMapStorage, CONFIG_MAX_SHADOWMAPS> mShadowMapCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament
|
} // namespace filament
|
||||||
|
|||||||
@@ -343,9 +343,10 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren
|
|||||||
lp[gpuIndex].typeShadow = LightsUib::packTypeShadow(
|
lp[gpuIndex].typeShadow = LightsUib::packTypeShadow(
|
||||||
lcm.isPointLight(li) ? 0u : 1u,
|
lcm.isPointLight(li) ? 0u : 1u,
|
||||||
shadowInfo[i].contactShadows,
|
shadowInfo[i].contactShadows,
|
||||||
shadowInfo[i].index,
|
shadowInfo[i].index);
|
||||||
shadowInfo[i].layer);
|
lp[gpuIndex].channels = LightsUib::packChannels(
|
||||||
lp[gpuIndex].channels = LightsUib::packChannels(lcm.getLightChannels(li), shadowInfo[i].castsShadows);
|
lcm.getLightChannels(li),
|
||||||
|
shadowInfo[i].castsShadows);
|
||||||
}
|
}
|
||||||
|
|
||||||
driver.updateBufferObject(lightUbh, { lp, positionalLightCount * sizeof(LightsUib) }, 0);
|
driver.updateBufferObject(lightUbh, { lp, positionalLightCount * sizeof(LightsUib) }, 0);
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ public:
|
|||||||
bool castsShadows = false; // whether this light casts shadows
|
bool castsShadows = false; // whether this light casts shadows
|
||||||
bool contactShadows = false; // whether this light casts contact shadows
|
bool contactShadows = false; // whether this light casts contact shadows
|
||||||
uint8_t index = 0; // an index into the arrays in the Shadows uniform buffer
|
uint8_t index = 0; // an index into the arrays in the Shadows uniform buffer
|
||||||
uint8_t layer = 0; // which layer of the shadow texture array to sample from
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -179,7 +178,8 @@ public:
|
|||||||
LightSoa const& getLightData() const noexcept { return mLightData; }
|
LightSoa const& getLightData() const noexcept { return mLightData; }
|
||||||
LightSoa& getLightData() noexcept { return mLightData; }
|
LightSoa& getLightData() noexcept { return mLightData; }
|
||||||
|
|
||||||
void updateUBOs(utils::Range<uint32_t> visibleRenderables, backend::Handle<backend::HwBufferObject> renderableUbh) noexcept;
|
void updateUBOs(utils::Range<uint32_t> visibleRenderables,
|
||||||
|
backend::Handle<backend::HwBufferObject> renderableUbh) noexcept;
|
||||||
|
|
||||||
bool hasContactShadows() const noexcept;
|
bool hasContactShadows() const noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
namespace filament {
|
namespace filament {
|
||||||
|
|
||||||
// update this when a new version of filament wouldn't work with older materials
|
// update this when a new version of filament wouldn't work with older materials
|
||||||
static constexpr size_t MATERIAL_VERSION = 28;
|
static constexpr size_t MATERIAL_VERSION = 29;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supported shading models
|
* Supported shading models
|
||||||
|
|||||||
@@ -124,12 +124,10 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
|||||||
// bit 0-3: cascade count
|
// bit 0-3: cascade count
|
||||||
// bit 4: visualize cascades
|
// bit 4: visualize cascades
|
||||||
// bit 8-11: cascade has visible shadows
|
// bit 8-11: cascade has visible shadows
|
||||||
// bit 31: elvsm
|
|
||||||
uint32_t cascades;
|
uint32_t cascades;
|
||||||
float shadowBulbRadiusLs; // light radius in light-space
|
float reserved0;
|
||||||
float shadowBias; // normal bias
|
float reserved1; // normal bias
|
||||||
float shadowPenumbraRatioScale; // For DPCF or PCSS, scale penumbra ratio for artistic use
|
float shadowPenumbraRatioScale; // For DPCF or PCSS, scale penumbra ratio for artistic use
|
||||||
std::array<math::mat4f, CONFIG_MAX_SHADOW_CASCADES> lightFromWorldMatrix;
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------
|
||||||
// VSM shadows [variant: VSM]
|
// VSM shadows [variant: VSM]
|
||||||
@@ -164,7 +162,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
|||||||
float ssrStride; // ssr texel stride, >= 1.0
|
float ssrStride; // ssr texel stride, >= 1.0
|
||||||
|
|
||||||
// bring PerViewUib to 2 KiB
|
// bring PerViewUib to 2 KiB
|
||||||
math::float4 reserved[47];
|
math::float4 reserved[63];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2 KiB == 128 float4s
|
// 2 KiB == 128 float4s
|
||||||
@@ -229,11 +227,11 @@ struct LightsUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
|||||||
math::float2 spotScaleOffset; // { scale, offset }
|
math::float2 spotScaleOffset; // { scale, offset }
|
||||||
float reserved3; // 0
|
float reserved3; // 0
|
||||||
float intensity; // float
|
float intensity; // float
|
||||||
uint32_t typeShadow; // 0x00.ll.ii.ct (t: 0=point, 1=spot, c:contact, ii: index, ll: layer)
|
uint32_t typeShadow; // 0x00.00.ii.ct (t: 0=point, 1=spot, c:contact, ii: index)
|
||||||
uint32_t channels; // 0x000c00ll (ll: light channels, c: caster)
|
uint32_t channels; // 0x000c00ll (ll: light channels, c: caster)
|
||||||
|
|
||||||
static uint32_t packTypeShadow(uint8_t type, bool contactShadow, uint8_t index, uint8_t layer) noexcept {
|
static uint32_t packTypeShadow(uint8_t type, bool contactShadow, uint8_t index) noexcept {
|
||||||
return (type & 0xF) | (contactShadow ? 0x10 : 0x00) | (index << 8) | (layer << 16);
|
return (type & 0xF) | (contactShadow ? 0x10 : 0x00) | (index << 8);
|
||||||
}
|
}
|
||||||
static uint32_t packChannels(uint8_t lightChannels, bool castShadows) noexcept {
|
static uint32_t packChannels(uint8_t lightChannels, bool castShadows) noexcept {
|
||||||
return lightChannels | (castShadows ? 0x10000 : 0);
|
return lightChannels | (castShadows ? 0x10000 : 0);
|
||||||
@@ -258,6 +256,11 @@ struct ShadowUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
|||||||
float bulbRadiusLs; // 4
|
float bulbRadiusLs; // 4
|
||||||
float nearOverFarMinusNear; // 4
|
float nearOverFarMinusNear; // 4
|
||||||
bool elvsm; // 4
|
bool elvsm; // 4
|
||||||
|
|
||||||
|
uint32_t layer; // 4
|
||||||
|
uint32_t reserved0; // 4
|
||||||
|
uint32_t reserved1; // 4
|
||||||
|
uint32_t reserved2; // 4
|
||||||
};
|
};
|
||||||
ShadowData shadows[CONFIG_MAX_SHADOWMAPS];
|
ShadowData shadows[CONFIG_MAX_SHADOWMAPS];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -105,10 +105,9 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
|
|||||||
|
|
||||||
{ "cascadeSplits", 0, Type::FLOAT4, Precision::HIGH },
|
{ "cascadeSplits", 0, Type::FLOAT4, Precision::HIGH },
|
||||||
{ "cascades", 0, Type::UINT },
|
{ "cascades", 0, Type::UINT },
|
||||||
{ "shadowBulbRadiusLs", 0, Type::FLOAT },
|
{ "reserved0", 0, Type::FLOAT },
|
||||||
{ "shadowBias", 0, Type::FLOAT },
|
{ "reserved1", 0, Type::FLOAT },
|
||||||
{ "shadowPenumbraRatioScale", 0, Type::FLOAT },
|
{ "shadowPenumbraRatioScale", 0, Type::FLOAT },
|
||||||
{ "lightFromWorldMatrix", 4, Type::MAT4, Precision::HIGH },
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------
|
||||||
// VSM shadows [variant: VSM]
|
// VSM shadows [variant: VSM]
|
||||||
|
|||||||
@@ -287,6 +287,10 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel,
|
|||||||
UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
|
UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
|
||||||
cg.generateUniforms(vs, ShaderStage::VERTEX,
|
cg.generateUniforms(vs, ShaderStage::VERTEX,
|
||||||
UniformBindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib());
|
UniformBindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib());
|
||||||
|
if (litVariants && filament::Variant::isShadowReceiverVariant(variant)) {
|
||||||
|
cg.generateUniforms(vs, ShaderStage::FRAGMENT,
|
||||||
|
UniformBindingPoints::SHADOW, UibGenerator::getShadowUib());
|
||||||
|
}
|
||||||
if (variant.hasSkinningOrMorphing()) {
|
if (variant.hasSkinningOrMorphing()) {
|
||||||
cg.generateUniforms(vs, ShaderStage::VERTEX,
|
cg.generateUniforms(vs, ShaderStage::VERTEX,
|
||||||
UniformBindingPoints::PER_RENDERABLE_BONES,
|
UniformBindingPoints::PER_RENDERABLE_BONES,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ struct Light {
|
|||||||
bool contactShadows;
|
bool contactShadows;
|
||||||
uint type;
|
uint type;
|
||||||
uint shadowIndex;
|
uint shadowIndex;
|
||||||
uint shadowLayer;
|
|
||||||
uint channels;
|
uint channels;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ struct ShadowData {
|
|||||||
float bulbRadiusLs;
|
float bulbRadiusLs;
|
||||||
float nearOverFarMinusNear;
|
float nearOverFarMinusNear;
|
||||||
bool elvsm;
|
bool elvsm;
|
||||||
|
uint layer;
|
||||||
|
uint reserved0;
|
||||||
|
uint reserved1;
|
||||||
|
uint reserved2;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BoneData {
|
struct BoneData {
|
||||||
|
|||||||
@@ -134,8 +134,9 @@ highp vec4 getCascadeLightSpacePosition(uint cascade) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
|
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
|
||||||
frameUniforms.lightDirection, frameUniforms.shadowBias,
|
frameUniforms.lightDirection,
|
||||||
frameUniforms.lightFromWorldMatrix[cascade]);
|
shadowUniforms.shadows[cascade].normalBias,
|
||||||
|
shadowUniforms.shadows[cascade].lightFromWorldMatrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ int getInstanceIndex() {
|
|||||||
// Uniforms access
|
// Uniforms access
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
mat4 getLightFromWorldMatrix() {
|
|
||||||
return frameUniforms.lightFromWorldMatrix[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
PerRenderableData getObjectUniforms() {
|
PerRenderableData getObjectUniforms() {
|
||||||
#if defined(MATERIAL_HAS_INSTANCES)
|
#if defined(MATERIAL_HAS_INSTANCES)
|
||||||
// the material manages instancing, all instances share the same uniform block.
|
// the material manages instancing, all instances share the same uniform block.
|
||||||
|
|||||||
@@ -56,9 +56,8 @@ void evaluateDirectionalLight(const MaterialInputs material,
|
|||||||
bool cascadeHasVisibleShadows = bool(frameUniforms.cascades & ((1u << cascade) << 8u));
|
bool cascadeHasVisibleShadows = bool(frameUniforms.cascades & ((1u << cascade) << 8u));
|
||||||
bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1u);
|
bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1u);
|
||||||
if (hasDirectionalShadows && cascadeHasVisibleShadows) {
|
if (hasDirectionalShadows && cascadeHasVisibleShadows) {
|
||||||
uint layer = cascade;
|
|
||||||
highp vec4 shadowPosition = getShadowPosition(true, 0u, cascade, 0.0f);
|
highp vec4 shadowPosition = getShadowPosition(true, 0u, cascade, 0.0f);
|
||||||
visibility = shadow(true, light_shadowMap, layer, 0u, shadowPosition, 0.0f);
|
visibility = shadow(true, light_shadowMap, cascade, shadowPosition, 0.0f);
|
||||||
}
|
}
|
||||||
if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) {
|
if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) {
|
||||||
if ((getObjectUniforms().flagsChannels & FILAMENT_OBJECT_CONTACT_SHADOWS_BIT) != 0u) {
|
if ((getObjectUniforms().flagsChannels & FILAMENT_OBJECT_CONTACT_SHADOWS_BIT) != 0u) {
|
||||||
|
|||||||
@@ -158,7 +158,6 @@ Light getLight(const uint lightIndex) {
|
|||||||
light.type = (typeShadow & 0x1u);
|
light.type = (typeShadow & 0x1u);
|
||||||
#if defined(VARIANT_HAS_SHADOWING)
|
#if defined(VARIANT_HAS_SHADOWING)
|
||||||
light.shadowIndex = (typeShadow >> 8u) & 0xFFu;
|
light.shadowIndex = (typeShadow >> 8u) & 0xFFu;
|
||||||
light.shadowLayer = (typeShadow >> 16u) & 0xFFu;
|
|
||||||
light.castsShadows = bool(channels & 0x10000u);
|
light.castsShadows = bool(channels & 0x10000u);
|
||||||
if (light.type == LIGHT_TYPE_SPOT) {
|
if (light.type == LIGHT_TYPE_SPOT) {
|
||||||
light.zLight = dot(shadowUniforms.shadows[light.shadowIndex].lightFromWorldZ, vec4(worldPosition, 1.0));
|
light.zLight = dot(shadowUniforms.shadows[light.shadowIndex].lightFromWorldZ, vec4(worldPosition, 1.0));
|
||||||
@@ -211,19 +210,20 @@ void evaluatePunctualLights(const MaterialInputs material,
|
|||||||
#if defined(VARIANT_HAS_SHADOWING)
|
#if defined(VARIANT_HAS_SHADOWING)
|
||||||
if (light.NoL > 0.0) {
|
if (light.NoL > 0.0) {
|
||||||
if (light.castsShadows) {
|
if (light.castsShadows) {
|
||||||
uint layer = light.shadowLayer;
|
uint shadowIndex = light.shadowIndex;
|
||||||
highp vec4 shadowPosition;
|
highp vec4 shadowPosition;
|
||||||
if (light.type == LIGHT_TYPE_POINT) {
|
if (light.type == LIGHT_TYPE_POINT) {
|
||||||
// point-light shadows are sampled from a direction
|
// point-light shadows are sampled from a direction
|
||||||
highp vec3 r = getWorldPosition() - light.worldPosition;
|
highp vec3 r = getWorldPosition() - light.worldPosition;
|
||||||
highp vec4 nf = shadowUniforms.shadows[light.shadowIndex].lightFromWorldZ;
|
highp uint face = 0u;
|
||||||
// getShadowPosition returns zLight which is needed for PCSS/DPCF
|
// getShadowPosition returns zLight which is needed for PCSS/DPCF
|
||||||
shadowPosition = getShadowPosition(r, nf, layer, light.zLight);
|
shadowPosition = getShadowPosition(r, shadowIndex, light.zLight, face);
|
||||||
|
shadowIndex += face;
|
||||||
} else {
|
} else {
|
||||||
// getShadowPosition needs zLight for applying the normal bias
|
// getShadowPosition needs zLight for applying the normal bias
|
||||||
shadowPosition = getShadowPosition(false, light.shadowIndex, 0u, light.zLight);
|
shadowPosition = getShadowPosition(false, shadowIndex, 0u, light.zLight);
|
||||||
}
|
}
|
||||||
visibility = shadow(false, light_shadowMap, layer, light.shadowIndex,
|
visibility = shadow(false, light_shadowMap, shadowIndex,
|
||||||
shadowPosition, light.zLight);
|
shadowPosition, light.zLight);
|
||||||
}
|
}
|
||||||
if (light.contactShadows && visibility > 0.0) {
|
if (light.contactShadows && visibility > 0.0) {
|
||||||
|
|||||||
@@ -143,7 +143,9 @@ void main() {
|
|||||||
#if defined(VARIANT_HAS_SHADOWING) && defined(VARIANT_HAS_DIRECTIONAL_LIGHTING)
|
#if defined(VARIANT_HAS_SHADOWING) && defined(VARIANT_HAS_DIRECTIONAL_LIGHTING)
|
||||||
vertex_lightSpacePosition = computeLightSpacePosition(
|
vertex_lightSpacePosition = computeLightSpacePosition(
|
||||||
vertex_worldPosition.xyz, vertex_worldNormal,
|
vertex_worldPosition.xyz, vertex_worldNormal,
|
||||||
frameUniforms.lightDirection, frameUniforms.shadowBias, getLightFromWorldMatrix());
|
frameUniforms.lightDirection,
|
||||||
|
shadowUniforms.shadows[0].normalBias,
|
||||||
|
shadowUniforms.shadows[0].lightFromWorldMatrix);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // !defined(USE_OPTIMIZED_DEPTH_VERTEX_SHADER)
|
#endif // !defined(USE_OPTIMIZED_DEPTH_VERTEX_SHADER)
|
||||||
|
|||||||
@@ -50,9 +50,8 @@ vec4 evaluateMaterial(const MaterialInputs material) {
|
|||||||
bool cascadeHasVisibleShadows = bool(frameUniforms.cascades & ((1u << cascade) << 8u));
|
bool cascadeHasVisibleShadows = bool(frameUniforms.cascades & ((1u << cascade) << 8u));
|
||||||
bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1u);
|
bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1u);
|
||||||
if (hasDirectionalShadows && cascadeHasVisibleShadows) {
|
if (hasDirectionalShadows && cascadeHasVisibleShadows) {
|
||||||
uint layer = cascade;
|
|
||||||
highp vec4 shadowPosition = getShadowPosition(true, 0u, cascade, 0.0f);
|
highp vec4 shadowPosition = getShadowPosition(true, 0u, cascade, 0.0f);
|
||||||
visibility = shadow(true, light_shadowMap, layer, 0u, shadowPosition, 0.0f);
|
visibility = shadow(true, light_shadowMap, cascade, shadowPosition, 0.0f);
|
||||||
}
|
}
|
||||||
if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) {
|
if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) {
|
||||||
if ((getObjectUniforms().flagsChannels & FILAMENT_OBJECT_CONTACT_SHADOWS_BIT) != 0u) {
|
if ((getObjectUniforms().flagsChannels & FILAMENT_OBJECT_CONTACT_SHADOWS_BIT) != 0u) {
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ float getPenumbraLs(const bool DIRECTIONAL, const uint index, const highp float
|
|||||||
float penumbra;
|
float penumbra;
|
||||||
// This conditional is resolved at compile time
|
// This conditional is resolved at compile time
|
||||||
if (DIRECTIONAL) {
|
if (DIRECTIONAL) {
|
||||||
penumbra = frameUniforms.shadowBulbRadiusLs;
|
penumbra = shadowUniforms.shadows[index].bulbRadiusLs;
|
||||||
} else {
|
} else {
|
||||||
// the penumbra radius depends on the light-space z for spotlights
|
// the penumbra radius depends on the light-space z for spotlights
|
||||||
penumbra = shadowUniforms.shadows[index].bulbRadiusLs / zLight;
|
penumbra = shadowUniforms.shadows[index].bulbRadiusLs / zLight;
|
||||||
@@ -495,8 +495,8 @@ highp vec4 getShadowPosition(const bool DIRECTIONAL,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get {texture coordinate, layer} for point shadow maps
|
// get {texture coordinate, layer} for point shadow maps
|
||||||
highp vec4 getShadowPosition(const highp vec3 r, const highp vec4 nf,
|
highp vec4 getShadowPosition(const highp vec3 r, const highp uint shadowIndex,
|
||||||
inout uint layer, out highp float d) {
|
out highp float d, out highp uint face) {
|
||||||
highp vec4 tc;
|
highp vec4 tc;
|
||||||
highp float rx = abs(r.x);
|
highp float rx = abs(r.x);
|
||||||
highp float ry = abs(r.y);
|
highp float ry = abs(r.y);
|
||||||
@@ -506,20 +506,22 @@ highp vec4 getShadowPosition(const highp vec3 r, const highp vec4 nf,
|
|||||||
if (d == rx) {
|
if (d == rx) {
|
||||||
tc.x = r.x >= 0.0 ? r.z : -r.z;
|
tc.x = r.x >= 0.0 ? r.z : -r.z;
|
||||||
tc.y = r.y;
|
tc.y = r.y;
|
||||||
layer += (r.x >= 0.0 ? 0u : 1u);
|
face = (r.x >= 0.0 ? 0u : 1u);
|
||||||
} else if (d == ry) {
|
} else if (d == ry) {
|
||||||
tc.x = r.y >= 0.0 ? r.x : -r.x;
|
tc.x = r.y >= 0.0 ? r.x : -r.x;
|
||||||
tc.y = r.z;
|
tc.y = r.z;
|
||||||
layer += (r.y >= 0.0 ? 2u : 3u);
|
face = (r.y >= 0.0 ? 2u : 3u);
|
||||||
} else {
|
} else {
|
||||||
tc.x = r.z >= 0.0 ? -r.x : r.x;
|
tc.x = r.z >= 0.0 ? -r.x : r.x;
|
||||||
tc.y = r.y;
|
tc.y = r.y;
|
||||||
layer += (r.z >= 0.0 ? 4u : 5u);
|
face = (r.z >= 0.0 ? 4u : 5u);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ma is guaranteed to be >= sc and tc
|
// ma is guaranteed to be >= sc and tc
|
||||||
tc.xy = (tc.xy * ma + vec2(1.0)) * 0.5;
|
tc.xy = (tc.xy * ma + vec2(1.0)) * 0.5;
|
||||||
|
|
||||||
|
highp vec4 nf = shadowUniforms.shadows[shadowIndex + face].lightFromWorldZ;
|
||||||
|
|
||||||
// z coordinate of the normalized fragment position in light-space
|
// z coordinate of the normalized fragment position in light-space
|
||||||
// i.e.: remap [near, far] to [0,1] : d = (d - n) / (f - n)
|
// i.e.: remap [near, far] to [0,1] : d = (d - n) / (f - n)
|
||||||
d = nf[2] + nf[3] * d;
|
d = nf[2] + nf[3] * d;
|
||||||
@@ -542,7 +544,8 @@ highp vec4 getShadowPosition(const highp vec3 r, const highp vec4 nf,
|
|||||||
// PCF sampling
|
// PCF sampling
|
||||||
float shadow(const bool DIRECTIONAL,
|
float shadow(const bool DIRECTIONAL,
|
||||||
const mediump sampler2DArrayShadow shadowMap,
|
const mediump sampler2DArrayShadow shadowMap,
|
||||||
const uint layer, const uint index, highp vec4 shadowPosition, highp float zLight) {
|
const uint index, highp vec4 shadowPosition, highp float zLight) {
|
||||||
|
uint layer = shadowUniforms.shadows[index].layer;
|
||||||
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HARD
|
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HARD
|
||||||
return ShadowSample_PCF_Hard(shadowMap, layer, shadowPosition);
|
return ShadowSample_PCF_Hard(shadowMap, layer, shadowPosition);
|
||||||
#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW
|
#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW
|
||||||
@@ -553,16 +556,11 @@ float shadow(const bool DIRECTIONAL,
|
|||||||
// Shadow requiring a sampler2D sampler (VSM, DPCF and PCSS)
|
// Shadow requiring a sampler2D sampler (VSM, DPCF and PCSS)
|
||||||
float shadow(const bool DIRECTIONAL,
|
float shadow(const bool DIRECTIONAL,
|
||||||
const mediump sampler2DArray shadowMap,
|
const mediump sampler2DArray shadowMap,
|
||||||
const uint layer, const uint index, highp vec4 shadowPosition, highp float zLight) {
|
const uint index, highp vec4 shadowPosition, highp float zLight) {
|
||||||
|
uint layer = shadowUniforms.shadows[index].layer;
|
||||||
// This conditional is resolved at compile time
|
// This conditional is resolved at compile time
|
||||||
if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_EVSM) {
|
if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_EVSM) {
|
||||||
bool elvsm = false;
|
bool elvsm = shadowUniforms.shadows[index].elvsm;
|
||||||
if (DIRECTIONAL) {
|
|
||||||
elvsm = bool((frameUniforms.cascades >> 31u) & 1u);
|
|
||||||
} else {
|
|
||||||
elvsm = shadowUniforms.shadows[index].elvsm;
|
|
||||||
}
|
|
||||||
return ShadowSample_VSM(elvsm, shadowMap, layer, shadowPosition);
|
return ShadowSample_VSM(elvsm, shadowMap, layer, shadowPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user