Compare commits

..
Author SHA1 Message Date
Run Yu 0ad90d139d wgpu: code clean up based on feedback 2026-01-28 21:35:45 -05:00
Run Yu cf694a3334 wgpu: add TextureUsage::SAMPLEABLE to the depth texture in test_Scissor,
which is needed to draw the triangle if the test is enabled.
2026-01-23 15:17:12 -05:00
Run Yu 863379b5b4 wgpu: fix the readPixels function so test_Scissor can draw the triangle 2026-01-23 15:17:12 -05:00
Filament Bot 6ce06b1b60 [automated] Updating /docs due to commit e21d4a5
Full commit hash is e21d4a5326

DOCS_ALLOW_DIRECT_EDITS
2026-01-23 09:10:02 +00:00
Mathias Agopian e21d4a5326 SimSky: Refine Stars, Water, and Heat Shimmer simulation (#9628)
- **Stars**:
  - Implemented procedural stars using hash-based noise.
  - Added UI controls for Star Density and Enable/Disable.
  - Tuned star brightness (reduced intensity) and refined twilight fade timing (visible during nautical twilight).
  - Improved compositing with aggressive cloud occlusion and non-linear fade.
  - Added star reflections to water, strictly masked to the horizon line.

- **Heat Shimmer**:
  - Fixed horizon artifacts by decoupling shimmer from atmospheric density (Mie scattering).
  - Implemented FBM-based view distortion for heat waves.
  - Added sun elevation fade (shimmer fades out as sun rises > 30°).

- **Water**:
  - Implemented Finite Difference normal calculation as a high-quality fallback when "Derivative Trick" is disabled.
  - Added "Octaves" parameter to control wave detail.
  - Refined reflection logic to handle stars and sun disk properly.

- **System**:
  - Updated [simulated_skybox.mat](cci:7://file:///Users/mathias/sources/git/filament/docs_src/src_raw/wip/sky/simulated_skybox.mat:0:0-0:0) with new material parameters (`starControl`, `waterControl`).
  - Refactored JS bindings in [SimulatedSkybox.js](cci:7://file:///Users/mathias/sources/git/filament/docs_src/src_raw/wip/sky/SimulatedSkybox.js:0:0-0:0) and organized `main.js` UI into logical folders.

DOCS_FORCE
2026-01-23 01:06:07 -08:00
Filament Bot 7fd9e728ae [automated] Updating /docs due to commit b66736b
Full commit hash is b66736b8cc

DOCS_ALLOW_DIRECT_EDITS
2026-01-22 23:23:09 +00:00
Powei Feng b66736b8cc docs: move wip skybox demo to /docs_src/src_raw (#9626)
/docs content is overwritten by content in /docs_src

(This tag will force a regeneration)
DOCS_FORCE
2026-01-22 23:20:10 +00:00
Doris Wu ea8f6a3c92 return empty string for es2 (#9623) 2026-01-22 14:49:54 -08:00
Doris Wu adcdbb45f9 fix typo (#9625) 2026-01-22 14:49:38 -08:00
Mathias Agopian cff958587d initialize VirtualMachineEnv on the right thread (#9624)
PerformanceHint manager needs a java thread during initialization,
so we need to attach a jvm to the thread that's going to be used.
That thread is the filament backend thread, not necessarily the thread 
the platform is created on.

So we make sure to do that from the backend thread.


FIXES=[427945768]
2026-01-22 10:24:39 -08:00
Mathias Agopian 9ab2326f15 some work in progress sample 2026-01-22 00:50:35 -08:00
Anish Goyal 8785acd352 Fix push constants with ext sampler (VK) (#9445)
Currently, in Vulkan, we set the pipeline layout when binding the
pipeline, which doesn't happen until draw time when external samplers
are present. In cases like that, we push constants with the layout
VK_NULL_HANDLE, which is not valid. Instead, we can wait until the
pipeline layout is known, and then flush all push constants.
2026-01-22 08:15:20 +00:00
Mathias Agopian 57767b7f27 Fix buffer overflow in RenderPass command batching (#9619)
The command batching logic in RenderPass::Executor::execute calculates a
conservative `maxCommandSizeInBytes` to determine how many commands can
safely fit in the driver's circular buffer.

However, this calculation failed to account for the dynamic memory
allocation incurred by the Per-Material descriptor set binding (via
`MaterialInstance::use`). This binding allocates a `DescriptorSetOffsetArray`
in the command stream, which was unbudgeted.

In scenarios with frequent material and scissor changes, the accumulated
deficit from these unbudgeted allocations could exceed the buffer 
capacity, leading to a crash.

This change:
1. Adds the missing accounting for the Per-Material dynamic allocation 
   to `maxCommandSizeInBytes`.
2. Adds a 20% safety margin to the calculated size to robustly handle
   alignment padding and worst-case scenarios.


FIXES=[474264976]
2026-01-21 21:03:49 -08:00
Powei Feng febcfc8f2d fgviewer: add info in panel for render passes (#9618)
- Add plumbing to add more information about rendertargets
   (including discard flags and pass id) to the fgviewer pass
   struct.
 - Add UI to display information about a selected pass
 - Highlight a selected resource or pass
2026-01-22 03:39:20 +00:00
Filament Bot c04c387a89 [automated] Updating /docs due to commit 78ac946
Full commit hash is 78ac9467f8

DOCS_ALLOW_DIRECT_EDITS
2026-01-22 03:05:41 +00:00
Sungun Park 78ac9467f8 Release Filament 1.68.5 2026-01-21 19:01:22 -08:00
Romain Guy 73b60d4db8 Add a new tone mapper (#9614)
The new tone mapper, dubbed "GT7" in the code, is based on
the Gran Turismo 7 tone mapper, as described in the SIGGRAPH
2025 presentation called "Driving Toward Reality: Physically
Based Tone Mapping and Perceptual Fidelity in Gran Turismo 7".

This tone mapper exhibits fewer hue skews than the other tone
mappers, at the exception of PBR Neutral. The GT7 tone mapper
is however better at preserving the perception of high
dynamic range.

The tone mapper, as implemented, targets an SDR paper white
of 250 nits, using 100 cd/m^2 as the reference luminance (for
values of 1.0 in the linear HDR framebuffer). This can result
in an overall darker image compared to the other tone mappers
but this can be controlled through camera settings, post-
processing exposure, or lighting intensity.

The GT7 tone mapper also allows to target HDR output, and
could be made customizable via APIs if desired. The current
implementation offers a fixed aesthetic solution.
2026-01-21 17:31:27 -08:00
Ben Doherty 7266fd67db Add filament-tools subproject (#9622) 2026-01-21 15:22:39 -08:00
Filament Bot 469f2c94da [automated] Updating /docs due to commit d4e0d05
Full commit hash is d4e0d051b1

DOCS_ALLOW_DIRECT_EDITS
2026-01-21 22:26:09 +00:00
Powei Feng d4e0d051b1 docs: make docs more consistent and complete (#9620)
- Add documentation for specgen along with proper math rendering
- Adjust the heading size, capitalization of various READMEs.
- Add backend test README to the doc
- Rename the CI related tests to have prefix "CI:"
2026-01-21 22:22:12 +00:00
Ben Doherty df74a5a352 Re-enable iOS filamat builds (#9598) 2026-01-21 19:30:00 +00:00
Doris Wu 21fd32266a Add support for custom morphing (#9550)
BUGS=[443321184]
2026-01-21 17:11:35 +08:00
Sungun Park cd7c7b369b Add helloasync sample (#9617)
Introduces a new sample application demonstrating asynchronous resource
management. It shows how to asynchronously create and update Filament
resources (Textures, VertexBuffers, IndexBuffers) and provides examples
of managing chains of asynchronous operations effectively.

BUGS=[442921995]
2026-01-21 00:38:08 +00:00
Powei Feng 65ed7099c0 fgviewer: UI updates (#9615)
- Pick a default view when database is filled.
 - Fix d3, d3-graphviz depdency.
 - reduce the size into a smaller table (to avoid scrolling)
 - slant the pass items to allow smaller cells for the table.
 - make subresource rendering the same as regular resource
   rendering with a color hint.
 - Use color-only to indicate resource/pass interaction (read,
   write, read-write, no-access)
 - add tooltip to indicate resource action
2026-01-20 21:02:23 +00:00
rafadevai af34572883 VK: Add support for VK_KHR_global_priority (#9607)
Add support for application that want to change the
priority of GPU queues at a system level.
2026-01-20 19:53:55 +00:00
Anish Goyal 1f6a67b2a2 Fix race condition for cache prewarming (#9613)
Right now, if a material is destroyed just after creation, it's possible
for prewarm to attempt to use destroyed shader modules. This ensures
that the resources are kept until after any queued jobs have run.

While it is likely ideal to properly cancel the prewarm if the program
has been destroyed, this seems like a rare occurrence in practice.
2026-01-20 18:04:34 +00:00
Sungun Park 7701e7a65a Handle deferred destruction for asynchronous objects (#9604)
When an asynchronous object is destroyed immediately after creation, the
background creation process may still be active, holding a reference to
the object. We now defer destruction in these cases until the creation
process completes to ensure the reference remains valid.

Implementation details:
1. Add a mechanism to stop the ServiceThread to drain pending
   asynchronous tasks earlier. This ensures the mCreationComplete flag
   in the frontend object is set to true.
2. Later, the engine's terminate performs garbage collection for the
   deferred destruction. All of them must be successfully processed.

BUGS=[442921995]
2026-01-17 02:44:28 +00:00
Filament Bot 65a91832a8 [automated] Updating /docs due to commit acb47ff
Full commit hash is acb47ffed5

DOCS_ALLOW_DIRECT_EDITS
2026-01-16 00:26:35 +00:00
122 changed files with 9594 additions and 618 deletions
+18 -18
View File
@@ -1,6 +1,6 @@
## Building Filament # Building Filament
### Prerequisites ## Prerequisites
To build Filament, you must first install the following tools: To build Filament, you must first install the following tools:
@@ -18,7 +18,7 @@ To build Filament for Android you must also install the following:
- Android NDK 25.1 or higher - Android NDK 25.1 or higher
- Java 17 - Java 17
### Environment variables ## Environment variables
To build Filament for Android, make sure the environment variable `ANDROID_HOME` points to the To build Filament for Android, make sure the environment variable `ANDROID_HOME` points to the
location of your Android SDK. location of your Android SDK.
@@ -30,7 +30,7 @@ When building for WebGL, you'll also need to set `EMSDK`. See [WebAssembly](#web
We recommend using CLion to develop for Filament. Simply open the root directory's CMakeLists.txt We recommend using CLion to develop for Filament. Simply open the root directory's CMakeLists.txt
in CLion to obtain a usable project. in CLion to obtain a usable project.
### Easy build ## Easy build
Once the required OS specific dependencies listed below are installed, you can use the script Once the required OS specific dependencies listed below are installed, you can use the script
located in `build.sh` to build Filament easily on macOS and Linux. located in `build.sh` to build Filament easily on macOS and Linux.
@@ -67,7 +67,7 @@ For more specialized options, please also consider the following pages:
- `-t`: [`fgviewer`](https://google.github.io/filament/dup/fgviewer.html) - `-t`: [`fgviewer`](https://google.github.io/filament/dup/fgviewer.html)
- `-b` and `-y`: [ASAN/UBSAN builds](https://google.github.io/filament/notes/asan_ubsan.html) - `-b` and `-y`: [ASAN/UBSAN builds](https://google.github.io/filament/notes/asan_ubsan.html)
### Filament-specific CMake Options ## Filament-specific CMake Options
The following CMake options are boolean options specific to Filament: The following CMake options are boolean options specific to Filament:
@@ -89,7 +89,7 @@ cmake . -DOPTION=ON # Replace OPTION with the option name, set to ON / OFF
Options can also be set with the CMake GUI. Options can also be set with the CMake GUI.
### Linux ## Linux
Make sure you've installed the following dependencies: Make sure you've installed the following dependencies:
@@ -148,7 +148,7 @@ ninja
This will build Filament, its tests and samples, and various host tools. This will build Filament, its tests and samples, and various host tools.
### macOS ## macOS
To compile Filament you must have the most recent version of Xcode installed and you need to To compile Filament you must have the most recent version of Xcode installed and you need to
make sure the command line tools are setup by running: make sure the command line tools are setup by running:
@@ -169,7 +169,7 @@ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/fila
ninja ninja
``` ```
### iOS ## iOS
The easiest way to build Filament for iOS is to use `build.sh` and the The easiest way to build Filament for iOS is to use `build.sh` and the
`-p ios` flag. For instance to build the debug target: `-p ios` flag. For instance to build the debug target:
@@ -180,9 +180,9 @@ The easiest way to build Filament for iOS is to use `build.sh` and the
See [ios/samples/README.md](./ios/samples/README.md) for more information. See [ios/samples/README.md](./ios/samples/README.md) for more information.
### Windows ## Windows
#### Building on Windows with Visual Studio 2019 or later ### Building on Windows with Visual Studio 2019 or later
Install the following components: Install the following components:
@@ -225,7 +225,7 @@ You can also use CMake to invoke the build without opening Visual Studio. For ex
cmake --build . --target gltf_viewer --config Release cmake --build . --target gltf_viewer --config Release
``` ```
### Android ## Android
Before building Filament for Android, make sure to build Filament for your host. Some of the Before building Filament for Android, make sure to build Filament for your host. Some of the
host tools are required to successfully build for Android. host tools are required to successfully build for Android.
@@ -242,13 +242,13 @@ foremost for `arm64-v8a`.
To build Android on Windows machines, see [android/Windows.md](android/Windows.md). To build Android on Windows machines, see [android/Windows.md](android/Windows.md).
#### Important: SDK location ### Important: SDK location
Either ensure your `ANDROID_HOME` environment variable is set or make sure the root project Either ensure your `ANDROID_HOME` environment variable is set or make sure the root project
contains a `local.properties` file with the `sdk.dir` property pointing to your installation of contains a `local.properties` file with the `sdk.dir` property pointing to your installation of
the Android SDK. the Android SDK.
#### Easy Android build ### Easy Android build
The easiest way to build Filament for Android is to use `build.sh` and the The easiest way to build Filament for Android is to use `build.sh` and the
`-p android` flag. For instance to build the release target: `-p android` flag. For instance to build the release target:
@@ -266,7 +266,7 @@ The output APK can be found in `android/samples/sample-hello-triangle/build/outp
Run `build.sh -h` for more information. Run `build.sh -h` for more information.
#### Android Studio ### Android Studio
You must use the latest stable release of Android Studio. You must use the latest stable release of Android Studio.
@@ -296,7 +296,7 @@ device's architecture. So if you are targeting a new Pixel phone, make sure that
an emulator on a Linux machine with an x86 64-bit chipset, you would indicate (`-q x86_64`) in the above step. an emulator on a Linux machine with an x86 64-bit chipset, you would indicate (`-q x86_64`) in the above step.
#### Manual builds ### Manual builds
Invoke CMake in a build directory of your choice, inside of filament's directory. The commands Invoke CMake in a build directory of your choice, inside of filament's directory. The commands
below show how to build Filament for ARM 64-bit (`aarch64`). below show how to build Filament for ARM 64-bit (`aarch64`).
@@ -324,7 +324,7 @@ This will generate Filament's Android binaries in `out/android-release`. This lo
to build the Android Studio projects located in `filament/android`. After install, the library to build the Android Studio projects located in `filament/android`. After install, the library
binaries should be found in `out/android-release/filament/lib/arm64-v8a`. binaries should be found in `out/android-release/filament/lib/arm64-v8a`.
#### AAR ### AAR
Before you attempt to build the AAR, make sure you've compiled and installed the native libraries Before you attempt to build the AAR, make sure you've compiled and installed the native libraries
as explained in the sections above. You must have the following ABIs built in as explained in the sections above. You must have the following ABIs built in
@@ -356,7 +356,7 @@ Alternatively you can build the AAR from the command line by executing the follo
The `-Pcom.google.android.filament.dist-dir` can be used to specify a different installation 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). directory (it must match the CMake install prefix used in the previous steps).
#### Using Filament's AAR ### Using Filament's AAR
Create a new module in your project and select _Import .JAR or .AAR Package_ when prompted. Make Create a new module in your project and select _Import .JAR or .AAR Package_ when prompted. Make
sure to add the newly created module as a dependency to your application. sure to add the newly created module as a dependency to your application.
@@ -397,7 +397,7 @@ productFlavors {
} }
``` ```
### WebAssembly ## WebAssembly
The core Filament library can be cross-compiled to WebAssembly from either macOS or Linux. To get The core Filament library can be cross-compiled to WebAssembly from either macOS or Linux. To get
started, follow the instructions for building Filament on your platform ([macOS](#macos) or started, follow the instructions for building Filament on your platform ([macOS](#macos) or
+2 -1
View File
@@ -10,6 +10,7 @@ cmake_minimum_required(VERSION 3.22.1)
# ================================================================================================== # ==================================================================================================
# Toolchain configuration # Toolchain configuration
# ================================================================================================== # ==================================================================================================
# On iOS, the deployment target is set inside third_party/clang/ios.cmake
if (APPLE AND NOT IOS) if (APPLE AND NOT IOS)
# This must be set before project() is called # This must be set before project() is called
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "") set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "")
@@ -586,7 +587,7 @@ if (FILAMENT_SUPPORTS_METAL)
endif() endif()
# Building filamat increases build times and isn't required for web, so turn it off by default. # Building filamat increases build times and isn't required for web, so turn it off by default.
if (NOT WEBGL AND NOT IOS) if (NOT WEBGL)
option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" ON) option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" ON)
else() else()
option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" OFF) option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" OFF)
+4
View File
@@ -7,6 +7,10 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.69.0
- engine: Support custom attributes morphing, and allow for omitting position and/or normal data. [⚠️ **Recompile Materials**]
## v1.68.5 ## v1.68.5
- engine: "native" Streams are officially deprecated. Use "acquired" streams instead. - engine: "native" Streams are officially deprecated. Use "acquired" streams instead.
+1 -1
View File
@@ -194,7 +194,7 @@ subprojects {
google() google()
} }
if (!name.startsWith("sample")) { if (!name.startsWith("sample") && name != "filament-tools") {
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
@@ -18,6 +18,7 @@
#include <filament/Camera.h> #include <filament/Camera.h>
#include <filament/Engine.h> #include <filament/Engine.h>
#include <filament/MorphTargetBuffer.h>
#include <utils/Entity.h> #include <utils/Entity.h>
#include <utils/EntityManager.h> #include <utils/EntityManager.h>
@@ -207,6 +208,14 @@ Java_com_google_android_filament_Engine_nDestroySkinningBuffer(JNIEnv*, jclass,
return engine->destroy(skinningBuffer); return engine->destroy(skinningBuffer);
} }
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nDestroyMorphTargetBuffer(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeMorphTargetBuffer) {
Engine* engine = (Engine*) nativeEngine;
MorphTargetBuffer* mtb = (MorphTargetBuffer*) nativeMorphTargetBuffer;
return engine->destroy(mtb);
}
extern "C" JNIEXPORT jboolean JNICALL extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nDestroyIndirectLight(JNIEnv*, jclass, Java_com_google_android_filament_Engine_nDestroyIndirectLight(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeIndirectLight) { jlong nativeEngine, jlong nativeIndirectLight) {
@@ -328,6 +337,13 @@ Java_com_google_android_filament_Engine_nIsValidSkinningBuffer(JNIEnv*, jclass,
return (jboolean)engine->isValid((SkinningBuffer*)nativeSkinningBuffer); return (jboolean)engine->isValid((SkinningBuffer*)nativeSkinningBuffer);
} }
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nIsValidMorphTargetBuffer(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeMorphTargetBuffer) {
Engine* engine = (Engine*) nativeEngine;
return (jboolean) engine->isValid((MorphTargetBuffer*) nativeMorphTargetBuffer);
}
extern "C" JNIEXPORT jboolean JNICALL extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nIsValidIndirectLight(JNIEnv*, jclass, Java_com_google_android_filament_Engine_nIsValidIndirectLight(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeIndirectLight) { jlong nativeEngine, jlong nativeIndirectLight) {
@@ -58,6 +58,27 @@ Java_com_google_android_filament_MorphTargetBuffer_nBuilderCount(JNIEnv*, jclass
builder->count((size_t) count); builder->count((size_t) count);
} }
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nBuilderWithPositions(JNIEnv*, jclass,
jlong nativeBuilder, jboolean enabled) {
MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder;
builder->withPositions(enabled);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nBuilderWithTangents(JNIEnv*, jclass,
jlong nativeBuilder, jboolean enabled) {
MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder;
builder->withTangents(enabled);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nBuilderEnableCustomMorphing(JNIEnv*,
jclass, jlong nativeBuilder, jboolean enabled) {
MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder;
builder->enableCustomMorphing(enabled);
}
extern "C" extern "C"
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nBuilderBuild(JNIEnv*, jclass, Java_com_google_android_filament_MorphTargetBuffer_nBuilderBuild(JNIEnv*, jclass,
@@ -112,3 +133,24 @@ Java_com_google_android_filament_MorphTargetBuffer_nGetCount(JNIEnv*, jclass,
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject; MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject;
return (jint)morphTargetBuffer->getCount(); return (jint)morphTargetBuffer->getCount();
} }
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nHasPositions(JNIEnv*, jclass,
jlong nativeObject) {
MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject;
return (jboolean) morphTargetBuffer->hasPositions();
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nHasTangents(JNIEnv*, jclass,
jlong nativeObject) {
MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject;
return (jboolean) morphTargetBuffer->hasTangents();
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nIsCustomMorphingEnabled(JNIEnv*, jclass,
jlong nativeObject) {
MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject;
return (jboolean) morphTargetBuffer->isCustomMorphingEnabled();
}
@@ -52,6 +52,11 @@ Java_com_google_android_filament_ToneMapper_nCreatePBRNeutralToneMapper(JNIEnv*,
return (jlong) new PBRNeutralToneMapper(); return (jlong) new PBRNeutralToneMapper();
} }
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_ToneMapper_nCreateGT7ToneMapper(JNIEnv*, jclass) {
return (jlong) new GT7ToneMapper();
}
extern "C" JNIEXPORT jlong JNICALL extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_ToneMapper_nCreateAgxToneMapper(JNIEnv*, jclass, jint look) { Java_com_google_android_filament_ToneMapper_nCreateAgxToneMapper(JNIEnv*, jclass, jint look) {
return (jlong) new AgxToneMapper(AgxToneMapper::AgxLook(look)); return (jlong) new AgxToneMapper(AgxToneMapper::AgxLook(look));
@@ -939,6 +939,15 @@ public class Engine {
return nIsValidSkinningBuffer(getNativeObject(), object.getNativeObject()); return nIsValidSkinningBuffer(getNativeObject(), object.getNativeObject());
} }
/**
* Returns whether the object is valid.
* @param object Object to check for validity
* @return returns true if the specified object is valid.
*/
public boolean isValidMorphTargetBuffer(@NonNull MorphTargetBuffer object) {
return nIsValidMorphTargetBuffer(getNativeObject(), object.getNativeObject());
}
/** /**
* Returns whether the object is valid. * Returns whether the object is valid.
* @param object Object to check for validity * @param object Object to check for validity
@@ -1192,6 +1201,15 @@ public class Engine {
skinningBuffer.clearNativeObject(); skinningBuffer.clearNativeObject();
} }
/**
* Destroys a {@link MorphTargetBuffer} and frees all its associated resources.
* @param morphTargetBuffer the {@link MorphTargetBuffer} to destroy
*/
public void destroyMorphTargetBuffer(@NonNull MorphTargetBuffer morphTargetBuffer) {
assertDestroy(nDestroyMorphTargetBuffer(getNativeObject(), morphTargetBuffer.getNativeObject()));
morphTargetBuffer.clearNativeObject();
}
/** /**
* Destroys a {@link IndirectLight} and frees all its associated resources. * Destroys a {@link IndirectLight} and frees all its associated resources.
* @param ibl the {@link IndirectLight} to destroy * @param ibl the {@link IndirectLight} to destroy
@@ -1483,6 +1501,7 @@ public class Engine {
private static native boolean nDestroyIndexBuffer(long nativeEngine, long nativeIndexBuffer); private static native boolean nDestroyIndexBuffer(long nativeEngine, long nativeIndexBuffer);
private static native boolean nDestroyVertexBuffer(long nativeEngine, long nativeVertexBuffer); private static native boolean nDestroyVertexBuffer(long nativeEngine, long nativeVertexBuffer);
private static native boolean nDestroySkinningBuffer(long nativeEngine, long nativeSkinningBuffer); private static native boolean nDestroySkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
private static native boolean nDestroyMorphTargetBuffer(long nativeEngine, long nativeMorphTargetBuffer);
private static native boolean nDestroyIndirectLight(long nativeEngine, long nativeIndirectLight); private static native boolean nDestroyIndirectLight(long nativeEngine, long nativeIndirectLight);
private static native boolean nDestroyMaterial(long nativeEngine, long nativeMaterial); private static native boolean nDestroyMaterial(long nativeEngine, long nativeMaterial);
private static native boolean nDestroyMaterialInstance(long nativeEngine, long nativeMaterialInstance); private static native boolean nDestroyMaterialInstance(long nativeEngine, long nativeMaterialInstance);
@@ -1499,6 +1518,7 @@ public class Engine {
private static native boolean nIsValidIndexBuffer(long nativeEngine, long nativeIndexBuffer); private static native boolean nIsValidIndexBuffer(long nativeEngine, long nativeIndexBuffer);
private static native boolean nIsValidVertexBuffer(long nativeEngine, long nativeVertexBuffer); private static native boolean nIsValidVertexBuffer(long nativeEngine, long nativeVertexBuffer);
private static native boolean nIsValidSkinningBuffer(long nativeEngine, long nativeSkinningBuffer); private static native boolean nIsValidSkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
private static native boolean nIsValidMorphTargetBuffer(long nativeEngine, long nativeMorphTargetBuffer);
private static native boolean nIsValidIndirectLight(long nativeEngine, long nativeIndirectLight); private static native boolean nIsValidIndirectLight(long nativeEngine, long nativeIndirectLight);
private static native boolean nIsValidMaterial(long nativeEngine, long nativeMaterial); private static native boolean nIsValidMaterial(long nativeEngine, long nativeMaterial);
private static native boolean nIsValidMaterialInstance(long nativeEngine, long nativeMaterial, long nativeMaterialInstance); private static native boolean nIsValidMaterialInstance(long nativeEngine, long nativeMaterial, long nativeMaterialInstance);
@@ -64,6 +64,45 @@ public class MorphTargetBuffer {
return this; return this;
} }
/**
* Use this method to enable or disable the built-in position morphing buffer.
* Default is true.
*
* @param enabled true to enable, false to disable
* @return this <code>Builder</code> object for chaining calls
*/
@NonNull
public Builder withPositions(boolean enabled) {
nBuilderWithPositions(mNativeBuilder, enabled);
return this;
}
/**
* Use this method to enable or disable the built-in tangent morphing buffer.
* Default is true.
*
* @param enabled true to enable, false to disable
* @return this <code>Builder</code> object for chaining calls
*/
@NonNull
public Builder withTangents(boolean enabled) {
nBuilderWithTangents(mNativeBuilder, enabled);
return this;
}
/**
* Use this method to enable or disable custom morphing.
* Default is false.
*
* @param enabled true to enable, false to disable
* @return this <code>Builder</code> object for chaining calls
*/
@NonNull
public Builder enableCustomMorphing(boolean enabled) {
nBuilderEnableCustomMorphing(mNativeBuilder, enabled);
return this;
}
/** /**
* Creates and returns the <code>MorphTargetBuffer</code> object. * Creates and returns the <code>MorphTargetBuffer</code> object.
* *
@@ -156,6 +195,27 @@ public class MorphTargetBuffer {
return nGetCount(mNativeObject); return nGetCount(mNativeObject);
} }
/**
* @return true if this MorphTargetBuffer has a position buffer.
*/
public boolean hasPositions() {
return nHasPositions(mNativeObject);
}
/**
* @return true if this MorphTargetBuffer has a tangent buffer.
*/
public boolean hasTangents() {
return nHasTangents(mNativeObject);
}
/**
* @return true if custom morphing is enabled.
*/
public boolean isCustomMorphingEnabled() {
return nIsCustomMorphingEnabled(mNativeObject);
}
public long getNativeObject() { public long getNativeObject() {
if (mNativeObject == 0) { if (mNativeObject == 0) {
throw new IllegalStateException("Calling method on destroyed MorphTargetBuffer"); throw new IllegalStateException("Calling method on destroyed MorphTargetBuffer");
@@ -171,10 +231,16 @@ public class MorphTargetBuffer {
private static native void nDestroyBuilder(long nativeBuilder); private static native void nDestroyBuilder(long nativeBuilder);
private static native void nBuilderVertexCount(long nativeBuilder, int vertexCount); private static native void nBuilderVertexCount(long nativeBuilder, int vertexCount);
private static native void nBuilderCount(long nativeBuilder, int count); private static native void nBuilderCount(long nativeBuilder, int count);
private static native void nBuilderWithPositions(long nativeBuilder, boolean enabled);
private static native void nBuilderWithTangents(long nativeBuilder, boolean enabled);
private static native void nBuilderEnableCustomMorphing(long nativeBuilder, boolean enabled);
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine); private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);
private static native int nSetPositionsAt(long nativeObject, long nativeEngine, int targetIndex, float[] positions, int count); private static native int nSetPositionsAt(long nativeObject, long nativeEngine, int targetIndex, float[] positions, int count);
private static native int nSetTangentsAt(long nativeObject, long nativeEngine, int targetIndex, short[] tangents, int count); private static native int nSetTangentsAt(long nativeObject, long nativeEngine, int targetIndex, short[] tangents, int count);
private static native int nGetVertexCount(long nativeObject); private static native int nGetVertexCount(long nativeObject);
private static native int nGetCount(long nativeObject); private static native int nGetCount(long nativeObject);
private static native boolean nHasPositions(long nativeObject);
private static native boolean nHasTangents(long nativeObject);
private static native boolean nIsCustomMorphingEnabled(long nativeObject);
} }
@@ -24,6 +24,7 @@ package com.google.android.filament;
* <li>ACESLegacyToneMapper</li> * <li>ACESLegacyToneMapper</li>
* <li>FilmicToneMapper</li> * <li>FilmicToneMapper</li>
* <li>PBRNeutralToneMapper</li> * <li>PBRNeutralToneMapper</li>
* <li>GT7ToneMapper</li>
* </ul> * </ul>
* <li>Debug/validation tone mapping operators</li> * <li>Debug/validation tone mapping operators</li>
* <ul> * <ul>
@@ -111,6 +112,19 @@ public class ToneMapper {
} }
} }
/**
* Gran Turismo 7 tone mapping operator. This tone mapper was designed
* to preserve the appearance of materials across lighting conditions while
* avoiding artifacts in the highlights in high dynamic range conditions.
* This tone mapper targets an SDR paper white value of 250 nits, with a
* reference luminance of 100 cd/m^2 (a value of 1.0 in the HDR framebuffer).
*/
public static class GT7ToneMapper extends ToneMapper {
public GT7ToneMapper() {
super(nCreateGT7ToneMapper());
}
}
/** /**
* AgX tone mapping operator. * AgX tone mapping operator.
*/ */
@@ -244,6 +258,7 @@ public class ToneMapper {
private static native long nCreateACESLegacyToneMapper(); private static native long nCreateACESLegacyToneMapper();
private static native long nCreateFilmicToneMapper(); private static native long nCreateFilmicToneMapper();
private static native long nCreatePBRNeutralToneMapper(); private static native long nCreatePBRNeutralToneMapper();
private static native long nCreateGT7ToneMapper();
private static native long nCreateAgxToneMapper(int look); private static native long nCreateAgxToneMapper(int look);
private static native long nCreateGenericToneMapper( private static native long nCreateGenericToneMapper(
float contrast, float midGrayIn, float midGrayOut, float hdrMax); float contrast, float midGrayIn, float midGrayOut, float hdrMax);
+12
View File
@@ -0,0 +1,12 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
/.idea/caches
/.idea/gradle.xml
.DS_Store
/build
/captures
.externalNativeBuild
/.cxx
+64
View File
@@ -0,0 +1,64 @@
plugins {
id "de.undercouch.download" version "5.6.0"
}
apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
def tools = ['matc', 'cmgen']
def platforms = [
'mac': [classifier: 'osx-aarch64', archive: "filament-v${VERSION_NAME}-mac.tgz", path: { t -> "filament/bin/${t}" }],
'linux': [classifier: 'linux-x86_64', archive: "filament-v${VERSION_NAME}-linux.tgz", path: { t -> "filament/bin/${t}" }],
'windows': [classifier: 'windows-x86_64', archive: "filament-v${VERSION_NAME}-windows.tgz", path: { t -> "bin/${t}.exe" }]
]
platforms.each { platform, config ->
def platformName = platform.capitalize()
def remoteUrl = "https://github.com/google/filament/releases/download/v${VERSION_NAME}/${config.archive}"
def downloadFile = file("${buildDir}/downloads/${config.archive}")
def extractDir = file("${buildDir}/extracted/filament-v${VERSION_NAME}-${platform}")
task "downloadRelease${platformName}"(type: Download) {
src remoteUrl
dest downloadFile
overwrite false
}
def extractionTask = task "extractTools${platformName}"(dependsOn: "downloadRelease${platformName}", type: Copy) {
group = "setup"
from tarTree(resources.gzip(downloadFile))
// Include specific tools based on platform pattern
include tools.collect { tool -> config.path(tool) }
// Flatten the path so it lands directly in 'into'
eachFile { fcd ->
fcd.relativePath = new RelativePath(true, fcd.name)
}
into extractDir
includeEmptyDirs = false
}
}
publishing {
publications {
tools.each { toolName ->
create(toolName, MavenPublication) {
artifactId = toolName
platforms.each { platform, config ->
def extractDir = file("${buildDir}/extracted/filament-v${VERSION_NAME}-${platform}")
def archivePath = config.path(toolName)
def exeName = new File(archivePath).name
artifact(new File(extractDir, exeName)) {
classifier = config.classifier
extension = "exe"
builtBy tasks.named("extractTools${platform.capitalize()}")
}
}
}
}
}
}
+2
View File
@@ -0,0 +1,2 @@
POM_NAME=Filament
POM_PACKAGING=exe
+1
View File
@@ -3,6 +3,7 @@ include ':filament-android'
include ':filamat-android' include ':filamat-android'
include ':gltfio-android' include ':gltfio-android'
include ':filament-utils-android' include ':filament-utils-android'
include ':filament-tools'
// Samples // Samples
include ':samples:sample-gltf-viewer' include ':samples:sample-gltf-viewer'
+255
View File
@@ -0,0 +1,255 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>backend - Filament</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div style="display:flex;align-items:center;justify-content:center">
<img class="flogo" src="../images/filament_logo_small.png"></img>
</div>
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
<!--
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
-->
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Filament</h1>
<div class="right-buttons">
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="backend-unit-tests"><a class="header" href="#backend-unit-tests">Backend Unit Tests</a></h1>
<p>These are tests that ensure the Filament backend is working properly on various operating systems
and graphics backends.</p>
<p>The majority of these tests generate images that are then compared against a known golden image.</p>
<h2 id="running-with-a-specific-graphics-library"><a class="header" href="#running-with-a-specific-graphics-library">Running with a specific graphics library</a></h2>
<p>Run with <code>-a&lt;backend&gt;</code> to run with a specific backend, such as <code>-avulkan</code> for vulkan or <code>-ametal</code>
for metal.</p>
<h2 id="image-comparisons"><a class="header" href="#image-comparisons">Image comparisons</a></h2>
<p>The expected images are stored as PNG files in the <code>expected_images</code> subdirectory of the test source
code. When cmake is run it will copy this directory to the build output creating
<code>images/expected_images</code> inside the same directory as the unit test binary.</p>
<p>The unit tests will then write their resulting images into <code>images/actual_images</code> in order to make
the tests easier to debug.</p>
<p>If an image comparison test fails, it writes a diff image to <code>images/diff_images</code> where each pixel of the diff is white (255,255,255) if the comparison for the corresponding pixel succeeds and black (0,0,0) of the comparison for the corresponding pixel fails.</p>
<h3 id="python-utility-for-updating-golden-images-and-comparing-results"><a class="header" href="#python-utility-for-updating-golden-images-and-comparing-results">Python utility for updating golden images and comparing results</a></h3>
<p>Inside the unit test source code directory there is a python script called
<code>move_actual_images_to_expected.py</code>.
It will display the actual and expected images side-by-side and copy the actual image into the
source tree's <code>expected_images</code> directory if the user approves the change.</p>
<h4 id="directories"><a class="header" href="#directories">Directories</a></h4>
<p>The <code>-r</code> flag is required and should be the directory where the test binary is.</p>
<p>If not running the script from the directory it's in, <code>-s</code> should be the source code's
<code>expected_images</code> directory.</p>
<h4 id="configuring-comparemove-behavior"><a class="header" href="#configuring-comparemove-behavior">Configuring compare/move behavior</a></h4>
<p>The <code>-c</code> flag has the tool display the actual and expected images side-by-side. By default it
does this by opening both with the OS's default behavior. <code>-p</code> can be used to specify a different
terminal program.</p>
<p>The <code>-m</code> flag has the tool move the actual image to overwrite the expected image in the source
tree. If the images are also being compared then the move will only happen if the user approves the
change.</p>
<p>After updating the expected images, cmake will need to be run again to copy them to the binary's
directory. Also, currently some platforms can't load images to compare and so have the hash
hardcoded into the test source code, so for now those need to be updated by running the test and
copying the value manually.</p>
<h4 id="picking-which-tests-to-compare"><a class="header" href="#picking-which-tests-to-compare">Picking which tests to compare</a></h4>
<p>The <code>-x</code> flag causes the tool to check the <code>test_detail.xml</code> file and only process the images who
failed a comparison. <code>--gtest_output=xml</code> needs to be passed to the test binary to generate this
file.</p>
<p>The <code>-t</code> argument takes a list of images to compare, provided as the file name without the <code>.png</code>
suffix.</p>
<h2 id="unsupported-tests"><a class="header" href="#unsupported-tests">Unsupported tests</a></h2>
<p>If a test depends on a feature that is unsupported by the current environment, it will start with
the <code>SKIP_IF</code> macro. This will cause the test to not be run.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../notes/tests.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/test_ci_backend.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../notes/tests.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/test_ci_backend.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>
+2 -1
View File
@@ -158,7 +158,8 @@
<div id="content" class="content"> <div id="content" class="content">
<main> <main>
<h2 id="updating-vulkan-headers"><a class="header" href="#updating-vulkan-headers">Updating Vulkan headers</a></h2> <h1 id="bluevk"><a class="header" href="#bluevk">bluevk</a></h1>
<h2 id="updating-vulkan-headers"><a class="header" href="#updating-vulkan-headers">Updating Vulkan headers</a></h2>
<p>To update the Vulkan headers, perform the following steps.</p> <p>To update the Vulkan headers, perform the following steps.</p>
<p>First, find the latest version of the Vulkan headers here: <p>First, find the latest version of the Vulkan headers here:
https://github.com/KhronosGroup/Vulkan-Headers/tags</p> https://github.com/KhronosGroup/Vulkan-Headers/tags</p>
+18 -18
View File
@@ -158,8 +158,8 @@
<div id="content" class="content"> <div id="content" class="content">
<main> <main>
<h2 id="building-filament"><a class="header" href="#building-filament">Building Filament</a></h2> <h1 id="building-filament"><a class="header" href="#building-filament">Building Filament</a></h1>
<h3 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h3> <h2 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h2>
<p>To build Filament, you must first install the following tools:</p> <p>To build Filament, you must first install the following tools:</p>
<ul> <ul>
<li>CMake 3.22.1 (or more recent)</li> <li>CMake 3.22.1 (or more recent)</li>
@@ -175,14 +175,14 @@ section below.</p>
<li>Android NDK 25.1 or higher</li> <li>Android NDK 25.1 or higher</li>
<li>Java 17</li> <li>Java 17</li>
</ul> </ul>
<h3 id="environment-variables"><a class="header" href="#environment-variables">Environment variables</a></h3> <h2 id="environment-variables"><a class="header" href="#environment-variables">Environment variables</a></h2>
<p>To build Filament for Android, make sure the environment variable <code>ANDROID_HOME</code> points to the <p>To build Filament for Android, make sure the environment variable <code>ANDROID_HOME</code> points to the
location of your Android SDK.</p> location of your Android SDK.</p>
<p>When building for WebGL, you'll also need to set <code>EMSDK</code>. See <a href="#webassembly">WebAssembly</a>.</p> <p>When building for WebGL, you'll also need to set <code>EMSDK</code>. See <a href="#webassembly">WebAssembly</a>.</p>
<h3 id="ide"><a class="header" href="#ide">IDE</a></h3> <h3 id="ide"><a class="header" href="#ide">IDE</a></h3>
<p>We recommend using CLion to develop for Filament. Simply open the root directory's CMakeLists.txt <p>We recommend using CLion to develop for Filament. Simply open the root directory's CMakeLists.txt
in CLion to obtain a usable project.</p> in CLion to obtain a usable project.</p>
<h3 id="easy-build"><a class="header" href="#easy-build">Easy build</a></h3> <h2 id="easy-build"><a class="header" href="#easy-build">Easy build</a></h2>
<p>Once the required OS specific dependencies listed below are installed, you can use the script <p>Once the required OS specific dependencies listed below are installed, you can use the script
located in <code>build.sh</code> to build Filament easily on macOS and Linux.</p> located in <code>build.sh</code> to build Filament easily on macOS and Linux.</p>
<p>This script can be invoked from anywhere and will produce build artifacts in the <code>out/</code> directory <p>This script can be invoked from anywhere and will produce build artifacts in the <code>out/</code> directory
@@ -206,7 +206,7 @@ The script offers more features described by executing <code>build.sh -h</code>.
<li><code>-t</code>: <a href="https://google.github.io/filament/dup/fgviewer.html"><code>fgviewer</code></a></li> <li><code>-t</code>: <a href="https://google.github.io/filament/dup/fgviewer.html"><code>fgviewer</code></a></li>
<li><code>-b</code> and <code>-y</code>: <a href="https://google.github.io/filament/notes/asan_ubsan.html">ASAN/UBSAN builds</a></li> <li><code>-b</code> and <code>-y</code>: <a href="https://google.github.io/filament/notes/asan_ubsan.html">ASAN/UBSAN builds</a></li>
</ul> </ul>
<h3 id="filament-specific-cmake-options"><a class="header" href="#filament-specific-cmake-options">Filament-specific CMake Options</a></h3> <h2 id="filament-specific-cmake-options"><a class="header" href="#filament-specific-cmake-options">Filament-specific CMake Options</a></h2>
<p>The following CMake options are boolean options specific to Filament:</p> <p>The following CMake options are boolean options specific to Filament:</p>
<ul> <ul>
<li><code>FILAMENT_ENABLE_LTO</code>: Enable link-time optimizations if supported by the compiler</li> <li><code>FILAMENT_ENABLE_LTO</code>: Enable link-time optimizations if supported by the compiler</li>
@@ -223,7 +223,7 @@ The script offers more features described by executing <code>build.sh -h</code>.
cmake . -DOPTION=ON # Replace OPTION with the option name, set to ON / OFF cmake . -DOPTION=ON # Replace OPTION with the option name, set to ON / OFF
</code></pre> </code></pre>
<p>Options can also be set with the CMake GUI.</p> <p>Options can also be set with the CMake GUI.</p>
<h3 id="linux"><a class="header" href="#linux">Linux</a></h3> <h2 id="linux"><a class="header" href="#linux">Linux</a></h2>
<p>Make sure you've installed the following dependencies:</p> <p>Make sure you've installed the following dependencies:</p>
<ul> <ul>
<li><code>clang-16</code> or higher</li> <li><code>clang-16</code> or higher</li>
@@ -265,7 +265,7 @@ update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
<pre><code class="language-shell">ninja <pre><code class="language-shell">ninja
</code></pre> </code></pre>
<p>This will build Filament, its tests and samples, and various host tools.</p> <p>This will build Filament, its tests and samples, and various host tools.</p>
<h3 id="macos"><a class="header" href="#macos">macOS</a></h3> <h2 id="macos"><a class="header" href="#macos">macOS</a></h2>
<p>To compile Filament you must have the most recent version of Xcode installed and you need to <p>To compile Filament you must have the most recent version of Xcode installed and you need to
make sure the command line tools are setup by running:</p> make sure the command line tools are setup by running:</p>
<pre><code class="language-shell">xcode-select --install <pre><code class="language-shell">xcode-select --install
@@ -278,14 +278,14 @@ cd out/cmake-release
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../.. cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
ninja ninja
</code></pre> </code></pre>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3> <h2 id="ios"><a class="header" href="#ios">iOS</a></h2>
<p>The easiest way to build Filament for iOS is to use <code>build.sh</code> and the <p>The easiest way to build Filament for iOS is to use <code>build.sh</code> and the
<code>-p ios</code> flag. For instance to build the debug target:</p> <code>-p ios</code> flag. For instance to build the debug target:</p>
<pre><code class="language-shell">./build.sh -p ios debug <pre><code class="language-shell">./build.sh -p ios debug
</code></pre> </code></pre>
<p>See <a href="./ios/samples/README.html">ios/samples/README.md</a> for more information.</p> <p>See <a href="./ios/samples/README.html">ios/samples/README.md</a> for more information.</p>
<h3 id="windows"><a class="header" href="#windows">Windows</a></h3> <h2 id="windows"><a class="header" href="#windows">Windows</a></h2>
<h4 id="building-on-windows-with-visual-studio-2019-or-later"><a class="header" href="#building-on-windows-with-visual-studio-2019-or-later">Building on Windows with Visual Studio 2019 or later</a></h4> <h3 id="building-on-windows-with-visual-studio-2019-or-later"><a class="header" href="#building-on-windows-with-visual-studio-2019-or-later">Building on Windows with Visual Studio 2019 or later</a></h3>
<p>Install the following components:</p> <p>Install the following components:</p>
<ul> <ul>
<li><a href="https://www.visualstudio.com/downloads">Visual Studio 2019 or later</a></li> <li><a href="https://www.visualstudio.com/downloads">Visual Studio 2019 or later</a></li>
@@ -314,7 +314,7 @@ target in the <em>Solution Explorer</em> and choose <em>Build</em> to build a sp
<code>out</code> folder run the following command.</p> <code>out</code> folder run the following command.</p>
<pre><code class="language-bat">cmake --build . --target gltf_viewer --config Release <pre><code class="language-bat">cmake --build . --target gltf_viewer --config Release
</code></pre> </code></pre>
<h3 id="android"><a class="header" href="#android">Android</a></h3> <h2 id="android"><a class="header" href="#android">Android</a></h2>
<p>Before building Filament for Android, make sure to build Filament for your host. Some of the <p>Before building Filament for Android, make sure to build Filament for your host. Some of the
host tools are required to successfully build for Android.</p> host tools are required to successfully build for Android.</p>
<p>Filament can be built for the following architectures:</p> <p>Filament can be built for the following architectures:</p>
@@ -327,11 +327,11 @@ host tools are required to successfully build for Android.</p>
<p>Note that the main target is the ARM 64-bit target. Our implementation is optimized first and <p>Note that the main target is the ARM 64-bit target. Our implementation is optimized first and
foremost for <code>arm64-v8a</code>.</p> foremost for <code>arm64-v8a</code>.</p>
<p>To build Android on Windows machines, see <a href="android/Windows.html">android/Windows.md</a>.</p> <p>To build Android on Windows machines, see <a href="android/Windows.html">android/Windows.md</a>.</p>
<h4 id="important-sdk-location"><a class="header" href="#important-sdk-location">Important: SDK location</a></h4> <h3 id="important-sdk-location"><a class="header" href="#important-sdk-location">Important: SDK location</a></h3>
<p>Either ensure your <code>ANDROID_HOME</code> environment variable is set or make sure the root project <p>Either ensure your <code>ANDROID_HOME</code> environment variable is set or make sure the root project
contains a <code>local.properties</code> file with the <code>sdk.dir</code> property pointing to your installation of contains a <code>local.properties</code> file with the <code>sdk.dir</code> property pointing to your installation of
the Android SDK.</p> the Android SDK.</p>
<h4 id="easy-android-build"><a class="header" href="#easy-android-build">Easy Android build</a></h4> <h3 id="easy-android-build"><a class="header" href="#easy-android-build">Easy Android build</a></h3>
<p>The easiest way to build Filament for Android is to use <code>build.sh</code> and the <p>The easiest way to build Filament for Android is to use <code>build.sh</code> and the
<code>-p android</code> flag. For instance to build the release target:</p> <code>-p android</code> flag. For instance to build the release target:</p>
<pre><code class="language-shell">./build.sh -p android release <pre><code class="language-shell">./build.sh -p android release
@@ -341,7 +341,7 @@ the Android SDK.</p>
</code></pre> </code></pre>
<p>The output APK can be found in <code>android/samples/sample-hello-triangle/build/outputs/apk/release/sample-hello-triangle-release-unsigned.apk</code></p> <p>The output APK can be found in <code>android/samples/sample-hello-triangle/build/outputs/apk/release/sample-hello-triangle-release-unsigned.apk</code></p>
<p>Run <code>build.sh -h</code> for more information.</p> <p>Run <code>build.sh -h</code> for more information.</p>
<h4 id="android-studio"><a class="header" href="#android-studio">Android Studio</a></h4> <h3 id="android-studio"><a class="header" href="#android-studio">Android Studio</a></h3>
<p>You must use the latest stable release of Android Studio.</p> <p>You must use the latest stable release of Android Studio.</p>
<p>The Android build of filament is separated into java/kotlin client APIs, a layer of jni bindings <p>The Android build of filament is separated into java/kotlin client APIs, a layer of jni bindings
that bridges java/kotlin with native code, and Filament and other component code that have been compiled that bridges java/kotlin with native code, and Filament and other component code that have been compiled
@@ -362,7 +362,7 @@ By doing so, Android Studio will automatically try to compile the app only for t
device's architecture. So if you are targeting a new Pixel phone, make sure that the step above device's architecture. So if you are targeting a new Pixel phone, make sure that the step above
(compiling the library) is targeting ARM 64-bit (<code>-q arm64-v8a</code> ), and if you are running the app on (compiling the library) is targeting ARM 64-bit (<code>-q arm64-v8a</code> ), and if you are running the app on
an emulator on a Linux machine with an x86 64-bit chipset, you would indicate (<code>-q x86_64</code>) in the above step.</p> an emulator on a Linux machine with an x86 64-bit chipset, you would indicate (<code>-q x86_64</code>) in the above step.</p>
<h4 id="manual-builds"><a class="header" href="#manual-builds">Manual builds</a></h4> <h3 id="manual-builds"><a class="header" href="#manual-builds">Manual builds</a></h3>
<p>Invoke CMake in a build directory of your choice, inside of filament's directory. The commands <p>Invoke CMake in a build directory of your choice, inside of filament's directory. The commands
below show how to build Filament for ARM 64-bit (<code>aarch64</code>).</p> below show how to build Filament for ARM 64-bit (<code>aarch64</code>).</p>
<pre><code class="language-shell">mkdir out/android-build-release-aarch64 <pre><code class="language-shell">mkdir out/android-build-release-aarch64
@@ -379,7 +379,7 @@ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=../../build/toolchain-aarch64-linux-androi
<p>This will generate Filament's Android binaries in <code>out/android-release</code>. This location is important <p>This will generate Filament's Android binaries in <code>out/android-release</code>. This location is important
to build the Android Studio projects located in <code>filament/android</code>. After install, the library to build the Android Studio projects located in <code>filament/android</code>. After install, the library
binaries should be found in <code>out/android-release/filament/lib/arm64-v8a</code>.</p> binaries should be found in <code>out/android-release/filament/lib/arm64-v8a</code>.</p>
<h4 id="aar"><a class="header" href="#aar">AAR</a></h4> <h3 id="aar"><a class="header" href="#aar">AAR</a></h3>
<p>Before you attempt to build the AAR, make sure you've compiled and installed the native libraries <p>Before you attempt to build the AAR, make sure you've compiled and installed the native libraries
as explained in the sections above. You must have the following ABIs built in as explained in the sections above. You must have the following ABIs built in
<code>out/android-release/filament/lib/</code>:</p> <code>out/android-release/filament/lib/</code>:</p>
@@ -405,7 +405,7 @@ AAR.</p>
</code></pre> </code></pre>
<p>The <code>-Pcom.google.android.filament.dist-dir</code> can be used to specify a different installation <p>The <code>-Pcom.google.android.filament.dist-dir</code> can be used to specify a different installation
directory (it must match the CMake install prefix used in the previous steps).</p> directory (it must match the CMake install prefix used in the previous steps).</p>
<h4 id="using-filaments-aar"><a class="header" href="#using-filaments-aar">Using Filament's AAR</a></h4> <h3 id="using-filaments-aar"><a class="header" href="#using-filaments-aar">Using Filament's AAR</a></h3>
<p>Create a new module in your project and select <em>Import .JAR or .AAR Package</em> when prompted. Make <p>Create a new module in your project and select <em>Import .JAR or .AAR Package</em> when prompted. Make
sure to add the newly created module as a dependency to your application.</p> sure to add the newly created module as a dependency to your application.</p>
<p>If you do not wish to include all supported ABIs, make sure to create the appropriate flavors in <p>If you do not wish to include all supported ABIs, make sure to create the appropriate flavors in
@@ -441,7 +441,7 @@ productFlavors {
} }
} }
</code></pre> </code></pre>
<h3 id="webassembly"><a class="header" href="#webassembly">WebAssembly</a></h3> <h2 id="webassembly"><a class="header" href="#webassembly">WebAssembly</a></h2>
<p>The core Filament library can be cross-compiled to WebAssembly from either macOS or Linux. To get <p>The core Filament library can be cross-compiled to WebAssembly from either macOS or Linux. To get
started, follow the instructions for building Filament on your platform (<a href="#macos">macOS</a> or started, follow the instructions for building Filament on your platform (<a href="#macos">macOS</a> or
<a href="#linux">linux</a>), which will ensure you have the proper dependencies installed.</p> <a href="#linux">linux</a>), which will ensure you have the proper dependencies installed.</p>
+3 -3
View File
@@ -158,7 +158,7 @@
<div id="content" class="content"> <div id="content" class="content">
<main> <main>
<h1 id="filamesh"><a class="header" href="#filamesh">Filamesh</a></h1> <h1 id="filamesh"><a class="header" href="#filamesh">filamesh</a></h1>
<p><code>filamesh</code> converts any mesh file supported by <code>assimp</code> (as configured in this source tree) into a <p><code>filamesh</code> converts any mesh file supported by <code>assimp</code> (as configured in this source tree) into a
custom binary file format. The goal of this binary file format is to allow test applications to custom binary file format. The goal of this binary file format is to allow test applications to
easily and quickly load meshes.</p> easily and quickly load meshes.</p>
@@ -419,7 +419,7 @@ Mesh loadMeshFromFile(filament::Engine* engine, const utils::Path&amp; path,
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/normal_blending.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/matinfo.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
@@ -433,7 +433,7 @@ Mesh loadMeshFromFile(filament::Engine* engine, const utils::Path&amp; path,
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/normal_blending.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/matinfo.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
</nav> </nav>
+1 -1
View File
@@ -158,7 +158,7 @@
<div id="content" class="content"> <div id="content" class="content">
<main> <main>
<h1 id="description"><a class="header" href="#description">Description</a></h1> <h1 id="gltfio"><a class="header" href="#gltfio">gltfio</a></h1>
<p><code>gltfio</code> is a loader library that consumes <code>gltf</code> or <code>glb</code> content and produces Filament <p><code>gltfio</code> is a loader library that consumes <code>gltf</code> or <code>glb</code> content and produces Filament
objects. For usage details, see the docstring for <code>AssetLoader</code>.</p> objects. For usage details, see the docstring for <code>AssetLoader</code>.</p>
<p>gltfio has two plug-in interfaces, <code>TextureProvider</code> and <code>MaterialProvider</code>. Filament ships with <p>gltfio has two plug-in interfaces, <code>TextureProvider</code> and <code>MaterialProvider</code>. Filament ships with
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
} }
dependencies { dependencies {
implementation 'com.google.android.filament:filament-android:1.68.3' implementation 'com.google.android.filament:filament-android:1.68.5'
} }
</code></pre> </code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p> <p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div> </div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3> <h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p> <p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.68.3' <pre><code class="language-shell">pod 'Filament', '~&gt; 1.68.5'
</code></pre> </code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2> <h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul> <ul>
+5 -5
View File
@@ -158,7 +158,7 @@
<div id="content" class="content"> <div id="content" class="content">
<main> <main>
<h1 id="matinfo"><a class="header" href="#matinfo">Matinfo</a></h1> <h1 id="matinfo"><a class="header" href="#matinfo">matinfo</a></h1>
<p><code>matinfo</code> lists the content of a compiled material as output by <code>matc</code>. This tool is meant to be <p><code>matinfo</code> lists the content of a compiled material as output by <code>matc</code>. This tool is meant to be
used for debug purpose only.</p> used for debug purpose only.</p>
<h2 id="usage"><a class="header" href="#usage">Usage</a></h2> <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
@@ -169,11 +169,11 @@ used for debug purpose only.</p>
<nav class="nav-wrapper" aria-label="Page navigation"> <nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/mipgen.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/filamesh.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/roughness_prefilter.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/mipgen.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
@@ -183,11 +183,11 @@ used for debug purpose only.</p>
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation"> <nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/mipgen.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/filamesh.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/roughness_prefilter.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/mipgen.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
</nav> </nav>
+4 -4
View File
@@ -169,11 +169,11 @@
<nav class="nav-wrapper" aria-label="Page navigation"> <nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/normal_blending.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/matinfo.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/matinfo.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/normal_blending.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
@@ -183,11 +183,11 @@
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation"> <nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/normal_blending.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/matinfo.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/matinfo.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/normal_blending.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
</nav> </nav>
+4 -4
View File
@@ -167,11 +167,11 @@ correct results (as opposed to common techniques such as linear or overlay blend
<nav class="nav-wrapper" aria-label="Page navigation"> <nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/filamesh.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/mipgen.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/mipgen.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/roughness_prefilter.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
@@ -181,11 +181,11 @@ correct results (as opposed to common techniques such as linear or overlay blend
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation"> <nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/filamesh.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/mipgen.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/mipgen.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/roughness_prefilter.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
</nav> </nav>
+4 -4
View File
@@ -167,11 +167,11 @@ be used to reduce shading aliasing.</p>
<nav class="nav-wrapper" aria-label="Page navigation"> <nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/matinfo.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/normal_blending.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/specular_color.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/specgen.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
@@ -181,11 +181,11 @@ be used to reduce shading aliasing.</p>
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation"> <nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/matinfo.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/normal_blending.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/specular_color.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/specgen.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
</nav> </nav>
+265
View File
@@ -0,0 +1,265 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>specgen - Filament</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div style="display:flex;align-items:center;justify-content:center">
<img class="flogo" src="../images/filament_logo_small.png"></img>
</div>
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
<!--
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
-->
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Filament</h1>
<div class="right-buttons">
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="specgen-spectral-integration-matrix-generator-for-real-time-dispersion"><a class="header" href="#specgen-spectral-integration-matrix-generator-for-real-time-dispersion">SPECGEN: Spectral Integration Matrix Generator for Real-Time Dispersion</a></h1>
<h2 id="theoretical-background"><a class="header" href="#theoretical-background">Theoretical Background</a></h2>
<h3 id="1-spectral-rendering-in-rgb"><a class="header" href="#1-spectral-rendering-in-rgb">1. Spectral Rendering in RGB</a></h3>
<p>Real-time rendering typically operates in a tristimulus color space (like sRGB). However, physical phenomena like dispersion (refraction splitting light by wavelength) are inherently spectral. To simulate this in an RGB pipeline, we approximate the continuous spectral integration using a finite sum of weighted samples.</p>
<p>The fundamental equation for perceived color \(C\) is:</p>
<p>$$ C = \int L(\lambda) \cdot r(\lambda) d\lambda $$</p>
<p>where:</p>
<ul>
<li>\(L(\lambda)\) is the spectral radiance reaching the eye.</li>
<li>\(r(\lambda)\) is the sensor response function (e.g., CIE 1931 \(\bar{x}, \bar{y}, \bar{z}\) matching functions).</li>
</ul>
<h3 id="2-basis-transformation-strategy"><a class="header" href="#2-basis-transformation-strategy">2. Basis Transformation Strategy</a></h3>
<p>We assume the input light is defined in linear sRGB. To process it spectrally:</p>
<ol>
<li>Convert sRGB to CIE XYZ (D65 white point).</li>
<li>Assume the spectral distribution of the light is a sum of impulses or narrow bands weighted by the XYZ components (or simplified directly from sRGB).</li>
<li>Apply spectral effects (like wavelength-dependent refraction).</li>
<li>Integrate back to XYZ using the CIE Color Matching Functions (CMFs).</li>
<li>Convert final XYZ back to sRGB.</li>
</ol>
<p>This tool pre-calculates a set of matrices (\(K_n\)) that combine these steps. For a set of \(N\) sample wavelengths \({ \lambda_0, \dots, \lambda_{N-1} }\), the final color is:</p>
<p>$$ C_{final} = \sum_{n=0}^{N-1} (K_n \cdot C_{input}) $$</p>
<p>Each matrix \(K_n\) represents the contribution of the \(n\)-th spectral sample to the final image, accounting for the conversion to/from XYZ and the spectral weight of that sample.</p>
<p><strong>Derivation of \(K_n\):</strong></p>
<p>$$ K_n = M_{XYZ \to sRGB} \cdot \text{Diag}(W_n) \cdot M_{sRGB \to XYZ} $$</p>
<p>where \(W_n\) is the "spectral weight" vector \((x, y, z)\) for wavelength \(\lambda_n\):</p>
<p>$$ W_n = \text{CMF}(\lambda_n) \cdot \text{weight}_n $$</p>
<h3 id="3-normalization-energy-conservation"><a class="header" href="#3-normalization-energy-conservation">3. Normalization (Energy Conservation)</a></h3>
<p>To ensure that a white input \((1, 1, 1)\) results in a white output \((1, 1, 1)\) when no dispersion occurs (i.e., all samples land on the same pixel), we must normalize the weights.</p>
<p>We require: \(\sum K_n = I\)</p>
<p>This implies:</p>
<p>$$ \sum ( M_{XYZ \to sRGB} \cdot \text{Diag}(W_n) \cdot M_{sRGB \to XYZ} ) = I $$
$$ M_{XYZ \to sRGB} \cdot \sum \text{Diag}(W_n) \cdot M_{sRGB \to XYZ} = I $$
$$ \sum \text{Diag}(W_n) = M_{sRGB \to XYZ} \cdot I \cdot M_{XYZ \to sRGB} $$
$$ \sum \text{Diag}(W_n) = I $$
<em>(since \(M \cdot M^{-1} = I\))</em></p>
<p>Therefore, we normalize \(W_n\) such that:</p>
<p>$$ \sum W_{n,x} = 1.0, \quad \sum W_{n,y} = 1.0, \quad \sum W_{n,z} = 1.0 $$</p>
<p><strong>Note:</strong> We do <strong>NOT</strong> normalize to the D65 white point \((0.95047, 1.0, 1.08883)\). The \(M_{sRGB \to XYZ}\) matrix already handles the conversion from linear sRGB \((1, 1, 1)\) to D65 XYZ. If we normalized \(W_n\) to D65, we would effectively be applying the white point twice, resulting in a tinted image. By normalizing \(\sum W_n\) to \((1, 1, 1)\), we ensure that the energy is conserved through the spectral transformation pipeline.</p>
<p>In practice, we calculate the raw sum of \(W_n\) from the quadrature weights and CMFs, then compute a correction factor:</p>
<p>$$ \text{Correction} = \frac{1.0}{\sum W_{n,raw}} $$
$$ W_{n,final} = W_{n,raw} \cdot \text{Correction} $$</p>
<h3 id="4-dispersion-and-ior-offsets"><a class="header" href="#4-dispersion-and-ior-offsets">4. Dispersion and IOR Offsets</a></h3>
<p>The Index of Refraction (IOR) varies with wavelength. We use the Abbe number (\(V_d\)) to parameterize this variation relative to a base IOR (\(n_D\)) at 589.3nm.</p>
<p><strong>Cauchy Dispersion Model:</strong></p>
<p>$$ n(\lambda) = A + \frac{B}{\lambda^2} $$</p>
<p>Using the definition of Abbe number \(V_d = \frac{n_D - 1}{n_F - n_C}\), we can derive:</p>
<p>$$ n(\lambda) = n_D + \frac{n_D - 1}{V_d} \cdot \text{Offset}(\lambda) $$</p>
<p>Where \(\text{Offset}(\lambda)\) is pre-calculated by this tool:</p>
<p>$$ \text{Offset}(\lambda) = \frac{ \frac{1}{\lambda^2} - \frac{1}{\lambda_D^2} }{ \frac{1}{\lambda_F^2} - \frac{1}{\lambda_C^2} } $$</p>
<p>This allows the shader to compute the specific IOR for each sample efficiently:</p>
<pre><code class="language-glsl">float ior_n = baseIOR + dispersionFactor * offsets[n];
</code></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/roughness_prefilter.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/specular_color.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/roughness_prefilter.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/specular_color.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>
+2 -2
View File
@@ -182,7 +182,7 @@ grazing angles. See Hoffman 2019, "Fresnel Equations Considered Harmful".</p>
<nav class="nav-wrapper" aria-label="Page navigation"> <nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/roughness_prefilter.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/specgen.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
@@ -196,7 +196,7 @@ grazing angles. See Hoffman 2019, "Fresnel Equations Considered Harmful".</p>
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation"> <nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/roughness_prefilter.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/specgen.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
@@ -3,7 +3,7 @@
<head> <head>
<!-- Book generated using mdBook --> <!-- Book generated using mdBook -->
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>backend - Filament</title> <title>CI: backend - Filament</title>
<!-- Custom HTML head --> <!-- Custom HTML head -->
@@ -181,11 +181,11 @@ list (e.g., <code>opengl,vulkan</code>). Defaults to both.</li>
<nav class="nav-wrapper" aria-label="Page navigation"> <nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
<a rel="prev" href="../notes/tests.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/backend_test.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/test_renderdiff.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/test_ci_renderdiff.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
@@ -195,11 +195,11 @@ list (e.g., <code>opengl,vulkan</code>). Defaults to both.</li>
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation"> <nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../notes/tests.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/backend_test.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/test_renderdiff.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/test_ci_renderdiff.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
</nav> </nav>
@@ -3,7 +3,7 @@
<head> <head>
<!-- Book generated using mdBook --> <!-- Book generated using mdBook -->
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>renderdiff - Filament</title> <title>CI: renderdiff - Filament</title>
<!-- Custom HTML head --> <!-- Custom HTML head -->
@@ -293,7 +293,7 @@ the run number is <code>18023632663</code>.</p>
<nav class="nav-wrapper" aria-label="Page navigation"> <nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/test_backend.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/test_ci_backend.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
@@ -307,7 +307,7 @@ the run number is <code>18023632663</code>.</p>
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation"> <nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/test_backend.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/test_ci_backend.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
+1 -5
View File
@@ -158,11 +158,7 @@
<div id="content" class="content"> <div id="content" class="content">
<main> <main>
<ul> <h1 id="uberz"><a class="header" href="#uberz">uberz</a></h1>
<li><a href="#ubershader-archive-files">Ubershader Archive Files</a></li>
<li><a href="#ubershader-spec-files">Ubershader Spec Files</a></li>
</ul>
<h1 id="ubershader-archive-files"><a class="header" href="#ubershader-archive-files">Ubershader Archive Files</a></h1>
<p>An ubershader archive provides a way to bundle up a set of <code>filamat</code> files along with some metadata <p>An ubershader archive provides a way to bundle up a set of <code>filamat</code> files along with some metadata
that conveys which glTF features each material can handle. It is a file that has been compressed that conveys which glTF features each material can handle. It is a file that has been compressed
with <code>zstd</code> and has an <code>.uberz</code> file extension. In uncompressed form, it has the following layout with <code>zstd</code> and has an <code>.uberz</code> file extension. In uncompressed form, it has the following layout
+199 -199
View File
@@ -504,9 +504,9 @@ D_{GGX}(h,\alpha) = \frac{\aa}{\pi ( (\NoH)^2 (\aa - 1) + 1)^2}
\end{equation}$$ \end{equation}$$
</p><p> </p><p>
The GLSL implementation of the NDF, shown in <a href="#listing_speculard">listing&nbsp;1</a>, is simple and efficient. The GLSL implementation of the NDF, shown in <a href="#listing_speculard">listing&nbsp;1</a>, is simple and efficient.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">D_GGX</span>(<span class="hljs-params"><span class="hljs-built_in">float</span> NoH, <span class="hljs-built_in">float</span> roughness</span>)</span> {</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">D_GGX</span><span class="hljs-params">(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> roughness)</span> </span>{</span>
<span class="line"> <span class="hljs-built_in">float</span> a = NoH * roughness;</span> <span class="line"> <span class="hljs-type">float</span> a = NoH * roughness;</span>
<span class="line"> <span class="hljs-built_in">float</span> k = roughness / (<span class="hljs-number">1.0</span> - NoH * NoH + a * a);</span> <span class="line"> <span class="hljs-type">float</span> k = roughness / (<span class="hljs-number">1.0</span> - NoH * NoH + a * a);</span>
<span class="line"> <span class="hljs-keyword">return</span> k * k * (<span class="hljs-number">1.0</span> / PI);</span> <span class="line"> <span class="hljs-keyword">return</span> k * k * (<span class="hljs-number">1.0</span> / PI);</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_speculard">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;1:</b> Implementation of the specular D term in GLSL</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_speculard">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;1:</b> Implementation of the specular D term in GLSL</div></center>
<p> <p>
@@ -590,10 +590,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL \sqrt{(\NoV)^2 (1 - \aa) + \aa} + \NoV \sqrt{(\N
\end{equation}$$ \end{equation}$$
</p><p> </p><p>
The GLSL implementation of the visibility term, shown in <a href="#listing_specularv">listing&nbsp;3</a>, is a bit more expensive than we would like since it requires two <code>sqrt</code> operations. The GLSL implementation of the visibility term, shown in <a href="#listing_specularv">listing&nbsp;3</a>, is a bit more expensive than we would like since it requires two <code>sqrt</code> operations.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoV, <span class="hljs-keyword">float</span> NoL, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> roughness)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">float</span> a2 = roughness * roughness;</span> <span class="line"> <span class="hljs-type">float</span> a2 = roughness * roughness;</span>
<span class="line"> <span class="hljs-keyword">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>(NoV * NoV * (<span class="hljs-number">1.0</span> - a2) + a2);</span> <span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>(NoV * NoV * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
<span class="line"> <span class="hljs-keyword">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>(NoL * NoL * (<span class="hljs-number">1.0</span> - a2) + a2);</span> <span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>(NoL * NoL * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_specularv">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;3:</b> Implementation of the specular V term in GLSL</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_specularv">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;3:</b> Implementation of the specular V term in GLSL</div></center>
<p> <p>
@@ -604,10 +604,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL (\NoV (1 - \alpha) + \alpha) + \NoV (\NoL (1 - \
\end{equation}$$ \end{equation}$$
</p><p> </p><p>
This approximation is mathematically wrong but saves two square root operations and is good enough for real-time mobile applications, as shown in <a href="#listing_approximatedspecularv">listing&nbsp;4</a>. This approximation is mathematically wrong but saves two square root operations and is good enough for real-time mobile applications, as shown in <a href="#listing_approximatedspecularv">listing&nbsp;4</a>.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">V_SmithGGXCorrelatedFast</span>(<span class="hljs-params"><span class="hljs-built_in">float</span> NoV, <span class="hljs-built_in">float</span> NoL, <span class="hljs-built_in">float</span> roughness</span>)</span> {</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">V_SmithGGXCorrelatedFast</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> roughness)</span> </span>{</span>
<span class="line"> <span class="hljs-built_in">float</span> a = roughness;</span> <span class="line"> <span class="hljs-type">float</span> a = roughness;</span>
<span class="line"> <span class="hljs-built_in">float</span> GGXV = NoL * (NoV * (<span class="hljs-number">1.0</span> - a) + a);</span> <span class="line"> <span class="hljs-type">float</span> GGXV = NoL * (NoV * (<span class="hljs-number">1.0</span> - a) + a);</span>
<span class="line"> <span class="hljs-built_in">float</span> GGXL = NoV * (NoL * (<span class="hljs-number">1.0</span> - a) + a);</span> <span class="line"> <span class="hljs-type">float</span> GGXL = NoV * (NoL * (<span class="hljs-number">1.0</span> - a) + a);</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_approximatedspecularv">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;4:</b> Implementation of the approximated specular V term in GLSL</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_approximatedspecularv">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;4:</b> Implementation of the approximated specular V term in GLSL</div></center>
<p> <p>
@@ -659,7 +659,7 @@ $$\begin{equation}
\end{equation}$$ \end{equation}$$
</p><p> </p><p>
In practice, the diffuse reflectance \(\sigma\) is multiplied later, as shown in <a href="#listing_diffusebrdf">listing&nbsp;8</a>. In practice, the diffuse reflectance \(\sigma\) is multiplied later, as shown in <a href="#listing_diffusebrdf">listing&nbsp;8</a>.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">Fd_Lambert</span>(<span class="hljs-params"></span>)</span> {</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">Fd_Lambert</span>()</span> {</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
@@ -680,14 +680,14 @@ Where:
$$\begin{equation} $$\begin{equation}
\fGrazing=0.5 + 2 \cdot \alpha cos^2(\theta_d) \fGrazing=0.5 + 2 \cdot \alpha cos^2(\theta_d)
\end{equation}$$ \end{equation}$$
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">F_Schlick</span><span class="hljs-params">(<span class="hljs-keyword">float</span> u, <span class="hljs-keyword">float</span> f0, <span class="hljs-keyword">float</span> f90)</span> </span>{</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">F_Schlick</span><span class="hljs-params">(<span class="hljs-type">float</span> u, <span class="hljs-type">float</span> f0, <span class="hljs-type">float</span> f90)</span> {</span>
<span class="line"> <span class="hljs-keyword">return</span> f0 + (f90 - f0) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span> <span class="line"> <span class="hljs-keyword">return</span> f0 + (f90 - f0) * pow(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">Fd_Burley</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoV, <span class="hljs-keyword">float</span> NoL, <span class="hljs-keyword">float</span> LoH, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span> <span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">Fd_Burley</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> LoH, <span class="hljs-type">float</span> roughness)</span> {</span>
<span class="line"> <span class="hljs-keyword">float</span> f90 = <span class="hljs-number">0.5</span> + <span class="hljs-number">2.0</span> * roughness * LoH * LoH;</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">f90</span> <span class="hljs-operator">=</span> <span class="hljs-number">0.5</span> + <span class="hljs-number">2.0</span> * roughness * LoH * LoH;</span>
<span class="line"> <span class="hljs-keyword">float</span> lightScatter = F_Schlick(NoL, <span class="hljs-number">1.0</span>, f90);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">lightScatter</span> <span class="hljs-operator">=</span> F_Schlick(NoL, <span class="hljs-number">1.0</span>, f90);</span>
<span class="line"> <span class="hljs-keyword">float</span> viewScatter = F_Schlick(NoV, <span class="hljs-number">1.0</span>, f90);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">viewScatter</span> <span class="hljs-operator">=</span> F_Schlick(NoV, <span class="hljs-number">1.0</span>, f90);</span>
<span class="line"> <span class="hljs-keyword">return</span> lightScatter * viewScatter * (<span class="hljs-number">1.0</span> / PI);</span> <span class="line"> <span class="hljs-keyword">return</span> lightScatter * viewScatter * (<span class="hljs-number">1.0</span> / PI);</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_diffusebrdf">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;8:</b> Implementation of the diffuse Disney BRDF in GLSL</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_diffusebrdf">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;8:</b> Implementation of the diffuse Disney BRDF in GLSL</div></center>
<p> <p>
@@ -704,47 +704,47 @@ We could allow artists/developers to choose the Disney diffuse BRDF depending on
<strong class="asterisk">Diffuse term</strong>: a Lambertian diffuse model. <strong class="asterisk">Diffuse term</strong>: a Lambertian diffuse model.
</p><p> </p><p>
The full GLSL implementation of the standard model is shown in <a href="#listing_glslbrdf">listing&nbsp;9</a>. The full GLSL implementation of the standard model is shown in <a href="#listing_glslbrdf">listing&nbsp;9</a>.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> D_GGX(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> a) {</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">D_GGX</span><span class="hljs-params">(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> a)</span> {</span>
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</span>
<span class="line"> <span class="hljs-type">float</span> f = (NoH * a2 - NoH) * NoH + <span class="hljs-number">1.0</span>;</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">f</span> <span class="hljs-operator">=</span> (NoH * a2 - NoH) * NoH + <span class="hljs-number">1.0</span>;</span>
<span class="line"> <span class="hljs-keyword">return</span> a2 / (PI * f * f);</span> <span class="line"> <span class="hljs-keyword">return</span> a2 / (PI * f * f);</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-type">vec3</span> F_Schlick(<span class="hljs-type">float</span> u, <span class="hljs-type">vec3</span> f0) {</span> <span class="line">vec3 <span class="hljs-title function_">F_Schlick</span><span class="hljs-params">(<span class="hljs-type">float</span> u, vec3 f0)</span> {</span>
<span class="line"> <span class="hljs-keyword">return</span> f0 + (<span class="hljs-type">vec3</span>(<span class="hljs-number">1.0</span>) - f0) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span> <span class="line"> <span class="hljs-keyword">return</span> f0 + (vec3(<span class="hljs-number">1.0</span>) - f0) * pow(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-type">float</span> V_SmithGGXCorrelated(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a) {</span> <span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a)</span> {</span>
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</span>
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>((-NoL * a2 + NoL) * NoL + a2);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXL</span> <span class="hljs-operator">=</span> NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);</span>
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>((-NoV * a2 + NoV) * NoV + a2);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXV</span> <span class="hljs-operator">=</span> NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-type">float</span> Fd_Lambert() {</span> <span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">Fd_Lambert</span><span class="hljs-params">()</span> {</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-type">void</span> BRDF(...) {</span> <span class="line"><span class="hljs-keyword">void</span> <span class="hljs-title function_">BRDF</span><span class="hljs-params">(...)</span> {</span>
<span class="line"> <span class="hljs-type">vec3</span> h = <span class="hljs-built_in">normalize</span>(v + l);</span> <span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">h</span> <span class="hljs-operator">=</span> normalize(v + l);</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-type">float</span> NoV = <span class="hljs-built_in">abs</span>(<span class="hljs-built_in">dot</span>(n, v)) + <span class="hljs-number">1e-5</span>;</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoV</span> <span class="hljs-operator">=</span> abs(dot(n, v)) + <span class="hljs-number">1e-5</span>;</span>
<span class="line"> <span class="hljs-type">float</span> NoL = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(n, l), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoL</span> <span class="hljs-operator">=</span> clamp(dot(n, l), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
<span class="line"> <span class="hljs-type">float</span> NoH = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(n, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoH</span> <span class="hljs-operator">=</span> clamp(dot(n, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
<span class="line"> <span class="hljs-type">float</span> LoH = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(l, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">LoH</span> <span class="hljs-operator">=</span> clamp(dot(l, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// perceptually linear roughness to roughness (see parameterization)</span></span> <span class="line"> <span class="hljs-comment">// perceptually linear roughness to roughness (see parameterization)</span></span>
<span class="line"> <span class="hljs-type">float</span> roughness = perceptualRoughness * perceptualRoughness;</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">roughness</span> <span class="hljs-operator">=</span> perceptualRoughness * perceptualRoughness;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-type">float</span> D = D_GGX(NoH, roughness);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">D</span> <span class="hljs-operator">=</span> D_GGX(NoH, roughness);</span>
<span class="line"> <span class="hljs-type">vec3</span> F = F_Schlick(LoH, f0);</span> <span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">F</span> <span class="hljs-operator">=</span> F_Schlick(LoH, f0);</span>
<span class="line"> <span class="hljs-type">float</span> V = V_SmithGGXCorrelated(NoV, NoL, roughness);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">V</span> <span class="hljs-operator">=</span> V_SmithGGXCorrelated(NoV, NoL, roughness);</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// specular BRDF</span></span> <span class="line"> <span class="hljs-comment">// specular BRDF</span></span>
<span class="line"> <span class="hljs-type">vec3</span> Fr = (D * V) * F;</span> <span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">Fr</span> <span class="hljs-operator">=</span> (D * V) * F;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// diffuse BRDF</span></span> <span class="line"> <span class="hljs-comment">// diffuse BRDF</span></span>
<span class="line"> <span class="hljs-type">vec3</span> Fd = diffuseColor * Fd_Lambert();</span> <span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">Fd</span> <span class="hljs-operator">=</span> diffuseColor * Fd_Lambert();</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// apply lighting...</span></span> <span class="line"> <span class="hljs-comment">// apply lighting...</span></span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_glslbrdf">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;9:</b> Evaluation of the BRDF in GLSL</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_glslbrdf">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;9:</b> Evaluation of the BRDF in GLSL</div></center>
@@ -965,7 +965,7 @@ $$\begin{equation}
\end{equation}$$ \end{equation}$$
</p><p> </p><p>
<a href="#listing_fnormal">Listing&nbsp;12</a> shows how \(\fNormal\) is computed for both dielectric and metallic materials. It shows that the color of the specular reflectance is derived from the base color in the metallic case. <a href="#listing_fnormal">Listing&nbsp;12</a> shows how \(\fNormal\) is computed for both dielectric and metallic materials. It shows that the color of the specular reflectance is derived from the base color in the metallic case.
</p><pre class="listing tilde"><code><span class="line">vec3 f0 = 0.16 <span class="hljs-emphasis">* reflectance *</span> reflectance <span class="hljs-emphasis">* (1.0 - metallic) + baseColor *</span> metallic;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fnormal">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;12:</b> Computing \(\fNormal\) for dielectric and metallic materials in GLSL</div></center> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-symbol">vec3</span> <span class="hljs-built_in">f0</span> = <span class="hljs-number">0</span>.<span class="hljs-number">16</span> * reflectance * reflectance * (<span class="hljs-number">1</span>.<span class="hljs-number">0</span> - metallic) + baseColor * metallic<span class="hljs-comment">;</span></span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fnormal">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;12:</b> Computing \(\fNormal\) for dielectric and metallic materials in GLSL</div></center>
<a class="target" name="roughnessremappingandclamping">&nbsp;</a><a class="target" name="materialsystem/parameterization/remapping/roughnessremappingandclamping">&nbsp;</a><a class="target" name="toc4.8.3.3">&nbsp;</a><h4 id="roughness-remapping-and-clamping"><a class="header" href="#roughness-remapping-and-clamping">Roughness remapping and clamping</a></h4> <a class="target" name="roughnessremappingandclamping">&nbsp;</a><a class="target" name="materialsystem/parameterization/remapping/roughnessremappingandclamping">&nbsp;</a><a class="target" name="toc4.8.3.3">&nbsp;</a><h4 id="roughness-remapping-and-clamping"><a class="header" href="#roughness-remapping-and-clamping">Roughness remapping and clamping</a></h4>
<p> <p>
<p>The roughness set by the user, called <code>perceptualRoughness</code> here, is remapped to a perceptually linear range using the following formulation:</p> <p>The roughness set by the user, called <code>perceptualRoughness</code> here, is remapped to a perceptually linear range using the following formulation:</p>
@@ -1054,7 +1054,7 @@ V(l,h) = \frac{1}{4(\LoH)^2}
This masking-shadowing function is not physically based, as shown in [<a href="#citation-heitz14">Heitz14</a>], but its simplicity makes it desirable for real-time rendering. This masking-shadowing function is not physically based, as shown in [<a href="#citation-heitz14">Heitz14</a>], but its simplicity makes it desirable for real-time rendering.
</p><p> </p><p>
In summary, our clear coat BRDF is a Cook-Torrance specular microfacet model, with a GGX normal distribution function, a Kelemen visibility function, and a Schlick Fresnel function. <a href="#listing_kelemen">Listing&nbsp;13</a> shows how trivial the GLSL implementation is. In summary, our clear coat BRDF is a Cook-Torrance specular microfacet model, with a GGX normal distribution function, a Kelemen visibility function, and a Schlick Fresnel function. <a href="#listing_kelemen">Listing&nbsp;13</a> shows how trivial the GLSL implementation is.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">V_Kelemen</span>(<span class="hljs-params"><span class="hljs-built_in">float</span> LoH</span>)</span> {</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">V_Kelemen</span><span class="hljs-params">(<span class="hljs-type">float</span> LoH)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.25</span> / (LoH * LoH);</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.25</span> / (LoH * LoH);</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_kelemen">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;13:</b> Implementation of the Kelemen visibility term in GLSL</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_kelemen">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;13:</b> Implementation of the Kelemen visibility term in GLSL</div></center>
<p> <p>
@@ -1097,18 +1097,18 @@ The clear coat roughness parameter is remapped and clamped in a similar way to t
<center><div class="image" style=""><a href="../images/material_clear_coat2.png" target="_blank"><img class="markdeep" src="../images/material_clear_coat2.png" /></a><center><span class="imagecaption"><a class="target" name="figure_clearcoatroughness">&nbsp;</a><b style="font-style:normal;">Figure&nbsp;26:</b> Clear coat roughness varying from 0.0 (left) to 1.0 (right) with metallic set to 1.0, roughness to 0.8 and clear coat to 1.0</span></center></div></center> <center><div class="image" style=""><a href="../images/material_clear_coat2.png" target="_blank"><img class="markdeep" src="../images/material_clear_coat2.png" /></a><center><span class="imagecaption"><a class="target" name="figure_clearcoatroughness">&nbsp;</a><b style="font-style:normal;">Figure&nbsp;26:</b> Clear coat roughness varying from 0.0 (left) to 1.0 (right) with metallic set to 1.0, roughness to 0.8 and clear coat to 1.0</span></center></div></center>
</p><p> </p><p>
<a href="#listing_clearcoatbrdf">Listing&nbsp;14</a> shows the GLSL implementation of the clear coat material model after remapping, parameterization and integration in the standard surface response. <a href="#listing_clearcoatbrdf">Listing&nbsp;14</a> shows the GLSL implementation of the clear coat material model after remapping, parameterization and integration in the standard surface response.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">BRDF</span>(<span class="hljs-params">...</span>)</span> {</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">BRDF</span><span class="hljs-params">(...)</span> </span>{</span>
<span class="line"> <span class="hljs-comment">// compute Fd and Fr from standard model</span></span> <span class="line"> <span class="hljs-comment">// compute Fd and Fr from standard model</span></span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// remapping and linearization of clear coat roughness</span></span> <span class="line"> <span class="hljs-comment">// remapping and linearization of clear coat roughness</span></span>
<span class="line"> clearCoatPerceptualRoughness = clamp(clearCoatPerceptualRoughness, <span class="hljs-number">0.089</span>, <span class="hljs-number">1.0</span>);</span> <span class="line"> clearCoatPerceptualRoughness = <span class="hljs-built_in">clamp</span>(clearCoatPerceptualRoughness, <span class="hljs-number">0.089</span>, <span class="hljs-number">1.0</span>);</span>
<span class="line"> clearCoatRoughness = clearCoatPerceptualRoughness * clearCoatPerceptualRoughness;</span> <span class="line"> clearCoatRoughness = clearCoatPerceptualRoughness * clearCoatPerceptualRoughness;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// clear coat BRDF</span></span> <span class="line"> <span class="hljs-comment">// clear coat BRDF</span></span>
<span class="line"> <span class="hljs-built_in">float</span> Dc = D_GGX(clearCoatRoughness, NoH);</span> <span class="line"> <span class="hljs-type">float</span> Dc = <span class="hljs-built_in">D_GGX</span>(clearCoatRoughness, NoH);</span>
<span class="line"> <span class="hljs-built_in">float</span> Vc = V_Kelemen(clearCoatRoughness, LoH);</span> <span class="line"> <span class="hljs-type">float</span> Vc = <span class="hljs-built_in">V_Kelemen</span>(clearCoatRoughness, LoH);</span>
<span class="line"> <span class="hljs-built_in">float</span> Fc = F_Schlick(<span class="hljs-number">0.04</span>, LoH) * clearCoat; <span class="hljs-comment">// clear coat strength</span></span> <span class="line"> <span class="hljs-type">float</span> Fc = <span class="hljs-built_in">F_Schlick</span>(<span class="hljs-number">0.04</span>, LoH) * clearCoat; <span class="hljs-comment">// clear coat strength</span></span>
<span class="line"> <span class="hljs-built_in">float</span> Frc = (Dc * Vc) * Fc;</span> <span class="line"> <span class="hljs-type">float</span> Frc = (Dc * Vc) * Fc;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// account for energy loss in the base layer</span></span> <span class="line"> <span class="hljs-comment">// account for energy loss in the base layer</span></span>
<span class="line"> <span class="hljs-keyword">return</span> color * ((Fd + Fr * (<span class="hljs-number">1.0</span> - Fc)) * (<span class="hljs-number">1.0</span> - Fc) + Frc);</span> <span class="line"> <span class="hljs-keyword">return</span> color * ((Fd + Fr * (<span class="hljs-number">1.0</span> - Fc)) * (<span class="hljs-number">1.0</span> - Fc) + Frc);</span>
@@ -1294,14 +1294,14 @@ f_{r}(v,h,\alpha) = \frac{D_{velvet}(v,h,\alpha)}{4(\NoL + \NoV - (\NoL)(\NoV))}
\end{equation}$$ \end{equation}$$
</p><p> </p><p>
The implementation of the velvet NDF is presented in <a href="#listing_clothbrdf">listing&nbsp;17</a>, optimized to properly fit in half float formats and to avoid computing a costly cotangent, relying instead on trigonometric identities. Note that we removed the Fresnel component from this BRDF. The implementation of the velvet NDF is presented in <a href="#listing_clothbrdf">listing&nbsp;17</a>, optimized to properly fit in half float formats and to avoid computing a costly cotangent, relying instead on trigonometric identities. Note that we removed the Fresnel component from this BRDF.
</p><pre class="listing tilde"><code><span class="line">float D_Ashikhmin(float roughness, float NoH) {</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">D_Ashikhmin</span><span class="hljs-params">(<span class="hljs-type">float</span> roughness, <span class="hljs-type">float</span> NoH)</span> </span>{</span>
<span class="line"> <span class="hljs-comment">// Ashikhmin 2007, &quot;Distribution-based BRDFs&quot;</span></span> <span class="line"> <span class="hljs-comment">// Ashikhmin 2007, &quot;Distribution-based BRDFs&quot;</span></span>
<span class="line"> float a<span class="hljs-number">2</span> = roughness * roughness;</span> <span class="line"> <span class="hljs-type">float</span> a2 = roughness * roughness;</span>
<span class="line"> float <span class="hljs-keyword">cos</span><span class="hljs-number">2</span>h = NoH * NoH;</span> <span class="line"> <span class="hljs-type">float</span> cos2h = NoH * NoH;</span>
<span class="line"> float <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h = <span class="hljs-keyword">max</span>(<span class="hljs-number">1.0</span> - <span class="hljs-keyword">cos</span><span class="hljs-number">2</span>h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 &gt; 0 in fp16</span></span> <span class="line"> <span class="hljs-type">float</span> sin2h = <span class="hljs-built_in">max</span>(<span class="hljs-number">1.0</span> - cos2h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 &gt; 0 in fp16</span></span>
<span class="line"> float <span class="hljs-keyword">sin</span><span class="hljs-number">4</span>h = <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h * <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h;</span> <span class="line"> <span class="hljs-type">float</span> sin4h = sin2h * sin2h;</span>
<span class="line"> float cot<span class="hljs-number">2</span> = -<span class="hljs-keyword">cos</span><span class="hljs-number">2</span>h / (a<span class="hljs-number">2</span> * <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h);</span> <span class="line"> <span class="hljs-type">float</span> cot2 = -cos2h / (a2 * sin2h);</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (PI * (<span class="hljs-number">4.0</span> * a<span class="hljs-number">2</span> + <span class="hljs-number">1.0</span>) * <span class="hljs-keyword">sin</span><span class="hljs-number">4</span>h) * (<span class="hljs-number">4.0</span> * exp(cot<span class="hljs-number">2</span>) + <span class="hljs-keyword">sin</span><span class="hljs-number">4</span>h);</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (PI * (<span class="hljs-number">4.0</span> * a2 + <span class="hljs-number">1.0</span>) * sin4h) * (<span class="hljs-number">4.0</span> * <span class="hljs-built_in">exp</span>(cot2) + sin4h);</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothbrdf">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;17:</b> Implementation of Ashikhmin's velvet NDF in GLSL</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothbrdf">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;17:</b> Implementation of Ashikhmin's velvet NDF in GLSL</div></center>
<p> <p>
<p>In [<a href="#citation-estevez17">Estevez17</a>] Estevez and Kulla propose a different NDF (called the “Charlie” sheen) that is based on an exponentiated sinusoidal instead of an inverted Gaussian. This NDF is appealing for several reasons: its parameterization feels more natural and intuitive, it provides a softer appearance and, as shown in equation (\ref{charlieNDF}), its implementation is simpler:</p> <p>In [<a href="#citation-estevez17">Estevez17</a>] Estevez and Kulla propose a different NDF (called the “Charlie” sheen) that is based on an exponentiated sinusoidal instead of an inverted Gaussian. This NDF is appealing for several reasons: its parameterization feels more natural and intuitive, it provides a softer appearance and, as shown in equation (\ref{charlieNDF}), its implementation is simpler:</p>
@@ -1312,11 +1312,11 @@ D(m) = \frac{(2 + \frac{1}{\alpha}) sin(\theta)^{\frac{1}{\alpha}}}{2 \pi}
</p><p> </p><p>
[<a href="#citation-estevez17">Estevez17</a>] also presents a new shadowing term that we omit here because of its cost. We instead rely on the visibility term from [<a href="#citation-neubelt13">Neubelt13</a>] (shown in equation \(\ref{clothSpecularBRDF}\) above). [<a href="#citation-estevez17">Estevez17</a>] also presents a new shadowing term that we omit here because of its cost. We instead rely on the visibility term from [<a href="#citation-neubelt13">Neubelt13</a>] (shown in equation \(\ref{clothSpecularBRDF}\) above).
The implementation of this NDF is presented in <a href="#listing_clothcharliebrdf">listing&nbsp;18</a>, optimized to properly fit in half float formats. The implementation of this NDF is presented in <a href="#listing_clothcharliebrdf">listing&nbsp;18</a>, optimized to properly fit in half float formats.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">D_Charlie</span><span class="hljs-params">(<span class="hljs-keyword">float</span> roughness, <span class="hljs-keyword">float</span> NoH)</span> </span>{</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">D_Charlie</span><span class="hljs-params">(<span class="hljs-type">float</span> roughness, <span class="hljs-type">float</span> NoH)</span> </span>{</span>
<span class="line"> <span class="hljs-comment">// Estevez and Kulla 2017, &quot;Production Friendly Microfacet Sheen BRDF&quot;</span></span> <span class="line"> <span class="hljs-comment">// Estevez and Kulla 2017, &quot;Production Friendly Microfacet Sheen BRDF&quot;</span></span>
<span class="line"> <span class="hljs-keyword">float</span> invAlpha = <span class="hljs-number">1.0</span> / roughness;</span> <span class="line"> <span class="hljs-type">float</span> invAlpha = <span class="hljs-number">1.0</span> / roughness;</span>
<span class="line"> <span class="hljs-keyword">float</span> cos2h = NoH * NoH;</span> <span class="line"> <span class="hljs-type">float</span> cos2h = NoH * NoH;</span>
<span class="line"> <span class="hljs-keyword">float</span> sin2h = max(<span class="hljs-number">1.0</span> - cos2h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 &gt; 0 in fp16</span></span> <span class="line"> <span class="hljs-type">float</span> sin2h = <span class="hljs-built_in">max</span>(<span class="hljs-number">1.0</span> - cos2h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 &gt; 0 in fp16</span></span>
<span class="line"> <span class="hljs-keyword">return</span> (<span class="hljs-number">2.0</span> + invAlpha) * <span class="hljs-built_in">pow</span>(sin2h, invAlpha * <span class="hljs-number">0.5</span>) / (<span class="hljs-number">2.0</span> * PI);</span> <span class="line"> <span class="hljs-keyword">return</span> (<span class="hljs-number">2.0</span> + invAlpha) * <span class="hljs-built_in">pow</span>(sin2h, invAlpha * <span class="hljs-number">0.5</span>) / (<span class="hljs-number">2.0</span> * PI);</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothcharliebrdf">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;18:</b> Implementation of the &ldquo;Charlie&rdquo; NDF in GLSL</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothcharliebrdf">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;18:</b> Implementation of the &ldquo;Charlie&rdquo; NDF in GLSL</div></center>
<a class="target" name="sheencolor">&nbsp;</a><a class="target" name="materialsystem/clothmodel/clothspecularbrdf/sheencolor">&nbsp;</a><a class="target" name="toc4.12.1.1">&nbsp;</a><h4 id="sheen-color"><a class="header" href="#sheen-color">Sheen color</a></h4> <a class="target" name="sheencolor">&nbsp;</a><a class="target" name="materialsystem/clothmodel/clothspecularbrdf/sheencolor">&nbsp;</a><a class="target" name="toc4.12.1.1">&nbsp;</a><h4 id="sheen-color"><a class="header" href="#sheen-color">Sheen color</a></h4>
@@ -1745,21 +1745,21 @@ The photometric attenuation function can be easily implemented in GLSL by adding
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_glslphotometricpunctuallight">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;22:</b> Implementation of attenuation from photometric profiles in GLSL</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_glslphotometricpunctuallight">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;22:</b> Implementation of attenuation from photometric profiles in GLSL</div></center>
<p> <p>
<p>The light intensity is computed CPU-side (<a href="#listing_photometriclightintensity">listing 23</a>) and depends on whether the photometric profile is used as a mask.</p> <p>The light intensity is computed CPU-side (<a href="#listing_photometriclightintensity">listing 23</a>) and depends on whether the photometric profile is used as a mask.</p>
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-keyword">float</span> multiplier;</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> multiplier;</span>
<span class="line"><span class="hljs-comment">// Photometric profile used as a mask</span></span> <span class="line"><span class="hljs-comment">// Photometric profile used as a mask</span></span>
<span class="line"><span class="hljs-keyword">if</span> (photometricLight.isMasked()) {</span> <span class="line"><span class="hljs-keyword">if</span> (photometricLight.<span class="hljs-built_in">isMasked</span>()) {</span>
<span class="line"> <span class="hljs-comment">// The desired intensity is set by the artist</span></span> <span class="line"> <span class="hljs-comment">// The desired intensity is set by the artist</span></span>
<span class="line"> <span class="hljs-comment">// The integrated intensity comes from a Monte-Carlo</span></span> <span class="line"> <span class="hljs-comment">// The integrated intensity comes from a Monte-Carlo</span></span>
<span class="line"> <span class="hljs-comment">// integration over the unit sphere around the luminaire</span></span> <span class="line"> <span class="hljs-comment">// integration over the unit sphere around the luminaire</span></span>
<span class="line"> multiplier = photometricLight.getDesiredIntensity() /</span> <span class="line"> multiplier = photometricLight.<span class="hljs-built_in">getDesiredIntensity</span>() /</span>
<span class="line"> photometricLight.getIntegratedIntensity();</span> <span class="line"> photometricLight.<span class="hljs-built_in">getIntegratedIntensity</span>();</span>
<span class="line">} <span class="hljs-keyword">else</span> {</span> <span class="line">} <span class="hljs-keyword">else</span> {</span>
<span class="line"> <span class="hljs-comment">// Multiplier provided for convenience, set to 1.0 by default</span></span> <span class="line"> <span class="hljs-comment">// Multiplier provided for convenience, set to 1.0 by default</span></span>
<span class="line"> multiplier = photometricLight.getMultiplier();</span> <span class="line"> multiplier = photometricLight.<span class="hljs-built_in">getMultiplier</span>();</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-comment">// The max intensity in cd comes from the IES profile</span></span> <span class="line"><span class="hljs-comment">// The max intensity in cd comes from the IES profile</span></span>
<span class="line"><span class="hljs-keyword">float</span> lightIntensity = photometricLight.getMaxIntensity() * multiplier;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_photometriclightintensity">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;23:</b> Computing the intensity of a photometric light on the CPU</div></center> <span class="line"><span class="hljs-type">float</span> lightIntensity = photometricLight.<span class="hljs-built_in">getMaxIntensity</span>() * multiplier;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_photometriclightintensity">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;23:</b> Computing the intensity of a photometric light on the CPU</div></center>
<p> <p>
<div class="endnote"><a class="target" name="endnote-xarrowintensity">&nbsp;</a><sup>4</sup> The XArrow profile declares a luminous intensity of 1,750 lm but a Monte-Carlo integration shows an intensity of only 350 lm. <div class="endnote"><a class="target" name="endnote-xarrowintensity">&nbsp;</a><sup>4</sup> The XArrow profile declares a luminous intensity of 1,750 lm but a Monte-Carlo integration shows an intensity of only 350 lm.
</div> </div>
@@ -2058,19 +2058,19 @@ In practice only 4 or 9 coefficients (i.e.: 2 or 3 bands) are enough for \(\cosT
<center><div class="image" style=""><a href="../images/ibl/ibl_irradiance_sh2.png" target="_blank"><img class="markdeep" src="../images/ibl/ibl_irradiance_sh2.png" style="max-width:100%;" /></a><center><span class="imagecaption"><a class="target" name="figure_iblsh2">&nbsp;</a><b style="font-style:normal;">Figure&nbsp;52:</b> 2 bands (4 coefficients)</span></center></div></center> <center><div class="image" style=""><a href="../images/ibl/ibl_irradiance_sh2.png" target="_blank"><img class="markdeep" src="../images/ibl/ibl_irradiance_sh2.png" style="max-width:100%;" /></a><center><span class="imagecaption"><a class="target" name="figure_iblsh2">&nbsp;</a><b style="font-style:normal;">Figure&nbsp;52:</b> 2 bands (4 coefficients)</span></center></div></center>
</p><p> </p><p>
In practice we pre-convolve \(\Lt\) with \(\cosTheta\) and pre-scale these coefficients by the basis scaling factors \(K_l^m\) so that the reconstruction code is as simple as possible in the shader: In practice we pre-convolve \(\Lt\) with \(\cosTheta\) and pre-scale these coefficients by the basis scaling factors \(K_l^m\) so that the reconstruction code is as simple as possible in the shader:
</p><pre class="listing tilde"><code><span class="line">vec3 <span class="hljs-function"><span class="hljs-title">irradianceSH</span>(<span class="hljs-params">vec3 n</span>)</span> {</span> </p><pre class="listing tilde"><code><span class="line">vec3 irradianceSH(vec3 n) {</span>
<span class="line"> <span class="hljs-comment">// uniform vec3 sphericalHarmonics[9]</span></span> <span class="line"> // uniform vec3 sphericalHarmonics<span class="hljs-selector-attr">[9]</span></span>
<span class="line"> <span class="hljs-comment">// We can use only the first 2 bands for better performance</span></span> <span class="line"> // We can <span class="hljs-selector-tag">use</span> only the first <span class="hljs-number">2</span> bands for better performance</span>
<span class="line"> <span class="hljs-keyword">return</span></span> <span class="line"> return</span>
<span class="line"> sphericalHarmonics[<span class="hljs-number">0</span>]</span> <span class="line"> sphericalHarmonics<span class="hljs-selector-attr">[0]</span></span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">1</span>] * (n.y)</span> <span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[1]</span> * (n<span class="hljs-selector-class">.y</span>)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">2</span>] * (n.z)</span> <span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[2]</span> * (n<span class="hljs-selector-class">.z</span>)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">3</span>] * (n.x)</span> <span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[3]</span> * (n<span class="hljs-selector-class">.x</span>)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">4</span>] * (n.y * n.x)</span> <span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[4]</span> * (n<span class="hljs-selector-class">.y</span> * n<span class="hljs-selector-class">.x</span>)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">5</span>] * (n.y * n.z)</span> <span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[5]</span> * (n<span class="hljs-selector-class">.y</span> * n<span class="hljs-selector-class">.z</span>)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">6</span>] * (<span class="hljs-number">3.0</span> * n.z * n.z - <span class="hljs-number">1.0</span>)</span> <span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[6]</span> * (<span class="hljs-number">3.0</span> * n<span class="hljs-selector-class">.z</span> * n<span class="hljs-selector-class">.z</span> - <span class="hljs-number">1.0</span>)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">7</span>] * (n.z * n.x)</span> <span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[7]</span> * (n<span class="hljs-selector-class">.z</span> * n<span class="hljs-selector-class">.x</span>)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">8</span>] * (n.x * n.x - n.y * n.y);</span> <span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[8]</span> * (n<span class="hljs-selector-class">.x</span> * n<span class="hljs-selector-class">.x</span> - n<span class="hljs-selector-class">.y</span> * n<span class="hljs-selector-class">.y</span>);</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_irradiancesh">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;26:</b> GLSL code to reconstruct the irradiance from the pre-scaled SH</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_irradiancesh">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;26:</b> GLSL code to reconstruct the irradiance from the pre-scaled SH</div></center>
<p> <p>
<p>Note that with 2 bands, the computation above becomes a single (4 \times 4) matrix-by-vector multiply.</p> <p>Note that with 2 bands, the computation above becomes a single (4 \times 4) matrix-by-vector multiply.</p>
@@ -2443,7 +2443,7 @@ LD(n, \alpha) &= \frac{\sum_i^N V(l_i, n,
$$ $$
</p><p> </p><p>
These two new \(DFG\) terms simply need to replace the ones used in the implementation shown in section <a href="#toc9.5">9.5</a>: These two new \(DFG\) terms simply need to replace the ones used in the implementation shown in section <a href="#toc9.5">9.5</a>:
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-keyword">float</span> Fc = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0f</span>);</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> Fc = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0f</span>);</span>
<span class="line">r.x += Gv * Fc;</span> <span class="line">r.x += Gv * Fc;</span>
<span class="line">r.y += Gv;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_multiscatteriblpreintegration">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;29:</b> C++ implementation of the \(L_{DFG}\) term for multiscattering</div></center> <span class="line">r.y += Gv;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_multiscatteriblpreintegration">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;29:</b> C++ implementation of the \(L_{DFG}\) term for multiscattering</div></center>
<p> <p>
@@ -2507,11 +2507,11 @@ using an environment made of colored vertical stripes (skybox hidden).</span></c
<p> <p>
<p>When sampling the IBL, the clear coat layer is calculated as a second specular lobe. This specular lobe is oriented along the view direction since we cannot reasonably integrate over the hemisphere. <a href="#listing_clearcoatibl">Listing 31</a> demonstrates this approximation in practice. It also shows the energy conservation step. It is important to note that this second specular lobe is computed exactly the same way as the main specular lobe, using the same DFG approximation.</p> <p>When sampling the IBL, the clear coat layer is calculated as a second specular lobe. This specular lobe is oriented along the view direction since we cannot reasonably integrate over the hemisphere. <a href="#listing_clearcoatibl">Listing 31</a> demonstrates this approximation in practice. It also shows the energy conservation step. It is important to note that this second specular lobe is computed exactly the same way as the main specular lobe, using the same DFG approximation.</p>
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-comment">// clearCoat_NoV == shading_NoV if the clear coat layer doesn&#x27;t have its own normal map</span></span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-comment">// clearCoat_NoV == shading_NoV if the clear coat layer doesn&#x27;t have its own normal map</span></span>
<span class="line"><span class="hljs-built_in">float</span> Fc = F_Schlick(<span class="hljs-number">0.04</span>, <span class="hljs-number">1.0</span>, clearCoat_NoV) * clearCoat;</span> <span class="line"><span class="hljs-type">float</span> Fc = <span class="hljs-built_in">F_Schlick</span>(<span class="hljs-number">0.04</span>, <span class="hljs-number">1.0</span>, clearCoat_NoV) * clearCoat;</span>
<span class="line"><span class="hljs-comment">// base layer attenuation for energy compensation</span></span> <span class="line"><span class="hljs-comment">// base layer attenuation for energy compensation</span></span>
<span class="line">iblDiffuse *= <span class="hljs-number">1.0</span> - Fc;</span> <span class="line">iblDiffuse *= <span class="hljs-number">1.0</span> - Fc;</span>
<span class="line">iblSpecular *= sq(<span class="hljs-number">1.0</span> - Fc);</span> <span class="line">iblSpecular *= <span class="hljs-built_in">sq</span>(<span class="hljs-number">1.0</span> - Fc);</span>
<span class="line">iblSpecular += specularIBL(r, clearCoatPerceptualRoughness) * Fc;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clearcoatibl">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;31:</b> GLSL implementation of the clear coat specular lobe for image-based lighting</div></center> <span class="line">iblSpecular += <span class="hljs-built_in">specularIBL</span>(r, clearCoatPerceptualRoughness) * Fc;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clearcoatibl">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;31:</b> GLSL implementation of the clear coat specular lobe for image-based lighting</div></center>
<a class="target" name="anisotropy">&nbsp;</a><a class="target" name="lighting/imagebasedlights/anisotropy">&nbsp;</a><a class="target" name="toc5.3.6">&nbsp;</a><h3 id="anisotropy"><a class="header" href="#anisotropy">Anisotropy </a></h3> <a class="target" name="anisotropy">&nbsp;</a><a class="target" name="lighting/imagebasedlights/anisotropy">&nbsp;</a><a class="target" name="toc5.3.6">&nbsp;</a><h3 id="anisotropy"><a class="header" href="#anisotropy">Anisotropy </a></h3>
<p> <p>
<p>[<a href="#citation-mcauley15">McAuley15</a>] describes a technique called “bent reflection vector”, based [<a href="#citation-revie12">Revie12</a>]. The bent reflection vector is a rough approximation of anisotropic lighting but the alternative is to use importance sampling. This approximation is sufficiently cheap to compute and provides good results, as shown in <a href="#figure_anisotropicibl1">figure 59</a> and <a href="#figure_anisotropicibl2">figure 60</a>.</p> <p>[<a href="#citation-mcauley15">McAuley15</a>] describes a technique called “bent reflection vector”, based [<a href="#citation-revie12">Revie12</a>]. The bent reflection vector is a rough approximation of anisotropic lighting but the alternative is to use importance sampling. This approximation is sufficiently cheap to compute and provides good results, as shown in <a href="#figure_anisotropicibl1">figure 59</a> and <a href="#figure_anisotropicibl2">figure 60</a>.</p>
@@ -2550,17 +2550,17 @@ The DG term is generated using uniform sampling as recommended in [<a href="#cit
<center><div class="image" style=""><a href="../images/ibl/dfg_cloth.png" target="_blank"><img class="markdeep" src="../images/ibl/dfg_cloth.png" /></a><center><span class="imagecaption"><a class="target" name="figure_dfgclothlut">&nbsp;</a><b style="font-style:normal;">Figure&nbsp;62:</b> DFG LUT with a 3rd channel encoding the DG term of the cloth BRDF</span></center></div></center> <center><div class="image" style=""><a href="../images/ibl/dfg_cloth.png" target="_blank"><img class="markdeep" src="../images/ibl/dfg_cloth.png" /></a><center><span class="imagecaption"><a class="target" name="figure_dfgclothlut">&nbsp;</a><b style="font-style:normal;">Figure&nbsp;62:</b> DFG LUT with a 3rd channel encoding the DG term of the cloth BRDF</span></center></div></center>
</p><p> </p><p>
The remainder of the image-based lighting implementation follows the same steps as the implementation of regular lights, including the optional subsurface scattering term and its wrap diffuse component. Just as with the clear coat IBL implementation, we cannot integrate over the hemisphere and use the view direction as the dominant light direction to compute the wrap diffuse component. The remainder of the image-based lighting implementation follows the same steps as the implementation of regular lights, including the optional subsurface scattering term and its wrap diffuse component. Just as with the clear coat IBL implementation, we cannot integrate over the hemisphere and use the view direction as the dominant light direction to compute the wrap diffuse component.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-built_in">float</span> diffuse = Fd_Lambert() * ambientOcclusion;</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> diffuse = <span class="hljs-built_in">Fd_Lambert</span>() * ambientOcclusion;</span>
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> defined(SHADING_MODEL_CLOTH)</span></span> <span class="line"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined(SHADING_MODEL_CLOTH)</span></span>
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span> <span class="line"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
<span class="line">diffuse *= saturate((NoV + <span class="hljs-number">0.5</span>) / <span class="hljs-number">2.25</span>);</span> <span class="line">diffuse *= <span class="hljs-built_in">saturate</span>((NoV + <span class="hljs-number">0.5</span>) / <span class="hljs-number">2.25</span>);</span>
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span> <span class="line"><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span></span>
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span> <span class="line"><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span></span>
<span class="line"></span> <span class="line"></span>
<span class="line">vec3 indirectDiffuse = irradianceIBL(n) * diffuse;</span> <span class="line">vec3 indirectDiffuse = <span class="hljs-built_in">irradianceIBL</span>(n) * diffuse;</span>
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> defined(SHADING_MODEL_CLOTH) &amp;&amp; defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span> <span class="line"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined(SHADING_MODEL_CLOTH) &amp;&amp; defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
<span class="line">indirectDiffuse *= saturate(subsurfaceColor + NoV);</span> <span class="line">indirectDiffuse *= <span class="hljs-built_in">saturate</span>(subsurfaceColor + NoV);</span>
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span> <span class="line"><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span></span>
<span class="line"></span> <span class="line"></span>
<span class="line">vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothapprox">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;34:</b> GLSL implementation of the DFG approximation for the cloth NDF</div></center> <span class="line">vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothapprox">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;34:</b> GLSL implementation of the DFG approximation for the cloth NDF</div></center>
<p> <p>
@@ -2982,20 +2982,20 @@ L_{max} &= 2^{EV_{100}} \times 1.2
<span class="line"><span class="hljs-comment">// aperture in f-stops</span></span> <span class="line"><span class="hljs-comment">// aperture in f-stops</span></span>
<span class="line"><span class="hljs-comment">// shutterSpeed in seconds</span></span> <span class="line"><span class="hljs-comment">// shutterSpeed in seconds</span></span>
<span class="line"><span class="hljs-comment">// sensitivity in ISO</span></span> <span class="line"><span class="hljs-comment">// sensitivity in ISO</span></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">exposureSettings</span><span class="hljs-params">(<span class="hljs-keyword">float</span> aperture, <span class="hljs-keyword">float</span> shutterSpeed, <span class="hljs-keyword">float</span> sensitivity)</span> </span>{</span> <span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">exposureSettings</span><span class="hljs-params">(<span class="hljs-type">float</span> aperture, <span class="hljs-type">float</span> shutterSpeed, <span class="hljs-type">float</span> sensitivity)</span> {</span>
<span class="line"> <span class="hljs-keyword">return</span> log2((aperture * aperture) / shutterSpeed * <span class="hljs-number">100.0</span> / sensitivity);</span> <span class="line"> <span class="hljs-keyword">return</span> log2((aperture * aperture) / shutterSpeed * <span class="hljs-number">100.0</span> / sensitivity);</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-comment">// Computes the exposure normalization factor from</span></span> <span class="line"><span class="hljs-comment">// Computes the exposure normalization factor from</span></span>
<span class="line"><span class="hljs-comment">// the camera&#x27;s EV100</span></span> <span class="line"><span class="hljs-comment">// the camera&#x27;s EV100</span></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">exposure</span><span class="hljs-params">(<span class="hljs-keyword">float</span> ev100)</span> </span>{</span> <span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">exposure</span><span class="hljs-params">(<span class="hljs-type">float</span> ev100)</span> {</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (<span class="hljs-built_in">pow</span>(<span class="hljs-number">2.0</span>, ev100) * <span class="hljs-number">1.2</span>);</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (pow(<span class="hljs-number">2.0</span>, ev100) * <span class="hljs-number">1.2</span>);</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-keyword">float</span> ev100 = exposureSettings(aperture, shutterSpeed, sensitivity);</span> <span class="line"><span class="hljs-type">float</span> <span class="hljs-variable">ev100</span> <span class="hljs-operator">=</span> exposureSettings(aperture, shutterSpeed, sensitivity);</span>
<span class="line"><span class="hljs-keyword">float</span> exposure = exposure(ev100);</span> <span class="line"><span class="hljs-type">float</span> <span class="hljs-variable">exposure</span> <span class="hljs-operator">=</span> exposure(ev100);</span>
<span class="line"></span> <span class="line"></span>
<span class="line">vec4 color = evaluateLighting();</span> <span class="line"><span class="hljs-type">vec4</span> <span class="hljs-variable">color</span> <span class="hljs-operator">=</span> evaluateLighting();</span>
<span class="line">color.rgb *= exposure;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fragmentexposure">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;42:</b> Implementation of exposure in GLSL</div></center> <span class="line">color.rgb *= exposure;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fragmentexposure">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;42:</b> Implementation of exposure in GLSL</div></center>
<p> <p>
<p>In practice the exposure factor can be pre-computed on the CPU to save shader instructions.</p> <p>In practice the exposure factor can be pre-computed on the CPU to save shader instructions.</p>
@@ -3466,9 +3466,9 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
<span class="line"><span class="hljs-comment">// Data source:</span></span> <span class="line"><span class="hljs-comment">// Data source:</span></span>
<span class="line"><span class="hljs-comment">// http://cvrl.ioo.ucl.ac.uk/cmfs.htm</span></span> <span class="line"><span class="hljs-comment">// http://cvrl.ioo.ucl.ac.uk/cmfs.htm</span></span>
<span class="line"><span class="hljs-comment">// http://cvrl.ioo.ucl.ac.uk/database/text/cmfs/ciexyz31.htm</span></span> <span class="line"><span class="hljs-comment">// http://cvrl.ioo.ucl.ac.uk/database/text/cmfs/ciexyz31.htm</span></span>
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_XYZ_START = <span class="hljs-number">360</span>;</span> <span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_XYZ_START = <span class="hljs-number">360</span>;</span>
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_XYZ_COUNT = <span class="hljs-number">471</span>;</span> <span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_XYZ_COUNT = <span class="hljs-number">471</span>;</span>
<span class="line"><span class="hljs-keyword">const</span> float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };</span> <span class="line"><span class="hljs-type">const</span> float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-comment">// CIE Standard Illuminant D65 relative spectral power distribution,</span></span> <span class="line"><span class="hljs-comment">// CIE Standard Illuminant D65 relative spectral power distribution,</span></span>
<span class="line"><span class="hljs-comment">// from 300nm to 830, at 5nm intervals</span></span> <span class="line"><span class="hljs-comment">// from 300nm to 830, at 5nm intervals</span></span>
@@ -3476,51 +3476,51 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
<span class="line"><span class="hljs-comment">// Data source:</span></span> <span class="line"><span class="hljs-comment">// Data source:</span></span>
<span class="line"><span class="hljs-comment">// https://en.wikipedia.org/wiki/Illuminant_D65</span></span> <span class="line"><span class="hljs-comment">// https://en.wikipedia.org/wiki/Illuminant_D65</span></span>
<span class="line"><span class="hljs-comment">// https://cielab.xyz/pdf/CIE_sel_colorimetric_tables.xls</span></span> <span class="line"><span class="hljs-comment">// https://cielab.xyz/pdf/CIE_sel_colorimetric_tables.xls</span></span>
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_INTERVAL = <span class="hljs-number">5</span>;</span> <span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_INTERVAL = <span class="hljs-number">5</span>;</span>
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_START = <span class="hljs-number">300</span>;</span> <span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_START = <span class="hljs-number">300</span>;</span>
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_END = <span class="hljs-number">830</span>;</span> <span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_END = <span class="hljs-number">830</span>;</span>
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_COUNT = <span class="hljs-number">107</span>;</span> <span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_COUNT = <span class="hljs-number">107</span>;</span>
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> CIE_D65[CIE_D65_COUNT] = { ... };</span> <span class="line"><span class="hljs-type">const</span> <span class="hljs-type">float</span> CIE_D65[CIE_D65_COUNT] = { ... };</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Sample</span> {</span></span> <span class="line"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Sample</span> {</span>
<span class="line"> <span class="hljs-keyword">float</span> w = <span class="hljs-number">0.0f</span>; <span class="hljs-comment">// wavelength</span></span> <span class="line"> <span class="hljs-type">float</span> w = <span class="hljs-number">0.0f</span>; <span class="hljs-comment">// wavelength</span></span>
<span class="line"> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span>&lt;<span class="hljs-keyword">float</span>&gt; ior; <span class="hljs-comment">// complex IOR, n + ik</span></span> <span class="line"> std::complex&lt;<span class="hljs-type">float</span>&gt; ior; <span class="hljs-comment">// complex IOR, n + ik</span></span>
<span class="line">};</span> <span class="line">};</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">float</span> <span class="hljs-title">illuminantD65</span><span class="hljs-params">(<span class="hljs-keyword">float</span> w)</span> </span>{</span> <span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">float</span> <span class="hljs-title">illuminantD65</span><span class="hljs-params">(<span class="hljs-type">float</span> w)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">auto</span> i0 = <span class="hljs-keyword">size_t</span>((w - CIE_D65_START) / CIE_D65_INTERVAL);</span> <span class="line"> <span class="hljs-keyword">auto</span> i0 = <span class="hljs-built_in">size_t</span>((w - CIE_D65_START) / CIE_D65_INTERVAL);</span>
<span class="line"> uint2 indexBounds{i0, <span class="hljs-built_in">std</span>::min(i0 + <span class="hljs-number">1</span>, CIE_D65_END)};</span> <span class="line"> uint2 indexBounds{i0, std::<span class="hljs-built_in">min</span>(i0 + <span class="hljs-number">1</span>, CIE_D65_END)};</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> float2 wavelengthBounds = CIE_D65_START + float2{indexBounds} * CIE_D65_INTERVAL;</span> <span class="line"> float2 wavelengthBounds = CIE_D65_START + float2{indexBounds} * CIE_D65_INTERVAL;</span>
<span class="line"> <span class="hljs-keyword">float</span> t = (w - wavelengthBounds.x) / (wavelengthBounds.y - wavelengthBounds.x);</span> <span class="line"> <span class="hljs-type">float</span> t = (w - wavelengthBounds.x) / (wavelengthBounds.y - wavelengthBounds.x);</span>
<span class="line"> <span class="hljs-keyword">return</span> lerp(CIE_D65[indexBounds.x], CIE_D65[indexBounds.y], t);</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-built_in">lerp</span>(CIE_D65[indexBounds.x], CIE_D65[indexBounds.y], t);</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-comment">// For std::lower_bound</span></span> <span class="line"><span class="hljs-comment">// For std::lower_bound</span></span>
<span class="line"><span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>&lt;(<span class="hljs-keyword">const</span> Sample&amp; lhs, <span class="hljs-keyword">const</span> Sample&amp; rhs) {</span> <span class="line"><span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span>&lt;(<span class="hljs-type">const</span> Sample&amp; lhs, <span class="hljs-type">const</span> Sample&amp; rhs) {</span>
<span class="line"> <span class="hljs-keyword">return</span> lhs.w &lt; rhs.w;</span> <span class="line"> <span class="hljs-keyword">return</span> lhs.w &lt; rhs.w;</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-comment">// The wavelength w must be between 360nm and 830nm</span></span> <span class="line"><span class="hljs-comment">// The wavelength w must be between 360nm and 830nm</span></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span>&lt;<span class="hljs-keyword">float</span>&gt; <span class="hljs-title">findSample</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span>&lt;sample&gt;&amp; samples, <span class="hljs-keyword">float</span> w)</span> </span>{</span> <span class="line"><span class="hljs-function"><span class="hljs-type">static</span> std::complex&lt;<span class="hljs-type">float</span>&gt; <span class="hljs-title">findSample</span><span class="hljs-params">(<span class="hljs-type">const</span> std::vector&lt;sample&gt;&amp; samples, <span class="hljs-type">float</span> w)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">auto</span> i1 = <span class="hljs-built_in">std</span>::lower_bound(</span> <span class="line"> <span class="hljs-keyword">auto</span> i1 = std::<span class="hljs-built_in">lower_bound</span>(</span>
<span class="line"> samples.begin(), samples.end(), Sample{w, <span class="hljs-number">0.0f</span> + <span class="hljs-number">0.0</span><span class="hljs-keyword">if</span>});</span> <span class="line"> samples.<span class="hljs-built_in">begin</span>(), samples.<span class="hljs-built_in">end</span>(), Sample{w, <span class="hljs-number">0.0f</span> + <span class="hljs-number">0.0</span><span class="hljs-keyword">if</span>});</span>
<span class="line"> <span class="hljs-keyword">auto</span> i0 = i1 - <span class="hljs-number">1</span>;</span> <span class="line"> <span class="hljs-keyword">auto</span> i0 = i1 - <span class="hljs-number">1</span>;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// Interpolate the complex IORs</span></span> <span class="line"> <span class="hljs-comment">// Interpolate the complex IORs</span></span>
<span class="line"> <span class="hljs-keyword">float</span> t = (w - i0-&gt;w) / (i1-&gt;w - i0-&gt;w);</span> <span class="line"> <span class="hljs-type">float</span> t = (w - i0-&gt;w) / (i1-&gt;w - i0-&gt;w);</span>
<span class="line"> <span class="hljs-keyword">float</span> n = lerp(i0-&gt;ior.real(), i1-&gt;ior.real(), t);</span> <span class="line"> <span class="hljs-type">float</span> n = <span class="hljs-built_in">lerp</span>(i0-&gt;ior.<span class="hljs-built_in">real</span>(), i1-&gt;ior.<span class="hljs-built_in">real</span>(), t);</span>
<span class="line"> <span class="hljs-keyword">float</span> k = lerp(i0-&gt;ior.imag(), i1-&gt;ior.imag(), t);</span> <span class="line"> <span class="hljs-type">float</span> k = <span class="hljs-built_in">lerp</span>(i0-&gt;ior.<span class="hljs-built_in">imag</span>(), i1-&gt;ior.<span class="hljs-built_in">imag</span>(), t);</span>
<span class="line"> <span class="hljs-keyword">return</span> { n, k };</span> <span class="line"> <span class="hljs-keyword">return</span> { n, k };</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">float</span> <span class="hljs-title">fresnel</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span>&lt;<span class="hljs-keyword">float</span>&gt;&amp; sample)</span> </span>{</span> <span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">float</span> <span class="hljs-title">fresnel</span><span class="hljs-params">(<span class="hljs-type">const</span> std::complex&lt;<span class="hljs-type">float</span>&gt;&amp; sample)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">return</span> (((sample - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (<span class="hljs-built_in">std</span>::conj(sample) - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>))) /</span> <span class="line"> <span class="hljs-keyword">return</span> (((sample - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (std::<span class="hljs-built_in">conj</span>(sample) - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>))) /</span>
<span class="line"> ((sample + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (<span class="hljs-built_in">std</span>::conj(sample) + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)))).real();</span> <span class="line"> ((sample + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (std::<span class="hljs-built_in">conj</span>(sample) + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)))).<span class="hljs-built_in">real</span>();</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> float3 <span class="hljs-title">XYZ_to_sRGB</span><span class="hljs-params">(<span class="hljs-keyword">const</span> float3&amp; v)</span> </span>{</span> <span class="line"><span class="hljs-function"><span class="hljs-type">static</span> float3 <span class="hljs-title">XYZ_to_sRGB</span><span class="hljs-params">(<span class="hljs-type">const</span> float3&amp; v)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">const</span> mat3f XYZ_sRGB{</span> <span class="line"> <span class="hljs-type">const</span> mat3f XYZ_sRGB{</span>
<span class="line"> <span class="hljs-number">3.2404542f</span>, <span class="hljs-number">-0.9692660f</span>, <span class="hljs-number">0.0556434f</span>,</span> <span class="line"> <span class="hljs-number">3.2404542f</span>, <span class="hljs-number">-0.9692660f</span>, <span class="hljs-number">0.0556434f</span>,</span>
<span class="line"> <span class="hljs-number">-1.5371385f</span>, <span class="hljs-number">1.8760108f</span>, <span class="hljs-number">-0.2040259f</span>,</span> <span class="line"> <span class="hljs-number">-1.5371385f</span>, <span class="hljs-number">1.8760108f</span>, <span class="hljs-number">-0.2040259f</span>,</span>
<span class="line"> <span class="hljs-number">-0.4985314f</span>, <span class="hljs-number">0.0415560f</span>, <span class="hljs-number">1.0572252f</span></span> <span class="line"> <span class="hljs-number">-0.4985314f</span>, <span class="hljs-number">0.0415560f</span>, <span class="hljs-number">1.0572252f</span></span>
@@ -3529,21 +3529,21 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-comment">// Outputs a linear sRGB color</span></span> <span class="line"><span class="hljs-comment">// Outputs a linear sRGB color</span></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> float3 <span class="hljs-title">computeColor</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span>&lt;sample&gt;&amp; samples)</span> </span>{</span> <span class="line"><span class="hljs-function"><span class="hljs-type">static</span> float3 <span class="hljs-title">computeColor</span><span class="hljs-params">(<span class="hljs-type">const</span> std::vector&lt;sample&gt;&amp; samples)</span> </span>{</span>
<span class="line"> float3 xyz{<span class="hljs-number">0.0f</span>};</span> <span class="line"> float3 xyz{<span class="hljs-number">0.0f</span>};</span>
<span class="line"> <span class="hljs-keyword">float</span> y = <span class="hljs-number">0.0f</span>;</span> <span class="line"> <span class="hljs-type">float</span> y = <span class="hljs-number">0.0f</span>;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">size_t</span> i = <span class="hljs-number">0</span>; i &lt; CIE_XYZ_COUNT; i++) {</span> <span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i &lt; CIE_XYZ_COUNT; i++) {</span>
<span class="line"> <span class="hljs-comment">// Current wavelength</span></span> <span class="line"> <span class="hljs-comment">// Current wavelength</span></span>
<span class="line"> <span class="hljs-keyword">float</span> w = CIE_XYZ_START + i;</span> <span class="line"> <span class="hljs-type">float</span> w = CIE_XYZ_START + i;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// Find most appropriate CIE XYZ sample for the wavelength</span></span> <span class="line"> <span class="hljs-comment">// Find most appropriate CIE XYZ sample for the wavelength</span></span>
<span class="line"> <span class="hljs-keyword">auto</span> sample = findSample(samples, w);</span> <span class="line"> <span class="hljs-keyword">auto</span> sample = <span class="hljs-built_in">findSample</span>(samples, w);</span>
<span class="line"> <span class="hljs-comment">// Compute Fresnel reflectance at normal incidence</span></span> <span class="line"> <span class="hljs-comment">// Compute Fresnel reflectance at normal incidence</span></span>
<span class="line"> <span class="hljs-keyword">float</span> f0 = fresnel(sample);</span> <span class="line"> <span class="hljs-type">float</span> f0 = <span class="hljs-built_in">fresnel</span>(sample);</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// We need to multiply by the spectral power distribution of the illuminant</span></span> <span class="line"> <span class="hljs-comment">// We need to multiply by the spectral power distribution of the illuminant</span></span>
<span class="line"> <span class="hljs-keyword">float</span> d65 = illuminantD65(w);</span> <span class="line"> <span class="hljs-type">float</span> d65 = <span class="hljs-built_in">illuminantD65</span>(w);</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> xyz += f0 * CIE_XYZ[i] * d65;</span> <span class="line"> xyz += f0 * CIE_XYZ[i] * d65;</span>
<span class="line"> y += CIE_XYZ[i].y * d65;</span> <span class="line"> y += CIE_XYZ[i].y * d65;</span>
@@ -3552,10 +3552,10 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
<span class="line"> <span class="hljs-comment">// Normalize so that 100% reflectance at every wavelength yields Y=1</span></span> <span class="line"> <span class="hljs-comment">// Normalize so that 100% reflectance at every wavelength yields Y=1</span></span>
<span class="line"> xyz /= y;</span> <span class="line"> xyz /= y;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> float3 linear = XYZ_to_sRGB(xyz);</span> <span class="line"> float3 linear = <span class="hljs-built_in">XYZ_to_sRGB</span>(xyz);</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-comment">// Normalize out-of-gamut values</span></span> <span class="line"> <span class="hljs-comment">// Normalize out-of-gamut values</span></span>
<span class="line"> <span class="hljs-keyword">if</span> (any(greaterThan(linear, float3{<span class="hljs-number">1.0f</span>}))) linear *= <span class="hljs-number">1.0f</span> / max(linear);</span> <span class="line"> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">any</span>(<span class="hljs-built_in">greaterThan</span>(linear, float3{<span class="hljs-number">1.0f</span>}))) linear *= <span class="hljs-number">1.0f</span> / <span class="hljs-built_in">max</span>(linear);</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-keyword">return</span> linear;</span> <span class="line"> <span class="hljs-keyword">return</span> linear;</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_specularcolorimpl">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;46:</b> C++ implementation to compute the base color of a metallic surface from spectral data</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_specularcolorimpl">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;46:</b> C++ implementation to compute the base color of a metallic surface from spectral data</div></center>
@@ -3735,47 +3735,47 @@ l &= \{ cos\phi sin\theta,sin\phi sin\theta,cos\theta \}
<pre class="listing tilde"><code><span class="line">vec2f hammersley(uint i, <span class="hljs-built_in">float</span> numSamples) {</span> <pre class="listing tilde"><code><span class="line">vec2f hammersley(uint i, <span class="hljs-built_in">float</span> numSamples) {</span>
<span class="line"> uint bits = i;</span> <span class="line"> uint bits = i;</span>
<span class="line"> bits = (bits &lt;&lt; <span class="hljs-string">16) | (bits &gt;&gt; 16</span>);</span> <span class="line"> bits = (bits &lt;&lt; <span class="hljs-string">16) | (bits &gt;&gt; 16</span>);</span>
<span class="line"> bits = ((bits &amp; 0x55555555) &lt;&lt; <span class="hljs-string">1) | ((bits &amp; 0xAAAAAAAA) &gt;&gt; 1</span>);</span> <span class="line"> bits = ((bits &amp; <span class="hljs-number">0</span>x55555555) &lt;&lt; <span class="hljs-number">1</span>) | ((bits &amp; <span class="hljs-number">0</span>xAAAAAAAA) &gt;&gt; <span class="hljs-number">1</span>);</span>
<span class="line"> bits = ((bits &amp; 0x33333333) &lt;&lt; <span class="hljs-string">2) | ((bits &amp; 0xCCCCCCCC) &gt;&gt; 2</span>);</span> <span class="line"> bits = ((bits &amp; <span class="hljs-number">0</span>x33333333) &lt;&lt; <span class="hljs-number">2</span>) | ((bits &amp; <span class="hljs-number">0</span>xCCCCCCCC) &gt;&gt; <span class="hljs-number">2</span>);</span>
<span class="line"> bits = ((bits &amp; 0x0F0F0F0F) &lt;&lt; <span class="hljs-string">4) | ((bits &amp; 0xF0F0F0F0) &gt;&gt; 4</span>);</span> <span class="line"> bits = ((bits &amp; <span class="hljs-number">0</span>x0F0F0F0F) &lt;&lt; <span class="hljs-number">4</span>) | ((bits &amp; <span class="hljs-number">0</span>xF0F0F0F0) &gt;&gt; <span class="hljs-number">4</span>);</span>
<span class="line"> bits = ((bits &amp; 0x00FF00FF) &lt;&lt; <span class="hljs-string">8) | ((bits &amp; 0xFF00FF00) &gt;&gt; 8</span>);</span> <span class="line"> bits = ((bits &amp; <span class="hljs-number">0</span>x00FF00FF) &lt;&lt; <span class="hljs-number">8</span>) | ((bits &amp; <span class="hljs-number">0</span>xFF00FF00) &gt;&gt; <span class="hljs-number">8</span>);</span>
<span class="line"> <span class="hljs-built_in">return</span> vec2f(i / numSamples, bits / exp2(32));</span> <span class="line"> return vec2f(i / numSamples, bits / exp2(<span class="hljs-number">32</span>));</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde">C++ implementation of a Hammersley sequence generator</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde">C++ implementation of a Hammersley sequence generator</div></center>
<a class="target" name="precomputinglforimage-basedlighting">&nbsp;</a><a class="target" name="annex/precomputinglforimage-basedlighting">&nbsp;</a><a class="target" name="toc9.5">&nbsp;</a><h2 id="precomputing-l-for-image-based-lighting"><a class="header" href="#precomputing-l-for-image-based-lighting">Precomputing L for image-based lighting</a></h2> <a class="target" name="precomputinglforimage-basedlighting">&nbsp;</a><a class="target" name="annex/precomputinglforimage-basedlighting">&nbsp;</a><a class="target" name="toc9.5">&nbsp;</a><h2 id="precomputing-l-for-image-based-lighting"><a class="header" href="#precomputing-l-for-image-based-lighting">Precomputing L for image-based lighting</a></h2>
<p> <p>
<p>The term ( L_{DFG} ) is only dependent on ( \NoV ). Below, the normal is arbitrarily set to ( n=\left[0, 0, 1\right] ) and (v) is chosen to satisfy ( \NoV ). The vector ( h_i ) is the ( D_{GGX}(\alpha) ) important direction sample (i).</p> <p>The term ( L_{DFG} ) is only dependent on ( \NoV ). Below, the normal is arbitrarily set to ( n=\left[0, 0, 1\right] ) and (v) is chosen to satisfy ( \NoV ). The vector ( h_i ) is the ( D_{GGX}(\alpha) ) important direction sample (i).</p>
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> GDFG(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a) {</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">GDFG</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a)</span> {</span>
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</span>
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>((-NoL * a2 + NoL) * NoL + a2);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXL</span> <span class="hljs-operator">=</span> NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);</span>
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>((-NoV * a2 + NoV) * NoV + a2);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXV</span> <span class="hljs-operator">=</span> NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);</span>
<span class="line"> <span class="hljs-keyword">return</span> (<span class="hljs-number">2</span> * NoL) / (GGXV + GGXL);</span> <span class="line"> <span class="hljs-keyword">return</span> (<span class="hljs-number">2</span> * NoL) / (GGXV + GGXL);</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line">float2 DFG(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> a) {</span> <span class="line">float2 <span class="hljs-title function_">DFG</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> a)</span> {</span>
<span class="line"> float3 V;</span> <span class="line"> float3 V;</span>
<span class="line"> V.x = <span class="hljs-built_in">sqrt</span>(<span class="hljs-number">1.0</span>f - NoV*NoV);</span> <span class="line"> V.x = sqrt(<span class="hljs-number">1.0f</span> - NoV*NoV);</span>
<span class="line"> V.y = <span class="hljs-number">0.0</span>f;</span> <span class="line"> V.y = <span class="hljs-number">0.0f</span>;</span>
<span class="line"> V.z = NoV;</span> <span class="line"> V.z = NoV;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> float2 r = <span class="hljs-number">0.0</span>f;</span> <span class="line"> <span class="hljs-type">float2</span> <span class="hljs-variable">r</span> <span class="hljs-operator">=</span> <span class="hljs-number">0.0f</span>;</span>
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">uint</span> i = <span class="hljs-number">0</span>; i &lt; sampleCount; i++) {</span> <span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">uint</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; sampleCount; i++) {</span>
<span class="line"> float2 Xi = hammersley(i, sampleCount);</span> <span class="line"> <span class="hljs-type">float2</span> <span class="hljs-variable">Xi</span> <span class="hljs-operator">=</span> hammersley(i, sampleCount);</span>
<span class="line"> float3 H = importanceSampleGGX(Xi, a, N);</span> <span class="line"> <span class="hljs-type">float3</span> <span class="hljs-variable">H</span> <span class="hljs-operator">=</span> importanceSampleGGX(Xi, a, N);</span>
<span class="line"> float3 L = <span class="hljs-number">2.0</span>f * <span class="hljs-built_in">dot</span>(V, H) * H - V;</span> <span class="line"> <span class="hljs-type">float3</span> <span class="hljs-variable">L</span> <span class="hljs-operator">=</span> <span class="hljs-number">2.0f</span> * dot(V, H) * H - V;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-type">float</span> VoH = saturate(<span class="hljs-built_in">dot</span>(V, H));</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">VoH</span> <span class="hljs-operator">=</span> saturate(dot(V, H));</span>
<span class="line"> <span class="hljs-type">float</span> NoL = saturate(L.z);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoL</span> <span class="hljs-operator">=</span> saturate(L.z);</span>
<span class="line"> <span class="hljs-type">float</span> NoH = saturate(H.z);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoH</span> <span class="hljs-operator">=</span> saturate(H.z);</span>
<span class="line"></span> <span class="line"></span>
<span class="line"> <span class="hljs-keyword">if</span> (NoL &gt; <span class="hljs-number">0.0</span>f) {</span> <span class="line"> <span class="hljs-keyword">if</span> (NoL &gt; <span class="hljs-number">0.0f</span>) {</span>
<span class="line"> <span class="hljs-type">float</span> G = GDFG(NoV, NoL, a);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">G</span> <span class="hljs-operator">=</span> GDFG(NoV, NoL, a);</span>
<span class="line"> <span class="hljs-type">float</span> Gv = G * VoH / NoH;</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">Gv</span> <span class="hljs-operator">=</span> G * VoH / NoH;</span>
<span class="line"> <span class="hljs-type">float</span> Fc = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0</span>f);</span> <span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">Fc</span> <span class="hljs-operator">=</span> pow(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0f</span>);</span>
<span class="line"> r.x += Gv * (<span class="hljs-number">1</span> - Fc);</span> <span class="line"> r.x += Gv * (<span class="hljs-number">1</span> - Fc);</span>
<span class="line"> r.y += Gv * Fc;</span> <span class="line"> r.y += Gv * Fc;</span>
<span class="line"> }</span> <span class="line"> }</span>
<span class="line"> }</span> <span class="line"> }</span>
<span class="line"> <span class="hljs-keyword">return</span> r * (<span class="hljs-number">1.0</span>f / sampleCount);</span> <span class="line"> <span class="hljs-keyword">return</span> r * (<span class="hljs-number">1.0f</span> / sampleCount);</span>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde">C++ implementation of the \( L_{DFG} \) term</div></center> <span class="line">}</span></code></pre><center><div class="listingcaption tilde">C++ implementation of the \( L_{DFG} \) term</div></center>
<a class="target" name="sphericalharmonics">&nbsp;</a><a class="target" name="annex/sphericalharmonics">&nbsp;</a><a class="target" name="toc9.6">&nbsp;</a><h2 id="spherical-harmonics"><a class="header" href="#spherical-harmonics">Spherical Harmonics</a></h2> <a class="target" name="sphericalharmonics">&nbsp;</a><a class="target" name="annex/sphericalharmonics">&nbsp;</a><a class="target" name="toc9.6">&nbsp;</a><h2 id="spherical-harmonics"><a class="header" href="#spherical-harmonics">Spherical Harmonics</a></h2>
<p> <p>
@@ -3851,56 +3851,56 @@ sin(m \phi + \phi) &= sin(m \phi) cos(\phi) + cos(m \phi) sin(\phi) \Leftrightar
\end{align*}$$ \end{align*}$$
</p><p> </p><p>
<a href="#listing_nonnormalizedshbasis">Listing&nbsp;47</a> shows the C++ code to compute the non-normalized SH basis \(\frac{y^m_l(s)}{\sqrt{2} K^m_l}\): <a href="#listing_nonnormalizedshbasis">Listing&nbsp;47</a> shows the C++ code to compute the non-normalized SH basis \(\frac{y^m_l(s)}{\sqrt{2} K^m_l}\):
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-keyword">size_t</span> <span class="hljs-title">SHindex</span><span class="hljs-params">(<span class="hljs-keyword">ssize_t</span> m, <span class="hljs-keyword">size_t</span> l)</span> </span>{</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">size_t</span> <span class="hljs-title">SHindex</span><span class="hljs-params">(<span class="hljs-type">ssize_t</span> m, <span class="hljs-type">size_t</span> l)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">return</span> l * (l + <span class="hljs-number">1</span>) + m;</span> <span class="line"> <span class="hljs-keyword">return</span> l * (l + <span class="hljs-number">1</span>) + m;</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">computeShBasis</span><span class="hljs-params">(</span> <span class="line"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">computeShBasis</span><span class="hljs-params">(</span>
<span class="line"> <span class="hljs-keyword">double</span>* <span class="hljs-keyword">const</span> SHb,</span> <span class="line"> <span class="hljs-type">double</span>* <span class="hljs-type">const</span> SHb,</span>
<span class="line"> <span class="hljs-keyword">size_t</span> numBands,</span> <span class="line"> <span class="hljs-type">size_t</span> numBands,</span>
<span class="line"> <span class="hljs-keyword">const</span> vec3&amp; s)</span></span> <span class="line"> <span class="hljs-type">const</span> vec3&amp; s)</span></span>
<span class="line"></span>{</span> <span class="line"></span>{</span>
<span class="line"> <span class="hljs-comment">// handle m=0 separately, since it produces only one coefficient</span></span> <span class="line"> <span class="hljs-comment">// handle m=0 separately, since it produces only one coefficient</span></span>
<span class="line"> <span class="hljs-keyword">double</span> Pml_2 = <span class="hljs-number">0</span>;</span> <span class="line"> <span class="hljs-type">double</span> Pml_2 = <span class="hljs-number">0</span>;</span>
<span class="line"> <span class="hljs-keyword">double</span> Pml_1 = <span class="hljs-number">1</span>;</span> <span class="line"> <span class="hljs-type">double</span> Pml_1 = <span class="hljs-number">1</span>;</span>
<span class="line"> SHb[<span class="hljs-number">0</span>] = Pml_1;</span> <span class="line"> SHb[<span class="hljs-number">0</span>] = Pml_1;</span>
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = <span class="hljs-number">1</span>; l &lt; numBands; l++) {</span> <span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = <span class="hljs-number">1</span>; l &lt; numBands; l++) {</span>
<span class="line"> <span class="hljs-keyword">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l - <span class="hljs-number">1</span>) * Pml_2) / l;</span> <span class="line"> <span class="hljs-type">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l - <span class="hljs-number">1</span>) * Pml_2) / l;</span>
<span class="line"> Pml_2 = Pml_1;</span> <span class="line"> Pml_2 = Pml_1;</span>
<span class="line"> Pml_1 = Pml;</span> <span class="line"> Pml_1 = Pml;</span>
<span class="line"> SHb[SHindex(<span class="hljs-number">0</span>, l)] = Pml;</span> <span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(<span class="hljs-number">0</span>, l)] = Pml;</span>
<span class="line"> }</span> <span class="line"> }</span>
<span class="line"> <span class="hljs-keyword">double</span> Pmm = <span class="hljs-number">1</span>;</span> <span class="line"> <span class="hljs-type">double</span> Pmm = <span class="hljs-number">1</span>;</span>
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> m = <span class="hljs-number">1</span>; m &lt; numBands ; m++) {</span> <span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> m = <span class="hljs-number">1</span>; m &lt; numBands ; m++) {</span>
<span class="line"> Pmm = (<span class="hljs-number">1</span> - <span class="hljs-number">2</span> * m) * Pmm;</span> <span class="line"> Pmm = (<span class="hljs-number">1</span> - <span class="hljs-number">2</span> * m) * Pmm;</span>
<span class="line"> <span class="hljs-keyword">double</span> Pml_2 = Pmm;</span> <span class="line"> <span class="hljs-type">double</span> Pml_2 = Pmm;</span>
<span class="line"> <span class="hljs-keyword">double</span> Pml_1 = (<span class="hljs-number">2</span> * m + <span class="hljs-number">1</span>)*Pmm*s.z;</span> <span class="line"> <span class="hljs-type">double</span> Pml_1 = (<span class="hljs-number">2</span> * m + <span class="hljs-number">1</span>)*Pmm*s.z;</span>
<span class="line"> <span class="hljs-comment">// l == m</span></span> <span class="line"> <span class="hljs-comment">// l == m</span></span>
<span class="line"> SHb[SHindex(-m, m)] = Pml_2;</span> <span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, m)] = Pml_2;</span>
<span class="line"> SHb[SHindex( m, m)] = Pml_2;</span> <span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, m)] = Pml_2;</span>
<span class="line"> <span class="hljs-keyword">if</span> (m + <span class="hljs-number">1</span> &lt; numBands) {</span> <span class="line"> <span class="hljs-keyword">if</span> (m + <span class="hljs-number">1</span> &lt; numBands) {</span>
<span class="line"> <span class="hljs-comment">// l == m+1</span></span> <span class="line"> <span class="hljs-comment">// l == m+1</span></span>
<span class="line"> SHb[SHindex(-m, m + <span class="hljs-number">1</span>)] = Pml_1;</span> <span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
<span class="line"> SHb[SHindex( m, m + <span class="hljs-number">1</span>)] = Pml_1;</span> <span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = m + <span class="hljs-number">2</span>; l &lt; numBands; l++) {</span> <span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = m + <span class="hljs-number">2</span>; l &lt; numBands; l++) {</span>
<span class="line"> <span class="hljs-keyword">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l + m - <span class="hljs-number">1</span>) * Pml_2)</span> <span class="line"> <span class="hljs-type">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l + m - <span class="hljs-number">1</span>) * Pml_2)</span>
<span class="line"> / (l - m);</span> <span class="line"> / (l - m);</span>
<span class="line"> Pml_2 = Pml_1;</span> <span class="line"> Pml_2 = Pml_1;</span>
<span class="line"> Pml_1 = Pml;</span> <span class="line"> Pml_1 = Pml;</span>
<span class="line"> SHb[SHindex(-m, l)] = Pml;</span> <span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, l)] = Pml;</span>
<span class="line"> SHb[SHindex( m, l)] = Pml;</span> <span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, l)] = Pml;</span>
<span class="line"> }</span> <span class="line"> }</span>
<span class="line"> }</span> <span class="line"> }</span>
<span class="line"> }</span> <span class="line"> }</span>
<span class="line"> <span class="hljs-keyword">double</span> Cm = s.x;</span> <span class="line"> <span class="hljs-type">double</span> Cm = s.x;</span>
<span class="line"> <span class="hljs-keyword">double</span> Sm = s.y;</span> <span class="line"> <span class="hljs-type">double</span> Sm = s.y;</span>
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> m = <span class="hljs-number">1</span>; m &lt;= numBands ; m++) {</span> <span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> m = <span class="hljs-number">1</span>; m &lt;= numBands ; m++) {</span>
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = m; l &lt; numBands ; l++) {</span> <span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = m; l &lt; numBands ; l++) {</span>
<span class="line"> SHb[SHindex(-m, l)] *= Sm;</span> <span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, l)] *= Sm;</span>
<span class="line"> SHb[SHindex( m, l)] *= Cm;</span> <span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, l)] *= Cm;</span>
<span class="line"> }</span> <span class="line"> }</span>
<span class="line"> <span class="hljs-keyword">double</span> Cm1 = Cm * s.x - Sm * s.y;</span> <span class="line"> <span class="hljs-type">double</span> Cm1 = Cm * s.x - Sm * s.y;</span>
<span class="line"> <span class="hljs-keyword">double</span> Sm1 = Sm * s.x + Cm * s.y;</span> <span class="line"> <span class="hljs-type">double</span> Sm1 = Sm * s.x + Cm * s.y;</span>
<span class="line"> Cm = Cm1;</span> <span class="line"> Cm = Cm1;</span>
<span class="line"> Sm = Sm1;</span> <span class="line"> Sm = Sm1;</span>
<span class="line"> }</span> <span class="line"> }</span>
@@ -3979,10 +3979,10 @@ $$\begin{equation}
\end{equation}$$ \end{equation}$$
</p><p> </p><p>
Here is the C++ code to compute \(\hat{C}_l\): Here is the C++ code to compute \(\hat{C}_l\):
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> n, <span class="hljs-keyword">size_t</span> d = <span class="hljs-number">1</span>)</span></span>;</span> </p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-type">size_t</span> n, <span class="hljs-type">size_t</span> d = <span class="hljs-number">1</span>)</span></span>;</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-comment">// &lt; cos(theta) &gt; SH coefficients pre-multiplied by 1 / K(0,l)</span></span> <span class="line"><span class="hljs-comment">// &lt; cos(theta) &gt; SH coefficients pre-multiplied by 1 / K(0,l)</span></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">computeTruncatedCosSh</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> l)</span> </span>{</span> <span class="line"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">computeTruncatedCosSh</span><span class="hljs-params">(<span class="hljs-type">size_t</span> l)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">if</span> (l == <span class="hljs-number">0</span>) {</span> <span class="line"> <span class="hljs-keyword">if</span> (l == <span class="hljs-number">0</span>) {</span>
<span class="line"> <span class="hljs-keyword">return</span> M_PI;</span> <span class="line"> <span class="hljs-keyword">return</span> M_PI;</span>
<span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (l == <span class="hljs-number">1</span>) {</span> <span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (l == <span class="hljs-number">1</span>) {</span>
@@ -3990,17 +3990,17 @@ Here is the C++ code to compute \(\hat{C}_l\):
<span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (l &amp; <span class="hljs-number">1</span>) {</span> <span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (l &amp; <span class="hljs-number">1</span>) {</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;</span>
<span class="line"> }</span> <span class="line"> }</span>
<span class="line"> <span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> l_2 = l / <span class="hljs-number">2</span>;</span> <span class="line"> <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> l_2 = l / <span class="hljs-number">2</span>;</span>
<span class="line"> <span class="hljs-keyword">double</span> A0 = ((l_2 &amp; <span class="hljs-number">1</span>) ? <span class="hljs-number">1.0</span> : <span class="hljs-number">-1.0</span>) / ((l + <span class="hljs-number">2</span>) * (l - <span class="hljs-number">1</span>));</span> <span class="line"> <span class="hljs-type">double</span> A0 = ((l_2 &amp; <span class="hljs-number">1</span>) ? <span class="hljs-number">1.0</span> : <span class="hljs-number">-1.0</span>) / ((l + <span class="hljs-number">2</span>) * (l - <span class="hljs-number">1</span>));</span>
<span class="line"> <span class="hljs-keyword">double</span> A1 = factorial(l, l_2) / (factorial(l_2) * (<span class="hljs-number">1</span> &lt;&lt; l));</span> <span class="line"> <span class="hljs-type">double</span> A1 = <span class="hljs-built_in">factorial</span>(l, l_2) / (<span class="hljs-built_in">factorial</span>(l_2) * (<span class="hljs-number">1</span> &lt;&lt; l));</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * M_PI * A0 * A1;</span> <span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * M_PI * A0 * A1;</span>
<span class="line">}</span> <span class="line">}</span>
<span class="line"></span> <span class="line"></span>
<span class="line"><span class="hljs-comment">// returns n! / d!</span></span> <span class="line"><span class="hljs-comment">// returns n! / d!</span></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> n, <span class="hljs-keyword">size_t</span> d )</span> </span>{</span> <span class="line"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-type">size_t</span> n, <span class="hljs-type">size_t</span> d )</span> </span>{</span>
<span class="line"> d = <span class="hljs-built_in">std</span>::max(<span class="hljs-keyword">size_t</span>(<span class="hljs-number">1</span>), d);</span> <span class="line"> d = std::<span class="hljs-built_in">max</span>(<span class="hljs-built_in">size_t</span>(<span class="hljs-number">1</span>), d);</span>
<span class="line"> n = <span class="hljs-built_in">std</span>::max(<span class="hljs-keyword">size_t</span>(<span class="hljs-number">1</span>), n);</span> <span class="line"> n = std::<span class="hljs-built_in">max</span>(<span class="hljs-built_in">size_t</span>(<span class="hljs-number">1</span>), n);</span>
<span class="line"> <span class="hljs-keyword">double</span> r = <span class="hljs-number">1.0</span>;</span> <span class="line"> <span class="hljs-type">double</span> r = <span class="hljs-number">1.0</span>;</span>
<span class="line"> <span class="hljs-keyword">if</span> (n == d) {</span> <span class="line"> <span class="hljs-keyword">if</span> (n == d) {</span>
<span class="line"> <span class="hljs-comment">// intentionally left blank</span></span> <span class="line"> <span class="hljs-comment">// intentionally left blank</span></span>
<span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (n &gt; d) {</span> <span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (n &gt; d) {</span>
+2 -2
View File
@@ -165,7 +165,7 @@
<nav class="nav-wrapper" aria-label="Page navigation"> <nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/test_renderdiff.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/test_ci_renderdiff.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
@@ -179,7 +179,7 @@
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation"> <nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/test_renderdiff.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left"> <a rel="prev" href="../dup/test_ci_renderdiff.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
+2 -2
View File
@@ -170,7 +170,7 @@ for in our Continuation Integration flow <a href="https://https://github.com/goo
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/test_backend.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/backend_test.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
@@ -184,7 +184,7 @@ for in our Continuation Integration flow <a href="https://https://github.com/goo
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
<a rel="next prefetch" href="../dup/test_backend.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right"> <a rel="next prefetch" href="../dup/backend_test.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
</nav> </nav>
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+311
View File
@@ -0,0 +1,311 @@
// SimulatedSkybox.js
// Ported from samples/utils/SimulatedSkybox.cpp
class SimulatedSkybox {
constructor(engine) {
this.engine = engine;
// Default Parameters
this.sunDirection = [0, 1, 0];
this.sunIntensity = 100000.0;
this.turbidity = 2.0;
this.rayleigh = 1.0;
this.mieCoefficient = 1.0;
this.mieG = 0.8;
this.ozone = 0.0;
this.msFactors = [0.1, 0.5, 0.0];
this.contrast = 1.0;
this.nightColor = [0.0, 0.0003, 0.00075];
this.shimmerControl = [0.0, 20.0, 0.1];
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
this.starControl = [1.0, 1.0]; // x=Density (0-1), y=Enabled (0-1)
this.planetRadius = 6360.0;
// Sun Halo
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
this.sunHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.5, 1.0, 1.0];
this.initEntity();
}
async loadMaterial(url) {
console.log("Loading material from:", url);
const response = await fetch(url);
const buffer = await response.arrayBuffer();
this.material = this.engine.createMaterial(new Uint8Array(buffer));
this.materialInstance = this.material.createInstance();
// Re-bind the entity with the loaded material
const rcm = this.engine.getRenderableManager();
const instance = rcm.getInstance(this.entity);
rcm.setMaterialInstanceAt(instance, 0, this.materialInstance);
console.log("Material loaded and bound.");
this.updateCoefficients();
}
initEntity() {
const EntityManager = Filament.EntityManager;
const RenderableManager = Filament.RenderableManager;
const VertexBuffer = Filament.VertexBuffer;
const IndexBuffer = Filament.IndexBuffer;
const AttributeType = Filament.VertexBuffer$AttributeType;
const VertexAttribute = Filament.VertexAttribute;
const PrimitiveType = Filament.RenderableManager$PrimitiveType;
const IndexType = Filament.IndexBuffer$IndexType;
this.entity = EntityManager.get().create();
// 3 vertices for full screen triangle
// coords: -1,-1 to 3,-1 to -1,3
const TRIANGLE_VERTICES = new Float32Array([
-1.0, -1.0,
3.0, -1.0,
-1.0, 3.0
]);
const TRIANGLE_INDICES = new Uint16Array([0, 1, 2]);
this.vb = VertexBuffer.Builder()
.vertexCount(3)
.bufferCount(1)
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, 8)
.build(this.engine);
this.vb.setBufferAt(this.engine, 0, TRIANGLE_VERTICES);
this.ib = IndexBuffer.Builder()
.indexCount(3)
.bufferType(IndexType.USHORT)
.build(this.engine);
this.ib.setBuffer(this.engine, TRIANGLE_INDICES);
// We create a dummy material first or wait?
// In JS we usually can't block. We'll rely on loadMaterial being called.
// For now, we build the Renderable without material, then set it later.
RenderableManager.Builder(1)
.geometry(0, PrimitiveType.TRIANGLES, this.vb, this.ib)
.culling(false)
.castShadows(false)
.receiveShadows(false)
.priority(7) // Render behind translucent objects? 7 is skybox priority typically.
.build(this.engine, this.entity);
}
setSunPosition(direction) {
// normalize
const len = Math.hypot(direction[0], direction[1], direction[2]);
if (len > 0) {
this.sunDirection = [direction[0] / len, direction[1] / len, direction[2] / len];
} else {
this.sunDirection = [0, 1, 0];
}
this.updateCoefficients();
}
setSunIntensity(intensity) {
this.sunIntensity = Math.max(0.0, intensity);
this.updateCoefficients();
}
setTurbidity(turbidity) {
this.turbidity = Math.max(0.0, turbidity);
this.updateCoefficients();
}
setRayleigh(rayleigh) {
this.rayleigh = Math.max(0.0, rayleigh);
this.updateCoefficients();
}
setMieCoefficient(mie) {
this.mieCoefficient = Math.max(0.0, mie);
this.updateCoefficients();
}
setMieG(g) {
this.mieG = Math.max(0.0, g);
this.updateCoefficients();
}
setOzone(strength) {
this.ozone = Math.max(0.0, strength);
this.updateCoefficients();
}
setMultiScattering(r, m) {
this.msFactors[0] = Math.max(0.0, Math.min(2.0, r));
this.msFactors[1] = Math.max(0.0, Math.min(2.0, m));
this.updateCoefficients();
}
setHorizonGlow(strength) {
this.msFactors[2] = Math.max(0.0, Math.min(1.0, strength));
this.updateCoefficients();
}
setContrast(contrast) {
this.contrast = contrast;
this.updateCoefficients();
}
setNightColor(color) {
this.nightColor = color;
this.updateCoefficients();
}
setSunRadius(degrees) {
const rad = degrees * (Math.PI / 180.0);
this.sunHalo[0] = Math.cos(rad);
this.updateCoefficients();
}
setSunDiskIntensity(intensity) {
this.sunHalo[2] = Math.max(0.0, intensity);
this.updateCoefficients();
}
setSunLimbDarkening(strength) {
this.sunHalo[1] = Math.max(0.0, strength);
this.updateCoefficients();
}
setSunDiskEnabled(enabled) {
this.sunHalo[3] = enabled ? 1.0 : 0.0;
this.updateCoefficients();
}
setShimmerControl(strength, frequency, maskHeight) {
this.shimmerControl[0] = Math.max(0.0, strength);
this.shimmerControl[1] = Math.max(0.0, frequency);
this.shimmerControl[2] = Math.max(0.001, maskHeight);
this.updateCoefficients();
}
setCloudControl(coverage, density, height, speed) {
this.cloudControl[0] = Math.max(0.0, Math.min(1.0, coverage));
this.cloudControl[1] = Math.max(0.0, density);
this.cloudControl[2] = Math.max(1000.0, height);
// JS speed adjustment logic matches C++: speed * (0.05 / 72.0)
this.cloudControl[3] = speed * (0.05 / 72.0);
this.updateCoefficients();
}
setCloudShapeEvolution(speed) {
this.cloudControl2[0] = speed;
this.updateCoefficients();
}
setCloudVolumetricLighting(enabled) {
this.cloudControl2[1] = enabled ? 1.0 : 0.0;
this.updateCoefficients();
}
setWaterControl(strength, speed, derivativeTrick, octaves) {
this.waterControl[0] = Math.max(0.0, strength);
this.waterControl[1] = Math.max(0.0, speed);
this.waterControl[2] = derivativeTrick;
this.waterControl[3] = Math.max(1.0, Math.min(8.0, octaves));
this.updateCoefficients();
}
setStarControl(density, enabled) {
this.starControl[0] = Math.max(0.0, Math.min(1.0, density));
this.starControl[1] = enabled ? 1.0 : 0.0;
this.updateCoefficients();
}
updateCoefficients() {
if (!this.materialInstance) {
console.warn("updateCoefficients called before material loaded");
return;
}
// 1. Rayleigh Coefficients
const F_PI = Math.PI;
const lambda = [680e-9, 550e-9, 440e-9];
const n = 1.0003;
const N = 2.545e25;
const term = (8.0 * Math.pow(F_PI, 3.0) * Math.pow(n * n - 1.0, 2.0)) / (3.0 * N);
const depthR = [
term / Math.pow(lambda[0], 4.0),
term / Math.pow(lambda[1], 4.0),
term / Math.pow(lambda[2], 4.0)
].map(v => v * 8000.0 * this.rayleigh);
// 2. Mie Coefficients
const mieAlpha = 1.3;
const mieBase = 2.0e-5 * this.turbidity;
const depthM = [
mieBase * Math.pow(550e-9 / lambda[0], mieAlpha),
mieBase * Math.pow(550e-9 / lambda[1], mieAlpha),
mieBase * Math.pow(550e-9 / lambda[2], mieAlpha)
].map(v => v * 1200.0 * this.mieCoefficient);
// Fake Ozone
const ozone = [0.0, this.ozone * 0.1, 0.0];
// Sun Fade (Horizon)
const cutoffAngle = 96.0 * (F_PI / 180.0);
const steepness = 1.5;
const zenithFade = 1.0 - Math.exp(-(cutoffAngle / steepness));
const zenithAngle = Math.acos(Math.max(-1.0, Math.min(1.0, this.sunDirection[1])));
const sunFade = Math.max(0.0, 1.0 - Math.exp(-((cutoffAngle - zenithAngle) / steepness))) / zenithFade;
const physicalSunIntensity = this.sunIntensity * sunFade;
// Radiance Conversion for Sun Halo
// Solid Angle = 2 * PI * (1 - cos(angularRadius))
const solidAngle = 2.0 * F_PI * (1.0 - this.sunHalo[0]);
const radianceConversion = 1.0 / Math.max(1e-9, solidAngle);
const sunHaloUpload = [...this.sunHalo];
sunHaloUpload[2] *= radianceConversion;
// Cloud Intersection
const r = this.planetRadius;
const h = this.cloudControl[2] * 0.001; // m -> km
const intersectC = r * r - (r + h) * (r + h);
const cloudUniform = [...this.cloudControl];
cloudUniform[2] = intersectC;
// Shimmer Uniform
const shimmerUniform = [...this.shimmerControl, r];
// Multi-Scattering Vector
const isotropicPhase = 0.25;
const msVector = depthR.map((v, i) => (v * this.msFactors[0] + depthM[i] * this.msFactors[1]) * isotropicPhase);
// Upload
this.materialInstance.setFloat3Parameter('sunDirection', new Float32Array(this.sunDirection));
this.materialInstance.setFloat3Parameter('depthR', new Float32Array(depthR));
this.materialInstance.setFloat3Parameter('depthM', new Float32Array(depthM));
this.materialInstance.setFloat3Parameter('ozone', new Float32Array(ozone));
this.materialInstance.setFloat4Parameter('sunHalo', new Float32Array(sunHaloUpload));
this.materialInstance.setFloat4Parameter('multiScatParams', new Float32Array([...msVector, this.msFactors[2]]));
// Mie Phase
const g2 = this.mieG * this.mieG;
this.materialInstance.setFloat2Parameter('miePhaseParams', new Float32Array([1.0 + g2, -2.0 * this.mieG]));
this.materialInstance.setFloatParameter('contrast', this.contrast);
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity);
this.materialInstance.setFloat3Parameter('nightColor', new Float32Array(nightColorScaled));
this.materialInstance.setFloat4Parameter('shimmerControl', new Float32Array(shimmerUniform));
this.materialInstance.setFloat4Parameter('cloudControl', new Float32Array(cloudUniform));
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
this.materialInstance.setFloat2Parameter('starControl', new Float32Array(this.starControl));
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
}
}
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
# Result: /Users/mathias/sources/git/filament/out/cmake-release/tools/matc/matc
MATC="../../../../out/cmake-release/tools/matc/matc"
# Navigate to script directory to ensure relative paths work
cd "$(dirname "$0")"
set -e
$MATC -a opengl -p mobile -o assets/simulated_skybox.filamat simulated_skybox.mat
echo "Material recompiled to assets/simulated_skybox.filamat"
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
+28
View File
File diff suppressed because one or more lines are too long
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Analytic Skybox</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
<link href="styles.css" rel="stylesheet">
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
}
canvas {
touch-action: none;
width: 100%;
height: 100%;
outline: none;
}
</style>
</head>
<body>
<canvas></canvas>
<!-- Filament -->
<script src="filament.js"></script>
<script src="gl-matrix-min.js"></script>
<!-- UI -->
<script src="lil-gui.js"></script>
<!-- App -->
<script src="SimulatedSkybox.js?v=26"></script>
<script src="main.js?v=26"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
+345
View File
@@ -0,0 +1,345 @@
// main.js
Filament.init(['assets/simulated_skybox.filamat'], () => {
window.app = new App(document.getElementsByTagName('canvas')[0]);
});
class App {
constructor(canvas) {
this.canvas = canvas;
const engine = this.engine = Filament.Engine.create(this.canvas);
this.scene = engine.createScene();
this.skybox = new SimulatedSkybox(engine);
this.skybox.entity = this.skybox.entity; // Ensuring access if needed
this.scene.addEntity(this.skybox.entity);
// Load the material explicitly since we passed it to init but SimulatedSkybox needs to bind it
// Actually SimulatedSkybox.loadMaterial fetches it.
// Since we already loaded it in Filament.init, we can arguably just use it if we had a way to access the asset.
// But Filament.init assets are for internal or easy access via assets object if configured?
// Let's just let SimulatedSkybox fetch it again or use a blob if we wanted.
// Simpler: Just let SimulatedSkybox fetch it.
this.skybox.loadMaterial('assets/simulated_skybox.filamat').then(() => {
this.initGUI();
});
this.swapChain = engine.createSwapChain();
this.renderer = engine.createRenderer();
this.camera = engine.createCamera(Filament.EntityManager.get().create());
this.view = engine.createView();
this.view.setCamera(this.camera);
this.view.setScene(this.scene);
// Color Grading
const ColorGrading = Filament.ColorGrading;
const ToneMapping = Filament.ColorGrading$ToneMapping;
this.colorGrading = ColorGrading.Builder()
.toneMapping(ToneMapping.ACES_LEGACY)
.build(engine);
this.view.setColorGrading(this.colorGrading);
this.view.setPostProcessingEnabled(true); // Essential for tone mapping
// Bloom
this.view.setBloomOptions({
enabled: false,
lenseFlare: false
});
// Clear color is not really visible behind skybox, but black is standard
this.renderer.setClearOptions({ clearColor: [0.0, 0.0, 0.0, 1.0], clear: true });
// Camera handling (Exposure)
this.params = {
aperture: 16.0,
shutterSpeed: 125.0,
iso: 100.0,
sunTheta: Math.acos(0.0), // Default Height 0.0 (Horizon)
sunPhi: 0.0,
focalLength: 24.0, // mm
sunIntensity: 100000.0 // Base intensity
};
this.camState = {
theta: Math.PI / 2, // Look at +X (Sun Position at Phi=0)
phi: 0.0,
dragging: false,
lastX: 0,
lastY: 0
};
this.initControls(); // Initialize controls immediately
this.resize();
window.addEventListener('resize', this.resize.bind(this));
this.render = this.render.bind(this);
window.requestAnimationFrame(this.render);
}
getExposure() {
// Formula: 1.0 / ( 1.2 * (N^2 / t) * (S / 100) )
// t = 1/shutterSpeed
const N = this.params.aperture;
const t = 1.0 / this.params.shutterSpeed;
const S = this.params.iso;
const ev100_linear = (N * N) / t * (100.0 / S);
const exposure = 1.0 / (1.2 * ev100_linear);
return exposure;
}
updateCameraExposure() {
this.camera.setExposure(this.params.aperture, 1.0 / this.params.shutterSpeed, this.params.iso);
// Also update Sun Intensity because it needs to be pre-exposed
this.updateSunIntensity();
}
updateSunIntensity() {
const exposure = this.getExposure();
const preExposedIntensity = this.params.sunIntensity * exposure;
this.skybox.setSunIntensity(preExposedIntensity);
}
updateCameraProjection() {
const width = this.canvas.width;
const height = this.canvas.height;
const aspect = width / height;
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
}
initGUI() {
const gui = new lil.GUI({ title: "Analytic Skybox" });
const self = this;
const sky = this.skybox;
// Initialize local params from skybox defaults
// Initialize local params from skybox defaults
// REMOVED: Do not overwrite this.params from sky.sunDirection (Zenith)
// const currentDir = sky.sunDirection;
// this.params.sunTheta = ...
const updateSun = () => {
const theta = this.params.sunTheta;
const phi = this.params.sunPhi;
const x = Math.sin(theta) * Math.cos(phi);
const y = Math.cos(theta);
const z = Math.sin(theta) * Math.sin(phi);
sky.setSunPosition([x, y, z]);
};
const sunFolder = gui.addFolder('Sun');
// Helper for "Sun Height" cosine slider like C++
const sunHeightParam = { height: Math.cos(this.params.sunTheta) };
sunFolder.add(sunHeightParam, 'height', -0.2, 1.0).name('Height (Cos)').onChange(v => {
this.params.sunTheta = Math.acos(v);
updateSun();
});
sunFolder.add(this.params, 'sunPhi', 0.0, Math.PI * 2).name('Azimuth').onChange(updateSun);
// Updated: Controls params.sunIntensity and triggers updateSunIntensity
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).onChange(v => this.updateSunIntensity());
const sunDisk = sunFolder.addFolder('Disk');
// We need local proxy for sunRadius due to conversion
const diskParams = {
radius: 1.2,
enabled: true // Enable sun disk
};
sky.setSunDiskEnabled(true);
sky.setSunRadius(1.2);
sunDisk.add(diskParams, 'enabled').onChange(v => sky.setSunDiskEnabled(v));
sunDisk.add(diskParams, 'radius', 0.0, 5.0).onChange(v => sky.setSunRadius(v));
sunDisk.add(sky.sunHalo, 1, 0.0, 2.0).name('Limb Darkening').onChange(v => sky.setSunLimbDarkening(v));
sunDisk.add(sky.sunHalo, 2, 0.0, 100.0).name('Intensity Boost').onChange(v => sky.setSunDiskIntensity(v));
const atmFolder = gui.addFolder('Atmosphere');
atmFolder.add(sky, 'turbidity', 1.0, 10.0).onChange(v => sky.setTurbidity(v));
atmFolder.add(sky, 'rayleigh', 0.0, 10.0).onChange(v => sky.setRayleigh(v));
atmFolder.add(sky, 'mieCoefficient', 0.0, 10.0).onChange(v => sky.setMieCoefficient(v));
// Set Ozone default to 0.25
sky.setOzone(0.25);
atmFolder.add(sky, 'ozone', 0.0, 1.0).onChange(v => sky.setOzone(v));
atmFolder.add(sky, 'mieG', 0.0, 0.999).onChange(v => sky.setMieG(v));
const artFolder = gui.addFolder('Artistic');
// Set Horizon Glow default to 1.0
sky.setHorizonGlow(1.0);
sky.msFactors[2] = 1.0;
// Set Contrast default to 0.85
sky.setContrast(0.85);
artFolder.add(sky.msFactors, 0, 0.0, 2.0).name('MS Rayleigh').onChange(v => sky.setMultiScattering(v, sky.msFactors[1]));
artFolder.add(sky.msFactors, 1, 0.0, 2.0).name('MS Mie').onChange(v => sky.setMultiScattering(sky.msFactors[0], v));
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
const shmFolder = artFolder.addFolder('Shimmer');
// Set Shimmer Strength default to 0.0
sky.setShimmerControl(0.0, sky.shimmerControl[1], sky.shimmerControl[2]);
shmFolder.add(sky.shimmerControl, 0, 0.0, 0.1).name('Strength').onChange(v => sky.setShimmerControl(v, sky.shimmerControl[1], sky.shimmerControl[2]));
shmFolder.add(sky.shimmerControl, 1, 1.0, 100.0).name('Frequency').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], v, sky.shimmerControl[2]));
shmFolder.add(sky.shimmerControl, 2, 0.01, 0.5).name('Mask Height').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], sky.shimmerControl[1], v));
const cloudFolder = gui.addFolder('Clouds');
const cParams = {
volumetrics: sky.cloudControl2[1] > 0.5,
coverage: 0.4,
density: 0.02,
height: sky.cloudControl[2],
speed: 50.0,
evolution: 0.02
};
// Apply Cloud Defaults
sky.setCloudControl(0.4, 0.02, cParams.height, 50.0);
sky.setCloudShapeEvolution(0.02);
cloudFolder.add(cParams, 'volumetrics').onChange(v => sky.setCloudVolumetricLighting(v));
cloudFolder.add(cParams, 'coverage', 0.0, 1.0).onChange(v => sky.setCloudControl(v, cParams.density, cParams.height, cParams.speed));
cloudFolder.add(cParams, 'density', 0.0, 1.0).onChange(v => sky.setCloudControl(cParams.coverage, v, cParams.height, cParams.speed));
cloudFolder.add(cParams, 'height', 2000.0, 20000.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, v, cParams.speed));
// Reverse speed calc: w = speed * (0.05 / 72.0)
cloudFolder.add(cParams, 'speed', 0.0, 200.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, cParams.height, v));
cloudFolder.add(cParams, 'evolution', 0.0, 2.0).onChange(v => sky.setCloudShapeEvolution(v));
const waterFolder = gui.addFolder('Water');
const wParams = {
derivativeTrick: true,
strength: 50.0,
speed: 1.0,
octaves: 4.0
};
// Initialize defaults
sky.setWaterControl(50.0, 1.0, 1.0, 4.0); // 1.0 = Derivative Trick On, 4 octaves
const updateWater = () => {
sky.setWaterControl(wParams.strength, wParams.speed, wParams.derivativeTrick ? 1.0 : 0.0, wParams.octaves);
};
waterFolder.add(wParams, 'derivativeTrick').name('Derivative Trick').onChange(updateWater);
waterFolder.add(wParams, 'strength', 10.0, 100.0).onChange(updateWater);
waterFolder.add(wParams, 'speed', 0.0, 5.0).onChange(updateWater);
waterFolder.add(wParams, 'octaves', 1, 8, 1).name('Octaves').onChange(updateWater);
waterFolder.close();
const starFolder = gui.addFolder('Stars');
const sParams = {
enabled: true,
density: 1.0
};
// Initialize defaults (Density 1.0, Enabled True)
sky.setStarControl(1.0, true);
const updateStars = () => {
sky.setStarControl(sParams.density, sParams.enabled);
};
starFolder.add(sParams, 'enabled').name('Enabled').onChange(updateStars);
starFolder.add(sParams, 'density', 0.0, 1.0).name('Density').onChange(updateStars);
starFolder.close();
const camFolder = gui.addFolder('Camera');
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
const bloomFolder = camFolder.addFolder('Bloom');
const bParams = {
enabled: false,
lensFlare: false
};
const updateBloom = () => {
this.view.setBloomOptions({
enabled: bParams.enabled,
lensFlare: bParams.lensFlare
});
};
bloomFolder.add(bParams, 'enabled').onChange(updateBloom);
bloomFolder.add(bParams, 'lensFlare').onChange(updateBloom);
bloomFolder.close();
// Collapse folders by default
sunDisk.close();
atmFolder.close();
artFolder.close();
// shmFolder is inside artFolder, so it's hidden, but we can close it too if we want
shmFolder.close();
cloudFolder.close();
// camFolder left open? User didn't specify, but "Artistic, shimmer and clouds" + "Disk, Atmosphere" were requested.
// So Camera might stay open or close. Let's keep Camera open for now as it wasn't listed.
// Initial sync
updateSun();
this.updateCameraExposure(); // This will trigger updateSunIntensity too
}
initControls() {
// listeners only
this.canvas.addEventListener('mousedown', e => {
this.camState.dragging = true;
this.camState.lastX = e.clientX;
this.camState.lastY = e.clientY;
});
window.addEventListener('mouseup', () => {
this.camState.dragging = false;
});
window.addEventListener('mousemove', e => {
if (!this.camState.dragging) return;
const dx = e.clientX - this.camState.lastX;
const dy = e.clientY - this.camState.lastY;
this.camState.lastX = e.clientX;
this.camState.lastY = e.clientY;
const sensitivity = 0.005;
this.camState.theta -= dx * sensitivity;
this.camState.phi += dy * sensitivity;
// Clamp pitch to avoid flip [ -PI/2, PI/2 ]
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
});
}
render() {
// Update Camera LookAt
const r = 1.0;
const theta = this.camState.theta;
const phi = this.camState.phi;
// Convert spherical to cartesian
// Y is UP. Z is Forward.
// At phi=0, y=0. forward vector should correspond to theta.
// Let's say theta=0 is -Z.
const y = Math.sin(phi);
const h = Math.cos(phi);
const x = h * Math.sin(theta);
const z = -h * Math.cos(theta);
const eye = [0, 0, 0];
const center = [x, y, z];
const up = [0, 1, 0];
this.camera.lookAt(eye, center, up);
this.renderer.render(this.swapChain, this.view);
window.requestAnimationFrame(this.render);
}
resize() {
const dpr = window.devicePixelRatio;
const width = this.canvas.width = window.innerWidth * dpr;
const height = this.canvas.height = window.innerHeight * dpr;
this.view.setViewport([0, 0, width, height]);
const aspect = width / height;
// near=0.1, far=5000.0
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
}
}
+909
View File
@@ -0,0 +1,909 @@
material {
name : SimulatedSkybox,
parameters : [
{
type : float3,
name : sunDirection
},
{
type : float3,
name : sunDirection2
},
{
type : float3,
name : depthR,
precision : high
},
{
type : float3,
name : depthM,
precision : high
},
{
type : float2,
name : miePhaseParams, // x=(1+g^2), y=(-2*g)
precision : high
},
{
type : float,
name : sunIntensity,
precision : high
},
{
type : float,
name : contrast,
precision : high
},
{
type : float3,
name : nightColor,
precision : high
},
{
type : float3,
name : ozone,
precision : high
},
{
type : float4,
name : multiScatParams, // xyz=MultiScatteringColor, w=HorizonGlow
precision : high
},
{
type : float4,
name : sunHalo, // x=Size, y=Limb, z=Intensity, w=Enabled
precision : high
},
{
type : float4,
name : shimmerControl, // x=Strength, y=Frequency, z=MaskHeight, w=PlanetRadius
precision : high
},
{
type : float4,
name : cloudControl, // x=Coverage, y=Density, z=QuadraticConst, w=WindSpeed
precision : high
},
{
type : float4,
name : cloudControl2, // x=EvolutionSpeed
precision : high
},
{
type : float,
name : sunIntensity2,
precision : high
},
{
type : float4,
name : sunHalo2, // x=Size, y=Limb, z=Intensity, w=Enabled
precision : high
},
{
type : float4,
name : waterControl, // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
precision : high
},
{
type : float2,
name : starControl, // x=Density, y=Enabled
precision : high
}
],
variables : [
eyeDirection
],
vertexDomain : device,
depthWrite : false,
shadingModel : unlit,
culling: none
}
vertex {
void materialVertex(inout MaterialVertexInputs material) {
// This code is taken from computeWorldPosition and assumes the vertex domain is 'device'.
highp vec4 p = getPosition();
// GL convention to inverted DX convention
p.z = p.z * -0.5 + 0.5;
highp vec4 worldPosition = getWorldFromClipMatrix() * p;
// Getting the true world position would require dividing by w, but since this is a skybox
// at inifinity, this results in very large numbers for material.eyeDirection.
// Since the eyeDirection is only used as a direction vector in the fragment shader, we can
// skip that step to improve precision.
material.eyeDirection.xyz = worldPosition.xyz;
}
}
fragment {
// ------------------------------------------------------------------------
// Analytic Rayleigh and Mie Scattering (Physics Based)
// Derived from:
// - Hoffman & Preetham (2002): "Real-time Light-Atmosphere Interactions"
// - Henyey & Greenstein (1941): "Diffuse radiation in the galaxy" (Mie Phase)
// - Kasten & Young (1989): "Revised optical air mass tables" (Air Mass)
// - "Simulated Sky" / Three.js (Sky.js): Empirical adjustments for aesthetics
// ------------------------------------------------------------------------
#define PI 3.14159265359
void dummy() {} // squash editor syntax highlighting bugs
// Rayleigh Phase Function: Scattering distribution for small particles (air molecules)
// Lord Rayleigh (1871)
// Normalized to integrate to 4*PI (Boosting brightness by factor PI vs standard 1-normalization)
highp float rayleighPhase(highp float cosTheta) {
const highp float THREE_SIXTEENTH = (3.0 / 16.0);
return THREE_SIXTEENTH * (1.0 + cosTheta * cosTheta);
}
// Henyey-Greenstein Phase Function (Mie)
// Henyey & Greenstein (1941)
// Controls the forward scattering peak (sun halo) via anisotropy parameter 'g'
// Optimized: params.x = (1 + g^2), params.y = (-2 * g)
highp float hgPhase(highp float cosTheta, highp vec2 params) {
const highp float ONE_FOURTH = (1.0 / 4.0);
// Recover (1 - g^2) => 2.0 - (1 + g^2)
highp float oneMinusG2 = 2.0 - params.x;
highp float inverse = 1.0 / pow(params.x + params.y * cosTheta, 1.5);
return ONE_FOURTH * (oneMinusG2 * inverse);
}
// --- Noise Functions for Clouds ---
highp float hash13(highp vec3 p3) {
p3 = fract(p3 * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
highp float noise(highp vec3 p) {
highp vec3 i = floor(p);
highp vec3 f = fract(p);
// Cubic Hermite Interpolation
highp vec3 u = f*f*(3.0-2.0*f);
return mix(mix(mix(hash13(i + vec3(0,0,0)), hash13(i + vec3(1,0,0)), u.x),
mix(hash13(i + vec3(0,1,0)), hash13(i + vec3(1,1,0)), u.x), u.y),
mix(mix(hash13(i + vec3(0,0,1)), hash13(i + vec3(1,0,1)), u.x),
mix(hash13(i + vec3(0,1,1)), hash13(i + vec3(1,1,1)), u.x), u.y), u.z);
}
// Fractal Brownian Motion (4 Octaves)
highp float fbm(highp vec3 p) {
highp float total = 0.0;
highp float amplitude = 0.5;
for (int i = 0; i < 4; i++) {
total += noise(p) * amplitude;
p *= 2.02; // Lacunarity
p += 100.0; // Shift to avoid artifacts
amplitude *= 0.5; // Gain
}
return total;
}
highp float fbm(highp vec3 p, int octaves) {
highp float total = 0.0;
highp float amplitude = 0.5;
for (int i = 0; i < 8; i++) {
if (i >= octaves) break;
total += noise(p) * amplitude;
p *= 2.02;
p += 100.0;
amplitude *= 0.5;
}
return total;
}
// Ray-Sphere Intersection
// Returns distance to intersection or -1.0 if none.
// Re = Planet Radius.
// C = Re^2 - (Re + height)^2 (Precalculated on CPU for precision).
highp float raySphereIntersect(highp vec3 rd, highp float Re, highp float C) {
// Ray Origin is (0, Re, 0) relative to Planet Center (0, 0, 0)
// We solve |(0, Re, 0) + t*rd|^2 = Rm^2
// |O + tD|^2 = R^2
// t^2 + 2t(O.D) + (O^2 - R^2) = 0
// a=1, b=2(O.D), c = O^2 - R^2 = C
// Reduced quadratic: t = -b' +/- sqrt(b'^2 - c) where b' = O.D
highp float b = Re * rd.y; // dot(vec3(0, Re, 0), rd)
highp float disc = b*b - C;
if (disc < 0.0) return -1.0;
// t = -b + sqrt(disc)
return -b + sqrt(disc);
}
// ------------------------------------------------------------------------
// Atmospheric Heat Shimmer (Mirage)
// ------------------------------------------------------------------------
// Simulates heat convection turbulence near the horizon (e.g., hot desert road effect).
//
// PHYSICS:
// Heat rising from the ground creates pockets of varying air density (refractive index).
// This bends light rays, causing a visual "shimmer" or displacement.
//
// IMPLEMENTATION:
// - Perturbs the view vector `V.y` using interleaved sine waves.
// - Uses World Space `V` so the noise is stable under camera rotation.
// - Masked to only affect the horizon line.
//
// PARAMETERS:
// @param V Normalized World View Vector (modified in place).
// @param strength Max vertical displacement amplitude. (e.g. 0.002).
// @param freq Ripple frequency/density. (e.g. 20.0).
// @param maskHeight Horizon mask height (0.0 to 1.0). (e.g. 0.1).
// ------------------------------------------------------------------------
float applyHeatShimmer(inout highp vec3 V, highp float strength, highp float freq, highp float maskHeight) {
if (strength <= 0.0) return 0.0;
// Mask: Strongest at horizon (0.0), fades out by maskHeight.
highp float mask = 1.0 - smoothstep(0.0, maskHeight, abs(V.y));
if (mask > 0.0) {
// Use FBM for organic turbulence (rising heat waves)
highp float time = getUserTime().x;
// Animate upward (y) and slightly drift (x)
highp vec3 p = vec3(V.x * freq, V.y * freq + time * 2.0, time * 0.5);
// We use a cheap noise or FBM. Since we have FBM:
// Use fewer octaves for performance if possible, but 4 is fine.
highp float distortion = fbm(p);
// Remap noise from [0, 1] to [-1, 1] for perturbation
distortion = distortion * 2.0 - 1.0;
// Apply vertical perturbation
V.y += distortion * strength * mask * 0.1;
V = normalize(V);
}
return mask;
}
// ------------------------------------------------------------------------
// Analytic Sky Model (Rayleigh + Mie + Ozone)
// ------------------------------------------------------------------------
// Computes the scattering and transmittance of the atmosphere along the view ray.
//
// PHYSICS:
// - Rayleigh: Scattering by air molecules (Blue sky). High frequency (lambda^-4).
// - Mie: Scattering by aerosols/dust (White haze). Low frequency (lambda^-1.3).
// - Ozone: Absorption layer (Pink sunset). Absorbs green light.
// - Optical Mass: Approximation of path length through spherical atmosphere.
//
// OUTPUTS:
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunIntensity Sun Illuminance (Lux).
// @param depthR Rayleigh Optical Depth (Precalculated).
// @param depthM Mie Optical Depth (Precalculated).
// @param ozone Ozone Absorption (Precalculated).
// @param msFactors Multi-Scattering factors (Rayleigh, Mie, Glow).
// @param mieG Mie Phase Anisotropy.
// @param outTransmittance Output: Atmospheric Transmittance (0..1) along V.
// @return Output: In-Scattered Radiance (The sky color).
// ------------------------------------------------------------------------
highp vec3 getAtmosphere(highp vec3 V, highp vec3 L, highp float sunIntensity,
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
highp vec4 multiScatParams, highp vec2 mieParams,
out highp vec3 outTransmittance) {
highp float cosTheta = dot(V, L);
// 1. Phase Functions
// "Golden Hour" Hack (Three.js Sky.js):
// Remapping cosTheta from [-1, 1] to [0, 1] breaks the symmetry of Rayleigh scattering.
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
highp float mPhase = hgPhase(cosTheta, mieParams);
// 2. Optical Depth (Air Mass)
// Kasten and Young (1989) - Relative Air Mass Model
highp float zenithCos = clamp(V.y, 0.0, 1.0);
highp float zenithAngle = acos(zenithCos);
highp float zenithAngleDeg = zenithAngle * (180.0 / PI);
highp float opticalMass = 1.0 / (zenithCos + 0.15 * pow(93.885 - zenithAngleDeg, -1.253));
// 3. Extinction & Transmittance
highp vec3 totalExtinction = depthR + depthM + ozone;
highp vec3 extinction = totalExtinction * opticalMass;
outTransmittance = exp(-extinction);
// 4. In-Scattering
// Approximate Multi-Scattering (Isotropic Fill) precomputed in C++.
highp vec3 multiScattering = multiScatParams.xyz;
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase) + multiScattering;
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
// Equilibrium Radiance (Source Function)
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
// Single-Scattering Integral: L = L_inf * (1 - exp(-opticalDepth))
highp vec3 sunLight = inScattering * (1.0 - outTransmittance);
// 5. Horizon "Glow" Mix (Artistic Hack)
// multiScatParams.w contains the Horizon Glow Strength
// Uses Sun Elevation (L.y) to only activate during golden hour/twilight.
mediump float horizonMix = saturate(pow(1.0 - L.y, 5.0)) * multiScatParams.w;
highp vec3 horizonGlow = sqrt(inScattering * outTransmittance);
sunLight *= mix(vec3(1.0), horizonGlow, horizonMix);
return sunLight;
}
// ------------------------------------------------------------------------
// Secondary Sun Scattering (Simplified)
// ------------------------------------------------------------------------
// Computes in-scattering for a second light source, reusing precomputed optical depths.
// Skips multi-scattering (ambient) for performance, providing only direct beams/glow.
//
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunIntensity Sun Illuminance.
// @param depthR Rayleigh Optical Depth.
// @param depthM Mie Optical Depth.
// @param ozone Ozone Absorption.
// @param mieParams Mie Phase Params.
// @param transmittance Precomputed Atmospheric Transmittance.
// @return In-Scattered Radiance.
// ------------------------------------------------------------------------
highp vec3 getSecondarySunScattering(highp vec3 V, highp vec3 L, highp float sunIntensity,
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
highp vec2 miePhaseParams, highp vec3 transmittance) {
highp float cosTheta = dot(V, L);
// Phase Functions
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
highp float mPhase = hgPhase(cosTheta, miePhaseParams);
// Scattering
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase);
highp vec3 totalExtinction = depthR + depthM + ozone;
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
return inScattering * (1.0 - transmittance);
}
// ------------------------------------------------------------------------
// Physical Sun Disk
// ------------------------------------------------------------------------
// Renders the Solar Photosphere with limb darkening.
//
// PHYSICS:
// - The sun is not a point light; it has an angular size (~0.53 deg).
// - Limb Darkening: The sun is darker at the edges (limbs) because we see cooler outer layers.
// - Drawn "Behind" the atmosphere, so it is attenuated by Transmittance.
//
// PARAMETERS:
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunParams x=CosRadius, y=LimbDarkening, z=IntensityBoost, w=Enabled.
// @param sunIntensity Peak Sun Illuminance (Lux).
// @param transmittance Atmospheric Transmittance (0..1).
// @return Radiance of the sun disk (if visible and enabled).
// ------------------------------------------------------------------------
highp vec3 getSunDisk(highp vec3 V, highp vec3 L, highp vec4 sunParams,
highp float sunIntensity, highp vec3 transmittance) {
highp float sunCosRadius = sunParams.x;
highp float limbDarkening = sunParams.y;
highp float sunDiskIntensity = sunParams.z;
bool sunEnabled = sunParams.w > 0.5;
highp float cosTheta = dot(V, L);
// Robust edge detection for small angles using (1 - cos)
highp float dist = 1.0 - cosTheta;
highp float diskRadius = max(1e-6, 1.0 - sunCosRadius);
// AA Edge: smoothstep from radius to radius+epsilon
// We invert it because we want 1.0 inside (dist < radius)
highp float sunDiskProfile = 1.0 - smoothstep(diskRadius, diskRadius + 0.00002, dist);
if (sunEnabled && sunDiskProfile > 0.0) {
// Limb Darkening approximation: mu = sqrt(1 - (r/R)^2)
// dist/diskRadius is approx (r/R)^2 for small angles
highp float relativeDist = min(1.0, dist / diskRadius);
highp float mu = sqrt(1.0 - relativeDist);
// Avoid pow(0, 0) which causes NaNs
highp float limbFactor = (limbDarkening < 1e-4) ? 1.0 : pow(mu, limbDarkening);
// Direct Sun Light (Radiance)
// SunIntensity * Transmittance -> Physical Sun Color
// SunDiskIntensity -> Artistic Boost to punch through Mie halo
return sunIntensity * transmittance * limbFactor * sunDiskIntensity * sunDiskProfile;
}
return vec3(0.0);
}
// ------------------------------------------------------------------------
// Procedural Cirrus Clouds
// ------------------------------------------------------------------------
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
//
// IMPLEMENTATION:
// - Modeled as a spherical shell at a specific altitude.
// - Ray-Sphere intersection determines UV layout and distance.
// - Animated using 3D FBM (Fractal Brownian Motion) for shape evolution + Wind drift.
// - Lighting includes Silver Lining (HG Phase) and Atmospheric Extinction.
//
// PARAMETERS:
// @param background Current Sky Color (to be blended with).
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param control x=Coverage, y=Density, z=QuadraticConst(C), w=WindSpeed.
// @param control2 x=EvolutionSpeed.
// @param geometry w=PlanetRadius (Re).
// @param sunIntensity Sun Illuminance.
// @param transmittance Atmospheric Transmittance (Cloud Color Tint).
// @return Sky color composed with clouds.
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Procedural Cirrus Clouds
// ------------------------------------------------------------------------
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
//
// PARAMETERS:
// @param V .
// @param L .
// @param control .
// @param control2 .
// @param geometry .
// @param sunIntensity .
// @param transmittance.
// @param outDensity Output: Cloud Density (0..1).
// @return Cloud Lit Color (pre-multiplied by density? No, just lit color).
// ------------------------------------------------------------------------
highp vec3 getCloudLayer(highp vec3 V, highp vec3 L,
highp vec4 control, highp vec4 control2, highp vec4 geometry,
highp float sunIntensity, highp vec3 transmittance,
out highp float outDensity) {
outDensity = 0.0;
highp float cloudCoverage = control.x;
// Clip clouds below the horizon (Earth occlusion)
// Simple check V.y > 0.0 is sufficient for skybox provided camera is near ground.
if (cloudCoverage > 0.0 && V.y > 0.0) {
highp float Re = geometry.w;
highp float intersectC = control.z;
highp float distToCloud = raySphereIntersect(V, Re, intersectC);
if (distToCloud > 0.0) {
highp vec3 p = V * distToCloud;
highp float speed = control.w;
highp float morphSpeed = control2.x;
highp float time = getUserTime().x;
// UV Mapping (Planar projected onto sphere cap is sufficient for skybox)
// Scale factor 0.05 km^-1
highp vec2 uv = (p.xz * 0.05) + vec2(time * speed * 2.0, 0.0);
// 3D Noise for Morphing
highp float noiseVal = fbm(vec3(uv, time * morphSpeed));
// Remap noise based on coverage.
// Coverage 0.5 -> threshold 0.5. Coverage 1.0 -> threshold 0.0.
highp float threshold = 1.0 - cloudCoverage;
highp float cloudDensity = smoothstep(threshold, threshold + 0.3, noiseVal);
if (cloudDensity > 0.0) {
cloudDensity *= control.y; // Global Density Scalar
cloudDensity = clamp(cloudDensity, 0.0, 1.0);
outDensity = cloudDensity;
// Cloud Lighting
// Silver Lining: Strong forward scattering (Fixed g=0.9 for clouds)
highp float cosTheta = dot(V, L);
// We need separate params for cloud silver lining (g=0.9).
// 1 + 0.9^2 = 1.81. -2*0.9 = -1.8.
// Attenuation (Beer's Law)
// Thick clouds block light.
// 20.0 is an artistic extinction coefficient.
highp float extinction = exp(-cloudDensity * 20.0);
highp float silver = hgPhase(cosTheta, vec2(1.81, -1.8)) * 40.0 * extinction;
// Ambient/Diffuse term.
// We allow some ambient light to pass through even thick clouds (0.05 min)
// so they don't look like black holes.
highp float ambient = 0.1 + 0.4 * extinction;
// Diffuse term (Sun Color) + Silver Lining
highp vec3 cloudLight = sunIntensity * transmittance * (ambient + silver);
// Mix based on density
highp float volumetric = control2.y;
highp float shading = 1.0;
if (volumetric > 0.5) {
// Gradient Lighting (Fake Volumetric Bump)
highp float gradX = dFdx(cloudDensity);
highp float gradY = dFdy(cloudDensity);
// Smaller Z = Steeper Bumps.
// dFdx(density) is typically small (e.g. 0.001).
// We want Normal to have significant X/Y component.
highp vec3 N = normalize(vec3(-gradX, -gradY, 0.001));
// Screen Space Sun Direction
highp vec3 sRight = normalize(dFdx(V));
highp vec3 sUp = normalize(dFdy(V));
highp vec3 L_screen = vec3(dot(L, sRight), dot(L, sUp), 0.5);
L_screen = normalize(L_screen);
shading = dot(N, L_screen);
// Increase contrast: Darker shadows
// dot is [-1, 1]. Map to [0.3, 1.0]
shading = mix(0.3, 1.0, shading * 0.5 + 0.5);
// Darken thick parts (Beer's Law approximation)
// Aggressively darken center of clouds
shading *= (1.0 - cloudDensity * 0.7);
}
return cloudLight * shading;
}
}
}
return vec3(0.0);
}
// ------------------------------------------------------------------------
// Dynamic Tone Mapping
// ------------------------------------------------------------------------
// Applies a contrast curve that varies with sun elevation.
//
// PROBLEM:
// Default linear/gamma tone mapping can make sunsets look washing out.
// Real eyes accept much higher dynamic range at twilight.
//
// SOLUTION:
// - Zenith (Noon): Linear gamma (Exponent 1.0). Physically accurate.
// - Horizon (Sunset): High contrast (Exponent > 1.0). Crushes shadows, boosts color.
//
// @param color Input HDR color.
// @param L Normalized Sun Vector.
// @param contrast Maximum contrast exponent (at horizon). e.g. 1.5.
// @return Tone mapped color.
// ------------------------------------------------------------------------
highp vec3 applyDynamicToneMapping(highp vec3 color, highp vec3 L, highp float contrast) {
float c = saturate(L.y);
// Exponent blends from 'contrast' (at L.y=0) to 1.0 (at L.y=1)
float exponent = mix(contrast, 1.0, sqrt(c));
return pow(max(vec3(0.0), color), vec3(exponent));
}
// ------------------------------------------------------------------------
// Procedural Water Surface
// ------------------------------------------------------------------------
// Simulates an infinite ocean plane at y=0 using screen-space derivatives for normals.
//
// FEATURES:
// - Projected grid for infinite surface.
// - Screen-space wave normal reconstruction (no geometry required).
// - Fresnel reflection of Atmosphere, Sun, and Clouds.
// - Specular highlights (Blinn-Phong).
//
// PARAMETERS:
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunIntensity Sun Illuminance.
// @param depthR Rayleigh Optical Depth.
// @param depthM Mie Optical Depth.
// @param ozone Ozone Absorption.
// @param multiScatParams Multi-Scattering Params.
// @param miePhaseParams Mie Phase Params.
// @param sunHalo Sun Halo Params.
// @param cloudControl Cloud Control Params.
// @param cloudControl2 Cloud Evolution Params.
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
// @return Water surface color.
// ------------------------------------------------------------------------
// 3D Noise for Stars
highp float hash31(highp vec3 p) {
p = fract(p * 0.1031);
p += dot(p, p.yzx + 33.33);
return fract((p.x + p.y) * p.z);
}
highp float getStars(highp vec3 V, highp float density) {
// Simple procedural stars
// We use view vector direction to tile the sky
// Higher frequency = smaller stars
highp float frequency = 300.0;
highp vec3 p = floor(V * frequency);
highp float h = hash31(p);
// Threshold for stars (very sparse)
// param density: 0.0 (none) to 1.0 (max)
// Default threshold was 0.995 (0.5% stars)
// We map density 0.0 -> 1.0 threshold (no stars)
// density 1.0 -> 0.990 threshold (1.0% stars)
highp float threshold = 1.0 - (0.001 + density * 0.009);
highp float star = 0.0;
if (h > threshold) {
// Random brightness
highp float brightness = (h - threshold) / (1.0 - threshold);
star = brightness * 15.0; // Reduced from 50.0 to 15.0
}
return star;
}
// New helper to handle Star Compositing (Fade, Rotation, Occlusion)
highp vec3 getStarLayer(highp vec3 V, highp vec3 L, highp float cloudDensity, highp vec3 transmittance, highp vec2 starControl) {
// starControl.x = Density, .y = Enabled
if (starControl.y < 0.5) return vec3(0.0);
// 1. Fade by Sun Elevation
// Start appearing sooner (when sun is still slightly up), but stay dim.
// 0.10 (5.7 deg up) -> 0.0
// -0.20 (11.5 deg down) -> 1.0
highp float starFade = 1.0 - smoothstep(-0.20, 0.10, L.y);
starFade *= starFade;
if (starFade <= 0.0) return vec3(0.0);
// 2. Rotate to break grid alignment
highp vec3 rotV = vec3(
dot(V, vec3(0.6, 0.8, 0.0)),
dot(V, vec3(-0.8, 0.6, 0.0)),
V.z
);
highp float starVal = getStars(rotV, starControl.x);
if (starVal <= 0.0) return vec3(0.0);
// 3. Cloud Occlusion (Aggressive)
highp float cloudOcclusion = 1.0 - smoothstep(0.0, 1.0, pow(cloudDensity, 0.1));
return vec3(starVal) * transmittance * starFade * cloudOcclusion * 0.1;
}
// ------------------------------------------------------------------------
// Procedural Water Surface
// ------------------------------------------------------------------------
// Simulates an infinite ocean plane at y=0 using screen-space derivatives for normals.
//
// FEATURES:
// - Projected grid for infinite surface.
// - Screen-space wave normal reconstruction (no geometry required).
// - Fresnel reflection of Atmosphere, Sun, and Clouds.
// - Specular highlights (Blinn-Phong).
//
// PARAMETERS:
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunIntensity Sun Illuminance.
// @param depthR Rayleigh Optical Depth.
// @param depthM Mie Optical Depth.
// @param ozone Ozone Absorption.
// @param multiScatParams Multi-Scattering Params.
// @param miePhaseParams Mie Phase Params.
// @param sunHalo Sun Halo Params.
// @param cloudControl Cloud Control Params.
// @param cloudControl2 Cloud Evolution Params.
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
// @return Water surface color.
// ------------------------------------------------------------------------
highp vec3 getWaterColor(highp vec3 V, highp vec3 L,
highp float sunIntensity,
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
highp vec4 multiScatParams, highp vec2 miePhaseParams,
highp vec4 sunHalo,
highp vec4 cloudControl, highp vec4 cloudControl2,
highp vec4 shimmerControl, highp vec4 waterControl) {
// Project to plane y=0
highp float t = -10.0 / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
highp vec2 uv = V.xz * t * 0.05;
highp float time = getUserTime().x;
highp float speed = waterControl.y;
uv += vec2(time * 0.5 * speed, time * 0.2 * speed);
// Wave Normal
// Use screen-space derivatives to compute world-space normal perturbation
// Wave Normal
// Use screen-space derivatives to compute world-space normal perturbation
int octaves = int(max(1.0, waterControl.w));
highp float h = fbm(vec3(uv, time * 0.1 * speed), octaves);
// Reconstruct screen-space basis in world space
// highp vec3 sRight = normalize(dFdx(V)); // Moved inside block
// highp vec3 sUp = normalize(dFdy(V)); // Moved inside block
// Perturb normal based on height gradient
// If h increases in screen-X direction, normal tilts against sRight.
// Fade out perturbation near horizon (V.y -> 0) to reduce aliasing
highp float horizonFade = smoothstep(0.0, 0.5, abs(V.y));
highp float strength = waterControl.x;
highp vec3 N_perturb;
// Derivative Trick Toggle
if (waterControl.z > 0.5) {
// Screen-Space Derivatives (Fast, 1 tap)
// Reconstruct screen-space basis in world space
// If h increases in screen-X direction, normal tilts against sRight.
highp vec3 sRight = normalize(dFdx(V));
highp vec3 sUp = normalize(dFdy(V));
N_perturb = (sRight * dFdx(h) + sUp * dFdy(h)) * strength * horizonFade;
} else {
// Finite Difference (Standard, 3 taps)
// More expensive but analytically correct in world space (independent of view resolution/derivatives)
float eps = 0.02; // Epsilon for gradient
vec3 p = vec3(uv, time * 0.1 * speed);
float hx = fbm(p + vec3(eps, 0.0, 0.0), octaves);
float hy = fbm(p + vec3(0.0, eps, 0.0), octaves);
// Gradient
float dx = (hx - h) / eps;
float dy = (hy - h) / eps;
// Construct World Space Perturbation
// Gradient (dx, dy) acts on XZ plane.
// Normal = normalize(-dx, 1, -dy).
// We want N_perturb to SUBTRACT from (0,1,0).
// N_water = normalize(Up - Perturb).
// So Perturb = (dx, 0, dy).
// Note: Strength needs to be calibrated to match derivative trick roughly, or just raw.
// Derivative trick Strength was ~50.0.
// Here dx/dy are raw noise slopes.
// Reduced to 0.002 to match visual range of derivative trick and prevent black artifacts.
N_perturb = vec3(dx, 0.0, dy) * (strength * 0.002) * horizonFade;
}
highp vec3 N_water = normalize(vec3(0.0, 1.0, 0.0) - N_perturb);
// Reflection
highp vec3 R = reflect(V, N_water);
highp vec3 transRefl;
highp vec3 reflection = getAtmosphere(R, L, sunIntensity,
depthR, depthM,
ozone, multiScatParams,
miePhaseParams,
transRefl);
// Clouds in reflection
highp float reflCloudDensity;
highp vec3 reflCloudLayer = getCloudLayer(R, L, materialParams.cloudControl, materialParams.cloudControl2,
materialParams.shimmerControl, materialParams.sunIntensity, transRefl,
reflCloudDensity);
// Add Stars to Reflection
// Use helper with Reflection Vector and Reflection Cloud Density
// Horizon Mask: Fade out star reflections that are deep in the water (high R.y)
// Restricted to very close to horizon (0.0 to 0.1) as requested.
highp float rHorizonMask = 1.0 - smoothstep(0.0, 0.1, R.y);
if (rHorizonMask > 0.0) {
reflection += getStarLayer(R, L, reflCloudDensity, transRefl, materialParams.starControl) * rHorizonMask;
}
// Add Sun Disk to reflection (Occluded)
highp float reflSunAccess = 1.0 - smoothstep(0.0, 0.7, reflCloudDensity * 1.5);
reflection += getSunDisk(R, L, sunHalo, sunIntensity, transRefl) * reflSunAccess;
// Apply clouds to reflection
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
// Fresnel
highp float F0 = 0.02; // Water
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
highp float F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
highp vec3 deepColor = vec3(0.0, 0.005, 0.02); // Deep blue/black
highp vec3 waterColor = mix(deepColor, reflection, F);
return waterColor;
}
void material(inout MaterialInputs material) {
prepareMaterial(material);
highp vec3 V = normalize(variable_eyeDirection.xyz);
highp vec3 L = normalize(materialParams.sunDirection);
// 1. Heat Shimmer
// Fade out as sun rises (Strongest at horizon, zero at 30 degrees up)
highp float sunFade = 1.0 - smoothstep(0.0, 0.5, abs(L.y));
highp float shimmerIntensity = applyHeatShimmer(V, materialParams.shimmerControl.x * sunFade,
materialParams.shimmerControl.y,
materialParams.shimmerControl.z);
// 2. Atmospheric Scattering
highp vec3 transmittance;
highp vec3 inScatter1 = getAtmosphere(V, L, materialParams.sunIntensity,
materialParams.depthR, materialParams.depthM,
materialParams.ozone, materialParams.multiScatParams,
materialParams.miePhaseParams,
transmittance);
// Sun 2 (Optional)
// We reuse the same Transmittance (view dependent) and Phase params.
// We do NOT add extra Multi-Scattering (Ambient) for the second sun to save cost/complexity.
// It contributes Direct In-Scattering (Beams/Glow) only.
highp vec3 inScatter2 = vec3(0.0);
if (materialParams.sunHalo2.w > 0.5) {
highp vec3 L2 = normalize(materialParams.sunDirection2);
inScatter2 = getSecondarySunScattering(V, L2,
materialParams.sunIntensity2,
materialParams.depthR,
materialParams.depthM,
materialParams.ozone,
materialParams.miePhaseParams,
transmittance);
}
highp vec3 finalColor = inScatter1 + inScatter2;
// 5. Procedural Clouds
highp float cloudDensity;
highp vec3 cloudLayer = getCloudLayer(V, L,
materialParams.cloudControl,
materialParams.cloudControl2,
materialParams.shimmerControl, // reusing w=PlanetRadius
materialParams.sunIntensity,
transmittance,
cloudDensity);
// Add Stars
// Stars are at infinity.
// Use helper function.
finalColor += getStarLayer(V, L, cloudDensity, transmittance, materialParams.starControl);
// 3. Sun Disks - Occluded by clouds
// Sun Access is (1.0 - cloudDensity) but arguably non-linear for sharp disk
highp float sunAccess = 1.0 - smoothstep(0.0, 0.7, cloudDensity * 1.5);
finalColor += getSunDisk(V, L, materialParams.sunHalo,
materialParams.sunIntensity, transmittance) * sunAccess;
if (materialParams.sunHalo2.w > 0.5) {
highp vec3 L2 = normalize(materialParams.sunDirection2);
// Note: Ideally we should compute cloud density for L2 direction if clouds are 3D...
// But here we use V direction clouds (view-based).
// Since clouds are in front of everything, this is correct for view-based occlusion.
finalColor += getSunDisk(V, L2, materialParams.sunHalo2,
materialParams.sunIntensity2, transmittance) * sunAccess;
}
// 4. Night Sky Offset
finalColor += materialParams.nightColor;
// 5. Apply Clouds
finalColor = mix(finalColor, cloudLayer, cloudDensity);
// 6. Dynamic Tone Mapping
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
if (V.y < 0.0) {
finalColor = getWaterColor(V, L, materialParams.sunIntensity,
materialParams.depthR, materialParams.depthM,
materialParams.ozone, materialParams.multiScatParams,
materialParams.miePhaseParams,
materialParams.sunHalo,
materialParams.cloudControl, materialParams.cloudControl2,
materialParams.shimmerControl,
materialParams.waterControl);
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
}
material.baseColor = vec4(finalColor, 1.0);
}
}
+9
View File
@@ -0,0 +1,9 @@
body {
margin: 0;
overflow: hidden;
}
canvas {
touch-action: none;
width: 100%;
height: 100%;
}
+8 -2
View File
@@ -82,16 +82,22 @@
"tools/specular-color/README.md": { "tools/specular-color/README.md": {
"dest": "dup/specular_color.md" "dest": "dup/specular_color.md"
}, },
"tools/specgen/README.md": {
"dest": "dup/specgen.md"
},
"docs_src/README.md": { "docs_src/README.md": {
"dest": "dup/docs.md" "dest": "dup/docs.md"
}, },
"test/renderdiff/README.md": { "test/renderdiff/README.md": {
"dest": "dup/test_renderdiff.md", "dest": "dup/test_ci_renderdiff.md",
"link_transforms": { "link_transforms": {
"docs/images/": "../images/" "docs/images/": "../images/"
} }
}, },
"test/backend/README.md": { "test/backend/README.md": {
"dest": "dup/test_backend.md" "dest": "dup/test_ci_backend.md"
},
"filament/backend/test/README.md": {
"dest": "dup/backend_test.md"
} }
} }
+3
View File
@@ -20,3 +20,6 @@ enable = false
[output.html.fold] [output.html.fold]
enable = false enable = false
[output.html.mathjax]
enable = true
+6 -4
View File
@@ -30,8 +30,9 @@
- [Performance analysis](./notes/performance_analysis.md) - [Performance analysis](./notes/performance_analysis.md)
- [Framegraph](./notes/framegraph.md) - [Framegraph](./notes/framegraph.md)
- [Tests](./notes/tests.md) - [Tests](./notes/tests.md)
- [backend](./dup/test_backend.md) - [backend](./dup/backend_test.md)
- [renderdiff](./dup/test_renderdiff.md) - [CI: backend](./dup/test_ci_backend.md)
- [CI: renderdiff](./dup/test_ci_renderdiff.md)
- [Libraries](./notes/libs.md) - [Libraries](./notes/libs.md)
- [bluegl](./dup/bluegl.md) - [bluegl](./dup/bluegl.md)
- [bluevk](./dup/bluevk.md) - [bluevk](./dup/bluevk.md)
@@ -46,9 +47,10 @@
- [cmgen](./dup/cmgen.md) - [cmgen](./dup/cmgen.md)
- [cso-lut](./dup/cso_lut.md) - [cso-lut](./dup/cso_lut.md)
- [filamesh](./dup/filamesh.md) - [filamesh](./dup/filamesh.md)
- [normal-blending](./dup/normal_blending.md)
- [mipgen](./dup/mipgen.md)
- [matinfo](./dup/matinfo.md) - [matinfo](./dup/matinfo.md)
- [mipgen](./dup/mipgen.md)
- [normal-blending](./dup/normal_blending.md)
- [roughness-prefilter](./dup/roughness_prefilter.md) - [roughness-prefilter](./dup/roughness_prefilter.md)
- [specgen](./dup/specgen.md)
- [specular-color](./dup/specular_color.md) - [specular-color](./dup/specular_color.md)
- [zbloat](./dup/zbloat.md) - [zbloat](./dup/zbloat.md)
+311
View File
@@ -0,0 +1,311 @@
// SimulatedSkybox.js
// Ported from samples/utils/SimulatedSkybox.cpp
class SimulatedSkybox {
constructor(engine) {
this.engine = engine;
// Default Parameters
this.sunDirection = [0, 1, 0];
this.sunIntensity = 100000.0;
this.turbidity = 2.0;
this.rayleigh = 1.0;
this.mieCoefficient = 1.0;
this.mieG = 0.8;
this.ozone = 0.0;
this.msFactors = [0.1, 0.5, 0.0];
this.contrast = 1.0;
this.nightColor = [0.0, 0.0003, 0.00075];
this.shimmerControl = [0.0, 20.0, 0.1];
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
this.starControl = [1.0, 1.0]; // x=Density (0-1), y=Enabled (0-1)
this.planetRadius = 6360.0;
// Sun Halo
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
this.sunHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.5, 1.0, 1.0];
this.initEntity();
}
async loadMaterial(url) {
console.log("Loading material from:", url);
const response = await fetch(url);
const buffer = await response.arrayBuffer();
this.material = this.engine.createMaterial(new Uint8Array(buffer));
this.materialInstance = this.material.createInstance();
// Re-bind the entity with the loaded material
const rcm = this.engine.getRenderableManager();
const instance = rcm.getInstance(this.entity);
rcm.setMaterialInstanceAt(instance, 0, this.materialInstance);
console.log("Material loaded and bound.");
this.updateCoefficients();
}
initEntity() {
const EntityManager = Filament.EntityManager;
const RenderableManager = Filament.RenderableManager;
const VertexBuffer = Filament.VertexBuffer;
const IndexBuffer = Filament.IndexBuffer;
const AttributeType = Filament.VertexBuffer$AttributeType;
const VertexAttribute = Filament.VertexAttribute;
const PrimitiveType = Filament.RenderableManager$PrimitiveType;
const IndexType = Filament.IndexBuffer$IndexType;
this.entity = EntityManager.get().create();
// 3 vertices for full screen triangle
// coords: -1,-1 to 3,-1 to -1,3
const TRIANGLE_VERTICES = new Float32Array([
-1.0, -1.0,
3.0, -1.0,
-1.0, 3.0
]);
const TRIANGLE_INDICES = new Uint16Array([0, 1, 2]);
this.vb = VertexBuffer.Builder()
.vertexCount(3)
.bufferCount(1)
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, 8)
.build(this.engine);
this.vb.setBufferAt(this.engine, 0, TRIANGLE_VERTICES);
this.ib = IndexBuffer.Builder()
.indexCount(3)
.bufferType(IndexType.USHORT)
.build(this.engine);
this.ib.setBuffer(this.engine, TRIANGLE_INDICES);
// We create a dummy material first or wait?
// In JS we usually can't block. We'll rely on loadMaterial being called.
// For now, we build the Renderable without material, then set it later.
RenderableManager.Builder(1)
.geometry(0, PrimitiveType.TRIANGLES, this.vb, this.ib)
.culling(false)
.castShadows(false)
.receiveShadows(false)
.priority(7) // Render behind translucent objects? 7 is skybox priority typically.
.build(this.engine, this.entity);
}
setSunPosition(direction) {
// normalize
const len = Math.hypot(direction[0], direction[1], direction[2]);
if (len > 0) {
this.sunDirection = [direction[0] / len, direction[1] / len, direction[2] / len];
} else {
this.sunDirection = [0, 1, 0];
}
this.updateCoefficients();
}
setSunIntensity(intensity) {
this.sunIntensity = Math.max(0.0, intensity);
this.updateCoefficients();
}
setTurbidity(turbidity) {
this.turbidity = Math.max(0.0, turbidity);
this.updateCoefficients();
}
setRayleigh(rayleigh) {
this.rayleigh = Math.max(0.0, rayleigh);
this.updateCoefficients();
}
setMieCoefficient(mie) {
this.mieCoefficient = Math.max(0.0, mie);
this.updateCoefficients();
}
setMieG(g) {
this.mieG = Math.max(0.0, g);
this.updateCoefficients();
}
setOzone(strength) {
this.ozone = Math.max(0.0, strength);
this.updateCoefficients();
}
setMultiScattering(r, m) {
this.msFactors[0] = Math.max(0.0, Math.min(2.0, r));
this.msFactors[1] = Math.max(0.0, Math.min(2.0, m));
this.updateCoefficients();
}
setHorizonGlow(strength) {
this.msFactors[2] = Math.max(0.0, Math.min(1.0, strength));
this.updateCoefficients();
}
setContrast(contrast) {
this.contrast = contrast;
this.updateCoefficients();
}
setNightColor(color) {
this.nightColor = color;
this.updateCoefficients();
}
setSunRadius(degrees) {
const rad = degrees * (Math.PI / 180.0);
this.sunHalo[0] = Math.cos(rad);
this.updateCoefficients();
}
setSunDiskIntensity(intensity) {
this.sunHalo[2] = Math.max(0.0, intensity);
this.updateCoefficients();
}
setSunLimbDarkening(strength) {
this.sunHalo[1] = Math.max(0.0, strength);
this.updateCoefficients();
}
setSunDiskEnabled(enabled) {
this.sunHalo[3] = enabled ? 1.0 : 0.0;
this.updateCoefficients();
}
setShimmerControl(strength, frequency, maskHeight) {
this.shimmerControl[0] = Math.max(0.0, strength);
this.shimmerControl[1] = Math.max(0.0, frequency);
this.shimmerControl[2] = Math.max(0.001, maskHeight);
this.updateCoefficients();
}
setCloudControl(coverage, density, height, speed) {
this.cloudControl[0] = Math.max(0.0, Math.min(1.0, coverage));
this.cloudControl[1] = Math.max(0.0, density);
this.cloudControl[2] = Math.max(1000.0, height);
// JS speed adjustment logic matches C++: speed * (0.05 / 72.0)
this.cloudControl[3] = speed * (0.05 / 72.0);
this.updateCoefficients();
}
setCloudShapeEvolution(speed) {
this.cloudControl2[0] = speed;
this.updateCoefficients();
}
setCloudVolumetricLighting(enabled) {
this.cloudControl2[1] = enabled ? 1.0 : 0.0;
this.updateCoefficients();
}
setWaterControl(strength, speed, derivativeTrick, octaves) {
this.waterControl[0] = Math.max(0.0, strength);
this.waterControl[1] = Math.max(0.0, speed);
this.waterControl[2] = derivativeTrick;
this.waterControl[3] = Math.max(1.0, Math.min(8.0, octaves));
this.updateCoefficients();
}
setStarControl(density, enabled) {
this.starControl[0] = Math.max(0.0, Math.min(1.0, density));
this.starControl[1] = enabled ? 1.0 : 0.0;
this.updateCoefficients();
}
updateCoefficients() {
if (!this.materialInstance) {
console.warn("updateCoefficients called before material loaded");
return;
}
// 1. Rayleigh Coefficients
const F_PI = Math.PI;
const lambda = [680e-9, 550e-9, 440e-9];
const n = 1.0003;
const N = 2.545e25;
const term = (8.0 * Math.pow(F_PI, 3.0) * Math.pow(n * n - 1.0, 2.0)) / (3.0 * N);
const depthR = [
term / Math.pow(lambda[0], 4.0),
term / Math.pow(lambda[1], 4.0),
term / Math.pow(lambda[2], 4.0)
].map(v => v * 8000.0 * this.rayleigh);
// 2. Mie Coefficients
const mieAlpha = 1.3;
const mieBase = 2.0e-5 * this.turbidity;
const depthM = [
mieBase * Math.pow(550e-9 / lambda[0], mieAlpha),
mieBase * Math.pow(550e-9 / lambda[1], mieAlpha),
mieBase * Math.pow(550e-9 / lambda[2], mieAlpha)
].map(v => v * 1200.0 * this.mieCoefficient);
// Fake Ozone
const ozone = [0.0, this.ozone * 0.1, 0.0];
// Sun Fade (Horizon)
const cutoffAngle = 96.0 * (F_PI / 180.0);
const steepness = 1.5;
const zenithFade = 1.0 - Math.exp(-(cutoffAngle / steepness));
const zenithAngle = Math.acos(Math.max(-1.0, Math.min(1.0, this.sunDirection[1])));
const sunFade = Math.max(0.0, 1.0 - Math.exp(-((cutoffAngle - zenithAngle) / steepness))) / zenithFade;
const physicalSunIntensity = this.sunIntensity * sunFade;
// Radiance Conversion for Sun Halo
// Solid Angle = 2 * PI * (1 - cos(angularRadius))
const solidAngle = 2.0 * F_PI * (1.0 - this.sunHalo[0]);
const radianceConversion = 1.0 / Math.max(1e-9, solidAngle);
const sunHaloUpload = [...this.sunHalo];
sunHaloUpload[2] *= radianceConversion;
// Cloud Intersection
const r = this.planetRadius;
const h = this.cloudControl[2] * 0.001; // m -> km
const intersectC = r * r - (r + h) * (r + h);
const cloudUniform = [...this.cloudControl];
cloudUniform[2] = intersectC;
// Shimmer Uniform
const shimmerUniform = [...this.shimmerControl, r];
// Multi-Scattering Vector
const isotropicPhase = 0.25;
const msVector = depthR.map((v, i) => (v * this.msFactors[0] + depthM[i] * this.msFactors[1]) * isotropicPhase);
// Upload
this.materialInstance.setFloat3Parameter('sunDirection', new Float32Array(this.sunDirection));
this.materialInstance.setFloat3Parameter('depthR', new Float32Array(depthR));
this.materialInstance.setFloat3Parameter('depthM', new Float32Array(depthM));
this.materialInstance.setFloat3Parameter('ozone', new Float32Array(ozone));
this.materialInstance.setFloat4Parameter('sunHalo', new Float32Array(sunHaloUpload));
this.materialInstance.setFloat4Parameter('multiScatParams', new Float32Array([...msVector, this.msFactors[2]]));
// Mie Phase
const g2 = this.mieG * this.mieG;
this.materialInstance.setFloat2Parameter('miePhaseParams', new Float32Array([1.0 + g2, -2.0 * this.mieG]));
this.materialInstance.setFloatParameter('contrast', this.contrast);
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity);
this.materialInstance.setFloat3Parameter('nightColor', new Float32Array(nightColorScaled));
this.materialInstance.setFloat4Parameter('shimmerControl', new Float32Array(shimmerUniform));
this.materialInstance.setFloat4Parameter('cloudControl', new Float32Array(cloudUniform));
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
this.materialInstance.setFloat2Parameter('starControl', new Float32Array(this.starControl));
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
}
}
+10
View File
@@ -0,0 +1,10 @@
# Result: /Users/mathias/sources/git/filament/out/cmake-release/tools/matc/matc
MATC="../../../../out/cmake-release/tools/matc/matc"
# Navigate to script directory to ensure relative paths work
cd "$(dirname "$0")"
set -e
$MATC -a opengl -p mobile -o assets/simulated_skybox.filamat simulated_skybox.mat
echo "Material recompiled to assets/simulated_skybox.filamat"
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Analytic Skybox</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
<link href="styles.css" rel="stylesheet">
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
}
canvas {
touch-action: none;
width: 100%;
height: 100%;
outline: none;
}
</style>
</head>
<body>
<canvas></canvas>
<!-- Filament -->
<script src="filament.js"></script>
<script src="gl-matrix-min.js"></script>
<!-- UI -->
<script src="lil-gui.js"></script>
<!-- App -->
<script src="SimulatedSkybox.js?v=26"></script>
<script src="main.js?v=26"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
+345
View File
@@ -0,0 +1,345 @@
// main.js
Filament.init(['assets/simulated_skybox.filamat'], () => {
window.app = new App(document.getElementsByTagName('canvas')[0]);
});
class App {
constructor(canvas) {
this.canvas = canvas;
const engine = this.engine = Filament.Engine.create(this.canvas);
this.scene = engine.createScene();
this.skybox = new SimulatedSkybox(engine);
this.skybox.entity = this.skybox.entity; // Ensuring access if needed
this.scene.addEntity(this.skybox.entity);
// Load the material explicitly since we passed it to init but SimulatedSkybox needs to bind it
// Actually SimulatedSkybox.loadMaterial fetches it.
// Since we already loaded it in Filament.init, we can arguably just use it if we had a way to access the asset.
// But Filament.init assets are for internal or easy access via assets object if configured?
// Let's just let SimulatedSkybox fetch it again or use a blob if we wanted.
// Simpler: Just let SimulatedSkybox fetch it.
this.skybox.loadMaterial('assets/simulated_skybox.filamat').then(() => {
this.initGUI();
});
this.swapChain = engine.createSwapChain();
this.renderer = engine.createRenderer();
this.camera = engine.createCamera(Filament.EntityManager.get().create());
this.view = engine.createView();
this.view.setCamera(this.camera);
this.view.setScene(this.scene);
// Color Grading
const ColorGrading = Filament.ColorGrading;
const ToneMapping = Filament.ColorGrading$ToneMapping;
this.colorGrading = ColorGrading.Builder()
.toneMapping(ToneMapping.ACES_LEGACY)
.build(engine);
this.view.setColorGrading(this.colorGrading);
this.view.setPostProcessingEnabled(true); // Essential for tone mapping
// Bloom
this.view.setBloomOptions({
enabled: false,
lenseFlare: false
});
// Clear color is not really visible behind skybox, but black is standard
this.renderer.setClearOptions({ clearColor: [0.0, 0.0, 0.0, 1.0], clear: true });
// Camera handling (Exposure)
this.params = {
aperture: 16.0,
shutterSpeed: 125.0,
iso: 100.0,
sunTheta: Math.acos(0.0), // Default Height 0.0 (Horizon)
sunPhi: 0.0,
focalLength: 24.0, // mm
sunIntensity: 100000.0 // Base intensity
};
this.camState = {
theta: Math.PI / 2, // Look at +X (Sun Position at Phi=0)
phi: 0.0,
dragging: false,
lastX: 0,
lastY: 0
};
this.initControls(); // Initialize controls immediately
this.resize();
window.addEventListener('resize', this.resize.bind(this));
this.render = this.render.bind(this);
window.requestAnimationFrame(this.render);
}
getExposure() {
// Formula: 1.0 / ( 1.2 * (N^2 / t) * (S / 100) )
// t = 1/shutterSpeed
const N = this.params.aperture;
const t = 1.0 / this.params.shutterSpeed;
const S = this.params.iso;
const ev100_linear = (N * N) / t * (100.0 / S);
const exposure = 1.0 / (1.2 * ev100_linear);
return exposure;
}
updateCameraExposure() {
this.camera.setExposure(this.params.aperture, 1.0 / this.params.shutterSpeed, this.params.iso);
// Also update Sun Intensity because it needs to be pre-exposed
this.updateSunIntensity();
}
updateSunIntensity() {
const exposure = this.getExposure();
const preExposedIntensity = this.params.sunIntensity * exposure;
this.skybox.setSunIntensity(preExposedIntensity);
}
updateCameraProjection() {
const width = this.canvas.width;
const height = this.canvas.height;
const aspect = width / height;
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
}
initGUI() {
const gui = new lil.GUI({ title: "Analytic Skybox" });
const self = this;
const sky = this.skybox;
// Initialize local params from skybox defaults
// Initialize local params from skybox defaults
// REMOVED: Do not overwrite this.params from sky.sunDirection (Zenith)
// const currentDir = sky.sunDirection;
// this.params.sunTheta = ...
const updateSun = () => {
const theta = this.params.sunTheta;
const phi = this.params.sunPhi;
const x = Math.sin(theta) * Math.cos(phi);
const y = Math.cos(theta);
const z = Math.sin(theta) * Math.sin(phi);
sky.setSunPosition([x, y, z]);
};
const sunFolder = gui.addFolder('Sun');
// Helper for "Sun Height" cosine slider like C++
const sunHeightParam = { height: Math.cos(this.params.sunTheta) };
sunFolder.add(sunHeightParam, 'height', -0.2, 1.0).name('Height (Cos)').onChange(v => {
this.params.sunTheta = Math.acos(v);
updateSun();
});
sunFolder.add(this.params, 'sunPhi', 0.0, Math.PI * 2).name('Azimuth').onChange(updateSun);
// Updated: Controls params.sunIntensity and triggers updateSunIntensity
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).onChange(v => this.updateSunIntensity());
const sunDisk = sunFolder.addFolder('Disk');
// We need local proxy for sunRadius due to conversion
const diskParams = {
radius: 1.2,
enabled: true // Enable sun disk
};
sky.setSunDiskEnabled(true);
sky.setSunRadius(1.2);
sunDisk.add(diskParams, 'enabled').onChange(v => sky.setSunDiskEnabled(v));
sunDisk.add(diskParams, 'radius', 0.0, 5.0).onChange(v => sky.setSunRadius(v));
sunDisk.add(sky.sunHalo, 1, 0.0, 2.0).name('Limb Darkening').onChange(v => sky.setSunLimbDarkening(v));
sunDisk.add(sky.sunHalo, 2, 0.0, 100.0).name('Intensity Boost').onChange(v => sky.setSunDiskIntensity(v));
const atmFolder = gui.addFolder('Atmosphere');
atmFolder.add(sky, 'turbidity', 1.0, 10.0).onChange(v => sky.setTurbidity(v));
atmFolder.add(sky, 'rayleigh', 0.0, 10.0).onChange(v => sky.setRayleigh(v));
atmFolder.add(sky, 'mieCoefficient', 0.0, 10.0).onChange(v => sky.setMieCoefficient(v));
// Set Ozone default to 0.25
sky.setOzone(0.25);
atmFolder.add(sky, 'ozone', 0.0, 1.0).onChange(v => sky.setOzone(v));
atmFolder.add(sky, 'mieG', 0.0, 0.999).onChange(v => sky.setMieG(v));
const artFolder = gui.addFolder('Artistic');
// Set Horizon Glow default to 1.0
sky.setHorizonGlow(1.0);
sky.msFactors[2] = 1.0;
// Set Contrast default to 0.85
sky.setContrast(0.85);
artFolder.add(sky.msFactors, 0, 0.0, 2.0).name('MS Rayleigh').onChange(v => sky.setMultiScattering(v, sky.msFactors[1]));
artFolder.add(sky.msFactors, 1, 0.0, 2.0).name('MS Mie').onChange(v => sky.setMultiScattering(sky.msFactors[0], v));
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
const shmFolder = artFolder.addFolder('Shimmer');
// Set Shimmer Strength default to 0.0
sky.setShimmerControl(0.0, sky.shimmerControl[1], sky.shimmerControl[2]);
shmFolder.add(sky.shimmerControl, 0, 0.0, 0.1).name('Strength').onChange(v => sky.setShimmerControl(v, sky.shimmerControl[1], sky.shimmerControl[2]));
shmFolder.add(sky.shimmerControl, 1, 1.0, 100.0).name('Frequency').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], v, sky.shimmerControl[2]));
shmFolder.add(sky.shimmerControl, 2, 0.01, 0.5).name('Mask Height').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], sky.shimmerControl[1], v));
const cloudFolder = gui.addFolder('Clouds');
const cParams = {
volumetrics: sky.cloudControl2[1] > 0.5,
coverage: 0.4,
density: 0.02,
height: sky.cloudControl[2],
speed: 50.0,
evolution: 0.02
};
// Apply Cloud Defaults
sky.setCloudControl(0.4, 0.02, cParams.height, 50.0);
sky.setCloudShapeEvolution(0.02);
cloudFolder.add(cParams, 'volumetrics').onChange(v => sky.setCloudVolumetricLighting(v));
cloudFolder.add(cParams, 'coverage', 0.0, 1.0).onChange(v => sky.setCloudControl(v, cParams.density, cParams.height, cParams.speed));
cloudFolder.add(cParams, 'density', 0.0, 1.0).onChange(v => sky.setCloudControl(cParams.coverage, v, cParams.height, cParams.speed));
cloudFolder.add(cParams, 'height', 2000.0, 20000.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, v, cParams.speed));
// Reverse speed calc: w = speed * (0.05 / 72.0)
cloudFolder.add(cParams, 'speed', 0.0, 200.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, cParams.height, v));
cloudFolder.add(cParams, 'evolution', 0.0, 2.0).onChange(v => sky.setCloudShapeEvolution(v));
const waterFolder = gui.addFolder('Water');
const wParams = {
derivativeTrick: true,
strength: 50.0,
speed: 1.0,
octaves: 4.0
};
// Initialize defaults
sky.setWaterControl(50.0, 1.0, 1.0, 4.0); // 1.0 = Derivative Trick On, 4 octaves
const updateWater = () => {
sky.setWaterControl(wParams.strength, wParams.speed, wParams.derivativeTrick ? 1.0 : 0.0, wParams.octaves);
};
waterFolder.add(wParams, 'derivativeTrick').name('Derivative Trick').onChange(updateWater);
waterFolder.add(wParams, 'strength', 10.0, 100.0).onChange(updateWater);
waterFolder.add(wParams, 'speed', 0.0, 5.0).onChange(updateWater);
waterFolder.add(wParams, 'octaves', 1, 8, 1).name('Octaves').onChange(updateWater);
waterFolder.close();
const starFolder = gui.addFolder('Stars');
const sParams = {
enabled: true,
density: 1.0
};
// Initialize defaults (Density 1.0, Enabled True)
sky.setStarControl(1.0, true);
const updateStars = () => {
sky.setStarControl(sParams.density, sParams.enabled);
};
starFolder.add(sParams, 'enabled').name('Enabled').onChange(updateStars);
starFolder.add(sParams, 'density', 0.0, 1.0).name('Density').onChange(updateStars);
starFolder.close();
const camFolder = gui.addFolder('Camera');
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
const bloomFolder = camFolder.addFolder('Bloom');
const bParams = {
enabled: false,
lensFlare: false
};
const updateBloom = () => {
this.view.setBloomOptions({
enabled: bParams.enabled,
lensFlare: bParams.lensFlare
});
};
bloomFolder.add(bParams, 'enabled').onChange(updateBloom);
bloomFolder.add(bParams, 'lensFlare').onChange(updateBloom);
bloomFolder.close();
// Collapse folders by default
sunDisk.close();
atmFolder.close();
artFolder.close();
// shmFolder is inside artFolder, so it's hidden, but we can close it too if we want
shmFolder.close();
cloudFolder.close();
// camFolder left open? User didn't specify, but "Artistic, shimmer and clouds" + "Disk, Atmosphere" were requested.
// So Camera might stay open or close. Let's keep Camera open for now as it wasn't listed.
// Initial sync
updateSun();
this.updateCameraExposure(); // This will trigger updateSunIntensity too
}
initControls() {
// listeners only
this.canvas.addEventListener('mousedown', e => {
this.camState.dragging = true;
this.camState.lastX = e.clientX;
this.camState.lastY = e.clientY;
});
window.addEventListener('mouseup', () => {
this.camState.dragging = false;
});
window.addEventListener('mousemove', e => {
if (!this.camState.dragging) return;
const dx = e.clientX - this.camState.lastX;
const dy = e.clientY - this.camState.lastY;
this.camState.lastX = e.clientX;
this.camState.lastY = e.clientY;
const sensitivity = 0.005;
this.camState.theta -= dx * sensitivity;
this.camState.phi += dy * sensitivity;
// Clamp pitch to avoid flip [ -PI/2, PI/2 ]
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
});
}
render() {
// Update Camera LookAt
const r = 1.0;
const theta = this.camState.theta;
const phi = this.camState.phi;
// Convert spherical to cartesian
// Y is UP. Z is Forward.
// At phi=0, y=0. forward vector should correspond to theta.
// Let's say theta=0 is -Z.
const y = Math.sin(phi);
const h = Math.cos(phi);
const x = h * Math.sin(theta);
const z = -h * Math.cos(theta);
const eye = [0, 0, 0];
const center = [x, y, z];
const up = [0, 1, 0];
this.camera.lookAt(eye, center, up);
this.renderer.render(this.swapChain, this.view);
window.requestAnimationFrame(this.render);
}
resize() {
const dpr = window.devicePixelRatio;
const width = this.canvas.width = window.innerWidth * dpr;
const height = this.canvas.height = window.innerHeight * dpr;
this.view.setViewport([0, 0, width, height]);
const aspect = width / height;
// near=0.1, far=5000.0
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
}
}
@@ -0,0 +1,909 @@
material {
name : SimulatedSkybox,
parameters : [
{
type : float3,
name : sunDirection
},
{
type : float3,
name : sunDirection2
},
{
type : float3,
name : depthR,
precision : high
},
{
type : float3,
name : depthM,
precision : high
},
{
type : float2,
name : miePhaseParams, // x=(1+g^2), y=(-2*g)
precision : high
},
{
type : float,
name : sunIntensity,
precision : high
},
{
type : float,
name : contrast,
precision : high
},
{
type : float3,
name : nightColor,
precision : high
},
{
type : float3,
name : ozone,
precision : high
},
{
type : float4,
name : multiScatParams, // xyz=MultiScatteringColor, w=HorizonGlow
precision : high
},
{
type : float4,
name : sunHalo, // x=Size, y=Limb, z=Intensity, w=Enabled
precision : high
},
{
type : float4,
name : shimmerControl, // x=Strength, y=Frequency, z=MaskHeight, w=PlanetRadius
precision : high
},
{
type : float4,
name : cloudControl, // x=Coverage, y=Density, z=QuadraticConst, w=WindSpeed
precision : high
},
{
type : float4,
name : cloudControl2, // x=EvolutionSpeed
precision : high
},
{
type : float,
name : sunIntensity2,
precision : high
},
{
type : float4,
name : sunHalo2, // x=Size, y=Limb, z=Intensity, w=Enabled
precision : high
},
{
type : float4,
name : waterControl, // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
precision : high
},
{
type : float2,
name : starControl, // x=Density, y=Enabled
precision : high
}
],
variables : [
eyeDirection
],
vertexDomain : device,
depthWrite : false,
shadingModel : unlit,
culling: none
}
vertex {
void materialVertex(inout MaterialVertexInputs material) {
// This code is taken from computeWorldPosition and assumes the vertex domain is 'device'.
highp vec4 p = getPosition();
// GL convention to inverted DX convention
p.z = p.z * -0.5 + 0.5;
highp vec4 worldPosition = getWorldFromClipMatrix() * p;
// Getting the true world position would require dividing by w, but since this is a skybox
// at inifinity, this results in very large numbers for material.eyeDirection.
// Since the eyeDirection is only used as a direction vector in the fragment shader, we can
// skip that step to improve precision.
material.eyeDirection.xyz = worldPosition.xyz;
}
}
fragment {
// ------------------------------------------------------------------------
// Analytic Rayleigh and Mie Scattering (Physics Based)
// Derived from:
// - Hoffman & Preetham (2002): "Real-time Light-Atmosphere Interactions"
// - Henyey & Greenstein (1941): "Diffuse radiation in the galaxy" (Mie Phase)
// - Kasten & Young (1989): "Revised optical air mass tables" (Air Mass)
// - "Simulated Sky" / Three.js (Sky.js): Empirical adjustments for aesthetics
// ------------------------------------------------------------------------
#define PI 3.14159265359
void dummy() {} // squash editor syntax highlighting bugs
// Rayleigh Phase Function: Scattering distribution for small particles (air molecules)
// Lord Rayleigh (1871)
// Normalized to integrate to 4*PI (Boosting brightness by factor PI vs standard 1-normalization)
highp float rayleighPhase(highp float cosTheta) {
const highp float THREE_SIXTEENTH = (3.0 / 16.0);
return THREE_SIXTEENTH * (1.0 + cosTheta * cosTheta);
}
// Henyey-Greenstein Phase Function (Mie)
// Henyey & Greenstein (1941)
// Controls the forward scattering peak (sun halo) via anisotropy parameter 'g'
// Optimized: params.x = (1 + g^2), params.y = (-2 * g)
highp float hgPhase(highp float cosTheta, highp vec2 params) {
const highp float ONE_FOURTH = (1.0 / 4.0);
// Recover (1 - g^2) => 2.0 - (1 + g^2)
highp float oneMinusG2 = 2.0 - params.x;
highp float inverse = 1.0 / pow(params.x + params.y * cosTheta, 1.5);
return ONE_FOURTH * (oneMinusG2 * inverse);
}
// --- Noise Functions for Clouds ---
highp float hash13(highp vec3 p3) {
p3 = fract(p3 * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
highp float noise(highp vec3 p) {
highp vec3 i = floor(p);
highp vec3 f = fract(p);
// Cubic Hermite Interpolation
highp vec3 u = f*f*(3.0-2.0*f);
return mix(mix(mix(hash13(i + vec3(0,0,0)), hash13(i + vec3(1,0,0)), u.x),
mix(hash13(i + vec3(0,1,0)), hash13(i + vec3(1,1,0)), u.x), u.y),
mix(mix(hash13(i + vec3(0,0,1)), hash13(i + vec3(1,0,1)), u.x),
mix(hash13(i + vec3(0,1,1)), hash13(i + vec3(1,1,1)), u.x), u.y), u.z);
}
// Fractal Brownian Motion (4 Octaves)
highp float fbm(highp vec3 p) {
highp float total = 0.0;
highp float amplitude = 0.5;
for (int i = 0; i < 4; i++) {
total += noise(p) * amplitude;
p *= 2.02; // Lacunarity
p += 100.0; // Shift to avoid artifacts
amplitude *= 0.5; // Gain
}
return total;
}
highp float fbm(highp vec3 p, int octaves) {
highp float total = 0.0;
highp float amplitude = 0.5;
for (int i = 0; i < 8; i++) {
if (i >= octaves) break;
total += noise(p) * amplitude;
p *= 2.02;
p += 100.0;
amplitude *= 0.5;
}
return total;
}
// Ray-Sphere Intersection
// Returns distance to intersection or -1.0 if none.
// Re = Planet Radius.
// C = Re^2 - (Re + height)^2 (Precalculated on CPU for precision).
highp float raySphereIntersect(highp vec3 rd, highp float Re, highp float C) {
// Ray Origin is (0, Re, 0) relative to Planet Center (0, 0, 0)
// We solve |(0, Re, 0) + t*rd|^2 = Rm^2
// |O + tD|^2 = R^2
// t^2 + 2t(O.D) + (O^2 - R^2) = 0
// a=1, b=2(O.D), c = O^2 - R^2 = C
// Reduced quadratic: t = -b' +/- sqrt(b'^2 - c) where b' = O.D
highp float b = Re * rd.y; // dot(vec3(0, Re, 0), rd)
highp float disc = b*b - C;
if (disc < 0.0) return -1.0;
// t = -b + sqrt(disc)
return -b + sqrt(disc);
}
// ------------------------------------------------------------------------
// Atmospheric Heat Shimmer (Mirage)
// ------------------------------------------------------------------------
// Simulates heat convection turbulence near the horizon (e.g., hot desert road effect).
//
// PHYSICS:
// Heat rising from the ground creates pockets of varying air density (refractive index).
// This bends light rays, causing a visual "shimmer" or displacement.
//
// IMPLEMENTATION:
// - Perturbs the view vector `V.y` using interleaved sine waves.
// - Uses World Space `V` so the noise is stable under camera rotation.
// - Masked to only affect the horizon line.
//
// PARAMETERS:
// @param V Normalized World View Vector (modified in place).
// @param strength Max vertical displacement amplitude. (e.g. 0.002).
// @param freq Ripple frequency/density. (e.g. 20.0).
// @param maskHeight Horizon mask height (0.0 to 1.0). (e.g. 0.1).
// ------------------------------------------------------------------------
float applyHeatShimmer(inout highp vec3 V, highp float strength, highp float freq, highp float maskHeight) {
if (strength <= 0.0) return 0.0;
// Mask: Strongest at horizon (0.0), fades out by maskHeight.
highp float mask = 1.0 - smoothstep(0.0, maskHeight, abs(V.y));
if (mask > 0.0) {
// Use FBM for organic turbulence (rising heat waves)
highp float time = getUserTime().x;
// Animate upward (y) and slightly drift (x)
highp vec3 p = vec3(V.x * freq, V.y * freq + time * 2.0, time * 0.5);
// We use a cheap noise or FBM. Since we have FBM:
// Use fewer octaves for performance if possible, but 4 is fine.
highp float distortion = fbm(p);
// Remap noise from [0, 1] to [-1, 1] for perturbation
distortion = distortion * 2.0 - 1.0;
// Apply vertical perturbation
V.y += distortion * strength * mask * 0.1;
V = normalize(V);
}
return mask;
}
// ------------------------------------------------------------------------
// Analytic Sky Model (Rayleigh + Mie + Ozone)
// ------------------------------------------------------------------------
// Computes the scattering and transmittance of the atmosphere along the view ray.
//
// PHYSICS:
// - Rayleigh: Scattering by air molecules (Blue sky). High frequency (lambda^-4).
// - Mie: Scattering by aerosols/dust (White haze). Low frequency (lambda^-1.3).
// - Ozone: Absorption layer (Pink sunset). Absorbs green light.
// - Optical Mass: Approximation of path length through spherical atmosphere.
//
// OUTPUTS:
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunIntensity Sun Illuminance (Lux).
// @param depthR Rayleigh Optical Depth (Precalculated).
// @param depthM Mie Optical Depth (Precalculated).
// @param ozone Ozone Absorption (Precalculated).
// @param msFactors Multi-Scattering factors (Rayleigh, Mie, Glow).
// @param mieG Mie Phase Anisotropy.
// @param outTransmittance Output: Atmospheric Transmittance (0..1) along V.
// @return Output: In-Scattered Radiance (The sky color).
// ------------------------------------------------------------------------
highp vec3 getAtmosphere(highp vec3 V, highp vec3 L, highp float sunIntensity,
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
highp vec4 multiScatParams, highp vec2 mieParams,
out highp vec3 outTransmittance) {
highp float cosTheta = dot(V, L);
// 1. Phase Functions
// "Golden Hour" Hack (Three.js Sky.js):
// Remapping cosTheta from [-1, 1] to [0, 1] breaks the symmetry of Rayleigh scattering.
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
highp float mPhase = hgPhase(cosTheta, mieParams);
// 2. Optical Depth (Air Mass)
// Kasten and Young (1989) - Relative Air Mass Model
highp float zenithCos = clamp(V.y, 0.0, 1.0);
highp float zenithAngle = acos(zenithCos);
highp float zenithAngleDeg = zenithAngle * (180.0 / PI);
highp float opticalMass = 1.0 / (zenithCos + 0.15 * pow(93.885 - zenithAngleDeg, -1.253));
// 3. Extinction & Transmittance
highp vec3 totalExtinction = depthR + depthM + ozone;
highp vec3 extinction = totalExtinction * opticalMass;
outTransmittance = exp(-extinction);
// 4. In-Scattering
// Approximate Multi-Scattering (Isotropic Fill) precomputed in C++.
highp vec3 multiScattering = multiScatParams.xyz;
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase) + multiScattering;
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
// Equilibrium Radiance (Source Function)
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
// Single-Scattering Integral: L = L_inf * (1 - exp(-opticalDepth))
highp vec3 sunLight = inScattering * (1.0 - outTransmittance);
// 5. Horizon "Glow" Mix (Artistic Hack)
// multiScatParams.w contains the Horizon Glow Strength
// Uses Sun Elevation (L.y) to only activate during golden hour/twilight.
mediump float horizonMix = saturate(pow(1.0 - L.y, 5.0)) * multiScatParams.w;
highp vec3 horizonGlow = sqrt(inScattering * outTransmittance);
sunLight *= mix(vec3(1.0), horizonGlow, horizonMix);
return sunLight;
}
// ------------------------------------------------------------------------
// Secondary Sun Scattering (Simplified)
// ------------------------------------------------------------------------
// Computes in-scattering for a second light source, reusing precomputed optical depths.
// Skips multi-scattering (ambient) for performance, providing only direct beams/glow.
//
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunIntensity Sun Illuminance.
// @param depthR Rayleigh Optical Depth.
// @param depthM Mie Optical Depth.
// @param ozone Ozone Absorption.
// @param mieParams Mie Phase Params.
// @param transmittance Precomputed Atmospheric Transmittance.
// @return In-Scattered Radiance.
// ------------------------------------------------------------------------
highp vec3 getSecondarySunScattering(highp vec3 V, highp vec3 L, highp float sunIntensity,
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
highp vec2 miePhaseParams, highp vec3 transmittance) {
highp float cosTheta = dot(V, L);
// Phase Functions
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
highp float mPhase = hgPhase(cosTheta, miePhaseParams);
// Scattering
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase);
highp vec3 totalExtinction = depthR + depthM + ozone;
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
return inScattering * (1.0 - transmittance);
}
// ------------------------------------------------------------------------
// Physical Sun Disk
// ------------------------------------------------------------------------
// Renders the Solar Photosphere with limb darkening.
//
// PHYSICS:
// - The sun is not a point light; it has an angular size (~0.53 deg).
// - Limb Darkening: The sun is darker at the edges (limbs) because we see cooler outer layers.
// - Drawn "Behind" the atmosphere, so it is attenuated by Transmittance.
//
// PARAMETERS:
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunParams x=CosRadius, y=LimbDarkening, z=IntensityBoost, w=Enabled.
// @param sunIntensity Peak Sun Illuminance (Lux).
// @param transmittance Atmospheric Transmittance (0..1).
// @return Radiance of the sun disk (if visible and enabled).
// ------------------------------------------------------------------------
highp vec3 getSunDisk(highp vec3 V, highp vec3 L, highp vec4 sunParams,
highp float sunIntensity, highp vec3 transmittance) {
highp float sunCosRadius = sunParams.x;
highp float limbDarkening = sunParams.y;
highp float sunDiskIntensity = sunParams.z;
bool sunEnabled = sunParams.w > 0.5;
highp float cosTheta = dot(V, L);
// Robust edge detection for small angles using (1 - cos)
highp float dist = 1.0 - cosTheta;
highp float diskRadius = max(1e-6, 1.0 - sunCosRadius);
// AA Edge: smoothstep from radius to radius+epsilon
// We invert it because we want 1.0 inside (dist < radius)
highp float sunDiskProfile = 1.0 - smoothstep(diskRadius, diskRadius + 0.00002, dist);
if (sunEnabled && sunDiskProfile > 0.0) {
// Limb Darkening approximation: mu = sqrt(1 - (r/R)^2)
// dist/diskRadius is approx (r/R)^2 for small angles
highp float relativeDist = min(1.0, dist / diskRadius);
highp float mu = sqrt(1.0 - relativeDist);
// Avoid pow(0, 0) which causes NaNs
highp float limbFactor = (limbDarkening < 1e-4) ? 1.0 : pow(mu, limbDarkening);
// Direct Sun Light (Radiance)
// SunIntensity * Transmittance -> Physical Sun Color
// SunDiskIntensity -> Artistic Boost to punch through Mie halo
return sunIntensity * transmittance * limbFactor * sunDiskIntensity * sunDiskProfile;
}
return vec3(0.0);
}
// ------------------------------------------------------------------------
// Procedural Cirrus Clouds
// ------------------------------------------------------------------------
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
//
// IMPLEMENTATION:
// - Modeled as a spherical shell at a specific altitude.
// - Ray-Sphere intersection determines UV layout and distance.
// - Animated using 3D FBM (Fractal Brownian Motion) for shape evolution + Wind drift.
// - Lighting includes Silver Lining (HG Phase) and Atmospheric Extinction.
//
// PARAMETERS:
// @param background Current Sky Color (to be blended with).
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param control x=Coverage, y=Density, z=QuadraticConst(C), w=WindSpeed.
// @param control2 x=EvolutionSpeed.
// @param geometry w=PlanetRadius (Re).
// @param sunIntensity Sun Illuminance.
// @param transmittance Atmospheric Transmittance (Cloud Color Tint).
// @return Sky color composed with clouds.
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Procedural Cirrus Clouds
// ------------------------------------------------------------------------
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
//
// PARAMETERS:
// @param V .
// @param L .
// @param control .
// @param control2 .
// @param geometry .
// @param sunIntensity .
// @param transmittance.
// @param outDensity Output: Cloud Density (0..1).
// @return Cloud Lit Color (pre-multiplied by density? No, just lit color).
// ------------------------------------------------------------------------
highp vec3 getCloudLayer(highp vec3 V, highp vec3 L,
highp vec4 control, highp vec4 control2, highp vec4 geometry,
highp float sunIntensity, highp vec3 transmittance,
out highp float outDensity) {
outDensity = 0.0;
highp float cloudCoverage = control.x;
// Clip clouds below the horizon (Earth occlusion)
// Simple check V.y > 0.0 is sufficient for skybox provided camera is near ground.
if (cloudCoverage > 0.0 && V.y > 0.0) {
highp float Re = geometry.w;
highp float intersectC = control.z;
highp float distToCloud = raySphereIntersect(V, Re, intersectC);
if (distToCloud > 0.0) {
highp vec3 p = V * distToCloud;
highp float speed = control.w;
highp float morphSpeed = control2.x;
highp float time = getUserTime().x;
// UV Mapping (Planar projected onto sphere cap is sufficient for skybox)
// Scale factor 0.05 km^-1
highp vec2 uv = (p.xz * 0.05) + vec2(time * speed * 2.0, 0.0);
// 3D Noise for Morphing
highp float noiseVal = fbm(vec3(uv, time * morphSpeed));
// Remap noise based on coverage.
// Coverage 0.5 -> threshold 0.5. Coverage 1.0 -> threshold 0.0.
highp float threshold = 1.0 - cloudCoverage;
highp float cloudDensity = smoothstep(threshold, threshold + 0.3, noiseVal);
if (cloudDensity > 0.0) {
cloudDensity *= control.y; // Global Density Scalar
cloudDensity = clamp(cloudDensity, 0.0, 1.0);
outDensity = cloudDensity;
// Cloud Lighting
// Silver Lining: Strong forward scattering (Fixed g=0.9 for clouds)
highp float cosTheta = dot(V, L);
// We need separate params for cloud silver lining (g=0.9).
// 1 + 0.9^2 = 1.81. -2*0.9 = -1.8.
// Attenuation (Beer's Law)
// Thick clouds block light.
// 20.0 is an artistic extinction coefficient.
highp float extinction = exp(-cloudDensity * 20.0);
highp float silver = hgPhase(cosTheta, vec2(1.81, -1.8)) * 40.0 * extinction;
// Ambient/Diffuse term.
// We allow some ambient light to pass through even thick clouds (0.05 min)
// so they don't look like black holes.
highp float ambient = 0.1 + 0.4 * extinction;
// Diffuse term (Sun Color) + Silver Lining
highp vec3 cloudLight = sunIntensity * transmittance * (ambient + silver);
// Mix based on density
highp float volumetric = control2.y;
highp float shading = 1.0;
if (volumetric > 0.5) {
// Gradient Lighting (Fake Volumetric Bump)
highp float gradX = dFdx(cloudDensity);
highp float gradY = dFdy(cloudDensity);
// Smaller Z = Steeper Bumps.
// dFdx(density) is typically small (e.g. 0.001).
// We want Normal to have significant X/Y component.
highp vec3 N = normalize(vec3(-gradX, -gradY, 0.001));
// Screen Space Sun Direction
highp vec3 sRight = normalize(dFdx(V));
highp vec3 sUp = normalize(dFdy(V));
highp vec3 L_screen = vec3(dot(L, sRight), dot(L, sUp), 0.5);
L_screen = normalize(L_screen);
shading = dot(N, L_screen);
// Increase contrast: Darker shadows
// dot is [-1, 1]. Map to [0.3, 1.0]
shading = mix(0.3, 1.0, shading * 0.5 + 0.5);
// Darken thick parts (Beer's Law approximation)
// Aggressively darken center of clouds
shading *= (1.0 - cloudDensity * 0.7);
}
return cloudLight * shading;
}
}
}
return vec3(0.0);
}
// ------------------------------------------------------------------------
// Dynamic Tone Mapping
// ------------------------------------------------------------------------
// Applies a contrast curve that varies with sun elevation.
//
// PROBLEM:
// Default linear/gamma tone mapping can make sunsets look washing out.
// Real eyes accept much higher dynamic range at twilight.
//
// SOLUTION:
// - Zenith (Noon): Linear gamma (Exponent 1.0). Physically accurate.
// - Horizon (Sunset): High contrast (Exponent > 1.0). Crushes shadows, boosts color.
//
// @param color Input HDR color.
// @param L Normalized Sun Vector.
// @param contrast Maximum contrast exponent (at horizon). e.g. 1.5.
// @return Tone mapped color.
// ------------------------------------------------------------------------
highp vec3 applyDynamicToneMapping(highp vec3 color, highp vec3 L, highp float contrast) {
float c = saturate(L.y);
// Exponent blends from 'contrast' (at L.y=0) to 1.0 (at L.y=1)
float exponent = mix(contrast, 1.0, sqrt(c));
return pow(max(vec3(0.0), color), vec3(exponent));
}
// ------------------------------------------------------------------------
// Procedural Water Surface
// ------------------------------------------------------------------------
// Simulates an infinite ocean plane at y=0 using screen-space derivatives for normals.
//
// FEATURES:
// - Projected grid for infinite surface.
// - Screen-space wave normal reconstruction (no geometry required).
// - Fresnel reflection of Atmosphere, Sun, and Clouds.
// - Specular highlights (Blinn-Phong).
//
// PARAMETERS:
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunIntensity Sun Illuminance.
// @param depthR Rayleigh Optical Depth.
// @param depthM Mie Optical Depth.
// @param ozone Ozone Absorption.
// @param multiScatParams Multi-Scattering Params.
// @param miePhaseParams Mie Phase Params.
// @param sunHalo Sun Halo Params.
// @param cloudControl Cloud Control Params.
// @param cloudControl2 Cloud Evolution Params.
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
// @return Water surface color.
// ------------------------------------------------------------------------
// 3D Noise for Stars
highp float hash31(highp vec3 p) {
p = fract(p * 0.1031);
p += dot(p, p.yzx + 33.33);
return fract((p.x + p.y) * p.z);
}
highp float getStars(highp vec3 V, highp float density) {
// Simple procedural stars
// We use view vector direction to tile the sky
// Higher frequency = smaller stars
highp float frequency = 300.0;
highp vec3 p = floor(V * frequency);
highp float h = hash31(p);
// Threshold for stars (very sparse)
// param density: 0.0 (none) to 1.0 (max)
// Default threshold was 0.995 (0.5% stars)
// We map density 0.0 -> 1.0 threshold (no stars)
// density 1.0 -> 0.990 threshold (1.0% stars)
highp float threshold = 1.0 - (0.001 + density * 0.009);
highp float star = 0.0;
if (h > threshold) {
// Random brightness
highp float brightness = (h - threshold) / (1.0 - threshold);
star = brightness * 15.0; // Reduced from 50.0 to 15.0
}
return star;
}
// New helper to handle Star Compositing (Fade, Rotation, Occlusion)
highp vec3 getStarLayer(highp vec3 V, highp vec3 L, highp float cloudDensity, highp vec3 transmittance, highp vec2 starControl) {
// starControl.x = Density, .y = Enabled
if (starControl.y < 0.5) return vec3(0.0);
// 1. Fade by Sun Elevation
// Start appearing sooner (when sun is still slightly up), but stay dim.
// 0.10 (5.7 deg up) -> 0.0
// -0.20 (11.5 deg down) -> 1.0
highp float starFade = 1.0 - smoothstep(-0.20, 0.10, L.y);
starFade *= starFade;
if (starFade <= 0.0) return vec3(0.0);
// 2. Rotate to break grid alignment
highp vec3 rotV = vec3(
dot(V, vec3(0.6, 0.8, 0.0)),
dot(V, vec3(-0.8, 0.6, 0.0)),
V.z
);
highp float starVal = getStars(rotV, starControl.x);
if (starVal <= 0.0) return vec3(0.0);
// 3. Cloud Occlusion (Aggressive)
highp float cloudOcclusion = 1.0 - smoothstep(0.0, 1.0, pow(cloudDensity, 0.1));
return vec3(starVal) * transmittance * starFade * cloudOcclusion * 0.1;
}
// ------------------------------------------------------------------------
// Procedural Water Surface
// ------------------------------------------------------------------------
// Simulates an infinite ocean plane at y=0 using screen-space derivatives for normals.
//
// FEATURES:
// - Projected grid for infinite surface.
// - Screen-space wave normal reconstruction (no geometry required).
// - Fresnel reflection of Atmosphere, Sun, and Clouds.
// - Specular highlights (Blinn-Phong).
//
// PARAMETERS:
// @param V Normalized View Vector.
// @param L Normalized Sun Vector.
// @param sunIntensity Sun Illuminance.
// @param depthR Rayleigh Optical Depth.
// @param depthM Mie Optical Depth.
// @param ozone Ozone Absorption.
// @param multiScatParams Multi-Scattering Params.
// @param miePhaseParams Mie Phase Params.
// @param sunHalo Sun Halo Params.
// @param cloudControl Cloud Control Params.
// @param cloudControl2 Cloud Evolution Params.
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
// @return Water surface color.
// ------------------------------------------------------------------------
highp vec3 getWaterColor(highp vec3 V, highp vec3 L,
highp float sunIntensity,
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
highp vec4 multiScatParams, highp vec2 miePhaseParams,
highp vec4 sunHalo,
highp vec4 cloudControl, highp vec4 cloudControl2,
highp vec4 shimmerControl, highp vec4 waterControl) {
// Project to plane y=0
highp float t = -10.0 / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
highp vec2 uv = V.xz * t * 0.05;
highp float time = getUserTime().x;
highp float speed = waterControl.y;
uv += vec2(time * 0.5 * speed, time * 0.2 * speed);
// Wave Normal
// Use screen-space derivatives to compute world-space normal perturbation
// Wave Normal
// Use screen-space derivatives to compute world-space normal perturbation
int octaves = int(max(1.0, waterControl.w));
highp float h = fbm(vec3(uv, time * 0.1 * speed), octaves);
// Reconstruct screen-space basis in world space
// highp vec3 sRight = normalize(dFdx(V)); // Moved inside block
// highp vec3 sUp = normalize(dFdy(V)); // Moved inside block
// Perturb normal based on height gradient
// If h increases in screen-X direction, normal tilts against sRight.
// Fade out perturbation near horizon (V.y -> 0) to reduce aliasing
highp float horizonFade = smoothstep(0.0, 0.5, abs(V.y));
highp float strength = waterControl.x;
highp vec3 N_perturb;
// Derivative Trick Toggle
if (waterControl.z > 0.5) {
// Screen-Space Derivatives (Fast, 1 tap)
// Reconstruct screen-space basis in world space
// If h increases in screen-X direction, normal tilts against sRight.
highp vec3 sRight = normalize(dFdx(V));
highp vec3 sUp = normalize(dFdy(V));
N_perturb = (sRight * dFdx(h) + sUp * dFdy(h)) * strength * horizonFade;
} else {
// Finite Difference (Standard, 3 taps)
// More expensive but analytically correct in world space (independent of view resolution/derivatives)
float eps = 0.02; // Epsilon for gradient
vec3 p = vec3(uv, time * 0.1 * speed);
float hx = fbm(p + vec3(eps, 0.0, 0.0), octaves);
float hy = fbm(p + vec3(0.0, eps, 0.0), octaves);
// Gradient
float dx = (hx - h) / eps;
float dy = (hy - h) / eps;
// Construct World Space Perturbation
// Gradient (dx, dy) acts on XZ plane.
// Normal = normalize(-dx, 1, -dy).
// We want N_perturb to SUBTRACT from (0,1,0).
// N_water = normalize(Up - Perturb).
// So Perturb = (dx, 0, dy).
// Note: Strength needs to be calibrated to match derivative trick roughly, or just raw.
// Derivative trick Strength was ~50.0.
// Here dx/dy are raw noise slopes.
// Reduced to 0.002 to match visual range of derivative trick and prevent black artifacts.
N_perturb = vec3(dx, 0.0, dy) * (strength * 0.002) * horizonFade;
}
highp vec3 N_water = normalize(vec3(0.0, 1.0, 0.0) - N_perturb);
// Reflection
highp vec3 R = reflect(V, N_water);
highp vec3 transRefl;
highp vec3 reflection = getAtmosphere(R, L, sunIntensity,
depthR, depthM,
ozone, multiScatParams,
miePhaseParams,
transRefl);
// Clouds in reflection
highp float reflCloudDensity;
highp vec3 reflCloudLayer = getCloudLayer(R, L, materialParams.cloudControl, materialParams.cloudControl2,
materialParams.shimmerControl, materialParams.sunIntensity, transRefl,
reflCloudDensity);
// Add Stars to Reflection
// Use helper with Reflection Vector and Reflection Cloud Density
// Horizon Mask: Fade out star reflections that are deep in the water (high R.y)
// Restricted to very close to horizon (0.0 to 0.1) as requested.
highp float rHorizonMask = 1.0 - smoothstep(0.0, 0.1, R.y);
if (rHorizonMask > 0.0) {
reflection += getStarLayer(R, L, reflCloudDensity, transRefl, materialParams.starControl) * rHorizonMask;
}
// Add Sun Disk to reflection (Occluded)
highp float reflSunAccess = 1.0 - smoothstep(0.0, 0.7, reflCloudDensity * 1.5);
reflection += getSunDisk(R, L, sunHalo, sunIntensity, transRefl) * reflSunAccess;
// Apply clouds to reflection
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
// Fresnel
highp float F0 = 0.02; // Water
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
highp float F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
highp vec3 deepColor = vec3(0.0, 0.005, 0.02); // Deep blue/black
highp vec3 waterColor = mix(deepColor, reflection, F);
return waterColor;
}
void material(inout MaterialInputs material) {
prepareMaterial(material);
highp vec3 V = normalize(variable_eyeDirection.xyz);
highp vec3 L = normalize(materialParams.sunDirection);
// 1. Heat Shimmer
// Fade out as sun rises (Strongest at horizon, zero at 30 degrees up)
highp float sunFade = 1.0 - smoothstep(0.0, 0.5, abs(L.y));
highp float shimmerIntensity = applyHeatShimmer(V, materialParams.shimmerControl.x * sunFade,
materialParams.shimmerControl.y,
materialParams.shimmerControl.z);
// 2. Atmospheric Scattering
highp vec3 transmittance;
highp vec3 inScatter1 = getAtmosphere(V, L, materialParams.sunIntensity,
materialParams.depthR, materialParams.depthM,
materialParams.ozone, materialParams.multiScatParams,
materialParams.miePhaseParams,
transmittance);
// Sun 2 (Optional)
// We reuse the same Transmittance (view dependent) and Phase params.
// We do NOT add extra Multi-Scattering (Ambient) for the second sun to save cost/complexity.
// It contributes Direct In-Scattering (Beams/Glow) only.
highp vec3 inScatter2 = vec3(0.0);
if (materialParams.sunHalo2.w > 0.5) {
highp vec3 L2 = normalize(materialParams.sunDirection2);
inScatter2 = getSecondarySunScattering(V, L2,
materialParams.sunIntensity2,
materialParams.depthR,
materialParams.depthM,
materialParams.ozone,
materialParams.miePhaseParams,
transmittance);
}
highp vec3 finalColor = inScatter1 + inScatter2;
// 5. Procedural Clouds
highp float cloudDensity;
highp vec3 cloudLayer = getCloudLayer(V, L,
materialParams.cloudControl,
materialParams.cloudControl2,
materialParams.shimmerControl, // reusing w=PlanetRadius
materialParams.sunIntensity,
transmittance,
cloudDensity);
// Add Stars
// Stars are at infinity.
// Use helper function.
finalColor += getStarLayer(V, L, cloudDensity, transmittance, materialParams.starControl);
// 3. Sun Disks - Occluded by clouds
// Sun Access is (1.0 - cloudDensity) but arguably non-linear for sharp disk
highp float sunAccess = 1.0 - smoothstep(0.0, 0.7, cloudDensity * 1.5);
finalColor += getSunDisk(V, L, materialParams.sunHalo,
materialParams.sunIntensity, transmittance) * sunAccess;
if (materialParams.sunHalo2.w > 0.5) {
highp vec3 L2 = normalize(materialParams.sunDirection2);
// Note: Ideally we should compute cloud density for L2 direction if clouds are 3D...
// But here we use V direction clouds (view-based).
// Since clouds are in front of everything, this is correct for view-based occlusion.
finalColor += getSunDisk(V, L2, materialParams.sunHalo2,
materialParams.sunIntensity2, transmittance) * sunAccess;
}
// 4. Night Sky Offset
finalColor += materialParams.nightColor;
// 5. Apply Clouds
finalColor = mix(finalColor, cloudLayer, cloudDensity);
// 6. Dynamic Tone Mapping
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
if (V.y < 0.0) {
finalColor = getWaterColor(V, L, materialParams.sunIntensity,
materialParams.depthR, materialParams.depthM,
materialParams.ozone, materialParams.multiScatParams,
materialParams.miePhaseParams,
materialParams.sunHalo,
materialParams.cloudControl, materialParams.cloudControl2,
materialParams.shimmerControl,
materialParams.waterControl);
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
}
material.baseColor = vec4(finalColor, 1.0);
}
}
+9
View File
@@ -0,0 +1,9 @@
body {
margin: 0;
overflow: hidden;
}
canvas {
touch-action: none;
width: 100%;
height: 100%;
}
@@ -180,10 +180,6 @@ private:
int32_t setProducerThrottlingEnabled(EGLNativeWindowType nativeWindow, bool enabled) const; int32_t setProducerThrottlingEnabled(EGLNativeWindowType nativeWindow, bool enabled) const;
struct InitializeJvmForPerformanceManagerIfNeeded {
InitializeJvmForPerformanceManagerIfNeeded();
};
struct ExternalTextureAndroid : public ExternalTexture { struct ExternalTextureAndroid : public ExternalTexture {
EGLImageKHR eglImage = EGL_NO_IMAGE; EGLImageKHR eglImage = EGL_NO_IMAGE;
}; };
@@ -191,7 +187,6 @@ private:
int mOSVersion; int mOSVersion;
ExternalStreamManagerAndroid& mExternalStreamManager; ExternalStreamManagerAndroid& mExternalStreamManager;
AndroidDetails& mAndroidDetails; AndroidDetails& mAndroidDetails;
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
utils::PerformanceHintManager mPerformanceHintManager; utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession; utils::PerformanceHintManager::Session mPerformanceHintSession;
using clock = std::chrono::high_resolution_clock; using clock = std::chrono::high_resolution_clock;
+17 -7
View File
@@ -81,14 +81,8 @@ DriverBase::DriverBase(const Platform::DriverConfig& driverConfig) noexcept
DriverBase::~DriverBase() noexcept { DriverBase::~DriverBase() noexcept {
assert_invariant(mCallbacks.empty()); assert_invariant(mCallbacks.empty());
assert_invariant(mServiceThreadCallbackQueue.empty());
if constexpr (UTILS_HAS_THREADING) { if constexpr (UTILS_HAS_THREADING) {
// quit our service thread stopServiceThread();
std::unique_lock<std::mutex> lock(mServiceThreadLock);
mExitRequested = true;
mServiceThreadCondition.notify_one();
lock.unlock();
mServiceThread.join();
} }
} }
@@ -185,6 +179,22 @@ void DriverBase::debugCommandEnd(CommandStream* cmds, bool synchronous,
} }
} }
#if UTILS_HAS_THREADING
void DriverBase::stopServiceThread() noexcept {
if (!mServiceThread.joinable()) {
return;
}
{
std::lock_guard<std::mutex> lock(mServiceThreadLock);
mExitRequested = true;
}
mServiceThreadCondition.notify_one();
mServiceThread.join();
assert_invariant(mServiceThreadCallbackQueue.empty());
}
#endif
size_t Driver::getElementTypeSize(ElementType type) noexcept { size_t Driver::getElementTypeSize(ElementType type) noexcept {
switch (type) { switch (type) {
case ElementType::BYTE: return sizeof(int8_t); case ElementType::BYTE: return sizeof(int8_t);
+6
View File
@@ -173,6 +173,8 @@ struct HwTimerQuery : public HwBase {
/* /*
* Base class of all Driver implementations * Base class of all Driver implementations
* If multithreading is supported, this class creates a `ServiceThread` responsible for executing
* user handlers.
*/ */
class DriverBase : public Driver { class DriverBase : public Driver {
@@ -236,6 +238,10 @@ protected:
void debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override; void debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override;
void debugCommandEnd(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override; void debugCommandEnd(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override;
// Stops the `ServiceThread`. This method is called during destruction but may be called
// explicitly if earlier shutdown is needed. This method is idempotent.
void stopServiceThread() noexcept;
private: private:
const Platform::DriverConfig mDriverConfig; const Platform::DriverConfig mDriverConfig;
@@ -382,9 +382,18 @@ void OpenGLDriver::terminate() {
delete mCurrentPushConstants; delete mCurrentPushConstants;
mCurrentPushConstants = nullptr; mCurrentPushConstants = nullptr;
// Flush all pending asynchronous tasks. Some tasks may end up posting follow-up operations to
// the `ServiceThread` (e.g., via CountdownCallbackHandler or any user-provided handlers). So we
// early stop the ServiceThread to ensure these are processed as well. Tasks posted to the main
// thread (due to no user handler) during this process are handled later by `Driver::purge`
// within `FEngine::shutdown`.
if (getJobWorker()) { if (getJobWorker()) {
getJobWorker()->terminate(); getJobWorker()->terminate();
} }
if constexpr (UTILS_HAS_THREADING) {
stopServiceThread();
}
mContext.terminate(); mContext.terminate();
mPlatform.terminate(); mPlatform.terminate();
} }
@@ -1089,9 +1089,16 @@ UTILS_NOINLINE
/* static */ std::string_view process_countBits(OpenGLContext& context) noexcept { /* static */ std::string_view process_countBits(OpenGLContext& context) noexcept {
using namespace std::literals; using namespace std::literals;
// bitCount is available in GL 4.0 and GLES 3.1.
if (context.isAtLeastGL<4, 0>() || context.isAtLeastGLES<3, 1>()) { if (context.isAtLeastGL<4, 0>() || context.isAtLeastGLES<3, 1>()) {
return ""sv; return ""sv;
} }
// GLES 2.0 does not support bitwise operations or unsigned integers.
if (context.isES2()) {
return ""sv;
}
return R"( return R"(
// https://graphics.stanford.edu/%7Eseander/bithacks.html // https://graphics.stanford.edu/%7Eseander/bithacks.html
int bitCount(highp uint value) { int bitCount(highp uint value) {
@@ -119,18 +119,6 @@ struct PlatformEGLAndroid::AndroidDetails {
// --------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need
// to attach this thread to the JVM before we initialize PerformanceHintManager.
// This should be done in PerformanceHintManager(), but libutils doesn't have access to
// VirtualMachineEnv.
if (PerformanceHintManager::isSupported()) {
(void)VirtualMachineEnv::get().getEnvironment();
}
}
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::PlatformEGLAndroid() noexcept PlatformEGLAndroid::PlatformEGLAndroid() noexcept
: mExternalStreamManager(ExternalStreamManagerAndroid::create()), : mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) { mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
@@ -145,6 +133,7 @@ PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
} }
void PlatformEGLAndroid::terminate() noexcept { void PlatformEGLAndroid::terminate() noexcept {
mPerformanceHintManager.terminate();
mAndroidDetails.androidFrameCallback.terminate(); mAndroidDetails.androidFrameCallback.terminate();
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager); ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
PlatformEGL::terminate(); PlatformEGL::terminate();
@@ -223,6 +212,14 @@ void PlatformEGLAndroid::preCommit() noexcept {
Driver* PlatformEGLAndroid::createDriver(void* sharedContext, Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
const DriverConfig& driverConfig) { const DriverConfig& driverConfig) {
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need
// to attach this thread to the JVM before we initialize PerformanceHintManager.
if (PerformanceHintManager::isSupported()) {
(void)VirtualMachineEnv::get().getEnvironment();
}
mPerformanceHintManager.init();
// the refresh rate default value doesn't matter, we change it later // the refresh rate default value doesn't matter, we change it later
int32_t const tid = gettid(); int32_t const tid = gettid();
mPerformanceHintSession = PerformanceHintManager::Session{ mPerformanceHintSession = PerformanceHintManager::Session{
@@ -2553,6 +2553,11 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
VkPipelineLayout const pipelineLayout = mPipelineLayoutCache.getLayout(vklayouts, program); VkPipelineLayout const pipelineLayout = mPipelineLayoutCache.getLayout(vklayouts, program);
if (pipelineLayout != mPipelineState.pipelineLayout) { if (pipelineLayout != mPipelineState.pipelineLayout) {
bindPipelineImpl(bundle.pipelineState, pipelineLayout, bundle.descriptorSetMask); bindPipelineImpl(bundle.pipelineState, pipelineLayout, bundle.descriptorSetMask);
// Any push constants received while the layout was null were not written out. They were
// added to a list of pending constants, so we'll flush them now.
// Further push constant updates after the pipeline is bound will immediately set
// the constants because the layout is non-null (see VulkanProgram::writePushConstant).
program->flushPushConstants(pipelineLayout);
} }
mPipelineState.bindInDraw.first = false; mPipelineState.bindInDraw.first = false;
} }
@@ -387,6 +387,15 @@ VulkanProgram::~VulkanProgram() {
delete mInfo; delete mInfo;
} }
void VulkanProgram::flushPushConstants(VkPipelineLayout layout) {
// At this point, we really ought to have a VkPipelineLayout.
assert_invariant(layout != VK_NULL_HANDLE);
for (const auto& c : mQueuedPushConstants) {
mInfo->pushConstantDescription.write(c.cmdbuf, layout, c.stage, c.index, c.value);
}
mQueuedPushConstants.clear();
}
// Creates a special "default" render target (i.e. associated with the swap chain) // Creates a special "default" render target (i.e. associated with the swap chain)
VulkanRenderTarget::VulkanRenderTarget() VulkanRenderTarget::VulkanRenderTarget()
: HwRenderTarget(0, 0), : HwRenderTarget(0, 0),
+25 -1
View File
@@ -262,6 +262,14 @@ struct VulkanProgram : public HwProgram, fvkmemory::Resource {
VulkanProgram(VkDevice device, Program const& builder) noexcept; VulkanProgram(VkDevice device, Program const& builder) noexcept;
~VulkanProgram(); ~VulkanProgram();
/**
* Writes out any queued push constants using the provided VkPipelineLayout.
*
* @param layout The layout that is to be used along with these push constants,
* in the next draw call.
*/
void flushPushConstants(VkPipelineLayout layout);
inline VkShaderModule getVertexShader() const { inline VkShaderModule getVertexShader() const {
return mInfo->shaders[0]; return mInfo->shaders[0];
} }
@@ -278,7 +286,15 @@ struct VulkanProgram : public HwProgram, fvkmemory::Resource {
inline void writePushConstant(VkCommandBuffer cmdbuf, VkPipelineLayout layout, inline void writePushConstant(VkCommandBuffer cmdbuf, VkPipelineLayout layout,
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) { backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
mInfo->pushConstantDescription.write(cmdbuf, layout, stage, index, value); // It's possible that we don't have the layout yet. When external samplers are used, bindPipeline()
// in VulkanDriver returns early, without binding a layout. If that happens, the layout is not
// set until draw time. Any push constants that are written during that time should be saved for
// later, and flushed when the layout is set.
if (layout != VK_NULL_HANDLE) {
mInfo->pushConstantDescription.write(cmdbuf, layout, stage, index, value);
} else {
mQueuedPushConstants.push_back({cmdbuf, stage, index, value});
}
} }
// TODO: handle compute shaders. // TODO: handle compute shaders.
@@ -295,8 +311,16 @@ private:
PushConstantDescription pushConstantDescription; PushConstantDescription pushConstantDescription;
}; };
struct PushConstantInfo {
VkCommandBuffer cmdbuf;
backend::ShaderStage stage;
uint8_t index;
backend::PushConstantVariant value;
};
PipelineInfo* mInfo; PipelineInfo* mInfo;
VkDevice mDevice = VK_NULL_HANDLE; VkDevice mDevice = VK_NULL_HANDLE;
std::vector<PushConstantInfo> mQueuedPushConstants;
}; };
// The render target bundles together a set of attachments, each of which can have one of the // The render target bundles together a set of attachments, each of which can have one of the
+15 -8
View File
@@ -1472,6 +1472,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) }; const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
assert_invariant(srcTarget); assert_invariant(srcTarget);
uint32_t srcMipLevel = 0;
wgpu::Texture srcTexture{ nullptr }; wgpu::Texture srcTexture{ nullptr };
if (srcTarget->isDefaultRenderTarget()) { if (srcTarget->isDefaultRenderTarget()) {
assert_invariant(mSwapChain); assert_invariant(mSwapChain);
@@ -1482,6 +1483,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
// TODO we are currently assuming the first attachment is the desired texture. // TODO we are currently assuming the first attachment is the desired texture.
if (colorAttachmentInfos[0].handle) { if (colorAttachmentInfos[0].handle) {
auto texture = handleCast<WebGPUTexture>(colorAttachmentInfos[0].handle); auto texture = handleCast<WebGPUTexture>(colorAttachmentInfos[0].handle);
srcMipLevel = colorAttachmentInfos[0].level;
if (texture) { if (texture) {
srcTexture = texture->getTexture(); srcTexture = texture->getTexture();
} }
@@ -1494,15 +1496,20 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
return; return;
} }
const uint32_t srcWidth {srcTexture.GetWidth()}; const uint32_t srcWidth {srcTexture.GetWidth()};
const uint32_t srcHeight{srcTexture.GetHeight()}; const uint32_t srcHeight {srcTexture.GetHeight()};
const uint32_t srcMipLevelCount {srcTexture.GetMipLevelCount()};
constexpr uint32_t minValidTextureSize = 1;
const uint32_t mipLeveledSrcWidth = std::max(minValidTextureSize, srcWidth >> srcMipLevel);
const uint32_t mipLeveledSrcHeight = std::max(minValidTextureSize, srcHeight >> srcMipLevel);
// Clamp read region to texture bounds // Clamp read region to texture bounds
if (UTILS_UNLIKELY(x >= srcWidth || y >= srcHeight)) { if (UTILS_UNLIKELY(x >= mipLeveledSrcWidth || y >= mipLeveledSrcHeight)) {
scheduleDestroy(std::move(pixelBufferDescriptor)); scheduleDestroy(std::move(pixelBufferDescriptor));
return; return;
} }
auto actualWidth{ std::min(width, srcWidth - x) }; auto actualWidth{ std::min(width, mipLeveledSrcWidth - x) };
auto actualHeight{ std::min(height, srcHeight - y)}; auto actualHeight{ std::min(height, mipLeveledSrcHeight - y)};
if (UTILS_UNLIKELY(actualWidth == 0 || actualHeight == 0)) { if (UTILS_UNLIKELY(actualWidth == 0 || actualHeight == 0)) {
scheduleDestroy(std::move(pixelBufferDescriptor)); scheduleDestroy(std::move(pixelBufferDescriptor));
return; return;
@@ -1526,6 +1533,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
// If the source format is different from the destination (e.g. BGRA vs RGBA), // If the source format is different from the destination (e.g. BGRA vs RGBA),
// we need to perform a conversion using an intermediate blit. // we need to perform a conversion using an intermediate blit.
if (conversionNecessary(srcFormat, dstFormat, pixelBufferDescriptor.type)) { if (conversionNecessary(srcFormat, dstFormat, pixelBufferDescriptor.type)) {
// TODO: check if the blit process here is correct when mipmap level > 0
const wgpu::TextureDescriptor stagingDescriptor{ const wgpu::TextureDescriptor stagingDescriptor{
.label = "readpixels_staging_texture", .label = "readpixels_staging_texture",
.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment, .usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment,
@@ -1536,7 +1544,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
.depthOrArrayLayers = 1, .depthOrArrayLayers = 1,
}, },
.format = dstFormat, .format = dstFormat,
.mipLevelCount = 1, .mipLevelCount = srcMipLevelCount,
.sampleCount = srcTexture.GetSampleCount(), .sampleCount = srcTexture.GetSampleCount(),
}; };
stagingTexture = mDevice.CreateTexture(&stagingDescriptor); stagingTexture = mDevice.CreateTexture(&stagingDescriptor);
@@ -1586,12 +1594,11 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
// WebGPU's texture coordinates for copies are top-left, but Filament's y-coordinate is // WebGPU's texture coordinates for copies are top-left, but Filament's y-coordinate is
// bottom-left. We must flip the y-coordinate relative to the texture we are reading from. // bottom-left. We must flip the y-coordinate relative to the texture we are reading from.
const uint32_t textureHeight{ textureToReadFrom.GetHeight() }; const uint32_t flippedY{ mipLeveledSrcHeight - readY - actualHeight };
const uint32_t flippedY{ textureHeight - readY - actualHeight };
const wgpu::TexelCopyTextureInfo source{ const wgpu::TexelCopyTextureInfo source{
.texture = textureToReadFrom, // Read from the original or intermediate texture .texture = textureToReadFrom, // Read from the original or intermediate texture
.mipLevel = 0, .mipLevel = srcMipLevel,
.origin = {.x = readX, .y = flippedY, .z = 0,}, .origin = {.x = readX, .y = flippedY, .z = 0,},
}; };
const wgpu::TexelCopyBufferInfo destination{ const wgpu::TexelCopyBufferInfo destination{
+4 -2
View File
@@ -83,8 +83,10 @@ TEST_F(BackendTest, ScissorViewportRegion) {
Handle<HwTexture> srcTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, Handle<HwTexture> srcTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS)); TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
Handle<HwTexture> depthTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::DEPTH16, 1, 512, 512, 1, TextureUsage::DEPTH_ATTACHMENT)); Handle<HwTexture> depthTexture =
addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::DEPTH16, 1,
512, 512, 1, TextureUsage::DEPTH_ATTACHMENT | TextureUsage::SAMPLEABLE));
// Render into the bottom-left quarter of the texture. // Render into the bottom-left quarter of the texture.
Viewport srcRect = { Viewport srcRect = {
+83 -9
View File
@@ -31,10 +31,29 @@
namespace filament { namespace filament {
/** /**
* MorphTargetBuffer is used to hold morphing data (positions and tangents). * A container for vertex morphing data that supports both automatic and manual morphing.
* *
* Both positions and tangents are required. * MorphTargetBuffer operates in a hybrid model depending on the attribute being morphed:
* *
* 1. Automatic for Built-ins (positions/tangents):
* Enable via `withPositions(true)` or `withTangents(true)`. The MorphTargetBuffer will
* allocate internal storage and hold the data for these attributes, which you upload
* via `setPositionsAt()` or `setTangentsAt()`. The framework automatically applies
* the morphing logic in the vertex shader.
*
* 2. Manual for Custom Data (e.g., UVs, colors):
* The MorphTargetBuffer does NOT hold data for custom targets. The user is responsible
* for the full data pipeline:
* - Create and manage a separate `Texture` to hold the morph target data (offsets).
* - In the material, declare a `sampler2d_array` parameter.
* - Bind the `Texture` to the material instance.
* - In the vertex shader, manually call `morphData2`, `morphData3`, or `morphData4`
* with the custom sampler to apply the morphing.
*
* A MorphTargetBuffer object must be associated with a Renderable via
* `RenderableManager::Builder::morphing()` to enable the morphing pipeline for all cases.
*
* @see RenderableManager
*/ */
class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI {
struct BuilderDetails; struct BuilderDetails;
@@ -91,6 +110,46 @@ public:
*/ */
Builder& name(utils::StaticString const& name) noexcept; Builder& name(utils::StaticString const& name) noexcept;
/**
* Enables and allocates the built-in buffer for position morphing.
*
* If enabled, `setPositionsAt` can be called to set the position data for each target.
* The vertex position will be morphed automatically without any further actions.
*
* @param enable true to enable, false to disable. Default is true.
* @return A reference to this Builder for chaining calls.
*/
Builder& withPositions(bool enable = true) noexcept;
/**
* Enables and allocates the built-in buffer for tangent/normal morphing.
*
* If enabled, `setTangentsAt` can be called to set the tangent data for each target.
* The vertex position will be morphed automatically without any further actions.
*
* @param enable true to enable, false to disable. Default is true.
* @return A reference to this Builder for chaining calls.
*/
Builder& withTangents(bool enable = true) noexcept;
/**
* Enables the custom morphing pipeline.
*
* When enabled, the `morphData2`, `morphData3`, and `morphData4` helper functions are
* available in the vertex shader. You must provide a 2D array texture containing the morph
* deltas, bind it to a `sampler2DArray` uniform, and call the appropriate `morphData`
* function to apply the morphing to your custom attributes.
*
* Note: Unlike `withPositions` or `withTangents`, this does NOT allocate any internal
* storage. You are responsible for managing the morph data texture.
*
* Custom morphing can be used together with automatic position and/or tangent morphing.
*
* @param enable true to enable, false to disable. Default is false.
* @return A reference to this Builder for chaining calls.
*/
Builder& enableCustomMorphing(bool enable) noexcept;
/** /**
* Creates the MorphTargetBuffer object and returns a pointer to it. * Creates the MorphTargetBuffer object and returns a pointer to it.
* *
@@ -110,16 +169,14 @@ public:
/** /**
* Updates positions for the given morph target. * Updates positions for the given morph target.
* *
* This method can only be called if the MorphTargetBuffer was built with `withPositions(true)`.
* This is equivalent to the float4 method, but uses 1.0 for the 4th component. * This is equivalent to the float4 method, but uses 1.0 for the 4th component.
* *
* Both positions and tangents must be provided.
*
* @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer.
* @param targetIndex the index of morph target to be updated. * @param targetIndex the index of morph target to be updated.
* @param positions pointer to at least "count" positions * @param positions pointer to at least "count" positions
* @param count number of float3 vectors in positions * @param count number of float3 vectors in positions
* @param offset offset into the target buffer, expressed as a number of float4 vectors * @param offset offset into the target buffer, expressed as a number of float3 vectors
* @see setTangentsAt
*/ */
void setPositionsAt(Engine& engine, size_t targetIndex, void setPositionsAt(Engine& engine, size_t targetIndex,
math::float3 const* UTILS_NONNULL positions, size_t count, size_t offset = 0); math::float3 const* UTILS_NONNULL positions, size_t count, size_t offset = 0);
@@ -127,14 +184,13 @@ public:
/** /**
* Updates positions for the given morph target. * Updates positions for the given morph target.
* *
* Both positions and tangents must be provided. * This method can only be called if the MorphTargetBuffer was built with `withPositions(true)`.
* *
* @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer.
* @param targetIndex the index of morph target to be updated. * @param targetIndex the index of morph target to be updated.
* @param positions pointer to at least "count" positions * @param positions pointer to at least "count" positions
* @param count number of float4 vectors in positions * @param count number of float4 vectors in positions
* @param offset offset into the target buffer, expressed as a number of float4 vectors * @param offset offset into the target buffer, expressed as a number of float4 vectors
* @see setTangentsAt
*/ */
void setPositionsAt(Engine& engine, size_t targetIndex, void setPositionsAt(Engine& engine, size_t targetIndex,
math::float4 const* UTILS_NONNULL positions, size_t count, size_t offset = 0); math::float4 const* UTILS_NONNULL positions, size_t count, size_t offset = 0);
@@ -142,6 +198,7 @@ public:
/** /**
* Updates tangents for the given morph target. * Updates tangents for the given morph target.
* *
* This method can only be called if the MorphTargetBuffer was built with `withTangents(true)`.
* These quaternions must be represented as signed shorts, where real numbers in the [-1,+1] * These quaternions must be represented as signed shorts, where real numbers in the [-1,+1]
* range multiplied by 32767. * range multiplied by 32767.
* *
@@ -150,7 +207,6 @@ public:
* @param tangents pointer to at least "count" tangents * @param tangents pointer to at least "count" tangents
* @param count number of short4 quaternions in tangents * @param count number of short4 quaternions in tangents
* @param offset offset into the target buffer, expressed as a number of short4 vectors * @param offset offset into the target buffer, expressed as a number of short4 vectors
* @see setPositionsAt
*/ */
void setTangentsAt(Engine& engine, size_t targetIndex, void setTangentsAt(Engine& engine, size_t targetIndex,
math::short4 const* UTILS_NONNULL tangents, size_t count, size_t offset = 0); math::short4 const* UTILS_NONNULL tangents, size_t count, size_t offset = 0);
@@ -167,6 +223,24 @@ public:
*/ */
size_t getCount() const noexcept; size_t getCount() const noexcept;
/**
* Returns true if this MorphTargetBuffer has a position buffer.
* @see Builder::withPositions
*/
bool hasPositions() const noexcept;
/**
* Returns true if this MorphTargetBuffer has a tangent buffer.
* @see Builder::withTangents
*/
bool hasTangents() const noexcept;
/**
* Returns true if custom morphing is enabled
* @see Builder::enableCustomMorphing
*/
bool isCustomMorphingEnabled() const noexcept;
protected: protected:
// prevent heap allocation // prevent heap allocation
~MorphTargetBuffer() = default; ~MorphTargetBuffer() = default;
@@ -165,6 +165,17 @@ public:
STATIC //!< skinning/morphing not allowed and Vertex/IndexBuffer immutables STATIC //!< skinning/morphing not allowed and Vertex/IndexBuffer immutables
}; };
/**
* Type of morphing for a Renderable.
* This usually acts as a bitmask of multiple types.
*/
enum class MorphType : uint8_t {
NONE = 0,
POSITION = 1,
TANGENT = 2,
CUSTOM = 4
};
/** /**
* Creates a builder for renderable components. * Creates a builder for renderable components.
* *
+26 -3
View File
@@ -45,6 +45,7 @@ namespace filament {
* - ACESLegacyToneMapper * - ACESLegacyToneMapper
* - FilmicToneMapper * - FilmicToneMapper
* - PBRNeutralToneMapper * - PBRNeutralToneMapper
* - GT7ToneMapper
* - Debug/validation tone mapping operators * - Debug/validation tone mapping operators
* - LinearToneMapper * - LinearToneMapper
* - DisplayRangeToneMapper * - DisplayRangeToneMapper
@@ -72,15 +73,17 @@ struct UTILS_PUBLIC ToneMapper {
/** /**
* If true, then this function holds that f(x) = vec3(f(x.r), f(x.g), f(x.b)) * If true, then this function holds that f(x) = vec3(f(x.r), f(x.g), f(x.b))
* *
* This may be used to indicate that the color grading's LUT only requires a 1D texture instead * This may be used to indicate that the color grading's LUT only requires a
* of a 3D texture, potentially saving a significant amount of memory and generation time. * 1D texture instead of a 3D texture, potentially saving a significant amount
* of memory and generation time.
*/ */
virtual bool isOneDimensional() const noexcept { return false; } virtual bool isOneDimensional() const noexcept { return false; }
/** /**
* True if this tonemapper only works in low-dynamic-range. * True if this tonemapper only works in low-dynamic-range.
* *
* This may be used to indicate that the color grading's LUT doesn't need to be log encoded. * This may be used to indicate that the color grading's LUT doesn't need to be
* log encoded.
*/ */
virtual bool isLDR() const noexcept { return false; } virtual bool isLDR() const noexcept { return false; }
}; };
@@ -156,6 +159,26 @@ struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper {
bool isLDR() const noexcept override { return false; } bool isLDR() const noexcept override { return false; }
}; };
/**
* Gran Turismo 7 tone mapping operator. This tone mapper was designed
* to preserve the appearance of materials across lighting conditions while
* avoiding artifacts in the highlights in high dynamic range conditions.
* This tone mapper targets an SDR paper white value of 250 nits, with a
* reference luminance of 100 cd/m^2 (a value of 1.0 in the HDR framebuffer).
*/
struct UTILS_PUBLIC GT7ToneMapper final : public ToneMapper {
GT7ToneMapper() noexcept;
~GT7ToneMapper() noexcept final;
math::float3 operator()(math::float3 x) const noexcept override;
bool isOneDimensional() const noexcept override { return false; }
bool isLDR() const noexcept override { return false; }
private:
struct State;
State* mState;
};
/** /**
* AgX tone mapping operator. * AgX tone mapping operator.
*/ */
+68 -40
View File
@@ -218,39 +218,6 @@ inline constexpr float chromaticityCoordinateIlluminantD(float const x) noexcept
return 2.87f * x - 3.0f * x * x - 0.275f; return 2.87f * x - 3.0f * x * x - 0.275f;
} }
//------------------------------------------------------------------------------
// Color space conversions
//------------------------------------------------------------------------------
inline constexpr XYZ xyY_to_XYZ(xyY const v) noexcept {
const float a = v.z / max(v.y, 1e-5f);
return XYZ{v.x * a, v.z, (1.0f - v.x - v.y) * a};
}
inline constexpr xyY XYZ_to_xyY(XYZ v) noexcept {
return {v.xy / max(v.x + v.y + v.z, 1e-5f), v.y};
}
inline constexpr float3 pow3(float3 const x) noexcept {
return x * x * x;
}
inline float3 sRGB_to_OkLab(float3 const x) noexcept {
return OkLab_LMS_to_OkLab * cbrt(sRGB_to_OkLab_LMS * x);
}
inline float3 Rec2020_to_OkLab(float3 const x) noexcept {
return OkLab_LMS_to_OkLab * cbrt(Rec2020_to_OkLab_LMS * x);
}
inline float3 OkLab_to_sRGB(float3 const x) noexcept {
return OkLab_LMS_to_sRGB * pow3(OkLab_to_OkLab_LMS * x);
}
inline float3 OkLab_to_Rec2020(float3 const x) noexcept {
return OkLab_LMS_to_Rec2020 * pow3(OkLab_to_OkLab_LMS * x);
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Conversion functions and encoding/decoding // Conversion functions and encoding/decoding
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -322,34 +289,95 @@ inline float3 EOTF_sRGB(float3 x) noexcept {
return x; return x;
} }
inline float3 OETF_PQ(float3 x, float maxPqValue) { inline float3 OETF_PQ(float3 x) {
constexpr float PQ_constant_N = 2610.0f / 4096.0f / 4.0f; constexpr float PQ_constant_N = 2610.0f / 4096.0f / 4.0f;
constexpr float PQ_constant_M = 2523.0f / 4096.0f * 128.0f; constexpr float PQ_constant_M = 2523.0f / 4096.0f * 128.0f;
constexpr float PQ_constant_C1 = 3424.0f / 4096.0f; constexpr float PQ_constant_C1 = 3424.0f / 4096.0f;
constexpr float PQ_constant_C2 = 2413.0f / 4096.0f * 32.0f; constexpr float PQ_constant_C2 = 2413.0f / 4096.0f * 32.0f;
constexpr float PQ_constant_C3 = 2392.0f / 4096.0f * 32.0f; constexpr float PQ_constant_C3 = 2392.0f / 4096.0f * 32.0f;
constexpr float PQ_C = 10000.0f;
x /= maxPqValue; float3 g = pow(x / PQ_C, PQ_constant_N);
float3 g = pow(x, PQ_constant_N);
float3 numerator = PQ_constant_C1 + PQ_constant_C2 * g; float3 numerator = PQ_constant_C1 + PQ_constant_C2 * g;
float3 denominator = 1.0f + PQ_constant_C3 * g; float3 denominator = 1.0f + PQ_constant_C3 * g;
return pow(numerator / denominator, PQ_constant_M); return pow(numerator / denominator, PQ_constant_M);
} }
inline float3 EOTF_PQ(float3 x, float maxPqValue) { inline float3 EOTF_PQ(float3 x) {
constexpr float PQ_constant_N = 2610.0f / 4096.0f / 4.0f; constexpr float PQ_constant_N = 2610.0f / 4096.0f / 4.0f;
constexpr float PQ_constant_M = 2523.0f / 4096.0f * 128.0f; constexpr float PQ_constant_M = 2523.0f / 4096.0f * 128.0f;
constexpr float PQ_constant_C1 = 3424.0f / 4096.0f; constexpr float PQ_constant_C1 = 3424.0f / 4096.0f;
constexpr float PQ_constant_C2 = 2413.0f / 4096.0f * 32.0f; constexpr float PQ_constant_C2 = 2413.0f / 4096.0f * 32.0f;
constexpr float PQ_constant_C3 = 2392.0f / 4096.0f * 32.0f; constexpr float PQ_constant_C3 = 2392.0f / 4096.0f * 32.0f;
constexpr float PQ_C = 10000.0f;
float3 g = pow(x, 1.0f / PQ_constant_M); float3 g = pow(saturate(x), 1.0f / PQ_constant_M);
float3 numerator = max(g - PQ_constant_C1, 0.0f); float3 numerator = max(g - PQ_constant_C1, 0.0f);
float3 denominator = PQ_constant_C2 - (PQ_constant_C3 * g); float3 denominator = PQ_constant_C2 - (PQ_constant_C3 * g);
float3 linearColor = pow(numerator / denominator, 1.0f / PQ_constant_N); float3 linearColor = pow(numerator / denominator, 1.0f / PQ_constant_N);
return linearColor * maxPqValue; return linearColor * PQ_C;
}
//------------------------------------------------------------------------------
// Color space conversions
//------------------------------------------------------------------------------
inline constexpr XYZ xyY_to_XYZ(xyY const v) noexcept {
const float a = v.z / max(v.y, 1e-5f);
return XYZ{v.x * a, v.z, (1.0f - v.x - v.y) * a};
}
inline constexpr xyY XYZ_to_xyY(XYZ v) noexcept {
return {v.xy / max(v.x + v.y + v.z, 1e-5f), v.y};
}
inline constexpr float3 pow3(float3 const x) noexcept {
return x * x * x;
}
inline float3 sRGB_to_OkLab(float3 const x) noexcept {
return OkLab_LMS_to_OkLab * cbrt(sRGB_to_OkLab_LMS * x);
}
inline float3 Rec2020_to_OkLab(float3 const x) noexcept {
return OkLab_LMS_to_OkLab * cbrt(Rec2020_to_OkLab_LMS * x);
}
inline float3 OkLab_to_sRGB(float3 const x) noexcept {
return OkLab_LMS_to_sRGB * pow3(OkLab_to_OkLab_LMS * x);
}
inline float3 OkLab_to_Rec2020(float3 const x) noexcept {
return OkLab_LMS_to_Rec2020 * pow3(OkLab_to_OkLab_LMS * x);
}
inline float3 Rec2020_to_ICtCp(float3 const x) noexcept {
const float3 lmsPQ = OETF_PQ(float3{
(x.r * 1688.0f + x.g * 2146.0f + x.b * 262.0f) / 4096.0f,
(x.r * 683.0f + x.g * 2951.0f + x.b * 462.0f) / 4096.0f,
(x.r * 99.0f + x.g * 309.0f + x.b * 3688.0f) / 4096.0f
});
return float3{
(2048.0f * lmsPQ.x + 2048.0f * lmsPQ.y) / 4096.0f,
(6610.0f * lmsPQ.x - 13613.0f * lmsPQ.y + 7003.0f * lmsPQ.z) / 4096.0f,
(17933.0f * lmsPQ.x - 17390.0f * lmsPQ.y - 543.0f * lmsPQ.z) / 4096.0f
};
}
inline float3 ICtCp_to_Rec2020(float3 const x) noexcept {
const float3 linear = EOTF_PQ(float3{
x.r + 0.00860904f * x.g + 0.11103f * x.b,
x.r - 0.00860904f * x.g - 0.11103f * x.b,
x.r + 0.560031f * x.g - 0.320627f * x.b
});
return float3{
std::max(3.43661f * linear.x - 2.50645f * linear.y + 0.0698454f * linear.z, 0.0f),
std::max(-0.79133f * linear.x + 1.9836f * linear.y - 0.192271f * linear.z, 0.0f),
std::max(-0.0259499f * linear.x - 0.0989137f * linear.y + 1.12486f * linear.z, 0.0f)
};
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
+12 -1
View File
@@ -43,5 +43,16 @@ size_t MorphTargetBuffer::getCount() const noexcept {
return downcast(this)->getCount(); return downcast(this)->getCount();
} }
} // namespace filament bool MorphTargetBuffer::hasPositions() const noexcept {
return downcast(this)->hasPositions();
}
bool MorphTargetBuffer::hasTangents() const noexcept {
return downcast(this)->hasTangents();
}
bool MorphTargetBuffer::isCustomMorphingEnabled() const noexcept {
return downcast(this)->isCustomMorphingEnabled();
}
} // namespace filament
+9 -8
View File
@@ -611,7 +611,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
// calculate the per-primitive face winding order inversion // calculate the per-primitive face winding order inversion
bool const inverseFrontFaces = viewInverseFrontFaces ^ soaVisibility[i].reversedWindingOrder; bool const inverseFrontFaces = viewInverseFrontFaces ^ soaVisibility[i].reversedWindingOrder;
bool const hasMorphing = soaSkinningData[i].morphing; bool const hasMorphing = soaSkinningData[i].morphType != RenderableManager::Builder::MorphType::NONE;
bool const hasSkinning = soaSkinningData[i].skinning; bool const hasSkinning = soaSkinningData[i].skinning;
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing; bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
@@ -940,31 +940,32 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
// Maximum space occupied in the CircularBuffer by a single `Command`. This must be // Maximum space occupied in the CircularBuffer by a single `Command`. This must be
// reevaluated when the inner loop below adds DriverApi commands or when we change the // reevaluated when the inner loop below adds DriverApi commands or when we change the
// CommandStream protocol. Currently, the maximum is 248 bytes. // CommandStream protocol. Currently, the maximum is 272 bytes.
// The batch size is calculated by adding the size of all commands that can possibly be // The batch size is calculated by adding the size of all commands that can possibly be
// emitted per draw call: // emitted per draw call:
constexpr size_t maxCommandSizeInBytes = constexpr size_t maxCommandSizeInBytes =
sizeof(COMMAND_TYPE(scissor)) + sizeof(COMMAND_TYPE(scissor)) +
sizeof(COMMAND_TYPE(bindDescriptorSet)) + sizeof(COMMAND_TYPE(bindDescriptorSet)) +
sizeof(COMMAND_TYPE(bindDescriptorSet)) + sizeof(COMMAND_TYPE(bindDescriptorSet)) + backend::CustomCommand::align(sizeof(NoopCommand) + 4) +
sizeof(COMMAND_TYPE(bindPipeline)) + sizeof(COMMAND_TYPE(bindPipeline)) +
sizeof(COMMAND_TYPE(bindRenderPrimitive)) + sizeof(COMMAND_TYPE(bindRenderPrimitive)) +
sizeof(COMMAND_TYPE(bindDescriptorSet)) + backend::CustomCommand::align(sizeof(NoopCommand) + 8) + sizeof(COMMAND_TYPE(bindDescriptorSet)) + backend::CustomCommand::align(sizeof(NoopCommand) + 8) +
sizeof(COMMAND_TYPE(setPushConstant)) + sizeof(COMMAND_TYPE(setPushConstant)) +
sizeof(COMMAND_TYPE(draw2)); sizeof(COMMAND_TYPE(draw2));
// Add 20% margin for safety
constexpr size_t safeCommandSizeInBytes = maxCommandSizeInBytes + (maxCommandSizeInBytes / 5);
// Number of Commands that can be issued and guaranteed to fit in the current // Number of Commands that can be issued and guaranteed to fit in the current
// CircularBuffer allocation. In practice, we'll have tons of headroom especially if // CircularBuffer allocation. With a 2 MiB buffer (the default) a batch is
// skinning and morphing aren't used. With a 2 MiB buffer (the default) a batch is // 6432 commands (i.e. draw calls).
// 6553 commands (i.e. draw calls). size_t const batchCommandCount = capacity / safeCommandSizeInBytes;
size_t const batchCommandCount = capacity / maxCommandSizeInBytes;
while(first != last) { while(first != last) {
Command const* const batchLast = std::min(first + batchCommandCount, last); Command const* const batchLast = std::min(first + batchCommandCount, last);
// actual number of commands we need to write (can be smaller than batchCommandCount) // actual number of commands we need to write (can be smaller than batchCommandCount)
size_t const commandCount = batchLast - first; size_t const commandCount = batchLast - first;
size_t const commandSizeInBytes = commandCount * maxCommandSizeInBytes; size_t const commandSizeInBytes = commandCount * safeCommandSizeInBytes;
// check we have enough capacity to write these commandCount commands, if not, // check we have enough capacity to write these commandCount commands, if not,
// request a new CircularBuffer allocation of `capacity` bytes. // request a new CircularBuffer allocation of `capacity` bytes.
+182
View File
@@ -256,6 +256,188 @@ float3 PBRNeutralToneMapper::operator()(float3 color) const noexcept {
return mix(color, float3(newPeak), g); return mix(color, float3(newPeak), g);
} }
//------------------------------------------------------------------------------
// GT7 tone mapper
//------------------------------------------------------------------------------
// The following implementation is based on code provided by Polyphony Digital,
// under the following license:
//
// MIT License
//
// Copyright (c) 2025 Polyphony Digital Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// See "Driving Toward Reality: Physically Based Tone Mapping and Perceptual
// Fidelity in Gran Turismo 7", SIGGRAPH 2025, by Yasutomi, Suzuki, and Uchimura.
constexpr float ReferenceLuminance = 100.0f; // 100 cd/m^2 equals a value of 1 in the framebuffer
constexpr float SdrPaperWhite = 250.0f; // Paper white target of 250 nits
inline float frameBufferValueToPhysicalValue(float v) {
// Converts a linear framebuffer value to physical luminance (cd/m^2)
// where 1.0 corresponds to the reference luminance.
return v * ReferenceLuminance;
}
inline float3 frameBufferValueToPhysicalValue(float3 v) {
return v * ReferenceLuminance;
}
inline float physicalValueToFrameBufferValue(float v) {
// Converts a physical luminance (cd/m^2) to a linear framebuffer value,
// where 1.0 corresponds to the reference luminance.
return v / ReferenceLuminance;
}
inline float3 physicalValueToFrameBufferValue(float3 v) {
return v / ReferenceLuminance;
}
class GT7Curve {
public:
float evaluate(const float x) const {
if (x < 0.0f) return 0.0f;
const float weightLinear = smoothstep(0.0f, mMidPoint, x);
const float weightToe = 1.0f - weightLinear;
// Shoulder mapping for highlights.
const float shoulder = mKa + mKb * std::expf(x * mKc);
if (x < mLinearSection * mPeakIntensity) {
const float toeMapped = mMidPoint * std::powf(x / mMidPoint, mToeStrength);
return weightToe * toeMapped + weightLinear * x;
}
return shoulder;
}
void initialize(
const float displayIntensity,
const float alpha,
const float grayPoint,
const float linearSection,
const float toeStrength
) {
mPeakIntensity = displayIntensity;
mAlpha = alpha;
mMidPoint = grayPoint;
mLinearSection = linearSection;
mToeStrength = toeStrength;
// Pre-compute constants for the shoulder region.
const float k = (mLinearSection - 1.0f) / (mAlpha - 1.0f);
mKa = mPeakIntensity * mLinearSection + mPeakIntensity * k;
mKb = -mPeakIntensity * k * std::expf(mLinearSection / k);
mKc = -1.0f / (k * mPeakIntensity);
}
private:
float mPeakIntensity{};
float mAlpha{};
float mMidPoint{};
float mLinearSection{};
float mToeStrength{};
float mKa{};
float mKb{};
float mKc{};
};
struct GT7ToneMapper::State {
float sdrCorrectionFactor;
float framebufferLuminanceTarget;
float framebufferLuminanceTargetUcs;
float blendRatio;
float fadeStart;
float fadeEnd;
GT7Curve curve;
// The display target luminance should be ~250 nits for SDR displays,
// and whatever peak luminance an HDR display supports (700, 1000, etc.).
void initializeParameters(float sdrCorrection, float displayTargetLuminance) {
sdrCorrectionFactor = sdrCorrection;
framebufferLuminanceTarget = physicalValueToFrameBufferValue(displayTargetLuminance);
// TODO: We could expose the curve parameters to users
curve.initialize(framebufferLuminanceTarget, 0.25f, 0.538f, 0.444f, 1.280f);
// TODO: Expose these controls to the user
blendRatio = 0.6f;
fadeStart = 0.98f;
fadeEnd = 1.16f;
const float3 rgb{framebufferLuminanceTarget};
framebufferLuminanceTargetUcs = Rec2020_to_ICtCp(frameBufferValueToPhysicalValue(rgb)).x;
}
};
GT7ToneMapper::GT7ToneMapper() noexcept {
mState = new State();
// Initialize for an SDR target
mState->initializeParameters(
1.0f / physicalValueToFrameBufferValue(SdrPaperWhite),
SdrPaperWhite
);
// TODO: To initialize for HDR output, pass 1.0 as the SDR correction factor,
// and the desired peak display luminance as the second parameter
}
GT7ToneMapper::~GT7ToneMapper() noexcept {
delete mState;
}
inline float chromaCurve(float a, float b, float x) {
return 1.0f - smoothstep(a, b, x);
}
float3 GT7ToneMapper::operator()(float3 color) const noexcept {
const State& state = *mState;
const GT7Curve& curve = state.curve;
float3 ucs = Rec2020_to_ICtCp(frameBufferValueToPhysicalValue(color));
float3 skewedRgb{
curve.evaluate(color.r),
curve.evaluate(color.g),
curve.evaluate(color.b)
};
float3 skewedUcs = Rec2020_to_ICtCp(frameBufferValueToPhysicalValue(skewedRgb));
float chromaScale = chromaCurve(
state.fadeStart, state.fadeEnd, ucs.x / state.framebufferLuminanceTargetUcs);
float3 scaledRgb = physicalValueToFrameBufferValue(ICtCp_to_Rec2020(float3{
skewedUcs.x,
ucs.y * chromaScale,
ucs.z * chromaScale
}));
const float blendRatio = state.blendRatio;
const float sdrFactor = state.sdrCorrectionFactor;
float3 luminanceTarget{state.framebufferLuminanceTarget};
return sdrFactor * min(mix(skewedRgb, scaledRgb, blendRatio), luminanceTarget);
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// AgX tone mapper // AgX tone mapper
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
+27 -1
View File
@@ -68,8 +68,33 @@ using namespace filament::math;
using namespace utils; using namespace utils;
namespace filament { namespace filament {
namespace {
RenderableManager::Builder::MorphType morphTargetBufferToBuildType(
const MorphTargetBuffer* const buffer, size_t const morphTargetCount) {
using MorphType = RenderableManager::Builder::MorphType;
if (!buffer || morphTargetCount == 0) {
return MorphType::NONE;
}
auto type = static_cast<uint8_t>(MorphType::NONE);
if (buffer->hasPositions()) {
type |= static_cast<uint8_t>(MorphType::POSITION);
}
if (buffer->hasTangents()) {
type |= static_cast<uint8_t>(MorphType::TANGENT);
}
if (buffer->isCustomMorphingEnabled()) {
type |= static_cast<uint8_t>(MorphType::CUSTOM);
}
return static_cast<MorphType>(type);
}
using namespace backend; using namespace backend;
} // anonymous namespace
struct RenderableManager::BuilderDetails { struct RenderableManager::BuilderDetails {
using Entry = FRenderableManager::Entry; using Entry = FRenderableManager::Entry;
@@ -578,7 +603,8 @@ void FRenderableManager::create(
setScreenSpaceContactShadows(ci, builder->mScreenSpaceContactShadows); setScreenSpaceContactShadows(ci, builder->mScreenSpaceContactShadows);
setCulling(ci, builder->mCulling); setCulling(ci, builder->mCulling);
setSkinning(ci, false); setSkinning(ci, false);
setMorphing(ci, builder->mMorphTargetCount); setMorphing(ci, morphTargetBufferToBuildType(builder->mMorphTargetBuffer,
builder->mMorphTargetCount));
setFogEnabled(ci, builder->mFogEnabled); setFogEnabled(ci, builder->mFogEnabled);
// do this after calling setAxisAlignedBoundingBox // do this after calling setAxisAlignedBoundingBox
static_cast<Visibility&>(mManager[ci].visibility).geometryType = builder->mGeometryType; static_cast<Visibility&>(mManager[ci].visibility).geometryType = builder->mGeometryType;
+7 -5
View File
@@ -63,6 +63,7 @@ class FRenderableManager : public RenderableManager {
public: public:
using Instance = Instance; using Instance = Instance;
using GeometryType = Builder::GeometryType; using GeometryType = Builder::GeometryType;
using MorphType = Builder::MorphType;
// TODO: consider renaming, this pertains to material variants, not strictly visibility. // TODO: consider renaming, this pertains to material variants, not strictly visibility.
struct Visibility { struct Visibility {
@@ -80,7 +81,7 @@ public:
struct Skinning { struct Skinning {
bool skinning : 1; bool skinning : 1;
bool morphing : 1; MorphType morphType : 3;
}; };
static_assert(sizeof(Visibility) == sizeof(uint16_t), "Visibility should be 16 bits"); static_assert(sizeof(Visibility) == sizeof(uint16_t), "Visibility should be 16 bits");
@@ -154,7 +155,7 @@ public:
void setSkinningBuffer(Instance instance, FSkinningBuffer* skinningBuffer, void setSkinningBuffer(Instance instance, FSkinningBuffer* skinningBuffer,
size_t count, size_t offset); size_t count, size_t offset);
inline void setMorphing(Instance instance, bool enable); inline void setMorphing(Instance instance, MorphType type);
void setMorphWeights(Instance instance, float const* weights, size_t count, size_t offset); void setMorphWeights(Instance instance, float const* weights, size_t count, size_t offset);
void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex, void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex,
size_t offset); size_t offset);
@@ -412,15 +413,16 @@ void FRenderableManager::setSkinning(Instance const instance, bool const enable)
} }
} }
void FRenderableManager::setMorphing(Instance const instance, bool const enable) { void FRenderableManager::setMorphing(Instance const instance, Builder::MorphType const type) {
if (instance) { if (instance) {
Visibility& visibility = mManager[instance].visibility; Visibility& visibility = mManager[instance].visibility;
FILAMENT_CHECK_PRECONDITION(visibility.geometryType != GeometryType::STATIC || !enable) FILAMENT_CHECK_PRECONDITION(
visibility.geometryType != GeometryType::STATIC || type != MorphType::NONE)
<< "Morphing can't be used with STATIC geometry"; << "Morphing can't be used with STATIC geometry";
Skinning& skinning = mManager[instance].skinning; Skinning& skinning = mManager[instance].skinning;
skinning.morphing = enable; skinning.morphType = type;
} }
} }
+68 -7
View File
@@ -705,6 +705,12 @@ void FEngine::shutdown() {
// Driver::terminate() has been called here. // Driver::terminate() has been called here.
} }
// Handle any pending deferred destruction for asynchronous objects.
if (isAsynchronousModeEnabled()) {
gcDeferredAsyncObjectDestruction();
assert_invariant(mDeferredAsyncObjectDestruction.empty());
}
// Finally, call user callbacks that might have been scheduled. // Finally, call user callbacks that might have been scheduled.
// These callbacks CANNOT call driver APIs. // These callbacks CANNOT call driver APIs.
getDriver().purge(); getDriver().purge();
@@ -755,7 +761,7 @@ void FEngine::prepare(DriverApi& driver) {
}); });
} }
void FEngine::gc() { void FEngine::gcManagers() {
// Note: this runs in a Job // Note: this runs in a Job
auto& em = mEntityManager; auto& em = mEntityManager;
mRenderableManager.gc(em); mRenderableManager.gc(em);
@@ -764,6 +770,21 @@ void FEngine::gc() {
mCameraManager.gc(*this, em); mCameraManager.gc(*this, em);
} }
void FEngine::gcDeferredAsyncObjectDestruction() {
if (mDeferredAsyncObjectDestruction.empty()) {
return;
}
auto it = std::remove_if(
mDeferredAsyncObjectDestruction.begin(),
mDeferredAsyncObjectDestruction.end(),
[](auto& f) {
return f(); // Returns true if it should be removed
}
);
// Actually erase the elements from the vector
mDeferredAsyncObjectDestruction.erase(it, mDeferredAsyncObjectDestruction.end());
}
void FEngine::submitFrame() { void FEngine::submitFrame() {
if (isUboBatchingEnabled()) { if (isUboBatchingEnabled()) {
DriverApi& driver = getDriverApi(); DriverApi& driver = getDriverApi();
@@ -1140,11 +1161,18 @@ bool FEngine::isValid(const T* ptr, ResourceList<T> const& list) const {
return l.find(ptr) != l.end(); return l.find(ptr) != l.end();
} }
template <typename T, typename = void>
struct HasIsCreationComplete : std::false_type {};
template <typename T>
struct HasIsCreationComplete<T, std::void_t<decltype(std::declval<T>().isCreationComplete())>>
: std::true_type {};
template<typename T> template<typename T>
UTILS_ALWAYS_INLINE UTILS_ALWAYS_INLINE
bool FEngine::terminateAndDestroy(const T* p, ResourceList<T>& list) { bool FEngine::terminateAndDestroy(const T* ptr, ResourceList<T>& list) {
if (p == nullptr) return true; if (ptr == nullptr) return true;
bool const success = list.remove(p); bool const success = list.remove(ptr);
#if UTILS_HAS_RTTI #if UTILS_HAS_RTTI
auto typeName = CallStack::typeName<T>(); auto typeName = CallStack::typeName<T>();
@@ -1154,9 +1182,42 @@ bool FEngine::terminateAndDestroy(const T* p, ResourceList<T>& list) {
#endif #endif
if (ASSERT_PRECONDITION_NON_FATAL(success, if (ASSERT_PRECONDITION_NON_FATAL(success,
"Object %s at %p doesn't exist (double free?)", typeNameCStr, p)) { "Object %s at %p doesn't exist (double free?)", typeNameCStr, ptr)) {
const_cast<T*>(p)->terminate(*this);
mHeapAllocator.destroy(const_cast<T*>(p)); T* p = const_cast<T*>(ptr);
if constexpr (HasIsCreationComplete<T>::value) {
// The presence of 'isCreationComplete' in type T implies it supports asynchronous
// creation. For these asynchronous objects, we can terminate the backend resources
// immediately as they are no longer referenced. However, we defer the destruction of
// the frontend object if it's still being loaded.
// Reason: The creation process is still active and holds a reference to the frontend
// object to set `mCreationComplete` to true (see FTexture::FTexture). Deleting it now
// may cause a crash, so we wait until creation completes.
// Terminate the backend resource immediately as they're unnecessary from this point.
p->terminate(*this);
if (p->isCreationComplete()) {
// If creation is complete, we free the frontend object immediately. Note that in
// regular (non-async) mode, the `isCreationComplete` method always return true.
mHeapAllocator.destroy(p);
} else {
// We defer the destruction of the frontend object until the creation process
// completes. This ensures the object remains valid while the creation process still
// holds references to it.
mDeferredAsyncObjectDestruction.push_back([this, p]() {
if (!p->isCreationComplete()) {
return false;
}
mHeapAllocator.destroy(p);
return true;
});
}
} else {
p->terminate(*this);
mHeapAllocator.destroy(p);
}
} }
return success; return success;
} }
+5 -2
View File
@@ -456,7 +456,8 @@ public:
} }
void prepare(backend::DriverApi& driver); void prepare(backend::DriverApi& driver);
void gc(); void gcManagers();
void gcDeferredAsyncObjectDestruction();
void submitFrame(); void submitFrame();
using ShaderContent = utils::FixedCapacityVector<uint8_t>; using ShaderContent = utils::FixedCapacityVector<uint8_t>;
@@ -586,7 +587,7 @@ private:
bool isValid(const T* ptr, ResourceList<T> const& list) const; bool isValid(const T* ptr, ResourceList<T> const& list) const;
template<typename T> template<typename T>
bool terminateAndDestroy(const T* p, ResourceList<T>& list); bool terminateAndDestroy(const T* ptr, ResourceList<T>& list);
template<typename T, typename Lock> template<typename T, typename Lock>
bool terminateAndDestroyLocked(Lock& lock, const T* p, ResourceList<T>& list); bool terminateAndDestroyLocked(Lock& lock, const T* p, ResourceList<T>& list);
@@ -708,6 +709,8 @@ private:
// Creation parameters // Creation parameters
Config mConfig; Config mConfig;
std::vector<std::function<bool()>> mDeferredAsyncObjectDestruction;
public: public:
// These are the debug properties used by FDebug. // These are the debug properties used by FDebug.
// They're accessed directly by modules who need them. // They're accessed directly by modules who need them.
+55 -17
View File
@@ -36,6 +36,9 @@ using namespace math;
struct MorphTargetBuffer::BuilderDetails { struct MorphTargetBuffer::BuilderDetails {
size_t mVertexCount = 0; size_t mVertexCount = 0;
size_t mCount = 0; size_t mCount = 0;
bool mWithPositions = true;
bool mWithTangents = true;
bool mEnableCustomMorphing = false;
}; };
using BuilderType = MorphTargetBuffer; using BuilderType = MorphTargetBuffer;
@@ -56,6 +59,21 @@ MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::count(size_t const count
return *this; return *this;
} }
MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::withPositions(bool enable) noexcept {
mImpl->mWithPositions = enable;
return *this;
}
MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::withTangents(bool enable) noexcept {
mImpl->mWithTangents = enable;
return *this;
}
MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::enableCustomMorphing(bool enable) noexcept {
mImpl->mEnableCustomMorphing = enable;
return *this;
}
MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::name(const char* name, size_t const len) noexcept { MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::name(const char* name, size_t const len) noexcept {
return BuilderNameMixin::name(name, len); return BuilderNameMixin::name(name, len);
} }
@@ -108,11 +126,17 @@ inline size_t getSize<TANGENTS>(size_t const vertexCount) noexcept {
FMorphTargetBuffer::EmptyMorphTargetBuilder::EmptyMorphTargetBuilder() { FMorphTargetBuffer::EmptyMorphTargetBuilder::EmptyMorphTargetBuilder() {
mImpl->mVertexCount = 1; mImpl->mVertexCount = 1;
mImpl->mCount = 1; mImpl->mCount = 1;
mImpl->mWithPositions = true;
mImpl->mWithTangents = true;
} }
FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder) FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder)
: mVertexCount(builder->mVertexCount), : mEnableCustomMorphing(builder->mEnableCustomMorphing),
mVertexCount(builder->mVertexCount),
mCount(builder->mCount) { mCount(builder->mCount) {
FILAMENT_CHECK_PRECONDITION(
builder->mEnableCustomMorphing || builder->mWithPositions || builder->mWithTangents)
<< "Requires enable at least one of the morphing type.";
if (UTILS_UNLIKELY(engine.getSupportedFeatureLevel() <= FeatureLevel::FEATURE_LEVEL_0)) { if (UTILS_UNLIKELY(engine.getSupportedFeatureLevel() <= FeatureLevel::FEATURE_LEVEL_0)) {
// feature level 0 doesn't support morph target buffers // feature level 0 doesn't support morph target buffers
@@ -121,22 +145,25 @@ FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder)
FEngine::DriverApi& driver = engine.getDriverApi(); FEngine::DriverApi& driver = engine.getDriverApi();
// create buffer (here a texture) to store the morphing vertex data if (builder->mWithPositions) {
mPbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1, mPbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
TextureFormat::RGBA32F, 1, TextureFormat::RGBA32F, 1,
getWidth(mVertexCount), getWidth(mVertexCount),
getHeight(mVertexCount), getHeight(mVertexCount),
mCount, mCount,
TextureUsage::DEFAULT, TextureUsage::DEFAULT,
utils::ImmutableCString{ builder.getName() }); utils::ImmutableCString{ builder.getName() });
}
mTbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1, if (builder->mWithTangents) {
TextureFormat::RGBA16I, 1, mTbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
getWidth(mVertexCount), TextureFormat::RGBA16I, 1,
getHeight(mVertexCount), getWidth(mVertexCount),
mCount, getHeight(mVertexCount),
TextureUsage::DEFAULT, mCount,
utils::ImmutableCString{ builder.getName() }); TextureUsage::DEFAULT,
utils::ImmutableCString{ builder.getName() });
}
} }
void FMorphTargetBuffer::terminate(FEngine& engine) { void FMorphTargetBuffer::terminate(FEngine& engine) {
@@ -151,6 +178,10 @@ void FMorphTargetBuffer::terminate(FEngine& engine) {
void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetIndex, void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetIndex,
float3 const* positions, size_t const count, size_t const offset) { float3 const* positions, size_t const count, size_t const offset) {
FILAMENT_CHECK_PRECONDITION(mPbHandle)
<< "setPositionsAt() called on a MorphTargetBuffer without a position buffer. Use "
"withPositions(true) in the Builder.";
FILAMENT_CHECK_PRECONDITION(offset + count <= mVertexCount) FILAMENT_CHECK_PRECONDITION(offset + count <= mVertexCount)
<< "MorphTargetBuffer (size=" << (unsigned)mVertexCount << "MorphTargetBuffer (size=" << (unsigned)mVertexCount
<< ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")"; << ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")";
@@ -174,6 +205,10 @@ void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetInde
void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetIndex, void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetIndex,
float4 const* positions, size_t const count, size_t const offset) { float4 const* positions, size_t const count, size_t const offset) {
FILAMENT_CHECK_PRECONDITION(mPbHandle)
<< "setPositionsAt() called on a MorphTargetBuffer without a position buffer. Use "
"withPositions(true) in the Builder.";
FILAMENT_CHECK_PRECONDITION(offset + count <= mVertexCount) FILAMENT_CHECK_PRECONDITION(offset + count <= mVertexCount)
<< "MorphTargetBuffer (size=" << mVertexCount << "MorphTargetBuffer (size=" << mVertexCount
<< ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")"; << ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")";
@@ -196,6 +231,10 @@ void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetInde
void FMorphTargetBuffer::setTangentsAt(FEngine& engine, size_t const targetIndex, void FMorphTargetBuffer::setTangentsAt(FEngine& engine, size_t const targetIndex,
short4 const* tangents, size_t const count, size_t const offset) { short4 const* tangents, size_t const count, size_t const offset) {
FILAMENT_CHECK_PRECONDITION(mTbHandle)
<< "setTangentsAt() called on a MorphTargetBuffer without a tangent buffer. Use "
"withTangents(true) in the Builder.";
FILAMENT_CHECK_PRECONDITION(offset + count <= mVertexCount) FILAMENT_CHECK_PRECONDITION(offset + count <= mVertexCount)
<< "MorphTargetBuffer (size=" << mVertexCount << "MorphTargetBuffer (size=" << mVertexCount
<< ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")"; << ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")";
@@ -278,4 +317,3 @@ void FMorphTargetBuffer::updateDataAt(DriverApi& driver,
} }
} // namespace filament } // namespace filament
+13
View File
@@ -56,6 +56,18 @@ public:
void setTangentsAt(FEngine& engine, size_t targetIndex, void setTangentsAt(FEngine& engine, size_t targetIndex,
math::short4 const* tangents, size_t count, size_t offset); math::short4 const* tangents, size_t count, size_t offset);
bool hasPositions() const noexcept {
return bool(mPbHandle);
}
bool hasTangents() const noexcept {
return bool(mTbHandle);
}
bool isCustomMorphingEnabled() const noexcept {
return mEnableCustomMorphing;
}
inline size_t getVertexCount() const noexcept { return mVertexCount; } inline size_t getVertexCount() const noexcept { return mVertexCount; }
inline size_t getCount() const noexcept { return mCount; } inline size_t getCount() const noexcept { return mCount; }
@@ -72,6 +84,7 @@ private:
backend::PixelDataFormat format, backend::PixelDataType type, const char* out, backend::PixelDataFormat format, backend::PixelDataType type, const char* out,
size_t elementSize, size_t targetIndex, size_t count, size_t offset); size_t elementSize, size_t targetIndex, size_t count, size_t offset);
bool mEnableCustomMorphing;
backend::TextureHandle mPbHandle; backend::TextureHandle mPbHandle;
backend::TextureHandle mTbHandle; backend::TextureHandle mTbHandle;
uint32_t mVertexCount; uint32_t mVertexCount;
+16 -6
View File
@@ -307,9 +307,14 @@ void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
engine.flush(); // flush command stream engine.flush(); // flush command stream
// Run the component managers' GC // Run GC
auto& js = engine.getJobSystem(); JobSystem& js = engine.getJobSystem();
js.runAndWait(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers auto *rootJob = js.createJob();
js.run(jobs::createJob(js, rootJob, &FEngine::gcManagers, &engine)); // gc all component managers
if (engine.isAsynchronousModeEnabled()) {
js.run(jobs::createJob(js, rootJob, &FEngine::gcDeferredAsyncObjectDestruction, &engine));
}
js.runAndWait(rootJob);
mFrameSkipper.frameSkipped(); mFrameSkipper.frameSkipped();
} }
@@ -510,9 +515,14 @@ void FRenderer::endFrame() {
engine.flush(); // flush command stream engine.flush(); // flush command stream
// Run the component managers' GC // Run GC
auto& js = engine.getJobSystem(); JobSystem& js = engine.getJobSystem();
js.runAndWait(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers auto *rootJob = js.createJob();
js.run(jobs::createJob(js, rootJob, &FEngine::gcManagers, &engine)); // gc all component managers
if (engine.isAsynchronousModeEnabled()) {
js.run(jobs::createJob(js, rootJob, &FEngine::gcDeferredAsyncObjectDestruction, &engine));
}
js.runAndWait(rootJob);
} }
void FRenderer::readPixels( void FRenderer::readPixels(
+1 -1
View File
@@ -395,7 +395,7 @@ void FScene::prepareVisibleRenderables(Range<uint32_t> visibleRenderables) noexc
uboData.flagsChannels = PerRenderableData::packFlagsChannels( uboData.flagsChannels = PerRenderableData::packFlagsChannels(
skinning.skinning, skinning.skinning,
skinning.morphing, static_cast<uint8_t>(skinning.morphType),
visibility.screenSpaceContactShadows, visibility.screenSpaceContactShadows,
sceneData.elementAt<INSTANCES>(i).buffer != nullptr, sceneData.elementAt<INSTANCES>(i).buffer != nullptr,
sceneData.elementAt<CHANNELS>(i)); sceneData.elementAt<CHANNELS>(i));
+12 -6
View File
@@ -33,6 +33,7 @@
#include "details/Engine.h" #include "details/Engine.h"
#include "details/IndirectLight.h" #include "details/IndirectLight.h"
#include "details/InstanceBuffer.h" #include "details/InstanceBuffer.h"
#include "details/MorphTargetBuffer.h"
#include "details/RenderTarget.h" #include "details/RenderTarget.h"
#include "details/Renderer.h" #include "details/Renderer.h"
#include "details/Scene.h" #include "details/Scene.h"
@@ -832,13 +833,18 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
+PerRenderableBindingPoints::MORPHING_UNIFORMS, +PerRenderableBindingPoints::MORPHING_UNIFORMS,
morphing.handle, 0, sizeof(PerRenderableMorphingUib)); morphing.handle, 0, sizeof(PerRenderableMorphingUib));
descriptorSet.setSampler(layout, const auto* mtb = morphing.morphTargetBuffer;
+PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, if (mtb->hasPositions()) {
morphing.morphTargetBuffer->getPositionsHandle(), {}); descriptorSet.setSampler(layout,
+PerRenderableBindingPoints::MORPH_TARGET_POSITIONS,
mtb->getPositionsHandle(), {});
}
descriptorSet.setSampler(layout, if (mtb->hasTangents()) {
+PerRenderableBindingPoints::MORPH_TARGET_TANGENTS, descriptorSet.setSampler(layout,
morphing.morphTargetBuffer->getTangentsHandle(), {}); +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS,
mtb->getTangentsHandle(), {});
}
} }
descriptorSet.commit(layout, driver); descriptorSet.commit(layout, driver);
+2 -2
View File
@@ -522,8 +522,8 @@ fgviewer::FrameGraphInfo FrameGraph::getFrameGraphInfo(const char *viewName) con
continue; continue;
writes.push_back(resourceNode->resourceHandle.index); writes.push_back(resourceNode->resourceHandle.index);
} }
passes.emplace_back(utils::CString(pass->getName()), passes.emplace_back(pass->getId(), utils::CString(pass->getName()),
std::move(reads), std::move(writes)); std::move(reads), std::move(writes), pass->getRenderTargetInfo());
} }
std::unordered_map<fgviewer::ResourceId, fgviewer::FrameGraphInfo::Resource> resources; std::unordered_map<fgviewer::ResourceId, fgviewer::FrameGraphInfo::Resource> resources;
+2
View File
@@ -64,6 +64,8 @@ public:
bool isInitialized() const noexcept { return index != UNINITIALIZED; } bool isInitialized() const noexcept { return index != UNINITIALIZED; }
uint16_t getIndex() const noexcept { return index; }
operator bool() const noexcept { return isInitialized(); } operator bool() const noexcept { return isInitialized(); }
void clear() noexcept { index = UNINITIALIZED; version = 0; } void clear() noexcept { index = UNINITIALIZED; version = 0; }
+5
View File
@@ -39,6 +39,11 @@ struct FrameGraphRenderPass {
FrameGraphId<FrameGraphTexture> stencil; FrameGraphId<FrameGraphTexture> stencil;
FrameGraphId<FrameGraphTexture>& operator[](size_t index) noexcept { FrameGraphId<FrameGraphTexture>& operator[](size_t index) noexcept {
return const_cast<FrameGraphId<FrameGraphTexture>&>(
static_cast<const Attachments*>(this)->operator[](index));
}
FrameGraphId<FrameGraphTexture> const& operator[](size_t index) const noexcept {
assert_invariant(index < ATTACHMENT_COUNT); assert_invariant(index < ATTACHMENT_COUNT);
if (index < backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT) { if (index < backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT) {
return color[index]; return color[index];
+48 -1
View File
@@ -175,7 +175,8 @@ void RenderPassNode::resolve() noexcept {
rt.backend.params.flags.discardStart |= target; rt.backend.params.flags.discardStart |= target;
} }
VirtualResource* pResource = mFrameGraph.getResource(rt.descriptor.attachments[i]); VirtualResource* pResource = mFrameGraph.getResource(rt.descriptor.attachments[i]);
Resource<FrameGraphTexture>* pTextureResource = static_cast<Resource<FrameGraphTexture>*>(pResource); Resource<FrameGraphTexture>* pTextureResource =
static_cast<Resource<FrameGraphTexture>*>(pResource);
pImportedRenderTarget = pImportedRenderTarget ? pImportedRenderTarget = pImportedRenderTarget ?
pImportedRenderTarget : pResource->asImportedRenderTarget(); pImportedRenderTarget : pResource->asImportedRenderTarget();
@@ -332,6 +333,52 @@ utils::CString RenderPassNode::graphvizify() const noexcept {
#endif #endif
} }
#if FILAMENT_ENABLE_FGVIEWER
using RenderTargetInfo = fgviewer::FrameGraphInfo::Pass::RenderTargetInfo;
using AttachmentInfo = fgviewer::FrameGraphInfo::Pass::AttachmentInfo;
std::vector<RenderTargetInfo> RenderPassNode::getRenderTargetInfo() const noexcept {
using namespace backend;
std::vector<RenderTargetInfo> info;
info.reserve(mRenderTargetData.size());
for (auto const& rt: mRenderTargetData) {
RenderTargetInfo rtInfo;
auto extractAttachmentInfo = [&](TargetBufferFlags flags,
std::vector<AttachmentInfo>& list) {
for (size_t i = 0; i < RenderPassData::ATTACHMENT_COUNT; ++i) {
TargetBufferFlags mask = getTargetBufferFlagsAt(i);
if (any(flags & mask)) {
FrameGraphHandle handle = rt.descriptor.attachments[i];
if (handle) {
const char* name = nullptr;
if (i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT) name = "color";
else if (i == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT)
name = "depth";
else if (i == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1)
name = "stencil";
utils::CString slotName(name);
if (i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT) {
slotName += utils::to_string(i);
}
list.push_back({ slotName, handle.getIndex() });
}
}
}
};
extractAttachmentInfo(rt.backend.params.flags.discardStart, rtInfo.discardStart);
extractAttachmentInfo(rt.backend.params.flags.discardEnd, rtInfo.discardEnd);
extractAttachmentInfo(rt.backend.params.flags.clear, rtInfo.clear);
info.push_back(std::move(rtInfo));
}
return info;
}
#endif
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
PresentPassNode::PresentPassNode(FrameGraph& fg) noexcept PresentPassNode::PresentPassNode(FrameGraph& fg) noexcept
+15 -1
View File
@@ -23,9 +23,12 @@
#include "fg/FrameGraphRenderPass.h" #include "fg/FrameGraphRenderPass.h"
#include "backend/DriverApiForward.h" #include "backend/DriverApiForward.h"
#include <backend/TargetBufferInfo.h> #include <backend/TargetBufferInfo.h>
#if FILAMENT_ENABLE_FGVIEWER
#include <fgviewer/FrameGraphInfo.h>
#endif
#include <cstdint> #include <cstdint>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
@@ -60,6 +63,12 @@ public:
virtual void resolve() noexcept = 0; virtual void resolve() noexcept = 0;
utils::CString graphvizifyEdgeColor() const noexcept override; utils::CString graphvizifyEdgeColor() const noexcept override;
#if FILAMENT_ENABLE_FGVIEWER
virtual std::vector<fgviewer::FrameGraphInfo::Pass::RenderTargetInfo>
getRenderTargetInfo() const noexcept {
return {};
}
#endif
Vector<VirtualResource*> devirtualize; // resources we need to create before executing Vector<VirtualResource*> devirtualize; // resources we need to create before executing
Vector<VirtualResource*> destroy; // resources we need to destroy after executing Vector<VirtualResource*> destroy; // resources we need to destroy after executing
}; };
@@ -93,6 +102,7 @@ public:
utils::StaticString name, FrameGraphRenderPass::Descriptor const& descriptor); utils::StaticString name, FrameGraphRenderPass::Descriptor const& descriptor);
RenderPassData const* getRenderPassData(uint32_t id) const noexcept; RenderPassData const* getRenderPassData(uint32_t id) const noexcept;
size_t getRenderTargetCount() const noexcept { return mRenderTargetData.size(); }
private: private:
// virtuals from DependencyGraph::Node // virtuals from DependencyGraph::Node
@@ -100,6 +110,10 @@ private:
utils::CString graphvizify() const noexcept override; utils::CString graphvizify() const noexcept override;
void execute(FrameGraphResources const& resources, backend::DriverApi& driver) noexcept override; void execute(FrameGraphResources const& resources, backend::DriverApi& driver) noexcept override;
void resolve() noexcept override; void resolve() noexcept override;
#if FILAMENT_ENABLE_FGVIEWER
std::vector<fgviewer::FrameGraphInfo::Pass::RenderTargetInfo>
getRenderTargetInfo() const noexcept override;
#endif
// constants // constants
const char* const mName = nullptr; const char* const mName = nullptr;
+2
View File
@@ -1,3 +1,5 @@
# bluevk
## Updating Vulkan headers ## Updating Vulkan headers
To update the Vulkan headers, perform the following steps. To update the Vulkan headers, perform the following steps.
@@ -39,14 +39,29 @@ public:
bool operator==(const FrameGraphInfo& rhs) const; bool operator==(const FrameGraphInfo& rhs) const;
struct Pass { struct Pass {
Pass(utils::CString name, std::vector<ResourceId> reads, struct AttachmentInfo {
std::vector<ResourceId> writes); utils::CString name;
ResourceId id;
bool operator==(const AttachmentInfo& rhs) const;
};
struct RenderTargetInfo {
std::vector<AttachmentInfo> discardStart;
std::vector<AttachmentInfo> discardEnd;
std::vector<AttachmentInfo> clear;
bool operator==(const RenderTargetInfo& rhs) const;
};
Pass(uint32_t id, utils::CString name, std::vector<ResourceId> reads,
std::vector<ResourceId> writes, std::vector<RenderTargetInfo> renderTargets = {});
bool operator==(const Pass& rhs) const; bool operator==(const Pass& rhs) const;
uint32_t id;
utils::CString name; utils::CString name;
std::vector<ResourceId> reads; std::vector<ResourceId> reads;
std::vector<ResourceId> writes; std::vector<ResourceId> writes;
std::vector<RenderTargetInfo> renderTargets;
}; };
struct Resource { struct Resource {
+16 -4
View File
@@ -37,13 +37,25 @@ bool FrameGraphInfo::operator==(const FrameGraphInfo& rhs) const {
&& resources == rhs.resources; && resources == rhs.resources;
} }
FrameGraphInfo::Pass::Pass(utils::CString name, std::vector<ResourceId> reads, FrameGraphInfo::Pass::Pass(uint32_t id, utils::CString name, std::vector<ResourceId> reads,
std::vector<ResourceId> writes): name(std::move(name)), std::vector<ResourceId> writes, std::vector<RenderTargetInfo> renderTargets):
id(id),
name(std::move(name)),
reads(std::move(reads)), reads(std::move(reads)),
writes(std::move(writes)) {} writes(std::move(writes)),
renderTargets(std::move(renderTargets)) {}
bool FrameGraphInfo::Pass::operator==(const Pass& rhs) const { bool FrameGraphInfo::Pass::operator==(const Pass& rhs) const {
return name == rhs.name && reads == rhs.reads && writes == rhs.writes; return id == rhs.id && name == rhs.name && reads == rhs.reads && writes == rhs.writes &&
renderTargets == rhs.renderTargets;
}
bool FrameGraphInfo::Pass::AttachmentInfo::operator==(const AttachmentInfo& rhs) const {
return name == rhs.name && id == rhs.id;
}
bool FrameGraphInfo::Pass::RenderTargetInfo::operator==(const RenderTargetInfo& rhs) const {
return discardStart == rhs.discardStart && discardEnd == rhs.discardEnd && clear == rhs.clear;
} }
FrameGraphInfo::Resource::Resource(ResourceId id, utils::CString name, FrameGraphInfo::Resource::Resource(ResourceId id, utils::CString name,
+41 -1
View File
@@ -57,12 +57,49 @@ void writeResourceIds(std::ostream& os, const std::vector<ResourceId>& resources
} }
} }
void writeRenderTargetInfo(std::ostream& os,
const std::vector<FrameGraphInfo::Pass::RenderTargetInfo>& renderTargets) {
os << " \"renderTargets\": [\n";
for (size_t i = 0; i < renderTargets.size(); ++i) {
const auto& rt = renderTargets[i];
os << " {\n";
auto writeAttachmentInfo =
[&](const char* label,
const std::vector<FrameGraphInfo::Pass::AttachmentInfo>& attachments) {
os << " \"" << label << "\": [\n";
for (size_t k = 0; k < attachments.size(); ++k) {
const auto& att = attachments[k];
os << " { \"name\": ";
writeJSONString(os, att.name.c_str());
os << ", \"id\": " << att.id << " }";
if (k + 1 < attachments.size()) os << ",";
os << "\n";
}
os << " ]";
};
writeAttachmentInfo("discardStart", rt.discardStart);
os << ",\n";
writeAttachmentInfo("discardEnd", rt.discardEnd);
os << ",\n";
writeAttachmentInfo("clear", rt.clear);
os << "\n";
os << " }";
if (i + 1 < renderTargets.size()) os << ",";
os << "\n";
}
os << " ]\n";
}
void writePasses(std::ostream& os, const FrameGraphInfo& frameGraph) { void writePasses(std::ostream& os, const FrameGraphInfo& frameGraph) {
os << " \"passes\": [\n"; os << " \"passes\": [\n";
auto& passes = frameGraph.getPasses(); auto& passes = frameGraph.getPasses();
for (size_t i = 0; i < passes.size(); ++i) { for (size_t i = 0; i < passes.size(); ++i) {
const FrameGraphInfo::Pass& pass = passes[i]; const FrameGraphInfo::Pass& pass = passes[i];
os << " {\n"; os << " {\n";
os << " \"id\": " << pass.id << ",\n";
os << " \"name\": "; os << " \"name\": ";
writeJSONString(os, pass.name.c_str()); writeJSONString(os, pass.name.c_str());
os << ",\n"; os << ",\n";
@@ -75,7 +112,10 @@ void writePasses(std::ostream& os, const FrameGraphInfo& frameGraph) {
const std::vector<ResourceId>& writes = pass.writes; const std::vector<ResourceId>& writes = pass.writes;
os << " \"writes\": ["; os << " \"writes\": [";
writeResourceIds(os, writes); writeResourceIds(os, writes);
os << "]\n"; os << "],\n";
writeRenderTargetInfo(os, pass.renderTargets);
os << "\n";
os << " }"; os << " }";
if (i + 1 < passes.size()) os << ","; if (i + 1 < passes.size()) os << ",";
+387 -138
View File
@@ -15,8 +15,6 @@
*/ */
import {LitElement, html, css, unsafeCSS, nothing} from "https://unpkg.com/lit@2.8.0?module"; import {LitElement, html, css, unsafeCSS, nothing} from "https://unpkg.com/lit@2.8.0?module";
import { graphviz } from "https://cdn.skypack.dev/d3-graphviz@5.1.0";
import * as d3 from "https://cdn.skypack.dev/d3@7";
const kUntitledPlaceholder = "Untitled View"; const kUntitledPlaceholder = "Untitled View";
@@ -28,22 +26,24 @@ const BACKGROUND_COLOR = '#5362e5';
const REGULAR_FONT_SIZE = 12; const REGULAR_FONT_SIZE = 12;
// Constants for color operations // Constants for color operations
const READ_COLOR = '#3ac224'; const READ_COLOR = '#aee1a6';
const WRITE_COLOR = '#d43232'; const WRITE_COLOR = '#df8e8e';
const NO_ACCESS_COLOR = '#d8dde754'; const NO_ACCESS_COLOR = '#bcbcbc';
const READ_WRITE_COLOR = '#ffeb99'; const READ_WRITE_COLOR = '#ffeb99';
const DEFAULT_COLOR = '#ffffff'; const DEFAULT_COLOR = '#ffffff';
const SUBRESOURCE_COLOR = '#d3d3d3'; const SUBRESOURCE_COLOR = '#d3d3d3';
const SELECTED_ROW_COLUMN_COLOR = "rgba(180,210,240,.35)";
// Constants for view mode toggle // Constants for view mode toggle
const VIEW_TOGGLE_INACTIVE_COLOR = '#292929'; const VIEW_TOGGLE_INACTIVE_COLOR = '#292929';
const VIEW_TOGGLE_UNSELECTED_COLOR = '#3f4cbe'; const VIEW_TOGGLE_UNSELECTED_COLOR = '#3f4cbe';
const VIEW_TOGGLE_SELECTED_COLOR = '#2c3892'; const VIEW_TOGGLE_SELECTED_COLOR = '#2c3892';
const RESOURCE_USAGE_TYPE_READ = 'read'; const RESOURCE_USAGE_TYPE_READ = 'R';
const RESOURCE_USAGE_TYPE_WRITE = 'write'; const RESOURCE_USAGE_TYPE_WRITE = 'W';
const RESOURCE_USAGE_TYPE_NO_ACCESS = 'no-access'; const RESOURCE_USAGE_TYPE_NO_ACCESS = 'NA';
const RESOURCE_USAGE_TYPE_READ_WRITE = 'read-write'; const RESOURCE_USAGE_TYPE_READ_WRITE = 'RW';
const IS_SUBRESOURCE_KEY = 'is_subresource_of' const IS_SUBRESOURCE_KEY = 'is_subresource_of'
@@ -51,6 +51,18 @@ const IS_SUBRESOURCE_KEY = 'is_subresource_of'
const VIEW_MODE_TABLE = 'table'; const VIEW_MODE_TABLE = 'table';
const VIEW_MODE_GRAPHVIZ = 'graphviz'; const VIEW_MODE_GRAPHVIZ = 'graphviz';
const COLORS = [
"#FFB3BA",
"#BAFFC9",
"#BAE1FF",
"#FFDFBA",
"#E2C2FF",
"#B2F0E8",
"#DDCBA4",
"#D4F0B2",
"#DCD0FF"
];
class MenuSection extends LitElement { class MenuSection extends LitElement {
static get properties() { static get properties() {
return { return {
@@ -128,15 +140,15 @@ class FrameGraphSidePanel extends LitElement {
dynamicStyle() { dynamicStyle() {
return ` return `
:host { :host {
position: fixed;
background: ${this.connected ? BACKGROUND_COLOR : INACTIVE_COLOR}; background: ${this.connected ? BACKGROUND_COLOR : INACTIVE_COLOR};
width: 20%;
height: 100%; height: 100%;
max-width: 250px; max-width: 250px;
min-width: 250px; min-width: 250px;
padding: 10px 20px;
overflow-y: auto; overflow-y: auto;
} }
.container {
padding: 10px 20px;
}
.title { .title {
color: white; color: white;
width: 100%; width: 100%;
@@ -190,6 +202,13 @@ class FrameGraphSidePanel extends LitElement {
background-color: ${this.connected ? VIEW_TOGGLE_SELECTED_COLOR:VIEW_TOGGLE_INACTIVE_COLOR}; background-color: ${this.connected ? VIEW_TOGGLE_SELECTED_COLOR:VIEW_TOGGLE_INACTIVE_COLOR};
font-weight: bold; font-weight: bold;
} }
.resource-link:hover {
text-decoration: underline;
}
.render-target-resource {
display: flex;
flex-direction: row;
}
`; `;
} }
@@ -198,6 +217,7 @@ class FrameGraphSidePanel extends LitElement {
connected: {type: Boolean, attribute: 'connected'}, connected: {type: Boolean, attribute: 'connected'},
selectedFrameGraph: {type: String, attribute: 'selected-framegraph'}, selectedFrameGraph: {type: String, attribute: 'selected-framegraph'},
selectedResourceId: {type: Number, attribute: 'selected-resource'}, selectedResourceId: {type: Number, attribute: 'selected-resource'},
selectedPassIndex: {type: Number, attribute: 'selected-pass'},
viewMode: {type: String, attribute: 'view-mode'}, viewMode: {type: String, attribute: 'view-mode'},
database: {type: Object, state: true}, database: {type: Object, state: true},
@@ -211,6 +231,8 @@ class FrameGraphSidePanel extends LitElement {
this.framegraphs = []; this.framegraphs = [];
this.database = {}; this.database = {};
this.viewMode = VIEW_MODE_TABLE; this.viewMode = VIEW_MODE_TABLE;
this.selectedFrameGraph = '';
this.selectedPassIndex = -1;
} }
updated(props) { updated(props) {
@@ -225,6 +247,10 @@ class FrameGraphSidePanel extends LitElement {
: (labelCount[name] = 0, name); : (labelCount[name] = 0, name);
return { fgid, name: uniqueName }; return { fgid, name: uniqueName };
}); });
// Select the 0-th view as default.
if (this.selectedFrameGraph.length == 0 && this.framegraphs.length > 0) {
this._handleFrameGraphClick(this.framegraphs[0].fgid);
}
} }
} }
@@ -245,8 +271,9 @@ class FrameGraphSidePanel extends LitElement {
} }
_findCurrentResource() { _findCurrentResource() {
if (!this.selectedFrameGraph) if (!this.selectedFrameGraph) {
return null; return null;
}
const resources = this.database[this.selectedFrameGraph]?.resources; const resources = this.database[this.selectedFrameGraph]?.resources;
if (resources) { if (resources) {
@@ -258,6 +285,32 @@ class FrameGraphSidePanel extends LitElement {
return null; return null;
} }
_findCurrentPass() {
if (!this.selectedFrameGraph || this.selectedPassIndex === -1 ||
this.selectedPassIndex === undefined) {
return null;
}
const passes = this.database[this.selectedFrameGraph]?.passes;
return passes ? passes[this.selectedPassIndex] : null;
}
_handleResourceLinkClick(ev, id) {
ev.stopPropagation();
this.dispatchEvent(new CustomEvent('select-resource', {
detail: id,
bubbles: true,
composed: true
}));
}
_formatResourceId(id) {
const resources = this.database[this.selectedFrameGraph]?.resources;
const resource = resources ? resources[id] : null;
const label = resource ? `${resource.name} [${id}]` : `[${id}]`;
return html`<span class="resource-link" style="cursor: pointer;"
@click="${(e) => this._handleResourceLinkClick(e, id)}">${label}</span>`;
}
render() { render() {
const renderFrameGraphs = (title) => { const renderFrameGraphs = (title) => {
if (!this.framegraphs.length) if (!this.framegraphs.length)
@@ -300,10 +353,8 @@ class FrameGraphSidePanel extends LitElement {
return html` return html`
<menu-section title="${title}"> <menu-section title="${title}">
<div class="resource-content"> <div class="resource-content">
<div><b>Name:</b> ${currentResource.name}</div> <div><b>Name:</b> ${currentResource.name}
</div> <b>[ID=${currentResource.id}]</b></div>
<div class="resource-content">
<div><b>ID:</b> ${currentResource.id}</div>
</div> </div>
${currentResource.properties.length > 0 ? html` ${currentResource.properties.length > 0 ? html`
<div class="resource-content"> <div class="resource-content">
@@ -319,12 +370,76 @@ class FrameGraphSidePanel extends LitElement {
`; `;
}; };
const renderPassDetails = (title) => {
const currentPass = this._findCurrentPass();
if (!currentPass) return nothing;
const renderRenderTarget = (rt, index) => {
const resourceFlags = {}; // id -> { name, flags: [] }
const processList = (list, flagName) => {
if (!list) return;
for (const att of list) {
if (!resourceFlags[att.id]) {
resourceFlags[att.id] = { name: att.name, id: att.id, flags: [] };
}
resourceFlags[att.id].flags.push(flagName);
}
};
processList(rt.discardStart, "Discard Start");
processList(rt.discardEnd, "Discard End");
processList(rt.clear, "Clear");
const resources = Object.values(resourceFlags);
if (resources.length === 0) return nothing;
return html`
<div class="resource-content render-target-resource"
style="margin-top: 5px; border-top: 1px dashed #6f6f6f;">
<div>(${index})</div>
<ul style="margin:0;padding-inline-start:25px">
${resources.map(res => html`
<li>
${this._formatResourceId(res.id)}: ${res.flags.join(", ")}
</li>
`)}
</ul>
</div>
`;
};
const joinResources = (ids) => {
return ids.map((id, i) => html`
<li>${this._formatResourceId(id)}</li>`);
};
return html`
<menu-section title="${title}">
<div class="resource-content">
<div><b>Name:</b> ${currentPass.name} <b>[ID=${currentPass.id}]</b></div>
</div>
${currentPass.reads.length > 0 ? html`
<div class="resource-content">
<div><b>Reads:</b> </div>
<ul style="margin: 4px 0;">
${joinResources(currentPass.reads)}
</ul>
</div>` : nothing}
${currentPass.renderTargets && currentPass.renderTargets.length > 0 ?
html`<div><b>Writes (Render Targets):</b></div>
${currentPass.renderTargets.map((rt, i) => renderRenderTarget(rt, i))}` : nothing}
</menu-section>
`;
};
return html` return html`
<style>${this.dynamicStyle()}</style> <style>${this.dynamicStyle()}</style>
<div class="container"> <div class="container">
<div class="title">fgviewer</div> <div class="title">fgviewer</div>
${renderFrameGraphs("Views") ?? nothing} ${renderFrameGraphs("Views") ?? nothing}
${renderResourceDetails("Resource Details") ?? nothing} ${renderResourceDetails("Resource Details") ?? nothing}
${renderPassDetails("Pass Details") ?? nothing}
</div> </div>
`; `;
} }
@@ -332,77 +447,109 @@ class FrameGraphSidePanel extends LitElement {
customElements.define("framegraph-sidepanel", FrameGraphSidePanel); customElements.define("framegraph-sidepanel", FrameGraphSidePanel);
class TooltipElement extends LitElement {
static get styles() {
return css`
:host {
position: fixed;
background-color: #333;
color: #fff;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
z-index: 10000;
display: none;
white-space: nowrap;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
:host([visible]) {
display: block;
}
`;
}
static get properties() {
return {
text: {type: String},
visible: {type: Boolean, reflect: true},
x: {type: Number},
y: {type: Number}
};
}
updated(changedProps) {
if (changedProps.has('x') || changedProps.has('y')) {
// Offset slightly so cursor doesn't cover it
this.style.left = `${this.x + 10}px`;
this.style.top = `${this.y + 10}px`;
}
}
render() {
return html`${this.text}`;
}
}
customElements.define('tooltip-element', TooltipElement);
class FrameGraphTable extends LitElement { class FrameGraphTable extends LitElement {
static get styles() { static get styles() {
return css` return css`
:host { :host {
display: block; display: block;
flex-grow: 1; font-size: 12px;
width: 80%; margin-top: 100px;
margin-left: 20px;
} }
.grid-table {
.table-container { display: flex;
max-height: 100%; flex-direction: column;
max-width: 100%;
overflow: auto;
border: 1px solid #ddd;
margin-left: 300px;
} }
.grid-row {
.scrollable-table { display: inline-grid;
width: 100%; /* Adjust column widths here (e.g., first column gets 2 parts, others get 1) */
height: 100%; grid-template-columns: 2fr 1fr 1fr;
border-collapse: collapse; }
.grid-cell:first-child {
}
.grid-cell {
padding: 2px;
min-height: 25px;
position: relative;
}
.top-row {
border: 1px solid rgba(0, 0, 0, 0);
}
.pass-cell {
position: absolute;
transform-origin: top left;
transform: rotateZ(-45deg);
top: 17px;
left: 10px;
width: 150px;
} }
.collapsible { .collapsible {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
.toggle-icon { .toggle-icon {
cursor: pointer; cursor: pointer;
margin-right: 5px; margin-right: 5px;
user-select: none; user-select: none;
} }
.scrollable-table td,
.scrollable-table th {
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}
.selected { .selected {
text-decoration: underline; text-decoration: underline;
font-weight: bold;
} }
.scrollable-table tr {
position: sticky;
padding: 12px;
text-align: left;
border: 1px solid #ddd;
left: 0;
z-index: 1;
}
.sticky-col { .sticky-col {
position: sticky; text-align: right;
left: 0; padding-right: 8px;
z-index: 1;
} }
.resource:hover { .resource:hover {
text-decoration: underline; text-decoration: underline;
} }
.resource { .resource {
cursor: pointer; cursor: pointer;
} }
th {
min-width: 100px;
background-color: #f2f2f2;
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}
`; `;
} }
@@ -410,6 +557,11 @@ class FrameGraphTable extends LitElement {
return { return {
frameGraphData: {type: Object, state: true}, // Expecting a JSON frame graph structure frameGraphData: {type: Object, state: true}, // Expecting a JSON frame graph structure
selectedResourceId: {type: Number, attribute: 'selected-resource'}, selectedResourceId: {type: Number, attribute: 'selected-resource'},
selectedPassIndex: {type: Number, attribute: 'selected-pass'},
tooltipText: {type: String, state: true},
tooltipVisible: {type: Boolean, state: true},
tooltipX: {type: Number, state: true},
tooltipY: {type: Number, state: true}
}; };
} }
@@ -417,30 +569,81 @@ class FrameGraphTable extends LitElement {
super(); super();
this.frameGraphData = null; this.frameGraphData = null;
this.selectedResourceId = -1; this.selectedResourceId = -1;
this.selectedPassIndex = -1;
this.expandedResourceSet = new Set(); this.expandedResourceSet = new Set();
this.subresourceToParent = {};
this._hoverTimeout = null;
}
_handleCellMouseEnter(e, text) {
if (this._hoverTimeout) {
clearTimeout(this._hoverTimeout);
}
this._hoverTimeout = setTimeout(() => {
this.tooltipText = text;
this.tooltipVisible = text && text.length > 0;
this.tooltipX = e.clientX;
this.tooltipY = e.clientY;
}, 500); // 500ms delay
}
_handleCellMouseLeave() {
if (this._hoverTimeout) {
clearTimeout(this._hoverTimeout);
this._hoverTimeout = null;
}
this.tooltipVisible = false;
}
_getRowGridTemplateColumnsStyle(numColumns) {
let out = '185px ';
const oneCol = '35px ';
for (let i = 0; i < numColumns; i++) {
out += oneCol;
}
return `grid-template-columns:${out};`;
} }
updated(props) { updated(props) {
if (props.has('frameGraphData')) { if (props.has('frameGraphData')) {
this.subresourceToParent = {};
if (this.frameGraphData && this.frameGraphData.resources) {
const resources = Object.values(this.frameGraphData.resources);
resources.forEach((subres) => {
resources.forEach((parent) => {
if (this._isSubresourceOfParent(subres, parent.id)) {
this.subresourceToParent[subres.id] = parent.id;
}
});
});
}
this.requestUpdate(); this.requestUpdate();
} }
if (props.has('selectedResourceId')) {
const parentId = this.subresourceToParent[this.selectedResourceId];
if (parentId && !this.expandedResourceSet.has(parentId)) {
this.expandedResourceSet.add(parentId);
this.requestUpdate();
}
}
} }
_getCellColor(type) { _getCellColor(type, defaultColor) {
return { return {
[RESOURCE_USAGE_TYPE_READ]: READ_COLOR, [RESOURCE_USAGE_TYPE_READ]: READ_COLOR,
[RESOURCE_USAGE_TYPE_WRITE]: WRITE_COLOR, [RESOURCE_USAGE_TYPE_WRITE]: WRITE_COLOR,
[RESOURCE_USAGE_TYPE_NO_ACCESS]: NO_ACCESS_COLOR, [RESOURCE_USAGE_TYPE_NO_ACCESS]: NO_ACCESS_COLOR,
[RESOURCE_USAGE_TYPE_READ_WRITE]: READ_WRITE_COLOR [RESOURCE_USAGE_TYPE_READ_WRITE]: READ_WRITE_COLOR
}[type] || DEFAULT_COLOR; }[type] || defaultColor;
} }
_toggleCollapse(resourceIndex) { _toggleCollapse(resourceId) {
if (this.expandedResourceSet.has(resourceIndex)) { if (this.expandedResourceSet.has(resourceId)) {
this.expandedResourceSet.delete(resourceIndex); this.expandedResourceSet.delete(resourceId);
} }
else { else {
this.expandedResourceSet.add(resourceIndex); this.expandedResourceSet.add(resourceId);
} }
this.requestUpdate(); this.requestUpdate();
} }
@@ -453,11 +656,13 @@ class FrameGraphTable extends LitElement {
})); }));
} }
_renderResourceUsage(allPasses, resourceIds, defaultColor) { _renderResourceUsage(allPasses, resourceIds, defaultColor, lastRow) {
const passLen = allPasses.length;
return allPasses.map((passData, index) => { return allPasses.map((passData, index) => {
const isSelectedPass = index === this.selectedPassIndex;
const isRead = resourceIds.some(resourceId => passData?.reads.includes(resourceId)); const isRead = resourceIds.some(resourceId => passData?.reads.includes(resourceId));
const isWrite = resourceIds.some(resourceId => passData?.writes.includes(resourceId)); const isWrite = resourceIds.some(resourceId => passData?.writes.includes(resourceId));
let type = null; let restype = null;
const hasUsed = (passData) => { const hasUsed = (passData) => {
return resourceIds.some(resourceId => return resourceIds.some(resourceId =>
@@ -467,22 +672,39 @@ class FrameGraphTable extends LitElement {
const hasBeenUsedBefore = allPasses.slice(0, index).some(hasUsed); const hasBeenUsedBefore = allPasses.slice(0, index).some(hasUsed);
const willBeUsedLater = allPasses.slice(index + 1).some(hasUsed); const willBeUsedLater = allPasses.slice(index + 1).some(hasUsed);
if (isRead && isWrite) type = RESOURCE_USAGE_TYPE_READ_WRITE; if (isRead && isWrite) restype = RESOURCE_USAGE_TYPE_READ_WRITE;
else if (isRead) type = RESOURCE_USAGE_TYPE_READ; else if (isRead) restype = RESOURCE_USAGE_TYPE_READ;
else if (isWrite) type = RESOURCE_USAGE_TYPE_WRITE; else if (isWrite) restype = RESOURCE_USAGE_TYPE_WRITE;
else if (hasBeenUsedBefore && willBeUsedLater) type = RESOURCE_USAGE_TYPE_NO_ACCESS; else if (hasBeenUsedBefore && willBeUsedLater) restype = RESOURCE_USAGE_TYPE_NO_ACCESS;
const lastPass = index == passLen - 1;
const borderStyle = '1px solid #a5a5a5';
const bgColor = isSelectedPass ? this._getCellColor(restype, SELECTED_ROW_COLUMN_COLOR)
: this._getCellColor(restype, defaultColor);
const style = `background-color:${bgColor};` +
`border-left:${borderStyle};border-top:${borderStyle};` +
(lastPass ? `border-right:${borderStyle};` : '') +
(lastRow ? `border-bottom:${borderStyle};` : '');
let tooltip = '';
if (restype === RESOURCE_USAGE_TYPE_READ) tooltip = 'Read';
else if (restype === RESOURCE_USAGE_TYPE_WRITE) tooltip = 'Write';
else if (restype === RESOURCE_USAGE_TYPE_READ_WRITE) tooltip = 'Read-Write';
else if (restype === RESOURCE_USAGE_TYPE_NO_ACCESS) tooltip = 'No Access';
return html` return html`
<td style="background-color: ${unsafeCSS(this._getCellColor(type, defaultColor))};"> <div class="grid-cell" style="${style}"
${type ?? nothing} @mouseenter="${(e) => this._handleCellMouseEnter(e, tooltip)}"
</td>`; @mouseleave="${this._handleCellMouseLeave}">
&nbsp;
</div>`;
}); });
} }
_isSubresourceOfParent(resource, parentResource){ _isSubresourceOfParent(resource, parentResourceId){
return resource.properties?.some(prop => return resource.properties?.some(prop =>
prop.key === IS_SUBRESOURCE_KEY && prop.key === IS_SUBRESOURCE_KEY &&
Number(prop.value) === parentResource.id) Number(prop.value) === parentResourceId)
} }
_isSubresource(resource) { _isSubresource(resource) {
@@ -490,64 +712,71 @@ class FrameGraphTable extends LitElement {
} }
_renderResourceRows(resources, allPasses) { _renderResourceRows(resources, allPasses) {
return resources const isExpandedSubresource = (subres) => {
.filter(resource => !this._isSubresource(resource)) const parentId = this.subresourceToParent[subres.id];
.map((resource, resourceIndex) => this._renderResourceRow(resource, resourceIndex, resources, allPasses)); return !!parentId && this.expandedResourceSet.has(parentId);
}
const allRes = resources
.filter(resource => (
!this._isSubresource(resource) || isExpandedSubresource(resource)
));
const allResLen = allRes.length;
return allRes.map((resource, resourceIndex) =>
this._renderResourceRow(
resource, resources, allPasses, resourceIndex == allResLen-1));
} }
_renderResourceRow(resource, resourceIndex, resources, allPasses) { _renderResourceRow(resource, resources, allPasses, lastRow) {
const subresourceIds = resources const subresourceIds = resources
.filter(subresource => this._isSubresourceOfParent(subresource, resource)) .filter(subresource => this._isSubresourceOfParent(subresource, resource.id))
.map(subresource => subresource.id); .map(subresource => subresource.id);
const hasSubresources = subresourceIds.length > 0; const hasSubresources = subresourceIds.length > 0;
const isExpanded = this.expandedResourceSet.has(resourceIndex); const isExpanded = this.expandedResourceSet.has(resource.id);
// Show the aggregated resource usage when the subresources are collapsed. // Show the aggregated resource usage when the subresources are collapsed.
const resourceIds = isExpanded ? [resource.id]:[resource.id, ...subresourceIds]; const resourceIds = isExpanded ? [resource.id]:[resource.id, ...subresourceIds];
const onClickResource = () => this._handleResourceClick(resource.id); // This will give the parent and expanded-subresources a coloring hint.
const selectedStyle = resource.id === this.selectedResourceId ? "selected" : ""; let groupColorStyle = "";
if (!!this.subresourceToParent[resource.id]) {
const colorIndex = this.subresourceToParent[resource.id] % COLORS.length;
groupColorStyle = "border-right:5px solid " + COLORS[colorIndex];
} else if (isExpanded) {
const colorIndex = resource.id % COLORS.length;
groupColorStyle = "border-right:5px solid " + COLORS[colorIndex];
}
const isSelectedResource = resource.id === this.selectedResourceId;
const onClickResource = () => this._handleResourceClick(resource.id);
const selectedStyle = isSelectedResource ? "selected" : "";
const rowStyle = this._getRowGridTemplateColumnsStyle(allPasses.length);
const defaultColor = isSelectedResource ? SELECTED_ROW_COLUMN_COLOR : DEFAULT_COLOR;
return html` return html`
<tr id="resource-${resourceIndex}"> <div class="grid-row" id="resource-${resource.id}" style="${rowStyle}">
<th class="sticky-col resource ${selectedStyle}" @click="${onClickResource}"> <div class="grid-cell sticky-col resource ${selectedStyle}" style="${groupColorStyle}"
@click="${onClickResource}">
${hasSubresources ${hasSubresources
? html` ? html`
<span class="toggle-icon" <span class="toggle-icon"
@click="${(e) => { e.stopPropagation(); this._toggleCollapse(resourceIndex); }}"> @click="${(e) => { e.stopPropagation(); this._toggleCollapse(resource.id); }}">
${isExpanded ? '▼' : '▶'} ${isExpanded ? '▼' : '▶'}
</span>` </span>`
: nothing} : nothing}
${resource.name} ${resource.name}
${hasSubresources && !isExpanded ? html`(${subresourceIds.length})` : nothing} ${hasSubresources && !isExpanded ? html`(${subresourceIds.length})` : nothing}
</th> </div>
${this._renderResourceUsage(allPasses, resourceIds, DEFAULT_COLOR)} ${this._renderResourceUsage(allPasses, resourceIds, defaultColor, lastRow)}
</tr> </div>
${isExpanded ? this._renderSubresourceRows(resources, resource, resourceIndex, allPasses) : nothing}
`; `;
} }
_handlePassClick(index) {
_renderSubresourceRows(resources, parentResource, resourceIndex, allPasses) { this.dispatchEvent(new CustomEvent('select-pass', {
return resources detail: index,
.filter(subresource => this._isSubresourceOfParent(subresource, parentResource)) bubbles: true,
.map((subresource, subIndex) => this._renderSubresourceRow(subresource, resourceIndex, subIndex, allPasses)); composed: true
} }));
_renderSubresourceRow(subresource, resourceIndex, subIndex, allPasses) {
const onClickResource = () => this._handleResourceClick(subresource.id);
const selectedStyle = subresource.id === this.selectedResourceId ? "selected" : "";
return html`
<tr id="subresource-${resourceIndex}-${subIndex}" class="collapsible">
<td class="sticky-col resource ${selectedStyle}"
@click="${onClickResource}"
style="background-color: ${SUBRESOURCE_COLOR}">
${subresource.name}
</td>
${this._renderResourceUsage(allPasses, [subresource.id], SUBRESOURCE_COLOR)}
</tr>
`;
} }
render() { render() {
@@ -556,21 +785,30 @@ class FrameGraphTable extends LitElement {
const allPasses = this.frameGraphData.passes; const allPasses = this.frameGraphData.passes;
const resources = Object.values(this.frameGraphData.resources); const resources = Object.values(this.frameGraphData.resources);
const rowStyle = this._getRowGridTemplateColumnsStyle(allPasses.length);
const colStyle = "border:1px solid rgba(0,0,0,0)";
return html` return html`
<div class="table-container"> <div class="grid-table">
<table class="scrollable-table"> <div class="grid-row" style="${rowStyle}">
<thead> <div class="grid-cell" style="${colStyle}">
<tr> </div>
<th class="sticky-col">Resources/Passes</th> ${allPasses.map((pass, index) => {
${allPasses.map(pass => html`<th>${pass.name}</th>`)} const isSelected = index === this.selectedPassIndex;
</tr> const gclass = isSelected ? "grid-cell selected" : "grid-cell";
</thead> const pclass = isSelected ? "pass-cell selected" : "pass-cell";
<tbody> return html`<div class="${gclass}" style="border:1px solid rgba(0,0,0,0); cursor: pointer;"
${this._renderResourceRows(resources, allPasses)} @click="${() => this._handlePassClick(index)}">
</tbody> <div class="${pclass}">${pass.name}</div></div>`;
</table> })}
</div>
${this._renderResourceRows(resources, allPasses)}
</div> </div>
<tooltip-element
.text="${this.tooltipText}"
?visible="${this.tooltipVisible}"
.x="${this.tooltipX}"
.y="${this.tooltipY}">
</tooltip-element>
`; `;
} }
} }
@@ -593,25 +831,25 @@ class GraphvizView extends LitElement {
overflow: auto; overflow: auto;
background-color: white; background-color: white;
} }
#graph { #graph {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
`; `;
} }
static get properties() { static get properties() {
return { return {
graphvizData: {type: String, state: true}, graphvizData: {type: String, state: true},
}; };
} }
constructor() { constructor() {
super(); super();
this.graphvizData = ''; this.graphvizData = '';
} }
updated(changedProps) { updated(changedProps) {
if (changedProps.has('graphvizData')) { if (changedProps.has('graphvizData')) {
this._renderGraphviz(); this._renderGraphviz();
@@ -638,7 +876,7 @@ class GraphvizView extends LitElement {
container.innerHTML = `<div class="error">Failed to render graphviz: ${error.message}</div>`; container.innerHTML = `<div class="error">Failed to render graphviz: ${error.message}</div>`;
} }
} }
render() { render() {
if(!this.graphvizData) if(!this.graphvizData)
return nothing; return nothing;
@@ -660,6 +898,7 @@ class FrameGraphViewer extends LitElement {
height: 100%; height: 100%;
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: row;
} }
`; `;
} }
@@ -671,7 +910,7 @@ class FrameGraphViewer extends LitElement {
get _framegraphTable() { get _framegraphTable() {
return this.renderRoot.querySelector('#table'); return this.renderRoot.querySelector('#table');
} }
get _graphvizView() { get _graphvizView() {
return this.renderRoot.querySelector('#graphviz'); return this.renderRoot.querySelector('#graphviz');
} }
@@ -708,6 +947,7 @@ class FrameGraphViewer extends LitElement {
this.database = {}; this.database = {};
this.selectedFrameGraph = null; this.selectedFrameGraph = null;
this.selectedResourceId = -1; this.selectedResourceId = -1;
this.selectedPassIndex = -1;
this.viewMode = VIEW_MODE_TABLE; this.viewMode = VIEW_MODE_TABLE;
this.init(); this.init();
@@ -719,10 +959,16 @@ class FrameGraphViewer extends LitElement {
this.addEventListener('select-resource', this.addEventListener('select-resource',
(ev) => { (ev) => {
this.selectedResourceId = ev.detail; this.selectedResourceId = this.selectedResourceId === ev.detail ? -1 : ev.detail;
} }
); );
this.addEventListener('select-pass',
(ev) => {
this.selectedPassIndex = this.selectedPassIndex === ev.detail ? -1 : ev.detail;
}
);
this.addEventListener('change-view-mode', this.addEventListener('change-view-mode',
(ev) => { (ev) => {
this.viewMode = ev.detail; this.viewMode = ev.detail;
@@ -736,6 +982,7 @@ class FrameGraphViewer extends LitElement {
database: {type: Object, state: true}, database: {type: Object, state: true},
selectedFrameGraph: {type: String, state: true}, selectedFrameGraph: {type: String, state: true},
selectedResourceId: {type: Number, state: true}, selectedResourceId: {type: Number, state: true},
selectedPassIndex: {type: Number, state: true},
viewMode: {type: String, state: true}, viewMode: {type: String, state: true},
} }
} }
@@ -756,16 +1003,18 @@ class FrameGraphViewer extends LitElement {
?connected="${this.connected}" ?connected="${this.connected}"
selected-framegraph="${this.selectedFrameGraph}" selected-framegraph="${this.selectedFrameGraph}"
selected-resource="${this.selectedResourceId}" selected-resource="${this.selectedResourceId}"
selected-pass="${this.selectedPassIndex}"
view-mode="${this.viewMode}"> view-mode="${this.viewMode}">
</framegraph-sidepanel> </framegraph-sidepanel>
<framegraph-table id="table" <framegraph-table id="table"
style="display: ${this.viewMode === VIEW_MODE_TABLE ? 'block' : 'none'};" style="display: ${this.viewMode === VIEW_MODE_TABLE ? 'block' : 'none'};"
?connected="${this.connected}" ?connected="${this.connected}"
selected-framegraph="${this.selectedFrameGraph}" selected-framegraph="${this.selectedFrameGraph}"
selected-resource="${this.selectedResourceId}"> selected-resource="${this.selectedResourceId}"
selected-pass="${this.selectedPassIndex}">
</framegraph-table> </framegraph-table>
<graphviz-view id="graphviz" <graphviz-view id="graphviz"
style="display: ${this.viewMode === VIEW_MODE_GRAPHVIZ ? 'block' : 'none'};" style="display: ${this.viewMode === VIEW_MODE_GRAPHVIZ ? 'block' : 'none'};"
framegraph-id="${this.selectedFrameGraph}"> framegraph-id="${this.selectedFrameGraph}">
+8
View File
@@ -6,6 +6,14 @@
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1"> <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
<link href="https://google.github.io/filament/favicon.png" rel="icon" type="image/x-icon" /> <link href="https://google.github.io/filament/favicon.png" rel="icon" type="image/x-icon" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
<!--
We load d3 and d3-graphviz via script tags instead of ESM imports in app.js.
This is because d3-graphviz relies on viz.js (WASM), and loading it purely via
ESM from CDNs often fails to locate the WASM file or handle the worker correctly.
-->
<script src="//d3js.org/d3.v7.min.js"></script>
<script src="https://unpkg.com/@hpcc-js/wasm@2.20.0/dist/index.min.js"></script>
<script src="https://unpkg.com/d3-graphviz@5.6.0/build/d3-graphviz.js"></script>
<style> <style>
html, body { html, body {
height: 100%; height: 100%;
@@ -235,12 +235,14 @@ struct PerRenderableData {
math::float4 reserved[8]; math::float4 reserved[8];
static uint32_t packFlagsChannels( static uint32_t packFlagsChannels(
bool skinning, bool morphing, bool contactShadows, bool hasInstanceBuffer, bool skinning, uint8_t morphing, bool contactShadows, bool hasInstanceBuffer,
uint8_t channels) noexcept { uint8_t channels) noexcept {
const uint32_t morphingFlags = uint32_t(morphing) << 9;
return (skinning ? 0x100 : 0) | return (skinning ? 0x100 : 0) |
(morphing ? 0x200 : 0) | morphingFlags |
(contactShadows ? 0x400 : 0) | (contactShadows ? 0x1000 : 0) |
(hasInstanceBuffer ? 0x800 : 0) | (hasInstanceBuffer ? 0x2000 : 0) |
channels; channels;
} }
}; };

Some files were not shown because too many files have changed in this diff Show More