Compare commits

..

14 Commits

Author SHA1 Message Date
Benjamin Doherty
41a809368b Merge branch 'rc/1.14.0' into release 2021-11-15 10:07:56 -08:00
Romain Guy
ea53eb9290 Skip task incompatible with configuration caching (#4831) 2021-11-09 15:56:00 -08:00
Benjamin Doherty
05875057c9 Update RELEASE_NOTES for 1.14.0 2021-11-09 15:51:13 -08:00
Benjamin Doherty
60734349de Bump version to 1.14.0 2021-11-08 11:52:50 -08:00
Benjamin Doherty
157c0264af Release Filament 1.13.0 2021-11-08 11:49:39 -08:00
Mathias Agopian
9bacfdb390 simplify shadowing code
- remove PCF "low" quality, we only use "HARD" now, when using PCF.
  Higher quality levels are achieved by using VSM.

- added a version of PCF that doesn't use a shadowSampler for future
  use.
2021-11-04 23:01:08 -07:00
Mathias Agopian
f9aaf5c42e Fix normal bias for spotlights.
The normal bias is now computed correctly, this requires to compute
the z in lightspace in the shader.
Note that this would not work as well if we used LISPSM, but we'll
cross that bridge when we get there.
2021-11-04 14:56:00 -07:00
Ben Doherty
21db695e79 Call VirtualMachineEnv::JNI_OnLoad for non-Android Java builds (better fix) (#4779) 2021-11-04 13:28:08 -07:00
Mathias Agopian
cf917f1093 Add a (crude) way to have structs in our UBOs
The struct must be declared in common_type.fs, so custom
structures are not supported.
2021-11-04 12:43:50 -07:00
Ben Doherty
a8d3a61c25 Enforce readPixels is called within frame (#4802) 2021-11-03 10:59:25 -07:00
Mathias Agopian
80f13a8149 fix typo in comments 2021-11-03 10:54:05 -07:00
Mathias Agopian
e172f3a67f Fix java shadow biases + minor cleanups
- constant bias and normal bias default values in java didn't match
  C++ or the documentation

- stable shadows were enabled by default in java

- polygon offset biases were missing from the java API

- document and don't apply polygon offset to VSM

- remove unused code
2021-11-03 00:20:48 -07:00
Mathias Agopian
c382c0a9cc Tighten spotlights near/far further
We now cull the shadow casters before computing the near/far plane
for spotlights -- we can do that because we know the light's frustum.
So only these casters that contribute to the shadow are accounted for
when calculating the near/far plane.

This PR also include more cleanup and simplifications.
2021-11-02 14:05:09 -07:00
Mathias Agopian
78b29fe967 Fix typo that broke the directional shadowmap 2021-11-01 16:41:30 -07:00
47 changed files with 487 additions and 324 deletions

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.13.0'
implementation 'com.google.android.filament:filament-android:1.14.0'
}
```
@@ -52,7 +52,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```
pod 'Filament', '~> 1.13.0'
pod 'Filament', '~> 1.14.0'
```
### Snapshots

View File

@@ -3,7 +3,15 @@
This file contains one line summaries of commits that are worthy of mentioning in release notes.
A new header is inserted each time a *tag* is created.
## v1.13.1 (currently main branch)
## v1.14.1 (currently main branch)
## v1.14.0
- engine: Internal materials can use structures as parameters [⚠️ **Material breakage**].
- engine: `readPixels` on a `SwapChain` must be called within `beginFrame` / `endFrame` [⚠️ **API
Change**].
- engine: Fix normal bias and improve spotlight quality.
- Java: Fix shadow biases.
## v1.13.0

View File

@@ -1,3 +1,5 @@
import java.nio.file.Paths
// This script accepts the following parameters:
//
// com.google.android.filament.dist-dir
@@ -42,23 +44,31 @@
//
buildscript {
def filamentPath = file("../out/android-release/filament").absolutePath
if (project.hasProperty("com.google.android.filament.dist-dir")) {
filamentPath = file(project.property("com.google.android.filament.dist-dir")).absolutePath
}
def path = providers
.gradleProperty("com.google.android.filament.dist-dir")
.forUseAtConfigurationTime().get()
def directory = objects.fileProperty().fileValue(new File(path)).getAsFile().get()
def filamentPath = directory.absolutePath
// Our CMake scripts require a forward-slash path for the FILAMENT_DIST_DIR
// variable, so here we convert the native path to a forward-slash path.
filamentPath = filamentPath.replace(File.separator, '/')
// Warning: changing this property does not work well with incremental builds.
def excludeVulkan = project.hasProperty("com.google.android.filament.exclude-vulkan")
def excludeVulkan = providers
.gradleProperty("com.google.android.filament.exclude-vulkan")
.forUseAtConfigurationTime()
.isPresent()
def abis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]
if (project.hasProperty("com.google.android.filament.abis")) {
def newAbis = project.property("com.google.android.filament.abis").split(',')
if (!newAbis.contains("all")) {
abis = newAbis
}
def newAbis = providers
.gradleProperty("com.google.android.filament.abis")
.forUseAtConfigurationTime()
.get()
.split(',')
if (!newAbis.contains("all")) {
abis = newAbis
}
ext.versions = [
@@ -72,7 +82,7 @@ buildscript {
ext.deps = [
'androidx': [
'annotations': "androidx.annotation:annotation:1.1.0",
'annotations': "androidx.annotation:annotation:1.3.0",
'core': "androidx.core:core:1.3.0",
],
'kotlin': "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}"
@@ -120,7 +130,7 @@ buildscript {
}
plugins {
id 'io.codearte.nexus-staging' version '0.22.0'
id 'io.codearte.nexus-staging' version '0.30.0'
}
// Nexus Staging configuration

View File

@@ -76,7 +76,9 @@ extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env, jclass,
jlong nativeBuilder, jint mapSize, jint cascades, jfloatArray splitPositions,
jfloat constantBias, jfloat normalBias, jfloat shadowFar, jfloat shadowNearHint,
jfloat shadowFarHint, jboolean stable, jboolean screenSpaceContactShadows, jint stepCount,
jfloat shadowFarHint, jboolean stable,
jfloat polygonOffsetConstant, jfloat polygonOffsetSlope,
jboolean screenSpaceContactShadows, jint stepCount,
jfloat maxShadowDistance, jint vsmMsaaSamples, jfloat blurWidth) {
LightManager::Builder *builder = (LightManager::Builder *) nativeBuilder;
LightManager::ShadowOptions shadowOptions {
@@ -88,6 +90,8 @@ Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env,
.shadowNearHint = shadowNearHint,
.shadowFarHint = shadowFarHint,
.stable = (bool)stable,
.polygonOffsetConstant = polygonOffsetConstant,
.polygonOffsetSlope = polygonOffsetConstant,
.screenSpaceContactShadows = (bool)screenSpaceContactShadows,
.stepCount = uint8_t(stepCount),
.maxShadowDistance = maxShadowDistance,

View File

@@ -244,13 +244,13 @@ public class LightManager {
* light. 1mm by default.
* This is ignored when the View's ShadowType is set to VSM.
*/
public float constantBias = 0.05f;
public float constantBias = 0.001f;
/** Amount by which the maximum sampling error is scaled. The resulting value is used
* to move the shadow away from the fragment normal. Should be 1.0.
* This is ignored when the View's ShadowType is set to VSM.
*/
public float normalBias = 0.4f;
public float normalBias = 1.0f;
/** Distance from the camera after which shadows are clipped. This is used to clip
* shadows that are too far and wouldn't contribute to the scene much, improving
@@ -279,7 +279,24 @@ public class LightManager {
* When set to true, all resolution enhancing features that can affect stability are
* disabling, resulting in significantly lower resolution shadows, albeit stable ones.
*/
public boolean stable = true;
public boolean stable = false;
/**
* Constant bias in depth-resolution units by which shadows are moved away from the
* light. The default value of 0.5 is used to round depth values up.
* Generally this value shouldn't be changed or at least be small and positive.
* This is ignored when the View's ShadowType is set to VSM.
*/
float polygonOffsetConstant = 0.5f;
/**
* Bias based on the change in depth in depth-resolution units by which shadows are moved
* away from the light. The default value of 2.0 works well with SHADOW_SAMPLING_PCF_LOW.
* Generally this value is between 0.5 and the size in texel of the PCF filter.
* Setting this value correctly is essential for LISPSM shadow-maps.
* This is ignored when the View's ShadowType is set to VSM.
*/
float polygonOffsetSlope = 2.0f;
/**
* Whether screen-space contact shadows are used. This applies regardless of whether a
@@ -471,7 +488,9 @@ public class LightManager {
nBuilderShadowOptions(mNativeBuilder,
options.mapSize, options.shadowCascades, options.cascadeSplitPositions,
options.constantBias, options.normalBias, options.shadowFar, options.shadowNearHint,
options.shadowFarHint, options.stable, options.screenSpaceContactShadows,
options.shadowFarHint, options.stable,
options.polygonOffsetConstant, options.polygonOffsetSlope,
options.screenSpaceContactShadows,
options.stepCount, options.maxShadowDistance, options.vsmMsaaSamples,
options.blurWidth);
return this;
@@ -1131,7 +1150,7 @@ public class LightManager {
private static native void nDestroyBuilder(long nativeBuilder);
private static native boolean nBuilderBuild(long nativeBuilder, long nativeEngine, int entity);
private static native void nBuilderCastShadows(long nativeBuilder, boolean enable);
private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize, int cascades, float[] splitPositions, float constantBias, float normalBias, float shadowFar, float shadowNearHint, float shadowFarhint, boolean stable, boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance, int vsmMsaaSamples, float blurWidth);
private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize, int cascades, float[] splitPositions, float constantBias, float normalBias, float shadowFar, float shadowNearHint, float shadowFarhint, boolean stable, float polygonOffsetConstant, float polygonOffsetSlope, boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance, int vsmMsaaSamples, float blurWidth);
private static native void nBuilderCastLight(long nativeBuilder, boolean enabled);
private static native void nBuilderPosition(long nativeBuilder, float x, float y, float z);
private static native void nBuilderDirection(long nativeBuilder, float x, float y, float z);

View File

@@ -437,8 +437,9 @@ public class Renderer {
*</pre>
*
*
* <p>Typically <code>readPixels</code> will be called after {@link #render} and before
* {@link #endFrame}.</p>
* <p><code>readPixels</code> must be called within a frame, meaning after {@link #beginFrame}
* and before {@link #endFrame}. Typically, <code>readPixels</code> will be called after
* {@link #render}.</p>
* <br>
* <p>After calling this method, the callback associated with <code>buffer</code>
* will be invoked on the main thread, indicating that the read-back has completed.

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.13.0
VERSION_NAME=1.14.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -19,6 +19,8 @@ org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
org.gradle.unsafe.configuration-cache=false
org.gradle.unsafe.configuration-cache=true
com.google.android.filament.tools-dir=../../../out/release/filament
com.google.android.filament.dist-dir=../out/android-release/filament
com.google.android.filament.abis=all

View File

@@ -32,6 +32,11 @@ android {
targetSdkVersion versions.targetSdk
missingDimensionStrategy 'functionality', 'full'
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
}
dependencies {

View File

@@ -23,6 +23,12 @@ android {
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -23,6 +23,12 @@ android {
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -29,6 +29,12 @@ android {
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -22,6 +22,12 @@ android {
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -10,6 +10,12 @@ android {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
}
dependencies {

View File

@@ -33,6 +33,12 @@ android {
missingDimensionStrategy 'functionality', 'full'
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -22,6 +22,12 @@ android {
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -25,6 +25,12 @@ android {
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {
@@ -34,5 +40,5 @@ android {
dependencies {
implementation project(':filament-android')
implementation 'androidx.annotation:annotation:1.1.0'
implementation deps.androidx.annotations
}

View File

@@ -23,6 +23,12 @@ android {
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -23,6 +23,12 @@ android {
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -30,6 +30,12 @@ android {
missingDimensionStrategy 'functionality', 'full'
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -23,6 +23,12 @@ android {
targetSdkVersion versions.targetSdk
}
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
// is not configuration-cache friendly yet; this is only useful for Play publication
dependenciesInfo {
includeInApk = false
}
// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {

View File

@@ -220,7 +220,8 @@ enum class UniformType : uint8_t {
UINT3,
UINT4,
MAT3, //!< a 3x3 float matrix
MAT4 //!< a 4x4 float matrix
MAT4, //!< a 4x4 float matrix
STRUCT
};
enum class Precision : uint8_t {

View File

@@ -273,6 +273,7 @@ public:
* Constant bias in depth-resolution units by which shadows are moved away from the
* light. The default value of 0.5 is used to round depth values up.
* Generally this value shouldn't be changed or at least be small and positive.
* This is ignored when the View's ShadowType is set to VSM.
*/
float polygonOffsetConstant = 0.5f;
@@ -281,6 +282,7 @@ public:
* away from the light. The default value of 2.0 works well with SHADOW_SAMPLING_PCF_LOW.
* Generally this value is between 0.5 and the size in texel of the PCF filter.
* Setting this value correctly is essential for LISPSM shadow-maps.
* This is ignored when the View's ShadowType is set to VSM.
*/
float polygonOffsetSlope = 2.0f;

View File

@@ -341,7 +341,7 @@ public:
*
* Framebuffer as seen on User buffer (PixelBufferDescriptor&)
* screen
*
*
* +--------------------+
* | | .stride .alignment
* | | ----------------------->-->
@@ -359,7 +359,8 @@ public:
* O------------+-------+
*
*
* Typically readPixels() will be called after render() and before endFrame().
* readPixels() must be called within a frame, meaning after beginFrame() and before endFrame().
* Typically, readPixels() will be called after render().
*
* After issuing this method, the callback associated with `buffer` will be invoked on the
* main thread, indicating that the read-back has completed. Typically, this will happen

View File

@@ -1171,6 +1171,11 @@ void FRenderer::endFrame() {
void FRenderer::readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
PixelBufferDescriptor&& buffer) {
#ifndef NDEBUG
const bool withinFrame = mSwapChain != nullptr;
ASSERT_PRECONDITION(withinFrame, "readPixels() on a SwapChain must be called after"
" beginFrame() and before endFrame().");
#endif
readPixels(mRenderTarget, xoffset, yoffset, width, height, std::move(buffer));
}

View File

@@ -22,6 +22,7 @@
#include "details/Engine.h"
#include "details/Scene.h"
#include "details/View.h"
#include <backend/DriverEnums.h>
@@ -74,7 +75,7 @@ void ShadowMap::render(FScene const& scene, utils::Range<uint32_t> range,
pass->setCamera(cameraInfo);
pass->setVisibilityMask(visibilityMask);
pass->setGeometry(scene.getRenderableData(), range, scene.getRenderableUBO());
pass->overridePolygonOffset(&mPolygonOffset);
pass->overridePolygonOffset(&mShadowMapInfo.polygonOffset);
pass->appendCommands(RenderPass::SHADOW);
pass->sortCommands();
}
@@ -84,85 +85,41 @@ mat4f ShadowMap::getDirectionalLightViewMatrix(float3 direction, float3 position
return FCamera::rigidTransformInverse(M);
}
void ShadowMap::update(const FScene::LightSoa& lightData, size_t index,
void ShadowMap::updateDirectional(const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
const ShadowMapInfo& shadowMapInfo, FScene const& scene,
SceneInfo& sceneInfo) noexcept {
// this is the hard part here, find a good frustum for our camera
auto& lcm = mEngine.getLightManager();
FLightManager::Instance li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
mShadowMapInfo = shadowMapInfo;
FLightManager::ShadowParams params = lcm.getShadowParams(li);
mPolygonOffset = {
// handle reversed Z
.slope = -params.options.polygonOffsetSlope,
.constant = -params.options.polygonOffsetConstant
};
// Note: we keep the polygon offset even with VSM as it seems to help.
auto& lcm = mEngine.getLightManager();
FLightManager::Instance li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
FLightManager::ShadowParams params = lcm.getShadowParams(li);
// Adjust the camera's projection for the light's shadowFar
mat4f cullingProjection(camera.cullingProjection);
if (params.options.shadowFar > 0.0f) {
float n = camera.zn;
float f = params.options.shadowFar;
if (std::abs(cullingProjection[2].w) > std::numeric_limits<float>::epsilon()) {
// perspective projection
cullingProjection[2].z = (f + n) / (n - f);
cullingProjection[3].z = (2 * f * n) / (n - f);
} else {
// orthographic projection
cullingProjection[2].z = 2.0f / (n - f);
cullingProjection[3].z = (f + n) / (n - f);
}
}
const ShadowCameraInfo cameraInfo = {
.projection = cullingProjection,
.model = camera.model,
.view = camera.view,
.worldOrigin = camera.worldOrigin,
.zn = camera.zn,
.zf = camera.zf
};
// debugging...
const float dz = cameraInfo.zf - cameraInfo.zn;
#ifndef NDEBUG
// LISPSM debugging for directional light (works because we only have one)
const float dz = camera.zf - camera.zn;
float& dzn = mEngine.debug.shadowmap.dzn;
float& dzf = mEngine.debug.shadowmap.dzf;
if (dzn < 0) dzn = std::max(0.0f, params.options.shadowNearHint - camera.zn) / dz;
else params.options.shadowNearHint = dzn * dz - camera.zn;
if (dzf > 0) dzf =-std::max(0.0f, camera.zf - params.options.shadowFarHint) / dz;
else params.options.shadowFarHint = dzf * dz + camera.zf;
#endif
using LightType = FLightManager::Type;
switch (lcm.getType(li)) {
case LightType::SUN:
case LightType::DIRECTIONAL:
computeShadowCameraDirectional(
lightData.elementAt<FScene::DIRECTION>(index),
cameraInfo, params, scene, sceneInfo);
break;
case LightType::FOCUSED_SPOT:
case LightType::SPOT:
computeShadowCameraSpot(
lightData.elementAt<FScene::POSITION_RADIUS>(index).xyz,
lightData.elementAt<FScene::DIRECTION>(index), lcm.getSpotLightOuterCone(li),
lightData.elementAt<FScene::POSITION_RADIUS>(index).w,
cameraInfo, params, scene, sceneInfo);
break;
case LightType::POINT:
break;
// Adjust the camera's projection for the light's shadowFar
mat4f cullingProjection(camera.cullingProjection);
if (params.options.shadowFar > 0.0f) {
float n = camera.zn;
float f = params.options.shadowFar;
// orthographic projection
assert_invariant(std::abs(cullingProjection[2].w) <= std::numeric_limits<float>::epsilon());
cullingProjection[2].z = 2.0f / (n - f);
cullingProjection[3].z = (f + n) / (n - f);
}
}
void ShadowMap::computeShadowCameraDirectional(
float3 const& dir, ShadowCameraInfo const& camera,
FLightManager::ShadowParams const& params,
FScene const& scene, SceneInfo& sceneInfo) noexcept {
auto direction = lightData.elementAt<FScene::DIRECTION>(index);
/*
* Compute the light's model matrix
@@ -171,7 +128,7 @@ void ShadowMap::computeShadowCameraDirectional(
// We compute the directional light's model matrix using the origin's as the light position.
// The choice of the light's origin initially doesn't matter for a directional light.
// This will be adjusted later because of how we compute the depth metric for VSM.
const mat4f MvAtOrigin = getDirectionalLightViewMatrix(dir);
const mat4f MvAtOrigin = getDirectionalLightViewMatrix(direction);
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfo(MvAtOrigin, scene, sceneInfo);
@@ -186,7 +143,7 @@ void ShadowMap::computeShadowCameraDirectional(
// view frustum vertices in world-space
float3 wsViewFrustumVertices[8];
computeFrustumCorners(wsViewFrustumVertices,
camera.model * FCamera::inverseProjection(camera.projection),
camera.model * FCamera::inverseProjection(cullingProjection),
sceneInfo.csNearFar);
// we use aligned_storage<> here to avoid the default initialization of std::array<>
@@ -233,7 +190,7 @@ void ShadowMap::computeShadowCameraDirectional(
// Now that we know the znear (-lsLightFrustumBounds.max.z), adjust the light's position such
// that znear = 0, this is only need for VSM, but doesn't hurt PCF.
const mat4f Mv = getDirectionalLightViewMatrix(dir, dir * -lsLightFrustumBounds.max.z);
const mat4f Mv = getDirectionalLightViewMatrix(direction, direction * -lsLightFrustumBounds.max.z);
// near / far planes are specified relative to the direction the eye is looking at
// i.e. the -z axis (see: ortho)
@@ -310,7 +267,7 @@ void ShadowMap::computeShadowCameraDirectional(
LMpMv = L * MpMv;
W = applyLISPSM(Wp, camera, params, LMpMv,
wsClippedShadowReceiverVolume, vertexCount, dir);
wsClippedShadowReceiverVolume, vertexCount, direction);
}
/*
@@ -363,7 +320,8 @@ void ShadowMap::computeShadowCameraDirectional(
if (params.options.stable) {
// Use the world origin as reference point, fixed w.r.t. the camera
snapLightFrustum(s, o, Mv, camera.worldOrigin[3].xyz, 1.0f / mShadowMapInfo.shadowDimension);
snapLightFrustum(s, o, Mv, camera.worldOrigin[3].xyz,
1.0f / mShadowMapInfo.shadowDimension);
}
const mat4f F(mat4f::row_major_init {
@@ -403,7 +361,7 @@ void ShadowMap::computeShadowCameraDirectional(
// for perspective and lispsm shadow maps. This also allows us to do this at zero-cost
// by baking it in the shadow-map itself.
const float constantBias = mShadowMapInfo.vsm ? 0.0f : params.options.constantBias;
const mat4f b = mat4f::translation(dir * constantBias);
const mat4f b = mat4f::translation(direction * constantBias);
// It's important to set the light camera's model matrix separately from its projection, so
// that the cameraPosition uniform gets set correctly.
@@ -421,10 +379,20 @@ void ShadowMap::computeShadowCameraDirectional(
}
}
void ShadowMap::computeShadowCameraSpot(float3 const& position, float3 const& dir,
float outerConeAngle, float radius, ShadowCameraInfo const& camera,
FLightManager::ShadowParams const& params, FScene const& scene,
SceneInfo& sceneInfo) noexcept {
void ShadowMap::updateSpot(const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
const ShadowMapInfo& shadowMapInfo,
FScene const& scene, SceneInfo& sceneInfo) noexcept {
mShadowMapInfo = shadowMapInfo;
auto& lcm = mEngine.getLightManager();
auto li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
auto position = lightData.elementAt<FScene::POSITION_RADIUS>(index).xyz;
auto direction = lightData.elementAt<FScene::DIRECTION>(index);
auto radius = lightData.elementAt<FScene::POSITION_RADIUS>(index).w;
auto outerConeAngle = lcm.getSpotLightOuterCone(li);
const FLightManager::ShadowParams& params = lcm.getShadowParams(li);
// TODO: correctly compute if this spot light has any visible shadows.
mHasVisibleShadows = true;
@@ -434,12 +402,11 @@ void ShadowMap::computeShadowCameraSpot(float3 const& position, float3 const& di
*/
// Choose a reasonable value for the near plane.
const mat4f Mv = getDirectionalLightViewMatrix(dir, position);
const mat4f Mv = getDirectionalLightViewMatrix(direction, position);
// find decent near/far
// TODO: we can do much better by rejecting objects that don't intersect our frustum
ShadowMap::updateSceneInfo(Mv, scene, sceneInfo);
// FIXME: we need a configuration minimum near plane (for now hardcoded to 1cm)
ShadowMap::updateSceneInfo(Mv, scene, sceneInfo, mShadowMapInfo.spotIndex);
// FIXME: we need a configuration for minimum near plane (for now hardcoded to 1cm)
float nearPlane = std::max(0.01f, -sceneInfo.lsNearFar.x);
float farPlane = std::min(radius, -sceneInfo.lsNearFar.y);
@@ -454,9 +421,17 @@ void ShadowMap::computeShadowCameraSpot(float3 const& position, float3 const& di
const mat4f St = mat4f(MbMt * S);
// TODO: focus projection
// 1) focus on the casters
// 2) additionally focus that on intersection of view & receivers
// Alternatively,
// Project receivers, casters and view onto near plane,
// compute intersection of that which gives the l,r,t,b planes
// FIXME: texelSizeWorldSpace doesn't work for spotlights
mTexelSizeWs = 0; //texelSizeWorldSpace(Mp, mat4f(MbMt));
// For spotlights, we store the texel size at 1 world unit
// The size of a texel in world unit is given by: (near/dimension) / lightspace.z,
// when computing the required bias we need a half-texel size, so we multiply by 0.5 here.
// Note: this would not work with LISPSM, which warps the texture space.
mTexelSizeWs = 0.5f * nearPlane / float(mShadowMapInfo.shadowDimension);
if (!mShadowMapInfo.vsm) {
mLightSpace = St;
@@ -465,7 +440,7 @@ void ShadowMap::computeShadowCameraSpot(float3 const& position, float3 const& di
}
const float constantBias = mShadowMapInfo.vsm ? 0.0f : params.options.constantBias;
const mat4f b = mat4f::translation(dir * constantBias);
const mat4f b = mat4f::translation(direction * constantBias);
const mat4f Sb = S * b;
// It's important to set the light camera's model matrix separately from its projection, so that
@@ -484,7 +459,7 @@ void ShadowMap::computeShadowCameraSpot(float3 const& position, float3 const& di
}
mat4f ShadowMap::applyLISPSM(mat4f& Wp,
ShadowCameraInfo const& camera, FLightManager::ShadowParams const& params,
filament::CameraInfo const& camera, FLightManager::ShadowParams const& params,
mat4f const& LMpMv,
FrustumBoxIntersection const& wsShadowReceiversVolume, size_t vertexCount,
float3 const& dir) {
@@ -993,7 +968,7 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF) const
const float ures = 1.0f / mShadowMapInfo.shadowDimension;
const float vres = 1.0f / mShadowMapInfo.shadowDimension;
const float dres = mShadowMapInfo.zResolution;
const float dres = 1.0f / 65536.0f;
constexpr bool JACOBIAN_ESTIMATE = false;
if constexpr (JACOBIAN_ESTIMATE) {
@@ -1052,16 +1027,17 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
float3 const* const UTILS_RESTRICT worldAABBExtent = soa.data<FScene::WORLD_AABB_EXTENT>();
uint8_t const* const UTILS_RESTRICT layers = soa.data<FScene::LAYERS>();
State const* const UTILS_RESTRICT visibility = soa.data<FScene::VISIBILITY_STATE>();
auto const* const UTILS_RESTRICT visibleMasks = soa.data<FScene::VISIBLE_MASK>();
size_t 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);
casters(aabb, visibleMasks[i]);
}
if (visibility[i].receiveShadows) {
receivers(aabb);
receivers(aabb, visibleMasks[i]);
}
}
}
@@ -1079,13 +1055,13 @@ void ShadowMap::initSceneInfo(FScene const& scene, filament::CameraInfo const& c
sceneInfo.wsShadowCastersVolume = {};
sceneInfo.wsShadowReceiversVolume = {};
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster) {
[&](Aabb caster, Culler::result_type) {
sceneInfo.wsShadowCastersVolume.min =
min(sceneInfo.wsShadowCastersVolume.min, caster.min);
sceneInfo.wsShadowCastersVolume.max =
max(sceneInfo.wsShadowCastersVolume.max, caster.max);
},
[&](Aabb receiver) {
[&](Aabb receiver, Culler::result_type) {
sceneInfo.wsShadowReceiversVolume.min =
min(sceneInfo.wsShadowReceiversVolume.min, receiver.min);
sceneInfo.wsShadowReceiversVolume.max =
@@ -1101,12 +1077,29 @@ void ShadowMap::updateSceneInfo(const mat4f& Mv, FScene const& scene,
ShadowMap::SceneInfo& sceneInfo) {
sceneInfo.lsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster) {
[&](Aabb caster, Culler::result_type) {
float2 nf = ShadowMap::computeNearFar(Mv, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, nf.x); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, nf.y); // far
},
[&](Aabb receiver) {
[&](Aabb receiver, Culler::result_type) {
}
);
}
void ShadowMap::updateSceneInfo(const mat4f& Mv, FScene const& scene,
ShadowMap::SceneInfo& sceneInfo, uint16_t index) {
sceneInfo.lsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
sceneInfo.vsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster, Culler::result_type mask) {
if (mask & VISIBLE_SPOT_SHADOW_RENDERABLE_N(index)) {
float2 nf = ShadowMap::computeNearFar(Mv, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, nf.x); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, nf.y); // far
}
},
[&](Aabb receiver, Culler::result_type) {
}
);
}

View File

@@ -44,10 +44,6 @@ public:
~ShadowMap();
struct ShadowMapInfo {
// the smallest increment in depth precision
// e.g., for 16 bit depth textures, is this 1 / (2^16)
float zResolution = 0.0f;
// the dimension of the encompassing texture atlas
uint16_t atlasDimension = 0;
@@ -59,8 +55,14 @@ public:
// e.g., for a texture dimension of 512, shadowDimension would be 510
uint16_t shadowDimension = 0;
// This spot shadowmap index.
uint16_t spotIndex = 0;
// whether we're using vsm
bool vsm = false;
// polygon offset
backend::PolygonOffset polygonOffset{};
};
struct SceneInfo {
@@ -73,10 +75,10 @@ public:
// light's near/far expressed in light-space, calculated from the scene's content
// assuming the light is at the origin.
math::float2 lsNearFar;
math::float2 lsNearFar{};
// Viewing camera's near/far expressed in view-space, calculated from the scene's content
math::float2 vsNearFar;
math::float2 vsNearFar{};
// World-space shadow-casters volume
Aabb wsShadowCastersVolume;
@@ -92,7 +94,13 @@ public:
// Call once per frame if the light, scene (or visible layers) or camera changes.
// This computes the light's camera.
void update(const FScene::LightSoa& lightData, size_t index, filament::CameraInfo const& camera,
void updateDirectional(const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
const ShadowMapInfo& shadowMapInfo, FScene const& scene,
SceneInfo& sceneInfo) noexcept;
void updateSpot(const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
const ShadowMapInfo& shadowMapInfo, FScene const& scene,
SceneInfo& sceneInfo) noexcept;
@@ -116,7 +124,7 @@ public:
// use only for debugging
FCamera const& getDebugCamera() const noexcept { return *mDebugCamera; }
backend::PolygonOffset getPolygonOffset() const noexcept { return mPolygonOffset; }
backend::PolygonOffset getPolygonOffset() const noexcept { return mShadowMapInfo.polygonOffset; }
// Call once per frame to populate the SceneInfo struct, then pass to update().
// This computes values constant across all shadow maps.
@@ -127,20 +135,10 @@ public:
static void updateSceneInfo(const math::mat4f& Mv, FScene const& scene,
ShadowMap::SceneInfo& sceneInfo);
private:
struct ShadowCameraInfo {
math::mat4f projection;
math::mat4f model;
math::mat4f view;
math::mat4f worldOrigin;
float zn = 0;
float zf = 0;
math::float3 const& getPosition() const noexcept { return model[3].xyz; }
math::float3 getForwardVector() const noexcept {
return -normalize(model[2].xyz); // the camera looks towards -z
}
};
static void updateSceneInfo(const math::mat4f& Mv, FScene const& scene,
ShadowMap::SceneInfo& sceneInfo, uint16_t index);
private:
struct Segment {
uint8_t v0, v1;
};
@@ -152,17 +150,8 @@ private:
// 8 corners, 12 segments w/ 2 intersection max -- all of this twice (8 + 12 * 2) * 2 (768 bytes)
using FrustumBoxIntersection = std::array<math::float3, 64>;
void computeShadowCameraDirectional(math::float3 const& dir, ShadowCameraInfo const& camera,
FLightManager::ShadowParams const& params, FScene const& scene,
SceneInfo& sceneInfo) noexcept;
void computeShadowCameraSpot(math::float3 const& position, math::float3 const& dir,
float outerConeAngle, float radius, ShadowCameraInfo const& camera,
FLightManager::ShadowParams const& params, FScene const& scene,
SceneInfo& sceneInfo) noexcept;
static math::mat4f applyLISPSM(math::mat4f& Wp,
ShadowCameraInfo const& camera, FLightManager::ShadowParams const& params,
filament::CameraInfo const& camera, FLightManager::ShadowParams const& params,
const math::mat4f& LMpMv,
FrustumBoxIntersection const& wsShadowReceiverVolume, size_t vertexCount,
const math::float3& dir);
@@ -247,9 +236,8 @@ private:
float mTexelSizeWs = 0.0f; // 4
// set-up in update()
ShadowMapInfo mShadowMapInfo; // 12
ShadowMapInfo mShadowMapInfo; // 20
bool mHasVisibleShadows = false; // 1
backend::PolygonOffset mPolygonOffset{}; // 8
FEngine& mEngine; // 8
const bool mClipSpaceFlipped; // 1

View File

@@ -57,9 +57,10 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(
FEngine& engine, FView& view,
TypedUniformBuffer<ShadowUib>& shadowUb, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData) noexcept {
calculateTextureRequirements(engine, view, lightData);
ShadowTechnique shadowTechnique = {};
calculateTextureRequirements(engine, view, lightData);
ShadowMap::SceneInfo sceneInfo(view.getVisibleLayers());
// Compute scene-dependent values shared across all shadow maps
@@ -107,7 +108,7 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp
const TextureFormat vsmTextureFormat = TextureFormat::RG16F;
// make a copy here, because it's a very small structure
const TextureRequirements textureRequirements = mTextureRequirements;
const TextureAtlasRequirements textureRequirements = mTextureAtlasRequirements;
assert_invariant(textureRequirements.layers <= MAX_SHADOW_LAYERS);
struct ShadowPass {
@@ -338,36 +339,39 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
ShadowMap::SceneInfo& sceneInfo) noexcept {
FScene* scene = view.getScene();
const CameraInfo& viewingCameraInfo = view.getCameraInfo();
const uint16_t textureSize = mTextureRequirements.size;
auto& lcm = engine.getLightManager();
FLightManager::Instance directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
LightManager::ShadowOptions const& options = lcm.getShadowOptions(directionalLight);
FLightManager::ShadowOptions const& options = lcm.getShadowOptions(directionalLight);
FLightManager::ShadowParams const& params = lcm.getShadowParams(directionalLight);
const ShadowMap::ShadowMapInfo shadowMapInfo{
.atlasDimension = mTextureAtlasRequirements.size,
.textureDimension = uint16_t(options.mapSize),
.shadowDimension = uint16_t(options.mapSize - 2u),
.vsm = view.hasVsm(),
.polygonOffset = { // handle reversed Z
.slope = view.hasVsm() ? 0.0f : -params.options.polygonOffsetSlope,
.constant = view.hasVsm() ? 0.0f : -params.options.polygonOffsetConstant
}
};
if (!mCascadeShadowMaps.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.
ShadowMapEntry& entry = mCascadeShadowMaps[0];
ShadowMap& map = entry.getShadowMap();
const size_t textureDimension = entry.getShadowOptions()->mapSize;
const ShadowMap::ShadowMapInfo shadowMapInfo {
.zResolution = mTextureZResolution,
.atlasDimension = textureSize,
.textureDimension = (uint16_t)textureDimension,
.shadowDimension = (uint16_t)(textureDimension - 2),
.vsm = view.hasVsm()
};
ShadowMap& shadowMap = entry.getShadowMap();
map.update(lightData, 0, viewingCameraInfo, shadowMapInfo, *scene, sceneInfo);
shadowMap.updateDirectional(lightData, 0, viewingCameraInfo, shadowMapInfo, *scene, sceneInfo);
Frustum const& frustum = map.getCamera().getCullingFrustum();
Frustum const& frustum = shadowMap.getCamera().getCullingFrustum();
FView::cullRenderables(engine.getJobSystem(), renderableData, frustum,
VISIBLE_DIR_SHADOW_RENDERABLE_BIT);
// note: normalBias is set to zero for VSM
const float normalBias = shadowMapInfo.vsm ? 0.0f : lcm.getShadowNormalBias(0);
// Set shadowBias, using the first directional cascade.
const float texelSizeWorldSpace = map.getTexelSizeWorldSpace();
const float texelSizeWorldSpace = shadowMap.getTexelSizeWorldSpace();
mShadowMappingUniforms.shadowBias = float3{ 0, normalBias * texelSizeWorldSpace, 0 };
}
@@ -425,19 +429,11 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
ShadowMap& shadowMap = entry.getShadowMap();
assert_invariant(entry.getLightIndex() == 0);
const size_t textureDimension = entry.getShadowOptions()->mapSize;
const ShadowMap::ShadowMapInfo shadowMapInfo{
.zResolution = mTextureZResolution,
.atlasDimension = textureSize,
.textureDimension = (uint16_t)textureDimension,
.shadowDimension = (uint16_t)(textureDimension - 2),
.vsm = view.hasVsm()
};
sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] };
shadowMap.update(lightData, 0,
shadowMap.updateDirectional(lightData, 0,
viewingCameraInfo, shadowMapInfo,
*scene,sceneInfo);
*scene, sceneInfo);
if (shadowMap.hasVisibleShadows()) {
mShadowMappingUniforms.lightFromWorldMatrix[i] = shadowMap.getLightSpaceMatrix();
@@ -476,64 +472,81 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine& engine,
FView& view, FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
ShadowMap::SceneInfo& sceneInfo, TypedUniformBuffer <ShadowUib>& shadowUb) noexcept {
ShadowMap::SceneInfo& sceneInfo, TypedUniformBuffer<ShadowUib>& shadowUb) noexcept {
ShadowTechnique shadowTechnique{};
auto& lcm = engine.getLightManager();
const CameraInfo& viewingCameraInfo = view.getCameraInfo();
const uint16_t textureSize = mTextureRequirements.size;
// shadow-map shadows for point/spotlights
auto& lcm = engine.getLightManager();
ShadowTechnique shadowTechnique{};
FScene::ShadowInfo* const shadowInfo = lightData.data<FScene::SHADOW_INFO>();
for (size_t i = 0, c = mSpotShadowMaps.size(); i < c; i++) {
auto& entry = mSpotShadowMaps[i];
// compute the frustum for this light
ShadowMap& shadowMap = entry.getShadowMap();
size_t l = entry.getLightIndex();
const size_t lightIndex = entry.getLightIndex();
const FLightManager::Instance li = lightData.elementAt<FScene::LIGHT_INSTANCE>(lightIndex);
FLightManager::ShadowParams params = lcm.getShadowParams(li);
const size_t textureDimension = entry.getShadowOptions()->mapSize;
FLightManager::ShadowOptions const* const options = entry.getShadowOptions();
const ShadowMap::ShadowMapInfo shadowMapInfo{
.zResolution = mTextureZResolution,
.atlasDimension = textureSize,
.textureDimension = (uint16_t)textureDimension,
.shadowDimension = (uint16_t)(textureDimension - 2),
.vsm = view.hasVsm()
.atlasDimension = mTextureAtlasRequirements.size,
.textureDimension = uint16_t(options->mapSize),
.shadowDimension = uint16_t(options->mapSize - 2u),
.spotIndex = uint16_t(i),
.vsm = view.hasVsm(),
.polygonOffset = { // handle reversed Z
.slope = view.hasVsm() ? 0.0f : -params.options.polygonOffsetSlope,
.constant = view.hasVsm() ? 0.0f : -params.options.polygonOffsetConstant
}
};
shadowMap.update(lightData, l,
// for spotlights, we cull shadow casters first because we already know the frustum,
// this will help us find better near/far plane later
const auto position = lightData.elementAt<FScene::POSITION_RADIUS>(lightIndex).xyz;
const auto direction = lightData.elementAt<FScene::DIRECTION>(lightIndex);
const auto radius = lightData.elementAt<FScene::POSITION_RADIUS>(lightIndex).w;
const auto outerConeAngle = lcm.getSpotLightOuterCone(li);
const mat4f Mv = ShadowMap::getDirectionalLightViewMatrix(direction, position);
const mat4f Mp = mat4f::perspective(outerConeAngle * f::RAD_TO_DEG * 2.0f,
1.0f, 0.01f, radius);
const mat4f MpMv(math::highPrecisionMultiply(Mp, Mv));
const Frustum frustum(MpMv);
// Cull shadow casters
FView::cullRenderables(engine.getJobSystem(), renderableData, frustum,
VISIBLE_SPOT_SHADOW_RENDERABLE_N_BIT(i));
shadowMap.updateSpot(lightData, lightIndex,
viewingCameraInfo, shadowMapInfo,
*view.getScene(), sceneInfo);
FLightManager::Instance light = lightData.elementAt<FScene::LIGHT_INSTANCE>(l);
if (shadowMap.hasVisibleShadows()) {
// Cull shadow casters
Frustum const& frustum = shadowMap.getCamera().getCullingFrustum();
FView::cullRenderables(engine.getJobSystem(), renderableData, frustum,
VISIBLE_SPOT_SHADOW_RENDERABLE_N_BIT(i));
auto& s = shadowUb.edit();
s.spotLightFromWorldMatrix[i] = shadowMap.getLightSpaceMatrix();
shadowInfo[l].castsShadows = true;
shadowInfo[l].index = i;
shadowInfo[l].layer = mSpotShadowMaps[i].getLayer();
shadowInfo[lightIndex].castsShadows = true;
shadowInfo[lightIndex].index = i;
shadowInfo[lightIndex].layer = entry.getLayer();
// note: normalBias is set to zero for VSM
const float3 dir = lightData.elementAt<FScene::DIRECTION>(l);
const float texelSizeWorldSpace = shadowMap.getTexelSizeWorldSpace();
const float normalBias = shadowMapInfo.vsm ? 0.0f : lcm.getShadowNormalBias(light);
s.directionShadowBias[i] = float4{ dir, normalBias * texelSizeWorldSpace };
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
auto& s = shadowUb.edit();
s.shadows[i].direction = direction;
s.shadows[i].normalBias = normalBias * texelSizeWorldSpace;
s.shadows[i].lightFromWorldMatrix = shadowMap.getLightSpaceMatrix();
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
}
}
// screen-space contact shadows for point/spotlights
auto *pInstance = lightData.data<FScene::LIGHT_INSTANCE>();
auto *pLightInstances = lightData.data<FScene::LIGHT_INSTANCE>();
for (size_t i = 0, c = lightData.size(); i < c; i++) {
// screen-space contact shadows
LightManager::ShadowOptions const& shadowOptions = lcm.getShadowOptions(pInstance[i]);
LightManager::ShadowOptions const& shadowOptions = lcm.getShadowOptions(pLightInstances[i]);
if (shadowOptions.screenSpaceContactShadows) {
shadowTechnique |= ShadowTechnique::SCREEN_SPACE;
shadowInfo[i].contactShadows = true;
@@ -579,7 +592,7 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
mipLevels = std::max(1, FTexture::maxLevelCount(maxDimension) - lowMipmapLevel);
}
mTextureRequirements = {
mTextureAtlasRequirements = {
(uint16_t)maxDimension,
layersNeeded,
mipLevels

View File

@@ -114,11 +114,13 @@ public:
private:
struct TextureRequirements {
// Atlas requirements, updated in ShadowMapManager::update(),
// consumed in ShadowMapManager::render()
struct TextureAtlasRequirements {
uint16_t size = 0;
uint8_t layers = 0;
uint8_t levels = 0;
} mTextureRequirements;
} mTextureAtlasRequirements;
ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
FView& view, FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
@@ -200,7 +202,6 @@ private:
// TODO: make it an option.
// TODO: iOS does not support the DEPTH16 texture format.
backend::TextureFormat mTextureFormat = backend::TextureFormat::DEPTH16;
float mTextureZResolution = 1.0f / (1u << 16u);
ShadowMappingUniforms mShadowMappingUniforms;

View File

@@ -316,21 +316,21 @@ void FView::prepareShadowing(FEngine& engine, DriverApi& driver,
// when we get here all the lights should be visible
assert_invariant(lightData.elementAt<FScene::VISIBILITY>(l));
FLightManager::Instance light = lightData.elementAt<FScene::LIGHT_INSTANCE>(l);
FLightManager::Instance li = lightData.elementAt<FScene::LIGHT_INSTANCE>(l);
if (UTILS_LIKELY(!light)) {
if (UTILS_LIKELY(!li)) {
continue; // invalid instance
}
if (UTILS_LIKELY(!lcm.isShadowCaster(light))) {
if (UTILS_LIKELY(!lcm.isShadowCaster(li))) {
continue; // doesn't cast shadows
}
if (UTILS_LIKELY(!lcm.isSpotLight(light))) {
continue; // is not a spot-light (we're not supporting point-lights yet)
if (UTILS_LIKELY(!lcm.isSpotLight(li))) {
continue; // is not a spot-li (we're not supporting point-lights yet)
}
const auto& shadowOptions = lcm.getShadowOptions(light);
const auto& shadowOptions = lcm.getShadowOptions(li);
mShadowMapManager.addSpotShadowMap(l, &shadowOptions);
++shadowCastingSpotCount;
if (shadowCastingSpotCount > CONFIG_MAX_SHADOW_CASTING_SPOTS - 1) {

View File

@@ -106,7 +106,7 @@ public:
VISIBILITY_STATE, // 1 | visibility data of the component
SKINNING_BUFFER, // 8 | bones uniform buffer handle, count, offset
WORLD_AABB_CENTER, // 12 | world-space bounding box center of the renderable
VISIBLE_MASK, // 1 | each bit represents a visibility in a pass
VISIBLE_MASK, // 2 | each bit represents a visibility in a pass
MORPH_WEIGHTS, // 4 | floats for morphing
CHANNELS, // 1 | currently light channels only

View File

@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.13.0"
spec.version = "1.14.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.13.0/filament-v1.13.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.14.0/filament-v1.14.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {

View File

@@ -27,7 +27,7 @@
namespace filament {
// update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 13;
static constexpr size_t MATERIAL_VERSION = 14;
/**
* Supported shading models

View File

@@ -185,9 +185,14 @@ static_assert(sizeof(LightsUib) == 64, "the actual UBO is an array of 256 mat4")
// UBO for punctual (spot light) shadows.
struct ShadowUib {
static constexpr utils::StaticString _name{ "ShadowUniforms" };
math::mat4f spotLightFromWorldMatrix[CONFIG_MAX_SHADOW_CASTING_SPOTS];
math::float4 directionShadowBias[CONFIG_MAX_SHADOW_CASTING_SPOTS]; // light direction, normal bias
struct alignas(16) ShadowData {
math::mat4f lightFromWorldMatrix;
math::float3 direction;
float normalBias;
};
ShadowData shadows[CONFIG_MAX_SHADOW_CASTING_SPOTS];
};
static_assert(sizeof(ShadowUib) <= 16384, "ShadowUib exceed max UBO size");
// UBO froxel record buffer.
struct FroxelRecordUib {
@@ -203,9 +208,8 @@ struct PerRenderableUibBone {
math::float4 s = { 1, 1, 1, 0 };
math::float4 ns = { 1, 1, 1, 0 };
};
static_assert(CONFIG_MAX_BONE_COUNT * sizeof(PerRenderableUibBone) <= 16384,
"Bones exceed max UBO size");
"PerRenderableUibBone exceed max UBO size");
} // namespace filament

View File

@@ -63,23 +63,38 @@ public:
Builder& add(utils::StaticString const& uniformName, size_t size,
Type type, Precision precision = Precision::DEFAULT);
// Add a known struct
Builder& add(utils::CString const& uniformName, size_t size,
utils::CString const& structName, size_t stride);
template<size_t N>
Builder& add(utils::StringLiteral<N> const& uniformName, size_t size,
Type type, Precision precision = Precision::DEFAULT) {
return add(utils::StaticString{ uniformName }, size, type, precision);
}
template<size_t N0, size_t N1>
Builder& add(utils::StringLiteral<N0> const& uniformName, size_t size,
utils::StringLiteral<N1> const& structName, size_t stride) {
return add(utils::StaticString{ uniformName }, size,
utils::StaticString{ structName }, stride);
}
// build and return the UniformInterfaceBlock
UniformInterfaceBlock build();
private:
friend class UniformInterfaceBlock;
struct Entry {
Entry(utils::CString name, uint32_t size, Type type, Precision precision) noexcept
: name(std::move(name)), size(size), type(type), precision(precision) { }
: name(std::move(name)), size(size), type(type), precision(precision), stride(strideForType(type, 0)) { }
Entry(utils::CString name, uint32_t size, utils::CString structName, size_t stride) noexcept
: name(std::move(name)), size(size), type(Type::STRUCT), structName(std::move(structName)), stride(stride) { }
utils::CString name;
uint32_t size;
Type type;
Precision precision;
Precision precision{};
utils::CString structName{};
uint32_t stride;
};
utils::CString mName;
std::vector<Entry> mEntries;
@@ -92,6 +107,7 @@ public:
Type type; // type of this uniform
uint32_t size; // size of the array in elements, or 1 if not an array
Precision precision;// precision of this uniform
utils::CString structName;// name of this uniform structure if type is STRUCT
// returns offset in bytes of this uniform (at index if an array)
inline size_t getBufferOffset(size_t index = 0) const {
assert(index < size);
@@ -125,7 +141,7 @@ private:
explicit UniformInterfaceBlock(Builder const& builder) noexcept;
static uint8_t baseAlignmentForType(Type type) noexcept;
static uint8_t strideForType(Type type) noexcept;
static uint8_t strideForType(Type type, uint32_t stride) noexcept;
utils::CString mName;
std::vector<UniformInfo> mUniformsInfoList;

View File

@@ -18,8 +18,6 @@
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <private/filament/UniformInterfaceBlock.h>
using namespace utils;
@@ -64,6 +62,14 @@ UniformInterfaceBlock::Builder& UniformInterfaceBlock::Builder::add(
return *this;
}
UniformInterfaceBlock::Builder& UniformInterfaceBlock::Builder::add(
utils::CString const& uniformName, size_t size,
utils::CString const& structName, size_t stride) {
mEntries.emplace_back(uniformName, (uint32_t)size, structName, stride);
return *this;
}
UniformInterfaceBlock UniformInterfaceBlock::Builder::build() {
return UniformInterfaceBlock(*this);
}
@@ -89,7 +95,7 @@ UniformInterfaceBlock::UniformInterfaceBlock(Builder const& builder) noexcept
uint16_t offset = 0;
for (auto const& e : builder.mEntries) {
size_t alignment = baseAlignmentForType(e.type);
uint8_t stride = strideForType(e.type);
uint8_t stride = strideForType(e.type, e.stride);
if (e.size > 1) { // this is an array
// round the alignment up to that of a float4
alignment = (alignment + 3) & ~3;
@@ -101,7 +107,7 @@ UniformInterfaceBlock::UniformInterfaceBlock(Builder const& builder) noexcept
offset += padding;
UniformInfo& info = uniformsInfoList[i];
info = { e.name, offset, stride, e.type, e.size, e.precision };
info = { e.name, offset, stride, e.type, e.size, e.precision, e.structName };
// record this uniform info
infoMap[info.name.c_str()] = i;
@@ -154,11 +160,12 @@ uint8_t UTILS_NOINLINE UniformInterfaceBlock::baseAlignmentForType(UniformInterf
case Type::UINT4:
case Type::MAT3:
case Type::MAT4:
case Type::STRUCT:
return 4;
}
}
uint8_t UTILS_NOINLINE UniformInterfaceBlock::strideForType(UniformInterfaceBlock::Type type) noexcept {
uint8_t UTILS_NOINLINE UniformInterfaceBlock::strideForType(UniformInterfaceBlock::Type type, uint32_t stride) noexcept {
switch (type) {
case Type::BOOL:
case Type::INT:
@@ -184,6 +191,8 @@ uint8_t UTILS_NOINLINE UniformInterfaceBlock::strideForType(UniformInterfaceBloc
return 12;
case Type::MAT4:
return 16;
case Type::STRUCT:
return stride;
}
}

View File

@@ -145,8 +145,7 @@ UniformInterfaceBlock const& UibGenerator::getLightsUib() noexcept {
UniformInterfaceBlock const& UibGenerator::getShadowUib() noexcept {
static UniformInterfaceBlock uib = UniformInterfaceBlock::Builder()
.name(ShadowUib::_name)
.add("spotLightFromWorldMatrix", CONFIG_MAX_SHADOW_CASTING_SPOTS, UniformInterfaceBlock::Type::MAT4, Precision::HIGH)
.add("directionShadowBias", CONFIG_MAX_SHADOW_CASTING_SPOTS, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH)
.add("shadows", CONFIG_MAX_SHADOW_CASTING_SPOTS, "ShadowData", sizeof(ShadowUib::ShadowData))
.build();
return uib;
}

View File

@@ -326,7 +326,7 @@ io::sstream& CodeGenerator::generateUniforms(io::sstream& out, ShaderType shader
}
out << "std140) uniform " << blockName.c_str() << " {\n";
for (auto const& info : infos) {
char const* const type = getUniformTypeName(info.type);
char const* const type = getUniformTypeName(info);
char const* const precision = getUniformPrecisionQualifier(info.type, info.precision,
uniformPrecision, defaultPrecision);
out << " " << precision;
@@ -673,9 +673,9 @@ char const* CodeGenerator::getConstantName(MaterialBuilder::Property property) n
}
}
char const* CodeGenerator::getUniformTypeName(UniformInterfaceBlock::Type type) noexcept {
char const* CodeGenerator::getUniformTypeName(UniformInterfaceBlock::UniformInfo const& info) noexcept {
using Type = UniformInterfaceBlock::Type;
switch (type) {
switch (info.type) {
case Type::BOOL: return "bool";
case Type::BOOL2: return "bvec2";
case Type::BOOL3: return "bvec3";
@@ -694,6 +694,7 @@ char const* CodeGenerator::getUniformTypeName(UniformInterfaceBlock::Type type)
case Type::UINT4: return "uvec4";
case Type::MAT3: return "mat3";
case Type::MAT4: return "mat4";
case Type::STRUCT: return info.structName.c_str();
}
}
@@ -777,6 +778,7 @@ bool CodeGenerator::hasPrecision(UniformInterfaceBlock::Type type) noexcept {
case UniformType::BOOL2:
case UniformType::BOOL3:
case UniformType::BOOL4:
case UniformType::STRUCT:
return false;
default:
return true;

View File

@@ -154,7 +154,7 @@ private:
TargetLanguage mTargetLanguage;
// return type name of uniform (e.g.: "vec3", "vec4", "float")
static char const* getUniformTypeName(filament::UniformInterfaceBlock::Type uniformType) noexcept;
static char const* getUniformTypeName(filament::UniformInterfaceBlock::UniformInfo const& info) noexcept;
// return type name of output (e.g.: "vec3", "vec4", "float")
static char const* getOutputTypeName(MaterialBuilder::OutputType type) noexcept;

View File

@@ -280,7 +280,7 @@ static float UTILS_UNUSED VisibilityAshikhmin(float NoV, float NoL, float /*a*/)
* N h
*
* N 4
* Er() = ------------- --- ∑ V(v) <n•l>
* Er() = ------------- --- ∑ L(v) <n•l>
* 4 ∑ <n•l> N
*
*
@@ -541,7 +541,7 @@ void CubemapIBL::roughnessFilter(
* N l n•l
*
*
* To avoid to multiply by 1/PI in the shader, we do it here, which simplifies to:
* To avoid multiplying by 1/PI in the shader, we do it here, which simplifies to:
*
* +----------------------+
* | 1 |

View File

@@ -182,6 +182,7 @@ const char* toString(backend::UniformType type) noexcept {
case backend::UniformType::UINT4: return "uint4";
case backend::UniformType::MAT3: return "float3x3";
case backend::UniformType::MAT4: return "float4x4";
case backend::UniformType::STRUCT: return "struct";
}
}

View File

@@ -102,7 +102,7 @@ float acosFastPositive(float x) {
*
* @public-api
*/
vec4 mulMat4x4Float3(const highp mat4 m, const highp vec3 v) {
highp vec4 mulMat4x4Float3(const highp mat4 m, const highp vec3 v) {
return v.x * m[0] + (v.y * m[1] + (v.z * m[2] + m[3]));
}
@@ -112,7 +112,7 @@ vec4 mulMat4x4Float3(const highp mat4 m, const highp vec3 v) {
*
* @public-api
*/
vec3 mulMat3x3Float3(const highp mat4 m, const highp vec3 v) {
highp vec3 mulMat3x3Float3(const highp mat4 m, const highp vec3 v) {
return v.x * m[0].xyz + (v.y * m[1].xyz + (v.z * m[2].xyz));
}

View File

@@ -8,18 +8,37 @@
* The returned point may contain a bias to attempt to eliminate common
* shadowing artifacts such as "acne". To achieve this, the world space
* normal at the point must also be passed to this function.
* Normal bias is not used for VSM.
*/
highp vec4 computeLightSpacePosition(const highp vec3 p, const highp vec3 n, const highp vec3 l,
const float b, const highp mat4 lightFromWorldMatrix) {
#if defined(HAS_VSM)
// VSM don't apply the shadow bias
highp vec4 lightSpacePosition = (lightFromWorldMatrix * vec4(p, 1.0));
#else
float NoL = saturate(dot(n, l));
float sinTheta = sqrt(1.0 - NoL * NoL);
highp vec3 offsetPosition = p + n * (sinTheta * b);
highp vec4 lightSpacePosition = (lightFromWorldMatrix * vec4(offsetPosition, 1.0));
#endif
return lightSpacePosition;
#if defined(HAS_DIRECTIONAL_LIGHTING)
highp vec4 computeLightSpacePositionDirectional(const highp vec3 p, const highp vec3 n,
const highp vec3 l, const float b, const highp mat4 lightFromWorldMatrix) {
return mulMat4x4Float3(lightFromWorldMatrix, p);
}
#endif
#endif // HAS_DIRECTIONAL_LIGHTING
#if defined(HAS_DYNAMIC_LIGHTING)
highp vec4 computeLightSpacePositionSpot(const highp vec3 p, const highp vec3 n,
const highp vec3 l, const float b, const highp mat4 lightFromWorldMatrix) {
return mulMat4x4Float3(lightFromWorldMatrix, p);
}
#endif // HAS_DYNAMIC_LIGHTING
#else // HAS_VSM
highp vec4 computeLightSpacePositionDirectional(const highp vec3 p, const highp vec3 n,
const highp vec3 l, const float b, const highp mat4 lightFromWorldMatrix) {
float NoL = saturate(dot(n, l));
highp float sinTheta = sqrt(1.0 - NoL * NoL);
highp vec3 offsetPosition = p + n * (sinTheta * b);
return mulMat4x4Float3(lightFromWorldMatrix, offsetPosition);
}
#if defined(HAS_DYNAMIC_LIGHTING)
highp vec4 computeLightSpacePositionSpot(const highp vec3 p, const highp vec3 n,
const highp vec3 l, const float b, const highp mat4 lightFromWorldMatrix) {
highp vec4 positionLs = mulMat4x4Float3(lightFromWorldMatrix, p);
highp float oneOverZ = positionLs.w / positionLs.z;
return computeLightSpacePositionDirectional(p, n, l, b * oneOverZ, lightFromWorldMatrix);
}
#endif // HAS_DIRECTIONAL_LIGHTING
#endif // HAS_VSM
#endif // HAS_SHADOWING

View File

@@ -22,3 +22,11 @@
#define float3x3 mat3
#define float4x4 mat4
// Adreno drivers seem to ignore precision qualifiers in structs, unless they're used in
// UBOs, which is is the case here.
struct ShadowData {
highp mat4 lightFromWorldMatrix;
highp vec3 direction;
float normalBias;
};

View File

@@ -100,10 +100,10 @@ highp vec3 getNormalizedViewportCoord2() {
#if defined(HAS_SHADOWING) && defined(HAS_DYNAMIC_LIGHTING)
highp vec4 getSpotLightSpacePosition(uint index) {
vec3 dir = shadowUniforms.directionShadowBias[index].xyz;
float bias = shadowUniforms.directionShadowBias[index].w;
return computeLightSpacePosition(vertex_worldPosition.xyz,
vertex_worldNormal, dir, bias, shadowUniforms.spotLightFromWorldMatrix[index]);
highp vec3 dir = shadowUniforms.shadows[index].direction;
float bias = shadowUniforms.shadows[index].normalBias;
return computeLightSpacePositionSpot(vertex_worldPosition.xyz,
vertex_worldNormal, dir, bias, shadowUniforms.shadows[index].lightFromWorldMatrix);
}
#endif
@@ -128,13 +128,13 @@ uint getShadowCascade() {
highp vec4 getCascadeLightSpacePosition(uint cascade) {
// For the first cascade, return the interpolated light space position.
// This branch will be coherent (mostly) for neighboring fragments, and it's worth avoiding
// the matrix multiply inside computeLightSpacePosition.
// the matrix multiply inside computeLightSpacePositionDirectional.
if (cascade == 0u) {
// Note: this branch may cause issues with derivatives
return vertex_lightSpacePosition;
}
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
return computeLightSpacePositionDirectional(getWorldPosition(), getWorldNormalVector(),
frameUniforms.lightDirection, frameUniforms.shadowBias.y,
frameUniforms.lightFromWorldMatrix[cascade]);
}

View File

@@ -89,7 +89,8 @@ void main() {
#endif
#if defined(HAS_SHADOWING) && defined(HAS_DIRECTIONAL_LIGHTING)
vertex_lightSpacePosition = computeLightSpacePosition(vertex_worldPosition.xyz, vertex_worldNormal,
vertex_lightSpacePosition = computeLightSpacePositionDirectional(
vertex_worldPosition.xyz, vertex_worldNormal,
frameUniforms.lightDirection, frameUniforms.shadowBias.y, getLightFromWorldMatrix());
#endif

View File

@@ -2,16 +2,12 @@
// Shadowing configuration
//------------------------------------------------------------------------------
#define SHADOW_SAMPLING_PCF_HARD 0
#define SHADOW_SAMPLING_PCF_LOW 1
#define SHADOW_SAMPLING_ERROR_DISABLED 0
#define SHADOW_SAMPLING_ERROR_ENABLED 1
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED 0
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS_ENABLED 1
#define SHADOW_SAMPLING_METHOD SHADOW_SAMPLING_PCF_HARD
#define SHADOW_SAMPLING_ERROR SHADOW_SAMPLING_ERROR_DISABLED
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED
@@ -57,52 +53,38 @@ float sampleDepth(const mediump sampler2DArrayShadow map, const uint layer,
return texture(map, vec4(base + dudv, layer, saturate(depth)));
}
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HARD
float ShadowSample_Hard(const mediump sampler2DArrayShadow map, const uint layer,
const highp vec2 size, const highp vec3 position) {
float ShadowSample_PCF(const mediump sampler2DArrayShadow map,
const uint layer, const highp vec3 position) {
highp vec2 size = vec2(textureSize(map, 0));
highp vec2 texelSize = vec2(1.0) / size;
vec2 rpdb = computeReceiverPlaneDepthBias(position);
float depth = samplingBias(position.z, rpdb, texelSize);
// use hardware assisted PCF
return sampleDepth(map,layer, position.xy, vec2(0.0f), depth, rpdb);
}
#endif
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW
float ShadowSample_PCF_Low(const mediump sampler2DArrayShadow map, const uint layer,
const highp vec2 size, highp vec3 position) {
// Castaño, 2013, "Shadow Mapping Summary Part 1"
float ShadowSample_PCF(const mediump sampler2DArray shadowMap,
const uint layer, const highp vec3 position) {
highp vec2 size = vec2(textureSize(shadowMap, 0));
highp vec2 texelSize = vec2(1.0) / size;
vec2 rpdb = computeReceiverPlaneDepthBias(position);
float depth = samplingBias(position.z, rpdb, texelSize);
// clamp position to avoid overflows below, which cause some GPUs to abort
position.xy = clamp(position.xy, vec2(-1.0), vec2(2.0));
vec2 offset = vec2(0.5);
highp vec2 uv = (position.xy * size) + offset;
highp vec2 base = (floor(uv) - offset) * texelSize;
highp vec2 st = fract(uv);
vec2 uw = vec2(3.0 - 2.0 * st.x, 1.0 + 2.0 * st.x);
vec2 vw = vec2(3.0 - 2.0 * st.y, 1.0 + 2.0 * st.y);
highp vec2 u = vec2((2.0 - st.x) / uw.x - 1.0, st.x / uw.y + 1.0);
highp vec2 v = vec2((2.0 - st.y) / vw.x - 1.0, st.y / vw.y + 1.0);
u *= texelSize.x;
v *= texelSize.y;
float sum = 0.0;
sum += uw.x * vw.x * sampleDepth(map, layer, base, vec2(u.x, v.x), depth, rpdb);
sum += uw.y * vw.x * sampleDepth(map, layer, base, vec2(u.y, v.x), depth, rpdb);
sum += uw.x * vw.y * sampleDepth(map, layer, base, vec2(u.x, v.y), depth, rpdb);
sum += uw.y * vw.y * sampleDepth(map, layer, base, vec2(u.y, v.y), depth, rpdb);
return sum * (1.0 / 16.0);
}
// use manual PCF
highp vec2 st = position.xy * size - 0.5;
vec4 d;
#if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER)
d = textureGather(shadowMap, vec3(position.xy, layer), 0); // 01, 11, 10, 00
#else
highp ivec3 tc = ivec3(st, layer);
d[0] = texelFetchOffset(shadowMap, tc, 0, ivec2(0, 1)).r;
d[1] = texelFetchOffset(shadowMap, tc, 0, ivec2(1, 1)).r;
d[2] = texelFetchOffset(shadowMap, tc, 0, ivec2(1, 0)).r;
d[3] = texelFetchOffset(shadowMap, tc, 0, ivec2(0, 0)).r;
#endif
vec4 pcf = step(0.0, d - vec4(depth));
highp vec2 grad = fract(st);
return mix(mix(pcf.w, pcf.z, grad.x), mix(pcf.x, pcf.y, grad.x), grad.y);
}
//------------------------------------------------------------------------------
// Screen-space Contact Shadows
@@ -208,6 +190,22 @@ float chebyshevUpperBound(const highp vec2 moments, const highp float mean,
return mean <= moments.x ? 1.0 : pMax;
}
float ShadowSample_VSM(const mediump sampler2DArray shadowMap,
const uint layer, const highp vec3 position) {
// Read the shadow map with all available filtering
highp vec2 moments = texture(shadowMap, vec3(position.xy, layer)).xy;
highp float depth = position.z;
// EVSM depth warping
depth = depth * 2.0 - 1.0;
depth = exp(frameUniforms.vsmExponent * depth);
highp float depthScale = frameUniforms.vsmDepthScale * depth;
highp float minVariance = depthScale * depthScale;
float lightBleedReduction = frameUniforms.vsmLightBleedReduction;
return chebyshevUpperBound(moments, depth, minVariance, lightBleedReduction);
}
//------------------------------------------------------------------------------
// Shadow sampling dispatch
//------------------------------------------------------------------------------
@@ -222,35 +220,16 @@ float chebyshevUpperBound(const highp vec2 moments, const highp float mean,
float shadow(const mediump sampler2DArrayShadow shadowMap,
const uint layer, const highp vec4 shadowPosition) {
highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w);
highp vec2 size = vec2(textureSize(shadowMap, 0));
// note: shadowPosition.z is in the [1, 0] range (reversed Z)
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HARD
return ShadowSample_Hard(shadowMap, layer, size, position);
#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW
return ShadowSample_PCF_Low(shadowMap, layer, size, position);
#endif
return ShadowSample_PCF(shadowMap, layer, position);
}
// VSM or DPCF sampling
// VSM sampling
float shadow(const mediump sampler2DArray shadowMap,
const uint layer, const highp vec4 shadowPosition) {
// note: shadowPosition.z is in linear light space normalized to [0, 1]
// 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);
// Read the shadow map with all available filtering
highp vec2 moments = texture(shadowMap, vec3(position.xy, layer)).xy;
highp float depth = position.z;
// EVSM depth warping
depth = depth * 2.0 - 1.0;
depth = exp(frameUniforms.vsmExponent * depth);
highp float depthScale = frameUniforms.vsmDepthScale * depth;
highp float minVariance = depthScale * depthScale;
float lightBleedReduction = frameUniforms.vsmLightBleedReduction;
return chebyshevUpperBound(moments, depth, minVariance, lightBleedReduction);
return ShadowSample_VSM(shadowMap, layer, position);
}

View File

@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.13.0",
"version": "1.14.0",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",