diff --git a/BUILDING.md b/BUILDING.md index 1a29b02410..0326df7026 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index fe21ebc543..de7c1734f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 "$") + if(TARGET ${DEPENDENCY}) + get_property(dep_type TARGET ${DEPENDENCY} PROPERTY TYPE) + if(dep_type STREQUAL "STATIC_LIBRARY") + list(APPEND DEPS_FILES "$") + endif() + endif() endforeach() add_custom_command( diff --git a/README.md b/README.md index b454300bfa..564b41fde2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f0b596f24b..5ed6b5349e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -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 diff --git a/android/Windows.md b/android/Windows.md index 8bf142c0ef..0e80198226 100644 --- a/android/Windows.md +++ b/android/Windows.md @@ -122,20 +122,20 @@ The Gradle project used to generate the AAR is located at `\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 ``` diff --git a/android/build.gradle b/android/build.gradle index 71008d4a9a..0ea3922804 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -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') } } } diff --git a/android/buildSrc/src/main/groovy/FilamentPlugin.groovy b/android/buildSrc/src/main/groovy/FilamentPlugin.groovy index cad4b90d19..e32fcc249e 100644 --- a/android/buildSrc/src/main/groovy/FilamentPlugin.groovy +++ b/android/buildSrc/src/main/groovy/FilamentPlugin.groovy @@ -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 binaryPath = null @@ -42,15 +48,23 @@ class TaskWithBinary extends DefaultTask { binaryName = name } + @Inject abstract ObjectFactory getObjects() + @Inject abstract ProviderFactory getProviders() + @Input Property 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 { 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() && diff --git a/android/gradle.properties b/android/gradle.properties index 1af8b47219..63e61ee5ae 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -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 diff --git a/android/samples/sample-page-curl/src/main/java/com/google/android/filament/pagecurl/MainActivity.java b/android/samples/sample-page-curl/src/main/java/com/google/android/filament/pagecurl/MainActivity.java index 5373470b91..be13ed91c4 100644 --- a/android/samples/sample-page-curl/src/main/java/com/google/android/filament/pagecurl/MainActivity.java +++ b/android/samples/sample-page-curl/src/main/java/com/google/android/filament/pagecurl/MainActivity.java @@ -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; diff --git a/build.sh b/build.sh index 7aeb83cd07..45b3cf1ae9 100755 --- a/build.sh +++ b/build.sh @@ -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." ;; diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 45a0520a12..7606b4f5b9 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -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 ) diff --git a/filament/backend/include/backend/Handle.h b/filament/backend/include/backend/Handle.h index 8dd8ed6f4c..bbea5a1b91 100644 --- a/filament/backend/include/backend/Handle.h +++ b/filament/backend/include/backend/Handle.h @@ -21,6 +21,8 @@ #include #include +#include + namespace filament { namespace backend { diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index ad9ac069f0..3fa3d2fb50 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -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; diff --git a/filament/include/filament/LightManager.h b/filament/include/filament/LightManager.h index eb542da2b4..71a7355f3b 100644 --- a/filament/include/filament/LightManager.h +++ b/filament/include/filament/LightManager.h @@ -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() */ diff --git a/filament/src/Froxelizer.cpp b/filament/src/Froxelizer.cpp index 44f30d993d..ca54105b86 100644 --- a/filament/src/Froxelizer.cpp +++ b/filament/src/Froxelizer.cpp @@ -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, }; diff --git a/filament/src/PerViewUniforms.cpp b/filament/src/PerViewUniforms.cpp index aa7f032f7c..dee21d0695 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/PerViewUniforms.cpp @@ -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; } diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index 7c27b6bf53..e17400af84 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -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); diff --git a/filament/src/ShadowMap.cpp b/filament/src/ShadowMap.cpp index feea841bed..8e16878b50 100644 --- a/filament/src/ShadowMap.cpp +++ b/filament/src/ShadowMap.cpp @@ -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 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::lowest(), std::numeric_limits::max() }; - sceneInfo.vsNearFar = { std::numeric_limits::lowest(), std::numeric_limits::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(index), - cameraInfo, params, cascadeParams); + cameraInfo, params, scene, sceneInfo); break; case LightType::FOCUSED_SPOT: case LightType::SPOT: computeShadowCameraSpot( lightData.elementAt(index).xyz, lightData.elementAt(index), lcm.getSpotLightOuterCone(li), - lightData.elementAt(index).w, cameraInfo, params); + lightData.elementAt(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::type localStorage; + std::aligned_storage::type localStorage; // NOLINT(cppcoreguidelines-pro-type-member-init) FrustumBoxIntersection& wsClippedShadowReceiverVolume{ reinterpret_cast(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::max(), std::numeric_limits::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 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::lowest(), std::numeric_limits::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::lowest(), std::numeric_limits::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 diff --git a/filament/src/ShadowMap.h b/filament/src/ShadowMap.h index fb30b1ccf7..d927552d6e 100644 --- a/filament/src/ShadowMap.h +++ b/filament/src/ShadowMap.h @@ -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 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; - 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; diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 725fa818c2..311c4197e4 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -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 tempBlurSrc; // temporary shadowmap when blurring - uint32_t blurRt; - uint32_t shadowRt; + FrameGraphId 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(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(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& 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 & 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(); 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(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(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(); 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) { diff --git a/filament/src/ShadowMapManager.h b/filament/src/ShadowMapManager.h index fc07aaeafb..838c6ebc72 100644 --- a/filament/src/ShadowMapManager.h +++ b/filament/src/ShadowMapManager.h @@ -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& shadowUb, - FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData) noexcept; + ShadowTechnique updateSpotShadowMaps(FEngine& engine, + FView& view, FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData, + ShadowMap::SceneInfo& sceneInfo, TypedUniformBuffer& shadowUb) noexcept; void calculateTextureRequirements(FEngine& engine, FView& view, FScene::LightSoa& lightData) noexcept; diff --git a/filament/src/components/LightManager.cpp b/filament/src/components/LightManager.cpp index 64f1825545..a344b09264 100644 --- a/filament/src/components/LightManager.cpp +++ b/filament/src/components/LightManager.cpp @@ -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 diff --git a/filament/src/materials/colorGrading/colorGrading.fs b/filament/src/materials/colorGrading/colorGrading.fs new file mode 100644 index 0000000000..2b2000142d --- /dev/null +++ b/filament/src/materials/colorGrading/colorGrading.fs @@ -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; +} diff --git a/filament/src/materials/colorGrading/colorGrading.mat b/filament/src/materials/colorGrading/colorGrading.mat index 3501b631a3..1c18adf80c 100644 --- a/filament/src/materials/colorGrading/colorGrading.mat +++ b/filament/src/materials/colorGrading/colorGrading.mat @@ -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; } } diff --git a/filament/src/materials/colorGrading/colorGradingAsSubpass.mat b/filament/src/materials/colorGrading/colorGradingAsSubpass.mat index 8b2eb982c4..828ab6dc23 100644 --- a/filament/src/materials/colorGrading/colorGradingAsSubpass.mat +++ b/filament/src/materials/colorGrading/colorGradingAsSubpass.mat @@ -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; +} + } diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 71c8b62198..54d2876077 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -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 = { diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index 4a7a35e8ca..7488131380 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -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 diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index befd6b48ed..f43a7a4d14 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -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); diff --git a/libs/filabridge/include/private/filament/Variant.h b/libs/filabridge/include/private/filament/Variant.h index 55255b232a..596acfbba3 100644 --- a/libs/filabridge/include/private/filament/Variant.h +++ b/libs/filabridge/include/private/filament/Variant.h @@ -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 { diff --git a/libs/filamat/src/UibGenerator.cpp b/libs/filamat/src/UibGenerator.cpp index d0c6f56f3b..8c0938031f 100644 --- a/libs/filamat/src/UibGenerator.cpp +++ b/libs/filamat/src/UibGenerator.cpp @@ -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 diff --git a/libs/imageio/src/HDRDecoder.cpp b/libs/imageio/src/HDRDecoder.cpp index be3527217d..10091348fc 100644 --- a/libs/imageio/src/HDRDecoder.cpp +++ b/libs/imageio/src/HDRDecoder.cpp @@ -22,6 +22,7 @@ #include +#include #include #include #include diff --git a/libs/utils/include/utils/FixedCapacityVector.h b/libs/utils/include/utils/FixedCapacityVector.h index 5a8c33cef9..72efe30c92 100644 --- a/libs/utils/include/utils/FixedCapacityVector.h +++ b/libs/utils/include/utils/FixedCapacityVector.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include diff --git a/libs/viewer/src/RemoteServer.cpp b/libs/viewer/src/RemoteServer.cpp index d083dd1410..441c730f78 100644 --- a/libs/viewer/src/RemoteServer.cpp +++ b/libs/viewer/src/RemoteServer.cpp @@ -20,6 +20,7 @@ #include +#include #include using namespace utils; diff --git a/shaders/src/depth_main.fs b/shaders/src/depth_main.fs index 9c0948c186..5be49c6002 100644 --- a/shaders/src/depth_main.fs +++ b/shaders/src/depth_main.fs @@ -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 diff --git a/shaders/src/depth_main.vs b/shaders/src/depth_main.vs index 2c99f8f558..9de5344313 100644 --- a/shaders/src/depth_main.vs +++ b/shaders/src/depth_main.vs @@ -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 diff --git a/shaders/src/getters.fs b/shaders/src/getters.fs index 957eb1c4ed..f8eb9c542d 100644 --- a/shaders/src/getters.fs +++ b/shaders/src/getters.fs @@ -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 diff --git a/shaders/src/inputs.fs b/shaders/src/inputs.fs index ed6400c600..614d214ff9 100644 --- a/shaders/src/inputs.fs +++ b/shaders/src/inputs.fs @@ -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; diff --git a/shaders/src/inputs.vs b/shaders/src/inputs.vs index 7e7ac92508..f999f351e2 100644 --- a/shaders/src/inputs.vs +++ b/shaders/src/inputs.vs @@ -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) diff --git a/shaders/src/light_punctual.fs b/shaders/src/light_punctual.fs index 8fa71898d6..4fdd182967 100644 --- a/shaders/src/light_punctual.fs +++ b/shaders/src/light_punctual.fs @@ -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; diff --git a/shaders/src/main.vs b/shaders/src/main.vs index 7da50c1d18..4b90130bb3 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -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; diff --git a/shaders/src/shading_parameters.fs b/shaders/src/shading_parameters.fs index 96457915aa..34fc249683 100644 --- a/shaders/src/shading_parameters.fs +++ b/shaders/src/shading_parameters.fs @@ -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: diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index 5337129427..9c7f3f7bbc 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -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; diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 34ae334466..9ffe7b243a 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -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",