Merge branch 'rc/1.13.0' into release

This commit is contained in:
Benjamin Doherty
2021-11-08 11:50:18 -08:00
43 changed files with 582 additions and 596 deletions

View File

@@ -291,11 +291,11 @@ Alternatively you can build the AAR from the command line by executing the follo
`android/` directory:
```
$ ./gradlew -Pfilament_dist_dir=../../out/android-release/filament assembleRelease
$ ./gradlew -Pcom.google.android.filament.dist-dir=../../out/android-release/filament assembleRelease
```
The `-Pfilament_dist_dir` can be used to specify a different installation directory (it must match
the CMake install prefix used in the previous steps).
The `-Pcom.google.android.filament.dist-dir` can be used to specify a different installation
directory (it must match the CMake install prefix used in the previous steps).
#### Using Filament's AAR

View File

@@ -502,11 +502,15 @@ function(list_licenses OUTPUT MODULES)
foreach(module ${_MODULES})
set(license_path "../../third_party/${module}/LICENSE")
get_filename_component(fullname "${license_path}" ABSOLUTE)
string(APPEND CONTENT "${STR_OPENER}License and copyrights for ${module}:\n${STR_CLOSER},\n")
file(READ ${license_path} license_long)
string(REPLACE "\n" "${STR_CLOSER},\n${STR_OPENER}" license ${license_long})
string(APPEND CONTENT ${STR_OPENER}${license}\n${STR_CLOSER},)
string(APPEND CONTENT "\n\n")
if(EXISTS ${fullname})
string(APPEND CONTENT "${STR_OPENER}License and copyrights for ${module}:\n${STR_CLOSER},\n")
file(READ ${license_path} license_long)
string(REPLACE "\n" "${STR_CLOSER},\n${STR_OPENER}" license ${license_long})
string(APPEND CONTENT ${STR_OPENER}${license}\n${STR_CLOSER},)
string(APPEND CONTENT "\n\n")
else()
message(AUTHOR_WARNING "${license_path} not found. You can ignore this warning if you have devendored ${module}.")
endif()
endforeach()
configure_file(${FILAMENT}/build/licenses.inc.in ${OUTPUT})
endfunction(list_licenses)
@@ -522,7 +526,12 @@ function(combine_static_libs TARGET OUTPUT DEPS)
# Loop through the dependent libraries and query their location on disk.
set(DEPS_FILES )
foreach(DEPENDENCY ${DEPS})
list(APPEND DEPS_FILES "$<TARGET_FILE:${DEPENDENCY}>")
if(TARGET ${DEPENDENCY})
get_property(dep_type TARGET ${DEPENDENCY} PROPERTY TYPE)
if(dep_type STREQUAL "STATIC_LIBRARY")
list(APPEND DEPS_FILES "$<TARGET_FILE:${DEPENDENCY}>")
endif()
endif()
endforeach()
add_custom_command(

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.12.11'
implementation 'com.google.android.filament:filament-android:1.13.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.12.11'
pod 'Filament', '~> 1.13.0'
```
### Snapshots

View File

@@ -3,7 +3,18 @@
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.12.12 (currently main branch)
## v1.13.1 (currently main branch)
## v1.13.0
- Android: Gradle configuration caching is now enabled.
- Android: Filament's Gradle properties have all been renamed to `com.google.android.filament.xxx`
where `xxx` is the property name. See `android/build.gradle` for a complete list [⚠️]
- Android: The Gradle property `filament_tools_dir` (now called
`com.google.android.filament.tools-dir`) does not have a default value anymore. Please specify one
in your `gradle.properties` if you reuse the Gradle plugin in your projects [⚠️]
- engine: Fix spotlights direction and falloff [⚠️ **Material breakage**].
- engine: Improvements to VSM and spotlight shadows.
## v1.12.11

View File

@@ -122,20 +122,20 @@ The Gradle project used to generate the AAR is located at `<filament>\android`.
```
cd android
gradlew -Pfilament_dist_dir=..\out\android-release\filament assembleRelease
gradlew -Pcom.google.android.filament.dist-dir=..\out\android-release\filament assembleRelease
copy filament-android\build\outputs\aar\filament-android-release.aar ..\..\out\
```
If you're only interested in building for a single ABI, you'll need to pass a `filament_abis` parameter:
If you're only interested in building for a single ABI, you'll need to pass a `com.google.android.filament.abis` parameter:
```
gradlew -Pfilament_dist_dir=..\out\android-release\filament assembleRelease -Pfilament_abis=x86
gradlew -Pcom.google.android.filament.dist-dir=..\out\android-release\filament assembleRelease -Pcom.google.android.filament.abis=x86
```
If you're only interested in building SDK, you may skip samples build by passing a `filament_skip_samples` flag:
If you're only interested in building SDK, you may skip samples build by passing a `com.google.android.filament.skip-samples` flag:
```
gradlew -Pfilament_dist_dir=..\out\android-release\filament assembleRelease -Pfilament_skip_samples
gradlew -Pcom.google.android.filament.dist-dir=..\out\android-release\filament assembleRelease -Pfilament_skip_samples
```

View File

@@ -1,26 +1,28 @@
// This script accepts the following parameters:
//
// filament_dist_dir
// com.google.android.filament.dist-dir
// Path to the Filament distribution/install directory for Android
// (produced by make/ninja install). This directory must contain lib/arm64-v8a/ etc.
//
// filament_tools_dir
// com.google.android.filament.tools-dir
// Path to the Filament distribution/install directory for desktop.
// This directory must contain bin/matc.
//
// filament_exclude_vulkan
// com.google.android.filament.exclude-vulkan
// When set, support for Vulkan will be excluded.
//
// filament_skip_samples
// com.google.android.filament.skip-samples
// Exclude samples from the project. Useful to speed up compilation.
//
// filament_abis
// com.google.android.filament.abis
// List of supported ABIs to build as a comma separated list. Available options are:
// arm64-v8a, armeabi-v7a, x86_64, x86, all
// Defaults to all.
//
// Example:
// ./gradlew -Pfilament_dist_dir=../dist-android-release assembleRelease -Pfilament_abis=x86
// ./gradlew -Pcom.google.android.filament.dist-dir=../dist-android-release \
// -Pcom.google.android.filament.abis=x86 \
// assembleRelease
// Publishing to Maven Central:
// - Build and upload artifacts with ./gradlew publish
@@ -41,19 +43,19 @@
buildscript {
def filamentPath = file("../out/android-release/filament").absolutePath
if (project.hasProperty("filament_dist_dir")) {
filamentPath = file(project.property("filament_dist_dir")).absolutePath
if (project.hasProperty("com.google.android.filament.dist-dir")) {
filamentPath = file(project.property("com.google.android.filament.dist-dir")).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("filament_exclude_vulkan")
def excludeVulkan = project.hasProperty("com.google.android.filament.exclude-vulkan")
def abis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]
if (project.hasProperty("filament_abis")) {
def newAbis = project.property("filament_abis").split(',')
if (project.hasProperty("com.google.android.filament.abis")) {
def newAbis = project.property("com.google.android.filament.abis").split(',')
if (!newAbis.contains("all")) {
abis = newAbis
}
@@ -187,7 +189,8 @@ subprojects {
gradle.taskGraph.whenReady {
gradle.taskGraph.allTasks.each {
it.onlyIf {
!it.project.ext.has('isSample') || !project.hasProperty('filament_skip_samples')
!it.project.ext.has('isSample') ||
!project.hasProperty('com.google.android.filament.skip-samples')
}
}
}

View File

@@ -1,20 +1,22 @@
// This plugin accepts the following parameters:
//
// filament_tools_dir
// com.google.android.filament.tools-dir
// Path to the Filament distribution/install directory for desktop.
// This directory must contain bin/matc.
//
// filament_exclude_vulkan
// com.google.android.filament.exclude-vulkan
// When set, support for Vulkan will be excluded.
//
// Example:
// ./gradlew -Pfilament_tools_dir=../../dist-release assembleDebug
// ./gradlew -Pcom.google.android.filament.tools-dir=../../dist-release assembleDebug
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.file.FileType
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.LogLevel
@@ -27,14 +29,18 @@ import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.incremental.InputFileDetails
import org.gradle.api.model.ObjectFactory
import org.gradle.internal.os.OperatingSystem
import org.gradle.process.ExecOperations
import org.gradle.work.ChangeType
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
import java.nio.file.Paths
class TaskWithBinary extends DefaultTask {
import javax.inject.Inject
abstract class TaskWithBinary extends DefaultTask {
private final String binaryName
private Property<String> binaryPath = null
@@ -42,15 +48,23 @@ class TaskWithBinary extends DefaultTask {
binaryName = name
}
@Inject abstract ObjectFactory getObjects()
@Inject abstract ProviderFactory getProviders()
@Input
Property<String> getBinary() {
if (binaryPath == null) {
def tool = ["/bin/${binaryName}.exe", "/bin/${binaryName}"]
def fullPath = tool.collect { path ->
Paths.get(project.ext.filamentToolsPath.absolutePath, path).toFile()
def filamentToolsPath = providers
.gradleProperty("com.google.android.filament.tools-dir")
.forUseAtConfigurationTime().get()
def directory = objects.fileProperty()
.fileValue(new File(filamentToolsPath)).getAsFile().get()
Paths.get(directory.absolutePath, path).toFile()
}
binaryPath = project.objects.property(String.class)
binaryPath = objects.property(String.class)
binaryPath.set(
(OperatingSystem.current().isWindows() ? fullPath[0] : fullPath[1]).toString())
}
@@ -84,6 +98,11 @@ abstract class MaterialCompiler extends TaskWithBinary {
@OutputDirectory
abstract DirectoryProperty getOutputDir()
@Inject abstract FileSystemOperations getFs()
@Inject abstract ExecOperations getExec()
@Inject abstract ObjectFactory getObjects()
@Inject abstract ProviderFactory getProviders()
MaterialCompiler() {
super("matc")
}
@@ -91,7 +110,9 @@ abstract class MaterialCompiler extends TaskWithBinary {
@TaskAction
void execute(InputChanges inputs) {
if (!inputs.incremental) {
project.delete(project.fileTree(outputDir.asFile.get()).matching { include '*.filamat' })
fs.delete({
delete(objects.fileTree().from(outputDir).matching { include '*.filamat' })
})
}
inputs.getFileChanges(inputDir).each { InputFileDetails change ->
@@ -115,12 +136,15 @@ abstract class MaterialCompiler extends TaskWithBinary {
}
def matcArgs = []
if (!project.hasProperty("filament_exclude_vulkan")) {
def exclude_vulkan = providers
.gradleProperty("com.google.android.filament.exclude-vulkan")
.forUseAtConfigurationTime().present
if (!exclude_vulkan) {
matcArgs += ['-a', 'vulkan']
}
matcArgs += ['-a', 'opengl', '-p', 'mobile', '-o', getOutputFile(file), file]
project.exec {
exec.exec {
standardOutput out
errorOutput err
executable "${binary.get()}"
@@ -149,6 +173,10 @@ abstract class IblGenerator extends TaskWithBinary {
@OutputDirectory
abstract DirectoryProperty getOutputDir()
@Inject abstract FileSystemOperations getFs()
@Inject abstract ExecOperations getExec()
@Inject abstract ObjectFactory getObjects()
IblGenerator() {
super("cmgen")
}
@@ -156,7 +184,9 @@ abstract class IblGenerator extends TaskWithBinary {
@TaskAction
void execute(InputChanges inputs) {
if (!inputs.incremental) {
project.delete(project.fileTree(outputDir.asFile.get()).matching { include '*' })
fs.delete({
delete(objects.fileTree().from(outputDir).matching { include '*' })
})
}
inputs.getFileChanges(inputFile).each { InputFileDetails change ->
@@ -188,7 +218,7 @@ abstract class IblGenerator extends TaskWithBinary {
}
commandArgs = commandArgs + " " + file
project.exec {
exec.exec {
standardOutput out
errorOutput err
executable "${binary.get()}"
@@ -213,6 +243,9 @@ abstract class MeshCompiler extends TaskWithBinary {
@OutputDirectory
abstract DirectoryProperty getOutputDir()
@Inject abstract FileSystemOperations getFs()
@Inject abstract ExecOperations getExec()
MeshCompiler() {
super("filamesh")
}
@@ -220,7 +253,9 @@ abstract class MeshCompiler extends TaskWithBinary {
@TaskAction
void execute(InputChanges inputs) {
if (!inputs.incremental) {
project.delete(project.fileTree(outputDir.asFile.get()).matching { include '*.filamesh' })
fs.delete({
delete(objects.fileTree().from(outputDir).matching { include '*.filamesh' })
})
}
inputs.getFileChanges(inputFile).each { InputFileDetails change ->
@@ -243,7 +278,7 @@ abstract class MeshCompiler extends TaskWithBinary {
" Ensure Filament has been built/installed before building this app.")
}
project.exec {
exec.exec {
standardOutput out
errorOutput err
executable "${binary.get()}"
@@ -280,11 +315,6 @@ class FilamentToolsPlugin implements Plugin<Project> {
extension.meshInputFile = project.objects.fileProperty()
extension.meshOutputDir = project.objects.directoryProperty()
project.ext.filamentToolsPath = project.file("../../../out/release/filament")
if (project.hasProperty("filament_tools_dir")) {
project.ext.filamentToolsPath = project.file(project.property("filament_tools_dir"))
}
project.tasks.register("filamentCompileMaterials", MaterialCompiler) {
enabled =
extension.materialInputDir.isPresent() &&

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.12.11
VERSION_NAME=1.13.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -18,3 +18,7 @@ POM_DEVELOPER_NAME=Filament Team
org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
org.gradle.unsafe.configuration-cache=true
com.google.android.filament.tools-dir=../../../out/release/filament

View File

@@ -70,11 +70,11 @@ public class MainActivity extends Activity
private Page mPage;
private PageMaterials mPageMaterials;
private Scene mScene;
private Texture[] mTextures = new Texture[2];
private final Texture[] mTextures = new Texture[2];
private @Entity int mLight;
private IndirectLight mIndirectLight;
private float[] mTouchDownPoint = new float[2];
private final float[] mTouchDownPoint = new float[2];
private float mTouchDownValue = 0;
private float mPageAnimationRadians = 0;
private float mPageAnimationValue = 0;

View File

@@ -453,23 +453,23 @@ function build_android {
if [[ "${ISSUE_DEBUG_BUILD}" == "true" ]]; then
./gradlew \
-Pfilament_dist_dir=../out/android-debug/filament \
-Pfilament_abis=${ABI_GRADLE_OPTION} \
-Pcom.google.android.filament.dist-dir=../out/android-debug/filament \
-Pcom.google.android.filament.abis=${ABI_GRADLE_OPTION} \
${VULKAN_ANDROID_GRADLE_OPTION} \
:filament-android:assembleDebug \
:gltfio-android:assembleDebug \
:filament-utils-android:assembleDebug
./gradlew \
-Pfilament_dist_dir=../out/android-debug/filament \
-Pfilament_abis=${ABI_GRADLE_OPTION} \
-Pcom.google.android.filament.dist-dir=../out/android-debug/filament \
-Pcom.google.android.filament.abis=${ABI_GRADLE_OPTION} \
:filamat-android:assembleDebug
if [[ "${BUILD_ANDROID_SAMPLES}" == "true" ]]; then
for sample in ${ANDROID_SAMPLES}; do
./gradlew \
-Pfilament_dist_dir=../out/android-debug/filament \
-Pfilament_abis=${ABI_GRADLE_OPTION} \
-Pcom.google.android.filament.dist-dir=../out/android-debug/filament \
-Pcom.google.android.filament.abis=${ABI_GRADLE_OPTION} \
:samples:${sample}:assembleDebug
done
fi
@@ -502,23 +502,23 @@ function build_android {
if [[ "${ISSUE_RELEASE_BUILD}" == "true" ]]; then
./gradlew \
-Pfilament_dist_dir=../out/android-release/filament \
-Pfilament_abis=${ABI_GRADLE_OPTION} \
-Pcom.google.android.filament.dist-dir=../out/android-release/filament \
-Pcom.google.android.filament.abis=${ABI_GRADLE_OPTION} \
${VULKAN_ANDROID_GRADLE_OPTION} \
:filament-android:assembleRelease \
:gltfio-android:assembleRelease \
:filament-utils-android:assembleRelease
./gradlew \
-Pfilament_dist_dir=../out/android-release/filament \
-Pfilament_abis=${ABI_GRADLE_OPTION} \
-Pcom.google.android.filament.dist-dir=../out/android-release/filament \
-Pcom.google.android.filament.abis=${ABI_GRADLE_OPTION} \
:filamat-android:assembleRelease
if [[ "${BUILD_ANDROID_SAMPLES}" == "true" ]]; then
for sample in ${ANDROID_SAMPLES}; do
./gradlew \
-Pfilament_dist_dir=../out/android-release/filament \
-Pfilament_abis=${ABI_GRADLE_OPTION} \
-Pcom.google.android.filament.dist-dir=../out/android-release/filament \
-Pcom.google.android.filament.abis=${ABI_GRADLE_OPTION} \
:samples:${sample}:assembleRelease
done
fi
@@ -831,7 +831,7 @@ while getopts ":hacCfijmp:q:uvslwtdk:" opt; do
;;
v)
VULKAN_ANDROID_OPTION="-DFILAMENT_SUPPORTS_VULKAN=OFF"
VULKAN_ANDROID_GRADLE_OPTION="-Pfilament_exclude_vulkan"
VULKAN_ANDROID_GRADLE_OPTION="-Pcom.google.android.filament.exclude-vulkan"
echo "Disabling support for Vulkan in the core Filament library."
echo "Consider using -c after changing this option to clear the Gradle cache."
;;

View File

@@ -275,6 +275,7 @@ add_custom_command(
OUTPUT "${MATERIAL_DIR}/colorGrading.filamat"
DEPENDS ../shaders/src/dithering.fs
DEPENDS ../shaders/src/vignette.fs
DEPENDS src/materials/colorGrading/colorGrading.fs
APPEND
)
@@ -282,6 +283,7 @@ add_custom_command(
OUTPUT "${MATERIAL_DIR}/colorGradingAsSubpass.filamat"
DEPENDS ../shaders/src/dithering.fs
DEPENDS ../shaders/src/vignette.fs
DEPENDS src/materials/colorGrading/colorGrading.fs
APPEND
)

View File

@@ -21,6 +21,8 @@
#include <utils/Log.h>
#include <utils/debug.h>
#include <limits>
namespace filament {
namespace backend {

View File

@@ -87,8 +87,14 @@ OpenGLContext::OpenGLContext() noexcept {
}
// On Adreno (As of 3/20) timer query seem to return the CPU time, not the GPU time.
bugs.dont_use_timer_query = true;
// Blits to texture arrays are failing
bugs.disable_sidecar_blit_into_texture_array = true;
// This bug doesn't happen anymore, but we don't know why. The standalone sample
// app that was written to show this problem still does. We have tested this on
// several V@0490.0 on several devices and the problem appears to have gone away.
// The working hypthesis is that some other state affects this behavior.
bugs.disable_sidecar_blit_into_texture_array = false;
// early exit condition is flattened in EASU code
bugs.split_easu = true;
bugs.invalidate_end_only_if_invalidate_start = true;

View File

@@ -558,10 +558,11 @@ public:
* and are defined by the angle from the center axis to where the falloff begins (i.e.
* cones are defined by their half-angle).
*
* @param inner inner cone angle in *radians* between 0 and @f$ \pi/2 @f$
*
* @param outer outer cone angle in *radians* between \p inner and @f$ \pi/2 @f$
* Both inner and outer are silently clamped to a minimum value of 0.5 degrees
* (~0.00873 radians) to avoid floating-point precision issues during rendering.
*
* @param inner inner cone angle in *radians* between 0.00873 and \p outer
* @param outer outer cone angle in *radians* between 0.00873 inner and @f$ \pi/2 @f$
* @return This Builder, for chaining calls.
*
* @note
@@ -812,8 +813,8 @@ public:
* Dynamically updates a spot light's cone as angles
*
* @param i Instance of the component obtained from getInstance().
* @param inner inner cone angle in *radians* between 0 and pi/2
* @param outer outer cone angle in *radians* between inner and pi/2
* @param inner inner cone angle in *radians* between 0.00873 and outer
* @param outer outer cone angle in *radians* between 0.00873 and pi/2
*
* @see Builder.spotLightCone()
*/

View File

@@ -560,14 +560,19 @@ void Froxelizer::froxelizeLoop(FEngine& engine,
const mat4f& projection = mProjection;
const mat3f& vn = camera.view.upperLeft();
// We use minimum cone angle of 0.5 degrees because too small angles cause issues in the
// sphere/cone intersection test, due to floating-point precision.
constexpr float maxInvSin = 114.59301f; // 1 / sin(0.5 degrees)
constexpr float maxCosSquared = 0.99992385f; // cos(0.5 degrees)^2
for (size_t i = offset; i < count; i += stride) {
const size_t j = i + FScene::DIRECTIONAL_LIGHTS_COUNT;
FLightManager::Instance li = instances[j];
LightParams light = {
.position = (camera.view * float4{ spheres[j].xyz, 1 }).xyz, // to view-space
.cosSqr = lcm.getCosOuterSquared(li), // spot only
.axis = vn * directions[j], // spot only
.invSin = lcm.getSinInverse(li), // spot only
.cosSqr = std::min(maxCosSquared, lcm.getCosOuterSquared(li)), // spot only
.axis = vn * directions[j], // spot only
.invSin = std::min(maxInvSin, lcm.getSinInverse(li)), // spot only
.radius = spheres[j].w,
};

View File

@@ -84,6 +84,8 @@ void PerViewUniforms::prepareCamera(const CameraInfo& camera) noexcept {
s.cameraPosition = float3{ camera.getPosition() };
s.worldOffset = camera.worldOffset;
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
s.clipControl = mClipControl;
}

View File

@@ -316,9 +316,11 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren
const size_t gpuIndex = i - DIRECTIONAL_LIGHTS_COUNT;
auto li = instances[i];
lp[gpuIndex].positionFalloff = { spheres[i].xyz, lcm.getSquaredFalloffInv(li) };
lp[gpuIndex].color = { lcm.getColor(li), 0.0f };
lp[gpuIndex].directionIES = { directions[i], 0.0f };
lp[gpuIndex].direction = directions[i];
lp[gpuIndex].reserved1 = {};
lp[gpuIndex].colorIES = { lcm.getColor(li), 0.0f };
lp[gpuIndex].spotScaleOffset = lcm.getSpotParams(li).scaleOffset;
lp[gpuIndex].reserved3 = {};
lp[gpuIndex].intensity = lcm.getIntensity(li);
lp[gpuIndex].typeShadow = LightsUib::packTypeShadow(
lcm.isPointLight(li) ? 0u : 1u,
@@ -326,7 +328,6 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren
shadowInfo[i].index,
shadowInfo[i].layer);
lp[gpuIndex].channels = LightsUib::packChannels(lcm.getLightChannels(li), shadowInfo[i].castsShadows);
lp[gpuIndex].reserved = {};
}
driver.updateBufferObject(lightUbh, { lp, positionalLightCount * sizeof(LightsUib) }, 0);

View File

@@ -52,7 +52,7 @@ ShadowMap::ShadowMap(FEngine& engine) noexcept :
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
debugRegistry.registerProperty("d.shadowmap.focus_shadowcasters", &engine.debug.shadowmap.focus_shadowcasters);
debugRegistry.registerProperty("d.shadowmap.far_uses_shadowcasters", &engine.debug.shadowmap.far_uses_shadowcasters);
if (ENABLE_LISPSM) {
if constexpr (ENABLE_LISPSM) {
debugRegistry.registerProperty("d.shadowmap.lispsm", &engine.debug.shadowmap.lispsm);
debugRegistry.registerProperty("d.shadowmap.dzn", &engine.debug.shadowmap.dzn);
debugRegistry.registerProperty("d.shadowmap.dzf", &engine.debug.shadowmap.dzf);
@@ -79,49 +79,15 @@ void ShadowMap::render(FScene const& scene, utils::Range<uint32_t> range,
pass->sortCommands();
}
mat4f ShadowMap::getLightViewMatrix(float3 position, float3 direction) noexcept {
mat4f ShadowMap::getDirectionalLightViewMatrix(float3 direction, float3 position) noexcept {
const mat4f M = mat4f::lookAt(position, position + direction, float3{ 0, 1, 0 });
return FCamera::rigidTransformInverse(M);
}
void ShadowMap::computeSceneInfo(float3 dir,
FScene const& scene, filament::CameraInfo const& camera, uint8_t visibleLayers,
SceneInfo& sceneInfo) {
// We assume the light is at the origin to compute the SceneInfo. This is consumed later by
// computeShadowCameraDirectional() which takes this into account.
const mat4f Mv = getLightViewMatrix({}, dir);
const mat4f V = camera.view;
// Compute scene bounds in world space, as well as the light-space and view-space near/far planes
sceneInfo.lsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
sceneInfo.vsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
sceneInfo.wsShadowCastersVolume = {};
sceneInfo.wsShadowReceiversVolume = {};
visitScene(scene, visibleLayers,
[&](Aabb caster) {
sceneInfo.wsShadowCastersVolume.min =
min(sceneInfo.wsShadowCastersVolume.min, caster.min);
sceneInfo.wsShadowCastersVolume.max =
max(sceneInfo.wsShadowCastersVolume.max, caster.max);
float2 nf = 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) {
sceneInfo.wsShadowReceiversVolume.min =
min(sceneInfo.wsShadowReceiversVolume.min, receiver.min);
sceneInfo.wsShadowReceiversVolume.max =
max(sceneInfo.wsShadowReceiversVolume.max, receiver.max);
float2 nf = computeNearFar(V, receiver);
sceneInfo.vsNearFar.x = std::max(sceneInfo.vsNearFar.x, nf.x);
sceneInfo.vsNearFar.y = std::min(sceneInfo.vsNearFar.y, nf.y);
}
);
}
void ShadowMap::update(const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera, const ShadowMapInfo& shadowMapInfo,
const SceneInfo& cascadeParams) noexcept {
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();
@@ -148,7 +114,7 @@ void ShadowMap::update(const FScene::LightSoa& lightData, size_t index,
cullingProjection[2].z = (f + n) / (n - f);
cullingProjection[3].z = (2 * f * n) / (n - f);
} else {
// ortho projection
// orthographic projection
cullingProjection[2].z = 2.0f / (n - f);
cullingProjection[3].z = (f + n) / (n - f);
}
@@ -178,14 +144,15 @@ void ShadowMap::update(const FScene::LightSoa& lightData, size_t index,
case LightType::DIRECTIONAL:
computeShadowCameraDirectional(
lightData.elementAt<FScene::DIRECTION>(index),
cameraInfo, params, cascadeParams);
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);
lightData.elementAt<FScene::POSITION_RADIUS>(index).w,
cameraInfo, params, scene, sceneInfo);
break;
case LightType::POINT:
break;
@@ -195,19 +162,22 @@ void ShadowMap::update(const FScene::LightSoa& lightData, size_t index,
void ShadowMap::computeShadowCameraDirectional(
float3 const& dir, ShadowCameraInfo const& camera,
FLightManager::ShadowParams const& params,
SceneInfo cascadeParams) noexcept {
FScene const& scene, SceneInfo& sceneInfo) noexcept {
/*
* Compute the light's model matrix
*/
// 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 mater for a directional light.
// 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 = getLightViewMatrix({}, dir);
const mat4f MvAtOrigin = getDirectionalLightViewMatrix(dir);
const Aabb wsShadowCastersVolume = cascadeParams.wsShadowCastersVolume;
const Aabb wsShadowReceiversVolume = cascadeParams.wsShadowReceiversVolume;
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfo(MvAtOrigin, scene, sceneInfo);
const Aabb wsShadowCastersVolume = sceneInfo.wsShadowCastersVolume;
const Aabb wsShadowReceiversVolume = sceneInfo.wsShadowReceiversVolume;
if (wsShadowCastersVolume.isEmpty() || wsShadowReceiversVolume.isEmpty()) {
mHasVisibleShadows = false;
return;
@@ -217,13 +187,13 @@ void ShadowMap::computeShadowCameraDirectional(
float3 wsViewFrustumVertices[8];
computeFrustumCorners(wsViewFrustumVertices,
camera.model * FCamera::inverseProjection(camera.projection),
cascadeParams.csNearFar);
sceneInfo.csNearFar);
// we use aligned_storage<> here to avoid the default initialization of std::array<>
std::aligned_storage<sizeof(FrustumBoxIntersection)>::type localStorage;
std::aligned_storage<sizeof(FrustumBoxIntersection)>::type localStorage; // NOLINT(cppcoreguidelines-pro-type-member-init)
FrustumBoxIntersection& wsClippedShadowReceiverVolume{ reinterpret_cast<FrustumBoxIntersection&>(localStorage) };
// compute the intersection of the shadow receivers volume with the view volume
// compute the intersection of the shadow receivers' volume with the view volume
// in world space. This returns a set of points on the convex-hull of the intersection.
size_t vertexCount = intersectFrustumWithBox(wsClippedShadowReceiverVolume,
wsViewFrustumVertices, wsShadowReceiversVolume);
@@ -243,27 +213,27 @@ void ShadowMap::computeShadowCameraDirectional(
*/
Aabb lsLightFrustumBounds;
if (!USE_DEPTH_CLAMP) {
if constexpr (!USE_DEPTH_CLAMP) {
// near plane from shadow caster volume
lsLightFrustumBounds.max.z = cascadeParams.lsNearFar[0];
lsLightFrustumBounds.max.z = sceneInfo.lsNearFar[0];
}
for (size_t i = 0; i < vertexCount; ++i) {
// far: figure out farthest shadow receivers
// far: figure out the farthest shadow receivers
float3 v = mat4f::project(MvAtOrigin, wsClippedShadowReceiverVolume[i]);
lsLightFrustumBounds.min.z = std::min(lsLightFrustumBounds.min.z, v.z);
if (USE_DEPTH_CLAMP) {
// further tighten to the shadow receiver volume
if constexpr (USE_DEPTH_CLAMP) {
// tighten the shadow receiver volume further
lsLightFrustumBounds.max.z = std::max(lsLightFrustumBounds.max.z, v.z);
}
}
if (mEngine.debug.shadowmap.far_uses_shadowcasters) {
// far: closest of the farthest shadow casters and receivers
lsLightFrustumBounds.min.z = std::max(lsLightFrustumBounds.min.z, cascadeParams.lsNearFar[1]);
lsLightFrustumBounds.min.z = std::max(lsLightFrustumBounds.min.z, sceneInfo.lsNearFar[1]);
}
// 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 = getLightViewMatrix(dir * -lsLightFrustumBounds.max.z, dir);
const mat4f Mv = getDirectionalLightViewMatrix(dir, dir * -lsLightFrustumBounds.max.z);
// near / far planes are specified relative to the direction the eye is looking at
// i.e. the -z axis (see: ortho)
@@ -279,7 +249,7 @@ void ShadowMap::computeShadowCameraDirectional(
float4 viewVolumeBoundingSphere = {};
if (params.options.stable) {
// In stable mode, the light frustum size must be fixed, so we can choose either the
// whole view frustum, or the whole scene bounding volume. We simply pick whichever is
// whole view frustum, or the whole scene bounding volume. We simply pick whichever
// is smaller.
// in stable mode we simply take the shadow receivers volume
@@ -411,7 +381,7 @@ void ShadowMap::computeShadowCameraDirectional(
const mat4f S = F * WLMpMv;
// Computes St the transform to use in the shader to access the shadow map texture
// i.e. it transform a world-space vertex to a texture coordinate in the shadow-map
// i.e. it transforms a world-space vertex to a texture coordinate in the shadowmap
const mat4 MbMt = getTextureCoordsMapping();
const mat4f St = mat4f(MbMt * S);
@@ -426,7 +396,7 @@ void ShadowMap::computeShadowCameraDirectional(
if (!mShadowMapInfo.vsm) {
mLightSpace = St;
} else {
mLightSpace = computeVsmLightSpaceMatrix(St, Mv, zfar);
mLightSpace = computeVsmLightSpaceMatrix(St, Mv, znear, zfar);
}
// We apply the constant bias in world space (as opposed to light-space) to account
@@ -451,39 +421,47 @@ void ShadowMap::computeShadowCameraDirectional(
}
}
void ShadowMap::computeShadowCameraSpot(math::float3 const& position, math::float3 const& dir,
void ShadowMap::computeShadowCameraSpot(float3 const& position, float3 const& dir,
float outerConeAngle, float radius, ShadowCameraInfo const& camera,
FLightManager::ShadowParams const& params) noexcept {
FLightManager::ShadowParams const& params, FScene const& scene,
SceneInfo& sceneInfo) noexcept {
// TODO: correctly compute if this spot light has any visible shadows.
mHasVisibleShadows = true;
/*
* Compute the light models matrix.
* Compute the light model matrix.
*/
// Choose a reasonable value for the near plane.
const float nearPlane = radius * (1.0f / 1024.0f);
const float farPlane = radius;
const mat4f Mv = getLightViewMatrix(position, dir);
const mat4f Mv = getDirectionalLightViewMatrix(dir, 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)
float nearPlane = std::max(0.01f, -sceneInfo.lsNearFar.x);
float farPlane = std::min(radius, -sceneInfo.lsNearFar.y);
float outerConeAngleDegrees = outerConeAngle * f::RAD_TO_DEG;
const mat4f Mp = mat4f::perspective(outerConeAngleDegrees * 2, 1.0f, nearPlane, farPlane,
mat4f::Fov::HORIZONTAL);
const mat4f MpMv(Mp * Mv);
const mat4f Mp = mat4f::perspective(outerConeAngleDegrees * 2.0f, 1.0f, nearPlane, farPlane);
const mat4f MpMv(math::highPrecisionMultiply(Mp, Mv));
// Final shadow transform
const mat4f S = MpMv;
const mat4 MbMt = getTextureCoordsMapping();
const mat4f St = mat4f(MbMt * S);
mTexelSizeWs = texelSizeWorldSpace(Mp, mat4f(MbMt));
// TODO: focus projection
// FIXME: texelSizeWorldSpace doesn't work for spotlights
mTexelSizeWs = 0; //texelSizeWorldSpace(Mp, mat4f(MbMt));
if (!mShadowMapInfo.vsm) {
mLightSpace = St;
} else {
mLightSpace = computeVsmLightSpaceMatrix(St, Mv, farPlane);
mLightSpace = computeVsmLightSpaceMatrix(St, Mv, nearPlane, farPlane);
}
const float constantBias = mShadowMapInfo.vsm ? 0.0f : params.options.constantBias;
@@ -505,7 +483,7 @@ void ShadowMap::computeShadowCameraSpot(math::float3 const& position, math::floa
mDebugCamera->setCustomProjection(mat4(Sb * camera.worldOrigin), nearPlane, radius);
}
mat4f ShadowMap::applyLISPSM(math::mat4f& Wp,
mat4f ShadowMap::applyLISPSM(mat4f& Wp,
ShadowCameraInfo const& camera, FLightManager::ShadowParams const& params,
mat4f const& LMpMv,
FrustumBoxIntersection const& wsShadowReceiversVolume, size_t vertexCount,
@@ -514,7 +492,7 @@ mat4f ShadowMap::applyLISPSM(math::mat4f& Wp,
const float LoV = dot(camera.getForwardVector(), dir);
const float sinLV = std::sqrt(std::max(0.0f, 1.0f - LoV * LoV));
// Virtual near plane -- the default is 1m, can be changed by the user.
// Virtual near plane -- the default is 1 m, can be changed by the user.
// The virtual near plane prevents too much resolution to be wasted in the area near the eye
// where shadows might not be visible (e.g. a character standing won't see shadows at her feet).
const float dzn = std::max(0.0f, params.options.shadowNearHint - camera.zn);
@@ -528,8 +506,8 @@ mat4f ShadowMap::applyLISPSM(math::mat4f& Wp,
// compute n and f, the near and far planes coordinates of Wp (warp space).
// It's found by looking down the Y axis in light space (i.e. -Z axis of Wp,
// i.e. the axis orthogonal to the light direction) and taking the min/max
// of the shadow receivers volume.
// Note: znear/zfar encoded in Mp has no influence here (b/c we're interested only by the y axis)
// of the shadow receivers' volume.
// Note: znear/zfar encoded in Mp has no influence here (b/c we're interested only by the y-axis)
const float2 nf = computeNearFarOfWarpSpace(LMpMv, wsShadowReceiversVolume.data(), vertexCount);
const float n = nf[0]; // near plane coordinate of Mp (light space)
const float f = nf[1]; // far plane coordinate of Mp (light space)
@@ -563,10 +541,10 @@ mat4f ShadowMap::applyLISPSM(math::mat4f& Wp,
const float3 lsCameraPosition = mat4f::project(LMpMv, camera.getPosition());
const float3 p = {
// Another option here is to use lsShadowReceiversCenter.x, which skews less the
// x axis. Doesn't seem to make a big difference in the end.
// x-axis. Doesn't seem to make a big difference in the end.
lsCameraPosition.x,
n - nopt,
// note: various papers suggest to use the shadow receiver's center z coordinate in light
// note: various papers suggest using the shadow receiver's center z coordinate in light
// space, i.e. to center "vertically" on the shadow receiver volume.
// e.g. (LMpMv * wsShadowReceiversVolume.center()).z
// However, simply using 0, guarantees to be centered on the light frustum, which itself
@@ -629,22 +607,22 @@ mat4 ShadowMap::getTextureCoordsMapping() const noexcept {
return mat4(Mf * Mb * Mv * Mt);
}
math::mat4f ShadowMap::computeVsmLightSpaceMatrix(const math::mat4f& lightSpacePcf,
const math::mat4f& Mv, float zfar) noexcept {
mat4f ShadowMap::computeVsmLightSpaceMatrix(const mat4f& lightSpacePcf,
const mat4f& Mv, float znear, float zfar) noexcept {
// The lightSpacePcf matrix transforms coordinates from world space into (u, v, z) coordinates,
// where (u, v) are used to access the shadow map, and z is the (non linear) PCF comparison
// where (u, v) are used to access the shadow map, and z is the (non-linear) PCF comparison
// value [0, 1].
//
// For VSM, we want to leave the z coordinate in linear light space, normalized between [0, 1]
// (the normalization factor is therefore -1/zfar).
//
// For VSM, we want to leave the z coordinate in linear light-space, normalized between [0, 1],
// i.e. remapping [near, far] to [0, 1].
// When sampling a VSM shadow map, the shader follows suit, and doesn't divide by w for the z
// coordinate. See getters.fs.
math::mat4f lightSpaceVsm{ lightSpacePcf };
lightSpaceVsm[0].z = Mv[0].z * (-1.0f / zfar);
lightSpaceVsm[1].z = Mv[1].z * (-1.0f / zfar);
lightSpaceVsm[2].z = Mv[2].z * (-1.0f / zfar);
lightSpaceVsm[3].z = Mv[3].z * (-1.0f / zfar);
// coordinate. See shadowing.fs.
// compute z' = -(Mv * position).z / (far - near) - (near / (far - near))
const float scale = 1.0f / (zfar - znear);
mat4f lightSpaceVsm{ lightSpacePcf };
lightSpaceVsm[0].z = -Mv[0].z * scale;
lightSpaceVsm[1].z = -Mv[1].z * scale;
lightSpaceVsm[2].z = -Mv[2].z * scale;
lightSpaceVsm[3].z = (-Mv[3].z - znear) * scale;
return lightSpaceVsm;
}
@@ -663,7 +641,7 @@ mat4f ShadowMap::warpFrustum(float n, float f) noexcept {
return Wp;
}
math::mat4f ShadowMap::directionalLightFrustum(float near, float far) noexcept {
mat4f ShadowMap::directionalLightFrustum(float near, float far) noexcept {
const float d = far - near;
mat4f m;
m[2][2] = -2 / d;
@@ -694,7 +672,7 @@ float2 ShadowMap::computeNearFarOfWarpSpace(mat4f const& lightView,
float2 nearFar = { std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest() };
#pragma nounroll
for (size_t i = 0; i < count; i++) {
// we're on the y axis in light space (looking down to +y)
// we're on the y-axis in light space (looking down to +y)
float c = mat4f::project(lightView, wsVertices[i]).y;
nearFar.x = std::min(nearFar.x, c);
nearFar.y = std::max(nearFar.y, c);
@@ -805,7 +783,7 @@ void ShadowMap::snapLightFrustum(float2& s, float2& o,
const float2 r = 2.0f * shadowMapResolution;
o -= fmod(o, r);
// This offsets the texture coordinates so it has a fixed offset w.r.t the world
// This offsets the texture coordinates, so it has a fixed offset w.r.t the world
const float2 lsOrigin = mat4f::project(Mv, worldOrigin).xy * s;
o -= fmod(lsOrigin, r);
}
@@ -854,7 +832,7 @@ size_t ShadowMap::intersectFrustumWithBox(
// b) add the scene's vertices that are known to be inside the view frustum
//
// We need to handle the case where a corner of the box lies exactly on a plane of
// the frustum. This actually happens often due to fitting light-space
// the frustum. This actually often happens due to fitting light-space
// We fudge the distance to the plane by a small amount.
#pragma nounroll
for (float3 p : wsSceneReceiversCorners) {
@@ -988,7 +966,7 @@ float ShadowMap::texelSizeWorldSpace(const mat3f& worldToShadowTexture) const no
// The Jacobian of the transformation from texture-to-world is the matrix itself for
// orthographic projections. We just need to inverse worldToShadowTexture,
// which is guaranteed to be orthographic.
// The two first columns give us the how a texel maps in world-space.
// The two first columns give us how a texel maps in world-space.
const float ures = 1.0f / mShadowMapInfo.shadowDimension;
const float vres = 1.0f / mShadowMapInfo.shadowDimension;
const mat3f shadowTextureToWorld(inverse(worldToShadowTexture));
@@ -1001,7 +979,7 @@ float ShadowMap::texelSizeWorldSpace(const mat3f& worldToShadowTexture) const no
float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF) const noexcept {
// Here we compute the Jacobian of inverse(MbMtF * Wp).
// The expression below has been computed with Mathematica. However, it's not very hard,
// albeit error prone, to do it by hand because MbMtF is a linear transform.
// albeit error-prone, to do it by hand because MbMtF is a linear transform.
// So we really only need to calculate the Jacobian of inverse(Wp) at inverse(MbMtF).
//
// Because we're only interested in the length of the columns of the Jacobian, we can use
@@ -1018,7 +996,7 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF) const
const float dres = mShadowMapInfo.zResolution;
constexpr bool JACOBIAN_ESTIMATE = false;
if (JACOBIAN_ESTIMATE) {
if constexpr (JACOBIAN_ESTIMATE) {
// this estimates the Jacobian -- this is a lot heavier. This is mostly for reference
// and testing.
const mat4f Si(inverse(MbMtF * Wp));
@@ -1063,7 +1041,6 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF) const
return s;
}
template<typename Casters, typename Receivers>
void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
Casters casters, Receivers receivers) noexcept {
@@ -1090,4 +1067,48 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
}
}
void ShadowMap::initSceneInfo(FScene const& scene, filament::CameraInfo const& camera,
ShadowMap::SceneInfo& sceneInfo) {
sceneInfo.vsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
// We assume the light is at the origin to compute the SceneInfo. This is consumed later by
// computeShadowCameraDirectional() which takes this into account.
const mat4f V = camera.view;
// Compute scene bounds in world space, as well as the light-space and view-space near/far planes
sceneInfo.wsShadowCastersVolume = {};
sceneInfo.wsShadowReceiversVolume = {};
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster) {
sceneInfo.wsShadowCastersVolume.min =
min(sceneInfo.wsShadowCastersVolume.min, caster.min);
sceneInfo.wsShadowCastersVolume.max =
max(sceneInfo.wsShadowCastersVolume.max, caster.max);
},
[&](Aabb receiver) {
sceneInfo.wsShadowReceiversVolume.min =
min(sceneInfo.wsShadowReceiversVolume.min, receiver.min);
sceneInfo.wsShadowReceiversVolume.max =
max(sceneInfo.wsShadowReceiversVolume.max, receiver.max);
float2 nf = ShadowMap::computeNearFar(V, receiver);
sceneInfo.vsNearFar.x = std::max(sceneInfo.vsNearFar.x, nf.x);
sceneInfo.vsNearFar.y = std::min(sceneInfo.vsNearFar.y, nf.y);
}
);
}
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) {
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) {
}
);
}
} // namespace filament

View File

@@ -64,6 +64,8 @@ public:
};
struct SceneInfo {
explicit SceneInfo(uint8_t visibleLayers) noexcept : visibleLayers(visibleLayers) { }
// The near and far planes, in clip space, to use for this shadow map
math::float2 csNearFar = { -1.0f, 1.0f };
@@ -81,22 +83,18 @@ public:
// World-space shadow-receivers volume
Aabb wsShadowReceiversVolume;
uint8_t visibleLayers;
};
static math::mat4f getLightViewMatrix(
math::float3 position, math::float3 direction) noexcept;
// Call once per frame to populate the CascadeParameters struct, then pass to update().
// This computes values constant across all cascades.
static void computeSceneInfo(math::float3 dir,
FScene const& scene, filament::CameraInfo const& camera, uint8_t visibleLayers,
SceneInfo& sceneInfo);
static math::mat4f getDirectionalLightViewMatrix(
math::float3 direction, math::float3 position = {}) noexcept;
// 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,
const ShadowMapInfo& shadowMapInfo, const SceneInfo& cascadeParams) noexcept;
void update(const FScene::LightSoa& lightData, size_t index, filament::CameraInfo const& camera,
const ShadowMapInfo& shadowMapInfo, FScene const& scene,
SceneInfo& sceneInfo) noexcept;
void render(FScene const& scene, utils::Range<uint32_t> range,
FScene::VisibleMaskType visibilityMask, filament::CameraInfo const& cameraInfo,
@@ -120,6 +118,15 @@ public:
backend::PolygonOffset getPolygonOffset() const noexcept { return mPolygonOffset; }
// Call once per frame to populate the SceneInfo struct, then pass to update().
// This computes values constant across all shadow maps.
static void initSceneInfo(FScene const& scene, filament::CameraInfo const& camera,
ShadowMap::SceneInfo& sceneInfo);
// Update SceneInfo struct for a given light
static void updateSceneInfo(const math::mat4f& Mv, FScene const& scene,
ShadowMap::SceneInfo& sceneInfo);
private:
struct ShadowCameraInfo {
math::mat4f projection;
@@ -145,13 +152,14 @@ 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& direction,
ShadowCameraInfo const& camera, FLightManager::ShadowParams const& params,
SceneInfo cascadeParams) noexcept;
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) noexcept;
FLightManager::ShadowParams const& params, FScene const& scene,
SceneInfo& sceneInfo) noexcept;
static math::mat4f applyLISPSM(math::mat4f& Wp,
ShadowCameraInfo const& camera, FLightManager::ShadowParams const& params,
@@ -213,8 +221,8 @@ private:
math::mat4 getTextureCoordsMapping() const noexcept;
static math::mat4f computeVsmLightSpaceMatrix(const math::mat4f& lightSpace,
const math::mat4f& Mv, float zfar) noexcept;
static math::mat4f computeVsmLightSpaceMatrix(const math::mat4f& lightSpacePcf,
const math::mat4f& Mv, float znear, float zfar) noexcept;
float texelSizeWorldSpace(const math::mat3f& worldToShadowTexture) const noexcept;
float texelSizeWorldSpace(const math::mat4f& W, const math::mat4f& MbMtF) const noexcept;

View File

@@ -34,7 +34,7 @@ namespace filament {
using namespace backend;
using namespace math;
ShadowMapManager::ShadowMapManager(FEngine& engine) {
ShadowMapManager::ShadowMapManager(FEngine& engine) { // NOLINT(cppcoreguidelines-pro-type-member-init)
// initialize our ShadowMap array in-place
for (auto& entry : mShadowMapCache) {
new (&entry) ShadowMap(engine);
@@ -59,8 +59,20 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(
FScene::LightSoa& lightData) noexcept {
calculateTextureRequirements(engine, view, lightData);
ShadowTechnique shadowTechnique = {};
shadowTechnique |= updateCascadeShadowMaps(engine, view, renderableData, lightData);
shadowTechnique |= updateSpotShadowMaps(engine, view, shadowUb, renderableData, lightData);
ShadowMap::SceneInfo sceneInfo(view.getVisibleLayers());
// Compute scene-dependent values shared across all shadow maps
ShadowMap::initSceneInfo(
*view.getScene(), view.getCameraInfo(),
sceneInfo);
shadowTechnique |= updateCascadeShadowMaps(
engine, view, renderableData, lightData, sceneInfo);
shadowTechnique |= updateSpotShadowMaps(
engine, view, renderableData, lightData, sceneInfo, shadowUb);
return shadowTechnique;
}
@@ -163,9 +175,9 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp
const float4 vsmClearColor{ vsmMoment1, vsmMoment2, 0.0f, 0.0f };
struct ShadowPassData {
FrameGraphId<FrameGraphTexture> tempBlurSrc; // temporary shadowmap when blurring
uint32_t blurRt;
uint32_t shadowRt;
FrameGraphId<FrameGraphTexture> tempBlurSrc{}; // temporary shadowmap when blurring
uint32_t blurRt{};
uint32_t shadowRt{};
};
auto shadows = prepareShadowPass.getData().shadows;
@@ -240,7 +252,7 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp
renderTargetDesc.clearFlags = TargetBufferFlags::DEPTH;
}
// finally create the shadowmap render target -- one per layer.
// finally, create the shadowmap render target -- one per layer.
data.shadowRt = builder.declareRenderPass("Shadow RT", renderTargetDesc);
},
[=, &engine, &view](FrameGraphResources const& resources,
@@ -261,7 +273,7 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp
view.prepareCamera(cameraInfo);
// We set a viewport with a 1-texel border for when we index outside of the
// We set a viewport with a 1-texel border for when we index outside the
// texture.
// DON'T CHANGE this unless ShadowMap::getTextureCoordsMapping() is updated too.
// see: ShadowMap::getTextureCoordsMapping()
@@ -276,7 +288,7 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp
view.prepareViewport(viewport);
// set uniforms needed to render this ShadowMap
// Currently these uniforms are owned by View and are global, but eventully
// Currently these uniforms are owned by View and are global, but eventually
// this will set a separate per shadowmap UBO
view.prepareShadowMap();
@@ -298,7 +310,7 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp
const float sigma = (blurWidth + 1.0f) / 6.0f;
size_t kernelWidth = std::ceil((blurWidth - 5.0f) / 4.0f);
kernelWidth = kernelWidth * 4 + 5;
const float ratio = (kernelWidth + 1.0f) / sigma;
const float ratio = float(kernelWidth + 1) / sigma;
ppm.gaussianBlurPass(fg,
shadowPass->tempBlurSrc, 0,
shadows, 0, layer,
@@ -321,26 +333,18 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp
fg.getBlackboard().put("shadows", shadows);
}
ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(
FEngine& engine, FView& view, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData) noexcept {
ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEngine& engine,
FView& view, FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
ShadowMap::SceneInfo& sceneInfo) noexcept {
FScene* scene = view.getScene();
const CameraInfo& viewingCameraInfo = view.getCameraInfo();
uint8_t visibleLayers = view.getVisibleLayers();
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);
ShadowMap::SceneInfo sceneInfo;
if (!mCascadeShadowMaps.empty()) {
// Compute scene-dependent values shared across all cascades
const float3 dir = lightData.elementAt<FScene::DIRECTION>(0);
ShadowMap::computeSceneInfo(dir,
*scene, viewingCameraInfo, visibleLayers, sceneInfo);
// 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];
@@ -354,14 +358,14 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(
.vsm = view.hasVsm()
};
map.update(lightData, 0, viewingCameraInfo, shadowMapInfo, sceneInfo);
map.update(lightData, 0, viewingCameraInfo, shadowMapInfo, *scene, sceneInfo);
Frustum const& frustum = map.getCamera().getCullingFrustum();
FView::cullRenderables(engine.getJobSystem(), renderableData, frustum,
VISIBLE_DIR_SHADOW_RENDERABLE_BIT);
// note: normalBias is ignored for VSM
const float normalBias = lcm.getShadowNormalBias(0);
// 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();
mShadowMappingUniforms.shadowBias = float3{ 0, normalBias * texelSizeWorldSpace, 0 };
@@ -414,7 +418,6 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(
ShadowTechnique shadowTechnique{};
uint32_t directionalShadowsMask = 0;
uint32_t cascadeHasVisibleShadows = 0;
float screenSpaceShadowDistance = 0.0f;
for (size_t i = 0, c = mCascadeShadowMaps.size(); i < c; i++) {
auto& entry = mCascadeShadowMaps[i];
@@ -431,7 +434,11 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(
.vsm = view.hasVsm()
};
sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] };
shadowMap.update(lightData, 0, viewingCameraInfo, shadowMapInfo, sceneInfo);
shadowMap.update(lightData, 0,
viewingCameraInfo, shadowMapInfo,
*scene,sceneInfo);
if (shadowMap.hasVisibleShadows()) {
mShadowMappingUniforms.lightFromWorldMatrix[i] = shadowMap.getLightSpaceMatrix();
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
@@ -440,7 +447,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(
}
// screen-space contact shadows for the directional light
screenSpaceShadowDistance = options.maxShadowDistance;
float screenSpaceShadowDistance = options.maxShadowDistance;
if (options.screenSpaceContactShadows) {
shadowTechnique |= ShadowTechnique::SCREEN_SPACE;
}
@@ -467,15 +474,15 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(
return shadowTechnique;
}
ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(
FEngine& engine, FView& view, TypedUniformBuffer<ShadowUib>& shadowUb,
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData) noexcept {
ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine& engine,
FView& view, FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
ShadowMap::SceneInfo& sceneInfo, TypedUniformBuffer <ShadowUib>& shadowUb) noexcept {
ShadowTechnique shadowTechnique{};
const CameraInfo& viewingCameraInfo = view.getCameraInfo();
const uint16_t textureSize = mTextureRequirements.size;
// shadow-map shadows for point/spot lights
// shadow-map shadows for point/spotlights
auto& lcm = engine.getLightManager();
FScene::ShadowInfo* const shadowInfo = lightData.data<FScene::SHADOW_INFO>();
for (size_t i = 0, c = mSpotShadowMaps.size(); i < c; i++) {
@@ -486,40 +493,43 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(
size_t l = entry.getLightIndex();
const size_t textureDimension = entry.getShadowOptions()->mapSize;
const ShadowMap::ShadowMapInfo layout{
const ShadowMap::ShadowMapInfo shadowMapInfo{
.zResolution = mTextureZResolution,
.atlasDimension = textureSize,
.textureDimension = (uint16_t)textureDimension,
.shadowDimension = (uint16_t)(textureDimension - 2),
.vsm = view.hasVsm()
};
shadowMap.update(lightData, l, viewingCameraInfo, layout, {});
shadowMap.update(lightData, l,
viewingCameraInfo, shadowMapInfo,
*view.getScene(), sceneInfo);
FLightManager::Instance light = lightData.elementAt<FScene::LIGHT_INSTANCE>(l);
if (shadowMap.hasVisibleShadows()) {
// Cull shadow casters
auto& s = shadowUb.edit();
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();
// note: normalBias is ignored for VSM
// note: normalBias is set to zero for VSM
const float3 dir = lightData.elementAt<FScene::DIRECTION>(l);
const float texelSizeWorldSpace = shadowMap.getTexelSizeWorldSpace();
const float normalBias = lcm.getShadowNormalBias(light);
const float normalBias = shadowMapInfo.vsm ? 0.0f : lcm.getShadowNormalBias(light);
s.directionShadowBias[i] = float4{ dir, normalBias * texelSizeWorldSpace };
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
}
}
// screen-space contact shadows for point/spot lights
// screen-space contact shadows for point/spotlights
auto *pInstance = lightData.data<FScene::LIGHT_INSTANCE>();
for (size_t i = 0, c = lightData.size(); i < c; i++) {
// screen-space contact shadows
@@ -539,7 +549,7 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
// Lay out the shadow maps. For now, we take the largest requested dimension and allocate a
// texture of that size. Each cascade / shadow map gets its own layer in the array texture.
// The directional shadow cascades start on layer 0, followed by spot lights.
// The directional shadow cascades start on layer 0, followed by spotlights.
uint8_t layer = 0;
uint32_t maxDimension = 0;
for (auto& entry : mCascadeShadowMaps) {

View File

@@ -120,12 +120,13 @@ private:
uint8_t levels = 0;
} mTextureRequirements;
ShadowTechnique updateCascadeShadowMaps(FEngine& engine, FView& view,
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData) noexcept;
ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
FView& view, FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
ShadowMap::SceneInfo& sceneInfo) noexcept;
ShadowTechnique updateSpotShadowMaps(FEngine& engine, FView& view,
TypedUniformBuffer<ShadowUib>& shadowUb,
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData) noexcept;
ShadowTechnique updateSpotShadowMaps(FEngine& engine,
FView& view, FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
ShadowMap::SceneInfo& sceneInfo, TypedUniformBuffer<ShadowUib>& shadowUb) noexcept;
void calculateTextureRequirements(FEngine& engine, FView& view, FScene::LightSoa& lightData) noexcept;

View File

@@ -352,23 +352,23 @@ void FLightManager::setFalloff(Instance i, float falloff) noexcept {
void FLightManager::setSpotLightCone(Instance i, float inner, float outer) noexcept {
auto& manager = mManager;
if (i && isSpotLight(i)) {
// clamp the inner/outer angles to pi
float innerClamped = std::min(std::abs(inner), f::PI_2);
float outerClamped = std::min(std::abs(outer), f::PI_2);
// clamp the inner/outer angles to [0.5 degrees, 90 degrees]
float innerClamped = std::clamp(std::abs(inner), 0.5f * f::DEG_TO_RAD, f::PI_2);
float outerClamped = std::clamp(std::abs(outer), 0.5f * f::DEG_TO_RAD, f::PI_2);
// outer must always be bigger than inner
outerClamped = std::max(innerClamped, outerClamped);
// inner must always be smaller than outer
innerClamped = std::min(innerClamped, outerClamped);
float cosOuter = fast::cos(outerClamped);
float cosInner = fast::cos(innerClamped);
float cosOuterSquared = cosOuter * cosOuter;
float scale = 1 / std::max(1.0f / 1024.0f, cosInner - cosOuter);
float scale = 1.0f / std::max(1.0f / 1024.0f, cosInner - cosOuter);
float offset = -cosOuter * scale;
SpotParams& spotParams = manager[i].spotParams;
spotParams.outerClamped = outerClamped;
spotParams.cosOuterSquared = cosOuterSquared;
spotParams.sinInverse = 1 / std::sqrt(1 - cosOuterSquared);
spotParams.sinInverse = 1.0f / std::sin(outerClamped);
spotParams.scaleOffset = { scale, offset };
// we need to recompute the luminous intensity

View File

@@ -0,0 +1,13 @@
vec3 colorGrade(mediump sampler3D lut, const vec3 x) {
// Alexa LogC EI 1000
const float a = 5.555556;
const float b = 0.047996;
const float c = 0.244161 / log2(10.0);
const float d = 0.386036;
vec3 logc = c * log2(a * x + b) + d;
// Remap to sample pixel centers
logc = materialParams.lutSize.x + logc * materialParams.lutSize.y;
return textureLod(lut, logc, 0.0).rgb;
}

View File

@@ -79,8 +79,9 @@ fragment {
#include "../../../../shaders/src/dithering.fs"
#include "../../../../shaders/src/vignette.fs"
#include "colorGrading.fs"
void dummy(){ }
void dummy(){}
float starburst(const vec2 uv) {
// get an offset that continuously moves with the camera
@@ -123,68 +124,52 @@ vec3 bloom(const vec3 color) {
return result;
}
vec3 colorGrade(mediump sampler3D lut, const vec3 x) {
// Alexa LogC EI 1000
const float a = 5.555556;
const float b = 0.047996;
const float c = 0.244161 / log2(10.0);
const float d = 0.386036;
vec3 logc = c * log2(a * x + b) + d;
// Remap to sample pixel centers
logc = materialParams.lutSize.x + logc * materialParams.lutSize.y;
return textureLod(lut, logc, 0.0).rgb;
}
vec3 resolveFragment(const ivec2 uv) {
return texelFetch(materialParams_colorBuffer, uv, 0).rgb;
}
vec4 resolveAlphaFragment(const ivec2 uv) {
return texelFetch(materialParams_colorBuffer, uv, 0);
}
vec4 resolve() {
vec4 resolveFragment(const ivec2 uv) {
#if POST_PROCESS_OPAQUE
vec4 color = vec4(resolveFragment(ivec2(getUV())), 1.0);
if (materialParams.bloom.x > 0.0) {
color.rgb = bloom(color.rgb);
}
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
}
color.rgb = colorGrade(materialParams_lut, color.rgb);
if (materialParams.fxaa > 0) {
color.a = luminance(color.rgb);
}
return vec4(texelFetch(materialParams_colorBuffer, uv, 0).rgb, 1.0);
#else
vec4 color = resolveAlphaFragment(ivec2(getUV()));
color.rgb /= color.a + FLT_EPS;
if (materialParams.bloom.x > 0.0) {
color.rgb = bloom(color.rgb);
}
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
}
color.rgb = colorGrade(materialParams_lut, color.rgb);
color.rgb *= color.a + FLT_EPS;
#endif
vec4 color = texelFetch(materialParams_colorBuffer, uv, 0);
color.rgb *= 1.0 / (color.a + FLT_EPS);
return color;
#endif
}
void postProcess(inout PostProcessInputs postProcess) {
postProcess.color = resolve();
if (materialParams.dithering > 0) {
vec4 dithered = dither(postProcess.color, materialParams.temporalNoise);
#if POST_PROCESS_OPAQUE
postProcess.color.rgb = dithered.rgb;
#else
postProcess.color = dithered;
#endif
vec4 color = resolveFragment(ivec2(getUV()));
// Bloom
if (materialParams.bloom.x > 0.0) {
color.rgb = bloom(color.rgb);
}
// Vignette
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
}
// Color grading
color.rgb = colorGrade(materialParams_lut, color.rgb);
// output in premultiplied alpha
#if !POST_PROCESS_OPAQUE
color.rgb *= color.a + FLT_EPS;
#endif
// dithering
if (materialParams.dithering > 0) {
color = dither(color, materialParams.temporalNoise);
}
// kill alpha computations when opaque / fxaa luminance
#if POST_PROCESS_OPAQUE
color.a = 1.0;
if (materialParams.fxaa > 0) {
color.a = luminance(color.rgb);
}
#endif
postProcess.color = color;
}
}

View File

@@ -71,64 +71,51 @@ fragment {
#include "../../../../shaders/src/dithering.fs"
#include "../../../../shaders/src/vignette.fs"
#include "colorGrading.fs"
vec3 colorGrade(mediump sampler3D lut, const vec3 x) {
// Alexa LogC EI 1000
const float a = 5.555556;
const float b = 0.047996;
const float c = 0.244161 / log2(10.0);
const float d = 0.386036;
vec3 logc = c * log2(a * x + b) + d;
void dummy(){}
// Remap to sample pixel centers
logc = materialParams.lutSize.x + logc * materialParams.lutSize.y;
return textureLod(lut, logc, 0.0).rgb;
}
vec3 resolveFragment(const ivec2 uv) {
return subpassLoad(materialParams_colorBuffer).rgb;
}
vec4 resolveAlphaFragment(const ivec2 uv) {
return subpassLoad(materialParams_colorBuffer);
}
vec4 resolve() {
vec4 resolveFragment() {
#if POST_PROCESS_OPAQUE
vec4 color = vec4(resolveFragment(ivec2(getUV())), 1.0);
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
}
color.rgb = colorGrade(materialParams_lut, color.rgb);
if (materialParams.fxaa > 0) {
color.a = luminance(color.rgb);
}
return vec4(subpassLoad(materialParams_colorBuffer).rgb, 1.0);
#else
vec4 color = resolveAlphaFragment(ivec2(getUV()));
color.rgb /= color.a + FLT_EPS;
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
}
color.rgb = colorGrade(materialParams_lut, color.rgb);
color.rgb *= color.a + FLT_EPS;
vec4 color = subpassLoad(materialParams_colorBuffer);
color.rgb *= 1.0 / (color.a + FLT_EPS);
return color;
#endif
return color;
}
void postProcess(inout PostProcessInputs postProcess) {
vec4 color = resolveFragment();
// Vignette
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
}
void postProcess(inout PostProcessInputs postProcess) {
vec4 color = resolve();
if (materialParams.dithering > 0) {
vec4 dithered = dither(color, materialParams.temporalNoise);
#if POST_PROCESS_OPAQUE
color.rgb = dithered.rgb;
#else
color = dithered;
// Color grading
color.rgb = colorGrade(materialParams_lut, color.rgb);
// output in premultiplied alpha
#if !POST_PROCESS_OPAQUE
color.rgb *= color.a + FLT_EPS;
#endif
}
postProcess.tonemappedOutput = color;
// dithering
if (materialParams.dithering > 0) {
color = dither(color, materialParams.temporalNoise);
}
// kill alpha computations when opaque / fxaa luminance
#if POST_PROCESS_OPAQUE
color.a = 1.0;
if (materialParams.fxaa > 0) {
color.a = luminance(color.rgb);
}
#endif
postProcess.tonemappedOutput = color;
}
}

View File

@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.12.11"
spec.version = "1.13.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.12.11/filament-v1.12.11-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.13.0/filament-v1.13.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 = 12;
static constexpr size_t MATERIAL_VERSION = 13;
/**
* Supported shading models

View File

@@ -88,7 +88,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
math::float4 userTime; // time(s), (double)time - (float)time, 0, 0
float iblRoughnessOneLevel; // level for roughness == 1
float cameraFar; // camera *culling* far-plane distance (projection far is at +inf)
float cameraFar; // camera *culling* far-plane distance, always positive (projection far is at +inf)
float refractionLodOffset;
// bit 0: directional (sun) shadow enabled
@@ -129,8 +129,8 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
float vsmReserved0;
float lodBias;
float reserved1;
float reserved2;
float oneOverFarMinusNear; // 1 / (f-n), always positive
float nearOverFarMinusNear; // n / (f-n), always positive
float reserved3;
// bring PerViewUib to 2 KiB
@@ -164,13 +164,14 @@ static_assert(sizeof(PerRenderableUib) % 256 == 0, "sizeof(Transform) should be
struct LightsUib {
static constexpr utils::StaticString _name{ "LightsUniforms" };
math::float4 positionFalloff; // { float3(pos), 1/falloff^2 }
math::half4 color; // { half3(col), 0 }
math::half4 directionIES; // { half3(dir), IES index }
math::half2 spotScaleOffset; // { scale, offset }
float intensity; // float
uint32_t typeShadow; // 0x00.ll.ii.ct (t: 0=point, 1=spot, c:contact, ii: index, ll: layer)
uint32_t channels; // 0x000c00ll (ll: light channels, c: caster)
math::float4 reserved; // 0
math::float3 direction; // dir
float reserved1; // 0
math::half4 colorIES; // { half3(col), IES index }
math::float2 spotScaleOffset; // { scale, offset }
float reserved3; // 0
float intensity; // float
uint32_t typeShadow; // 0x00.ll.ii.ct (t: 0=point, 1=spot, c:contact, ii: index, ll: layer)
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 {
return (type & 0xF) | (contactShadow ? 0x10 : 0x00) | (index << 8) | (layer << 16);

View File

@@ -53,7 +53,7 @@ namespace filament {
//
// Standard variants:
// +-----+-----+-----+-----+-----+-----+-----+-----+
// | 0 | VSM | FOG | 0 | SKN | SRE | DYN | DIR | 64 (-24)
// | 0 | VSM | FOG | 0 | SKN | SRE | DYN | DIR | 40 (-24)
// +-----+-----+-----+-----+-----+-----+-----+-----+
// Vertex shader 0 0 0 X X X X
// Fragment shader X X 0 0 X X X
@@ -62,14 +62,18 @@ namespace filament {
//
// Depth variants:
// +-----+-----+-----+-----+-----+-----+-----+-----+
// | 0 | VSM | PCK | 1 | SKN | 0 | 0 | 0 | 8 (-58)
// | 0 | VSM | PCK | 1 | SKN | 0 | 0 | 0 | 6 (-58)
// +-----+-----+-----+-----+-----+-----+-----+-----+
// Vertex depth X 0 1 X 0 0 0
// Fragment depth X X 1 0 0 0 0
// Reserved 1 1 1 X 0 0 0
// Reserved X X 1 X X X 1
// Reserved X X 1 X X 1 X
// Reserved X X 1 X 1 X X
// Reserved X X 1 X 0 0 1
// Reserved X X 1 X 0 1 0
// Reserved X X 1 X 0 1 1
// Reserved X X 1 X 1 0 0
// Reserved X X 1 X 1 0 1
// Reserved X X 1 X 1 1 0
// Reserved X X 1 X 1 1 1
//
// 46 variants used, 82 reserved
//
@@ -87,14 +91,16 @@ namespace filament {
static constexpr uint8_t PICKING = 0x20; // picking (depth)
static constexpr uint8_t VSM = 0x40; // variance shadow maps
static constexpr uint8_t DEPTH_MASK = DIRECTIONAL_LIGHTING |
DYNAMIC_LIGHTING |
SHADOW_RECEIVER |
DEPTH;
static constexpr uint8_t STANDARD_VARIANT = 0u;
static constexpr uint8_t STANDARD_MASK = DEPTH;
// the depth variant deactivates all variants that make no sense when writing the depth
// only -- essentially, all fragment-only variants.
static constexpr uint8_t DEPTH_VARIANT = DEPTH;
static constexpr uint8_t DEPTH_MASK = DIRECTIONAL_LIGHTING |
DYNAMIC_LIGHTING |
SHADOW_RECEIVER |
DEPTH;
// this mask filters out the lighting variants
static constexpr uint8_t UNLIT_MASK = SKINNING_OR_MORPHING | FOG;
@@ -118,22 +124,31 @@ namespace filament {
inline void setVsm(bool v) noexcept { set(v, VSM); }
inline static constexpr bool isValidDepthVariant(uint8_t variantKey) noexcept {
// VSM and PICKING are mutually exclusive for DEPTH variants
// Can't have VSM and PICKING together with DEPTH variants
constexpr uint8_t RESERVED_MASK = VSM | PICKING | DEPTH;
constexpr uint8_t RESERVED_VALUE = VSM | PICKING | DEPTH;
return (variantKey & DEPTH_MASK) == DEPTH_VARIANT &&
variantKey != 0b1110000u &&
variantKey != 0b1111000u;
((variantKey & RESERVED_MASK) != RESERVED_VALUE);
}
inline static constexpr bool isValidStandardVariant(uint8_t variantKey) noexcept {
// can't have shadow receiver if we don't have any lighting
constexpr uint8_t RESERVED0_MASK = SHADOW_RECEIVER | DYNAMIC_LIGHTING | DIRECTIONAL_LIGHTING;
constexpr uint8_t RESERVED0_VALUE = SHADOW_RECEIVER;
// can't have VSM without shadow receiver
constexpr uint8_t RESERVED1_MASK = VSM | SHADOW_RECEIVER;
constexpr uint8_t RESERVED1_VALUE = VSM;
return (variantKey & STANDARD_MASK) == STANDARD_VARIANT &&
(variantKey & RESERVED0_MASK) != RESERVED0_VALUE &&
(variantKey & RESERVED1_MASK) != RESERVED1_VALUE;
}
static constexpr bool isReserved(uint8_t variantKey) noexcept {
// reserved variants that should just be skipped
// 1. If the DEPTH bit is set, then it must be a valid depth variant. Otherwise, the
// variant is reserved.
// 2. If SRE is set, either DYN or DIR must also be set (it makes no sense to have
// shadows without lights).
// 3. If VSM is set, then SRE must be set.
return ((variantKey & DEPTH) && !isValidDepthVariant(variantKey)) ||
(variantKey & 0b0010111u) == 0b0000100u ||
(variantKey & 0b1010100u) == 0b1000000u;
if (variantKey & DEPTH) {
return !isValidDepthVariant(variantKey);
}
return !isValidStandardVariant(variantKey);
}
static constexpr uint8_t filterVariantVertex(uint8_t variantKey) noexcept {

View File

@@ -110,8 +110,8 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
.add("vsmReserved0", 1, UniformInterfaceBlock::Type::FLOAT)
.add("lodBias", 1, UniformInterfaceBlock::Type::FLOAT)
.add("reserved1", 1, UniformInterfaceBlock::Type::FLOAT)
.add("reserved2", 1, UniformInterfaceBlock::Type::FLOAT)
.add("oneOverFarMinusNear", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH)
.add("nearOverFarMinusNear", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH)
.add("reserved3", 1, UniformInterfaceBlock::Type::FLOAT)
// bring PerViewUib to 2 KiB

View File

@@ -22,6 +22,7 @@
#include <utils/Log.h>
#include <cstring>
#include <limits>
#include <memory>
#include <sstream>

View File

@@ -21,6 +21,7 @@
#include <utils/Panic.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <type_traits>
#include <utility>

View File

@@ -20,6 +20,7 @@
#include <utils/Log.h>
#include <cstring>
#include <vector>
using namespace utils;

View File

@@ -37,18 +37,8 @@ void main() {
#endif
#if defined(HAS_VSM)
// For VSM, we use the linear light space Z coordinate as the depth metric, which works for both
// directional and spot lights.
// The value is guaranteed to be between [0, -zfar] by construction of viewFromWorldMatrix,
// (see ShadowMap.cpp).
highp float z = (frameUniforms.viewFromWorldMatrix * vec4(vertex_worldPosition, 1.0)).z;
// rescale the depth between [0, 1]
highp float depth = -z / abs(frameUniforms.cameraFar);
// We use positive only EVSM which helps a lot with light bleeding.
depth = depth * 2.0 - 1.0;
depth = exp(frameUniforms.vsmExponent * depth);
// interpolated depth is stored in vertex_worldPosition.w (see depth_main.vs / main.vs)
highp float depth = exp(vertex_worldPosition.w);
// computes the moments
// See GPU Gems 3

View File

@@ -25,7 +25,21 @@ void main() {
#endif
#if defined(HAS_VSM)
vertex_worldPosition = material.worldPosition.xyz;
// For VSM, we use the linear light-space Z coordinate as the depth metric, which works for both
// directional and spot lights and can be safely interpolated.
// The value is guaranteed to be between [-znear, -zfar] by construction of viewFromWorldMatrix,
// (see ShadowMap.cpp).
// Use vertex_worldPosition.w which is otherwise not used to store the interpolated
// light-space depth.
highp float z = (frameUniforms.viewFromWorldMatrix * vec4(material.worldPosition.xyz, 1.0)).z;
// rescale [near, far] to [0, 1]
highp float depth = -z * frameUniforms.oneOverFarMinusNear - frameUniforms.nearOverFarMinusNear;
// EVSM pre-mapping
depth = frameUniforms.vsmExponent * (depth * 2.0 - 1.0);
vertex_worldPosition.w = depth;
#endif
// this must happen before we compensate for vulkan below

View File

@@ -81,19 +81,6 @@ highp vec2 uvToRenderTargetUV(highp vec2 uv) {
return uv;
}
#if defined(HAS_SHADOWING) && defined(HAS_DIRECTIONAL_LIGHTING)
highp vec3 getLightSpacePosition() {
#if defined(HAS_VSM)
// For VSM, do not project the Z coordinate. It remains as linear Z in light space.
// See the computeVsmLightSpaceMatrix comments in ShadowMap.cpp.
return vec3(vertex_lightSpacePosition.xy * (1.0 / vertex_lightSpacePosition.w),
vertex_lightSpacePosition.z);
#else
return vertex_lightSpacePosition.xyz * (1.0 / vertex_lightSpacePosition.w);
#endif
}
#endif
/**
* Returns the normalized [0, 1] viewport coordinates with the origin at the viewport's bottom-left.
* Z coordinate is in the [0, 1] range as well.
@@ -112,19 +99,11 @@ highp vec3 getNormalizedViewportCoord2() {
}
#if defined(HAS_SHADOWING) && defined(HAS_DYNAMIC_LIGHTING)
highp vec3 getSpotLightSpacePosition(uint index) {
highp vec4 getSpotLightSpacePosition(uint index) {
vec3 dir = shadowUniforms.directionShadowBias[index].xyz;
float bias = shadowUniforms.directionShadowBias[index].w;
highp vec4 position = computeLightSpacePosition(vertex_worldPosition,
return computeLightSpacePosition(vertex_worldPosition.xyz,
vertex_worldNormal, dir, bias, shadowUniforms.spotLightFromWorldMatrix[index]);
#if defined(HAS_VSM)
// For VSM, do not project the Z coordinate. It remains as linear Z in light space.
// See the computeVsmLightSpaceMatrix comments in ShadowMap.cpp.
return vec3(position.xy * (1.0 / position.w), position.z);
#else
return position.xyz * (1.0 / position.w);
#endif
}
#endif
@@ -146,25 +125,18 @@ uint getShadowCascade() {
#if defined(HAS_SHADOWING) && defined(HAS_DIRECTIONAL_LIGHTING)
highp vec3 getCascadeLightSpacePosition(uint cascade) {
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.
if (cascade == 0u) {
// Note: this branch may cause issues with derivatives
return getLightSpacePosition();
return vertex_lightSpacePosition;
}
highp vec4 pos = computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
frameUniforms.lightDirection, frameUniforms.shadowBias.y,
frameUniforms.lightFromWorldMatrix[cascade]);
#if defined(HAS_VSM)
// For VSM, do not project the Z coordinate. It remains as linear Z in light space.
// See the computeVsmLightSpaceMatrix comments in ShadowMap.cpp.
return vec3(pos.xy * (1.0 / pos.w), pos.z);
#else
return pos.xyz * (1.0 / pos.w);
#endif
}
#endif

View File

@@ -2,7 +2,7 @@
// Attributes and uniforms
//------------------------------------------------------------------------------
LAYOUT_LOCATION(4) in highp vec3 vertex_worldPosition;
LAYOUT_LOCATION(4) in highp vec4 vertex_worldPosition;
#if defined(HAS_ATTRIBUTE_TANGENTS)
LAYOUT_LOCATION(5) SHADING_INTERPOLATION in mediump vec3 vertex_worldNormal;

View File

@@ -56,7 +56,8 @@ layout(location = LOCATION_CUSTOM6) in vec4 mesh_custom6;
layout(location = LOCATION_CUSTOM7) in vec4 mesh_custom7;
#endif
LAYOUT_LOCATION(4) out highp vec3 vertex_worldPosition;
LAYOUT_LOCATION(4) out highp vec4 vertex_worldPosition;
#if defined(HAS_ATTRIBUTE_TANGENTS)
LAYOUT_LOCATION(5) SHADING_INTERPOLATION out mediump vec3 vertex_worldNormal;
#if defined(MATERIAL_NEEDS_TBN)

View File

@@ -110,7 +110,7 @@ float getDistanceAttenuation(const highp vec3 posToLight, float falloff) {
return attenuation * 1.0 / max(distanceSquare, 1e-4);
}
float getAngleAttenuation(const vec3 lightDir, const vec3 l, const vec2 scaleOffset) {
float getAngleAttenuation(const highp vec3 lightDir, const highp vec3 l, const highp vec2 scaleOffset) {
float cd = dot(lightDir, l);
float attenuation = saturate(cd * scaleOffset.x + scaleOffset.y);
return attenuation * attenuation;
@@ -132,26 +132,23 @@ Light getLight(const uint index) {
highp mat4 data = lightsUniforms.lights[lightIndex];
highp vec4 positionFalloff = data[0];
vec4 color = vec4(
unpackHalf2x16(floatBitsToUint(data[1][0])),
unpackHalf2x16(floatBitsToUint(data[1][1]))
highp vec3 direction = data[1].xyz;
vec4 colorIES = vec4(
unpackHalf2x16(floatBitsToUint(data[2][0])),
unpackHalf2x16(floatBitsToUint(data[2][1]))
);
vec4 directionIES = vec4(
unpackHalf2x16(floatBitsToUint(data[1][2])),
unpackHalf2x16(floatBitsToUint(data[1][3]))
);
vec2 scaleOffset = unpackHalf2x16(floatBitsToUint(data[2][0]));
highp float intensity = data[2][1];
highp uint typeShadow = floatBitsToUint(data[2][2]);
highp uint channels = floatBitsToUint(data[2][3]);
highp vec2 scaleOffset = data[2].zw;
highp float intensity = data[3][1];
highp uint typeShadow = floatBitsToUint(data[3][2]);
highp uint channels = floatBitsToUint(data[3][3]);
// poition-to-light vector
highp vec3 worldPosition = vertex_worldPosition;
highp vec3 worldPosition = vertex_worldPosition.xyz;
highp vec3 posToLight = positionFalloff.xyz - worldPosition;
// and populate the Light structure
Light light;
light.colorIntensity.rgb = color.rgb;
light.colorIntensity.rgb = colorIES.rgb;
light.colorIntensity.w = computePreExposedIntensity(intensity, frameUniforms.exposure);
light.l = normalize(posToLight);
light.attenuation = getDistanceAttenuation(posToLight, positionFalloff.w);
@@ -165,7 +162,7 @@ Light getLight(const uint index) {
uint type = typeShadow & 0x1u;
if (type == LIGHT_TYPE_SPOT) {
light.attenuation *= getAngleAttenuation(-directionIES.xyz, light.l, scaleOffset);
light.attenuation *= getAngleAttenuation(-direction, light.l, scaleOffset);
light.contactShadows = bool(typeShadow & 0x10u);
light.shadowIndex = (typeShadow >> 8u) & 0xFFu;
light.shadowLayer = (typeShadow >> 16u) & 0xFFu;

View File

@@ -82,13 +82,14 @@ void main() {
#endif
// The world position can be changed by the user in materialVertex()
vertex_worldPosition = material.worldPosition.xyz;
vertex_worldPosition.xyz = material.worldPosition.xyz;
#ifdef HAS_ATTRIBUTE_TANGENTS
vertex_worldNormal = material.worldNormal;
#endif
#if defined(HAS_SHADOWING) && defined(HAS_DIRECTIONAL_LIGHTING)
vertex_lightSpacePosition = computeLightSpacePosition(vertex_worldPosition, vertex_worldNormal,
vertex_lightSpacePosition = computeLightSpacePosition(vertex_worldPosition.xyz, vertex_worldNormal,
frameUniforms.lightDirection, frameUniforms.shadowBias.y, getLightFromWorldMatrix());
#endif
@@ -108,6 +109,24 @@ void main() {
gl_Position.z = gl_Position.z * -0.5 + 0.5;
#endif
#if defined(HAS_VSM)
// For VSM, we use the linear light-space Z coordinate as the depth metric, which works for both
// directional and spot lights and can be safely interpolated.
// The value is guaranteed to be between [-znear, -zfar] by construction of viewFromWorldMatrix,
// (see ShadowMap.cpp).
// Use vertex_worldPosition.w which is otherwise not used to store the interpolated
// light-space depth.
highp float z = (frameUniforms.viewFromWorldMatrix * vec4(material.worldPosition.xyz, 1.0)).z;
// rescale [near, far] to [0, 1]
highp float depth = -z * frameUniforms.oneOverFarMinusNear - frameUniforms.nearOverFarMinusNear;
// EVSM pre-mapping
depth = frameUniforms.vsmExponent * (depth * 2.0 - 1.0);
vertex_worldPosition.w = depth;
#endif
// this must happen before we compensate for vulkan below
vertex_position = gl_Position;

View File

@@ -32,7 +32,7 @@ void computeShadingParams() {
#endif
#endif
shading_position = vertex_worldPosition;
shading_position = vertex_worldPosition.xyz;
shading_view = normalize(frameUniforms.cameraPosition - shading_position);
// we do this so we avoid doing (matrix multiply), but we burn 4 varyings:

View File

@@ -2,31 +2,19 @@
// Shadowing configuration
//------------------------------------------------------------------------------
#define SHADOW_SAMPLING_PCF_HARD 0
#define SHADOW_SAMPLING_PCF_LOW 1
#define SHADOW_SAMPLING_PCF_MEDIUM 2
#define SHADOW_SAMPLING_PCF_HIGH 3
#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_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_RECEIVER_PLANE_DEPTH_BIAS_MIN_SAMPLING_METHOD SHADOW_SAMPLING_PCF_MEDIUM
#define SHADOW_SAMPLING_METHOD SHADOW_SAMPLING_PCF_LOW
#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
#if SHADOW_SAMPLING_ERROR == SHADOW_SAMPLING_ERROR_ENABLED
#undef SHADOW_RECEIVER_PLANE_DEPTH_BIAS
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_ENABLED
#elif SHADOW_SAMPLING_METHOD < SHADOW_RECEIVER_PLANE_DEPTH_BIAS_MIN_SAMPLING_METHOD
#undef SHADOW_RECEIVER_PLANE_DEPTH_BIAS
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED
#endif
//------------------------------------------------------------------------------
// Shadow sampling methods
//------------------------------------------------------------------------------
@@ -61,9 +49,7 @@ float samplingBias(float depth, const vec2 rpdb, const highp vec2 texelSize) {
float sampleDepth(const mediump sampler2DArrayShadow map, const uint layer,
const highp vec2 base, const highp vec2 dudv, float depth, vec2 rpdb) {
#if SHADOW_RECEIVER_PLANE_DEPTH_BIAS == SHADOW_RECEIVER_PLANE_DEPTH_BIAS_ENABLED
#if SHADOW_SAMPLING_METHOD >= SHADOW_RECEIVER_PLANE_DEPTH_BIAS_MIN_SAMPLING_METHOD
depth += dot(dudv, rpdb);
#endif
#endif
// depth must be clamped to support floating-point depth formats. This is to avoid comparing a
// value from the depth texture (which is never greater than 1.0) with a greater-than-one
@@ -77,7 +63,7 @@ float ShadowSample_Hard(const mediump sampler2DArrayShadow map, const uint layer
highp vec2 texelSize = vec2(1.0) / size;
vec2 rpdb = computeReceiverPlaneDepthBias(position);
float depth = samplingBias(position.z, rpdb, texelSize);
return texture(map, vec4(position.xy, layer, saturate(depth)));
return sampleDepth(map,layer, position.xy, vec2(0.0f), depth, rpdb);
}
#endif
@@ -86,6 +72,8 @@ float ShadowSample_PCF_Low(const mediump sampler2DArrayShadow map, const uint la
const highp vec2 size, highp vec3 position) {
// Castaño, 2013, "Shadow Mapping Summary Part 1"
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));
@@ -104,9 +92,6 @@ float ShadowSample_PCF_Low(const mediump sampler2DArrayShadow map, const uint la
u *= texelSize.x;
v *= texelSize.y;
vec2 rpdb = computeReceiverPlaneDepthBias(position);
float depth = samplingBias(position.z, rpdb, texelSize);
float sum = 0.0;
sum += uw.x * vw.x * sampleDepth(map, layer, base, vec2(u.x, v.x), depth, rpdb);
@@ -119,118 +104,6 @@ float ShadowSample_PCF_Low(const mediump sampler2DArrayShadow map, const uint la
}
#endif
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_MEDIUM
float ShadowSample_PCF_Medium(const mediump sampler2DArrayShadow map, const uint layer,
const highp vec2 size, highp vec3 position) {
// Castaño, 2013, "Shadow Mapping Summary Part 1"
highp vec2 texelSize = vec2(1.0) / size;
// 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);
vec3 uw = vec3(4.0 - 3.0 * st.x, 7.0, 1.0 + 3.0 * st.x);
vec3 vw = vec3(4.0 - 3.0 * st.y, 7.0, 1.0 + 3.0 * st.y);
highp vec3 u = vec3((3.0 - 2.0 * st.x) / uw.x - 2.0, (3.0 + st.x) / uw.y, st.x / uw.z + 2.0);
highp vec3 v = vec3((3.0 - 2.0 * st.y) / vw.x - 2.0, (3.0 + st.y) / vw.y, st.y / vw.z + 2.0);
u *= texelSize.x;
v *= texelSize.y;
vec2 rpdb = computeReceiverPlaneDepthBias(position);
float depth = samplingBias(position.z, rpdb, texelSize);
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.z * vw.x * sampleDepth(map, layer, base, vec2(u.z, 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);
sum += uw.z * vw.y * sampleDepth(map, layer, base, vec2(u.z, v.y), depth, rpdb);
sum += uw.x * vw.z * sampleDepth(map, layer, base, vec2(u.x, v.z), depth, rpdb);
sum += uw.y * vw.z * sampleDepth(map, layer, base, vec2(u.y, v.z), depth, rpdb);
sum += uw.z * vw.z * sampleDepth(map, layer, base, vec2(u.z, v.z), depth, rpdb);
return sum * (1.0 / 144.0);
}
#endif
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HIGH
float ShadowSample_PCF_High(const mediump sampler2DArrayShadow map, const uint layer,
const highp vec2 size, highp vec3 position) {
// Castaño, 2013, "Shadow Mapping Summary Part 1"
highp vec2 texelSize = vec2(1.0) / size;
// 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);
vec4 uw = vec4(
5.0 * st.x - 6.0,
11.0 * st.x - 28.0,
-(11.0 * st.x + 17.0),
-(5.0 * st.x + 1.0));
vec4 vw = vec4(
5.0 * st.y - 6.0,
11.0 * st.y - 28.0,
-(11.0 * st.y + 17.0),
-(5.0 * st.y + 1.0));
vec4 u = vec4(
(4.0 * st.x - 5.0) / uw.x - 3.0,
(4.0 * st.x - 16.0) / uw.y - 1.0,
-(7.0 * st.x + 5.0) / uw.z + 1.0,
-st.x / uw.w + 3.0);
vec4 v = vec4(
(4.0 * st.y - 5.0) / vw.x - 3.0,
(4.0 * st.y - 16.0) / vw.y - 1.0,
-(7.0 * st.y + 5.0) / vw.z + 1.0,
-st.y / vw.w + 3.0);
u *= texelSize.x;
v *= texelSize.y;
vec2 rpdb = computeReceiverPlaneDepthBias(position);
float depth = samplingBias(position.z, rpdb, texelSize);
highp 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.z * vw.x * sampleDepth(map, layer, base, vec2(u.z, v.x), depth, rpdb);
sum += uw.w * vw.x * sampleDepth(map, layer, base, vec2(u.w, 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);
sum += uw.z * vw.y * sampleDepth(map, layer, base, vec2(u.z, v.y), depth, rpdb);
sum += uw.w * vw.y * sampleDepth(map, layer, base, vec2(u.w, v.y), depth, rpdb);
sum += uw.x * vw.z * sampleDepth(map, layer, base, vec2(u.x, v.z), depth, rpdb);
sum += uw.y * vw.z * sampleDepth(map, layer, base, vec2(u.y, v.z), depth, rpdb);
sum += uw.z * vw.z * sampleDepth(map, layer, base, vec2(u.z, v.z), depth, rpdb);
sum += uw.w * vw.z * sampleDepth(map, layer, base, vec2(u.w, v.z), depth, rpdb);
sum += uw.x * vw.w * sampleDepth(map, layer, base, vec2(u.x, v.w), depth, rpdb);
sum += uw.y * vw.w * sampleDepth(map, layer, base, vec2(u.y, v.w), depth, rpdb);
sum += uw.z * vw.w * sampleDepth(map, layer, base, vec2(u.z, v.w), depth, rpdb);
sum += uw.w * vw.w * sampleDepth(map, layer, base, vec2(u.w, v.w), depth, rpdb);
return sum * (1.0 / 2704.0);
}
#endif
//------------------------------------------------------------------------------
// Screen-space Contact Shadows
//------------------------------------------------------------------------------
@@ -347,30 +220,30 @@ float chebyshevUpperBound(const highp vec2 moments, const highp float mean,
// PCF sampling
float shadow(const mediump sampler2DArrayShadow shadowMap,
const uint layer, const highp vec3 shadowPosition) {
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, shadowPosition);
return ShadowSample_Hard(shadowMap, layer, size, position);
#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW
return ShadowSample_PCF_Low(shadowMap, layer, size, shadowPosition);
#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_MEDIUM
return ShadowSample_PCF_Medium(shadowMap, layer, size, shadowPosition);
#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HIGH
return ShadowSample_PCF_High(shadowMap, layer, size, shadowPosition);
return ShadowSample_PCF_Low(shadowMap, layer, size, position);
#endif
}
// VSM sampling
// VSM or DPCF sampling
float shadow(const mediump sampler2DArray shadowMap,
const uint layer, const highp vec3 shadowPosition) {
const uint layer, const highp vec4 shadowPosition) {
// note: shadowPosition.z is in linear light space normalized to [0, 1]
// see: ShadowMap::computeVsmLightSpaceMatrix() in ShadowMap.cpp
// see: computeLightSpacePosition() in common_shadowing.fs
highp vec3 position = vec3(shadowPosition.xy * (1.0 / shadowPosition.w), shadowPosition.z);
// Read the shadow map with all available filtering
highp vec2 moments = texture(shadowMap, vec3(shadowPosition.xy, layer)).xy;
highp float depth = shadowPosition.z;
highp vec2 moments = texture(shadowMap, vec3(position.xy, layer)).xy;
highp float depth = position.z;
// EVSM depth warping
depth = depth * 2.0 - 1.0;

View File

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