Compare commits

..
Author SHA1 Message Date
Sungun Park 7017bde04d Optimize thread variables in ServiceThread
Wraps thread-related member variables in preprocessor checks to ensure
they are excluded when multithreading is not supported. Previously,
these variables were declared unconditionally, leading to unnecessary
memory usage for platforms that don't support multithreading.
2026-01-20 07:42:15 -08:00
90 changed files with 590 additions and 3059 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:
@@ -18,7 +18,7 @@ To build Filament for Android you must also install the following:
- Android NDK 25.1 or higher
- Java 17
## Environment variables
### Environment variables
To build Filament for Android, make sure the environment variable `ANDROID_HOME` points to the
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
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
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)
- `-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:
@@ -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.
## Linux
### Linux
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.
## macOS
### macOS
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:
@@ -169,7 +169,7 @@ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/fila
ninja
```
## iOS
### iOS
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:
@@ -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.
## Windows
### Windows
### Building on Windows with Visual Studio 2019 or later
#### Building on Windows with Visual Studio 2019 or later
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
```
## Android
### Android
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.
@@ -242,13 +242,13 @@ foremost for `arm64-v8a`.
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
contains a `local.properties` file with the `sdk.dir` property pointing to your installation of
the Android SDK.
### Easy Android build
#### Easy Android build
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:
@@ -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.
### Android Studio
#### 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.
### Manual builds
#### Manual builds
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`).
@@ -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
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
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
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
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
started, follow the instructions for building Filament on your platform ([macOS](#macos) or
+1 -2
View File
@@ -10,7 +10,6 @@ cmake_minimum_required(VERSION 3.22.1)
# ==================================================================================================
# Toolchain configuration
# ==================================================================================================
# On iOS, the deployment target is set inside third_party/clang/ios.cmake
if (APPLE AND NOT IOS)
# This must be set before project() is called
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "")
@@ -587,7 +586,7 @@ if (FILAMENT_SUPPORTS_METAL)
endif()
# Building filamat increases build times and isn't required for web, so turn it off by default.
if (NOT WEBGL)
if (NOT WEBGL AND NOT IOS)
option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" ON)
else()
option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" OFF)
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.69.0'
implementation 'com.google.android.filament:filament-android:1.68.4'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.69.0'
pod 'Filament', '~> 1.68.4'
```
## Documentation
-4
View File
@@ -7,10 +7,6 @@ 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
[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
- engine: "native" Streams are officially deprecated. Use "acquired" streams instead.
+1 -1
View File
@@ -194,7 +194,7 @@ subprojects {
google()
}
if (!name.startsWith("sample") && name != "filament-tools") {
if (!name.startsWith("sample")) {
apply plugin: 'com.android.library'
android {
@@ -18,7 +18,6 @@
#include <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/MorphTargetBuffer.h>
#include <utils/Entity.h>
#include <utils/EntityManager.h>
@@ -208,14 +207,6 @@ Java_com_google_android_filament_Engine_nDestroySkinningBuffer(JNIEnv*, jclass,
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
Java_com_google_android_filament_Engine_nDestroyIndirectLight(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeIndirectLight) {
@@ -337,13 +328,6 @@ Java_com_google_android_filament_Engine_nIsValidSkinningBuffer(JNIEnv*, jclass,
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
Java_com_google_android_filament_Engine_nIsValidIndirectLight(JNIEnv*, jclass,
jlong nativeEngine, jlong nativeIndirectLight) {
@@ -58,27 +58,6 @@ Java_com_google_android_filament_MorphTargetBuffer_nBuilderCount(JNIEnv*, jclass
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"
JNIEXPORT jlong JNICALL
Java_com_google_android_filament_MorphTargetBuffer_nBuilderBuild(JNIEnv*, jclass,
@@ -133,24 +112,3 @@ Java_com_google_android_filament_MorphTargetBuffer_nGetCount(JNIEnv*, jclass,
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject;
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,11 +52,6 @@ Java_com_google_android_filament_ToneMapper_nCreatePBRNeutralToneMapper(JNIEnv*,
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
Java_com_google_android_filament_ToneMapper_nCreateAgxToneMapper(JNIEnv*, jclass, jint look) {
return (jlong) new AgxToneMapper(AgxToneMapper::AgxLook(look));
@@ -939,15 +939,6 @@ public class Engine {
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.
* @param object Object to check for validity
@@ -1201,15 +1192,6 @@ public class Engine {
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.
* @param ibl the {@link IndirectLight} to destroy
@@ -1501,7 +1483,6 @@ public class Engine {
private static native boolean nDestroyIndexBuffer(long nativeEngine, long nativeIndexBuffer);
private static native boolean nDestroyVertexBuffer(long nativeEngine, long nativeVertexBuffer);
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 nDestroyMaterial(long nativeEngine, long nativeMaterial);
private static native boolean nDestroyMaterialInstance(long nativeEngine, long nativeMaterialInstance);
@@ -1518,7 +1499,6 @@ public class Engine {
private static native boolean nIsValidIndexBuffer(long nativeEngine, long nativeIndexBuffer);
private static native boolean nIsValidVertexBuffer(long nativeEngine, long nativeVertexBuffer);
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 nIsValidMaterial(long nativeEngine, long nativeMaterial);
private static native boolean nIsValidMaterialInstance(long nativeEngine, long nativeMaterial, long nativeMaterialInstance);
@@ -64,45 +64,6 @@ public class MorphTargetBuffer {
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.
*
@@ -195,27 +156,6 @@ public class MorphTargetBuffer {
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() {
if (mNativeObject == 0) {
throw new IllegalStateException("Calling method on destroyed MorphTargetBuffer");
@@ -231,16 +171,10 @@ public class MorphTargetBuffer {
private static native void nDestroyBuilder(long nativeBuilder);
private static native void nBuilderVertexCount(long nativeBuilder, int vertexCount);
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 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 nGetVertexCount(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,7 +24,6 @@ package com.google.android.filament;
* <li>ACESLegacyToneMapper</li>
* <li>FilmicToneMapper</li>
* <li>PBRNeutralToneMapper</li>
* <li>GT7ToneMapper</li>
* </ul>
* <li>Debug/validation tone mapping operators</li>
* <ul>
@@ -112,19 +111,6 @@ 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.
*/
@@ -258,7 +244,6 @@ public class ToneMapper {
private static native long nCreateACESLegacyToneMapper();
private static native long nCreateFilmicToneMapper();
private static native long nCreatePBRNeutralToneMapper();
private static native long nCreateGT7ToneMapper();
private static native long nCreateAgxToneMapper(int look);
private static native long nCreateGenericToneMapper(
float contrast, float midGrayIn, float midGrayOut, float hdrMax);
-12
View File
@@ -1,12 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
/.idea/caches
/.idea/gradle.xml
.DS_Store
/build
/captures
.externalNativeBuild
/.cxx
-64
View File
@@ -1,64 +0,0 @@
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
@@ -1,2 +0,0 @@
POM_NAME=Filament
POM_PACKAGING=exe
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.69.0
VERSION_NAME=1.68.4
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
-1
View File
@@ -3,7 +3,6 @@ include ':filament-android'
include ':filamat-android'
include ':gltfio-android'
include ':filament-utils-android'
include ':filament-tools'
// Samples
include ':samples:sample-gltf-viewer'
-255
View File
@@ -1,255 +0,0 @@
<!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>
+1 -2
View File
@@ -158,8 +158,7 @@
<div id="content" class="content">
<main>
<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>
<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>First, find the latest version of the Vulkan headers here:
https://github.com/KhronosGroup/Vulkan-Headers/tags</p>
+18 -18
View File
@@ -158,8 +158,8 @@
<div id="content" class="content">
<main>
<h1 id="building-filament"><a class="header" href="#building-filament">Building Filament</a></h1>
<h2 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h2>
<h2 id="building-filament"><a class="header" href="#building-filament">Building Filament</a></h2>
<h3 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h3>
<p>To build Filament, you must first install the following tools:</p>
<ul>
<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>Java 17</li>
</ul>
<h2 id="environment-variables"><a class="header" href="#environment-variables">Environment variables</a></h2>
<h3 id="environment-variables"><a class="header" href="#environment-variables">Environment variables</a></h3>
<p>To build Filament for Android, make sure the environment variable <code>ANDROID_HOME</code> points to the
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>
<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
in CLion to obtain a usable project.</p>
<h2 id="easy-build"><a class="header" href="#easy-build">Easy build</a></h2>
<h3 id="easy-build"><a class="header" href="#easy-build">Easy build</a></h3>
<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>
<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>-b</code> and <code>-y</code>: <a href="https://google.github.io/filament/notes/asan_ubsan.html">ASAN/UBSAN builds</a></li>
</ul>
<h2 id="filament-specific-cmake-options"><a class="header" href="#filament-specific-cmake-options">Filament-specific CMake Options</a></h2>
<h3 id="filament-specific-cmake-options"><a class="header" href="#filament-specific-cmake-options">Filament-specific CMake Options</a></h3>
<p>The following CMake options are boolean options specific to Filament:</p>
<ul>
<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
</code></pre>
<p>Options can also be set with the CMake GUI.</p>
<h2 id="linux"><a class="header" href="#linux">Linux</a></h2>
<h3 id="linux"><a class="header" href="#linux">Linux</a></h3>
<p>Make sure you've installed the following dependencies:</p>
<ul>
<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
</code></pre>
<p>This will build Filament, its tests and samples, and various host tools.</p>
<h2 id="macos"><a class="header" href="#macos">macOS</a></h2>
<h3 id="macos"><a class="header" href="#macos">macOS</a></h3>
<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>
<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 ../..
ninja
</code></pre>
<h2 id="ios"><a class="header" href="#ios">iOS</a></h2>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<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>
<pre><code class="language-shell">./build.sh -p ios debug
</code></pre>
<p>See <a href="./ios/samples/README.html">ios/samples/README.md</a> for more information.</p>
<h2 id="windows"><a class="header" href="#windows">Windows</a></h2>
<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>
<h3 id="windows"><a class="header" href="#windows">Windows</a></h3>
<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>
<p>Install the following components:</p>
<ul>
<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>
<pre><code class="language-bat">cmake --build . --target gltf_viewer --config Release
</code></pre>
<h2 id="android"><a class="header" href="#android">Android</a></h2>
<h3 id="android"><a class="header" href="#android">Android</a></h3>
<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>
<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
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>
<h3 id="important-sdk-location"><a class="header" href="#important-sdk-location">Important: SDK location</a></h3>
<h4 id="important-sdk-location"><a class="header" href="#important-sdk-location">Important: SDK location</a></h4>
<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
the Android SDK.</p>
<h3 id="easy-android-build"><a class="header" href="#easy-android-build">Easy Android build</a></h3>
<h4 id="easy-android-build"><a class="header" href="#easy-android-build">Easy Android build</a></h4>
<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>
<pre><code class="language-shell">./build.sh -p android release
@@ -341,7 +341,7 @@ the Android SDK.</p>
</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>Run <code>build.sh -h</code> for more information.</p>
<h3 id="android-studio"><a class="header" href="#android-studio">Android Studio</a></h3>
<h4 id="android-studio"><a class="header" href="#android-studio">Android Studio</a></h4>
<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
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
(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>
<h3 id="manual-builds"><a class="header" href="#manual-builds">Manual builds</a></h3>
<h4 id="manual-builds"><a class="header" href="#manual-builds">Manual builds</a></h4>
<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>
<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
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>
<h3 id="aar"><a class="header" href="#aar">AAR</a></h3>
<h4 id="aar"><a class="header" href="#aar">AAR</a></h4>
<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
<code>out/android-release/filament/lib/</code>:</p>
@@ -405,7 +405,7 @@ AAR.</p>
</code></pre>
<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>
<h3 id="using-filaments-aar"><a class="header" href="#using-filaments-aar">Using Filament's AAR</a></h3>
<h4 id="using-filaments-aar"><a class="header" href="#using-filaments-aar">Using Filament's AAR</a></h4>
<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>
<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>
<h2 id="webassembly"><a class="header" href="#webassembly">WebAssembly</a></h2>
<h3 id="webassembly"><a class="header" href="#webassembly">WebAssembly</a></h3>
<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
<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">
<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
custom binary file format. The goal of this binary file format is to allow test applications to
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>
</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>
</a>
@@ -433,7 +433,7 @@ Mesh loadMeshFromFile(filament::Engine* engine, const utils::Path&amp; path,
<i class="fa fa-angle-left"></i>
</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>
</a>
</nav>
+1 -1
View File
@@ -158,7 +158,7 @@
<div id="content" class="content">
<main>
<h1 id="gltfio"><a class="header" href="#gltfio">gltfio</a></h1>
<h1 id="description"><a class="header" href="#description">Description</a></h1>
<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>
<p>gltfio has two plug-in interfaces, <code>TextureProvider</code> and <code>MaterialProvider</code>. Filament ships with
+5 -5
View File
@@ -158,7 +158,7 @@
<div id="content" class="content">
<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
used for debug purpose only.</p>
<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">
<!-- 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>
</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>
</a>
@@ -183,11 +183,11 @@ used for debug purpose only.</p>
</div>
<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>
</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>
</a>
</nav>
+4 -4
View File
@@ -169,11 +169,11 @@
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- 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>
</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>
</a>
@@ -183,11 +183,11 @@
</div>
<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>
</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>
</a>
</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">
<!-- 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>
</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>
</a>
@@ -181,11 +181,11 @@ correct results (as opposed to common techniques such as linear or overlay blend
</div>
<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>
</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>
</a>
</nav>
+4 -4
View File
@@ -167,11 +167,11 @@ be used to reduce shading aliasing.</p>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- 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>
</a>
<a rel="next prefetch" href="../dup/specgen.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<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>
@@ -181,11 +181,11 @@ be used to reduce shading aliasing.</p>
</div>
<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>
</a>
<a rel="next prefetch" href="../dup/specgen.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<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>
-265
View File
@@ -1,265 +0,0 @@
<!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">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/specgen.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<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>
@@ -196,7 +196,7 @@ grazing angles. See Hoffman 2019, "Fresnel Equations Considered Harmful".</p>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/specgen.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<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>
@@ -3,7 +3,7 @@
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>CI: backend - Filament</title>
<title>backend - Filament</title>
<!-- 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">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/backend_test.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<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_renderdiff.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../dup/test_renderdiff.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
@@ -195,11 +195,11 @@ list (e.g., <code>opengl,vulkan</code>). Defaults to both.</li>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/backend_test.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<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_renderdiff.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../dup/test_renderdiff.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
@@ -3,7 +3,7 @@
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>CI: renderdiff - Filament</title>
<title>renderdiff - Filament</title>
<!-- Custom HTML head -->
@@ -293,7 +293,7 @@ the run number is <code>18023632663</code>.</p>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/test_ci_backend.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../dup/test_backend.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
@@ -307,7 +307,7 @@ the run number is <code>18023632663</code>.</p>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/test_ci_backend.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../dup/test_backend.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
+5 -1
View File
@@ -158,7 +158,11 @@
<div id="content" class="content">
<main>
<h1 id="uberz"><a class="header" href="#uberz">uberz</a></h1>
<ul>
<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
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
+199 -199
View File
@@ -504,9 +504,9 @@ D_{GGX}(h,\alpha) = \frac{\aa}{\pi ( (\NoH)^2 (\aa - 1) + 1)^2}
\end{equation}$$
</p><p>
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>
<span class="line"> <span class="hljs-built_in">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>
</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-type">float</span> a = NoH * roughness;</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></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>
@@ -590,10 +590,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL \sqrt{(\NoV)^2 (1 - \aa) + \aa} + \NoV \sqrt{(\N
\end{equation}$$
</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.
</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>
<span class="line"> <span class="hljs-keyword">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-keyword">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>(NoL * NoL * (<span class="hljs-number">1.0</span> - a2) + a2);</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-type">float</span> a2 = roughness * roughness;</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-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></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>
@@ -604,10 +604,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL (\NoV (1 - \alpha) + \alpha) + \NoV (\NoL (1 - \
\end{equation}$$
</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>.
</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>
<span class="line"> <span class="hljs-built_in">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-built_in">float</span> GGXL = NoV * (NoL * (<span class="hljs-number">1.0</span> - a) + a);</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-type">float</span> a = roughness;</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-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></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>
@@ -659,7 +659,7 @@ $$\begin{equation}
\end{equation}$$
</p><p>
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>
<span class="line"></span>
@@ -680,14 +680,14 @@ Where:
$$\begin{equation}
\fGrazing=0.5 + 2 \cdot \alpha cos^2(\theta_d)
\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>
<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>
</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) * 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 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-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-keyword">float</span> lightScatter = 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-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-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-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-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></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>
@@ -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.
</p><p>
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>
<span class="line"> <span class="hljs-type">float</span> a2 = 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>
</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> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</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>
<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"> <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">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 + (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 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> a2 = 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> 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-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> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</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> <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>
<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>
<span class="line"></span>
<span class="line"><span class="hljs-type">void</span> BRDF(...) {</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-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> <span class="hljs-variable">h</span> <span class="hljs-operator">=</span> normalize(v + l);</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> 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> 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> 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">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> <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> <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> <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 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 class="hljs-type">float</span> D = 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">float</span> V = V_SmithGGXCorrelated(NoV, NoL, 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> <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> <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 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 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 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>
@@ -965,7 +965,7 @@ $$\begin{equation}
\end{equation}$$
</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.
</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>
<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.
</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.
</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></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>
@@ -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>
</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.
</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>
<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"></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-built_in">float</span> Vc = V_Kelemen(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-built_in">float</span> Frc = (Dc * Vc) * Fc;</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-type">float</span> Vc = <span class="hljs-built_in">V_Kelemen</span>(clearCoatRoughness, LoH);</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-type">float</span> Frc = (Dc * Vc) * Fc;</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-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}$$
</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.
</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"> float a<span class="hljs-number">2</span> = 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"> 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"> 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"> 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-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-type">float</span> a2 = roughness * roughness;</span>
<span class="line"> <span class="hljs-type">float</span> cos2h = NoH * NoH;</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-type">float</span> sin4h = sin2h * sin2h;</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> * 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>
<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>
[<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.
</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-keyword">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-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> invAlpha = <span class="hljs-number">1.0</span> / roughness;</span>
<span class="line"> <span class="hljs-type">float</span> cos2h = NoH * NoH;</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></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>
@@ -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>
<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-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 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"> multiplier = photometricLight.getDesiredIntensity() /</span>
<span class="line"> photometricLight.getIntegratedIntensity();</span>
<span class="line"> multiplier = photometricLight.<span class="hljs-built_in">getDesiredIntensity</span>() /</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-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 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>
<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>
@@ -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>
</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:
</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>
<span class="line"> <span class="hljs-comment">// uniform vec3 sphericalHarmonics[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"> <span class="hljs-keyword">return</span></span>
<span class="line"> sphericalHarmonics[<span class="hljs-number">0</span>]</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">1</span>] * (n.y)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">2</span>] * (n.z)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">3</span>] * (n.x)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">4</span>] * (n.y * n.x)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">5</span>] * (n.y * n.z)</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-number">7</span>] * (n.z * n.x)</span>
<span class="line"> + sphericalHarmonics[<span class="hljs-number">8</span>] * (n.x * n.x - n.y * n.y);</span>
</p><pre class="listing tilde"><code><span class="line">vec3 irradianceSH(vec3 n) {</span>
<span class="line"> // uniform vec3 sphericalHarmonics<span class="hljs-selector-attr">[9]</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"> return</span>
<span class="line"> sphericalHarmonics<span class="hljs-selector-attr">[0]</span></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-selector-attr">[2]</span> * (n<span class="hljs-selector-class">.z</span>)</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-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-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-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-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-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>
<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>
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.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>
@@ -2507,11 +2507,11 @@ using an environment made of colored vertical stripes (skybox hidden).</span></c
<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>
<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">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 += 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">sq</span>(<span class="hljs-number">1.0</span> - Fc);</span>
<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>
<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>
</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.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-built_in">float</span> diffuse = Fd_Lambert() * 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-meta-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"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span>
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></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-keyword">if</span> defined(SHADING_MODEL_CLOTH)</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 *= <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-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">vec3 indirectDiffuse = irradianceIBL(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">indirectDiffuse *= saturate(subsurfaceColor + NoV);</span>
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></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-keyword">if</span> defined(SHADING_MODEL_CLOTH) &amp;&amp; defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></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-keyword">endif</span></span></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>
<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">// shutterSpeed in seconds</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>
<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">// 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-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-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> / (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 class="hljs-keyword">float</span> ev100 = 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">ev100</span> <span class="hljs-operator">=</span> exposureSettings(aperture, shutterSpeed, sensitivity);</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">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>
<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">// 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-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-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-keyword">const</span> float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };</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-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-type">const</span> float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };</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">// 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">// 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-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-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-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-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-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">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_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_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_COUNT = <span class="hljs-number">107</span>;</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 class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Sample</span> {</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-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"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Sample</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"> 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 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-keyword">auto</span> i0 = <span class="hljs-keyword">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"><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-built_in">size_t</span>((w - CIE_D65_START) / CIE_D65_INTERVAL);</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"> 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-keyword">return</span> lerp(CIE_D65[indexBounds.x], CIE_D65[indexBounds.y], t);</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> <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 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>
<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-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-keyword">auto</span> i1 = <span class="hljs-built_in">std</span>::lower_bound(</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"><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 = std::<span class="hljs-built_in">lower_bound</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>
<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-keyword">float</span> n = lerp(i0-&gt;ior.real(), i1-&gt;ior.real(), 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> t = (w - i0-&gt;w) / (i1-&gt;w - i0-&gt;w);</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-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>
<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-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"> ((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"><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>)) * (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>)) * (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 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-keyword">const</span> mat3f XYZ_sRGB{</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-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">-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>
@@ -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 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"> <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 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-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 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-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 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"> xyz += f0 * CIE_XYZ[i] * 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"> xyz /= y;</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 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 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>
@@ -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>
<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 &amp; 0x55555555) &lt;&lt; <span class="hljs-string">1) | ((bits &amp; 0xAAAAAAAA) &gt;&gt; 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; 0x0F0F0F0F) &lt;&lt; <span class="hljs-string">4) | ((bits &amp; 0xF0F0F0F0) &gt;&gt; 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"> <span class="hljs-built_in">return</span> vec2f(i / numSamples, bits / exp2(32));</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; <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; <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; <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"> 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>
<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>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>
<span class="line"> <span class="hljs-type">float</span> a2 = 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> GGXV = NoL * <span class="hljs-built_in">sqrt</span>((-NoV * a2 + NoV) * NoV + a2);</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> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</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> <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>
<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"> V.x = <span class="hljs-built_in">sqrt</span>(<span class="hljs-number">1.0</span>f - NoV*NoV);</span>
<span class="line"> V.y = <span class="hljs-number">0.0</span>f;</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.0f</span>;</span>
<span class="line"> V.z = NoV;</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-keyword">for</span> (<span class="hljs-type">uint</span> i = <span class="hljs-number">0</span>; i &lt; sampleCount; i++) {</span>
<span class="line"> float2 Xi = hammersley(i, sampleCount);</span>
<span class="line"> float3 H = 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">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> <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"> <span class="hljs-type">float2</span> <span class="hljs-variable">Xi</span> <span class="hljs-operator">=</span> hammersley(i, sampleCount);</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"> <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 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> NoL = 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">VoH</span> <span class="hljs-operator">=</span> saturate(dot(V, H));</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> <span class="hljs-variable">NoH</span> <span class="hljs-operator">=</span> saturate(H.z);</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-type">float</span> G = 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> 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-keyword">if</span> (NoL &gt; <span class="hljs-number">0.0f</span>) {</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> <span class="hljs-variable">Gv</span> <span class="hljs-operator">=</span> G * VoH / NoH;</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.y += Gv * Fc;</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>
<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>
@@ -3851,56 +3851,56 @@ sin(m \phi + \phi) &= sin(m \phi) cos(\phi) + cos(m \phi) sin(\phi) \Leftrightar
\end{align*}$$
</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}\):
</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>
<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-keyword">double</span>* <span class="hljs-keyword">const</span> SHb,</span>
<span class="line"> <span class="hljs-keyword">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-function"><span class="hljs-type">void</span> <span class="hljs-title">computeShBasis</span><span class="hljs-params">(</span>
<span class="line"> <span class="hljs-type">double</span>* <span class="hljs-type">const</span> SHb,</span>
<span class="line"> <span class="hljs-type">size_t</span> numBands,</span>
<span class="line"> <span class="hljs-type">const</span> vec3&amp; s)</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-keyword">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_2 = <span class="hljs-number">0</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"> <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">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-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-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_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 class="hljs-keyword">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-type">double</span> Pmm = <span class="hljs-number">1</span>;</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"> <span class="hljs-keyword">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_2 = Pmm;</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"> SHb[SHindex(-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"> 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-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[SHindex( 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">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"> SHb[<span class="hljs-built_in">SHindex</span>(-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-type">ssize_t</span> l = m + <span class="hljs-number">2</span>; l &lt; numBands; 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 + m - <span class="hljs-number">1</span>) * Pml_2)</span>
<span class="line"> / (l - m);</span>
<span class="line"> Pml_2 = Pml_1;</span>
<span class="line"> Pml_1 = Pml;</span>
<span class="line"> SHb[SHindex(-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"> 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 class="hljs-keyword">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-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-keyword">ssize_t</span> l = m; l &lt; numBands ; l++) {</span>
<span class="line"> SHb[SHindex(-m, l)] *= Sm;</span>
<span class="line"> SHb[SHindex( m, l)] *= Cm;</span>
<span class="line"> <span class="hljs-type">double</span> Cm = s.x;</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-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-type">ssize_t</span> l = m; l &lt; numBands ; l++) {</span>
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, l)] *= Sm;</span>
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, l)] *= Cm;</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-keyword">double</span> Sm1 = Sm * s.x + Cm * 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-type">double</span> Sm1 = Sm * s.x + Cm * s.y;</span>
<span class="line"> Cm = Cm1;</span>
<span class="line"> Sm = Sm1;</span>
<span class="line"> }</span>
@@ -3979,10 +3979,10 @@ $$\begin{equation}
\end{equation}$$
</p><p>
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 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">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>
@@ -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">return</span> <span class="hljs-number">0</span>;</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-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-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">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-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-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>
<span class="line"></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"> 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"> 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"> <span class="hljs-keyword">double</span> r = <span class="hljs-number">1.0</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 = 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 = 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-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-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>
+2 -2
View File
@@ -165,7 +165,7 @@
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../dup/test_ci_renderdiff.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../dup/test_renderdiff.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
@@ -179,7 +179,7 @@
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../dup/test_ci_renderdiff.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../dup/test_renderdiff.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</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>
</a>
<a rel="next prefetch" href="../dup/backend_test.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../dup/test_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>
@@ -184,7 +184,7 @@ for in our Continuation Integration flow <a href="https://https://github.com/goo
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/backend_test.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../dup/test_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>
+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
+2 -8
View File
@@ -82,22 +82,16 @@
"tools/specular-color/README.md": {
"dest": "dup/specular_color.md"
},
"tools/specgen/README.md": {
"dest": "dup/specgen.md"
},
"docs_src/README.md": {
"dest": "dup/docs.md"
},
"test/renderdiff/README.md": {
"dest": "dup/test_ci_renderdiff.md",
"dest": "dup/test_renderdiff.md",
"link_transforms": {
"docs/images/": "../images/"
}
},
"test/backend/README.md": {
"dest": "dup/test_ci_backend.md"
},
"filament/backend/test/README.md": {
"dest": "dup/backend_test.md"
"dest": "dup/test_backend.md"
}
}
-3
View File
@@ -20,6 +20,3 @@ enable = false
[output.html.fold]
enable = false
[output.html.mathjax]
enable = true
+4 -6
View File
@@ -30,9 +30,8 @@
- [Performance analysis](./notes/performance_analysis.md)
- [Framegraph](./notes/framegraph.md)
- [Tests](./notes/tests.md)
- [backend](./dup/backend_test.md)
- [CI: backend](./dup/test_ci_backend.md)
- [CI: renderdiff](./dup/test_ci_renderdiff.md)
- [backend](./dup/test_backend.md)
- [renderdiff](./dup/test_renderdiff.md)
- [Libraries](./notes/libs.md)
- [bluegl](./dup/bluegl.md)
- [bluevk](./dup/bluevk.md)
@@ -47,10 +46,9 @@
- [cmgen](./dup/cmgen.md)
- [cso-lut](./dup/cso_lut.md)
- [filamesh](./dup/filamesh.md)
- [matinfo](./dup/matinfo.md)
- [mipgen](./dup/mipgen.md)
- [normal-blending](./dup/normal_blending.md)
- [mipgen](./dup/mipgen.md)
- [matinfo](./dup/matinfo.md)
- [roughness-prefilter](./dup/roughness_prefilter.md)
- [specgen](./dup/specgen.md)
- [specular-color](./dup/specular_color.md)
- [zbloat](./dup/zbloat.md)
@@ -540,11 +540,6 @@ private:
* to be created from a 3d VkImage.
*/
bool imageView2Don3DImage;
/**
* Desired global priority value for all VkQueue at a system level.
*/
Platform::GpuContextPriority gpuContextPriority = Platform::GpuContextPriority::DEFAULT;
};
void createInstance(ExtensionSet const& requiredExts) noexcept;
+33 -30
View File
@@ -51,39 +51,39 @@ namespace filament::backend {
DriverBase::DriverBase(const Platform::DriverConfig& driverConfig) noexcept
: mDriverConfig(driverConfig) {
if constexpr (UTILS_HAS_THREADING) {
// This thread services user callbacks
mServiceThread = std::thread([this]() {
JobSystem::setThreadName("ServiceThread");
do {
auto& serviceThreadCondition = mServiceThreadCondition;
auto& serviceThreadCallbackQueue = mServiceThreadCallbackQueue;
#if UTILS_HAS_THREADING
// This thread services user callbacks
mServiceThread = std::thread([this]() {
JobSystem::setThreadName("ServiceThread");
do {
auto& serviceThreadCondition = mServiceThreadCondition;
auto& serviceThreadCallbackQueue = mServiceThreadCallbackQueue;
// wait for some callbacks to dispatch
std::unique_lock<std::mutex> lock(mServiceThreadLock);
while (serviceThreadCallbackQueue.empty() && !mExitRequested) {
serviceThreadCondition.wait(lock);
}
if (mExitRequested) {
break;
}
// move the callbacks to a temporary vector
auto callbacks(std::move(serviceThreadCallbackQueue));
lock.unlock();
// and make sure to call them without our lock held
for (auto[handler, callback, user]: callbacks) {
handler->post(user, callback);
}
} while (true);
});
}
// wait for some callbacks to dispatch
std::unique_lock<std::mutex> lock(mServiceThreadLock);
while (serviceThreadCallbackQueue.empty() && !mExitRequested) {
serviceThreadCondition.wait(lock);
}
if (mExitRequested) {
break;
}
// move the callbacks to a temporary vector
auto callbacks(std::move(serviceThreadCallbackQueue));
lock.unlock();
// and make sure to call them without our lock held
for (auto[handler, callback, user]: callbacks) {
handler->post(user, callback);
}
} while (true);
});
#endif
}
DriverBase::~DriverBase() noexcept {
assert_invariant(mCallbacks.empty());
if constexpr (UTILS_HAS_THREADING) {
stopServiceThread();
}
#if UTILS_HAS_THREADING
stopServiceThread();
#endif
}
// ------------------------------------------------------------------------------------------------
@@ -107,11 +107,14 @@ void DriverBase::CallbackData::release(CallbackData* data) {
void DriverBase::scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback) {
if (handler && UTILS_HAS_THREADING) {
#if UTILS_HAS_THREADING
if (handler) {
std::lock_guard<std::mutex> const lock(mServiceThreadLock);
mServiceThreadCallbackQueue.emplace_back(handler, callback, user);
mServiceThreadCondition.notify_one();
} else {
} else
#endif
{
std::lock_guard<std::mutex> const lock(mPurgeLock);
mCallbacks.emplace_back(user, callback);
}
+4
View File
@@ -238,9 +238,11 @@ protected:
void debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override;
void debugCommandEnd(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override;
#if UTILS_HAS_THREADING
// 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;
#endif
private:
const Platform::DriverConfig mDriverConfig;
@@ -248,11 +250,13 @@ private:
std::mutex mPurgeLock;
std::vector<std::pair<void*, CallbackHandler::Callback>> mCallbacks;
#if UTILS_HAS_THREADING
std::thread mServiceThread;
std::mutex mServiceThreadLock;
std::condition_variable mServiceThreadCondition;
std::vector<std::tuple<CallbackHandler*, CallbackHandler::Callback, void*>> mServiceThreadCallbackQueue;
bool mExitRequested = false;
#endif
};
+3 -3
View File
@@ -390,9 +390,9 @@ void OpenGLDriver::terminate() {
if (getJobWorker()) {
getJobWorker()->terminate();
}
if constexpr (UTILS_HAS_THREADING) {
stopServiceThread();
}
#if UTILS_HAS_THREADING
stopServiceThread();
#endif
mContext.terminate();
mPlatform.terminate();
@@ -1089,16 +1089,9 @@ UTILS_NOINLINE
/* static */ std::string_view process_countBits(OpenGLContext& context) noexcept {
using namespace std::literals;
// bitCount is available in GL 4.0 and GLES 3.1.
if (context.isAtLeastGL<4, 0>() || context.isAtLeastGLES<3, 1>()) {
return ""sv;
}
// GLES 2.0 does not support bitwise operations or unsigned integers.
if (context.isES2()) {
return ""sv;
}
return R"(
// https://graphics.stanford.edu/%7Eseander/bithacks.html
int bitCount(highp uint value) {
@@ -207,10 +207,6 @@ public:
isDynamicRenderingSupported();
}
inline bool isGlobalPrioritySupported() const noexcept {
return mGlobalPrioritySupported;
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
@@ -244,7 +240,6 @@ private:
bool mPipelineCreationFeedbackSupported = false;
bool mProtectedMemorySupported = false;
bool mVertexInputDynamicStateSupported = false;
bool mGlobalPrioritySupported = false;
// These are options that can be enabled or disabled at an application level.
bool mAsyncPipelineCachePrewarmingEnabled = false;
+2 -2
View File
@@ -851,7 +851,7 @@ void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program, utils
// Base case - build the pipeline without any external samplers.
mPipelineCache.asyncPrewarmCache(
vprogram,
*vprogram.get(),
mPipelineLayoutCache.getLayout(vkLayouts, vprogram),
stereoscopicType,
mStereoscopicEyeCount,
@@ -888,7 +888,7 @@ void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program, utils
}
mPipelineCache.asyncPrewarmCache(
vprogram,
*vprogram.get(),
mPipelineLayoutCache.getLayout(vkLayouts, vprogram),
stereoscopicType,
mStereoscopicEyeCount,
@@ -126,16 +126,14 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
}
}
void VulkanPipelineCache::asyncPrewarmCache(
resource_ptr<VulkanProgram> vprogram,
void VulkanPipelineCache::asyncPrewarmCache(const VulkanProgram& program,
VkPipelineLayout layout,
StereoscopicType stereoscopicType,
uint8_t stereoscopicViewCount,
CompilerPriorityQueue priority) {
uint8_t stereoscopicViewCount, CompilerPriorityQueue priority) {
PipelineKey key {
.shaders = {
vprogram->getVertexShader(),
vprogram->getFragmentShader(),
program.getVertexShader(),
program.getFragmentShader(),
},
// We're using dynamic rendering, so this should be empty.
.renderPass = VK_NULL_HANDLE,
@@ -177,10 +175,7 @@ void VulkanPipelineCache::asyncPrewarmCache(
CallbackManager::Handle cmh = mCallbackManager.get();
auto token = std::make_shared<ProgramToken>();
// Note: we keep a ref to vprogram in this lambda so that the shader modules don't get
// destroyed before we call createPipeline(). This is rare enough that we are ok with
// occasionally creating pipelines for destroyed materials.
mCompilerThreadPool.queue(priority, token, [this, vprogram, key, dynamicOptions, cmh]() mutable {
mCompilerThreadPool.queue(priority, token, [this, key, dynamicOptions, cmh]() mutable {
VkPipeline pipeline = createPipeline(key, dynamicOptions);
mCallbackManager.put(cmh);
// We don't actually need this pipeline, we just wanted to force the driver to cache
@@ -104,7 +104,7 @@ public:
// compiling the pipeline on the main thread at draw time. This is very dependent
// on the implementation of the driver on the current device; it's expected to work
// on devices with VK_EXT_vertex_input_dynamic_state and VK_KHR_dynamic_rendering.
void asyncPrewarmCache(resource_ptr<VulkanProgram> vprogram, VkPipelineLayout layout,
void asyncPrewarmCache(const VulkanProgram& program, VkPipelineLayout layout,
StereoscopicType stereoscopicType, uint8_t stereoscopicViewCount,
CompilerPriorityQueue priority);
@@ -232,7 +232,6 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME,
// Required for dynamic rendering, enable this too.
VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME,
VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME,
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME,
@@ -486,22 +485,6 @@ bool hasUnifiedMemoryArchitecture(VkPhysicalDeviceMemoryProperties memoryPropert
return true;
}
VkQueueGlobalPriorityKHR getVkQueueGlobalPriority(
Platform::GpuContextPriority priority) {
switch (priority) {
case Platform::GpuContextPriority::LOW:
return VK_QUEUE_GLOBAL_PRIORITY_LOW_KHR;
case Platform::GpuContextPriority::MEDIUM:
return VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR;
case Platform::GpuContextPriority::HIGH:
return VK_QUEUE_GLOBAL_PRIORITY_HIGH_KHR;
case Platform::GpuContextPriority::REALTIME:
return VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR;
case Platform::GpuContextPriority::DEFAULT:
return VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR;
}
}
}// anonymous namespace
using SwapChainPtr = VulkanPlatform::SwapChainPtr;
@@ -641,6 +624,9 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
}
}
// TODO: Add support of VK_KHR_global_priority with `driverConfig.gpuContextPriority`
// in VulkanPlatform::createDriver.
MiscDeviceFeatures requestedFeatures {};
if (setContains(deviceExts, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)) {
@@ -653,10 +639,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
context.mPortabilitySubsetFeatures.imageView2DOn3DImage == VK_TRUE;
}
if (context.mGlobalPrioritySupported) {
requestedFeatures.gpuContextPriority = driverConfig.gpuContextPriority;
}
if (mImpl->mDevice == VK_NULL_HANDLE) {
createLogicalDeviceAndQueues(deviceExts, context.mPhysicalDeviceFeatures.features,
context.mPhysicalDeviceVk11Features, context.mProtectedMemorySupported,
@@ -945,13 +927,6 @@ void VulkanPlatform::queryAndSetDeviceFeatures(Platform::DriverConfig const& dri
chainStruct(&context.mPhysicalDeviceFeatures, &context.mPortabilitySubsetFeatures);
}
VkPhysicalDeviceGlobalPriorityQueryFeaturesKHR globalPriorityFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES_KHR,
};
if (setContains(deviceExts, VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME)) {
chainStruct(&context.mPhysicalDeviceFeatures, &globalPriorityFeatures);
}
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
// physicalDeviceFeatures.
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
@@ -968,7 +943,6 @@ void VulkanPlatform::queryAndSetDeviceFeatures(Platform::DriverConfig const& dri
setContains(deviceExts, VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME);
context.mVertexInputDynamicStateSupported =
setContains(deviceExts, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
context.mGlobalPrioritySupported = globalPriorityFeatures.globalPriorityQuery == VK_TRUE;
} else {
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
@@ -1041,18 +1015,9 @@ void VulkanPlatform::createLogicalDeviceAndQueues(const ExtensionSet& deviceExte
for (auto const& ext: deviceExtensions) {
requestExtensions.push_back(ext.data());
}
bool const requiresGpuPriority =
requestedFeatures.gpuContextPriority != Platform::GpuContextPriority::DEFAULT;
VkDeviceQueueGlobalPriorityCreateInfoKHR queuePriorityCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_KHR,
.globalPriority = getVkQueueGlobalPriority(requestedFeatures.gpuContextPriority),
};
VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = {};
deviceQueueCreateInfo[0] = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.pNext = requiresGpuPriority ? &queuePriorityCreateInfo : nullptr,
.queueFamilyIndex = mImpl->mGraphicsQueueFamilyIndex,
.queueCount = 1,
.pQueuePriorities = &queuePriority[0],
@@ -1130,14 +1095,6 @@ void VulkanPlatform::createLogicalDeviceAndQueues(const ExtensionSet& deviceExte
chainStruct(&deviceCreateInfo, &protectedMemory);
}
VkPhysicalDeviceGlobalPriorityQueryFeaturesKHR globalPriority = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES_KHR,
.globalPriorityQuery = VK_TRUE,
};
if (requiresGpuPriority) {
chainStruct(&deviceCreateInfo, &globalPriority);
}
mImpl->mDevice = createVkDevice(deviceCreateInfo);
}
+9 -83
View File
@@ -31,29 +31,10 @@
namespace filament {
/**
* A container for vertex morphing data that supports both automatic and manual morphing.
* MorphTargetBuffer is used to hold morphing data (positions and tangents).
*
* MorphTargetBuffer operates in a hybrid model depending on the attribute being morphed:
* Both positions and tangents are required.
*
* 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 {
struct BuilderDetails;
@@ -110,46 +91,6 @@ public:
*/
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.
*
@@ -169,14 +110,16 @@ public:
/**
* 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.
*
* Both positions and tangents must be provided.
*
* @param engine Reference to the filament::Engine associated with this MorphTargetBuffer.
* @param targetIndex the index of morph target to be updated.
* @param positions pointer to at least "count" positions
* @param count number of float3 vectors in positions
* @param offset offset into the target buffer, expressed as a number of float3 vectors
* @param offset offset into the target buffer, expressed as a number of float4 vectors
* @see setTangentsAt
*/
void setPositionsAt(Engine& engine, size_t targetIndex,
math::float3 const* UTILS_NONNULL positions, size_t count, size_t offset = 0);
@@ -184,13 +127,14 @@ public:
/**
* Updates positions for the given morph target.
*
* This method can only be called if the MorphTargetBuffer was built with `withPositions(true)`.
* Both positions and tangents must be provided.
*
* @param engine Reference to the filament::Engine associated with this MorphTargetBuffer.
* @param targetIndex the index of morph target to be updated.
* @param positions pointer to at least "count" positions
* @param count number of float4 vectors in positions
* @param offset offset into the target buffer, expressed as a number of float4 vectors
* @see setTangentsAt
*/
void setPositionsAt(Engine& engine, size_t targetIndex,
math::float4 const* UTILS_NONNULL positions, size_t count, size_t offset = 0);
@@ -198,7 +142,6 @@ public:
/**
* 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]
* range multiplied by 32767.
*
@@ -207,6 +150,7 @@ public:
* @param tangents pointer to at least "count" tangents
* @param count number of short4 quaternions in tangents
* @param offset offset into the target buffer, expressed as a number of short4 vectors
* @see setPositionsAt
*/
void setTangentsAt(Engine& engine, size_t targetIndex,
math::short4 const* UTILS_NONNULL tangents, size_t count, size_t offset = 0);
@@ -223,24 +167,6 @@ public:
*/
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:
// prevent heap allocation
~MorphTargetBuffer() = default;
@@ -165,17 +165,6 @@ public:
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.
*
+3 -26
View File
@@ -45,7 +45,6 @@ namespace filament {
* - ACESLegacyToneMapper
* - FilmicToneMapper
* - PBRNeutralToneMapper
* - GT7ToneMapper
* - Debug/validation tone mapping operators
* - LinearToneMapper
* - DisplayRangeToneMapper
@@ -73,17 +72,15 @@ struct UTILS_PUBLIC ToneMapper {
/**
* 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 of a 3D texture, potentially saving a significant amount
* of memory and generation time.
* This may be used to indicate that the color grading's LUT only requires a 1D texture instead
* of a 3D texture, potentially saving a significant amount of memory and generation time.
*/
virtual bool isOneDimensional() const noexcept { return false; }
/**
* 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; }
};
@@ -159,26 +156,6 @@ struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper {
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.
*/
+40 -68
View File
@@ -218,6 +218,39 @@ inline constexpr float chromaticityCoordinateIlluminantD(float const x) noexcept
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
//------------------------------------------------------------------------------
@@ -289,95 +322,34 @@ inline float3 EOTF_sRGB(float3 x) noexcept {
return x;
}
inline float3 OETF_PQ(float3 x) {
inline float3 OETF_PQ(float3 x, float maxPqValue) {
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_C1 = 3424.0f / 4096.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_C = 10000.0f;
float3 g = pow(x / PQ_C, PQ_constant_N);
x /= maxPqValue;
float3 g = pow(x, PQ_constant_N);
float3 numerator = PQ_constant_C1 + PQ_constant_C2 * g;
float3 denominator = 1.0f + PQ_constant_C3 * g;
return pow(numerator / denominator, PQ_constant_M);
}
inline float3 EOTF_PQ(float3 x) {
inline float3 EOTF_PQ(float3 x, float maxPqValue) {
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_C1 = 3424.0f / 4096.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_C = 10000.0f;
float3 g = pow(saturate(x), 1.0f / PQ_constant_M);
float3 g = pow(x, 1.0f / PQ_constant_M);
float3 numerator = max(g - PQ_constant_C1, 0.0f);
float3 denominator = PQ_constant_C2 - (PQ_constant_C3 * g);
float3 linearColor = pow(numerator / denominator, 1.0f / PQ_constant_N);
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)
};
return linearColor * maxPqValue;
}
//------------------------------------------------------------------------------
+1 -12
View File
@@ -43,16 +43,5 @@ size_t MorphTargetBuffer::getCount() const noexcept {
return downcast(this)->getCount();
}
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
+1 -1
View File
@@ -611,7 +611,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
// calculate the per-primitive face winding order inversion
bool const inverseFrontFaces = viewInverseFrontFaces ^ soaVisibility[i].reversedWindingOrder;
bool const hasMorphing = soaSkinningData[i].morphType != RenderableManager::Builder::MorphType::NONE;
bool const hasMorphing = soaSkinningData[i].morphing;
bool const hasSkinning = soaSkinningData[i].skinning;
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
-182
View File
@@ -256,188 +256,6 @@ float3 PBRNeutralToneMapper::operator()(float3 color) const noexcept {
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
//------------------------------------------------------------------------------
+1 -27
View File
@@ -68,33 +68,8 @@ using namespace filament::math;
using namespace utils;
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;
} // anonymous namespace
struct RenderableManager::BuilderDetails {
using Entry = FRenderableManager::Entry;
@@ -603,8 +578,7 @@ void FRenderableManager::create(
setScreenSpaceContactShadows(ci, builder->mScreenSpaceContactShadows);
setCulling(ci, builder->mCulling);
setSkinning(ci, false);
setMorphing(ci, morphTargetBufferToBuildType(builder->mMorphTargetBuffer,
builder->mMorphTargetCount));
setMorphing(ci, builder->mMorphTargetCount);
setFogEnabled(ci, builder->mFogEnabled);
// do this after calling setAxisAlignedBoundingBox
static_cast<Visibility&>(mManager[ci].visibility).geometryType = builder->mGeometryType;
+5 -7
View File
@@ -63,7 +63,6 @@ class FRenderableManager : public RenderableManager {
public:
using Instance = Instance;
using GeometryType = Builder::GeometryType;
using MorphType = Builder::MorphType;
// TODO: consider renaming, this pertains to material variants, not strictly visibility.
struct Visibility {
@@ -81,7 +80,7 @@ public:
struct Skinning {
bool skinning : 1;
MorphType morphType : 3;
bool morphing : 1;
};
static_assert(sizeof(Visibility) == sizeof(uint16_t), "Visibility should be 16 bits");
@@ -155,7 +154,7 @@ public:
void setSkinningBuffer(Instance instance, FSkinningBuffer* skinningBuffer,
size_t count, size_t offset);
inline void setMorphing(Instance instance, MorphType type);
inline void setMorphing(Instance instance, bool enable);
void setMorphWeights(Instance instance, float const* weights, size_t count, size_t offset);
void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex,
size_t offset);
@@ -413,16 +412,15 @@ void FRenderableManager::setSkinning(Instance const instance, bool const enable)
}
}
void FRenderableManager::setMorphing(Instance const instance, Builder::MorphType const type) {
void FRenderableManager::setMorphing(Instance const instance, bool const enable) {
if (instance) {
Visibility& visibility = mManager[instance].visibility;
FILAMENT_CHECK_PRECONDITION(
visibility.geometryType != GeometryType::STATIC || type != MorphType::NONE)
FILAMENT_CHECK_PRECONDITION(visibility.geometryType != GeometryType::STATIC || !enable)
<< "Morphing can't be used with STATIC geometry";
Skinning& skinning = mManager[instance].skinning;
skinning.morphType = type;
skinning.morphing = enable;
}
}
+17 -55
View File
@@ -36,9 +36,6 @@ using namespace math;
struct MorphTargetBuffer::BuilderDetails {
size_t mVertexCount = 0;
size_t mCount = 0;
bool mWithPositions = true;
bool mWithTangents = true;
bool mEnableCustomMorphing = false;
};
using BuilderType = MorphTargetBuffer;
@@ -59,21 +56,6 @@ MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::count(size_t const count
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 {
return BuilderNameMixin::name(name, len);
}
@@ -126,17 +108,11 @@ inline size_t getSize<TANGENTS>(size_t const vertexCount) noexcept {
FMorphTargetBuffer::EmptyMorphTargetBuilder::EmptyMorphTargetBuilder() {
mImpl->mVertexCount = 1;
mImpl->mCount = 1;
mImpl->mWithPositions = true;
mImpl->mWithTangents = true;
}
FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder)
: mEnableCustomMorphing(builder->mEnableCustomMorphing),
mVertexCount(builder->mVertexCount),
: mVertexCount(builder->mVertexCount),
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)) {
// feature level 0 doesn't support morph target buffers
@@ -145,25 +121,22 @@ FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder)
FEngine::DriverApi& driver = engine.getDriverApi();
if (builder->mWithPositions) {
mPbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
TextureFormat::RGBA32F, 1,
getWidth(mVertexCount),
getHeight(mVertexCount),
mCount,
TextureUsage::DEFAULT,
utils::ImmutableCString{ builder.getName() });
}
// create buffer (here a texture) to store the morphing vertex data
mPbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
TextureFormat::RGBA32F, 1,
getWidth(mVertexCount),
getHeight(mVertexCount),
mCount,
TextureUsage::DEFAULT,
utils::ImmutableCString{ builder.getName() });
if (builder->mWithTangents) {
mTbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
TextureFormat::RGBA16I, 1,
getWidth(mVertexCount),
getHeight(mVertexCount),
mCount,
TextureUsage::DEFAULT,
utils::ImmutableCString{ builder.getName() });
}
mTbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
TextureFormat::RGBA16I, 1,
getWidth(mVertexCount),
getHeight(mVertexCount),
mCount,
TextureUsage::DEFAULT,
utils::ImmutableCString{ builder.getName() });
}
void FMorphTargetBuffer::terminate(FEngine& engine) {
@@ -178,10 +151,6 @@ void FMorphTargetBuffer::terminate(FEngine& engine) {
void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetIndex,
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)
<< "MorphTargetBuffer (size=" << (unsigned)mVertexCount
<< ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")";
@@ -205,10 +174,6 @@ void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetInde
void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetIndex,
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)
<< "MorphTargetBuffer (size=" << mVertexCount
<< ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")";
@@ -231,10 +196,6 @@ void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t const targetInde
void FMorphTargetBuffer::setTangentsAt(FEngine& engine, size_t const targetIndex,
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)
<< "MorphTargetBuffer (size=" << mVertexCount
<< ") overflow (count=" << (unsigned)count << ", offset=" << (unsigned)offset << ")";
@@ -317,3 +278,4 @@ void FMorphTargetBuffer::updateDataAt(DriverApi& driver,
}
} // namespace filament
-13
View File
@@ -56,18 +56,6 @@ public:
void setTangentsAt(FEngine& engine, size_t targetIndex,
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 getCount() const noexcept { return mCount; }
@@ -84,7 +72,6 @@ private:
backend::PixelDataFormat format, backend::PixelDataType type, const char* out,
size_t elementSize, size_t targetIndex, size_t count, size_t offset);
bool mEnableCustomMorphing;
backend::TextureHandle mPbHandle;
backend::TextureHandle mTbHandle;
uint32_t mVertexCount;
+1 -1
View File
@@ -395,7 +395,7 @@ void FScene::prepareVisibleRenderables(Range<uint32_t> visibleRenderables) noexc
uboData.flagsChannels = PerRenderableData::packFlagsChannels(
skinning.skinning,
static_cast<uint8_t>(skinning.morphType),
skinning.morphing,
visibility.screenSpaceContactShadows,
sceneData.elementAt<INSTANCES>(i).buffer != nullptr,
sceneData.elementAt<CHANNELS>(i));
+6 -12
View File
@@ -33,7 +33,6 @@
#include "details/Engine.h"
#include "details/IndirectLight.h"
#include "details/InstanceBuffer.h"
#include "details/MorphTargetBuffer.h"
#include "details/RenderTarget.h"
#include "details/Renderer.h"
#include "details/Scene.h"
@@ -833,18 +832,13 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
+PerRenderableBindingPoints::MORPHING_UNIFORMS,
morphing.handle, 0, sizeof(PerRenderableMorphingUib));
const auto* mtb = morphing.morphTargetBuffer;
if (mtb->hasPositions()) {
descriptorSet.setSampler(layout,
+PerRenderableBindingPoints::MORPH_TARGET_POSITIONS,
mtb->getPositionsHandle(), {});
}
descriptorSet.setSampler(layout,
+PerRenderableBindingPoints::MORPH_TARGET_POSITIONS,
morphing.morphTargetBuffer->getPositionsHandle(), {});
if (mtb->hasTangents()) {
descriptorSet.setSampler(layout,
+PerRenderableBindingPoints::MORPH_TARGET_TANGENTS,
mtb->getTangentsHandle(), {});
}
descriptorSet.setSampler(layout,
+PerRenderableBindingPoints::MORPH_TARGET_TANGENTS,
morphing.morphTargetBuffer->getTangentsHandle(), {});
}
descriptorSet.commit(layout, driver);
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.69.0"
spec.version = "1.68.4"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.69.0/filament-v1.69.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.68.4/filament-v1.68.4-ios.tgz" }
spec.libraries = 'c++'
-2
View File
@@ -1,5 +1,3 @@
# bluevk
## Updating Vulkan headers
To update the Vulkan headers, perform the following steps.
+126 -237
View File
@@ -15,6 +15,8 @@
*/
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";
@@ -26,8 +28,8 @@ const BACKGROUND_COLOR = '#5362e5';
const REGULAR_FONT_SIZE = 12;
// Constants for color operations
const READ_COLOR = '#aee1a6';
const WRITE_COLOR = '#df8e8e';
const READ_COLOR = '#3ac224';
const WRITE_COLOR = '#d43232';
const NO_ACCESS_COLOR = '#d8dde754';
const READ_WRITE_COLOR = '#ffeb99';
const DEFAULT_COLOR = '#ffffff';
@@ -38,10 +40,10 @@ const VIEW_TOGGLE_INACTIVE_COLOR = '#292929';
const VIEW_TOGGLE_UNSELECTED_COLOR = '#3f4cbe';
const VIEW_TOGGLE_SELECTED_COLOR = '#2c3892';
const RESOURCE_USAGE_TYPE_READ = 'R';
const RESOURCE_USAGE_TYPE_WRITE = 'W';
const RESOURCE_USAGE_TYPE_NO_ACCESS = 'NA';
const RESOURCE_USAGE_TYPE_READ_WRITE = 'RW';
const RESOURCE_USAGE_TYPE_READ = 'read';
const RESOURCE_USAGE_TYPE_WRITE = 'write';
const RESOURCE_USAGE_TYPE_NO_ACCESS = 'no-access';
const RESOURCE_USAGE_TYPE_READ_WRITE = 'read-write';
const IS_SUBRESOURCE_KEY = 'is_subresource_of'
@@ -49,18 +51,6 @@ const IS_SUBRESOURCE_KEY = 'is_subresource_of'
const VIEW_MODE_TABLE = 'table';
const VIEW_MODE_GRAPHVIZ = 'graphviz';
const COLORS = [
"#FFB3BA",
"#BAFFC9",
"#BAE1FF",
"#FFDFBA",
"#E2C2FF",
"#B2F0E8",
"#DDCBA4",
"#D4F0B2",
"#DCD0FF"
];
class MenuSection extends LitElement {
static get properties() {
return {
@@ -138,14 +128,14 @@ class FrameGraphSidePanel extends LitElement {
dynamicStyle() {
return `
:host {
position: fixed;
background: ${this.connected ? BACKGROUND_COLOR : INACTIVE_COLOR};
width: 20%;
height: 100%;
max-width: 250px;
min-width: 250px;
overflow-y: auto;
}
.container {
padding: 10px 20px;
overflow-y: auto;
}
.title {
color: white;
@@ -221,7 +211,6 @@ class FrameGraphSidePanel extends LitElement {
this.framegraphs = [];
this.database = {};
this.viewMode = VIEW_MODE_TABLE;
this.selectedFrameGraph = '';
}
updated(props) {
@@ -236,10 +225,6 @@ class FrameGraphSidePanel extends LitElement {
: (labelCount[name] = 0, name);
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);
}
}
}
@@ -260,9 +245,8 @@ class FrameGraphSidePanel extends LitElement {
}
_findCurrentResource() {
if (!this.selectedFrameGraph) {
if (!this.selectedFrameGraph)
return null;
}
const resources = this.database[this.selectedFrameGraph]?.resources;
if (resources) {
@@ -348,108 +332,77 @@ class FrameGraphSidePanel extends LitElement {
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 {
static get styles() {
return css`
:host {
display: block;
font-size: 12px;
margin-top: 100px;
margin-left: 20px;
flex-grow: 1;
width: 80%;
}
.grid-table {
display: flex;
flex-direction: column;
.table-container {
max-height: 100%;
max-width: 100%;
overflow: auto;
border: 1px solid #ddd;
margin-left: 300px;
}
.grid-row {
display: inline-grid;
/* Adjust column widths here (e.g., first column gets 2 parts, others get 1) */
grid-template-columns: 2fr 1fr 1fr;
}
.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;
.scrollable-table {
width: 100%;
height: 100%;
border-collapse: collapse;
}
.collapsible {
background-color: #f9f9f9;
}
.toggle-icon {
cursor: pointer;
margin-right: 5px;
user-select: none;
}
.scrollable-table td,
.scrollable-table th {
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}
.selected {
text-decoration: underline;
}
.sticky-col {
text-align: right;
padding-right: 8px;
.scrollable-table tr {
position: sticky;
padding: 12px;
text-align: left;
border: 1px solid #ddd;
left: 0;
z-index: 1;
}
.sticky-col {
position: sticky;
left: 0;
z-index: 1;
}
.resource:hover {
text-decoration: underline;
}
.resource {
cursor: pointer;
}
th {
min-width: 100px;
background-color: #f2f2f2;
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}
`;
}
@@ -457,10 +410,6 @@ class FrameGraphTable extends LitElement {
return {
frameGraphData: {type: Object, state: true}, // Expecting a JSON frame graph structure
selectedResourceId: {type: Number, attribute: 'selected-resource'},
tooltipText: {type: String, state: true},
tooltipVisible: {type: Boolean, state: true},
tooltipX: {type: Number, state: true},
tooltipY: {type: Number, state: true}
};
}
@@ -469,52 +418,10 @@ class FrameGraphTable extends LitElement {
this.frameGraphData = null;
this.selectedResourceId = -1;
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 = '165px ';
const oneCol = '35px ';
for (let i = 0; i < numColumns; i++) {
out += oneCol;
}
return `grid-template-columns:${out};`;
}
updated(props) {
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();
}
}
@@ -528,12 +435,12 @@ class FrameGraphTable extends LitElement {
}[type] || DEFAULT_COLOR;
}
_toggleCollapse(resourceId) {
if (this.expandedResourceSet.has(resourceId)) {
this.expandedResourceSet.delete(resourceId);
_toggleCollapse(resourceIndex) {
if (this.expandedResourceSet.has(resourceIndex)) {
this.expandedResourceSet.delete(resourceIndex);
}
else {
this.expandedResourceSet.add(resourceId);
this.expandedResourceSet.add(resourceIndex);
}
this.requestUpdate();
}
@@ -546,8 +453,7 @@ class FrameGraphTable extends LitElement {
}));
}
_renderResourceUsage(allPasses, resourceIds, defaultColor, lastRow) {
const passLen = allPasses.length;
_renderResourceUsage(allPasses, resourceIds, defaultColor) {
return allPasses.map((passData, index) => {
const isRead = resourceIds.some(resourceId => passData?.reads.includes(resourceId));
const isWrite = resourceIds.some(resourceId => passData?.writes.includes(resourceId));
@@ -566,33 +472,17 @@ class FrameGraphTable extends LitElement {
else if (isWrite) type = RESOURCE_USAGE_TYPE_WRITE;
else if (hasBeenUsedBefore && willBeUsedLater) type = RESOURCE_USAGE_TYPE_NO_ACCESS;
const lastPass = index == passLen - 1;
const borderStyle = '1px solid #a5a5a5';
const bgColor = this._getCellColor(type, defaultColor);
const style = `background-color:${bgColor};` +
`border-left:${borderStyle};border-top:${borderStyle};` +
(lastPass ? `border-right:${borderStyle};` : '') +
(lastRow ? `border-bottom:${borderStyle};` : '');
let tooltip = '';
if (type === RESOURCE_USAGE_TYPE_READ) tooltip = 'Read';
else if (type === RESOURCE_USAGE_TYPE_WRITE) tooltip = 'Write';
else if (type === RESOURCE_USAGE_TYPE_READ_WRITE) tooltip = 'Read-Write';
else if (type === RESOURCE_USAGE_TYPE_NO_ACCESS) tooltip = 'No Access';
return html`
<div class="grid-cell" style="${style}"
@mouseenter="${(e) => this._handleCellMouseEnter(e, tooltip)}"
@mouseleave="${this._handleCellMouseLeave}">
&nbsp;
</div>`;
<td style="background-color: ${unsafeCSS(this._getCellColor(type, defaultColor))};">
${type ?? nothing}
</td>`;
});
}
_isSubresourceOfParent(resource, parentResourceId){
_isSubresourceOfParent(resource, parentResource){
return resource.properties?.some(prop =>
prop.key === IS_SUBRESOURCE_KEY &&
Number(prop.value) === parentResourceId)
Number(prop.value) === parentResource.id)
}
_isSubresource(resource) {
@@ -600,87 +490,87 @@ class FrameGraphTable extends LitElement {
}
_renderResourceRows(resources, allPasses) {
const isExpandedSubresource = (subres) => {
const parentId = this.subresourceToParent[subres.id];
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));
return resources
.filter(resource => !this._isSubresource(resource))
.map((resource, resourceIndex) => this._renderResourceRow(resource, resourceIndex, resources, allPasses));
}
_renderResourceRow(resource, resources, allPasses, lastRow) {
_renderResourceRow(resource, resourceIndex, resources, allPasses) {
const subresourceIds = resources
.filter(subresource => this._isSubresourceOfParent(subresource, resource.id))
.filter(subresource => this._isSubresourceOfParent(subresource, resource))
.map(subresource => subresource.id);
const hasSubresources = subresourceIds.length > 0;
const isExpanded = this.expandedResourceSet.has(resource.id);
const isExpanded = this.expandedResourceSet.has(resourceIndex);
// Show the aggregated resource usage when the subresources are collapsed.
const resourceIds = isExpanded ? [resource.id]:[resource.id, ...subresourceIds];
// This will give the parent and expanded-subresources a coloring hint.
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 onClickResource = () => this._handleResourceClick(resource.id);
const selectedStyle = resource.id === this.selectedResourceId ? "selected" : "";
const rowStyle = this._getRowGridTemplateColumnsStyle(allPasses.length);
return html`
<div class="grid-row" id="resource-${resource.id}" style="${rowStyle}">
<div class="grid-cell sticky-col resource ${selectedStyle}" style="${groupColorStyle}"
@click="${onClickResource}">
<tr id="resource-${resourceIndex}">
<th class="sticky-col resource ${selectedStyle}" @click="${onClickResource}">
${hasSubresources
? html`
<span class="toggle-icon"
@click="${(e) => { e.stopPropagation(); this._toggleCollapse(resource.id); }}">
@click="${(e) => { e.stopPropagation(); this._toggleCollapse(resourceIndex); }}">
${isExpanded ? '▼' : '▶'}
</span>`
: nothing}
${resource.name}
${hasSubresources && !isExpanded ? html`(${subresourceIds.length})` : nothing}
</div>
${this._renderResourceUsage(allPasses, resourceIds, DEFAULT_COLOR, lastRow)}
</div>
</th>
${this._renderResourceUsage(allPasses, resourceIds, DEFAULT_COLOR)}
</tr>
${isExpanded ? this._renderSubresourceRows(resources, resource, resourceIndex, allPasses) : nothing}
`;
}
_renderSubresourceRows(resources, parentResource, resourceIndex, allPasses) {
return resources
.filter(subresource => this._isSubresourceOfParent(subresource, parentResource))
.map((subresource, subIndex) => this._renderSubresourceRow(subresource, resourceIndex, subIndex, allPasses));
}
_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() {
if (!this.frameGraphData?.passes || !this.frameGraphData?.resources)
return nothing;
const allPasses = this.frameGraphData.passes;
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`
<div class="grid-table">
<div class="grid-row" style="${rowStyle}">
<div class="grid-cell" style="${colStyle}">
</div>
${allPasses.map(pass => html`<div class="grid-cell" style="border:1px solid rgba(0,0,0,0)">
<div class="pass-cell">${pass.name}</div></div>`)}
</div>
${this._renderResourceRows(resources, allPasses)}
<div class="table-container">
<table class="scrollable-table">
<thead>
<tr>
<th class="sticky-col">Resources/Passes</th>
${allPasses.map(pass => html`<th>${pass.name}</th>`)}
</tr>
</thead>
<tbody>
${this._renderResourceRows(resources, allPasses)}
</tbody>
</table>
</div>
<tooltip-element
.text="${this.tooltipText}"
?visible="${this.tooltipVisible}"
.x="${this.tooltipX}"
.y="${this.tooltipY}">
</tooltip-element>
`;
}
}
@@ -703,25 +593,25 @@ class GraphvizView extends LitElement {
overflow: auto;
background-color: white;
}
#graph {
width: 100%;
height: 100%;
}
`;
}
static get properties() {
return {
graphvizData: {type: String, state: true},
};
}
constructor() {
super();
this.graphvizData = '';
}
updated(changedProps) {
if (changedProps.has('graphvizData')) {
this._renderGraphviz();
@@ -748,7 +638,7 @@ class GraphvizView extends LitElement {
container.innerHTML = `<div class="error">Failed to render graphviz: ${error.message}</div>`;
}
}
render() {
if(!this.graphvizData)
return nothing;
@@ -770,7 +660,6 @@ class FrameGraphViewer extends LitElement {
height: 100%;
width: 100%;
display: flex;
flex-direction: row;
}
`;
}
@@ -782,7 +671,7 @@ class FrameGraphViewer extends LitElement {
get _framegraphTable() {
return this.renderRoot.querySelector('#table');
}
get _graphvizView() {
return this.renderRoot.querySelector('#graphviz');
}
@@ -833,7 +722,7 @@ class FrameGraphViewer extends LitElement {
this.selectedResourceId = ev.detail;
}
);
this.addEventListener('change-view-mode',
(ev) => {
this.viewMode = ev.detail;
@@ -869,14 +758,14 @@ class FrameGraphViewer extends LitElement {
selected-resource="${this.selectedResourceId}"
view-mode="${this.viewMode}">
</framegraph-sidepanel>
<framegraph-table id="table"
style="display: ${this.viewMode === VIEW_MODE_TABLE ? 'block' : 'none'};"
?connected="${this.connected}"
selected-framegraph="${this.selectedFrameGraph}"
selected-resource="${this.selectedResourceId}">
</framegraph-table>
<graphviz-view id="graphviz"
style="display: ${this.viewMode === VIEW_MODE_GRAPHVIZ ? 'block' : 'none'};"
framegraph-id="${this.selectedFrameGraph}">
-8
View File
@@ -6,14 +6,6 @@
<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://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>
html, body {
height: 100%;
@@ -28,7 +28,7 @@
namespace filament {
// update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 69;
static constexpr size_t MATERIAL_VERSION = 68;
// Those are the api levels that are used in the source material file (.mat)
//
@@ -235,14 +235,12 @@ struct PerRenderableData {
math::float4 reserved[8];
static uint32_t packFlagsChannels(
bool skinning, uint8_t morphing, bool contactShadows, bool hasInstanceBuffer,
bool skinning, bool morphing, bool contactShadows, bool hasInstanceBuffer,
uint8_t channels) noexcept {
const uint32_t morphingFlags = uint32_t(morphing) << 9;
return (skinning ? 0x100 : 0) |
morphingFlags |
(contactShadows ? 0x1000 : 0) |
(hasInstanceBuffer ? 0x2000 : 0) |
(morphing ? 0x200 : 0) |
(contactShadows ? 0x400 : 0) |
(hasInstanceBuffer ? 0x800 : 0) |
channels;
}
};
-3
View File
@@ -1,7 +1,4 @@
cmake_minimum_required(VERSION 3.19)
include(${EXTERNAL}/clang/usage_ios_13.cmake)
project(filamat)
set(TARGET filamat)
+1 -1
View File
@@ -1,4 +1,4 @@
# gltfio
# Description
`gltfio` is a loader library that consumes `gltf` or `glb` content and produces Filament
objects. For usage details, see the docstring for `AssetLoader`.
+4 -1
View File
@@ -1,4 +1,7 @@
# uberz
- [Ubershader Archive Files](#ubershader-archive-files)
- [Ubershader Spec Files](#ubershader-spec-files)
# Ubershader Archive Files
An ubershader archive provides a way to bundle up a set of `filamat` files along with some metadata
that conveys which glTF features each material can handle. It is a file that has been compressed
+1 -2
View File
@@ -61,8 +61,7 @@ enum class ToneMapping : uint8_t {
AGX = 4,
GENERIC = 5,
PBR_NEUTRAL = 6,
GT7 = 7,
DISPLAY_RANGE = 8,
DISPLAY_RANGE = 7,
};
using AmbientOcclusionOptions = filament::View::AmbientOcclusionOptions;
-3
View File
@@ -88,7 +88,6 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ToneMapp
else if (0 == compare(tokens[i], jsonChunk, "AGX")) { *out = ToneMapping::AGX; }
else if (0 == compare(tokens[i], jsonChunk, "GENERIC")) { *out = ToneMapping::GENERIC; }
else if (0 == compare(tokens[i], jsonChunk, "PBR_NEUTRAL")) { *out = ToneMapping::PBR_NEUTRAL; }
else if (0 == compare(tokens[i], jsonChunk, "GT7")) { *out = ToneMapping::GT7; }
else if (0 == compare(tokens[i], jsonChunk, "DISPLAY_RANGE")) { *out = ToneMapping::DISPLAY_RANGE; }
else {
slog.w << "Invalid ToneMapping: '" << STR(tokens[i], jsonChunk) << "'" << io::endl;
@@ -723,7 +722,6 @@ constexpr ToneMapper* createToneMapper(const ColorGradingSettings& settings) noe
settings.genericToneMapper.hdrMax
);
case ToneMapping::PBR_NEUTRAL: return new PBRNeutralToneMapper;
case ToneMapping::GT7: return new GT7ToneMapper;
case ToneMapping::DISPLAY_RANGE: return new DisplayRangeToneMapper;
}
}
@@ -775,7 +773,6 @@ static std::ostream& operator<<(std::ostream& out, ToneMapping in) {
case ToneMapping::AGX: return out << "\"AGX\"";
case ToneMapping::GENERIC: return out << "\"GENERIC\"";
case ToneMapping::PBR_NEUTRAL: return out << "\"PBR_NEUTRAL\"";
case ToneMapping::GT7: return out << "\"GT7\"";
case ToneMapping::DISPLAY_RANGE: return out << "\"DISPLAY_RANGE\"";
}
return out << "\"INVALID\"";
+1 -4
View File
@@ -178,9 +178,6 @@ static void computeToneMapPlot(ColorGradingSettings& settings, float* plot) {
case ToneMapping::PBR_NEUTRAL:
mapper = new PBRNeutralToneMapper;
break;
case ToneMapping::GT7:
mapper = new GT7ToneMapper;
break;
case ToneMapping::DISPLAY_RANGE:
mapper = new DisplayRangeToneMapper;
break;
@@ -232,7 +229,7 @@ static void colorGradingUI(Settings& settings, float* rangePlot, float* curvePlo
int toneMapping = (int) colorGrading.toneMapping;
ImGui::Combo("Tone-mapping", &toneMapping,
"Linear\0ACES (legacy)\0ACES\0Filmic\0AgX\0Generic\0PBR Neutral\0GT7\0Display Range\0\0");
"Linear\0ACES (legacy)\0ACES\0Filmic\0AgX\0Generic\0PBR Neutral\0Display Range\0\0");
colorGrading.toneMapping = (decltype(colorGrading.toneMapping)) toneMapping;
if (colorGrading.toneMapping == ToneMapping::GENERIC) {
if (ImGui::CollapsingHeader("Tonemap parameters")) {
-3
View File
@@ -39,7 +39,6 @@ set(MATERIAL_SRCS
materials/sandboxSubsurface.mat
materials/sandboxUnlit.mat
materials/texturedLit.mat
materials/uvmorph.mat
)
# Tint does not support setting gl_PointSize, disable the relevant sample if using WebGPU
@@ -270,9 +269,7 @@ if (NOT ANDROID)
add_demo(gltf_viewer)
add_demo(gltf_instances)
add_demo(heightfield)
add_demo(helloasync)
add_demo(hellomorphing)
add_demo(hellouvmorphing)
add_demo(hellopbr)
add_demo(hellotriangle)
add_demo(helloskinning)
-582
View File
@@ -1,582 +0,0 @@
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "common/arguments.h"
#include "filament/TransformManager.h"
#include <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/Skybox.h>
#include <filament/Texture.h>
#include <filament/TextureSampler.h>
#include <filament/VertexBuffer.h>
#include <filament/View.h>
#include <utils/EntityManager.h>
#include <utils/Path.h>
#include <filamentapp/Config.h>
#include <filamentapp/FilamentApp.h>
#include <getopt/getopt.h>
#include <stb_image.h>
#include <iostream> // for cerr
#include <memory>
#include <string> // for printing usage/help
#include "generated/resources/resources.h"
using namespace filament;
using utils::Entity;
using utils::EntityManager;
using utils::Path;
using MinFilter = TextureSampler::MinFilter;
using MagFilter = TextureSampler::MagFilter;
struct Vertex {
filament::math::float2 position;
filament::math::float2 uv;
};
static const Vertex QUAD_VERTICES[4] = {
{{-1, -1}, {0, 0}},
{{ 1, -1}, {1, 0}},
{{-1, 1}, {0, 1}},
{{ 1, 1}, {1, 1}},
};
static constexpr uint16_t QUAD_INDICES[6] = {
0, 1, 2,
3, 2, 1,
};
static void printUsage(char* name) {
std::string exec_name(utils::Path(name).getName());
std::string usage("HELLOASYNC creates resources asynchronously\n"
"Usage:\n"
" HELLOASYNC [options]\n"
"Options:\n"
" --help, -h\n"
" Prints this message\n\n"
"API_USAGE");
const std::string from("HELLOASYNC");
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
usage.replace(pos, from.length(), exec_name);
}
const std::string apiUsage("API_USAGE");
for (size_t pos = usage.find(apiUsage); pos != std::string::npos;
pos = usage.find(apiUsage, pos)) {
usage.replace(pos, apiUsage.length(), samples::getBackendAPIArgumentsUsage());
}
std::cout << usage;
}
static int handleCommandLineArguments(int argc, char* argv[], Config& config) {
static constexpr const char* OPTSTR = "ha:";
static const struct option OPTIONS[] = {
{ "help", no_argument, nullptr, 'h' },
{ "api", required_argument, nullptr, 'a' },
{ nullptr, 0, nullptr, 0 }
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) {
std::string arg(optarg ? optarg : "");
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
exit(0);
case 'a':
config.backend = samples::parseArgumentsForBackend(arg);
break;
}
}
return optind;
}
struct App {
// Global data
Engine* engine = nullptr;
Entity camera;
Scene* scene;
Skybox* skybox = nullptr;
Camera* cam = nullptr;
Material* mat = nullptr;
// --------------------------------------------------------------------------------------------
// Everything below this point is for demonstrating async logic.
// The number of objects to be created.
static constexpr int OBJECT_COUNT = 400;
static constexpr int OBJECT_COUNT_PER_ROW = 20;
static constexpr int ROW_COUNT =
(OBJECT_COUNT + OBJECT_COUNT_PER_ROW - 1) / OBJECT_COUNT_PER_ROW;
// For demonstration purposes, we load one image and it is shared for every ObjectData instance.
int imageWidth;
int imageHeight;
int imageChannels;
struct StbImageDeleter {
void operator()(stbi_uc* p) const {
// We delay freeing the stb image until after the engine has completely shut down. This
// ensures the data remains valid while the engine flushing pending tasks (see
// `updateTexture`).
// Note: This cleanup is specific to this sample because the image is shared across
// multiple objects. In a standard application, memory should be released via the
// cleanup callback in `PixelBufferDescriptor`. (see `updateTexture`)
stbi_image_free(p);
}
};
std::unique_ptr<stbi_uc, StbImageDeleter> imageData;
// Object data associated with a single renderable object.
struct ObjectData {
Texture* tex = nullptr;
MaterialInstance* matInstance = nullptr;
VertexBuffer* vb = nullptr;
IndexBuffer* ib = nullptr;
Entity renderable;
filament::math::mat4f baseTransform;
bool texReady = false;
bool vbReady = false;
bool ibReady = false;
[[nodiscard]] bool isReadyToCreateRenderable() const {
return texReady && vbReady && ibReady;
}
} objectData[OBJECT_COUNT];
// The number of objects currently being loaded.
int loadingObjectIndex = 0;
// To prevent calling APIs during shutdown. This variable is always referenced in the main(app)
// thread, so synchronization is unnecessary.
bool shuttingDown = false;
// Completion callbacks for chained actions. We store them here instead of directly passing them
// to async APIs as parameters for better maintainability and legibility.
using OnLoadImageComplete = Engine::AsyncCompletionCallback;
using OnCreateTextureComplete = Texture::AsyncCompletionCallback;
using OnTextureUpdateComplete = Texture::AsyncCompletionCallback;
using OnCreateVertexBufferComplete = VertexBuffer::AsyncCompletionCallback;
using OnVertexBufferUpdateComplete = VertexBuffer::AsyncCompletionCallback;
using OnCreateIndexBufferComplete = IndexBuffer::AsyncCompletionCallback;
using OnIndexBufferUpdateComplete = IndexBuffer::AsyncCompletionCallback;
OnLoadImageComplete onLoadImageComplete; // -> Create material & start async renderable creation
OnCreateTextureComplete onCreateTextureComplete; // -> Update texture
OnTextureUpdateComplete onTextureUpdateComplete; // -> Create mat instance & mark texture ready!
OnCreateVertexBufferComplete onCreateVertexBufferComplete; // -> Update vertex buffer
OnVertexBufferUpdateComplete onVertexBufferUpdateComplete; // -> Mark vertex buffer ready!
OnCreateIndexBufferComplete onCreateIndexBufferComplete; // -> Update index buffer
OnIndexBufferUpdateComplete onIndexBufferUpdateComplete; // -> Mark index buffer ready!
// These methods below handle resource creation and updates. They are intended to support for
// both standard synchronous flows and asynchronous operations. Note that they must be invoked
// from the main thread as they call "Filament APIs" in it. In this sample, you see some methods
// are called directly inside the asynchronous completion callbacks, which is safe because the
// callbacks are guaranteed to run on the main thread.
void createMaterial() {
mat = Material::Builder()
.package(RESOURCES_BAKEDTEXTURE_DATA, RESOURCES_BAKEDTEXTURE_SIZE)
.build(*engine);
}
void startLoadingOneRenderable() {
if (loadingObjectIndex >= OBJECT_COUNT) {
return;
}
// `loadingObjectIndex` doesn't have to be an atomic variable because this method is always
// called from the main thread.
int index = loadingObjectIndex++;
auto* data = &objectData[index];
// Create required resources for a renderable in parallel
createTexture(data, onCreateTextureComplete);
createVertexBuffer(data, onCreateVertexBufferComplete);
createIndexBuffer(data, onCreateIndexBufferComplete);
}
void loadImage(OnLoadImageComplete callback = nullptr) {
if (shuttingDown) {
return;
}
utils::Invocable<void()> command = [this](){
Path const path =
FilamentApp::getRootAssetsPath() + "textures/Moss_01/Moss_01_Color.png";
if (!path.exists()) {
std::cerr << "The texture " << path << " does not exist" << std::endl;
exit(1);
}
imageData.reset(stbi_load(path.c_str(), &imageWidth, &imageHeight,
&imageChannels, 4));
if (!imageData) {
std::cerr << "The texture " << path << " could not be loaded" << std::endl;
exit(1);
}
};
if (callback) {
engine->runCommandAsync(std::move(command), nullptr, std::move(callback));
} else {
command();
}
}
void createTexture(void* user, OnCreateTextureComplete callback = nullptr) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
auto builder = Texture::Builder()
.width(static_cast<uint32_t>(imageWidth))
.height(static_cast<uint32_t>(imageHeight))
.levels(1)
// (For testing purposes) This will add a chained asynchronous operation during the
// texture creation.
.swizzle(Texture::Swizzle::SUBSTITUTE_ZERO, Texture::Swizzle::CHANNEL_1,
Texture::Swizzle::SUBSTITUTE_ZERO, Texture::Swizzle::SUBSTITUTE_ZERO)
.sampler(Texture::Sampler::SAMPLER_2D)
.format(Texture::InternalFormat::RGBA8);
if (callback) {
builder.async(nullptr, std::move(callback), user);
}
data->tex = builder.build(*engine);
}
void updateTexture(void* user, OnTextureUpdateComplete callback = nullptr) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
Texture::PixelBufferDescriptor buffer(imageData.get(),
static_cast<size_t>(imageWidth * imageHeight * 4),
Texture::Format::RGBA, Texture::Type::UBYTE
// Don't destroy the loaded image since it needs to be reused.
/*, (Texture::PixelBufferDescriptor::Callback)&stbi_image_free*/);
if (callback) {
data->tex->setImageAsync(*engine, 0, std::move(buffer),
nullptr, std::move(callback), user);
} else {
data->tex->setImage(*engine, 0, std::move(buffer));
}
}
void createMaterialInstance(void* user) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
data->matInstance = mat->createInstance();
TextureSampler sampler(MinFilter::LINEAR, MagFilter::LINEAR);
data->matInstance->setParameter("albedo", data->tex, sampler);
}
void textureReady(void* user) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
data->texReady = true;
// try creating renderable
mayCreateRenderable(user);
}
void createVertexBuffer(void* user, OnCreateVertexBufferComplete callback = nullptr) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
static_assert(sizeof(Vertex) == 16, "Strange vertex size.");
auto builder = VertexBuffer::Builder()
.vertexCount(4)
.bufferCount(1)
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 16)
.attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2, 8, 16);
if (callback) {
builder.async(nullptr, std::move(callback), user);
}
data->vb = builder.build(*engine);
}
void updateVertexBuffer(void* user, OnVertexBufferUpdateComplete callback = nullptr) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
if (callback) {
data->vb->setBufferAtAsync(*engine, 0,
VertexBuffer::BufferDescriptor(QUAD_VERTICES, 64, nullptr), 0,
nullptr, std::move(callback), user);
} else {
data->vb->setBufferAt(*engine, 0,
VertexBuffer::BufferDescriptor(QUAD_VERTICES, 64, nullptr));
}
}
void vertexBufferReady(void* user) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
data->vbReady = true;
// try creating renderable
mayCreateRenderable(user);
}
void createIndexBuffer(void* user, OnCreateIndexBufferComplete callback = nullptr) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
auto builder = IndexBuffer::Builder()
.indexCount(6)
.bufferType(IndexBuffer::IndexType::USHORT);
if (callback) {
builder.async(nullptr, std::move(callback), user);
}
data->ib = builder.build(*engine);
}
void updateIndexBuffer(void* user, OnIndexBufferUpdateComplete callback = nullptr) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
if (callback) {
data->ib->setBufferAsync(*engine,
IndexBuffer::BufferDescriptor(QUAD_INDICES, 12, nullptr), 0,
nullptr, std::move(callback), user);
} else {
data->ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(QUAD_INDICES, 12, nullptr));
}
}
void indexBufferReady(void* user) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
data->ibReady = true;
// try creating renderable
mayCreateRenderable(user);
}
void mayCreateRenderable(void* user) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
if (data->isReadyToCreateRenderable()) {
createRenderable(user);
// Done with loading a renderable, load the next one.
startLoadingOneRenderable();
}
}
void createRenderable(void* user) {
if (shuttingDown) {
return;
}
auto* data = static_cast<ObjectData*>(user);
data->renderable = EntityManager::get().create();
RenderableManager::Builder(1)
.boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
.material(0, data->matInstance)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, data->vb, data->ib, 0, 6)
.culling(false)
.receiveShadows(false)
.castShadows(false)
.build(*engine, data->renderable);
scene->addEntity(data->renderable);
}
};
int main(int argc, char** argv) {
Config config;
config.title = "helloasync";
config.asynchronousMode = backend::AsynchronousMode::THREAD_PREFERRED;
handleCommandLineArguments(argc, argv, config);
App app;
auto setup = [&app](Engine* engine, View* view, Scene* scene) {
app.engine = engine;
app.scene = scene;
// Set up view (Skybox & Camera)
app.skybox = Skybox::Builder().color({0.1, 0.125, 0.25, 1.0}).build(*engine);
scene->setSkybox(app.skybox);
app.camera = EntityManager::get().create();
app.cam = engine->createCamera(app.camera);
const float zoom = 12.0;
const float aspect =
static_cast<float>(view->getViewport().width) / view->getViewport().height;
app.cam->setProjection(Camera::Projection::ORTHO, -zoom, zoom,
-zoom, zoom, -1, 1);
view->setCamera(app.cam);
view->setPostProcessingEnabled(false);
// Pre-calculate the layout transform for each object in a centered 2D grid arrangement.
const float rowStart = (App::ROW_COUNT - 1) * 0.5f;
for (int i = 0; i < App::OBJECT_COUNT; ++i) {
int row = i / App::OBJECT_COUNT_PER_ROW;
int col = i % App::OBJECT_COUNT_PER_ROW;
// Calculate number of items in this row to center it horizontally.
// Usually equal to OBJECT_COUNT_PER_ROW, except for the last partial row.
int colCountForThisRow = std::min(
App::OBJECT_COUNT - (row * App::OBJECT_COUNT_PER_ROW),
App::OBJECT_COUNT_PER_ROW);
float colStart = (colCountForThisRow - 1) * 0.5f;
auto s = math::mat4f::scaling(math::float3(0.4f, 0.4f, 0.4f));
auto t = math::mat4f::translation(
math::float3(-colStart + (col * 1.0f), rowStart - (row * 1.0f), 0.0f));
app.objectData[i].baseTransform = t * s;
}
if (engine->isAsynchronousModeEnabled()) {
// Build a pipeline for asynchronous operations.
app.onLoadImageComplete = [&app](void* user) {
// Load this once as it's universal across all objects
app.createMaterial();
// Initiate loading multiple renderables at the same time.
app.startLoadingOneRenderable();
app.startLoadingOneRenderable();
app.startLoadingOneRenderable();
app.startLoadingOneRenderable();
app.startLoadingOneRenderable();
};
app.onCreateTextureComplete = [&app](Texture* tex, void* user) {
app.updateTexture(user, app.onTextureUpdateComplete);
};
app.onTextureUpdateComplete = [&app](Texture* tex, void* user) {
app.createMaterialInstance(user);
app.textureReady(user);
};
app.onCreateVertexBufferComplete = [&app](VertexBuffer* vb, void* user) {
app.updateVertexBuffer(user, app.onVertexBufferUpdateComplete);
};
app.onVertexBufferUpdateComplete = [&app](VertexBuffer* vb, void* user) {
app.vertexBufferReady(user);
};
app.onCreateIndexBufferComplete = [&app](IndexBuffer* ib, void* user) {
app.updateIndexBuffer(user, app.onIndexBufferUpdateComplete);
};
app.onIndexBufferUpdateComplete = [&app](IndexBuffer* ib, void* user) {
app.indexBufferReady(user);
};
// Start the chain of asynchronous operations.
app.loadImage(app.onLoadImageComplete);
} else {
// Load an image and a material once as they are shared across all objects
app.loadImage();
app.createMaterial();
// Load renderables synchronously
for (int i = 0; i < App::OBJECT_COUNT; ++i) {
void* data = &app.objectData[i];
app.createTexture(data);
app.updateTexture(data);
app.createMaterialInstance(data);
app.createVertexBuffer(data);
app.updateVertexBuffer(data);
app.createIndexBuffer(data);
app.updateIndexBuffer(data);
app.createRenderable(data);
}
}
};
auto cleanup = [&app](Engine* engine, View*, Scene*) {
// We set this flag to guard against accessing resources (textures/buffers) inside
// completion callbacks after cleanup.
app.shuttingDown = true;
for (int i = 0; i < App::OBJECT_COUNT; ++i) {
auto& data = app.objectData[i];
if (data.renderable) {
engine->destroy(data.renderable);
}
if (data.matInstance) {
engine->destroy(data.matInstance);
}
if (data.ib) {
engine->destroy(data.ib);
}
if (data.vb) {
engine->destroy(data.vb);
}
if (data.tex) {
engine->destroy(data.tex);
}
}
if (app.mat) {
engine->destroy(app.mat);
}
if (app.skybox) {
engine->destroy(app.skybox);
}
if (app.camera) {
engine->destroyCameraComponent(app.camera);
EntityManager::get().destroy(app.camera);
}
};
FilamentApp::get().animate([&app](Engine* engine, View* view, double now) {
auto& tm = engine->getTransformManager();
for (int i = 0; i < App::OBJECT_COUNT; ++i) {
auto& data = app.objectData[i];
if (!data.renderable) {
continue; // Skip updating transform for renderables that are not loaded yet.
}
auto r = math::mat4f::rotation(now, math::float3(0, 0, 1));
tm.setTransform(tm.getInstance(data.renderable), data.baseTransform * r);
}
});
FilamentApp::get().run(config, setup, cleanup);
return 0;
}
-2
View File
@@ -169,8 +169,6 @@ int main(int argc, char** argv) {
.build(*engine);
app.mt1 = MorphTargetBuffer::Builder()
.withPositions(true)
.withTangents(true)
.vertexCount(9 * 2)
.count(3)
.build(*engine);
-330
View File
@@ -1,330 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "common/arguments.h"
#include <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/MorphTargetBuffer.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/Skybox.h>
#include <filament/Texture.h>
#include <filament/TextureSampler.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <filament/View.h>
#include <filament/Viewport.h>
#include <utils/EntityManager.h>
#include <utils/Path.h>
#include <getopt/getopt.h>
#include <filamentapp/Config.h>
#include <filamentapp/FilamentApp.h>
#include <vector>
#include <iostream>
#include "generated/resources/resources.h"
using namespace filament;
using namespace filament::math;
using utils::Entity;
using utils::EntityManager;
using utils::Path;
struct Vertex {
float3 position;
float2 uv;
};
struct App {
Config config;
VertexBuffer* vb = nullptr;
IndexBuffer* ib = nullptr;
Material* mat = nullptr;
MaterialInstance* mi = nullptr;
Texture* baseColorMap = nullptr;
Texture* uvMorphTexture = nullptr;
Camera* cam = nullptr;
Entity camera;
Skybox* skybox = nullptr;
Entity renderable;
MorphTargetBuffer* mtb = nullptr;
std::vector<Vertex> vertices;
std::vector<uint16_t> indices;
std::vector<float2> uvMorphData;
std::vector<float4> posMorphData;
bool posMorphing = false;
};
static constexpr int GRID_SIZE = 20;
static constexpr float GRID_SCALE = 2.0f;
static void printUsage(char* name) {
std::string exec_name(utils::Path(name).getName());
std::string usage("HELLOUVMORPHING renders a quad with uv morphing\n"
"Usage:\n"
" HELLOUVMORPHING [options]\n"
"Options:\n"
" --help, -h\n"
" Prints this message\n"
" --positions, -p\n"
" Enable position morphing\n\n"
"API_USAGE");
const std::string from("HELLOUVMORPHING");
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
usage.replace(pos, from.length(), exec_name);
}
const std::string apiUsage("API_USAGE");
for (size_t pos = usage.find(apiUsage); pos != std::string::npos; pos = usage.find(apiUsage, pos)) {
usage.replace(pos, apiUsage.length(), samples::getBackendAPIArgumentsUsage());
}
std::cout << usage;
}
static int handleCommandLineArguments(int argc, char* argv[], App* app) {
static constexpr const char* OPTSTR = "ha:p";
static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' },
{ "api", required_argument, nullptr, 'a' }, { "positions", no_argument, nullptr, 'p' },
{ nullptr, 0, nullptr, 0 } };
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) {
std::string arg(optarg ? optarg : "");
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
exit(0);
case 'a':
app->config.backend = samples::parseArgumentsForBackend(arg);
break;
case 'p':
app->posMorphing = true;
break;
}
}
return optind;
}
int main(int argc, char** argv) {
App app{};
app.config.title = "hellouvmorphing";
handleCommandLineArguments(argc, argv, &app);
auto setup = [&app](Engine* engine, View* view, Scene* scene) {
app.skybox = Skybox::Builder().color({0.1, 0.125, 0.25, 1.0}).build(*engine);
scene->setSkybox(app.skybox);
view->setPostProcessingEnabled(false);
app.vertices.reserve(GRID_SIZE * GRID_SIZE);
app.indices.reserve((GRID_SIZE - 1) * (GRID_SIZE - 1) * 6);
// Grid vertices
for (int y = 0; y < GRID_SIZE; ++y) {
for (int x = 0; x < GRID_SIZE; ++x) {
float u = (float) x / (GRID_SIZE - 1);
float v = (float) y / (GRID_SIZE - 1);
float px = (u * 2.0f - 1.0f) * GRID_SCALE;
float yScale = app.posMorphing ? 1.0f : GRID_SCALE;
float py = (v * 2.0f - 1.0f) * yScale;
app.vertices.push_back({ { px, py, 0.0f }, { u, v } });
}
}
// Grid triangles
for (int y = 0; y < GRID_SIZE - 1; ++y) {
for (int x = 0; x < GRID_SIZE - 1; ++x) {
uint16_t tl = y * GRID_SIZE + x;
uint16_t tr = tl + 1;
uint16_t bl = (y + 1) * GRID_SIZE + x;
uint16_t br = bl + 1;
// CCW Triangles
app.indices.push_back(tl);
app.indices.push_back(tr);
app.indices.push_back(bl);
app.indices.push_back(tr);
app.indices.push_back(br);
app.indices.push_back(bl);
}
}
app.vb = VertexBuffer::Builder()
.vertexCount(app.vertices.size())
.bufferCount(1)
.attribute(VertexAttribute::POSITION, 0,
VertexBuffer::AttributeType::FLOAT3, 0, sizeof(Vertex))
.attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2,
sizeof(float3), sizeof(Vertex))
.build(*engine);
app.vb->setBufferAt(*engine, 0,
VertexBuffer::BufferDescriptor(
app.vertices.data(), app.vertices.size() * sizeof(Vertex),
[](void* buffer, size_t size, void* user) {}, nullptr));
app.ib = IndexBuffer::Builder()
.indexCount(app.indices.size())
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*engine);
app.ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(
app.indices.data(), app.indices.size() * sizeof(uint16_t),
[](void* buffer, size_t size, void* user) {}, nullptr));
// Create a 4x1 "rainbow" texture with Mipmaps
static const uint32_t rainbow[] = {0xff0000ff, 0xff00ff00, 0xffff0000, 0xff00ffff};
app.baseColorMap = Texture::Builder()
.width(4)
.height(1)
.levels(0xff) // Auto levels
.format(Texture::InternalFormat::RGBA8)
.sampler(Texture::Sampler::SAMPLER_2D)
.build(*engine);
Texture::PixelBufferDescriptor pbd(rainbow, sizeof(rainbow),
Texture::Format::RGBA, Texture::Type::UBYTE);
app.baseColorMap->setImage(*engine, 0, std::move(pbd));
// Generate swirl deltas
const float2 center = { 0.5f, 0.5f };
app.uvMorphData.reserve(app.vertices.size());
for (const auto& v: app.vertices) {
float2 uv = v.uv;
float2 toCenter = uv - center;
float dist = length(toCenter);
float angle = (1.0f - dist) * 3.14159f * 4.0f;
float s = sin(angle);
float c = cos(angle);
float2 rotated;
rotated.x = toCenter.x * c - toCenter.y * s;
rotated.y = toCenter.x * s + toCenter.y * c;
float2 newUV = rotated + center;
app.uvMorphData.push_back(newUV - uv);
}
size_t width = app.vertices.size();
size_t height = 1;
app.uvMorphTexture = Texture::Builder()
.width(width)
.height(height)
.depth(1)
.levels(1)
.format(Texture::InternalFormat::RG32F)
.sampler(Texture::Sampler::SAMPLER_2D_ARRAY)
.build(*engine);
Texture::PixelBufferDescriptor uvDeltaDesc(
app.uvMorphData.data(), app.uvMorphData.size() * sizeof(float2),
Texture::Format::RG, Texture::Type::FLOAT,
[](void* buffer, size_t size, void* user) {}, nullptr);
app.uvMorphTexture->setImage(*engine, 0, 0, 0, 0, width, height, 1, std::move(uvDeltaDesc));
app.mat = Material::Builder()
.package(RESOURCES_UVMORPH_DATA, RESOURCES_UVMORPH_SIZE)
.build(*engine);
app.mi = app.mat->createInstance();
TextureSampler colorSampler(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR,
TextureSampler::MagFilter::LINEAR, TextureSampler::WrapMode::REPEAT);
app.mi->setParameter("baseColor", app.baseColorMap, colorSampler);
TextureSampler dataSampler(TextureSampler::MinFilter::NEAREST, TextureSampler::MagFilter::NEAREST);
app.mi->setParameter("uv0_morph", app.uvMorphTexture, dataSampler);
app.mtb = MorphTargetBuffer::Builder()
.vertexCount(app.vertices.size())
.count(1)
.withPositions(app.posMorphing)
.enableCustomMorphing(true)
.build(*engine);
if (app.posMorphing) {
// Generate position morph deltas
app.posMorphData.reserve(app.vertices.size());
for (const auto& v: app.vertices) {
// Morph to square (GRID_SCALE)
float targetPy = (v.uv.y * 2.0f - 1.0f) * GRID_SCALE;
float currentPy = v.position.y;
float deltaY = targetPy - currentPy;
// W must be 0 for deltas
app.posMorphData.push_back({ 0, deltaY, 0, 0.0f });
}
app.mtb->setPositionsAt(*engine, 0, app.posMorphData.data(), app.posMorphData.size());
}
app.renderable = EntityManager::get().create();
RenderableManager::Builder(1)
.boundingBox({ { -GRID_SCALE, -GRID_SCALE, -1 }, { GRID_SCALE, GRID_SCALE, 1 } })
.material(0, app.mi)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib)
.culling(false)
.morphing(app.mtb)
.build(*engine, app.renderable);
scene->addEntity(app.renderable);
app.camera = utils::EntityManager::get().create();
app.cam = engine->createCamera(app.camera);
view->setCamera(app.cam);
};
auto cleanup = [&app](Engine* engine, View*, Scene*) {
engine->destroy(app.skybox);
engine->destroy(app.renderable);
engine->destroy(app.mi);
engine->destroy(app.mat);
engine->destroy(app.baseColorMap);
engine->destroy(app.uvMorphTexture);
engine->destroy(app.vb);
engine->destroy(app.ib);
engine->destroy(app.mtb);
engine->destroyCameraComponent(app.camera);
EntityManager::get().destroy(app.camera);
};
FilamentApp::get().animate([&app](Engine* engine, View* view, double now) {
constexpr float ZOOM = 2.5f;
const uint32_t w = view->getViewport().width;
const uint32_t h = view->getViewport().height;
const float aspect = (float)w / h;
app.cam->setProjection(Camera::Projection::ORTHO, -aspect * ZOOM, aspect * ZOOM, -ZOOM, ZOOM, 0, 1);
auto& rm = engine->getRenderableManager();
float weight = (sin(now * 2.0) + 1.0f) * 0.5f;
rm.setMorphWeights(rm.getInstance(app.renderable), &weight, 1);
});
FilamentApp::get().run(app.config, setup, cleanup);
return 0;
}
-38
View File
@@ -1,38 +0,0 @@
material {
name : UvMorph,
requires : [uv0],
shadingModel : unlit,
culling: none,
apiLevel: 2,
parameters : [
{
type : sampler2d,
name : baseColor
},
{
type : sampler2dArray,
name : uv0_morph,
shader: vertex
}
]
}
vertex {
void materialVertex(inout MaterialVertexInputs material) {
#if defined(VARIANT_HAS_SKINNING_OR_MORPHING)
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_CUSTOM_BIT) != 0) {
// Call morphData2 with the sampler that we declared and bound manually.
morphData2(material.uv0, materialParams_uv0_morph);
}
#endif
}
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = texture(materialParams_baseColor, getUV0());
}
}
+11 -55
View File
@@ -154,50 +154,6 @@ void skinNormalTangent(inout vec3 n, inout vec3 t, const uvec4 ids, const vec4 w
#define MAX_MORPH_TARGET_BUFFER_WIDTH 2048
#if CLIENT_MATERIAL_API_LEVEL >= UNSTABLE_MATERIAL_API_LEVEL
void morphData2(inout vec2 v, highp sampler2DArray data) {
int index = getVertexIndex() + pushConstants.morphingBufferOffset;
ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0);
int c = object_uniforms_morphTargetCount;
for (int i = 0; i < c; ++i) {
float w = morphingUniforms.weights[i][0];
if (w != 0.0) {
texcoord.z = i;
v += w * texelFetch(data, texcoord, 0).xy;
}
}
}
void morphData3(inout vec3 v, highp sampler2DArray data) {
int index = getVertexIndex() + pushConstants.morphingBufferOffset;
ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0);
int c = object_uniforms_morphTargetCount;
for (int i = 0; i < c; ++i) {
float w = morphingUniforms.weights[i][0];
if (w != 0.0) {
texcoord.z = i;
v += w * texelFetch(data, texcoord, 0).xyz;
}
}
}
void morphData4(inout vec4 v, highp sampler2DArray data) {
int index = getVertexIndex() + pushConstants.morphingBufferOffset;
ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0);
int c = object_uniforms_morphTargetCount;
for (int i = 0; i < c; ++i) {
float w = morphingUniforms.weights[i][0];
if (w != 0.0) {
texcoord.z = i;
v += w * texelFetch(data, texcoord, 0);
}
}
}
void morphPosition(inout vec4 p) {
morphData4(p, sampler1_positions);
}
#else
void morphPosition(inout vec4 p) {
int index = getVertexIndex() + pushConstants.morphingBufferOffset;
ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0);
@@ -210,7 +166,6 @@ void morphPosition(inout vec4 p) {
}
}
}
#endif
void morphNormal(inout vec3 n) {
vec3 baseNormal = n;
@@ -235,16 +190,17 @@ vec4 getPosition() {
vec4 pos = mesh_position;
#if defined(VARIANT_HAS_SKINNING_OR_MORPHING)
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_POSITION_BIT) != 0) {
#if defined(LEGACY_MORPHING)
pos += morphingUniforms.weights[0] * mesh_custom0;
pos += morphingUniforms.weights[1] * mesh_custom1;
pos += morphingUniforms.weights[2] * mesh_custom2;
pos += morphingUniforms.weights[3] * mesh_custom3;
#else
morphPosition(pos);
#endif
}
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0) {
#if defined(LEGACY_MORPHING)
pos += morphingUniforms.weights[0] * mesh_custom0;
pos += morphingUniforms.weights[1] * mesh_custom1;
pos += morphingUniforms.weights[2] * mesh_custom2;
pos += morphingUniforms.weights[3] * mesh_custom3;
#else
morphPosition(pos);
#endif
}
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_SKINNING_ENABLED_BIT) != 0) {
skinPosition(pos.xyz, mesh_bone_indices, mesh_bone_weights);
+2 -2
View File
@@ -61,7 +61,7 @@ void main() {
toTangentFrame(mesh_tangents, material.worldNormal, vertex_worldTangent.xyz);
#if defined(VARIANT_HAS_SKINNING_OR_MORPHING)
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_TANGENT_BIT) != 0) {
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0) {
#if defined(LEGACY_MORPHING)
vec3 normal0, normal1, normal2, normal3;
toTangentFrame(mesh_custom4, normal0);
@@ -96,7 +96,7 @@ void main() {
toTangentFrame(mesh_tangents, material.worldNormal);
#if defined(VARIANT_HAS_SKINNING_OR_MORPHING)
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_TANGENT_BIT) != 0) {
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0) {
#if defined(LEGACY_MORPHING)
vec3 normal0, normal1, normal2, normal3;
toTangentFrame(mesh_custom4, normal0);
+3 -5
View File
@@ -36,8 +36,6 @@ struct PerRenderableData {
// Bits for flagsChannels
#define FILAMENT_OBJECT_SKINNING_ENABLED_BIT 0x100
#define FILAMENT_OBJECT_MORPHING_POSITION_BIT 0x200
#define FILAMENT_OBJECT_MORPHING_TANGENT_BIT 0x400
#define FILAMENT_OBJECT_MORPHING_CUSTOM_BIT 0x800
#define FILAMENT_OBJECT_CONTACT_SHADOWS_BIT 0x1000
#define FILAMENT_OBJECT_INSTANCE_BUFFER_BIT 0x2000
#define FILAMENT_OBJECT_MORPHING_ENABLED_BIT 0x200
#define FILAMENT_OBJECT_CONTACT_SHADOWS_BIT 0x400
#define FILAMENT_OBJECT_INSTANCE_BUFFER_BIT 0x800
+5 -1
View File
@@ -103,4 +103,8 @@ IF (NOT DEFINED IOS_MIN_TARGET)
OUTPUT_STRIP_TRAILING_WHITESPACE)
ENDIF()
set(CMAKE_OSX_DEPLOYMENT_TARGET ${IOS_MIN_TARGET} CACHE STRING "Minimum iOS version")
SET(IOS_COMMON_FLAGS "-m${PLATFORM_FLAG_NAME}-version-min=${IOS_MIN_TARGET}")
SET(CMAKE_C_FLAGS_INIT "${IOS_COMMON_FLAGS}")
SET(CMAKE_CXX_FLAGS_INIT "${IOS_COMMON_FLAGS}")
SET(CMAKE_ASM_FLAGS_INIT "${IOS_COMMON_FLAGS}")
-5
View File
@@ -1,5 +0,0 @@
# This file is included by filamat and glslang because glslang (and thus filamat) require
# iOS 13.0+ whereas the rest of Filament targets iOS 11.0.
if(IOS)
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0")
endif()
-2
View File
@@ -1,8 +1,6 @@
cmake_minimum_required(VERSION 3.19)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
include(${EXTERNAL}/clang/usage_ios_13.cmake)
# Adhere to GNU filesystem layout conventions
include(GNUInstallDirs)
+1 -1
View File
@@ -1,4 +1,4 @@
# filamesh
# Filamesh
`filamesh` converts any mesh file supported by `assimp` (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
+1 -1
View File
@@ -1,4 +1,4 @@
# matinfo
# Matinfo
`matinfo` lists the content of a compiled material as output by `matc`. This tool is meant to be
used for debug purpose only.
-87
View File
@@ -1,87 +0,0 @@
# SPECGEN: Spectral Integration Matrix Generator for Real-Time Dispersion
## Theoretical Background
### 1. Spectral Rendering in RGB
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.
The fundamental equation for perceived color \\(C\\) is:
$$ C = \int L(\lambda) \cdot r(\lambda) d\lambda $$
where:
* \\(L(\lambda)\\) is the spectral radiance reaching the eye.
* \\(r(\lambda)\\) is the sensor response function (e.g., CIE 1931 \\(\bar{x}, \bar{y}, \bar{z}\\) matching functions).
### 2. Basis Transformation Strategy
We assume the input light is defined in linear sRGB. To process it spectrally:
1. Convert sRGB to CIE XYZ (D65 white point).
2. 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).
3. Apply spectral effects (like wavelength-dependent refraction).
4. Integrate back to XYZ using the CIE Color Matching Functions (CMFs).
5. Convert final XYZ back to sRGB.
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:
$$ C_{final} = \sum_{n=0}^{N-1} (K_n \cdot C_{input}) $$
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.
**Derivation of \\(K_n\\):**
$$ K_n = M_{XYZ \to sRGB} \cdot \text{Diag}(W_n) \cdot M_{sRGB \to XYZ} $$
where \\(W_n\\) is the "spectral weight" vector \\((x, y, z)\\) for wavelength \\(\lambda_n\\):
$$ W_n = \text{CMF}(\lambda_n) \cdot \text{weight}_n $$
### 3. Normalization (Energy Conservation)
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.
We require: \\(\sum K_n = I\\)
This implies:
$$ \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 $$
*(since \\(M \cdot M^{-1} = I\\))*
Therefore, we normalize \\(W_n\\) such that:
$$ \sum W_{n,x} = 1.0, \quad \sum W_{n,y} = 1.0, \quad \sum W_{n,z} = 1.0 $$
**Note:** We do **NOT** 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.
In practice, we calculate the raw sum of \\(W_n\\) from the quadrature weights and CMFs, then compute a correction factor:
$$ \text{Correction} = \frac{1.0}{\sum W_{n,raw}} $$
$$ W_{n,final} = W_{n,raw} \cdot \text{Correction} $$
### 4. Dispersion and IOR Offsets
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.
**Cauchy Dispersion Model:**
$$ n(\lambda) = A + \frac{B}{\lambda^2} $$
Using the definition of Abbe number \\(V_d = \frac{n_D - 1}{n_F - n_C}\\), we can derive:
$$ n(\lambda) = n_D + \frac{n_D - 1}{V_d} \cdot \text{Offset}(\lambda) $$
Where \\(\text{Offset}(\lambda)\\) is pre-calculated by this tool:
$$ \text{Offset}(\lambda) = \frac{ \frac{1}{\lambda^2} - \frac{1}{\lambda_D^2} }{ \frac{1}{\lambda_F^2} - \frac{1}{\lambda_C^2} } $$
This allows the shader to compute the specific IOR for each sample efficiently:
```glsl
float ior_n = baseIOR + dispersionFactor * offsets[n];
```
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.69.0",
"version": "1.68.4",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",