Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ad90d139d | ||
|
|
cf694a3334 | ||
|
|
863379b5b4 |
@@ -163,7 +163,9 @@ jobs:
|
|||||||
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
|
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
|
||||||
mv out/gltfio-android-release.aar out/gltfio-${TAG}-android.aar
|
mv out/gltfio-android-release.aar out/gltfio-${TAG}-android.aar
|
||||||
mv out/filament-utils-android-release.aar out/filament-utils-${TAG}-android.aar
|
mv out/filament-utils-android-release.aar out/filament-utils-${TAG}-android.aar
|
||||||
mv out/filament-android-release-linux.tgz out/filament-${TAG}-android-native.tgz
|
cd out/android-release/filament
|
||||||
|
tar -czf ../../filament-${TAG}-android-native.tgz .
|
||||||
|
cd ../../..
|
||||||
- name: Sign sample-gltf-viewer
|
- name: Sign sample-gltf-viewer
|
||||||
run: |
|
run: |
|
||||||
echo "${APK_KEYSTORE_BASE64}" > filament.jks.base64
|
echo "${APK_KEYSTORE_BASE64}" > filament.jks.base64
|
||||||
@@ -243,8 +245,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||||
run: |
|
run: |
|
||||||
@REMARK 'call' is required to ensure control returns to this script after the batch file finishes.
|
build\windows\build-github.bat release
|
||||||
call build\windows\build-github.bat release
|
|
||||||
echo on
|
echo on
|
||||||
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
|
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ option(FILAMENT_ENABLE_COVERAGE "Enable LLVM code coverage" OFF)
|
|||||||
|
|
||||||
option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
|
option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
|
||||||
|
|
||||||
|
option(FILAMENT_ENABLE_MULTIVIEW "Enable multiview for Filament" OFF)
|
||||||
|
|
||||||
option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filament" OFF)
|
option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filament" OFF)
|
||||||
|
|
||||||
option(FILAMENT_ENABLE_FGVIEWER "Enable the frame graph viewer" OFF)
|
option(FILAMENT_ENABLE_FGVIEWER "Enable the frame graph viewer" OFF)
|
||||||
@@ -605,6 +607,23 @@ else()
|
|||||||
option(FILAMENT_DISABLE_MATOPT "Disable material optimizations" ON)
|
option(FILAMENT_DISABLE_MATOPT "Disable material optimizations" ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# This only affects the prebuilt shader files in gltfio and samples, not filament library.
|
||||||
|
# The value can be either "instanced", "multiview", or "none"
|
||||||
|
set(FILAMENT_SAMPLES_STEREO_TYPE "none" CACHE STRING
|
||||||
|
"Stereoscopic type that shader files in gltfio and samples are built for."
|
||||||
|
)
|
||||||
|
string(TOLOWER "${FILAMENT_SAMPLES_STEREO_TYPE}" FILAMENT_SAMPLES_STEREO_TYPE)
|
||||||
|
if (NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced"
|
||||||
|
AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview"
|
||||||
|
AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "none")
|
||||||
|
message(FATAL_ERROR "Invalid stereo type: \"${FILAMENT_SAMPLES_STEREO_TYPE}\" choose either \"instanced\", \"multiview\", or \"none\" ")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Compiling samples for multiview implies enabling multiview feature as well.
|
||||||
|
if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview")
|
||||||
|
set(FILAMENT_ENABLE_MULTIVIEW ON)
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Define backend flag for debug only
|
# Define backend flag for debug only
|
||||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT FILAMENT_BACKEND_DEBUG_FLAG STREQUAL "")
|
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT FILAMENT_BACKEND_DEBUG_FLAG STREQUAL "")
|
||||||
add_definitions(-DFILAMENT_BACKEND_DEBUG_FLAG=${FILAMENT_BACKEND_DEBUG_FLAG})
|
add_definitions(-DFILAMENT_BACKEND_DEBUG_FLAG=${FILAMENT_BACKEND_DEBUG_FLAG})
|
||||||
@@ -858,7 +877,6 @@ add_subdirectory(${LIBRARIES}/gltfio)
|
|||||||
add_subdirectory(${LIBRARIES}/ibl)
|
add_subdirectory(${LIBRARIES}/ibl)
|
||||||
add_subdirectory(${LIBRARIES}/iblprefilter)
|
add_subdirectory(${LIBRARIES}/iblprefilter)
|
||||||
add_subdirectory(${LIBRARIES}/image)
|
add_subdirectory(${LIBRARIES}/image)
|
||||||
add_subdirectory(${LIBRARIES}/imagediff)
|
|
||||||
add_subdirectory(${LIBRARIES}/ktxreader)
|
add_subdirectory(${LIBRARIES}/ktxreader)
|
||||||
add_subdirectory(${LIBRARIES}/math)
|
add_subdirectory(${LIBRARIES}/math)
|
||||||
add_subdirectory(${LIBRARIES}/mathio)
|
add_subdirectory(${LIBRARIES}/mathio)
|
||||||
@@ -885,8 +903,6 @@ add_subdirectory(${EXTERNAL}/getopt)
|
|||||||
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
||||||
|
|
||||||
# imageio-lite is needed for viewer
|
|
||||||
add_subdirectory(${LIBRARIES}/imageio-lite)
|
|
||||||
|
|
||||||
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
||||||
add_subdirectory(${LIBRARIES}/geometry)
|
add_subdirectory(${LIBRARIES}/geometry)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.69.2'
|
implementation 'com.google.android.filament:filament-android:1.68.5'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -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:
|
iOS projects can use CocoaPods to install the latest release:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pod 'Filament', '~> 1.69.2'
|
pod 'Filament', '~> 1.68.5'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -7,14 +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
|
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||||
|
|
||||||
## v1.69.2
|
|
||||||
|
|
||||||
- engine: fix shader compilation failure in TAA material
|
|
||||||
- engine: fix stereo & parallel shader compilation
|
|
||||||
|
|
||||||
## v1.69.1
|
|
||||||
|
|
||||||
|
|
||||||
## v1.69.0
|
## v1.69.0
|
||||||
|
|
||||||
- engine: Support custom attributes morphing, and allow for omitting position and/or normal data. [⚠️ **Recompile Materials**]
|
- engine: Support custom attributes morphing, and allow for omitting position and/or normal data. [⚠️ **Recompile Materials**]
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include <filament/Camera.h>
|
#include <filament/Camera.h>
|
||||||
|
|
||||||
|
|
||||||
#include <utils/Entity.h>
|
#include <utils/Entity.h>
|
||||||
|
|
||||||
#include <math/mat4.h>
|
#include <math/mat4.h>
|
||||||
@@ -41,13 +40,6 @@ Java_com_google_android_filament_Camera_nSetProjectionFov(JNIEnv*, jclass ,
|
|||||||
camera->setProjection(fovInDegrees, aspect, near, far, (Camera::Fov) fov);
|
camera->setProjection(fovInDegrees, aspect, near, far, (Camera::Fov) fov);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jdouble JNICALL
|
|
||||||
Java_com_google_android_filament_Camera_nGetFieldOfViewInDegrees(JNIEnv*, jclass,
|
|
||||||
jlong nativeCamera, jint direction) {
|
|
||||||
Camera *camera = (Camera *) nativeCamera;
|
|
||||||
return camera->getFieldOfViewInDegrees((Camera::Fov) direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_Camera_nSetLensProjection(JNIEnv*, jclass,
|
Java_com_google_android_filament_Camera_nSetLensProjection(JNIEnv*, jclass,
|
||||||
jlong nativeCamera, jdouble focalLength, jdouble aspect, jdouble near, jdouble far) {
|
jlong nativeCamera, jdouble focalLength, jdouble aspect, jdouble near, jdouble far) {
|
||||||
@@ -70,21 +62,6 @@ Java_com_google_android_filament_Camera_nSetCustomProjection(JNIEnv *env, jclass
|
|||||||
env->ReleaseDoubleArrayElements(inProjectionForCulling_, inProjectionForCulling, JNI_ABORT);
|
env->ReleaseDoubleArrayElements(inProjectionForCulling_, inProjectionForCulling, JNI_ABORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_com_google_android_filament_Camera_nSetCustomEyeProjection(JNIEnv *env, jclass,
|
|
||||||
jlong nativeCamera, jdoubleArray inProjection_, jint count, jdoubleArray inProjectionForCulling_,
|
|
||||||
jdouble near, jdouble far) {
|
|
||||||
Camera *camera = (Camera *) nativeCamera;
|
|
||||||
jdouble *inProjection = env->GetDoubleArrayElements(inProjection_, NULL);
|
|
||||||
jdouble *inProjectionForCulling = env->GetDoubleArrayElements(inProjectionForCulling_, NULL);
|
|
||||||
camera->setCustomEyeProjection(
|
|
||||||
reinterpret_cast<const filament::math::mat4 *>(inProjection), (size_t) count,
|
|
||||||
*reinterpret_cast<const filament::math::mat4 *>(inProjectionForCulling),
|
|
||||||
near, far);
|
|
||||||
env->ReleaseDoubleArrayElements(inProjection_, inProjection, JNI_ABORT);
|
|
||||||
env->ReleaseDoubleArrayElements(inProjectionForCulling_, inProjectionForCulling, JNI_ABORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_Camera_nSetScaling(JNIEnv* env, jclass,
|
Java_com_google_android_filament_Camera_nSetScaling(JNIEnv* env, jclass,
|
||||||
jlong nativeCamera, jdouble x, jdouble y) {
|
jlong nativeCamera, jdouble x, jdouble y) {
|
||||||
@@ -99,17 +76,6 @@ Java_com_google_android_filament_Camera_nSetShift(JNIEnv* env, jclass,
|
|||||||
camera->setShift({(double)x, (double)y});
|
camera->setShift({(double)x, (double)y});
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_com_google_android_filament_Camera_nGetShift(JNIEnv* env, jclass,
|
|
||||||
jlong nativeCamera, jdoubleArray out_) {
|
|
||||||
Camera *camera = (Camera *) nativeCamera;
|
|
||||||
jdouble *out = env->GetDoubleArrayElements(out_, NULL);
|
|
||||||
filament::math::double2 s = camera->getShift();
|
|
||||||
out[0] = s.x;
|
|
||||||
out[1] = s.y;
|
|
||||||
env->ReleaseDoubleArrayElements(out_, out, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_Camera_nLookAt(JNIEnv*, jclass, jlong nativeCamera,
|
Java_com_google_android_filament_Camera_nLookAt(JNIEnv*, jclass, jlong nativeCamera,
|
||||||
jdouble eye_x, jdouble eye_y, jdouble eye_z, jdouble center_x, jdouble center_y,
|
jdouble eye_x, jdouble eye_y, jdouble eye_z, jdouble center_x, jdouble center_y,
|
||||||
@@ -149,15 +115,6 @@ Java_com_google_android_filament_Camera_nSetModelMatrixFp64(JNIEnv *env, jclass,
|
|||||||
env->ReleaseDoubleArrayElements(in_, in, JNI_ABORT);
|
env->ReleaseDoubleArrayElements(in_, in, JNI_ABORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_com_google_android_filament_Camera_nSetEyeModelMatrix(JNIEnv *env, jclass,
|
|
||||||
jlong nativeCamera, jint eyeId, jdoubleArray model_) {
|
|
||||||
Camera* camera = (Camera *) nativeCamera;
|
|
||||||
jdouble *model = env->GetDoubleArrayElements(model_, NULL);
|
|
||||||
camera->setEyeModelMatrix((uint8_t)eyeId, *reinterpret_cast<const filament::math::mat4*>(model));
|
|
||||||
env->ReleaseDoubleArrayElements(model_, model, JNI_ABORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_Camera_nGetProjectionMatrix(JNIEnv *env, jclass,
|
Java_com_google_android_filament_Camera_nGetProjectionMatrix(JNIEnv *env, jclass,
|
||||||
jlong nativeCamera, jdoubleArray out_) {
|
jlong nativeCamera, jdoubleArray out_) {
|
||||||
@@ -323,5 +280,3 @@ Java_com_google_android_filament_Camera_nComputeEffectiveFov(JNIEnv*, jclass,
|
|||||||
jdouble fovInDegrees, jdouble focusDistance) {
|
jdouble fovInDegrees, jdouble focusDistance) {
|
||||||
return Camera::computeEffectiveFov(fovInDegrees, focusDistance);
|
return Camera::computeEffectiveFov(fovInDegrees, focusDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -96,14 +96,6 @@ Java_com_google_android_filament_Material_nGetBlendingMode(JNIEnv*, jclass,
|
|||||||
return (jint) material->getBlendingMode();
|
return (jint) material->getBlendingMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_google_android_filament_Material_nGetTransparencyMode(JNIEnv*, jclass,
|
|
||||||
jlong nativeMaterial) {
|
|
||||||
Material* material = (Material*) nativeMaterial;
|
|
||||||
return (jint) material->getTransparencyMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
|
|||||||
@@ -564,19 +564,3 @@ Java_com_google_android_filament_MaterialInstance_nGetDepthFunc(JNIEnv* env, jcl
|
|||||||
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
|
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
|
||||||
return (jint)instance->getDepthFunc();
|
return (jint)instance->getDepthFunc();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_com_google_android_filament_MaterialInstance_nSetTransparencyMode(JNIEnv*, jclass,
|
|
||||||
jlong nativeMaterialInstance, jint mode) {
|
|
||||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
|
||||||
instance->setTransparencyMode((MaterialInstance::TransparencyMode) mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jint JNICALL
|
|
||||||
Java_com_google_android_filament_MaterialInstance_nGetTransparencyMode(JNIEnv*, jclass,
|
|
||||||
jlong nativeMaterialInstance) {
|
|
||||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
|
||||||
return (jint) instance->getTransparencyMode();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -366,13 +366,6 @@ Java_com_google_android_filament_RenderableManager_nSetPriority(JNIEnv*, jclass,
|
|||||||
rm->setPriority((RenderableManager::Instance) i, (uint8_t) priority);
|
rm->setPriority((RenderableManager::Instance) i, (uint8_t) priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
|
||||||
Java_com_google_android_filament_RenderableManager_nGetPriority(JNIEnv*, jclass,
|
|
||||||
jlong nativeRenderableManager, jint i) {
|
|
||||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
|
||||||
return (jint) rm->getPriority((RenderableManager::Instance) i);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_RenderableManager_nSetChannel(JNIEnv*, jclass,
|
Java_com_google_android_filament_RenderableManager_nSetChannel(JNIEnv*, jclass,
|
||||||
jlong nativeRenderableManager, jint i, jint channel) {
|
jlong nativeRenderableManager, jint i, jint channel) {
|
||||||
@@ -380,13 +373,6 @@ Java_com_google_android_filament_RenderableManager_nSetChannel(JNIEnv*, jclass,
|
|||||||
rm->setChannel((RenderableManager::Instance) i, (uint8_t) channel);
|
rm->setChannel((RenderableManager::Instance) i, (uint8_t) channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
|
||||||
Java_com_google_android_filament_RenderableManager_nGetChannel(JNIEnv*, jclass,
|
|
||||||
jlong nativeRenderableManager, jint i) {
|
|
||||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
|
||||||
return (jint) rm->getChannel((RenderableManager::Instance) i);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_RenderableManager_nSetCulling(JNIEnv*, jclass,
|
Java_com_google_android_filament_RenderableManager_nSetCulling(JNIEnv*, jclass,
|
||||||
jlong nativeRenderableManager, jint i, jboolean enabled) {
|
jlong nativeRenderableManager, jint i, jboolean enabled) {
|
||||||
@@ -394,13 +380,6 @@ Java_com_google_android_filament_RenderableManager_nSetCulling(JNIEnv*, jclass,
|
|||||||
rm->setCulling((RenderableManager::Instance) i, enabled);
|
rm->setCulling((RenderableManager::Instance) i, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_google_android_filament_RenderableManager_nIsCullingEnabled(JNIEnv*, jclass,
|
|
||||||
jlong nativeRenderableManager, jint i) {
|
|
||||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
|
||||||
return (jboolean) rm->isCullingEnabled((RenderableManager::Instance) i);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_RenderableManager_nSetFogEnabled(JNIEnv*, jclass,
|
Java_com_google_android_filament_RenderableManager_nSetFogEnabled(JNIEnv*, jclass,
|
||||||
jlong nativeRenderableManager, jint i, jboolean enabled) {
|
jlong nativeRenderableManager, jint i, jboolean enabled) {
|
||||||
@@ -450,13 +429,6 @@ Java_com_google_android_filament_RenderableManager_nIsShadowReceiver(JNIEnv*, jc
|
|||||||
return (jboolean) rm->isShadowReceiver((RenderableManager::Instance) i);
|
return (jboolean) rm->isShadowReceiver((RenderableManager::Instance) i);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_google_android_filament_RenderableManager_nIsScreenSpaceContactShadowsEnabled(JNIEnv*, jclass,
|
|
||||||
jlong nativeRenderableManager, jint i) {
|
|
||||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
|
||||||
return (jboolean) rm->isScreenSpaceContactShadowsEnabled((RenderableManager::Instance) i);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_RenderableManager_nGetAxisAlignedBoundingBox(JNIEnv* env,
|
Java_com_google_android_filament_RenderableManager_nGetAxisAlignedBoundingBox(JNIEnv* env,
|
||||||
jclass, jlong nativeRenderableManager, jint i, jfloatArray center_,
|
jclass, jlong nativeRenderableManager, jint i, jfloatArray center_,
|
||||||
@@ -528,13 +500,6 @@ Java_com_google_android_filament_RenderableManager_nSetBlendOrderAt(JNIEnv*, jcl
|
|||||||
(uint16_t) blendOrder);
|
(uint16_t) blendOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
|
||||||
Java_com_google_android_filament_RenderableManager_nGetBlendOrderAt(JNIEnv*, jclass,
|
|
||||||
jlong nativeRenderableManager, jint i, jint primitiveIndex) {
|
|
||||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
|
||||||
return (jint) rm->getBlendOrderAt((RenderableManager::Instance) i, (size_t) primitiveIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_RenderableManager_nSetGlobalBlendOrderEnabledAt(JNIEnv*, jclass,
|
Java_com_google_android_filament_RenderableManager_nSetGlobalBlendOrderEnabledAt(JNIEnv*, jclass,
|
||||||
jlong nativeRenderableManager, jint i, jint primitiveIndex, jboolean enabled) {
|
jlong nativeRenderableManager, jint i, jint primitiveIndex, jboolean enabled) {
|
||||||
@@ -543,13 +508,6 @@ Java_com_google_android_filament_RenderableManager_nSetGlobalBlendOrderEnabledAt
|
|||||||
(bool) enabled);
|
(bool) enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_google_android_filament_RenderableManager_nIsGlobalBlendOrderEnabledAt(JNIEnv*, jclass,
|
|
||||||
jlong nativeRenderableManager, jint i, jint primitiveIndex) {
|
|
||||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
|
||||||
return (jboolean) rm->isGlobalBlendOrderEnabledAt((RenderableManager::Instance) i, (size_t) primitiveIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
Java_com_google_android_filament_RenderableManager_nGetEnabledAttributesAt(JNIEnv*, jclass,
|
Java_com_google_android_filament_RenderableManager_nGetEnabledAttributesAt(JNIEnv*, jclass,
|
||||||
jlong nativeRenderableManager, jint i, jint primitiveIndex) {
|
jlong nativeRenderableManager, jint i, jint primitiveIndex) {
|
||||||
|
|||||||
@@ -173,13 +173,6 @@ Java_com_google_android_filament_Texture_nBuilderSwizzle(JNIEnv *, jclass ,
|
|||||||
(Texture::Swizzle)r, (Texture::Swizzle)g, (Texture::Swizzle)b, (Texture::Swizzle)a);
|
(Texture::Swizzle)r, (Texture::Swizzle)g, (Texture::Swizzle)b, (Texture::Swizzle)a);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_com_google_android_filament_Texture_nBuilderSamples(JNIEnv*, jclass,
|
|
||||||
jlong nativeBuilder, jint samples) {
|
|
||||||
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
|
|
||||||
builder->samples((uint8_t) samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_Texture_nBuilderImportTexture(JNIEnv*, jclass, jlong nativeBuilder, jlong id) {
|
Java_com_google_android_filament_Texture_nBuilderImportTexture(JNIEnv*, jclass, jlong nativeBuilder, jlong id) {
|
||||||
|
|||||||
@@ -76,12 +76,6 @@ Java_com_google_android_filament_View_nSetVisibleLayers(JNIEnv*, jclass, jlong n
|
|||||||
view->setVisibleLayers((uint8_t) select, (uint8_t) value);
|
view->setVisibleLayers((uint8_t) select, (uint8_t) value);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
|
||||||
Java_com_google_android_filament_View_nGetVisibleLayers(JNIEnv*, jclass, jlong nativeView) {
|
|
||||||
View* view = (View*) nativeView;
|
|
||||||
return view->getVisibleLayers();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_View_nSetShadowingEnabled(JNIEnv*, jclass, jlong nativeView, jboolean enabled) {
|
Java_com_google_android_filament_View_nSetShadowingEnabled(JNIEnv*, jclass, jlong nativeView, jboolean enabled) {
|
||||||
View* view = (View*) nativeView;
|
View* view = (View*) nativeView;
|
||||||
@@ -446,18 +440,6 @@ Java_com_google_android_filament_View_nIsShadowingEnabled(JNIEnv *, jclass, jlon
|
|||||||
return (jboolean)view->isShadowingEnabled();
|
return (jboolean)view->isShadowingEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_com_google_android_filament_View_nSetFrustumCullingEnabled(JNIEnv*, jclass, jlong nativeView, jboolean enabled) {
|
|
||||||
View* view = (View*) nativeView;
|
|
||||||
view->setFrustumCullingEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_google_android_filament_View_nIsFrustumCullingEnabled(JNIEnv*, jclass, jlong nativeView) {
|
|
||||||
View* view = (View*) nativeView;
|
|
||||||
return (jboolean)view->isFrustumCullingEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_View_nSetScreenSpaceRefractionEnabled(JNIEnv *, jclass,
|
Java_com_google_android_filament_View_nSetScreenSpaceRefractionEnabled(JNIEnv *, jclass,
|
||||||
|
|||||||
@@ -136,13 +136,4 @@ final class Asserts {
|
|||||||
throw new ArrayIndexOutOfBoundsException("Array length must be at least 4");
|
throw new ArrayIndexOutOfBoundsException("Array length must be at least 4");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull @Size(min = 2)
|
|
||||||
static double[] assertDouble2(@Nullable double[] out) {
|
|
||||||
if (out == null) out = new double[2];
|
|
||||||
else if (out.length < 2) {
|
|
||||||
throw new ArrayIndexOutOfBoundsException("Array length must be at least 2");
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,27 +343,6 @@ public class Camera {
|
|||||||
nSetScaling(getNativeObject(), xscaling, yscaling);
|
nSetScaling(getNativeObject(), xscaling, yscaling);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a custom projection matrix for each eye.
|
|
||||||
*
|
|
||||||
* @param inProjection An array of projection matrices, one for each eye.
|
|
||||||
* Must have at least 16 * count elements.
|
|
||||||
* @param count Number of eyes to set.
|
|
||||||
* @param inProjectionForCulling Custom projection matrix for culling, must encompass all eyes.
|
|
||||||
* @param near Distance to the near plane.
|
|
||||||
* @param far Distance to the far plane.
|
|
||||||
*/
|
|
||||||
public void setCustomEyeProjection(
|
|
||||||
@NonNull double[] inProjection, int count,
|
|
||||||
@NonNull @Size(min = 16) double[] inProjectionForCulling,
|
|
||||||
double near, double far) {
|
|
||||||
Asserts.assertMat4dIn(inProjectionForCulling);
|
|
||||||
if (inProjection.length < 16 * count) {
|
|
||||||
throw new IllegalArgumentException("inProjection array too small for the given count");
|
|
||||||
}
|
|
||||||
nSetCustomEyeProjection(getNativeObject(), inProjection, count, inProjectionForCulling, near, far);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets an additional matrix that scales the projection matrix.
|
* Sets an additional matrix that scales the projection matrix.
|
||||||
*
|
*
|
||||||
@@ -420,31 +399,6 @@ public class Camera {
|
|||||||
nSetShift(getNativeObject(), xshift, yshift);
|
nSetShift(getNativeObject(), xshift, yshift);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the shift amount used to translate the projection matrix.
|
|
||||||
*
|
|
||||||
* @param out A 2-double array where the shift will be stored, or null.
|
|
||||||
* @return A 2-double array containing the x and y shift.
|
|
||||||
*/
|
|
||||||
@NonNull @Size(min = 2)
|
|
||||||
public double[] getShift(@Nullable @Size(min = 2) double[] out) {
|
|
||||||
out = Asserts.assertDouble2(out);
|
|
||||||
nGetShift(getNativeObject(), out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the camera's field of view in degrees.
|
|
||||||
*
|
|
||||||
* @param direction The direction of the FOV (VERTICAL or HORIZONTAL).
|
|
||||||
* @return The field of view in degrees.
|
|
||||||
*/
|
|
||||||
public double getFieldOfViewInDegrees(@NonNull Fov direction) {
|
|
||||||
return nGetFieldOfViewInDegrees(getNativeObject(), direction.ordinal());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the camera's model matrix.
|
* Sets the camera's model matrix.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -791,17 +745,6 @@ public class Camera {
|
|||||||
return mEntity;
|
return mEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the model matrix for a specific eye.
|
|
||||||
*
|
|
||||||
* @param eyeId The index of the eye.
|
|
||||||
* @param model The model matrix for the eye.
|
|
||||||
*/
|
|
||||||
public void setEyeModelMatrix(int eyeId, @NonNull @Size(min = 16) double[] model) {
|
|
||||||
Asserts.assertMat4dIn(model);
|
|
||||||
nSetEyeModelMatrix(getNativeObject(), eyeId, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to compute the effective focal length taking into account the focus distance
|
* Helper to compute the effective focal length taking into account the focus distance
|
||||||
*
|
*
|
||||||
@@ -841,13 +784,8 @@ public class Camera {
|
|||||||
private static native void nSetCustomProjection(long nativeCamera, double[] inProjection, double[] inProjectionForCulling, double near, double far);
|
private static native void nSetCustomProjection(long nativeCamera, double[] inProjection, double[] inProjectionForCulling, double near, double far);
|
||||||
private static native void nSetScaling(long nativeCamera, double x, double y);
|
private static native void nSetScaling(long nativeCamera, double x, double y);
|
||||||
private static native void nSetShift(long nativeCamera, double x, double y);
|
private static native void nSetShift(long nativeCamera, double x, double y);
|
||||||
private static native void nGetShift(long nativeCamera, double[] out);
|
|
||||||
private static native void nSetModelMatrix(long nativeCamera, float[] in);
|
private static native void nSetModelMatrix(long nativeCamera, float[] in);
|
||||||
private static native void nSetModelMatrixFp64(long nativeCamera, double[] in);
|
private static native void nSetModelMatrixFp64(long nativeCamera, double[] in);
|
||||||
private static native void nSetEyeModelMatrix(long nativeCamera, int eyeId, double[] model);
|
|
||||||
private static native void nSetCustomEyeProjection(long nativeCamera, double[] inProjection, int count, double[] inProjectionForCulling, double near, double far);
|
|
||||||
private static native double nGetFieldOfViewInDegrees(long nativeCamera, int direction);
|
|
||||||
|
|
||||||
private static native void nLookAt(long nativeCamera, double eyeX, double eyeY, double eyeZ, double centerX, double centerY, double centerZ, double upX, double upY, double upZ);
|
private static native void nLookAt(long nativeCamera, double eyeX, double eyeY, double eyeZ, double centerX, double centerY, double centerZ, double upX, double upY, double upZ);
|
||||||
private static native double nGetNear(long nativeCamera);
|
private static native double nGetNear(long nativeCamera);
|
||||||
private static native double nGetCullingFar(long nativeCamera);
|
private static native double nGetCullingFar(long nativeCamera);
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ public class Material {
|
|||||||
static final CullingMode[] sCullingModeValues = CullingMode.values();
|
static final CullingMode[] sCullingModeValues = CullingMode.values();
|
||||||
static final VertexBuffer.VertexAttribute[] sVertexAttributeValues =
|
static final VertexBuffer.VertexAttribute[] sVertexAttributeValues =
|
||||||
VertexBuffer.VertexAttribute.values();
|
VertexBuffer.VertexAttribute.values();
|
||||||
static final TransparencyMode[] sTransparencyModeValues = TransparencyMode.values();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long mNativeObject;
|
private long mNativeObject;
|
||||||
@@ -161,31 +160,6 @@ public class Material {
|
|||||||
SCREEN,
|
SCREEN,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* How transparent objects are handled
|
|
||||||
*
|
|
||||||
* @see
|
|
||||||
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/blendingandtransparency:transparencymode">
|
|
||||||
* Blending and transparency: transparencyMode</a>
|
|
||||||
*/
|
|
||||||
public enum TransparencyMode {
|
|
||||||
/** The transparent object is drawn honoring the raster state. */
|
|
||||||
DEFAULT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The transparent object is first drawn in the depth buffer,
|
|
||||||
* then in the color buffer, honoring the culling mode, but ignoring the depth test function.
|
|
||||||
*/
|
|
||||||
TWO_PASSES_ONE_SIDE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The transparent object is drawn twice in the color buffer,
|
|
||||||
* first with back faces only, then with front faces; the culling
|
|
||||||
* mode is ignored. Can be combined with two-sided lighting.
|
|
||||||
*/
|
|
||||||
TWO_PASSES_TWO_SIDES
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supported refraction modes
|
* Supported refraction modes
|
||||||
*
|
*
|
||||||
@@ -613,18 +587,6 @@ public class Material {
|
|||||||
return EnumCache.sBlendingModeValues[nGetBlendingMode(getNativeObject())];
|
return EnumCache.sBlendingModeValues[nGetBlendingMode(getNativeObject())];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the transparency mode of this material.
|
|
||||||
* This value only makes sense when the blending mode is transparent or fade.
|
|
||||||
*
|
|
||||||
* @see
|
|
||||||
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/blendingandtransparency:transparencymode">
|
|
||||||
* Blending and transparency: transparencyMode</a>
|
|
||||||
*/
|
|
||||||
public TransparencyMode getTransparencyMode() {
|
|
||||||
return EnumCache.sTransparencyModeValues[nGetTransparencyMode(getNativeObject())];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the refraction mode of this material.
|
* Returns the refraction mode of this material.
|
||||||
*
|
*
|
||||||
@@ -1168,7 +1130,6 @@ public class Material {
|
|||||||
private static native int nGetShading(long nativeMaterial);
|
private static native int nGetShading(long nativeMaterial);
|
||||||
private static native int nGetInterpolation(long nativeMaterial);
|
private static native int nGetInterpolation(long nativeMaterial);
|
||||||
private static native int nGetBlendingMode(long nativeMaterial);
|
private static native int nGetBlendingMode(long nativeMaterial);
|
||||||
private static native int nGetTransparencyMode(long nativeMaterial);
|
|
||||||
private static native int nGetVertexDomain(long nativeMaterial);
|
private static native int nGetVertexDomain(long nativeMaterial);
|
||||||
private static native int nGetCullingMode(long nativeMaterial);
|
private static native int nGetCullingMode(long nativeMaterial);
|
||||||
private static native boolean nIsColorWriteEnabled(long nativeMaterial);
|
private static native boolean nIsColorWriteEnabled(long nativeMaterial);
|
||||||
|
|||||||
@@ -537,14 +537,6 @@ public class MaterialInstance {
|
|||||||
nSetDoubleSided(getNativeObject(), doubleSided);
|
nSetDoubleSided(getNativeObject(), doubleSided);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the transparency mode for this material instance.
|
|
||||||
* @see Material.TransparencyMode
|
|
||||||
*/
|
|
||||||
public void setTransparencyMode(@NonNull Material.TransparencyMode mode) {
|
|
||||||
nSetTransparencyMode(getNativeObject(), mode.ordinal());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether double-sided lighting is enabled when the parent Material has double-sided
|
* Returns whether double-sided lighting is enabled when the parent Material has double-sided
|
||||||
* capability.
|
* capability.
|
||||||
@@ -553,14 +545,6 @@ public class MaterialInstance {
|
|||||||
return nIsDoubleSided(getNativeObject());
|
return nIsDoubleSided(getNativeObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the transparency mode.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public Material.TransparencyMode getTransparencyMode() {
|
|
||||||
return Material.EnumCache.sTransparencyModeValues[nGetTransparencyMode(getNativeObject())];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides the default triangle culling state that was set on the material.
|
* Overrides the default triangle culling state that was set on the material.
|
||||||
*
|
*
|
||||||
@@ -998,6 +982,4 @@ public class MaterialInstance {
|
|||||||
private static native boolean nIsStencilWriteEnabled(long nativeMaterialInstance);
|
private static native boolean nIsStencilWriteEnabled(long nativeMaterialInstance);
|
||||||
private static native boolean nIsDepthCullingEnabled(long nativeMaterialInstance);
|
private static native boolean nIsDepthCullingEnabled(long nativeMaterialInstance);
|
||||||
private static native int nGetDepthFunc(long nativeMaterialInstance);
|
private static native int nGetDepthFunc(long nativeMaterialInstance);
|
||||||
private static native void nSetTransparencyMode(long nativeMaterialInstance, int mode);
|
|
||||||
private static native int nGetTransparencyMode(long nativeMaterialInstance);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,8 +346,8 @@ public class RenderableManager {
|
|||||||
*
|
*
|
||||||
* @return Builder reference for chaining calls.
|
* @return Builder reference for chaining calls.
|
||||||
*
|
*
|
||||||
* @see Builder#blendOrder()
|
* @see Builder::blendOrder()
|
||||||
* @see Builder#priority()
|
* @see Builder::priority()
|
||||||
* @see RenderableManager::setBlendOrderAt()
|
* @see RenderableManager::setBlendOrderAt()
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -725,10 +725,6 @@ public class RenderableManager {
|
|||||||
nSetPriority(mNativeObject, i, priority);
|
nSetPriority(mNativeObject, i, priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPriority(@EntityInstance int i) {
|
|
||||||
return nGetPriority(mNativeObject, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the channel of a renderable
|
* Changes the channel of a renderable
|
||||||
*
|
*
|
||||||
@@ -738,10 +734,6 @@ public class RenderableManager {
|
|||||||
nSetChannel(mNativeObject, i, channel);
|
nSetChannel(mNativeObject, i, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getChannel(@EntityInstance int i) {
|
|
||||||
return nGetChannel(mNativeObject, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes whether or not frustum culling is on.
|
* Changes whether or not frustum culling is on.
|
||||||
*
|
*
|
||||||
@@ -751,10 +743,6 @@ public class RenderableManager {
|
|||||||
nSetCulling(mNativeObject, i, enabled);
|
nSetCulling(mNativeObject, i, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCullingEnabled(@EntityInstance int i) {
|
|
||||||
return nIsCullingEnabled(mNativeObject, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes whether or not the large-scale fog is applied to this renderable
|
* Changes whether or not the large-scale fog is applied to this renderable
|
||||||
* @see Builder#fog
|
* @see Builder#fog
|
||||||
@@ -824,10 +812,6 @@ public class RenderableManager {
|
|||||||
nSetScreenSpaceContactShadows(mNativeObject, i, enabled);
|
nSetScreenSpaceContactShadows(mNativeObject, i, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isScreenSpaceContactShadowsEnabled(@EntityInstance int i) {
|
|
||||||
return nIsScreenSpaceContactShadowsEnabled(mNativeObject, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the renderable can cast shadows.
|
* Checks if the renderable can cast shadows.
|
||||||
*
|
*
|
||||||
@@ -948,10 +932,6 @@ public class RenderableManager {
|
|||||||
nSetBlendOrderAt(mNativeObject, instance, primitiveIndex, blendOrder);
|
nSetBlendOrderAt(mNativeObject, instance, primitiveIndex, blendOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBlendOrderAt(@EntityInstance int instance, @IntRange(from = 0) int primitiveIndex) {
|
|
||||||
return nGetBlendOrderAt(mNativeObject, instance, primitiveIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes whether the blend order is global or local to this Renderable (by default).
|
* Changes whether the blend order is global or local to this Renderable (by default).
|
||||||
*
|
*
|
||||||
@@ -966,10 +946,6 @@ public class RenderableManager {
|
|||||||
nSetGlobalBlendOrderEnabledAt(mNativeObject, instance, primitiveIndex, enabled);
|
nSetGlobalBlendOrderEnabledAt(mNativeObject, instance, primitiveIndex, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGlobalBlendOrderEnabledAt(@EntityInstance int instance, @IntRange(from = 0) int primitiveIndex) {
|
|
||||||
return nIsGlobalBlendOrderEnabledAt(mNativeObject, instance, primitiveIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the set of enabled attribute slots in the given primitive's VertexBuffer.
|
* Retrieves the set of enabled attribute slots in the given primitive's VertexBuffer.
|
||||||
*/
|
*/
|
||||||
@@ -1037,11 +1013,8 @@ public class RenderableManager {
|
|||||||
private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez);
|
private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez);
|
||||||
private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value);
|
private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value);
|
||||||
private static native void nSetPriority(long nativeRenderableManager, int i, int priority);
|
private static native void nSetPriority(long nativeRenderableManager, int i, int priority);
|
||||||
private static native int nGetPriority(long nativeRenderableManager, int i);
|
|
||||||
private static native void nSetChannel(long nativeRenderableManager, int i, int channel);
|
private static native void nSetChannel(long nativeRenderableManager, int i, int channel);
|
||||||
private static native int nGetChannel(long nativeRenderableManager, int i);
|
|
||||||
private static native void nSetCulling(long nativeRenderableManager, int i, boolean enabled);
|
private static native void nSetCulling(long nativeRenderableManager, int i, boolean enabled);
|
||||||
private static native boolean nIsCullingEnabled(long nativeRenderableManager, int i);
|
|
||||||
private static native void nSetFogEnabled(long nativeRenderableManager, int i, boolean enabled);
|
private static native void nSetFogEnabled(long nativeRenderableManager, int i, boolean enabled);
|
||||||
private static native boolean nGetFogEnabled(long nativeRenderableManager, int i);
|
private static native boolean nGetFogEnabled(long nativeRenderableManager, int i);
|
||||||
private static native void nSetLightChannel(long nativeRenderableManager, int i, int channel, boolean enable);
|
private static native void nSetLightChannel(long nativeRenderableManager, int i, int channel, boolean enable);
|
||||||
@@ -1049,7 +1022,6 @@ public class RenderableManager {
|
|||||||
private static native void nSetCastShadows(long nativeRenderableManager, int i, boolean enabled);
|
private static native void nSetCastShadows(long nativeRenderableManager, int i, boolean enabled);
|
||||||
private static native void nSetReceiveShadows(long nativeRenderableManager, int i, boolean enabled);
|
private static native void nSetReceiveShadows(long nativeRenderableManager, int i, boolean enabled);
|
||||||
private static native void nSetScreenSpaceContactShadows(long nativeRenderableManager, int i, boolean enabled);
|
private static native void nSetScreenSpaceContactShadows(long nativeRenderableManager, int i, boolean enabled);
|
||||||
private static native boolean nIsScreenSpaceContactShadowsEnabled(long nativeRenderableManager, int i);
|
|
||||||
private static native boolean nIsShadowCaster(long nativeRenderableManager, int i);
|
private static native boolean nIsShadowCaster(long nativeRenderableManager, int i);
|
||||||
private static native boolean nIsShadowReceiver(long nativeRenderableManager, int i);
|
private static native boolean nIsShadowReceiver(long nativeRenderableManager, int i);
|
||||||
private static native void nGetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float[] center, float[] halfExtent);
|
private static native void nGetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float[] center, float[] halfExtent);
|
||||||
@@ -1060,8 +1032,6 @@ public class RenderableManager {
|
|||||||
private static native long nGetMaterialInstanceAt(long nativeRenderableManager, int i, int primitiveIndex);
|
private static native long nGetMaterialInstanceAt(long nativeRenderableManager, int i, int primitiveIndex);
|
||||||
private static native void nSetGeometryAt(long nativeRenderableManager, int i, int primitiveIndex, int primitiveType, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int count);
|
private static native void nSetGeometryAt(long nativeRenderableManager, int i, int primitiveIndex, int primitiveType, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int count);
|
||||||
private static native void nSetBlendOrderAt(long nativeRenderableManager, int i, int primitiveIndex, int blendOrder);
|
private static native void nSetBlendOrderAt(long nativeRenderableManager, int i, int primitiveIndex, int blendOrder);
|
||||||
private static native int nGetBlendOrderAt(long nativeRenderableManager, int i, int primitiveIndex);
|
|
||||||
private static native void nSetGlobalBlendOrderEnabledAt(long nativeRenderableManager, int i, int primitiveIndex, boolean enabled);
|
private static native void nSetGlobalBlendOrderEnabledAt(long nativeRenderableManager, int i, int primitiveIndex, boolean enabled);
|
||||||
private static native boolean nIsGlobalBlendOrderEnabledAt(long nativeRenderableManager, int i, int primitiveIndex);
|
|
||||||
private static native int nGetEnabledAttributesAt(long nativeRenderableManager, int i, int primitiveIndex);
|
private static native int nGetEnabledAttributesAt(long nativeRenderableManager, int i, int primitiveIndex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -795,17 +795,6 @@ public class Texture {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies the number of samples for multisample anti-aliasing.
|
|
||||||
* @param samples number of samples, must be at least 1. Default is 1.
|
|
||||||
* @return This Builder, for chaining calls.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public Builder samples(@IntRange(from = 1) int samples) {
|
|
||||||
nBuilderSamples(mNativeBuilder, samples);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the texture's internal format.
|
* Specifies the texture's internal format.
|
||||||
* <p>The internal format specifies how texels are stored (which may be different from how
|
* <p>The internal format specifies how texels are stored (which may be different from how
|
||||||
@@ -1381,7 +1370,6 @@ public class Texture {
|
|||||||
private static native void nBuilderFormat(long nativeBuilder, int format);
|
private static native void nBuilderFormat(long nativeBuilder, int format);
|
||||||
private static native void nBuilderUsage(long nativeBuilder, int flags);
|
private static native void nBuilderUsage(long nativeBuilder, int flags);
|
||||||
private static native void nBuilderSwizzle(long nativeBuilder, int r, int g, int b, int a);
|
private static native void nBuilderSwizzle(long nativeBuilder, int r, int g, int b, int a);
|
||||||
private static native void nBuilderSamples(long nativeBuilder, int samples);
|
|
||||||
private static native void nBuilderImportTexture(long nativeBuilder, long id);
|
private static native void nBuilderImportTexture(long nativeBuilder, long id);
|
||||||
private static native void nBuilderExternal(long nativeBuilder);
|
private static native void nBuilderExternal(long nativeBuilder);
|
||||||
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);
|
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);
|
||||||
|
|||||||
@@ -350,26 +350,6 @@ public class View {
|
|||||||
nSetVisibleLayers(getNativeObject(), select & 0xFF, values & 0xFF);
|
nSetVisibleLayers(getNativeObject(), select & 0xFF, values & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the visible layers.
|
|
||||||
*
|
|
||||||
* @return a bitmask specifying which layer is visible.
|
|
||||||
*/
|
|
||||||
public int getVisibleLayers() {
|
|
||||||
return nGetVisibleLayers(getNativeObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables or disables a specific layer.
|
|
||||||
*
|
|
||||||
* @param layer Index of the layer to enable or disable, must be between 0 and 7.
|
|
||||||
* @param enabled True to enable the layer, false to disable it.
|
|
||||||
*/
|
|
||||||
public void setLayerEnabled(@IntRange(from = 0, to = 7) int layer, boolean enabled) {
|
|
||||||
int mask = 1 << layer;
|
|
||||||
setVisibleLayers(mask, enabled ? mask : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables or disables shadow mapping. Enabled by default.
|
* Enables or disables shadow mapping. Enabled by default.
|
||||||
*
|
*
|
||||||
@@ -388,22 +368,6 @@ public class View {
|
|||||||
return nIsShadowingEnabled(getNativeObject());
|
return nIsShadowingEnabled(getNativeObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables or disables frustum culling. Enabled by default.
|
|
||||||
*
|
|
||||||
* @param enabled true enables frustum culling, false disables it.
|
|
||||||
*/
|
|
||||||
public void setFrustumCullingEnabled(boolean enabled) {
|
|
||||||
nSetFrustumCullingEnabled(getNativeObject(), enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return whether frustum culling is enabled
|
|
||||||
*/
|
|
||||||
public boolean isFrustumCullingEnabled() {
|
|
||||||
return nIsFrustumCullingEnabled(getNativeObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables or disables screen space refraction. Enabled by default.
|
* Enables or disables screen space refraction. Enabled by default.
|
||||||
*
|
*
|
||||||
@@ -1358,9 +1322,6 @@ public class View {
|
|||||||
private static native boolean nHasCamera(long nativeView);
|
private static native boolean nHasCamera(long nativeView);
|
||||||
private static native void nSetViewport(long nativeView, int left, int bottom, int width, int height);
|
private static native void nSetViewport(long nativeView, int left, int bottom, int width, int height);
|
||||||
private static native void nSetVisibleLayers(long nativeView, int select, int value);
|
private static native void nSetVisibleLayers(long nativeView, int select, int value);
|
||||||
private static native int nGetVisibleLayers(long nativeView);
|
|
||||||
private static native void nSetFrustumCullingEnabled(long nativeView, boolean enabled);
|
|
||||||
private static native boolean nIsFrustumCullingEnabled(long nativeView);
|
|
||||||
private static native void nSetShadowingEnabled(long nativeView, boolean enabled);
|
private static native void nSetShadowingEnabled(long nativeView, boolean enabled);
|
||||||
private static native void nSetRenderTarget(long nativeView, long nativeRenderTarget);
|
private static native void nSetRenderTarget(long nativeView, long nativeRenderTarget);
|
||||||
private static native void nSetSampleCount(long nativeView, int count);
|
private static native void nSetSampleCount(long nativeView, int count);
|
||||||
@@ -1445,59 +1406,65 @@ public class View {
|
|||||||
* by lowering the resolution of a View, or to increase the quality when the
|
* by lowering the resolution of a View, or to increase the quality when the
|
||||||
* rendering is faster than the target frame rate.
|
* rendering is faster than the target frame rate.
|
||||||
*
|
*
|
||||||
* <p>This structure can be used to specify the minimum scale factor used when
|
* This structure can be used to specify the minimum scale factor used when
|
||||||
* lowering the resolution of a View, and the maximum scale factor used when
|
* lowering the resolution of a View, and the maximum scale factor used when
|
||||||
* increasing the resolution for higher quality rendering. The scale factors
|
* increasing the resolution for higher quality rendering. The scale factors
|
||||||
* can be controlled on each X and Y axis independently. By default, all scale
|
* can be controlled on each X and Y axis independently. By default, all scale
|
||||||
* factors are set to 1.0.</p>
|
* factors are set to 1.0.
|
||||||
*
|
*
|
||||||
* <ul>
|
* enabled: enable or disables dynamic resolution on a View
|
||||||
* <li>enabled: enable or disables dynamic resolution on a View</li>
|
|
||||||
*
|
*
|
||||||
* <li>homogeneousScaling: by default the system scales the major axis first. Set this to true
|
* homogeneousScaling: by default the system scales the major axis first. Set this to true
|
||||||
* to force homogeneous scaling.</li>
|
* to force homogeneous scaling.
|
||||||
*
|
*
|
||||||
* <li>minScale: the minimum scale in X and Y this View should use</li>
|
* minScale: the minimum scale in X and Y this View should use
|
||||||
*
|
*
|
||||||
* <li>maxScale: the maximum scale in X and Y this View should use</li>
|
* maxScale: the maximum scale in X and Y this View should use
|
||||||
*
|
*
|
||||||
* <li>quality: upscaling quality.
|
* quality: upscaling quality.
|
||||||
* LOW: 1 bilinear tap, Medium: 4 bilinear taps, High: 9 bilinear taps (tent)</li>
|
* LOW: 1 bilinear tap, Medium: 4 bilinear taps, High: 9 bilinear taps (tent)
|
||||||
* </ul>
|
|
||||||
*
|
*
|
||||||
* <p>Note:
|
* \note
|
||||||
* Dynamic resolution is only supported on platforms where the time to render
|
* Dynamic resolution is only supported on platforms where the time to render
|
||||||
* a frame can be measured accurately. On platforms where this is not supported,
|
* a frame can be measured accurately. On platforms where this is not supported,
|
||||||
* Dynamic Resolution can't be enabled unless <code>minScale == maxScale</code>.</p>
|
* Dynamic Resolution can't be enabled unless minScale == maxScale.
|
||||||
*
|
*
|
||||||
* @see Renderer.FrameRateOptions
|
* @see Renderer::FrameRateOptions
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static class DynamicResolutionOptions {
|
public static class DynamicResolutionOptions {
|
||||||
/** minimum scale factors in x and y */
|
/**
|
||||||
|
* minimum scale factors in x and y
|
||||||
|
*/
|
||||||
public float minScale = 0.5f;
|
public float minScale = 0.5f;
|
||||||
/** maximum scale factors in x and y */
|
/**
|
||||||
|
* maximum scale factors in x and y
|
||||||
|
*/
|
||||||
public float maxScale = 1.0f;
|
public float maxScale = 1.0f;
|
||||||
/** sharpness when QualityLevel::MEDIUM or higher is used [0 (disabled), 1 (sharpest)] */
|
/**
|
||||||
|
* sharpness when QualityLevel::MEDIUM or higher is used [0 (disabled), 1 (sharpest)]
|
||||||
|
*/
|
||||||
public float sharpness = 0.9f;
|
public float sharpness = 0.9f;
|
||||||
/** enable or disable dynamic resolution */
|
/**
|
||||||
|
* enable or disable dynamic resolution
|
||||||
|
*/
|
||||||
public boolean enabled = false;
|
public boolean enabled = false;
|
||||||
/** set to true to force homogeneous scaling */
|
/**
|
||||||
|
* set to true to force homogeneous scaling
|
||||||
|
*/
|
||||||
public boolean homogeneousScaling = false;
|
public boolean homogeneousScaling = false;
|
||||||
/**
|
/**
|
||||||
* Upscaling quality
|
* Upscaling quality
|
||||||
* <ul>
|
* LOW: bilinear filtered blit. Fastest, poor quality
|
||||||
* <li>LOW: bilinear filtered blit. Fastest, poor quality</li>
|
* MEDIUM: Qualcomm Snapdragon Game Super Resolution (SGSR) 1.0
|
||||||
* <li>MEDIUM: Qualcomm Snapdragon Game Super Resolution (SGSR) 1.0</li>
|
* HIGH: AMD FidelityFX FSR1 w/ mobile optimizations
|
||||||
* <li>HIGH: AMD FidelityFX FSR1 w/ mobile optimizations</li>
|
* ULTRA: AMD FidelityFX FSR1
|
||||||
* <li>ULTRA: AMD FidelityFX FSR1</li>
|
|
||||||
* </ul>
|
|
||||||
* FSR1 and SGSR require a well anti-aliased (MSAA or TAA), noise free scene.
|
* FSR1 and SGSR require a well anti-aliased (MSAA or TAA), noise free scene.
|
||||||
* Avoid FXAA and dithering.
|
* Avoid FXAA and dithering.
|
||||||
*
|
*
|
||||||
* <p>The default upscaling quality is set to LOW.</p>
|
* The default upscaling quality is set to LOW.
|
||||||
*
|
*
|
||||||
* <p>caveat: currently, <code>quality</code> is always set to LOW if the View is TRANSLUCENT.</p>
|
* caveat: currently, 'quality' is always set to LOW if the View is TRANSLUCENT.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public QualityLevel quality = QualityLevel.LOW;
|
public QualityLevel quality = QualityLevel.LOW;
|
||||||
@@ -1506,98 +1473,134 @@ public class View {
|
|||||||
/**
|
/**
|
||||||
* Options to control the bloom effect
|
* Options to control the bloom effect
|
||||||
*
|
*
|
||||||
* <ul>
|
* enabled: Enable or disable the bloom post-processing effect. Disabled by default.
|
||||||
* <li>enabled: Enable or disable the bloom post-processing effect. Disabled by default.</li>
|
|
||||||
*
|
*
|
||||||
* <li>levels: Number of successive blurs to achieve the blur effect, the minimum is 3 and the
|
* levels: Number of successive blurs to achieve the blur effect, the minimum is 3 and the
|
||||||
* maximum is 12. This value together with resolution influences the spread of the
|
* maximum is 12. This value together with resolution influences the spread of the
|
||||||
* blur effect. This value can be silently reduced to accommodate the original
|
* blur effect. This value can be silently reduced to accommodate the original
|
||||||
* image size.</li>
|
* image size.
|
||||||
*
|
*
|
||||||
* <li>resolution: Resolution of bloom's minor axis. The minimum value is 2^levels and the
|
* resolution: Resolution of bloom's minor axis. The minimum value is 2^levels and the
|
||||||
* the maximum is lower of the original resolution and 4096. This parameter is
|
* the maximum is lower of the original resolution and 4096. This parameter is
|
||||||
* silently clamped to the minimum and maximum.
|
* silently clamped to the minimum and maximum.
|
||||||
* It is highly recommended that this value be smaller than the target resolution
|
* It is highly recommended that this value be smaller than the target resolution
|
||||||
* after dynamic resolution is applied (horizontally and vertically).</li>
|
* after dynamic resolution is applied (horizontally and vertically).
|
||||||
*
|
*
|
||||||
* <li>strength: how much of the bloom is added to the original image. Between 0 and 1.</li>
|
* strength: how much of the bloom is added to the original image. Between 0 and 1.
|
||||||
*
|
*
|
||||||
* <li>blendMode: Whether the bloom effect is purely additive (false) or mixed with the original
|
* blendMode: Whether the bloom effect is purely additive (false) or mixed with the original
|
||||||
* image (true).</li>
|
* image (true).
|
||||||
*
|
*
|
||||||
* <li>threshold: When enabled, a threshold at 1.0 is applied on the source image, this is
|
* threshold: When enabled, a threshold at 1.0 is applied on the source image, this is
|
||||||
* useful for artistic reasons and is usually needed when a dirt texture is used.</li>
|
* useful for artistic reasons and is usually needed when a dirt texture is used.
|
||||||
*
|
*
|
||||||
* <li>dirt: A dirt/scratch/smudges texture (that can be RGB), which gets added to the
|
* dirt: A dirt/scratch/smudges texture (that can be RGB), which gets added to the
|
||||||
* bloom effect. Smudges are visible where bloom occurs. Threshold must be
|
* bloom effect. Smudges are visible where bloom occurs. Threshold must be
|
||||||
* enabled for the dirt effect to work properly.</li>
|
* enabled for the dirt effect to work properly.
|
||||||
*
|
*
|
||||||
* <li>dirtStrength: Strength of the dirt texture.</li>
|
* dirtStrength: Strength of the dirt texture.
|
||||||
* </ul>
|
|
||||||
*/
|
*/
|
||||||
public static class BloomOptions {
|
public static class BloomOptions {
|
||||||
public enum BlendMode {
|
public enum BlendMode {
|
||||||
/** Bloom is modulated by the strength parameter and added to the scene */
|
/**
|
||||||
|
* Bloom is modulated by the strength parameter and added to the scene
|
||||||
|
*/
|
||||||
ADD,
|
ADD,
|
||||||
/** Bloom is interpolated with the scene using the strength parameter */
|
/**
|
||||||
|
* Bloom is interpolated with the scene using the strength parameter
|
||||||
|
*/
|
||||||
INTERPOLATE,
|
INTERPOLATE,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** user provided dirt texture */
|
/**
|
||||||
|
* user provided dirt texture
|
||||||
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public Texture dirt = null;
|
public Texture dirt = null;
|
||||||
/** strength of the dirt texture */
|
/**
|
||||||
|
* strength of the dirt texture
|
||||||
|
*/
|
||||||
public float dirtStrength = 0.2f;
|
public float dirtStrength = 0.2f;
|
||||||
/** bloom's strength between 0.0 and 1.0 */
|
/**
|
||||||
|
* bloom's strength between 0.0 and 1.0
|
||||||
|
*/
|
||||||
public float strength = 0.10f;
|
public float strength = 0.10f;
|
||||||
/** resolution of vertical axis (2^levels to 2048) */
|
/**
|
||||||
|
* resolution of vertical axis (2^levels to 2048)
|
||||||
|
*/
|
||||||
public int resolution = 384;
|
public int resolution = 384;
|
||||||
/** number of blur levels (1 to 11) */
|
/**
|
||||||
|
* number of blur levels (1 to 11)
|
||||||
|
*/
|
||||||
public int levels = 6;
|
public int levels = 6;
|
||||||
/** how the bloom effect is applied */
|
/**
|
||||||
|
* how the bloom effect is applied
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public BloomOptions.BlendMode blendMode = BloomOptions.BlendMode.ADD;
|
public BloomOptions.BlendMode blendMode = BloomOptions.BlendMode.ADD;
|
||||||
/** whether to threshold the source */
|
/**
|
||||||
|
* whether to threshold the source
|
||||||
|
*/
|
||||||
public boolean threshold = true;
|
public boolean threshold = true;
|
||||||
/** enable or disable bloom */
|
/**
|
||||||
|
* enable or disable bloom
|
||||||
|
*/
|
||||||
public boolean enabled = false;
|
public boolean enabled = false;
|
||||||
/** limit highlights to this value before bloom [10, +inf] */
|
/**
|
||||||
|
* limit highlights to this value before bloom [10, +inf]
|
||||||
|
*/
|
||||||
public float highlight = 1000.0f;
|
public float highlight = 1000.0f;
|
||||||
/**
|
/**
|
||||||
* Bloom quality level.
|
* Bloom quality level.
|
||||||
* <ul>
|
* LOW (default): use a more optimized down-sampling filter, however there can be artifacts
|
||||||
* <li>LOW (default): use a more optimized down-sampling filter, however there can be artifacts
|
* with dynamic resolution, this can be alleviated by using the homogenous mode.
|
||||||
* with dynamic resolution, this can be alleviated by using the homogenous mode.</li>
|
* MEDIUM: Good balance between quality and performance.
|
||||||
* <li>MEDIUM: Good balance between quality and performance.</li>
|
* HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts.
|
||||||
* <li>HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts.
|
|
||||||
* This mode can be significantly slower on mobile, especially at high resolution.
|
* This mode can be significantly slower on mobile, especially at high resolution.
|
||||||
* This mode greatly improves the anamorphic bloom.</li>
|
* This mode greatly improves the anamorphic bloom.
|
||||||
* </ul>
|
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public QualityLevel quality = QualityLevel.LOW;
|
public QualityLevel quality = QualityLevel.LOW;
|
||||||
/** enable screen-space lens flare */
|
/**
|
||||||
|
* enable screen-space lens flare
|
||||||
|
*/
|
||||||
public boolean lensFlare = false;
|
public boolean lensFlare = false;
|
||||||
/** enable starburst effect on lens flare */
|
/**
|
||||||
|
* enable starburst effect on lens flare
|
||||||
|
*/
|
||||||
public boolean starburst = true;
|
public boolean starburst = true;
|
||||||
/** amount of chromatic aberration */
|
/**
|
||||||
|
* amount of chromatic aberration
|
||||||
|
*/
|
||||||
public float chromaticAberration = 0.005f;
|
public float chromaticAberration = 0.005f;
|
||||||
/** number of flare "ghosts" */
|
/**
|
||||||
|
* number of flare "ghosts"
|
||||||
|
*/
|
||||||
public int ghostCount = 4;
|
public int ghostCount = 4;
|
||||||
/** spacing of the ghost in screen units [0, 1[ */
|
/**
|
||||||
|
* spacing of the ghost in screen units [0, 1[
|
||||||
|
*/
|
||||||
public float ghostSpacing = 0.6f;
|
public float ghostSpacing = 0.6f;
|
||||||
/** hdr threshold for the ghosts */
|
/**
|
||||||
|
* hdr threshold for the ghosts
|
||||||
|
*/
|
||||||
public float ghostThreshold = 10.0f;
|
public float ghostThreshold = 10.0f;
|
||||||
/** thickness of halo in vertical screen units, 0 to disable */
|
/**
|
||||||
|
* thickness of halo in vertical screen units, 0 to disable
|
||||||
|
*/
|
||||||
public float haloThickness = 0.1f;
|
public float haloThickness = 0.1f;
|
||||||
/** radius of halo in vertical screen units [0, 0.5] */
|
/**
|
||||||
|
* radius of halo in vertical screen units [0, 0.5]
|
||||||
|
*/
|
||||||
public float haloRadius = 0.4f;
|
public float haloRadius = 0.4f;
|
||||||
/** hdr threshold for the halo */
|
/**
|
||||||
|
* hdr threshold for the halo
|
||||||
|
*/
|
||||||
public float haloThreshold = 10.0f;
|
public float haloThreshold = 10.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options to control large-scale fog in the scene. Materials can enable the <code>linearFog</code> property,
|
* Options to control large-scale fog in the scene. Materials can enable the `linearFog` property,
|
||||||
* which uses a simplified, linear equation for fog calculation; in this mode, the heightFalloff
|
* which uses a simplified, linear equation for fog calculation; in this mode, the heightFalloff
|
||||||
* is ignored as well as the mipmap selection in IBL or skyColor mode.
|
* is ignored as well as the mipmap selection in IBL or skyColor mode.
|
||||||
*/
|
*/
|
||||||
@@ -1611,12 +1614,12 @@ public class View {
|
|||||||
* This can be used to exclude the skybox, which is desirable if it already contains clouds or
|
* This can be used to exclude the skybox, which is desirable if it already contains clouds or
|
||||||
* fog. The default value is +infinity which applies the fog to everything.
|
* fog. The default value is +infinity which applies the fog to everything.
|
||||||
*
|
*
|
||||||
* <p>Note: The SkyBox is typically at a distance of 1e19 in world space (depending on the near
|
* Note: The SkyBox is typically at a distance of 1e19 in world space (depending on the near
|
||||||
* plane distance and projection used though).</p>
|
* plane distance and projection used though).
|
||||||
*/
|
*/
|
||||||
public float cutOffDistance = Float.POSITIVE_INFINITY;
|
public float cutOffDistance = Float.POSITIVE_INFINITY;
|
||||||
/**
|
/**
|
||||||
* fog's maximum opacity between 0 and 1. Ignored in <code>linearFog</code> mode.
|
* fog's maximum opacity between 0 and 1. Ignored in `linearFog` mode.
|
||||||
*/
|
*/
|
||||||
public float maximumOpacity = 1.0f;
|
public float maximumOpacity = 1.0f;
|
||||||
/**
|
/**
|
||||||
@@ -1628,11 +1631,11 @@ public class View {
|
|||||||
* It can be expressed as 1/H, where H is the altitude change in world units [m] that causes a
|
* It can be expressed as 1/H, where H is the altitude change in world units [m] that causes a
|
||||||
* factor 2.78 (e) change in fog density.
|
* factor 2.78 (e) change in fog density.
|
||||||
*
|
*
|
||||||
* <p>A falloff of 0 means the fog density is constant everywhere and may result is slightly
|
* A falloff of 0 means the fog density is constant everywhere and may result is slightly
|
||||||
* faster computations.</p>
|
* faster computations.
|
||||||
*
|
*
|
||||||
* <p>In <code>linearFog</code> mode, only use to compute the slope of the linear equation. Completely
|
* In `linearFog` mode, only use to compute the slope of the linear equation. Completely
|
||||||
* ignored if set to 0.</p>
|
* ignored if set to 0.
|
||||||
*/
|
*/
|
||||||
public float heightFalloff = 1.0f;
|
public float heightFalloff = 1.0f;
|
||||||
/**
|
/**
|
||||||
@@ -1642,11 +1645,11 @@ public class View {
|
|||||||
* above one are allowed but could create a non energy-conservative fog (this is dependant
|
* above one are allowed but could create a non energy-conservative fog (this is dependant
|
||||||
* on the IBL's intensity as well).
|
* on the IBL's intensity as well).
|
||||||
*
|
*
|
||||||
* <p>We assume that our fog has no absorption and therefore all the light it scatters out
|
* We assume that our fog has no absorption and therefore all the light it scatters out
|
||||||
* becomes ambient light in-scattering and has lost all directionality, i.e.: scattering is
|
* becomes ambient light in-scattering and has lost all directionality, i.e.: scattering is
|
||||||
* isotropic. This somewhat simulates Rayleigh scattering.</p>
|
* isotropic. This somewhat simulates Rayleigh scattering.
|
||||||
*
|
*
|
||||||
* <p>This value is used as a tint instead, when fogColorFromIbl is enabled.</p>
|
* This value is used as a tint instead, when fogColorFromIbl is enabled.
|
||||||
*
|
*
|
||||||
* @see #fogColorFromIbl
|
* @see #fogColorFromIbl
|
||||||
*/
|
*/
|
||||||
@@ -1657,20 +1660,20 @@ public class View {
|
|||||||
* light is absorbed and out-scattered per unit of distance. Each unit of extinction reduces
|
* light is absorbed and out-scattered per unit of distance. Each unit of extinction reduces
|
||||||
* the incoming light to 37% of its original value.
|
* the incoming light to 37% of its original value.
|
||||||
*
|
*
|
||||||
* <p>Note: The extinction factor is related to the fog density, it's usually some constant K times
|
* Note: The extinction factor is related to the fog density, it's usually some constant K times
|
||||||
* the density at sea level (more specifically at fog height). The constant K depends on
|
* the density at sea level (more specifically at fog height). The constant K depends on
|
||||||
* the composition of the fog/atmosphere.</p>
|
* the composition of the fog/atmosphere.
|
||||||
*
|
*
|
||||||
* <p>For historical reason this parameter is called <code>density</code>.</p>
|
* For historical reason this parameter is called `density`.
|
||||||
*
|
*
|
||||||
* <p>In <code>linearFog</code> mode this is the slope of the linear equation if heightFalloff is set to 0.
|
* In `linearFog` mode this is the slope of the linear equation if heightFalloff is set to 0.
|
||||||
* Otherwise, heightFalloff affects the slope calculation such that it matches the slope of
|
* Otherwise, heightFalloff affects the slope calculation such that it matches the slope of
|
||||||
* the standard equation at the camera height.</p>
|
* the standard equation at the camera height.
|
||||||
*/
|
*/
|
||||||
public float density = 0.1f;
|
public float density = 0.1f;
|
||||||
/**
|
/**
|
||||||
* Distance in world units [m] from the camera where the Sun in-scattering starts.
|
* Distance in world units [m] from the camera where the Sun in-scattering starts.
|
||||||
* Ignored in <code>linearFog</code> mode.
|
* Ignored in `linearFog` mode.
|
||||||
*/
|
*/
|
||||||
public float inScatteringStart = 0.0f;
|
public float inScatteringStart = 0.0f;
|
||||||
/**
|
/**
|
||||||
@@ -1678,16 +1681,16 @@ public class View {
|
|||||||
* is scattered (by the fog) towards the camera.
|
* is scattered (by the fog) towards the camera.
|
||||||
* Size of the Sun in-scattering (>0 to activate). Good values are >> 1 (e.g. ~10 - 100).
|
* Size of the Sun in-scattering (>0 to activate). Good values are >> 1 (e.g. ~10 - 100).
|
||||||
* Smaller values result is a larger scattering size.
|
* Smaller values result is a larger scattering size.
|
||||||
* Ignored in <code>linearFog</code> mode.
|
* Ignored in `linearFog` mode.
|
||||||
*/
|
*/
|
||||||
public float inScatteringSize = -1.0f;
|
public float inScatteringSize = -1.0f;
|
||||||
/**
|
/**
|
||||||
* The fog color will be sampled from the IBL in the view direction and tinted by <code>color</code>.
|
* The fog color will be sampled from the IBL in the view direction and tinted by `color`.
|
||||||
* Depending on the scene this can produce very convincing results.
|
* Depending on the scene this can produce very convincing results.
|
||||||
*
|
*
|
||||||
* <p>This simulates a more anisotropic phase-function.</p>
|
* This simulates a more anisotropic phase-function.
|
||||||
*
|
*
|
||||||
* <p><code>fogColorFromIbl</code> is ignored when skyTexture is specified.</p>
|
* `fogColorFromIbl` is ignored when skyTexture is specified.
|
||||||
*
|
*
|
||||||
* @see #skyColor
|
* @see #skyColor
|
||||||
*/
|
*/
|
||||||
@@ -1700,11 +1703,11 @@ public class View {
|
|||||||
* level with a strong gaussian filter or even an irradiance filter and then generate mip
|
* level with a strong gaussian filter or even an irradiance filter and then generate mip
|
||||||
* levels as usual. How blurred the base level is somewhat of an artistic decision.
|
* levels as usual. How blurred the base level is somewhat of an artistic decision.
|
||||||
*
|
*
|
||||||
* <p>This simulates a more anisotropic phase-function.</p>
|
* This simulates a more anisotropic phase-function.
|
||||||
*
|
*
|
||||||
* <p><code>fogColorFromIbl</code> is ignored when skyTexture is specified.</p>
|
* `fogColorFromIbl` is ignored when skyTexture is specified.
|
||||||
*
|
*
|
||||||
* <p>In <code>linearFog</code> mode mipmap level 0 is always used.</p>
|
* In `linearFog` mode mipmap level 0 is always used.
|
||||||
*
|
*
|
||||||
* @see Texture
|
* @see Texture
|
||||||
* @see #fogColorFromIbl
|
* @see #fogColorFromIbl
|
||||||
@@ -1720,9 +1723,9 @@ public class View {
|
|||||||
/**
|
/**
|
||||||
* Options to control Depth of Field (DoF) effect in the scene.
|
* Options to control Depth of Field (DoF) effect in the scene.
|
||||||
*
|
*
|
||||||
* <p>cocScale can be used to set the depth of field blur independently of the camera
|
* cocScale can be used to set the depth of field blur independently of the camera
|
||||||
* aperture, e.g. for artistic reasons. This can be achieved by setting:
|
* aperture, e.g. for artistic reasons. This can be achieved by setting:
|
||||||
* cocScale = cameraAperture / desiredDoFAperture</p>
|
* cocScale = cameraAperture / desiredDoFAperture
|
||||||
*
|
*
|
||||||
* @see Camera
|
* @see Camera
|
||||||
*/
|
*/
|
||||||
@@ -1733,24 +1736,59 @@ public class View {
|
|||||||
MEDIAN,
|
MEDIAN,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** circle of confusion scale factor (amount of blur) */
|
/**
|
||||||
|
* circle of confusion scale factor (amount of blur)
|
||||||
|
*/
|
||||||
public float cocScale = 1.0f;
|
public float cocScale = 1.0f;
|
||||||
/** width/height aspect ratio of the circle of confusion (simulate anamorphic lenses) */
|
/**
|
||||||
|
* width/height aspect ratio of the circle of confusion (simulate anamorphic lenses)
|
||||||
|
*/
|
||||||
public float cocAspectRatio = 1.0f;
|
public float cocAspectRatio = 1.0f;
|
||||||
/** maximum aperture diameter in meters (zero to disable rotation) */
|
/**
|
||||||
|
* maximum aperture diameter in meters (zero to disable rotation)
|
||||||
|
*/
|
||||||
public float maxApertureDiameter = 0.01f;
|
public float maxApertureDiameter = 0.01f;
|
||||||
/** enable or disable depth of field effect */
|
/**
|
||||||
|
* enable or disable depth of field effect
|
||||||
|
*/
|
||||||
public boolean enabled = false;
|
public boolean enabled = false;
|
||||||
/** filter to use for filling gaps in the kernel */
|
/**
|
||||||
|
* filter to use for filling gaps in the kernel
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public DepthOfFieldOptions.Filter filter = DepthOfFieldOptions.Filter.MEDIAN;
|
public DepthOfFieldOptions.Filter filter = DepthOfFieldOptions.Filter.MEDIAN;
|
||||||
/** perform DoF processing at native resolution */
|
/**
|
||||||
|
* perform DoF processing at native resolution
|
||||||
|
*/
|
||||||
public boolean nativeResolution = false;
|
public boolean nativeResolution = false;
|
||||||
/** number of kernel rings for foreground tiles */
|
/**
|
||||||
|
* Number of of rings used by the gather kernels. The number of rings affects quality
|
||||||
|
* and performance. The actual number of sample per pixel is defined
|
||||||
|
* as (ringCount * 2 - 1)^2. Here are a few commonly used values:
|
||||||
|
* 3 rings : 25 ( 5x 5 grid)
|
||||||
|
* 4 rings : 49 ( 7x 7 grid)
|
||||||
|
* 5 rings : 81 ( 9x 9 grid)
|
||||||
|
* 17 rings : 1089 (33x33 grid)
|
||||||
|
*
|
||||||
|
* With a maximum circle-of-confusion of 32, it is never necessary to use more than 17 rings.
|
||||||
|
*
|
||||||
|
* Usually all three settings below are set to the same value, however, it is often
|
||||||
|
* acceptable to use a lower ring count for the "fast tiles", which improves performance.
|
||||||
|
* Fast tiles are regions of the screen where every pixels have a similar
|
||||||
|
* circle-of-confusion radius.
|
||||||
|
*
|
||||||
|
* A value of 0 means default, which is 5 on desktop and 3 on mobile.
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
public int foregroundRingCount = 0;
|
public int foregroundRingCount = 0;
|
||||||
/** number of kernel rings for background tiles */
|
/**
|
||||||
|
* number of kernel rings for background tiles
|
||||||
|
*/
|
||||||
public int backgroundRingCount = 0;
|
public int backgroundRingCount = 0;
|
||||||
/** number of kernel rings for fast tiles */
|
/**
|
||||||
|
* number of kernel rings for fast tiles
|
||||||
|
*/
|
||||||
public int fastGatherRingCount = 0;
|
public int fastGatherRingCount = 0;
|
||||||
/**
|
/**
|
||||||
* maximum circle-of-confusion in pixels for the foreground, must be in [0, 32] range.
|
* maximum circle-of-confusion in pixels for the foreground, must be in [0, 32] range.
|
||||||
@@ -1768,16 +1806,26 @@ public class View {
|
|||||||
* Options to control the vignetting effect.
|
* Options to control the vignetting effect.
|
||||||
*/
|
*/
|
||||||
public static class VignetteOptions {
|
public static class VignetteOptions {
|
||||||
/** high values restrict the vignette closer to the corners, between 0 and 1 */
|
/**
|
||||||
|
* high values restrict the vignette closer to the corners, between 0 and 1
|
||||||
|
*/
|
||||||
public float midPoint = 0.5f;
|
public float midPoint = 0.5f;
|
||||||
/** controls the shape of the vignette, from a rounded rectangle (0.0), to an oval (0.5), to a circle (1.0) */
|
/**
|
||||||
|
* controls the shape of the vignette, from a rounded rectangle (0.0), to an oval (0.5), to a circle (1.0)
|
||||||
|
*/
|
||||||
public float roundness = 0.5f;
|
public float roundness = 0.5f;
|
||||||
/** softening amount of the vignette effect, between 0 and 1 */
|
/**
|
||||||
|
* softening amount of the vignette effect, between 0 and 1
|
||||||
|
*/
|
||||||
public float feather = 0.5f;
|
public float feather = 0.5f;
|
||||||
/** color of the vignette effect, alpha is currently ignored */
|
/**
|
||||||
|
* color of the vignette effect, alpha is currently ignored
|
||||||
|
*/
|
||||||
@NonNull @Size(min = 4)
|
@NonNull @Size(min = 4)
|
||||||
public float[] color = {0.0f, 0.0f, 0.0f, 1.0f};
|
public float[] color = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
/** enables or disables the vignette effect */
|
/**
|
||||||
|
* enables or disables the vignette effect
|
||||||
|
*/
|
||||||
public boolean enabled = false;
|
public boolean enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1791,11 +1839,11 @@ public class View {
|
|||||||
/**
|
/**
|
||||||
* Sets the quality of the HDR color buffer.
|
* Sets the quality of the HDR color buffer.
|
||||||
*
|
*
|
||||||
* <p>A quality of HIGH or ULTRA means using an RGB16F or RGBA16F color buffer. This means
|
* A quality of HIGH or ULTRA means using an RGB16F or RGBA16F color buffer. This means
|
||||||
* colors in the LDR range (0..1) have a 10 bit precision. A quality of LOW or MEDIUM means
|
* colors in the LDR range (0..1) have a 10 bit precision. A quality of LOW or MEDIUM means
|
||||||
* using an R11G11B10F opaque color buffer or an RGBA16F transparent color buffer. With
|
* using an R11G11B10F opaque color buffer or an RGBA16F transparent color buffer. With
|
||||||
* R11G11B10F colors in the LDR range have a precision of either 6 bits (red and green
|
* R11G11B10F colors in the LDR range have a precision of either 6 bits (red and green
|
||||||
* channels) or 5 bits (blue channel).</p>
|
* channels) or 5 bits (blue channel).
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public QualityLevel hdrColorBuffer = QualityLevel.HIGH;
|
public QualityLevel hdrColorBuffer = QualityLevel.HIGH;
|
||||||
@@ -1807,44 +1855,72 @@ public class View {
|
|||||||
*/
|
*/
|
||||||
public static class AmbientOcclusionOptions {
|
public static class AmbientOcclusionOptions {
|
||||||
public enum AmbientOcclusionType {
|
public enum AmbientOcclusionType {
|
||||||
/** use Scalable Ambient Occlusion */
|
/**
|
||||||
|
* use Scalable Ambient Occlusion
|
||||||
|
*/
|
||||||
SAO,
|
SAO,
|
||||||
/** use Ground Truth-Based Ambient Occlusion */
|
/**
|
||||||
|
* use Ground Truth-Based Ambient Occlusion
|
||||||
|
*/
|
||||||
GTAO,
|
GTAO,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Type of ambient occlusion algorithm. */
|
/**
|
||||||
|
* Type of ambient occlusion algorithm.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public AmbientOcclusionOptions.AmbientOcclusionType aoType = AmbientOcclusionOptions.AmbientOcclusionType.SAO;
|
public AmbientOcclusionOptions.AmbientOcclusionType aoType = AmbientOcclusionOptions.AmbientOcclusionType.SAO;
|
||||||
/** Ambient Occlusion radius in meters, between 0 and ~10. */
|
/**
|
||||||
|
* Ambient Occlusion radius in meters, between 0 and ~10.
|
||||||
|
*/
|
||||||
public float radius = 0.3f;
|
public float radius = 0.3f;
|
||||||
/** Controls ambient occlusion's contrast. Must be positive. */
|
/**
|
||||||
|
* Controls ambient occlusion's contrast. Must be positive.
|
||||||
|
*/
|
||||||
public float power = 1.0f;
|
public float power = 1.0f;
|
||||||
/**
|
/**
|
||||||
* Self-occlusion bias in meters. Use to avoid self-occlusion.
|
* Self-occlusion bias in meters. Use to avoid self-occlusion.
|
||||||
* Between 0 and a few mm. No effect when aoType set to GTAO
|
* Between 0 and a few mm. No effect when aoType set to GTAO
|
||||||
*/
|
*/
|
||||||
public float bias = 0.0005f;
|
public float bias = 0.0005f;
|
||||||
/** How each dimension of the AO buffer is scaled. Must be either 0.5 or 1.0. */
|
/**
|
||||||
|
* How each dimension of the AO buffer is scaled. Must be either 0.5 or 1.0.
|
||||||
|
*/
|
||||||
public float resolution = 0.5f;
|
public float resolution = 0.5f;
|
||||||
/** Strength of the Ambient Occlusion effect. */
|
/**
|
||||||
|
* Strength of the Ambient Occlusion effect.
|
||||||
|
*/
|
||||||
public float intensity = 1.0f;
|
public float intensity = 1.0f;
|
||||||
/** depth distance that constitute an edge for filtering */
|
/**
|
||||||
|
* depth distance that constitute an edge for filtering
|
||||||
|
*/
|
||||||
public float bilateralThreshold = 0.05f;
|
public float bilateralThreshold = 0.05f;
|
||||||
/** affects # of samples used for AO and params for filtering */
|
/**
|
||||||
|
* affects # of samples used for AO and params for filtering
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public QualityLevel quality = QualityLevel.LOW;
|
public QualityLevel quality = QualityLevel.LOW;
|
||||||
/** affects AO smoothness. Recommend setting to HIGH when aoType set to GTAO. */
|
/**
|
||||||
|
* affects AO smoothness. Recommend setting to HIGH when aoType set to GTAO.
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public QualityLevel lowPassFilter = QualityLevel.MEDIUM;
|
public QualityLevel lowPassFilter = QualityLevel.MEDIUM;
|
||||||
/** affects AO buffer upsampling quality */
|
/**
|
||||||
|
* affects AO buffer upsampling quality
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public QualityLevel upsampling = QualityLevel.LOW;
|
public QualityLevel upsampling = QualityLevel.LOW;
|
||||||
/** enables or disables screen-space ambient occlusion */
|
/**
|
||||||
|
* enables or disables screen-space ambient occlusion
|
||||||
|
*/
|
||||||
public boolean enabled = false;
|
public boolean enabled = false;
|
||||||
/** enables bent normals computation from AO, and specular AO */
|
/**
|
||||||
|
* enables bent normals computation from AO, and specular AO
|
||||||
|
*/
|
||||||
public boolean bentNormals = false;
|
public boolean bentNormals = false;
|
||||||
/** min angle in radian to consider. No effect when aoType set to GTAO. */
|
/**
|
||||||
|
* min angle in radian to consider. No effect when aoType set to GTAO.
|
||||||
|
*/
|
||||||
public float minHorizonAngleRad = 0.0f;
|
public float minHorizonAngleRad = 0.0f;
|
||||||
/**
|
/**
|
||||||
* Screen Space Cone Tracing (SSCT) options
|
* Screen Space Cone Tracing (SSCT) options
|
||||||
@@ -1930,10 +2006,12 @@ public class View {
|
|||||||
* @see #setMultiSampleAntiAliasingOptions
|
* @see #setMultiSampleAntiAliasingOptions
|
||||||
*/
|
*/
|
||||||
public static class MultiSampleAntiAliasingOptions {
|
public static class MultiSampleAntiAliasingOptions {
|
||||||
/** enables or disables msaa */
|
/**
|
||||||
|
* enables or disables msaa
|
||||||
|
*/
|
||||||
public boolean enabled = false;
|
public boolean enabled = false;
|
||||||
/**
|
/**
|
||||||
* sampleCount number of samples to use for multi-sampled anti-aliasing.<br>
|
* sampleCount number of samples to use for multi-sampled anti-aliasing.\n
|
||||||
* 0: treated as 1
|
* 0: treated as 1
|
||||||
* 1: no anti-aliasing
|
* 1: no anti-aliasing
|
||||||
* n: sample count. Effective sample could be different depending on the
|
* n: sample count. Effective sample could be different depending on the
|
||||||
@@ -1952,75 +2030,106 @@ public class View {
|
|||||||
* shaders to be recompiled. These options should be changed or set during initialization.
|
* shaders to be recompiled. These options should be changed or set during initialization.
|
||||||
* `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time.
|
* `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time.
|
||||||
*
|
*
|
||||||
* <p><code>feedback</code> of 0.1 effectively accumulates a maximum of 19 samples in steady state.
|
* `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state.
|
||||||
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.</p>
|
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.
|
||||||
*
|
*
|
||||||
* @see #setTemporalAntiAliasingOptions
|
* @see #setTemporalAntiAliasingOptions
|
||||||
*/
|
*/
|
||||||
public static class TemporalAntiAliasingOptions {
|
public static class TemporalAntiAliasingOptions {
|
||||||
public enum BoxType {
|
public enum BoxType {
|
||||||
/** use an AABB neighborhood */
|
/**
|
||||||
|
* use an AABB neighborhood
|
||||||
|
*/
|
||||||
AABB,
|
AABB,
|
||||||
/** use both AABB and variance */
|
/**
|
||||||
|
* use both AABB and variance
|
||||||
|
*/
|
||||||
AABB_VARIANCE,
|
AABB_VARIANCE,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BoxClipping {
|
public enum BoxClipping {
|
||||||
/** Accurate box clipping */
|
/**
|
||||||
|
* Accurate box clipping
|
||||||
|
*/
|
||||||
ACCURATE,
|
ACCURATE,
|
||||||
/** clamping */
|
/**
|
||||||
|
* clamping
|
||||||
|
*/
|
||||||
CLAMP,
|
CLAMP,
|
||||||
/** no rejections (use for debugging) */
|
/**
|
||||||
|
* no rejections (use for debugging)
|
||||||
|
*/
|
||||||
NONE,
|
NONE,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum JitterPattern {
|
public enum JitterPattern {
|
||||||
/** 4-samples, rotated grid sampling */
|
|
||||||
RGSS_X4,
|
RGSS_X4,
|
||||||
/** 4-samples, uniform grid in helix sequence */
|
|
||||||
UNIFORM_HELIX_X4,
|
UNIFORM_HELIX_X4,
|
||||||
/** 8-samples of halton 2,3 */
|
|
||||||
HALTON_23_X8,
|
HALTON_23_X8,
|
||||||
/** 16-samples of halton 2,3 */
|
|
||||||
HALTON_23_X16,
|
HALTON_23_X16,
|
||||||
/** 32-samples of halton 2,3 */
|
|
||||||
HALTON_23_X32,
|
HALTON_23_X32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated has no effect. */
|
/**
|
||||||
|
* @deprecated has no effect.
|
||||||
|
*/
|
||||||
public float filterWidth = 1.0f;
|
public float filterWidth = 1.0f;
|
||||||
/** history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA). */
|
/**
|
||||||
|
* history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA).
|
||||||
|
*/
|
||||||
public float feedback = 0.12f;
|
public float feedback = 0.12f;
|
||||||
/** texturing lod bias (typically -1 or -2) */
|
/**
|
||||||
|
* texturing lod bias (typically -1 or -2)
|
||||||
|
*/
|
||||||
public float lodBias = -1.0f;
|
public float lodBias = -1.0f;
|
||||||
/** post-TAA sharpen, especially useful when upscaling is true. */
|
/**
|
||||||
|
* post-TAA sharpen, especially useful when upscaling is true.
|
||||||
|
*/
|
||||||
public float sharpness = 0.0f;
|
public float sharpness = 0.0f;
|
||||||
/** enables or disables temporal anti-aliasing */
|
/**
|
||||||
|
* enables or disables temporal anti-aliasing
|
||||||
|
*/
|
||||||
public boolean enabled = false;
|
public boolean enabled = false;
|
||||||
/** Upscaling factor. Disables Dynamic Resolution. [BETA] */
|
/**
|
||||||
|
* Upscaling factor. Disables Dynamic Resolution. [BETA]
|
||||||
|
*/
|
||||||
public float upscaling = 1.0f;
|
public float upscaling = 1.0f;
|
||||||
/** whether to filter the history buffer */
|
/**
|
||||||
|
* whether to filter the history buffer
|
||||||
|
*/
|
||||||
public boolean filterHistory = true;
|
public boolean filterHistory = true;
|
||||||
/** whether to apply the reconstruction filter to the input */
|
/**
|
||||||
|
* whether to apply the reconstruction filter to the input
|
||||||
|
*/
|
||||||
public boolean filterInput = true;
|
public boolean filterInput = true;
|
||||||
/** whether to use the YcoCg color-space for history rejection */
|
/**
|
||||||
|
* whether to use the YcoCg color-space for history rejection
|
||||||
|
*/
|
||||||
public boolean useYCoCg = false;
|
public boolean useYCoCg = false;
|
||||||
/** set to true for HDR content */
|
/**
|
||||||
|
* set to true for HDR content
|
||||||
|
*/
|
||||||
public boolean hdr = true;
|
public boolean hdr = true;
|
||||||
/** type of color gamut box */
|
/**
|
||||||
|
* type of color gamut box
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public TemporalAntiAliasingOptions.BoxType boxType = TemporalAntiAliasingOptions.BoxType.AABB;
|
public TemporalAntiAliasingOptions.BoxType boxType = TemporalAntiAliasingOptions.BoxType.AABB;
|
||||||
/** clipping algorithm */
|
/**
|
||||||
|
* clipping algorithm
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public TemporalAntiAliasingOptions.BoxClipping boxClipping = TemporalAntiAliasingOptions.BoxClipping.ACCURATE;
|
public TemporalAntiAliasingOptions.BoxClipping boxClipping = TemporalAntiAliasingOptions.BoxClipping.ACCURATE;
|
||||||
/** Jitter Pattern */
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public TemporalAntiAliasingOptions.JitterPattern jitterPattern = TemporalAntiAliasingOptions.JitterPattern.HALTON_23_X16;
|
public TemporalAntiAliasingOptions.JitterPattern jitterPattern = TemporalAntiAliasingOptions.JitterPattern.HALTON_23_X16;
|
||||||
/** High values increases ghosting artefact, lower values increases jittering, range [0.75, 1.25] */
|
|
||||||
public float varianceGamma = 1.0f;
|
public float varianceGamma = 1.0f;
|
||||||
/** adjust the feedback dynamically to reduce flickering */
|
/**
|
||||||
|
* adjust the feedback dynamically to reduce flickering
|
||||||
|
*/
|
||||||
public boolean preventFlickering = false;
|
public boolean preventFlickering = false;
|
||||||
/** whether to apply history reprojection (debug option) */
|
/**
|
||||||
|
* whether to apply history reprojection (debug option)
|
||||||
|
*/
|
||||||
public boolean historyReprojection = true;
|
public boolean historyReprojection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2029,22 +2138,30 @@ public class View {
|
|||||||
* @see #setScreenSpaceReflectionsOptions
|
* @see #setScreenSpaceReflectionsOptions
|
||||||
*/
|
*/
|
||||||
public static class ScreenSpaceReflectionsOptions {
|
public static class ScreenSpaceReflectionsOptions {
|
||||||
/** ray thickness, in world units */
|
/**
|
||||||
|
* ray thickness, in world units
|
||||||
|
*/
|
||||||
public float thickness = 0.1f;
|
public float thickness = 0.1f;
|
||||||
/** bias, in world units, to prevent self-intersections */
|
/**
|
||||||
|
* bias, in world units, to prevent self-intersections
|
||||||
|
*/
|
||||||
public float bias = 0.01f;
|
public float bias = 0.01f;
|
||||||
/** maximum distance, in world units, to raycast */
|
/**
|
||||||
|
* maximum distance, in world units, to raycast
|
||||||
|
*/
|
||||||
public float maxDistance = 3.0f;
|
public float maxDistance = 3.0f;
|
||||||
/** stride, in texels, for samples along the ray. */
|
/**
|
||||||
|
* stride, in texels, for samples along the ray.
|
||||||
|
*/
|
||||||
public float stride = 2.0f;
|
public float stride = 2.0f;
|
||||||
public boolean enabled = false;
|
public boolean enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for the screen-space guard band.
|
* Options for the screen-space guard band.
|
||||||
* <p>A guard band can be enabled to avoid some artifacts towards the edge of the screen when
|
* A guard band can be enabled to avoid some artifacts towards the edge of the screen when
|
||||||
* using screen-space effects such as SSAO. Enabling the guard band reduces performance slightly.
|
* using screen-space effects such as SSAO. Enabling the guard band reduces performance slightly.
|
||||||
* Currently the guard band can only be enabled or disabled.</p>
|
* Currently the guard band can only be enabled or disabled.
|
||||||
*/
|
*/
|
||||||
public static class GuardBandOptions {
|
public static class GuardBandOptions {
|
||||||
public boolean enabled = false;
|
public boolean enabled = false;
|
||||||
@@ -2057,9 +2174,13 @@ public class View {
|
|||||||
* @see #setSampleCount
|
* @see #setSampleCount
|
||||||
*/
|
*/
|
||||||
public enum AntiAliasing {
|
public enum AntiAliasing {
|
||||||
/** no anti aliasing performed as part of post-processing */
|
/**
|
||||||
|
* no anti aliasing performed as part of post-processing
|
||||||
|
*/
|
||||||
NONE,
|
NONE,
|
||||||
/** FXAA is a low-quality but very efficient type of anti-aliasing. (default). */
|
/**
|
||||||
|
* FXAA is a low-quality but very efficient type of anti-aliasing. (default).
|
||||||
|
*/
|
||||||
FXAA,
|
FXAA,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2067,9 +2188,13 @@ public class View {
|
|||||||
* List of available post-processing dithering techniques.
|
* List of available post-processing dithering techniques.
|
||||||
*/
|
*/
|
||||||
public enum Dithering {
|
public enum Dithering {
|
||||||
/** No dithering */
|
/**
|
||||||
|
* No dithering
|
||||||
|
*/
|
||||||
NONE,
|
NONE,
|
||||||
/** Temporal dithering (default) */
|
/**
|
||||||
|
* Temporal dithering (default)
|
||||||
|
*/
|
||||||
TEMPORAL,
|
TEMPORAL,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2078,13 +2203,21 @@ public class View {
|
|||||||
* @see #setShadowType
|
* @see #setShadowType
|
||||||
*/
|
*/
|
||||||
public enum ShadowType {
|
public enum ShadowType {
|
||||||
/** percentage-closer filtered shadows (default) */
|
/**
|
||||||
|
* percentage-closer filtered shadows (default)
|
||||||
|
*/
|
||||||
PCF,
|
PCF,
|
||||||
/** variance shadows */
|
/**
|
||||||
|
* variance shadows
|
||||||
|
*/
|
||||||
VSM,
|
VSM,
|
||||||
/** PCF with contact hardening simulation */
|
/**
|
||||||
|
* PCF with contact hardening simulation
|
||||||
|
*/
|
||||||
DPCF,
|
DPCF,
|
||||||
/** PCF with soft shadows and contact hardening */
|
/**
|
||||||
|
* PCF with soft shadows and contact hardening
|
||||||
|
*/
|
||||||
PCSS,
|
PCSS,
|
||||||
PCFd,
|
PCFd,
|
||||||
}
|
}
|
||||||
@@ -2092,14 +2225,14 @@ public class View {
|
|||||||
/**
|
/**
|
||||||
* View-level options for VSM Shadowing.
|
* View-level options for VSM Shadowing.
|
||||||
* @see #setVsmShadowOptions
|
* @see #setVsmShadowOptions
|
||||||
* <b>Warning:</b> This API is still experimental and subject to change.
|
* @warning This API is still experimental and subject to change.
|
||||||
*/
|
*/
|
||||||
public static class VsmShadowOptions {
|
public static class VsmShadowOptions {
|
||||||
/**
|
/**
|
||||||
* Sets the number of anisotropic samples to use when sampling a VSM shadow map. If greater
|
* Sets the number of anisotropic samples to use when sampling a VSM shadow map. If greater
|
||||||
* than 0, mipmaps will automatically be generated each frame for all lights.
|
* than 0, mipmaps will automatically be generated each frame for all lights.
|
||||||
*
|
*
|
||||||
* <p>The number of anisotropic samples = 2 ^ vsmAnisotropy.</p>
|
* The number of anisotropic samples = 2 ^ vsmAnisotropy.
|
||||||
*/
|
*/
|
||||||
public int anisotropy = 0;
|
public int anisotropy = 0;
|
||||||
/**
|
/**
|
||||||
@@ -2133,7 +2266,7 @@ public class View {
|
|||||||
/**
|
/**
|
||||||
* View-level options for DPCF and PCSS Shadowing.
|
* View-level options for DPCF and PCSS Shadowing.
|
||||||
* @see #setSoftShadowOptions
|
* @see #setSoftShadowOptions
|
||||||
* <b>Warning:</b> This API is still experimental and subject to change.
|
* @warning This API is still experimental and subject to change.
|
||||||
*/
|
*/
|
||||||
public static class SoftShadowOptions {
|
public static class SoftShadowOptions {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
|
|||||||
def tools = ['matc', 'cmgen']
|
def tools = ['matc', 'cmgen']
|
||||||
|
|
||||||
def platforms = [
|
def platforms = [
|
||||||
'mac': [classifier: 'osx-aarch_64', archive: "filament-v${VERSION_NAME}-mac.tgz", path: { t -> "filament/bin/${t}" }],
|
'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}" }],
|
'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" }]
|
'windows': [classifier: 'windows-x86_64', archive: "filament-v${VERSION_NAME}-windows.tgz", path: { t -> "bin/${t}.exe" }]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ add_library(image STATIC IMPORTED)
|
|||||||
set_target_properties(image PROPERTIES IMPORTED_LOCATION
|
set_target_properties(image PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimage.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimage.a)
|
||||||
|
|
||||||
add_library(imageio-lite STATIC IMPORTED)
|
|
||||||
set_target_properties(imageio-lite PROPERTIES IMPORTED_LOCATION
|
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimageio-lite.a)
|
|
||||||
|
|
||||||
add_library(ktxreader STATIC IMPORTED)
|
add_library(ktxreader STATIC IMPORTED)
|
||||||
set_target_properties(ktxreader PROPERTIES IMPORTED_LOCATION
|
set_target_properties(ktxreader PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libktxreader.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libktxreader.a)
|
||||||
@@ -34,10 +30,6 @@ add_library(viewer STATIC IMPORTED)
|
|||||||
set_target_properties(viewer PROPERTIES IMPORTED_LOCATION
|
set_target_properties(viewer PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libviewer.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libviewer.a)
|
||||||
|
|
||||||
add_library(imagediff STATIC IMPORTED)
|
|
||||||
set_target_properties(imagediff PROPERTIES IMPORTED_LOCATION
|
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimagediff.a)
|
|
||||||
|
|
||||||
add_library(civetweb STATIC IMPORTED)
|
add_library(civetweb STATIC IMPORTED)
|
||||||
set_target_properties(civetweb PROPERTIES IMPORTED_LOCATION
|
set_target_properties(civetweb PROPERTIES IMPORTED_LOCATION
|
||||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libcivetweb.a)
|
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libcivetweb.a)
|
||||||
@@ -65,7 +57,6 @@ add_library(filament-utils-jni SHARED
|
|||||||
src/main/cpp/IBLPrefilterContext.cpp
|
src/main/cpp/IBLPrefilterContext.cpp
|
||||||
src/main/cpp/Utils.cpp
|
src/main/cpp/Utils.cpp
|
||||||
src/main/cpp/Manipulator.cpp
|
src/main/cpp/Manipulator.cpp
|
||||||
src/main/cpp/ImageDiff.cpp
|
|
||||||
src/main/cpp/RemoteServer.cpp
|
src/main/cpp/RemoteServer.cpp
|
||||||
|
|
||||||
${IMAGEIO_DIR}/include/imageio/ImageDecoder.h
|
${IMAGEIO_DIR}/include/imageio/ImageDecoder.h
|
||||||
@@ -83,7 +74,6 @@ target_include_directories(filament-utils-jni PRIVATE
|
|||||||
${FILAMENT_DIR}/include
|
${FILAMENT_DIR}/include
|
||||||
../../filament/backend/include
|
../../filament/backend/include
|
||||||
${IMAGEIO_DIR}/include
|
${IMAGEIO_DIR}/include
|
||||||
../../libs/imagediff/include
|
|
||||||
../../libs/utils/include)
|
../../libs/utils/include)
|
||||||
|
|
||||||
set_target_properties(filament-utils-jni PROPERTIES LINK_DEPENDS ${VERSION_SCRIPT})
|
set_target_properties(filament-utils-jni PROPERTIES LINK_DEPENDS ${VERSION_SCRIPT})
|
||||||
@@ -95,13 +85,10 @@ target_link_libraries(filament-utils-jni
|
|||||||
PRIVATE camutils
|
PRIVATE camutils
|
||||||
PRIVATE iblprefilter
|
PRIVATE iblprefilter
|
||||||
PRIVATE image
|
PRIVATE image
|
||||||
PRIVATE imageio-lite
|
|
||||||
PRIVATE filament-jni
|
PRIVATE filament-jni
|
||||||
PRIVATE ktxreader
|
PRIVATE ktxreader
|
||||||
PRIVATE viewer
|
PRIVATE viewer
|
||||||
PRIVATE imagediff
|
|
||||||
PRIVATE log
|
PRIVATE log
|
||||||
PRIVATE utils
|
PRIVATE utils
|
||||||
PRIVATE perfetto # needed only when FILAMENT_ENABLE_PERFETTO is defined
|
PRIVATE perfetto # needed only when FILAMENT_ENABLE_PERFETTO is defined
|
||||||
PRIVATE jnigraphics # needed for AndroidBitmap_* functions in ImageDiff
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -159,50 +159,37 @@ extern "C" JNIEXPORT void JNICALL
|
|||||||
Java_com_google_android_filament_utils_AutomationEngine_nGetViewerOptions(JNIEnv* env, jclass,
|
Java_com_google_android_filament_utils_AutomationEngine_nGetViewerOptions(JNIEnv* env, jclass,
|
||||||
jlong nativeObject, jobject result) {
|
jlong nativeObject, jobject result) {
|
||||||
AutomationEngine* automation = (AutomationEngine*) nativeObject;
|
AutomationEngine* automation = (AutomationEngine*) nativeObject;
|
||||||
const auto& settings = automation->getSettings();
|
auto options = automation->getViewerOptions();
|
||||||
const auto& options = settings.viewer;
|
|
||||||
|
|
||||||
const jclass klass = env->GetObjectClass(result);
|
const jclass klass = env->GetObjectClass(result);
|
||||||
|
|
||||||
|
const jfieldID cameraAperture = env->GetFieldID(klass, "cameraAperture", "F");
|
||||||
|
const jfieldID cameraSpeed = env->GetFieldID(klass, "cameraSpeed", "F");
|
||||||
|
const jfieldID cameraISO = env->GetFieldID(klass, "cameraISO", "F");
|
||||||
|
const jfieldID cameraNear = env->GetFieldID(klass, "cameraNear", "F");
|
||||||
|
const jfieldID cameraFar = env->GetFieldID(klass, "cameraFar", "F");
|
||||||
const jfieldID groundShadowStrength = env->GetFieldID(klass, "groundShadowStrength", "F");
|
const jfieldID groundShadowStrength = env->GetFieldID(klass, "groundShadowStrength", "F");
|
||||||
const jfieldID groundPlaneEnabled = env->GetFieldID(klass, "groundPlaneEnabled", "Z");
|
const jfieldID groundPlaneEnabled = env->GetFieldID(klass, "groundPlaneEnabled", "Z");
|
||||||
const jfieldID skyboxEnabled = env->GetFieldID(klass, "skyboxEnabled", "Z");
|
const jfieldID skyboxEnabled = env->GetFieldID(klass, "skyboxEnabled", "Z");
|
||||||
|
const jfieldID cameraFocalLength = env->GetFieldID(klass, "cameraFocalLength", "F");
|
||||||
|
const jfieldID cameraFocusDistance = env->GetFieldID(klass, "cameraFocusDistance", "F");
|
||||||
const jfieldID autoScaleEnabled = env->GetFieldID(klass, "autoScaleEnabled", "Z");
|
const jfieldID autoScaleEnabled = env->GetFieldID(klass, "autoScaleEnabled", "Z");
|
||||||
const jfieldID autoInstancingEnabled = env->GetFieldID(klass, "autoInstancingEnabled", "Z");
|
const jfieldID autoInstancingEnabled = env->GetFieldID(klass, "autoInstancingEnabled", "Z");
|
||||||
|
|
||||||
|
env->SetFloatField(result, cameraAperture, options.cameraAperture);
|
||||||
|
env->SetFloatField(result, cameraSpeed, options.cameraSpeed);
|
||||||
|
env->SetFloatField(result, cameraISO, options.cameraISO);
|
||||||
|
env->SetFloatField(result, cameraNear, options.cameraNear);
|
||||||
|
env->SetFloatField(result, cameraFar, options.cameraFar);
|
||||||
env->SetFloatField(result, groundShadowStrength, options.groundShadowStrength);
|
env->SetFloatField(result, groundShadowStrength, options.groundShadowStrength);
|
||||||
env->SetBooleanField(result, groundPlaneEnabled, options.groundPlaneEnabled);
|
env->SetBooleanField(result, groundPlaneEnabled, options.groundPlaneEnabled);
|
||||||
env->SetBooleanField(result, skyboxEnabled, options.skyboxEnabled);
|
env->SetBooleanField(result, skyboxEnabled, options.skyboxEnabled);
|
||||||
|
env->SetFloatField(result, cameraFocalLength, options.cameraFocalLength);
|
||||||
|
env->SetFloatField(result, cameraFocusDistance, options.cameraFocusDistance);
|
||||||
env->SetBooleanField(result, autoScaleEnabled, options.autoScaleEnabled);
|
env->SetBooleanField(result, autoScaleEnabled, options.autoScaleEnabled);
|
||||||
env->SetBooleanField(result, autoInstancingEnabled, options.autoInstancingEnabled);
|
env->SetBooleanField(result, autoInstancingEnabled, options.autoInstancingEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_com_google_android_filament_utils_AutomationEngine_nGetCameraSettings(JNIEnv* env, jclass,
|
|
||||||
jlong nativeObject, jobject result) {
|
|
||||||
AutomationEngine* automation = (AutomationEngine*) nativeObject;
|
|
||||||
const auto& settings = automation->getSettings();
|
|
||||||
const auto& camera = settings.camera;
|
|
||||||
|
|
||||||
const jclass klass = env->GetObjectClass(result);
|
|
||||||
|
|
||||||
const jfieldID aperture = env->GetFieldID(klass, "aperture", "F");
|
|
||||||
const jfieldID shutterSpeed = env->GetFieldID(klass, "shutterSpeed", "F");
|
|
||||||
const jfieldID sensitivity = env->GetFieldID(klass, "sensitivity", "F");
|
|
||||||
const jfieldID near = env->GetFieldID(klass, "near", "F");
|
|
||||||
const jfieldID far = env->GetFieldID(klass, "far", "F");
|
|
||||||
const jfieldID focalLength = env->GetFieldID(klass, "focalLength", "F");
|
|
||||||
const jfieldID focusDistance = env->GetFieldID(klass, "focusDistance", "F");
|
|
||||||
|
|
||||||
env->SetFloatField(result, aperture, camera.aperture);
|
|
||||||
env->SetFloatField(result, shutterSpeed, camera.shutterSpeed);
|
|
||||||
env->SetFloatField(result, sensitivity, camera.sensitivity);
|
|
||||||
env->SetFloatField(result, near, camera.near);
|
|
||||||
env->SetFloatField(result, far, camera.far);
|
|
||||||
env->SetFloatField(result, focalLength, camera.focalLength);
|
|
||||||
env->SetFloatField(result, focusDistance, camera.focusDistance);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT jlong JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_utils_AutomationEngine_nGetColorGrading(JNIEnv*, jclass,
|
Java_com_google_android_filament_utils_AutomationEngine_nGetColorGrading(JNIEnv*, jclass,
|
||||||
jlong nativeObject, jlong nativeEngine) {
|
jlong nativeObject, jlong nativeEngine) {
|
||||||
@@ -228,18 +215,6 @@ Java_com_google_android_filament_utils_AutomationEngine_nShouldClose(JNIEnv*, jc
|
|||||||
return automation->shouldClose();
|
return automation->shouldClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
|
||||||
Java_com_google_android_filament_utils_AutomationEngine_nGetTestCount(JNIEnv*, jclass, jlong native) {
|
|
||||||
AutomationEngine* automation = (AutomationEngine*) native;
|
|
||||||
return (jint) automation->testCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
|
||||||
Java_com_google_android_filament_utils_AutomationEngine_nGetCurrentTest(JNIEnv*, jclass, jlong native) {
|
|
||||||
AutomationEngine* automation = (AutomationEngine*) native;
|
|
||||||
return (jint) automation->currentTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_utils_AutomationEngine_nDestroy(JNIEnv*, jclass, jlong native) {
|
Java_com_google_android_filament_utils_AutomationEngine_nDestroy(JNIEnv*, jclass, jlong native) {
|
||||||
AutomationEngine* automation = (AutomationEngine*) native;
|
AutomationEngine* automation = (AutomationEngine*) native;
|
||||||
|
|||||||
@@ -1,217 +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 <jni.h>
|
|
||||||
#include <android/bitmap.h>
|
|
||||||
|
|
||||||
#include <imagediff/ImageDiff.h>
|
|
||||||
#include <utils/Log.h>
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
using namespace imagediff;
|
|
||||||
using namespace utils;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
struct BitmapLock {
|
|
||||||
JNIEnv* env;
|
|
||||||
jobject bitmap;
|
|
||||||
void* pixels;
|
|
||||||
AndroidBitmapInfo info;
|
|
||||||
|
|
||||||
BitmapLock(JNIEnv* env, jobject bitmap) : env(env), bitmap(bitmap), pixels(nullptr) {
|
|
||||||
if (!bitmap) return;
|
|
||||||
if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) {
|
|
||||||
pixels = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~BitmapLock() {
|
|
||||||
if (pixels) {
|
|
||||||
AndroidBitmap_unlockPixels(env, bitmap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValid() const { return pixels != nullptr; }
|
|
||||||
|
|
||||||
imagediff::Bitmap toBitmap() const {
|
|
||||||
return {
|
|
||||||
.width = (uint32_t) info.width,
|
|
||||||
.height = (uint32_t) info.height,
|
|
||||||
.stride = (size_t) info.stride,
|
|
||||||
.data = pixels
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Helper to convert C++ ImageDiffResult to Java Result
|
|
||||||
jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDiff) {
|
|
||||||
// Create Result class/objects
|
|
||||||
jclass resultClass = env->FindClass("com/google/android/filament/utils/ImageDiff$Result");
|
|
||||||
jmethodID resultCtor = env->GetMethodID(resultClass, "<init>", "()V");
|
|
||||||
jobject resultObj = env->NewObject(resultClass, resultCtor);
|
|
||||||
jfieldID statusField = env->GetFieldID(resultClass, "status", "Lcom/google/android/filament/utils/ImageDiff$Result$Status;");
|
|
||||||
jfieldID failingCountField = env->GetFieldID(resultClass, "failingPixelCount", "J");
|
|
||||||
jfieldID maxDiffField = env->GetFieldID(resultClass, "maxDiffFound", "[F");
|
|
||||||
jfieldID diffImageField = env->GetFieldID(resultClass, "diffImage", "Landroid/graphics/Bitmap;");
|
|
||||||
|
|
||||||
// Map Status enum
|
|
||||||
jclass statusEnum = env->FindClass("com/google/android/filament/utils/ImageDiff$Result$Status");
|
|
||||||
jobject statusObj = nullptr;
|
|
||||||
jfieldID enumField = nullptr;
|
|
||||||
switch (result.status) {
|
|
||||||
case ImageDiffResult::Status::PASSED:
|
|
||||||
enumField = env->GetStaticFieldID(statusEnum, "PASSED", "Lcom/google/android/filament/utils/ImageDiff$Result$Status;");
|
|
||||||
break;
|
|
||||||
case ImageDiffResult::Status::SIZE_MISMATCH:
|
|
||||||
enumField = env->GetStaticFieldID(statusEnum, "SIZE_MISMATCH", "Lcom/google/android/filament/utils/ImageDiff$Result$Status;");
|
|
||||||
break;
|
|
||||||
case ImageDiffResult::Status::PIXEL_DIFFERENCE:
|
|
||||||
enumField = env->GetStaticFieldID(statusEnum, "PIXEL_DIFFERENCE", "Lcom/google/android/filament/utils/ImageDiff$Result$Status;");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
statusObj = env->GetStaticObjectField(statusEnum, enumField);
|
|
||||||
env->SetObjectField(resultObj, statusField, statusObj);
|
|
||||||
|
|
||||||
env->SetLongField(resultObj, failingCountField, (jlong) result.failingPixelCount);
|
|
||||||
|
|
||||||
jfloatArray maxDiffArray = env->NewFloatArray(4);
|
|
||||||
env->SetFloatArrayRegion(maxDiffArray, 0, 4, result.maxDiffFound);
|
|
||||||
env->SetObjectField(resultObj, maxDiffField, maxDiffArray);
|
|
||||||
|
|
||||||
if (generateDiff && result.diffImage.getWidth() > 0) {
|
|
||||||
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
|
|
||||||
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
|
|
||||||
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
|
||||||
|
|
||||||
jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
|
|
||||||
jfieldID argb8888 = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
|
||||||
jobject configObj = env->GetStaticObjectField(configClass, argb8888);
|
|
||||||
|
|
||||||
uint32_t width = result.diffImage.getWidth();
|
|
||||||
uint32_t height = result.diffImage.getHeight();
|
|
||||||
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint)width, (jint)height, configObj);
|
|
||||||
|
|
||||||
if (diffBitmap) {
|
|
||||||
void* diffPixels;
|
|
||||||
if (AndroidBitmap_lockPixels(env, diffBitmap, &diffPixels) == 0) {
|
|
||||||
float const* src = result.diffImage.getPixelRef();
|
|
||||||
uint8_t* dst = (uint8_t*) diffPixels;
|
|
||||||
uint32_t channels = result.diffImage.getChannels(); // usually 4
|
|
||||||
|
|
||||||
for (size_t i = 0; i < width * height; ++i) {
|
|
||||||
for (int c = 0; c < 4; ++c) {
|
|
||||||
float v = 0.0f;
|
|
||||||
if (c < channels) v = src[i * channels + c];
|
|
||||||
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
|
|
||||||
dst[i * 4 + c] = (uint8_t) std::min(255.0f, std::max(0.0f, v * 255.0f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AndroidBitmap_unlockPixels(env, diffBitmap);
|
|
||||||
env->SetObjectField(resultObj, diffImageField, diffBitmap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT jobject JNICALL
|
|
||||||
Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jclass,
|
|
||||||
jobject refBitmap, jobject candBitmap, jint mode, jint swizzle, jint channelMask,
|
|
||||||
jfloat maxAbsDiff, jfloat maxFailingPixelsFraction, jobject maskBitmap) {
|
|
||||||
|
|
||||||
BitmapLock refArg(env, refBitmap);
|
|
||||||
BitmapLock candArg(env, candBitmap);
|
|
||||||
BitmapLock maskArg(env, maskBitmap);
|
|
||||||
|
|
||||||
if (!refArg.isValid() || !candArg.isValid()) {
|
|
||||||
ImageDiffResult emptyResult;
|
|
||||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
|
||||||
return createResult(env, emptyResult, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageDiffConfig config;
|
|
||||||
config.mode = (ImageDiffConfig::Mode) mode;
|
|
||||||
config.swizzle = (ImageDiffConfig::Swizzle) swizzle;
|
|
||||||
config.channelMask = (uint8_t) channelMask;
|
|
||||||
config.maxAbsDiff = maxAbsDiff;
|
|
||||||
config.maxFailingPixelsFraction = maxFailingPixelsFraction;
|
|
||||||
|
|
||||||
imagediff::Bitmap const* maskPtr = nullptr;
|
|
||||||
imagediff::Bitmap maskVal;
|
|
||||||
if (maskBitmap && maskArg.isValid()) {
|
|
||||||
maskVal = maskArg.toBitmap();
|
|
||||||
maskPtr = &maskVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool generateDiff = true;
|
|
||||||
ImageDiffResult result = compare(refArg.toBitmap(), candArg.toBitmap(), config, maskPtr, generateDiff);
|
|
||||||
|
|
||||||
return createResult(env, result, generateDiff);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT jobject JNICALL
|
|
||||||
Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclass,
|
|
||||||
jobject refBitmap, jobject candBitmap, jstring jsonConfig, jobject maskBitmap) {
|
|
||||||
|
|
||||||
BitmapLock refArg(env, refBitmap);
|
|
||||||
BitmapLock candArg(env, candBitmap);
|
|
||||||
BitmapLock maskArg(env, maskBitmap);
|
|
||||||
|
|
||||||
if (!refArg.isValid() || !candArg.isValid()) {
|
|
||||||
ImageDiffResult emptyResult;
|
|
||||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
|
||||||
return createResult(env, emptyResult, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageDiffConfig config;
|
|
||||||
const char* nativeJson = env->GetStringUTFChars(jsonConfig, 0);
|
|
||||||
size_t length = env->GetStringUTFLength(jsonConfig);
|
|
||||||
|
|
||||||
bool parsed = parseConfig(nativeJson, length, &config);
|
|
||||||
env->ReleaseStringUTFChars(jsonConfig, nativeJson);
|
|
||||||
|
|
||||||
if (!parsed) {
|
|
||||||
// Fallback to default or error?
|
|
||||||
// We could log error.
|
|
||||||
utils::slog.e << "ImageDiff JNI: Failed to parse JSON config" << utils::io::endl;
|
|
||||||
ImageDiffResult errResult;
|
|
||||||
errResult.status = ImageDiffResult::Status::PIXEL_DIFFERENCE; // assume fail
|
|
||||||
return createResult(env, errResult, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
imagediff::Bitmap const* maskPtr = nullptr;
|
|
||||||
imagediff::Bitmap maskVal;
|
|
||||||
if (maskBitmap && maskArg.isValid()) {
|
|
||||||
maskVal = maskArg.toBitmap();
|
|
||||||
maskPtr = &maskVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool generateDiff = true;
|
|
||||||
ImageDiffResult result = compare(refArg.toBitmap(), candArg.toBitmap(), config, maskPtr, generateDiff);
|
|
||||||
|
|
||||||
return createResult(env, result, generateDiff);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -94,23 +94,20 @@ public class AutomationEngine {
|
|||||||
* Allows remote control for the viewer.
|
* Allows remote control for the viewer.
|
||||||
*/
|
*/
|
||||||
public static class ViewerOptions {
|
public static class ViewerOptions {
|
||||||
|
public float cameraAperture = 16.0f;
|
||||||
|
public float cameraSpeed = 125.0f;
|
||||||
|
public float cameraISO = 100.0f;
|
||||||
|
public float cameraNear = 0.1f;
|
||||||
|
public float cameraFar = 100.0f;
|
||||||
public float groundShadowStrength = 0.75f;
|
public float groundShadowStrength = 0.75f;
|
||||||
public boolean groundPlaneEnabled = false;
|
public boolean groundPlaneEnabled = false;
|
||||||
public boolean skyboxEnabled = true;
|
public boolean skyboxEnabled = true;
|
||||||
|
public float cameraFocalLength = 28.0f;
|
||||||
|
public float cameraFocusDistance = 0.0f;
|
||||||
public boolean autoScaleEnabled = true;
|
public boolean autoScaleEnabled = true;
|
||||||
public boolean autoInstancingEnabled = false;
|
public boolean autoInstancingEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CameraSettings {
|
|
||||||
public float aperture = 16.0f;
|
|
||||||
public float shutterSpeed = 125.0f;
|
|
||||||
public float sensitivity = 100.0f;
|
|
||||||
public float near = 0.1f;
|
|
||||||
public float far = 100.0f;
|
|
||||||
public float focalLength = 28.0f;
|
|
||||||
public float focusDistance = 10.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an automation engine from a JSON specification.
|
* Creates an automation engine from a JSON specification.
|
||||||
*
|
*
|
||||||
@@ -232,13 +229,6 @@ public class AutomationEngine {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public CameraSettings getCameraSettings() {
|
|
||||||
CameraSettings result = new CameraSettings();
|
|
||||||
nGetCameraSettings(mNativeObject, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a color grading object that corresponds to the latest settings.
|
* Gets a color grading object that corresponds to the latest settings.
|
||||||
*
|
*
|
||||||
@@ -271,9 +261,6 @@ public class AutomationEngine {
|
|||||||
*/
|
*/
|
||||||
public boolean shouldClose() { return nShouldClose(mNativeObject); }
|
public boolean shouldClose() { return nShouldClose(mNativeObject); }
|
||||||
|
|
||||||
public int getTestCount() { return nGetTestCount(mNativeObject); }
|
|
||||||
public int getCurrentTest() { return nGetCurrentTest(mNativeObject); }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void finalize() throws Throwable {
|
protected void finalize() throws Throwable {
|
||||||
nDestroy(mNativeObject);
|
nDestroy(mNativeObject);
|
||||||
@@ -293,12 +280,9 @@ public class AutomationEngine {
|
|||||||
long[] materials, long ibl, int sunlight, int[] assetLights, long lightManager,
|
long[] materials, long ibl, int sunlight, int[] assetLights, long lightManager,
|
||||||
long scene, long renderer);
|
long scene, long renderer);
|
||||||
private static native void nGetViewerOptions(long nativeObject, Object result);
|
private static native void nGetViewerOptions(long nativeObject, Object result);
|
||||||
private static native void nGetCameraSettings(long nativeObject, Object result);
|
|
||||||
private static native long nGetColorGrading(long nativeObject, long nativeEngine);
|
private static native long nGetColorGrading(long nativeObject, long nativeEngine);
|
||||||
private static native void nSignalBatchMode(long nativeObject);
|
private static native void nSignalBatchMode(long nativeObject);
|
||||||
private static native void nStopRunning(long nativeObject);
|
private static native void nStopRunning(long nativeObject);
|
||||||
private static native boolean nShouldClose(long nativeObject);
|
private static native boolean nShouldClose(long nativeObject);
|
||||||
private static native int nGetTestCount(long nativeObject);
|
|
||||||
private static native int nGetCurrentTest(long nativeObject);
|
|
||||||
private static native void nDestroy(long nativeObject);
|
private static native void nDestroy(long nativeObject);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,91 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.filament.utils;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
|
|
||||||
public class ImageDiff {
|
|
||||||
public enum Mode {
|
|
||||||
LEAF, AND, OR
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Swizzle {
|
|
||||||
RGBA, BGRA
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Config {
|
|
||||||
@NonNull
|
|
||||||
public Mode mode = Mode.LEAF;
|
|
||||||
@NonNull
|
|
||||||
public Swizzle swizzle = Swizzle.RGBA;
|
|
||||||
public int channelMask = 0xF;
|
|
||||||
public float maxAbsDiff = 0.0f;
|
|
||||||
public float maxFailingPixelsFraction = 0.0f;
|
|
||||||
// Children not supported in this simple wrapper for now, can be added if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Result {
|
|
||||||
public enum Status {
|
|
||||||
PASSED,
|
|
||||||
SIZE_MISMATCH,
|
|
||||||
PIXEL_DIFFERENCE
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status status;
|
|
||||||
public long failingPixelCount;
|
|
||||||
public float[] maxDiffFound; // [R, G, B, A]
|
|
||||||
public Bitmap diffImage; // Null if not generated
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares two bitmaps using a configuration object.
|
|
||||||
*
|
|
||||||
* @param reference Golden image
|
|
||||||
* @param candidate Actual image
|
|
||||||
* @param config Comparison configuration
|
|
||||||
* @param mask Optional mask (grayscale)
|
|
||||||
* @return Result of comparison
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static Result compareBasic(@NonNull Bitmap reference, @NonNull Bitmap candidate,
|
|
||||||
@NonNull Config config, @Nullable Bitmap mask) {
|
|
||||||
return nCompareBasic(reference, candidate, config.mode.ordinal(), config.swizzle.ordinal(),
|
|
||||||
config.channelMask, config.maxAbsDiff, config.maxFailingPixelsFraction, mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares two bitmaps using a JSON configuration string.
|
|
||||||
*
|
|
||||||
* @param reference Golden image
|
|
||||||
* @param candidate Actual image
|
|
||||||
* @param jsonConfig Comparison configuration in JSON format
|
|
||||||
* @param mask Optional mask (grayscale)
|
|
||||||
* @return Result of comparison
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static Result compare(@NonNull Bitmap reference, @NonNull Bitmap candidate,
|
|
||||||
@NonNull String jsonConfig, @Nullable Bitmap mask) {
|
|
||||||
return nCompareJson(reference, candidate, jsonConfig, mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static native Result nCompareBasic(Bitmap reference, Bitmap candidate, int mode, int swizzle,
|
|
||||||
int channelMask, float maxAbsDiff, float maxFailingPixelsFraction, Bitmap mask);
|
|
||||||
|
|
||||||
private static native Result nCompareJson(Bitmap reference, Bitmap candidate, String jsonConfig, Bitmap mask);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
GROUP=com.google.android.filament
|
GROUP=com.google.android.filament
|
||||||
VERSION_NAME=1.69.2
|
VERSION_NAME=1.68.5
|
||||||
|
|
||||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||||
|
|
||||||
|
|||||||
@@ -389,9 +389,9 @@ class MainActivity : Activity() {
|
|||||||
viewerContent.assetLights = modelViewer.asset?.lightEntities
|
viewerContent.assetLights = modelViewer.asset?.lightEntities
|
||||||
automation.applySettings(modelViewer.engine, json, viewerContent)
|
automation.applySettings(modelViewer.engine, json, viewerContent)
|
||||||
modelViewer.view.colorGrading = automation.getColorGrading(modelViewer.engine)
|
modelViewer.view.colorGrading = automation.getColorGrading(modelViewer.engine)
|
||||||
modelViewer.cameraFocalLength = automation.cameraSettings.focalLength
|
modelViewer.cameraFocalLength = automation.viewerOptions.cameraFocalLength
|
||||||
modelViewer.cameraNear = automation.cameraSettings.near
|
modelViewer.cameraNear = automation.viewerOptions.cameraNear
|
||||||
modelViewer.cameraFar = automation.cameraSettings.far
|
modelViewer.cameraFar = automation.viewerOptions.cameraFar
|
||||||
updateRootTransform()
|
updateRootTransform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'com.android.application'
|
|
||||||
id 'kotlin-android'
|
|
||||||
}
|
|
||||||
|
|
||||||
project.ext.isSample = true
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
jvmToolchain(versions.jdk)
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'com.google.android.filament.validation'
|
|
||||||
|
|
||||||
compileSdkVersion versions.compileSdk
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "com.google.android.filament.validation"
|
|
||||||
minSdkVersion versions.minSdk
|
|
||||||
targetSdkVersion versions.targetSdk
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility versions.jdk
|
|
||||||
targetCompatibility versions.jdk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation deps.kotlin
|
|
||||||
implementation deps.coroutines.core
|
|
||||||
implementation project(':filament-android')
|
|
||||||
implementation project(':gltfio-android')
|
|
||||||
implementation project(':filament-utils-android')
|
|
||||||
implementation project(':filament-utils-android')
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:label="Filament Validation"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:largeHeap="true"
|
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:theme="@android:style/Theme.NoTitleBar">
|
|
||||||
<activity
|
|
||||||
android:name=".MainActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
|
||||||
android:screenOrientation="fullSensor">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
@@ -1,154 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.filament.validation
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.Choreographer
|
|
||||||
import android.view.SurfaceView
|
|
||||||
import android.view.WindowManager
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
|
||||||
import com.google.android.filament.utils.ModelViewer
|
|
||||||
import com.google.android.filament.utils.Utils
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class MainActivity : Activity(), ValidationRunner.Callback {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
init {
|
|
||||||
Utils.init()
|
|
||||||
System.loadLibrary("filament-utils-jni")
|
|
||||||
}
|
|
||||||
private const val TAG = "FilamentValidation"
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var surfaceView: SurfaceView
|
|
||||||
private lateinit var choreographer: Choreographer
|
|
||||||
private lateinit var modelViewer: ModelViewer
|
|
||||||
private lateinit var statusTextView: TextView
|
|
||||||
private var validationRunner: ValidationRunner? = null
|
|
||||||
|
|
||||||
// Frame callback
|
|
||||||
private val frameScheduler = object : Choreographer.FrameCallback {
|
|
||||||
override fun doFrame(frameTimeNanos: Long) {
|
|
||||||
choreographer.postFrameCallback(this)
|
|
||||||
modelViewer.render(frameTimeNanos)
|
|
||||||
validationRunner?.onFrame(frameTimeNanos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
// Simple layout
|
|
||||||
val layout = android.widget.FrameLayout(this)
|
|
||||||
surfaceView = SurfaceView(this)
|
|
||||||
layout.addView(surfaceView)
|
|
||||||
|
|
||||||
statusTextView = TextView(this)
|
|
||||||
statusTextView.setTextColor(0xFFFFFFFF.toInt())
|
|
||||||
statusTextView.textSize = 16f
|
|
||||||
statusTextView.setPadding(20, 20, 20, 20)
|
|
||||||
statusTextView.text = "Initializing..."
|
|
||||||
layout.addView(statusTextView)
|
|
||||||
|
|
||||||
setContentView(layout)
|
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
|
|
||||||
choreographer = Choreographer.getInstance()
|
|
||||||
modelViewer = ModelViewer(surfaceView)
|
|
||||||
|
|
||||||
// Check permissions?
|
|
||||||
// We assume 'adb install -g' or permissions granted.
|
|
||||||
// But for scoped storage we might not need PERMISSION if reading from app-specific dirs,
|
|
||||||
// but user mentioned /sdcard/ so we need MANAGE_EXTERNAL_STORAGE or READ_EXTERNAL_STORAGE.
|
|
||||||
// For waiting/simplicity, we just try.
|
|
||||||
|
|
||||||
handleIntent()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleIntent() {
|
|
||||||
val intent = intent
|
|
||||||
val testConfigPath = intent.getStringExtra("test_config")
|
|
||||||
|
|
||||||
if (testConfigPath != null) {
|
|
||||||
startValidation(testConfigPath)
|
|
||||||
} else {
|
|
||||||
statusTextView.text = "No test_config provided via Intent.\nUse -e test_config <path>"
|
|
||||||
Log.w(TAG, "No test config provided")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startValidation(configPath: String) {
|
|
||||||
try {
|
|
||||||
Log.i(TAG, "Parsing config from $configPath")
|
|
||||||
val config = ConfigParser.parseFromPath(configPath)
|
|
||||||
|
|
||||||
val outputDir = File(getExternalFilesDir(null), "validation_results")
|
|
||||||
Log.i(TAG, "Output dir: ${outputDir.absolutePath}")
|
|
||||||
|
|
||||||
validationRunner = ValidationRunner(this, modelViewer, config, outputDir)
|
|
||||||
validationRunner?.callback = this
|
|
||||||
validationRunner?.start()
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to start validation", e)
|
|
||||||
statusTextView.text = "Error: ${e.message}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
choreographer.postFrameCallback(frameScheduler)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
choreographer.removeFrameCallback(frameScheduler)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
choreographer.removeFrameCallback(frameScheduler)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTestFinished(result: ValidationRunner.TestResult) {
|
|
||||||
runOnUiThread {
|
|
||||||
val status = "Test ${result.name} finished: ${if(result.passed) "PASS" else "FAIL"}"
|
|
||||||
statusTextView.text = status
|
|
||||||
Log.i(TAG, status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAllTestsFinished() {
|
|
||||||
runOnUiThread {
|
|
||||||
statusTextView.text = "All tests finished!"
|
|
||||||
Log.i(TAG, "All tests finished")
|
|
||||||
// Optional: Auto-close activity?
|
|
||||||
// finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStatusChanged(status: String) {
|
|
||||||
runOnUiThread {
|
|
||||||
statusTextView.text = status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,166 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.filament.validation
|
|
||||||
|
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONObject
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
data class RenderTestConfig(
|
|
||||||
val name: String,
|
|
||||||
val backends: List<String>,
|
|
||||||
val models: Map<String, String>, // name -> path
|
|
||||||
val tests: List<TestConfig>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class TestConfig(
|
|
||||||
val name: String,
|
|
||||||
val description: String?,
|
|
||||||
val backends: List<String>,
|
|
||||||
val models: Set<String>,
|
|
||||||
val rendering: JSONObject,
|
|
||||||
val tolerance: JSONObject?
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
// See test/renderdiff/FORMAT.md for the full specification matched by this parser.
|
|
||||||
class ConfigParser {
|
|
||||||
companion object {
|
|
||||||
fun parseFromPath(path: String): RenderTestConfig {
|
|
||||||
val file = File(path)
|
|
||||||
val jsonTxt = removeComments(file.readText())
|
|
||||||
val json = JSONObject(jsonTxt)
|
|
||||||
return parseRenderTestConfig(json, file.parentFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeComments(json: String): String {
|
|
||||||
return json.lines().joinToString("\n") { it.substringBefore("//") }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseRenderTestConfig(json: JSONObject, baseDir: File?): RenderTestConfig {
|
|
||||||
val name = json.getString("name")
|
|
||||||
val backends = json.getJSONArray("backends").toList<String>()
|
|
||||||
|
|
||||||
val modelSearchPaths = json.optJSONArray("model_search_paths")?.toList<String>() ?: emptyList()
|
|
||||||
val models = mutableMapOf<String, String>()
|
|
||||||
|
|
||||||
baseDir?.let { dir ->
|
|
||||||
modelSearchPaths.forEach { searchPath ->
|
|
||||||
val searchDir = File(dir, searchPath)
|
|
||||||
if (searchDir.exists()) {
|
|
||||||
searchDir.walkTopDown().filter { it.isFile && (it.extension == "glb" || it.extension == "gltf") }.forEach { file ->
|
|
||||||
models[file.nameWithoutExtension] = file.absolutePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val presetsJson = json.optJSONArray("presets")
|
|
||||||
val presets = mutableMapOf<String, PresetConfig>()
|
|
||||||
if (presetsJson != null) {
|
|
||||||
for (i in 0 until presetsJson.length()) {
|
|
||||||
val p = parsePreset(presetsJson.getJSONObject(i), models.keys)
|
|
||||||
presets[p.name] = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val testsJson = json.getJSONArray("tests")
|
|
||||||
val tests = mutableListOf<TestConfig>()
|
|
||||||
for (i in 0 until testsJson.length()) {
|
|
||||||
tests.add(parseTestConfig(testsJson.getJSONObject(i), models.keys, presets, backends))
|
|
||||||
}
|
|
||||||
|
|
||||||
return RenderTestConfig(name, backends, models, tests)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parsePreset(json: JSONObject, existingModels: Set<String>): PresetConfig {
|
|
||||||
val name = json.getString("name")
|
|
||||||
val rendering = json.getJSONObject("rendering")
|
|
||||||
val models = json.optJSONArray("models")?.toList<String>() ?: emptyList()
|
|
||||||
|
|
||||||
// Validate models
|
|
||||||
models.forEach { if (!existingModels.contains(it)) throw IllegalArgumentException("Model $it not found") }
|
|
||||||
|
|
||||||
val tolerance = json.optJSONObject("tolerance")
|
|
||||||
return PresetConfig(name, rendering, models, tolerance)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseTestConfig(
|
|
||||||
json: JSONObject,
|
|
||||||
existingModels: Set<String>,
|
|
||||||
presets: Map<String, PresetConfig>,
|
|
||||||
defaultBackends: List<String>
|
|
||||||
): TestConfig {
|
|
||||||
val name = json.getString("name")
|
|
||||||
val description = json.optString("description")
|
|
||||||
val backends = json.optJSONArray("backends")?.toList<String>() ?: defaultBackends
|
|
||||||
|
|
||||||
val applyPresets = json.optJSONArray("apply_presets")?.toList<String>() ?: emptyList()
|
|
||||||
|
|
||||||
val rendering = JSONObject()
|
|
||||||
val combinedModels = mutableSetOf<String>()
|
|
||||||
var lastTolerance: JSONObject? = null
|
|
||||||
|
|
||||||
applyPresets.forEach { presetName ->
|
|
||||||
val preset = presets[presetName] ?: throw IllegalArgumentException("Unknown preset $presetName")
|
|
||||||
// Merge rendering (flat copy)
|
|
||||||
val keys = preset.rendering.keys()
|
|
||||||
while(keys.hasNext()) {
|
|
||||||
val k = keys.next()
|
|
||||||
rendering.put(k, preset.rendering.get(k))
|
|
||||||
}
|
|
||||||
combinedModels.addAll(preset.models)
|
|
||||||
if (preset.tolerance != null) lastTolerance = preset.tolerance
|
|
||||||
}
|
|
||||||
|
|
||||||
val testRendering = json.optJSONObject("rendering")
|
|
||||||
if (testRendering != null) {
|
|
||||||
val keys = testRendering.keys()
|
|
||||||
while(keys.hasNext()) {
|
|
||||||
val k = keys.next()
|
|
||||||
rendering.put(k, testRendering.get(k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val testModels = json.optJSONArray("models")?.toList<String>() ?: emptyList()
|
|
||||||
combinedModels.addAll(testModels)
|
|
||||||
|
|
||||||
// Validate models
|
|
||||||
combinedModels.forEach { if (!existingModels.contains(it)) throw IllegalArgumentException("Model $it not found") }
|
|
||||||
|
|
||||||
val tolerance = json.optJSONObject("tolerance") ?: lastTolerance
|
|
||||||
|
|
||||||
return TestConfig(name, description, backends, combinedModels, rendering, tolerance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class PresetConfig(
|
|
||||||
val name: String,
|
|
||||||
val rendering: JSONObject,
|
|
||||||
val models: List<String>,
|
|
||||||
val tolerance: JSONObject?
|
|
||||||
)
|
|
||||||
|
|
||||||
private inline fun <reified T> JSONArray.toList(): List<T> {
|
|
||||||
val list = mutableListOf<T>()
|
|
||||||
for (i in 0 until length()) {
|
|
||||||
list.add(get(i) as T)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
@@ -1,327 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.android.filament.validation
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.util.Log
|
|
||||||
import com.google.android.filament.Engine
|
|
||||||
import com.google.android.filament.Renderer
|
|
||||||
import com.google.android.filament.View
|
|
||||||
import com.google.android.filament.utils.AutomationEngine
|
|
||||||
import com.google.android.filament.utils.ImageDiff
|
|
||||||
import com.google.android.filament.utils.ModelViewer
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.json.JSONObject
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
class ValidationRunner(
|
|
||||||
private val context: Context,
|
|
||||||
private val modelViewer: ModelViewer,
|
|
||||||
private val config: RenderTestConfig,
|
|
||||||
private val outputDir: File
|
|
||||||
) {
|
|
||||||
|
|
||||||
private var currentState = State.IDLE
|
|
||||||
private var currentTestIndex = 0
|
|
||||||
private var currentModelIndex = 0
|
|
||||||
private var currentEngine: AutomationEngine? = null
|
|
||||||
private var currentTestConfig: TestConfig? = null
|
|
||||||
private var currentModelName: String? = null
|
|
||||||
|
|
||||||
private var loadStartFence: com.google.android.filament.Fence? = null
|
|
||||||
private var loadStartTime = 0L
|
|
||||||
|
|
||||||
enum class State {
|
|
||||||
IDLE,
|
|
||||||
LOADING_MODEL,
|
|
||||||
WAITING_FOR_FENCE,
|
|
||||||
RUNNING_TEST,
|
|
||||||
COMPARING
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Callback {
|
|
||||||
fun onTestFinished(result: TestResult)
|
|
||||||
fun onAllTestsFinished()
|
|
||||||
fun onStatusChanged(status: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
var callback: Callback? = null
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
if (config.tests.isEmpty()) {
|
|
||||||
callback?.onAllTestsFinished()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
currentTestIndex = 0
|
|
||||||
currentModelIndex = 0
|
|
||||||
startTest(config.tests[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startTest(test: TestConfig) {
|
|
||||||
currentTestConfig = test
|
|
||||||
if (test.models.isEmpty()) {
|
|
||||||
nextTest()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
currentModelIndex = 0
|
|
||||||
startModel(test.models.elementAt(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startModel(modelName: String) {
|
|
||||||
currentModelName = modelName
|
|
||||||
val modelPath = config.models[modelName]
|
|
||||||
if (modelPath == null) {
|
|
||||||
Log.e("ValidationRunner", "Model $modelName not found")
|
|
||||||
nextModel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState = State.LOADING_MODEL
|
|
||||||
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
|
||||||
|
|
||||||
// Load model on main thread (required by ModelViewer)
|
|
||||||
// We assume this is called from main thread or we dispatch
|
|
||||||
loadModel(modelPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadModel(path: String) {
|
|
||||||
// Assume called on Main Thread
|
|
||||||
modelViewer.destroyModel()
|
|
||||||
try {
|
|
||||||
val bytes = File(path).readBytes()
|
|
||||||
val buffer = ByteBuffer.wrap(bytes)
|
|
||||||
modelViewer.loadModelGlb(buffer)
|
|
||||||
modelViewer.transformToUnitCube()
|
|
||||||
loadStartFence = modelViewer.engine.createFence()
|
|
||||||
loadStartTime = System.nanoTime()
|
|
||||||
currentState = State.WAITING_FOR_FENCE
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
|
||||||
nextModel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onFrame(frameTimeNanos: Long) {
|
|
||||||
when (currentState) {
|
|
||||||
State.IDLE -> {}
|
|
||||||
State.WAITING_FOR_FENCE -> {
|
|
||||||
loadStartFence?.let { fence ->
|
|
||||||
if (fence.wait(com.google.android.filament.Fence.Mode.FLUSH, 0) == com.google.android.filament.Fence.FenceStatus.CONDITION_SATISFIED) {
|
|
||||||
modelViewer.engine.destroyFence(fence)
|
|
||||||
loadStartFence = null
|
|
||||||
|
|
||||||
// Compile materials (simplified)
|
|
||||||
modelViewer.scene.forEach { entity ->
|
|
||||||
// ... existing material compilation logic ...
|
|
||||||
}
|
|
||||||
|
|
||||||
startAutomation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
State.RUNNING_TEST -> {
|
|
||||||
currentEngine?.let { engine ->
|
|
||||||
val content = AutomationEngine.ViewerContent()
|
|
||||||
content.view = modelViewer.view
|
|
||||||
content.renderer = modelViewer.renderer
|
|
||||||
content.scene = modelViewer.scene
|
|
||||||
content.lightManager = modelViewer.engine.lightManager
|
|
||||||
|
|
||||||
// Tick
|
|
||||||
// Delta time?
|
|
||||||
val deltaTime = 1.0f / 60.0f // Fixed step for consistency?
|
|
||||||
engine.tick(modelViewer.engine, content, deltaTime)
|
|
||||||
|
|
||||||
if (!engine.isRunning) {
|
|
||||||
// Test finished (for this spec)
|
|
||||||
currentState = State.COMPARING
|
|
||||||
captureAndCompare()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
State.COMPARING -> {} // Busy
|
|
||||||
State.LOADING_MODEL -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAutomation() {
|
|
||||||
val test = currentTestConfig!!
|
|
||||||
val specJson = JSONObject()
|
|
||||||
specJson.put("name", test.name)
|
|
||||||
specJson.put("base", test.rendering)
|
|
||||||
val fullSpec = "[${specJson.toString()}]"
|
|
||||||
|
|
||||||
currentEngine = AutomationEngine(fullSpec)
|
|
||||||
val options = AutomationEngine.Options()
|
|
||||||
options.sleepDuration = 0.0f // Minimal sleep, let frames drive it
|
|
||||||
options.minFrameCount = 5 // Ensure some frames pass
|
|
||||||
currentEngine?.setOptions(options)
|
|
||||||
currentEngine?.startRunning()
|
|
||||||
currentState = State.RUNNING_TEST
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun captureAndCompare() {
|
|
||||||
callback?.onStatusChanged("Comparing ${currentTestConfig?.name}...")
|
|
||||||
val view = modelViewer.view
|
|
||||||
val renderer = modelViewer.renderer
|
|
||||||
val width = view.viewport.width
|
|
||||||
val height = view.viewport.height
|
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocateDirect(width * height * 4)
|
|
||||||
|
|
||||||
val pbd = com.google.android.filament.Texture.PixelBufferDescriptor(
|
|
||||||
buffer,
|
|
||||||
com.google.android.filament.Texture.Format.RGBA,
|
|
||||||
com.google.android.filament.Texture.Type.UBYTE,
|
|
||||||
1, 0, 0, 0, 0, // alignment, left, top, stride (0=default)
|
|
||||||
null // handler (null = current thread? no, handler is for callback)
|
|
||||||
) {
|
|
||||||
// Callback when readPixels is done
|
|
||||||
// Dispatch to background thread for comparison to avoid blocking UI?
|
|
||||||
// "it" is undefined here? The callback interface is Runnable?
|
|
||||||
// Kotlin lambda for Runnable.
|
|
||||||
compareCapturedImage(buffer, width, height)
|
|
||||||
}
|
|
||||||
renderer.readPixels(0, 0, width, height, pbd)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun compareCapturedImage(buffer: java.nio.Buffer, width: Int, height: Int) {
|
|
||||||
// This runs on... which thread? Filament driver thread possibly.
|
|
||||||
// We should use a helper to process.
|
|
||||||
|
|
||||||
val testName = currentTestConfig!!.name
|
|
||||||
val modelName = currentModelName!!
|
|
||||||
val backend = "opengl" // Hardcoded for now, or get from View/Engine?
|
|
||||||
val testFullName = "${testName}.${backend}.${modelName}"
|
|
||||||
|
|
||||||
// Golden path
|
|
||||||
// We expect a golden directory.
|
|
||||||
val goldenFile = File(config.models.get(modelName)!!).parentFile.parentFile.resolve("golden/${testFullName}.png")
|
|
||||||
// Strategy: models are in .../models/model.glb
|
|
||||||
// Goldens are in .../golden/
|
|
||||||
|
|
||||||
Thread {
|
|
||||||
try {
|
|
||||||
// Convert buffer to Bitmap
|
|
||||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
||||||
bitmap.copyPixelsFromBuffer(buffer)
|
|
||||||
|
|
||||||
// Flip Y? ReadPixels is typically bottom-up?
|
|
||||||
// Filament readPixels is bottom-left? YES.
|
|
||||||
// Bitmap is top-left.
|
|
||||||
// We need to flip.
|
|
||||||
val matrix = android.graphics.Matrix()
|
|
||||||
matrix.postScale(1f, -1f)
|
|
||||||
val flipped = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true)
|
|
||||||
|
|
||||||
var passed = false
|
|
||||||
if (goldenFile.exists()) {
|
|
||||||
val golden = android.graphics.BitmapFactory.decodeFile(goldenFile.absolutePath)
|
|
||||||
if (golden != null) {
|
|
||||||
// Populate tolerance from config
|
|
||||||
val tol = currentTestConfig?.tolerance ?: org.json.JSONObject()
|
|
||||||
val tolJson = tol.toString()
|
|
||||||
|
|
||||||
val result = ImageDiff.compare(golden, flipped, tolJson, null)
|
|
||||||
passed = (result.status == ImageDiff.Result.Status.PASSED)
|
|
||||||
|
|
||||||
// Save diff if failed?
|
|
||||||
if (!passed) {
|
|
||||||
val diffFile = File(outputDir, "${testFullName}_diff.png")
|
|
||||||
if (result.diffImage != null) {
|
|
||||||
FileOutputStream(diffFile).use { out ->
|
|
||||||
result.diffImage.compress(Bitmap.CompressFormat.PNG, 100, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e("ValidationRunner", "Failed to load golden: ${goldenFile.absolutePath}")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.w("ValidationRunner", "Golden not found: ${goldenFile.absolutePath}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save output
|
|
||||||
val outFile = File(outputDir, "${testFullName}.png")
|
|
||||||
FileOutputStream(outFile).use { out ->
|
|
||||||
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
callback?.onTestFinished(TestResult(testFullName, passed))
|
|
||||||
|
|
||||||
// Schedule next model on main thread
|
|
||||||
// Use Handler or View.post
|
|
||||||
modelViewer.view.viewport
|
|
||||||
// dispatch nextModel()
|
|
||||||
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
|
||||||
nextModel()
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("ValidationRunner", "Comparison failed", e)
|
|
||||||
android.os.Handler(android.os.Looper.getMainLooper()).post { nextModel() }
|
|
||||||
}
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nextModel() {
|
|
||||||
currentModelIndex++
|
|
||||||
if (currentTestConfig != null && currentModelIndex < currentTestConfig!!.models.size) {
|
|
||||||
startModel(currentTestConfig!!.models.elementAt(currentModelIndex))
|
|
||||||
} else {
|
|
||||||
nextTest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun nextTest() {
|
|
||||||
currentTestIndex++
|
|
||||||
if (currentTestIndex < config.tests.size) {
|
|
||||||
startTest(config.tests[currentTestIndex])
|
|
||||||
} else {
|
|
||||||
currentState = State.IDLE
|
|
||||||
zipResults()
|
|
||||||
callback?.onAllTestsFinished()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun zipResults() {
|
|
||||||
callback?.onStatusChanged("Zipping results...")
|
|
||||||
val zipFile = File(outputDir, "results.zip")
|
|
||||||
try {
|
|
||||||
java.util.zip.ZipOutputStream(java.io.FileOutputStream(zipFile)).use { zos ->
|
|
||||||
outputDir.walkTopDown().filter { it.isFile && it.name != "results.zip" }.forEach { file ->
|
|
||||||
val entryName = file.relativeTo(outputDir).path
|
|
||||||
zos.putNextEntry(java.util.zip.ZipEntry(entryName))
|
|
||||||
file.inputStream().use { it.copyTo(zos) }
|
|
||||||
zos.closeEntry()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.i("ValidationRunner", "Zipped results to ${zipFile.absolutePath}")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("ValidationRunner", "Failed to zip results", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class TestResult(val name: String, val passed: Boolean)
|
|
||||||
|
|
||||||
@@ -21,6 +21,5 @@ include ':samples:sample-texture-view'
|
|||||||
include ':samples:sample-texture-target'
|
include ':samples:sample-texture-target'
|
||||||
include ':samples:sample-textured-object'
|
include ':samples:sample-textured-object'
|
||||||
include ':samples:sample-transparent-view'
|
include ':samples:sample-transparent-view'
|
||||||
include ':samples:sample-render-validation'
|
|
||||||
|
|
||||||
rootProject.name = 'filament'
|
rootProject.name = 'filament'
|
||||||
|
|||||||
@@ -214,6 +214,8 @@ ENABLE_PERFETTO=""
|
|||||||
|
|
||||||
BACKEND_DEBUG_FLAG_OPTION=""
|
BACKEND_DEBUG_FLAG_OPTION=""
|
||||||
|
|
||||||
|
STEREOSCOPIC_OPTION=""
|
||||||
|
|
||||||
OSMESA_OPTION=""
|
OSMESA_OPTION=""
|
||||||
|
|
||||||
IOS_BUILD_SIMULATOR=false
|
IOS_BUILD_SIMULATOR=false
|
||||||
@@ -314,6 +316,7 @@ function build_desktop_target {
|
|||||||
${ASAN_UBSAN_OPTION} \
|
${ASAN_UBSAN_OPTION} \
|
||||||
${COVERAGE_OPTION} \
|
${COVERAGE_OPTION} \
|
||||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||||
|
${STEREOSCOPIC_OPTION} \
|
||||||
${OSMESA_OPTION} \
|
${OSMESA_OPTION} \
|
||||||
${architectures} \
|
${architectures} \
|
||||||
../..
|
../..
|
||||||
@@ -452,6 +455,7 @@ function build_android_target {
|
|||||||
${VULKAN_ANDROID_OPTION} \
|
${VULKAN_ANDROID_OPTION} \
|
||||||
${WEBGPU_OPTION} \
|
${WEBGPU_OPTION} \
|
||||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||||
|
${STEREOSCOPIC_OPTION} \
|
||||||
${ENABLE_PERFETTO} \
|
${ENABLE_PERFETTO} \
|
||||||
../..
|
../..
|
||||||
ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \
|
ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \
|
||||||
@@ -693,6 +697,7 @@ function build_ios_target {
|
|||||||
${WEBGPU_OPTION} \
|
${WEBGPU_OPTION} \
|
||||||
${MATDBG_OPTION} \
|
${MATDBG_OPTION} \
|
||||||
${MATOPT_OPTION} \
|
${MATOPT_OPTION} \
|
||||||
|
${STEREOSCOPIC_OPTION} \
|
||||||
../..
|
../..
|
||||||
ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \
|
ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \
|
||||||
../../compile_commands.json
|
../../compile_commands.json
|
||||||
@@ -1006,6 +1011,20 @@ while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:Py:" opt; do
|
|||||||
;;
|
;;
|
||||||
x) BACKEND_DEBUG_FLAG_OPTION="-DFILAMENT_BACKEND_DEBUG_FLAG=${OPTARG}"
|
x) BACKEND_DEBUG_FLAG_OPTION="-DFILAMENT_BACKEND_DEBUG_FLAG=${OPTARG}"
|
||||||
;;
|
;;
|
||||||
|
S) case $(echo "${OPTARG}" | tr '[:upper:]' '[:lower:]') in
|
||||||
|
instanced)
|
||||||
|
STEREOSCOPIC_OPTION="-DFILAMENT_SAMPLES_STEREO_TYPE=instanced"
|
||||||
|
;;
|
||||||
|
multiview)
|
||||||
|
STEREOSCOPIC_OPTION="-DFILAMENT_SAMPLES_STEREO_TYPE=multiview"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown stereoscopic type ${OPTARG}"
|
||||||
|
echo "Type must be one of [instanced|multiview]"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
;;
|
||||||
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
|
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
|
||||||
;;
|
;;
|
||||||
y)
|
y)
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.69.0'
|
implementation 'com.google.android.filament:filament-android:1.68.5'
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||||
@@ -196,7 +196,7 @@ dependencies {
|
|||||||
</div>
|
</div>
|
||||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||||
<pre><code class="language-shell">pod 'Filament', '~> 1.69.0'
|
<pre><code class="language-shell">pod 'Filament', '~> 1.68.5'
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ used to create the SPIR-V is not available.</p>
|
|||||||
<i class="fa fa-angle-left"></i>
|
<i class="fa fa-angle-left"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a rel="next prefetch" href="../dup/viewer.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
<a rel="next prefetch" href="../dup/uberz.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -366,7 +366,7 @@ used to create the SPIR-V is not available.</p>
|
|||||||
<i class="fa fa-angle-left"></i>
|
<i class="fa fa-angle-left"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a rel="next prefetch" href="../dup/viewer.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
<a rel="next prefetch" href="../dup/uberz.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||||
<i class="fa fa-angle-right"></i>
|
<i class="fa fa-angle-right"></i>
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ mapping should be specified as an <code>optional</code> feature of the ubershade
|
|||||||
|
|
||||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||||
<!-- Mobile navigation buttons -->
|
<!-- Mobile navigation buttons -->
|
||||||
<a rel="prev" href="../dup/viewer.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
<a rel="prev" href="../dup/matdbg.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||||
<i class="fa fa-angle-left"></i>
|
<i class="fa fa-angle-left"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ mapping should be specified as an <code>optional</code> feature of the ubershade
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||||
<a rel="prev" href="../dup/viewer.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
<a rel="prev" href="../dup/matdbg.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||||
<i class="fa fa-angle-left"></i>
|
<i class="fa fa-angle-left"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -1,333 +0,0 @@
|
|||||||
<!DOCTYPE HTML>
|
|
||||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
|
||||||
<head>
|
|
||||||
<!-- Book generated using mdBook -->
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>viewer - 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="viewer-library"><a class="header" href="#viewer-library">Viewer Library</a></h1>
|
|
||||||
<p>The <strong>Viewer Library</strong> (<code>libs/viewer</code>) provides a high-level abstraction for configuring and rendering Filament scenes. It is used by tools like <code>gltf_viewer</code> to load assets, manage settings, and drive the rendering loop.</p>
|
|
||||||
<h2 id="features"><a class="header" href="#features">Features</a></h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Settings Management</strong>: Centralized configuration for View, Camera, Lights, and Materials via the <code>Settings</code> struct.</li>
|
|
||||||
<li><strong>JSON Serialization</strong>: Full support for loading and saving settings via JSON.</li>
|
|
||||||
<li><strong>Automation</strong>: <code>AutomationEngine</code> allows scripting the viewer with a sequence of JSON-based test cases (batch mode).</li>
|
|
||||||
<li><strong>GUI Integration</strong>: Built-in support for <code>imgui</code> via <code>ViewerGui</code> and <code>Settings</code> binding.</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="json-settings-schema"><a class="header" href="#json-settings-schema">JSON Settings Schema</a></h2>
|
|
||||||
<p>The viewer settings can be configured using a JSON object. This is used for <code>gltf_viewer --settings</code> or in automation specs.</p>
|
|
||||||
<h3 id="root-object"><a class="header" href="#root-object">Root Object</a></h3>
|
|
||||||
<p>The root object contains the following categories:</p>
|
|
||||||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Key</th><th style="text-align: left">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
|
||||||
<tr><td style="text-align: left"><code>view</code></td><td style="text-align: left">Object</td><td style="text-align: left">Post-processing and rendering quality settings.</td></tr>
|
|
||||||
<tr><td style="text-align: left"><code>camera</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Explicit camera control (pose, projection, exposure).</td></tr>
|
|
||||||
<tr><td style="text-align: left"><code>lighting</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Environment and dynamic light settings.</td></tr>
|
|
||||||
<tr><td style="text-align: left"><code>viewer</code></td><td style="text-align: left">Object</td><td style="text-align: left">Global viewer options (skybox, background, scaling).</td></tr>
|
|
||||||
<tr><td style="text-align: left"><code>animation</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Animation playback control.</td></tr>
|
|
||||||
<tr><td style="text-align: left"><code>material</code></td><td style="text-align: left">Object</td><td style="text-align: left">Material overrides.</td></tr>
|
|
||||||
</tbody></table>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<h3 id="camera-settings-camera"><a class="header" href="#camera-settings-camera">Camera Settings (<code>camera</code>)</a></h3>
|
|
||||||
<p>Allows explicit control over the camera. If <code>enabled</code> is false, the viewer uses its default orbit camera logic (auto-scaling/centering).</p>
|
|
||||||
<pre><code class="language-json">"camera": {
|
|
||||||
"enabled": true, // Must be true to use these explicit settings
|
|
||||||
"projection": "PERSPECTIVE", // "PERSPECTIVE" or "ORTHO"
|
|
||||||
"center": [0, 0, 0], // World-space look-at point
|
|
||||||
"lookAt": [0, 0, -1], // World-space eye position (confusingly named 'lookAt' in internal legacy, often 'eye')
|
|
||||||
"up": [0, 1, 0], // Up vector
|
|
||||||
"near": 0.1, // Near plane
|
|
||||||
"far": 100.0, // Far plane
|
|
||||||
"focalLength": 28.0, // Focal length in mm (Perspective only)
|
|
||||||
"fov": 0.0, // Field of view in degrees (overrides focalLength if > 0)
|
|
||||||
"aperture": 16.0, // f-stop
|
|
||||||
"shutterSpeed": 125.0, // 1/seconds
|
|
||||||
"sensitivity": 100.0, // ISO
|
|
||||||
"focusDistance": 10.0, // Focus distance in world units
|
|
||||||
"scaling": [1.0, 1.0], // Custom projection matrix scaling (mostly for Ortho)
|
|
||||||
"shift": [0.0, 0.0] // Custom projection matrix shift
|
|
||||||
}
|
|
||||||
</code></pre>
|
|
||||||
<h3 id="lighting-settings-lighting"><a class="header" href="#lighting-settings-lighting">Lighting Settings (<code>lighting</code>)</a></h3>
|
|
||||||
<p>Controls the Image Based Lighting (IBL), the Sun, and additional dynamic lights.</p>
|
|
||||||
<pre><code class="language-json">"lighting": {
|
|
||||||
"iblIntensity": 30000.0,
|
|
||||||
"iblRotation": 0.0, // Rotation in degrees
|
|
||||||
"enableSunlight": true,
|
|
||||||
"enableShadows": true,
|
|
||||||
"sunlight": { // **[NEW]** Nested sunlight properties
|
|
||||||
"intensity": 100000.0,
|
|
||||||
"color": [0.98, 0.92, 0.89],
|
|
||||||
"direction": [0.6, -1.0, -0.8],
|
|
||||||
"sunHaloSize": 10.0,
|
|
||||||
"sunHaloFalloff": 80.0,
|
|
||||||
"sunAngularRadius": 1.9,
|
|
||||||
"castShadows": true,
|
|
||||||
"shadowOptions": { // Per-light shadow options
|
|
||||||
"mapSize": 1024,
|
|
||||||
"shadowCascades": 1,
|
|
||||||
"stable": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lights": [ // **[NEW]** Array of custom lights
|
|
||||||
{
|
|
||||||
"type": "POINT", // "POINT", "SPOT", "FOCUSED_SPOT", "DIRECTIONAL", "SUN"
|
|
||||||
"position": [0, 2, 0],
|
|
||||||
"color": [1, 0, 0],
|
|
||||||
"intensity": 5000.0,
|
|
||||||
"falloff": 10.0,
|
|
||||||
"castShadows": true,
|
|
||||||
"shadowOptions": { "mapSize": 512 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "SPOT",
|
|
||||||
"position": [2, 5, 2],
|
|
||||||
"direction": [0, -1, 0],
|
|
||||||
"spotInner": 0.5, // Inner cone angle (radians)
|
|
||||||
"spotOuter": 0.8 // Outer cone angle (radians)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
</code></pre>
|
|
||||||
<h3 id="view-settings-view"><a class="header" href="#view-settings-view">View Settings (<code>view</code>)</a></h3>
|
|
||||||
<p>Standard Filament view settings.</p>
|
|
||||||
<pre><code class="language-json">"view": {
|
|
||||||
"postProcessingEnabled": true,
|
|
||||||
"antiAliasing": "FXAA", // "NONE", "FXAA"
|
|
||||||
"msaa": {
|
|
||||||
"enabled": true,
|
|
||||||
"sampleCount": 4
|
|
||||||
},
|
|
||||||
"ssao": { "enabled": true, ... },
|
|
||||||
"bloom": { "enabled": true, ... },
|
|
||||||
"dof": { "enabled": false, ... },
|
|
||||||
"vignette": { "enabled": false, ... },
|
|
||||||
"colorGrading": {
|
|
||||||
"toneMapping": "ACES_LEGACY", // "LINEAR", "ACES", "FILMIC", "PBR_NEUTRAL", etc.
|
|
||||||
"exposure": 0.0,
|
|
||||||
"gamma": [1.0, 1.0, 1.0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</code></pre>
|
|
||||||
<h3 id="viewer-options-viewer"><a class="header" href="#viewer-options-viewer">Viewer Options (<code>viewer</code>)</a></h3>
|
|
||||||
<p>General app-level settings.</p>
|
|
||||||
<pre><code class="language-json">"viewer": {
|
|
||||||
"skyboxEnabled": true,
|
|
||||||
"backgroundColor": [0, 0, 0], // Used if skybox is disabled
|
|
||||||
"autoScaleEnabled": true, // Fit model to unit cube
|
|
||||||
"groundPlaneEnabled": false
|
|
||||||
}
|
|
||||||
</code></pre>
|
|
||||||
<h3 id="animation-settings-animation"><a class="header" href="#animation-settings-animation">Animation Settings (<code>animation</code>)</a></h3>
|
|
||||||
<p>Control glTF animation playback.</p>
|
|
||||||
<pre><code class="language-json">"animation": {
|
|
||||||
"enabled": true,
|
|
||||||
"speed": 1.0,
|
|
||||||
"time": -1.0 // If >= 0, forces animation to this specific time (seconds)
|
|
||||||
}
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
||||||
<!-- Mobile navigation buttons -->
|
|
||||||
<a rel="prev" href="../dup/matdbg.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/uberz.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/matdbg.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/uberz.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,40 +0,0 @@
|
|||||||
# Building Skybox Assets
|
|
||||||
|
|
||||||
This directory contains the source code for the Simulated Skybox sample. The assets (material and textures) are pre-built in the `assets/` directory.
|
|
||||||
|
|
||||||
If you need to modify the material or regenerate the moon textures, you can use the scripts provided in the `tools/` directory.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- **Filament**: You must have a built version of Filament. The scripts assume a standard CMake build output structure (e.g., `out/cmake-release`).
|
|
||||||
- **Python 3**: Required for texture generation.
|
|
||||||
- **Python Dependencies**: `numpy`, `Pillow` (automatically installed if missing, via pip).
|
|
||||||
|
|
||||||
## Rebuilding the Material
|
|
||||||
|
|
||||||
If you modify `simulated_skybox.mat`, you must recompile it into a `.filamat` file.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run from the sample root or tools directory
|
|
||||||
./tools/build_material.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This will update `assets/simulated_skybox.filamat`.
|
|
||||||
|
|
||||||
## Regenerating Moon Textures
|
|
||||||
|
|
||||||
If you want to change the moon's resolution, bump scale, or blur, you can regenerate the textures.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run from the sample root or tools directory
|
|
||||||
./tools/generate_moon_assets.sh [size]
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```bash
|
|
||||||
./tools/generate_moon_assets.sh 512
|
|
||||||
```
|
|
||||||
|
|
||||||
This will download raw NASA data (if not present) and generate:
|
|
||||||
- `assets/moon_disk.png`
|
|
||||||
- `assets/moon_normal.png`
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# Analytic Skybox Sample
|
|
||||||
|
|
||||||
This sample demonstrates a **fully procedural, single-pass skybox shader** capable of simulating a dynamic day-night cycle, atmospheric scattering, volumetric clouds, and water reflections.
|
|
||||||
|
|
||||||
It is designed for graphics engineers and technical artists who need a lightweight yet physically plausible environment background without relying on static HDRI textures.
|
|
||||||
|
|
||||||
## Features & Performance
|
|
||||||
|
|
||||||
The shader uses a "Uber-Shader" approach where all features are computed per-pixel. Features can be toggled or tuned via uniforms to balance quality vs performance.
|
|
||||||
|
|
||||||
| Feature | Cost | Control | Description |
|
|
||||||
| :--- | :---: | :--- | :--- |
|
|
||||||
| **Atmosphere** | 🟡 **Medium** | `turbidity`, `rayleigh`, `mie` | Analytic Rayleigh & Mie scattering. Physically based colors. |
|
|
||||||
| **Sun Disk** | 🟢 **Low** | `sunHalo` (Radius, Limb) | Analytic sphere intersection with limb darkening. Conservation of energy (Lux). |
|
|
||||||
| **Moon & Earthshine** | 🟢 **Low** | `sunHalo2`, `moonIntensity` | Resolved moon disk with geometric phases and dynamic Earthshine. |
|
|
||||||
| **Stars** | 🟢 **Low** | `starControl` (Density) | High-frequency procedural noise. Occluded by clouds/moon. |
|
|
||||||
| **Clouds** | 🔴 **High** | `cloudControl` (Coverage, Density) | 4-Octave 3D Fractal Brownian Motion (FBM). Dominates the cost when enabled. |
|
|
||||||
| **Heat Shimmer** | 🟡 **Medium** | `shimmerControl` | UV perturbation near the horizon to simulate mirages. |
|
|
||||||
| **Water Reflection** | 🟣 **Very High** | `waterControl` | **Renders the sky twice**. Includes procedural waves (FBM) and fresnel. |
|
|
||||||
|
|
||||||
> **Note**: Rendering water (`V.y < 0`) is significantly more expensive (~2.5x) than the sky because it requires re-evaluating the atmospheric scattering and cloud noise for the reflection vector.
|
|
||||||
|
|
||||||
## Shader Techniques
|
|
||||||
|
|
||||||
### 1. Analytic Atmospheric Scattering
|
|
||||||
Based on the **Hoffman & Preetham** model. It solves the single-scattering integral analytically for air molecules (Rayleigh) and aerosols (Mie).
|
|
||||||
- **Rayleigh**: Produces the deep blue sky and red sunset colors.
|
|
||||||
- **Mie**: Produces the white halo around the sun and general haziness.
|
|
||||||
- **Optimization**: Uses a simplified optical depth approximation ("Air Mass") to avoid expensive ray-marching.
|
|
||||||
|
|
||||||
### 2. Procedural Clouds (3D Noise)
|
|
||||||
Clouds are rendered as a spherical shell at a specific altitude.
|
|
||||||
- **Technique**: Ray-sphere intersection finds the entry point, then **3D FBM Noise** determines density.
|
|
||||||
- **Lighting**: Uses a "Silver Lining" approximation (strong forward scattering) and Beers-Lambert attenuation for dark underbellies.
|
|
||||||
- **Animation**: The noise coordinate logic helps simulate wind drift and shape evolution over time.
|
|
||||||
|
|
||||||
### 3. Infinite Water Ocean
|
|
||||||
When looking below the horizon, the shader switches to "Water Mode".
|
|
||||||
- **Geometry**: A flat plane at $y=0$.
|
|
||||||
- **Waves**: Generated using **Derivative-Based Noise** (or Finite Difference). This creates slope vectors that perturb the normal without needing actual geometry.
|
|
||||||
- **Reflection**: A ray is cast from the water surface back into the sky ($R = \text{reflect}(V, N)$). The sky function is called again with $R$ to get the reflected color.
|
|
||||||
|
|
||||||
### 4. Dynamic Tone Mapping
|
|
||||||
Applies a custom tone mapping curve that varies with Sun Elevation.
|
|
||||||
- **Noon**: Linear/Gamma (Standard).
|
|
||||||
- **Sunset**: Higher contrast curve to compress the dynamic range and enhance the rich sunset oranges/purples.
|
|
||||||
|
|
||||||
## Integration
|
|
||||||
|
|
||||||
To use this in your own Filament application:
|
|
||||||
|
|
||||||
1. **Compile the Material**:
|
|
||||||
Use `matc` to compile `simulated_skybox.mat` into a `.filamat` file.
|
|
||||||
```bash
|
|
||||||
matc -p mobile -a opengl -o assets/simulated_skybox.filamat simulated_skybox.mat
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Load in JavaScript/C++**:
|
|
||||||
Create a Skybox entity and assign the material.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// JavaScript Example
|
|
||||||
const material = engine.createMaterial('assets/simulated_skybox.filamat');
|
|
||||||
const skybox = engine.createSkybox(material);
|
|
||||||
scene.setSkybox(skybox);
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Update Uniforms**:
|
|
||||||
The shader requires specific uniforms (Sun Direction, Time, etc.) to be updated every frame. See `SimulatedSkybox.js` for a reference implementation of the uniform buffer management.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
* **Hoffman & Preetham (2002)**: *"Real-time Light-Atmosphere Interactions"*
|
|
||||||
* **Henyey & Greenstein (1941)**: *"Diffuse radiation in the galaxy"* (Mie Phase Function)
|
|
||||||
* **Kasten & Young (1989)**: *"Revised optical air mass tables"*
|
|
||||||
* **Three.js / Sky.js**: Empirical adjustments for "Golden Hour" aesthetics.
|
|
||||||
@@ -21,31 +21,13 @@ class SimulatedSkybox {
|
|||||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||||
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
this.starControl = [1.0, 1.0]; // x=Density (0-1), y=Enabled (0-1)
|
||||||
this.focalLength = 24.0;
|
|
||||||
this.height = 1000.0;
|
|
||||||
this.planetRadius = 6360.0;
|
this.planetRadius = 6360.0;
|
||||||
|
|
||||||
// Sun Halo
|
// Sun Halo
|
||||||
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
|
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
|
||||||
// Sun Halo
|
|
||||||
this.sunHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.5, 1.0, 1.0];
|
this.sunHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.5, 1.0, 1.0];
|
||||||
|
|
||||||
// Moon Parameters (Mapped to Secondary Sun)
|
|
||||||
this.moonDirection = [-0.2, 0.8, -0.2]; // Default Moon Pos
|
|
||||||
this.moonIntensity = 1.0; // Scale Factor (1.0 = Physical Peak)
|
|
||||||
// x=cos(rad), y=sin(rad) [Precision Fix], z=intensity, w=enabled
|
|
||||||
this.moonHalo = [Math.cos(0.5 * Math.PI / 180.0), Math.sin(0.5 * Math.PI / 180.0), 1.0, 0.0]; // Disabled by default
|
|
||||||
|
|
||||||
this.moonTextureObj = null;
|
|
||||||
this.moonNormalObj = null;
|
|
||||||
this.milkyWayTextureObj = null;
|
|
||||||
|
|
||||||
// Milky Way Parameters
|
|
||||||
// x=Intensity, y=Saturation, z=Unused
|
|
||||||
this.milkyWayControl = [1.0, 1.0, 0.07];
|
|
||||||
this.milkyWayEnabled = true;
|
|
||||||
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
|
||||||
this.initEntity();
|
this.initEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,184 +44,6 @@ class SimulatedSkybox {
|
|||||||
rcm.setMaterialInstanceAt(instance, 0, this.materialInstance);
|
rcm.setMaterialInstanceAt(instance, 0, this.materialInstance);
|
||||||
|
|
||||||
console.log("Material loaded and bound.");
|
console.log("Material loaded and bound.");
|
||||||
|
|
||||||
// Load Moon Texture
|
|
||||||
try {
|
|
||||||
const texUrl = 'assets/moon_disk.png';
|
|
||||||
const Texture = Filament.Texture;
|
|
||||||
const TextureSampler = Filament.TextureSampler;
|
|
||||||
const PixelDataFormat = Filament.PixelDataFormat;
|
|
||||||
const PixelDataType = Filament.PixelDataType;
|
|
||||||
const TextureUsage = Filament.Texture$Usage;
|
|
||||||
const TextureFormat = Filament.Texture$InternalFormat;
|
|
||||||
const MinFilter = Filament.MinFilter;
|
|
||||||
const MagFilter = Filament.MagFilter;
|
|
||||||
const WrapMode = Filament.WrapMode;
|
|
||||||
const texResponse = await fetch(texUrl);
|
|
||||||
const texBlob = await texResponse.blob();
|
|
||||||
const bitmap = await createImageBitmap(texBlob);
|
|
||||||
|
|
||||||
const width = bitmap.width;
|
|
||||||
const height = bitmap.height;
|
|
||||||
|
|
||||||
this.moonTextureObj = Texture.Builder()
|
|
||||||
.width(width)
|
|
||||||
.height(height)
|
|
||||||
.levels(0xff)
|
|
||||||
.format(TextureFormat.SRGB8_A8)
|
|
||||||
.usage(TextureUsage.SAMPLEABLE.value | TextureUsage.UPLOADABLE.value | TextureUsage.GEN_MIPMAPPABLE.value)
|
|
||||||
.build(this.engine);
|
|
||||||
|
|
||||||
// Extract Data using a Canvas
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.drawImage(bitmap, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, width, height);
|
|
||||||
|
|
||||||
// Use RGBA data directly (Uint8ClampedArray)
|
|
||||||
// Filament supports RGBA upload for RGB/SRGB internal formats (drops alpha).
|
|
||||||
// This matches Canvas layout (Top-Down, RGBA) and is 4-byte aligned.
|
|
||||||
const pbd = Filament.PixelBuffer(
|
|
||||||
new Uint8Array(imageData.data.buffer), // Wrap buffer to ensure Uint8Array
|
|
||||||
PixelDataFormat.RGBA,
|
|
||||||
PixelDataType.UBYTE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.moonTextureObj.setImage(this.engine, 0, pbd);
|
|
||||||
this.moonTextureObj.generateMipmaps(this.engine);
|
|
||||||
|
|
||||||
const sampler = new TextureSampler(
|
|
||||||
MinFilter.LINEAR,
|
|
||||||
MagFilter.LINEAR,
|
|
||||||
WrapMode.CLAMP_TO_EDGE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.materialInstance.setTextureParameter('moonTexture', this.moonTextureObj, sampler);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to load moon texture:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load Moon Normal
|
|
||||||
try {
|
|
||||||
const texUrl = 'assets/moon_normal.png';
|
|
||||||
const Texture = Filament.Texture;
|
|
||||||
const TextureSampler = Filament.TextureSampler;
|
|
||||||
const PixelDataFormat = Filament.PixelDataFormat;
|
|
||||||
const PixelDataType = Filament.PixelDataType;
|
|
||||||
const TextureUsage = Filament.Texture$Usage;
|
|
||||||
const TextureFormat = Filament.Texture$InternalFormat;
|
|
||||||
const MinFilter = Filament.MinFilter;
|
|
||||||
const MagFilter = Filament.MagFilter;
|
|
||||||
const WrapMode = Filament.WrapMode;
|
|
||||||
const texResponse = await fetch(texUrl);
|
|
||||||
const texBlob = await texResponse.blob();
|
|
||||||
const bitmap = await createImageBitmap(texBlob);
|
|
||||||
|
|
||||||
const width = bitmap.width;
|
|
||||||
const height = bitmap.height;
|
|
||||||
|
|
||||||
this.moonNormalObj = Texture.Builder()
|
|
||||||
.width(width)
|
|
||||||
.height(height)
|
|
||||||
.levels(0xff)
|
|
||||||
.format(TextureFormat.RGBA8)
|
|
||||||
.usage(TextureUsage.SAMPLEABLE.value | TextureUsage.UPLOADABLE.value | TextureUsage.GEN_MIPMAPPABLE.value)
|
|
||||||
.build(this.engine);
|
|
||||||
|
|
||||||
// Extract Data using a Canvas
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.drawImage(bitmap, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, width, height);
|
|
||||||
|
|
||||||
// Use RGBA data directly
|
|
||||||
const pbd = Filament.PixelBuffer(
|
|
||||||
new Uint8Array(imageData.data.buffer),
|
|
||||||
PixelDataFormat.RGBA,
|
|
||||||
PixelDataType.UBYTE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.moonNormalObj.setImage(this.engine, 0, pbd);
|
|
||||||
this.moonNormalObj.generateMipmaps(this.engine);
|
|
||||||
|
|
||||||
const sampler = new TextureSampler(
|
|
||||||
MinFilter.LINEAR_MIPMAP_LINEAR,
|
|
||||||
MagFilter.LINEAR,
|
|
||||||
WrapMode.CLAMP_TO_EDGE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.materialInstance.setTextureParameter('moonNormal', this.moonNormalObj, sampler);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to load moon normal:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load Milky Way Texture
|
|
||||||
try {
|
|
||||||
const texUrl = 'assets/milkyway.png';
|
|
||||||
const Texture = Filament.Texture;
|
|
||||||
const TextureSampler = Filament.TextureSampler;
|
|
||||||
const PixelDataFormat = Filament.PixelDataFormat;
|
|
||||||
const PixelDataType = Filament.PixelDataType;
|
|
||||||
const TextureUsage = Filament.Texture$Usage;
|
|
||||||
const TextureFormat = Filament.Texture$InternalFormat;
|
|
||||||
const MinFilter = Filament.MinFilter;
|
|
||||||
const MagFilter = Filament.MagFilter;
|
|
||||||
const WrapMode = Filament.WrapMode;
|
|
||||||
const texResponse = await fetch(texUrl);
|
|
||||||
const texBlob = await texResponse.blob();
|
|
||||||
const bitmap = await createImageBitmap(texBlob);
|
|
||||||
|
|
||||||
const width = bitmap.width;
|
|
||||||
const height = bitmap.height;
|
|
||||||
|
|
||||||
this.milkyWayTextureObj = Texture.Builder()
|
|
||||||
.width(width)
|
|
||||||
.height(height)
|
|
||||||
.levels(0xff)
|
|
||||||
.format(TextureFormat.SRGB8_A8)
|
|
||||||
.usage(TextureUsage.SAMPLEABLE.value | TextureUsage.UPLOADABLE.value | TextureUsage.GEN_MIPMAPPABLE.value)
|
|
||||||
.build(this.engine);
|
|
||||||
|
|
||||||
// Extract Data using a Canvas
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.drawImage(bitmap, 0, 0);
|
|
||||||
try {
|
|
||||||
const imageData = ctx.getImageData(0, 0, width, height);
|
|
||||||
|
|
||||||
// Use RGBA data directly
|
|
||||||
const pbd = Filament.PixelBuffer(
|
|
||||||
new Uint8Array(imageData.data.buffer),
|
|
||||||
PixelDataFormat.RGBA,
|
|
||||||
PixelDataType.UBYTE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.milkyWayTextureObj.setImage(this.engine, 0, pbd);
|
|
||||||
this.milkyWayTextureObj.generateMipmaps(this.engine);
|
|
||||||
|
|
||||||
const sampler = new TextureSampler(
|
|
||||||
MinFilter.LINEAR_MIPMAP_LINEAR,
|
|
||||||
MagFilter.LINEAR,
|
|
||||||
WrapMode.REPEAT // Equirectangular wraps horizontally
|
|
||||||
);
|
|
||||||
|
|
||||||
this.materialInstance.setTextureParameter('milkyWayTexture', this.milkyWayTextureObj, sampler);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn("Milky Way texture data access failed (CORS?):", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to load milky way texture:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateCoefficients();
|
this.updateCoefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,16 +84,16 @@ class SimulatedSkybox {
|
|||||||
|
|
||||||
this.ib.setBuffer(this.engine, TRIANGLE_INDICES);
|
this.ib.setBuffer(this.engine, TRIANGLE_INDICES);
|
||||||
|
|
||||||
// Build the Renderable without material; it will be set later by loadMaterial.
|
// We create a dummy material first or wait?
|
||||||
|
// In JS we usually can't block. We'll rely on loadMaterial being called.
|
||||||
|
// For now, we build the Renderable without material, then set it later.
|
||||||
|
|
||||||
RenderableManager.Builder(1)
|
RenderableManager.Builder(1)
|
||||||
.geometry(0, PrimitiveType.TRIANGLES, this.vb, this.ib)
|
.geometry(0, PrimitiveType.TRIANGLES, this.vb, this.ib)
|
||||||
.culling(false)
|
.culling(false)
|
||||||
.castShadows(false)
|
.castShadows(false)
|
||||||
.receiveShadows(false)
|
.receiveShadows(false)
|
||||||
.priority(7) // Skybox priority
|
.priority(7) // Render behind translucent objects? 7 is skybox priority typically.
|
||||||
|
|
||||||
.build(this.engine, this.entity);
|
.build(this.engine, this.entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,95 +215,11 @@ class SimulatedSkybox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setStarControl(density, enabled) {
|
setStarControl(density, enabled) {
|
||||||
// Compensate for grid frequency reduction (350 -> 100)
|
this.starControl[0] = Math.max(0.0, Math.min(1.0, density));
|
||||||
// Fewer cells = fewer stars, so we increase density threshold.
|
|
||||||
// Factor ~ (350/100)^2 = 12.25
|
|
||||||
const compensatedDensity = density * 12.0;
|
|
||||||
this.starControl[0] = Math.max(0.0, Math.min(1.0, compensatedDensity));
|
|
||||||
this.starControl[1] = enabled ? 1.0 : 0.0;
|
this.starControl[1] = enabled ? 1.0 : 0.0;
|
||||||
this.updateCoefficients();
|
this.updateCoefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
setFocalLength(mm) {
|
|
||||||
this.focalLength = Math.max(1.0, mm);
|
|
||||||
this.updateStarFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
setResolution(height) {
|
|
||||||
this.height = Math.max(1.0, height);
|
|
||||||
this.updateStarFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStarFrequency() {
|
|
||||||
// World-Anchored Stars
|
|
||||||
// z = Fixed Frequency (World Space Grid)
|
|
||||||
// w = Pixel Scale (Screen Space Radius)
|
|
||||||
|
|
||||||
// Fixed Frequency: Defines the "Universe" coordinate system.
|
|
||||||
// Reduced to 100.0 to allow larger stars without clipping (square artifacts).
|
|
||||||
this.starControl[2] = 100.0;
|
|
||||||
|
|
||||||
// Pixel Scale in Radians
|
|
||||||
// We use linear scaling (24/f) instead of atan(fov) to ensure star size remains
|
|
||||||
// constant in pixels across all focal lengths (Perspective Projection).
|
|
||||||
const fovFactor = 24.0 / this.focalLength;
|
|
||||||
const pixelScale = (1.0 / this.height) * fovFactor;
|
|
||||||
|
|
||||||
// Pass to shader (w component)
|
|
||||||
// Target radius: 1.3 pixels (Diameter 2.6 pixels)
|
|
||||||
// Visible but sharp.
|
|
||||||
this.starControl[3] = pixelScale * 1.3;
|
|
||||||
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoonPosition(direction) {
|
|
||||||
// normalize
|
|
||||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
|
||||||
if (len > 0) {
|
|
||||||
this.moonDirection = [direction[0] / len, direction[1] / len, direction[2] / len];
|
|
||||||
}
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoonIntensity(intensity) {
|
|
||||||
this.moonIntensity = Math.max(0.0, intensity);
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoonRadius(degrees) {
|
|
||||||
const rad = degrees * (Math.PI / 180.0);
|
|
||||||
this.moonHalo[0] = Math.cos(rad);
|
|
||||||
this.moonHalo[1] = Math.sin(rad);
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoonEnabled(enabled) {
|
|
||||||
this.moonHalo[3] = enabled ? 1.0 : 0.0;
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMilkyWayControl(intensity, saturation, blackPoint) {
|
|
||||||
this.milkyWayControl[0] = Math.max(0.0, intensity);
|
|
||||||
this.milkyWayControl[1] = Math.max(0.0, saturation);
|
|
||||||
if (blackPoint !== undefined) {
|
|
||||||
this.milkyWayControl[2] = Math.max(0.0, blackPoint);
|
|
||||||
}
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMilkyWayEnabled(enabled) {
|
|
||||||
this.milkyWayEnabled = !!enabled;
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMilkyWayRotation(rotationMatrix) {
|
|
||||||
if (rotationMatrix && rotationMatrix.length === 9) {
|
|
||||||
this.milkyWayRotation = rotationMatrix;
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCoefficients() {
|
updateCoefficients() {
|
||||||
if (!this.materialInstance) {
|
if (!this.materialInstance) {
|
||||||
console.warn("updateCoefficients called before material loaded");
|
console.warn("updateCoefficients called before material loaded");
|
||||||
@@ -577,130 +297,15 @@ class SimulatedSkybox {
|
|||||||
|
|
||||||
this.materialInstance.setFloatParameter('contrast', this.contrast);
|
this.materialInstance.setFloatParameter('contrast', this.contrast);
|
||||||
|
|
||||||
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity); // Lux scaling
|
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity);
|
||||||
this.materialInstance.setFloat3Parameter('nightColor', new Float32Array(nightColorScaled));
|
this.materialInstance.setFloat3Parameter('nightColor', new Float32Array(nightColorScaled));
|
||||||
|
|
||||||
this.materialInstance.setFloat4Parameter('shimmerControl', new Float32Array(shimmerUniform));
|
this.materialInstance.setFloat4Parameter('shimmerControl', new Float32Array(shimmerUniform));
|
||||||
this.materialInstance.setFloat4Parameter('cloudControl', new Float32Array(cloudUniform));
|
this.materialInstance.setFloat4Parameter('cloudControl', new Float32Array(cloudUniform));
|
||||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||||
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
this.materialInstance.setFloat2Parameter('starControl', new Float32Array(this.starControl));
|
||||||
|
|
||||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||||
|
|
||||||
// Moon Upload (Secondary Sun)
|
|
||||||
this.materialInstance.setFloat3Parameter('sunDirection2', new Float32Array(this.moonDirection));
|
|
||||||
|
|
||||||
// Calculate Moon Phase Factor (Lambertian Sphere)
|
|
||||||
// We model the moon as a Lambertian sphere to calculate its integrated brightness (illuminance)
|
|
||||||
// based on the phase angle (angle between Sun-Moon and Observer-Moon vectors).
|
|
||||||
//
|
|
||||||
// Phase Angle (alpha):
|
|
||||||
// For a distant observer (Earth), the phase angle can be approximated as the angle between
|
|
||||||
// the vector to the Sun and the vector to the Earth (from the Moon).
|
|
||||||
// cos(alpha) = -dot(L_moon, L_sun)
|
|
||||||
//
|
|
||||||
// Lambertian Phase Law:
|
|
||||||
// The integrated flux of a lit sphere varies as:
|
|
||||||
// Phi(alpha) = (1/PI) * (sin(alpha) + (PI - alpha) * cos(alpha))
|
|
||||||
// This gives 1.0 at Full Moon (alpha=0) and 0.0 at New Moon (alpha=PI).
|
|
||||||
|
|
||||||
const dotSM = this.sunDirection[0] * this.moonDirection[0] +
|
|
||||||
this.sunDirection[1] * this.moonDirection[1] +
|
|
||||||
this.sunDirection[2] * this.moonDirection[2];
|
|
||||||
|
|
||||||
// Final Intensity = Peak * Scale (No Phase Factor - Phase is handled in Shader via N.L)
|
|
||||||
const MOON_PEAK_LUX = 1.0;
|
|
||||||
const finalMoonIntensity = MOON_PEAK_LUX * this.moonIntensity;
|
|
||||||
|
|
||||||
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
|
||||||
|
|
||||||
// Scale Milky Way by Sun Intensity (Pre-exposed) to match dynamic range
|
|
||||||
// Calibration: 1.0 User Intensity ~ 0.025 Lux Relative (approx 2.5% of Sun Pixel Value at Day)
|
|
||||||
// At Sunny 16 (Sun ~ 2.6), this gives ~0.065 pixel brightness, which is visible but dim.
|
|
||||||
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
|
||||||
const mwUniform = [
|
|
||||||
mwIntensity * this.sunIntensity * 0.025,
|
|
||||||
this.milkyWayControl[1],
|
|
||||||
this.milkyWayControl[2]
|
|
||||||
];
|
|
||||||
this.materialInstance.setFloat3Parameter('milkyWayControl', new Float32Array(mwUniform));
|
|
||||||
this.materialInstance.setMat3Parameter('milkyWayRotation', new Float32Array(this.milkyWayRotation));
|
|
||||||
|
|
||||||
// Moon Halo Upload (Disk Visualization)
|
|
||||||
// Multiplier = 1.0 / SolidAngle
|
|
||||||
const moonSolidAngle = 2.0 * F_PI * (1.0 - this.moonHalo[0]);
|
|
||||||
const moonRadConv = 1.0 / Math.max(1e-9, moonSolidAngle);
|
|
||||||
const moonHaloUpload = [...this.moonHalo];
|
|
||||||
moonHaloUpload[2] *= moonRadConv;
|
|
||||||
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
|
||||||
|
|
||||||
// Solar Eclipse (CPU Calculation)
|
|
||||||
const sunRadius = Math.acos(this.sunHalo[0]);
|
|
||||||
const moonRadius = Math.acos(this.moonHalo[0]);
|
|
||||||
// Dot product of Sun and Moon directions
|
|
||||||
const dot = this.sunDirection[0] * this.moonDirection[0] +
|
|
||||||
this.sunDirection[1] * this.moonDirection[1] +
|
|
||||||
this.sunDirection[2] * this.moonDirection[2];
|
|
||||||
const separation = Math.acos(Math.max(-1.0, Math.min(1.0, dot)));
|
|
||||||
|
|
||||||
let eclipseFactor = 1.0;
|
|
||||||
// Only calculate if moon is enabled
|
|
||||||
if (this.moonHalo[3] > 0.5) {
|
|
||||||
const overlap = this.areaIntersection(sunRadius, moonRadius, separation);
|
|
||||||
const sunArea = Math.PI * sunRadius * sunRadius;
|
|
||||||
// Ensure we don't divide by zero and clamp result
|
|
||||||
const ratio = overlap / Math.max(1e-9, sunArea);
|
|
||||||
eclipseFactor = 1.0 - Math.max(0.0, Math.min(1.0, ratio));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safety check for NaN
|
|
||||||
if (isNaN(eclipseFactor)) {
|
|
||||||
console.warn("SimulatedSkybox: eclipseFactor is NaN, resetting to 1.0");
|
|
||||||
eclipseFactor = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.materialInstance.setFloatParameter('eclipseFactor', eclipseFactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
areaIntersection(r1, r2, d) {
|
|
||||||
// Circle intersection area
|
|
||||||
// r1, r2: radii
|
|
||||||
// d: distance between centers
|
|
||||||
|
|
||||||
// Case 1: Too far apart
|
|
||||||
if (d >= r1 + r2) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
// Case 2: One inside another
|
|
||||||
if (d <= Math.abs(r1 - r2)) {
|
|
||||||
return Math.PI * Math.min(r1, r2) * Math.min(r1, r2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const r1sq = r1 * r1;
|
|
||||||
const r2sq = r2 * r2;
|
|
||||||
|
|
||||||
// Law of Cosines for sector angles
|
|
||||||
// c1 = (d^2 + r1^2 - r2^2) / (2 * d * r1)
|
|
||||||
// c2 = (d^2 + r2^2 - r1^2) / (2 * d * r2)
|
|
||||||
// We clamp to [-1, 1] to avoid NaN from floating point errors
|
|
||||||
const c1 = Math.max(-1.0, Math.min(1.0, (d * d + r1sq - r2sq) / (2.0 * d * r1)));
|
|
||||||
const c2 = Math.max(-1.0, Math.min(1.0, (d * d + r2sq - r1sq) / (2.0 * d * r2)));
|
|
||||||
|
|
||||||
const part1 = r1sq * Math.acos(c1);
|
|
||||||
const part2 = r2sq * Math.acos(c2);
|
|
||||||
|
|
||||||
// Heron's formula for the triangle area * 2
|
|
||||||
// The sqrt term represents the area of the two triangles formed by the chord and centers.
|
|
||||||
|
|
||||||
|
|
||||||
// Robust sqrt
|
|
||||||
const val = (-d + r1 + r2) * (d + r1 - r2) * (d - r1 + r2) * (d + r1 + r2);
|
|
||||||
const part3 = 0.5 * Math.sqrt(Math.max(0.0, val));
|
|
||||||
|
|
||||||
return part1 + part2 - part3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 596 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 119 KiB |
@@ -1,332 +0,0 @@
|
|||||||
/*
|
|
||||||
(c) 2011-2015, Vladimir Agafonkin
|
|
||||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
|
||||||
https://github.com/mourner/suncalc
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function () { 'use strict';
|
|
||||||
|
|
||||||
// shortcuts for easier to read formulas
|
|
||||||
|
|
||||||
var PI = Math.PI,
|
|
||||||
sin = Math.sin,
|
|
||||||
cos = Math.cos,
|
|
||||||
tan = Math.tan,
|
|
||||||
asin = Math.asin,
|
|
||||||
atan = Math.atan2,
|
|
||||||
acos = Math.acos,
|
|
||||||
rad = PI / 180;
|
|
||||||
|
|
||||||
// sun calculations are based on https://aa.quae.nl/en/reken/zonpositie.html formulas
|
|
||||||
|
|
||||||
|
|
||||||
// date/time constants and conversions
|
|
||||||
|
|
||||||
var dayMs = 1000 * 60 * 60 * 24,
|
|
||||||
J1970 = 2440588,
|
|
||||||
J2000 = 2451545;
|
|
||||||
|
|
||||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
|
||||||
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
|
|
||||||
function toDays(date) { return toJulian(date) - J2000; }
|
|
||||||
|
|
||||||
|
|
||||||
// general calculations for position
|
|
||||||
|
|
||||||
var e = rad * 23.4397; // obliquity of the Earth
|
|
||||||
|
|
||||||
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
|
|
||||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
|
||||||
|
|
||||||
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
|
|
||||||
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
|
|
||||||
|
|
||||||
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
|
|
||||||
|
|
||||||
function astroRefraction(h) {
|
|
||||||
if (h < 0) // the following formula works for positive altitudes only.
|
|
||||||
h = 0; // if h = -0.08901179 a div/0 would occur.
|
|
||||||
|
|
||||||
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
|
||||||
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
|
|
||||||
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
|
|
||||||
}
|
|
||||||
|
|
||||||
// general sun calculations
|
|
||||||
|
|
||||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
|
||||||
|
|
||||||
function eclipticLongitude(M) {
|
|
||||||
|
|
||||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
|
||||||
P = rad * 102.9372; // perihelion of the Earth
|
|
||||||
|
|
||||||
return M + C + P + PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sunCoords(d) {
|
|
||||||
|
|
||||||
var M = solarMeanAnomaly(d),
|
|
||||||
L = eclipticLongitude(M);
|
|
||||||
|
|
||||||
return {
|
|
||||||
dec: declination(L, 0),
|
|
||||||
ra: rightAscension(L, 0)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var SunCalc = {};
|
|
||||||
|
|
||||||
|
|
||||||
// calculates sun position for a given date and latitude/longitude
|
|
||||||
|
|
||||||
SunCalc.getPosition = function (date, lat, lng) {
|
|
||||||
|
|
||||||
var lw = rad * -lng,
|
|
||||||
phi = rad * lat,
|
|
||||||
d = toDays(date),
|
|
||||||
|
|
||||||
c = sunCoords(d),
|
|
||||||
H = siderealTime(d, lw) - c.ra;
|
|
||||||
|
|
||||||
return {
|
|
||||||
azimuth: azimuth(H, phi, c.dec),
|
|
||||||
altitude: altitude(H, phi, c.dec)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// sun times configuration (angle, morning name, evening name)
|
|
||||||
|
|
||||||
var times = SunCalc.times = [
|
|
||||||
[-0.833, 'sunrise', 'sunset' ],
|
|
||||||
[-0.3, 'sunriseEnd', 'sunsetStart' ],
|
|
||||||
[-6, 'dawn', 'dusk' ],
|
|
||||||
[-12, 'nauticalDawn', 'nauticalDusk'],
|
|
||||||
[-18, 'nightEnd', 'night' ],
|
|
||||||
[6, 'goldenHourEnd', 'goldenHour' ]
|
|
||||||
];
|
|
||||||
|
|
||||||
// adds a custom time to the times config
|
|
||||||
|
|
||||||
SunCalc.addTime = function (angle, riseName, setName) {
|
|
||||||
times.push([angle, riseName, setName]);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// calculations for sun times
|
|
||||||
|
|
||||||
var J0 = 0.0009;
|
|
||||||
|
|
||||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
|
||||||
|
|
||||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
|
||||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
|
||||||
|
|
||||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
|
||||||
|
|
||||||
// returns set time for the given sun altitude
|
|
||||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
|
||||||
|
|
||||||
var w = hourAngle(h, phi, dec),
|
|
||||||
a = approxTransit(w, lw, n);
|
|
||||||
return solarTransitJ(a, M, L);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
|
||||||
// the observer height (in meters) relative to the horizon
|
|
||||||
|
|
||||||
SunCalc.getTimes = function (date, lat, lng, height) {
|
|
||||||
|
|
||||||
height = height || 0;
|
|
||||||
|
|
||||||
var lw = rad * -lng,
|
|
||||||
phi = rad * lat,
|
|
||||||
|
|
||||||
dh = observerAngle(height),
|
|
||||||
|
|
||||||
d = toDays(date),
|
|
||||||
n = julianCycle(d, lw),
|
|
||||||
ds = approxTransit(0, lw, n),
|
|
||||||
|
|
||||||
M = solarMeanAnomaly(ds),
|
|
||||||
L = eclipticLongitude(M),
|
|
||||||
dec = declination(L, 0),
|
|
||||||
|
|
||||||
Jnoon = solarTransitJ(ds, M, L);
|
|
||||||
|
|
||||||
var result = {
|
|
||||||
solarNoon: fromJulian(Jnoon),
|
|
||||||
nadir: fromJulian(Jnoon - 0.5)
|
|
||||||
};
|
|
||||||
|
|
||||||
var i, len, time, h0, Jset, Jrise;
|
|
||||||
|
|
||||||
for (i = 0, len = times.length; i < len; i += 1) {
|
|
||||||
time = times[i];
|
|
||||||
|
|
||||||
h0 = (time[0] + dh) * rad;
|
|
||||||
|
|
||||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
|
||||||
Jrise = Jnoon - (Jset - Jnoon);
|
|
||||||
|
|
||||||
result[time[1]] = fromJulian(Jrise);
|
|
||||||
result[time[2]] = fromJulian(Jset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
|
|
||||||
|
|
||||||
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
|
|
||||||
|
|
||||||
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
|
|
||||||
M = rad * (134.963 + 13.064993 * d), // mean anomaly
|
|
||||||
F = rad * (93.272 + 13.229350 * d), // mean distance
|
|
||||||
|
|
||||||
l = L + rad * 6.289 * sin(M), // longitude
|
|
||||||
b = rad * 5.128 * sin(F), // latitude
|
|
||||||
dt = 385001 - 20905 * cos(M); // distance to the moon in km
|
|
||||||
|
|
||||||
return {
|
|
||||||
ra: rightAscension(l, b),
|
|
||||||
dec: declination(l, b),
|
|
||||||
dist: dt
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
SunCalc.getMoonPosition = function (date, lat, lng) {
|
|
||||||
|
|
||||||
var lw = rad * -lng,
|
|
||||||
phi = rad * lat,
|
|
||||||
d = toDays(date),
|
|
||||||
|
|
||||||
c = moonCoords(d),
|
|
||||||
H = siderealTime(d, lw) - c.ra,
|
|
||||||
h = altitude(H, phi, c.dec),
|
|
||||||
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
|
||||||
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
|
|
||||||
|
|
||||||
return {
|
|
||||||
azimuth: azimuth(H, phi, c.dec),
|
|
||||||
altitude: h + astroRefraction(h), // altitude correction for refraction,
|
|
||||||
distance: c.dist,
|
|
||||||
parallacticAngle: pa
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// calculations for illumination parameters of the moon,
|
|
||||||
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
|
|
||||||
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
|
||||||
|
|
||||||
SunCalc.getMoonIllumination = function (date) {
|
|
||||||
|
|
||||||
var d = toDays(date || new Date()),
|
|
||||||
s = sunCoords(d),
|
|
||||||
m = moonCoords(d),
|
|
||||||
|
|
||||||
sdist = 149598000, // distance from Earth to Sun in km
|
|
||||||
|
|
||||||
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
|
|
||||||
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
|
|
||||||
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
|
|
||||||
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
|
|
||||||
|
|
||||||
return {
|
|
||||||
fraction: (1 + cos(inc)) / 2,
|
|
||||||
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
|
|
||||||
angle: angle
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function hoursLater(date, h) {
|
|
||||||
return new Date(date.valueOf() + h * dayMs / 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
|
||||||
|
|
||||||
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
|
|
||||||
var t = new Date(date);
|
|
||||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
|
||||||
else t.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
var hc = 0.133 * rad,
|
|
||||||
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
|
|
||||||
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
|
|
||||||
|
|
||||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
|
||||||
for (var i = 1; i <= 24; i += 2) {
|
|
||||||
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
|
||||||
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
|
||||||
|
|
||||||
a = (h0 + h2) / 2 - h1;
|
|
||||||
b = (h2 - h0) / 2;
|
|
||||||
xe = -b / (2 * a);
|
|
||||||
ye = (a * xe + b) * xe + h1;
|
|
||||||
d = b * b - 4 * a * h1;
|
|
||||||
roots = 0;
|
|
||||||
|
|
||||||
if (d >= 0) {
|
|
||||||
dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
|
||||||
x1 = xe - dx;
|
|
||||||
x2 = xe + dx;
|
|
||||||
if (Math.abs(x1) <= 1) roots++;
|
|
||||||
if (Math.abs(x2) <= 1) roots++;
|
|
||||||
if (x1 < -1) x1 = x2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (roots === 1) {
|
|
||||||
if (h0 < 0) rise = i + x1;
|
|
||||||
else set = i + x1;
|
|
||||||
|
|
||||||
} else if (roots === 2) {
|
|
||||||
rise = i + (ye < 0 ? x2 : x1);
|
|
||||||
set = i + (ye < 0 ? x1 : x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rise && set) break;
|
|
||||||
|
|
||||||
h0 = h2;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = {};
|
|
||||||
|
|
||||||
if (rise) result.rise = hoursLater(t, rise);
|
|
||||||
if (set) result.set = hoursLater(t, set);
|
|
||||||
|
|
||||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// observerAngle helper was missing in my copy-paste?
|
|
||||||
// Ah, it was in the chunks. Let me check the chunks again.
|
|
||||||
// Function `observerAngle` was in chunk 0.
|
|
||||||
// `function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }`
|
|
||||||
// I included it.
|
|
||||||
|
|
||||||
// Note: I replaced var with const/let or kept var?
|
|
||||||
// The original used const/let but I used var in my head above?
|
|
||||||
// I will just use the original code but adapted.
|
|
||||||
// Actually, to be safe, I'll use the code I wrote in this block which is adapted to use 'var' (ES5 safe) or just ES6 is fine.
|
|
||||||
// The user Environment is modern (Chrome/ES6 likely).
|
|
||||||
// But for safety:
|
|
||||||
// I'll stick to 'var' definitions I wrote above which seem complete?
|
|
||||||
// Wait, I might have missed `observerAngle` definition in my manual reconstruction above?
|
|
||||||
// No, `observerAngle` is called in `getTimes`.
|
|
||||||
// I MUST define it.
|
|
||||||
// I added it in the loop above? No.
|
|
||||||
// Let me double check the `write_to_file` content I prepared.
|
|
||||||
// I see:
|
|
||||||
// `function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }`
|
|
||||||
// Yes, it is there.
|
|
||||||
|
|
||||||
// export
|
|
||||||
window.SunCalc = SunCalc;
|
|
||||||
|
|
||||||
}());
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
# Result: /Users/mathias/sources/git/filament/out/cmake-release/tools/matc/matc
|
# Result: /Users/mathias/sources/git/filament/out/cmake-release/tools/matc/matc
|
||||||
MATC="../../../../../out/cmake-release/tools/matc/matc"
|
MATC="../../../../out/cmake-release/tools/matc/matc"
|
||||||
|
|
||||||
# Navigate to script directory to ensure relative paths work
|
# Navigate to script directory to ensure relative paths work
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
$MATC -a opengl -p mobile -o ../assets/simulated_skybox.filamat ../simulated_skybox.mat
|
$MATC -a opengl -p mobile -o assets/simulated_skybox.filamat simulated_skybox.mat
|
||||||
echo "Material recompiled to assets/simulated_skybox.filamat"
|
echo "Material recompiled to assets/simulated_skybox.filamat"
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<canvas></canvas>
|
<canvas></canvas>
|
||||||
|
|
||||||
<!-- Filament -->
|
<!-- Filament -->
|
||||||
<script src="filament.js"></script>
|
<script src="filament.js"></script>
|
||||||
<script src="gl-matrix-min.js"></script>
|
<script src="gl-matrix-min.js"></script>
|
||||||
@@ -32,13 +31,9 @@
|
|||||||
<!-- UI -->
|
<!-- UI -->
|
||||||
<script src="lil-gui.js"></script>
|
<script src="lil-gui.js"></script>
|
||||||
|
|
||||||
<!-- Utils -->
|
|
||||||
<script src="assets/suncalc_global.js"></script>
|
|
||||||
|
|
||||||
<!-- App -->
|
<!-- App -->
|
||||||
|
<script src="SimulatedSkybox.js?v=26"></script>
|
||||||
<script src="SimulatedSkybox.js?v=9999"></script>
|
<script src="main.js?v=26"></script>
|
||||||
<script src="main.js?v=9999"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,123 +1,10 @@
|
|||||||
|
|
||||||
// main.js
|
// main.js
|
||||||
|
|
||||||
Filament.init(['assets/simulated_skybox.filamat?v=' + Date.now()], () => {
|
Filament.init(['assets/simulated_skybox.filamat'], () => {
|
||||||
window.app = new App(document.getElementsByTagName('canvas')[0]);
|
window.app = new App(document.getElementsByTagName('canvas')[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper: Julian Date
|
|
||||||
function getJD(date) {
|
|
||||||
return date.getTime() / 86400000.0 + 2440587.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: GMST from Date
|
|
||||||
function getGMST(date) {
|
|
||||||
const JD = getJD(date);
|
|
||||||
const D = JD - 2451545.0;
|
|
||||||
// GMST = 18.697... + 24.0657... * D
|
|
||||||
let gmst = 18.697374558 + 24.06570982441908 * D;
|
|
||||||
gmst = gmst % 24.0;
|
|
||||||
if (gmst < 0) gmst += 24.0;
|
|
||||||
return gmst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Matrix Rotation
|
|
||||||
function rotateX(m, angle) {
|
|
||||||
const c = Math.cos(angle);
|
|
||||||
const s = Math.sin(angle);
|
|
||||||
const m1 = m[1], m2 = m[2];
|
|
||||||
const m4 = m[4], m5 = m[5];
|
|
||||||
const m7 = m[7], m8 = m[8];
|
|
||||||
m[1] = m1 * c - m2 * s;
|
|
||||||
m[2] = m1 * s + m2 * c;
|
|
||||||
m[4] = m4 * c - m5 * s;
|
|
||||||
m[5] = m4 * s + m5 * c;
|
|
||||||
m[7] = m7 * c - m8 * s;
|
|
||||||
m[8] = m7 * s + m8 * c;
|
|
||||||
}
|
|
||||||
function rotateY(m, angle) {
|
|
||||||
const c = Math.cos(angle);
|
|
||||||
const s = Math.sin(angle);
|
|
||||||
const m0 = m[0], m2 = m[2];
|
|
||||||
const m3 = m[3], m5 = m[5];
|
|
||||||
const m6 = m[6], m8 = m[8];
|
|
||||||
m[0] = m0 * c + m2 * s;
|
|
||||||
m[2] = -m0 * s + m2 * c;
|
|
||||||
m[3] = m3 * c + m5 * s;
|
|
||||||
m[5] = -m3 * s + m5 * c;
|
|
||||||
m[6] = m6 * c + m8 * s;
|
|
||||||
m[8] = -m6 * s + m8 * c;
|
|
||||||
}
|
|
||||||
function rotateZ(m, angle) {
|
|
||||||
const c = Math.cos(angle);
|
|
||||||
const s = Math.sin(angle);
|
|
||||||
const m0 = m[0], m1 = m[1];
|
|
||||||
const m3 = m[3], m4 = m[4];
|
|
||||||
const m6 = m[6], m7 = m[7];
|
|
||||||
m[0] = m0 * c - m1 * s;
|
|
||||||
m[1] = m0 * s + m1 * c;
|
|
||||||
m[3] = m3 * c - m4 * s;
|
|
||||||
m[4] = m3 * s + m4 * c;
|
|
||||||
m[6] = m6 * c - m7 * s;
|
|
||||||
m[7] = m6 * s + m7 * c;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Galactic to Equatorial (J2000)
|
|
||||||
// This matrix converts Galactic vectors to Equatorial vectors.
|
|
||||||
// Or effectively, if we want to render Galactic texture from Equatorial View vector V_eq:
|
|
||||||
// V_gal = Inv(Rot_Gal_to_Eq) * V_eq = Rot_Eq_to_Gal * V_eq.
|
|
||||||
// The shader does: V_gal = Rotation * V_world.
|
|
||||||
// So Rotation = Rot_Eq_to_Gal * Rot_World_to_Eq.
|
|
||||||
//
|
|
||||||
// Galactic North Pole (J2000): RA = 192.85948, Dec = 27.12825
|
|
||||||
// Ascending Node: RA = 282.85
|
|
||||||
//
|
|
||||||
// Pre-computed Rotation Matrix (Equatorial -> Galactic)
|
|
||||||
// Based on standard transformation matrices.
|
|
||||||
//
|
|
||||||
// R_eq_gal =
|
|
||||||
// [ -0.054876 -0.873437 -0.483835 ]
|
|
||||||
// [ 0.494109 -0.444830 0.746982 ]
|
|
||||||
// [ -0.867666 -0.198076 0.455984 ]
|
|
||||||
//
|
|
||||||
// Let's use this static definition.
|
|
||||||
const MAT_EQ_TO_GAL = [
|
|
||||||
-0.054876, 0.494109, -0.867666,
|
|
||||||
-0.873437, -0.444830, -0.198076,
|
|
||||||
-0.483835, 0.746982, 0.455984
|
|
||||||
];
|
|
||||||
|
|
||||||
// Matrix multiplication 3x3
|
|
||||||
function multiplyMat3(a, b) {
|
|
||||||
const out = new Float32Array(9);
|
|
||||||
// Row-major or Column-major? Filament is Column-major usually.
|
|
||||||
// GLSL is Column-major.
|
|
||||||
// Mat3 in array: [col0.x, col0.y, col0.z, col1.x, ...]
|
|
||||||
// So a[0] is (0,0), a[1] is (1,0), a[3] is (0,1).
|
|
||||||
//
|
|
||||||
// out = a * b
|
|
||||||
const a00 = a[0], a10 = a[1], a20 = a[2];
|
|
||||||
const a01 = a[3], a11 = a[4], a21 = a[5];
|
|
||||||
const a02 = a[6], a12 = a[7], a22 = a[8];
|
|
||||||
|
|
||||||
const b00 = b[0], b10 = b[1], b20 = b[2];
|
|
||||||
const b01 = b[3], b11 = b[4], b21 = b[5];
|
|
||||||
const b02 = b[6], b12 = b[7], b22 = b[8];
|
|
||||||
|
|
||||||
out[0] = a00 * b00 + a01 * b10 + a02 * b20;
|
|
||||||
out[1] = a10 * b00 + a11 * b10 + a12 * b20;
|
|
||||||
out[2] = a20 * b00 + a21 * b10 + a22 * b20;
|
|
||||||
|
|
||||||
out[3] = a00 * b01 + a01 * b11 + a02 * b21;
|
|
||||||
out[4] = a10 * b01 + a11 * b11 + a12 * b21;
|
|
||||||
out[5] = a20 * b01 + a21 * b11 + a22 * b21;
|
|
||||||
|
|
||||||
out[6] = a00 * b02 + a01 * b12 + a02 * b22;
|
|
||||||
out[7] = a10 * b02 + a11 * b12 + a12 * b22;
|
|
||||||
out[8] = a20 * b02 + a21 * b12 + a22 * b22;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
constructor(canvas) {
|
constructor(canvas) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
@@ -128,10 +15,13 @@ class App {
|
|||||||
this.skybox.entity = this.skybox.entity; // Ensuring access if needed
|
this.skybox.entity = this.skybox.entity; // Ensuring access if needed
|
||||||
this.scene.addEntity(this.skybox.entity);
|
this.scene.addEntity(this.skybox.entity);
|
||||||
|
|
||||||
// Load the material explicitly. SimulatedSkybox.loadMaterial fetches it.
|
// Load the material explicitly since we passed it to init but SimulatedSkybox needs to bind it
|
||||||
|
// Actually SimulatedSkybox.loadMaterial fetches it.
|
||||||
const matUrl = 'assets/simulated_skybox.filamat?v=' + Date.now();
|
// Since we already loaded it in Filament.init, we can arguably just use it if we had a way to access the asset.
|
||||||
this.skybox.loadMaterial(matUrl).then(() => {
|
// But Filament.init assets are for internal or easy access via assets object if configured?
|
||||||
|
// Let's just let SimulatedSkybox fetch it again or use a blob if we wanted.
|
||||||
|
// Simpler: Just let SimulatedSkybox fetch it.
|
||||||
|
this.skybox.loadMaterial('assets/simulated_skybox.filamat').then(() => {
|
||||||
this.initGUI();
|
this.initGUI();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -181,15 +71,6 @@ class App {
|
|||||||
|
|
||||||
this.initControls(); // Initialize controls immediately
|
this.initControls(); // Initialize controls immediately
|
||||||
|
|
||||||
this.mwParams = {
|
|
||||||
enabled: true,
|
|
||||||
intensity: 1.0,
|
|
||||||
saturation: 1.0,
|
|
||||||
blackPoint: 0.07,
|
|
||||||
siderealTime: 0.0, // Hours [0-24]
|
|
||||||
latitude: 34.0, // Default Lat
|
|
||||||
};
|
|
||||||
|
|
||||||
this.resize();
|
this.resize();
|
||||||
window.addEventListener('resize', this.resize.bind(this));
|
window.addEventListener('resize', this.resize.bind(this));
|
||||||
|
|
||||||
@@ -218,12 +99,6 @@ class App {
|
|||||||
const exposure = this.getExposure();
|
const exposure = this.getExposure();
|
||||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||||
this.skybox.setSunIntensity(preExposedIntensity);
|
this.skybox.setSunIntensity(preExposedIntensity);
|
||||||
|
|
||||||
// Moon Exposure
|
|
||||||
if (this.mParams) {
|
|
||||||
const preExposedMoon = this.mParams.intensity * exposure;
|
|
||||||
this.skybox.setMoonIntensity(preExposedMoon);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCameraProjection() {
|
updateCameraProjection() {
|
||||||
@@ -231,7 +106,6 @@ class App {
|
|||||||
const height = this.canvas.height;
|
const height = this.canvas.height;
|
||||||
const aspect = width / height;
|
const aspect = width / height;
|
||||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||||
if (this.skybox) this.skybox.setFocalLength(this.params.focalLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initGUI() {
|
initGUI() {
|
||||||
@@ -240,7 +114,10 @@ class App {
|
|||||||
const sky = this.skybox;
|
const sky = this.skybox;
|
||||||
|
|
||||||
// Initialize local params from skybox defaults
|
// Initialize local params from skybox defaults
|
||||||
|
// Initialize local params from skybox defaults
|
||||||
|
// REMOVED: Do not overwrite this.params from sky.sunDirection (Zenith)
|
||||||
|
// const currentDir = sky.sunDirection;
|
||||||
|
// this.params.sunTheta = ...
|
||||||
|
|
||||||
const updateSun = () => {
|
const updateSun = () => {
|
||||||
const theta = this.params.sunTheta;
|
const theta = this.params.sunTheta;
|
||||||
@@ -251,165 +128,29 @@ class App {
|
|||||||
sky.setSunPosition([x, y, z]);
|
sky.setSunPosition([x, y, z]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sun UI Proxy
|
|
||||||
this.sunUI = {
|
|
||||||
azimuth: (this.params.sunPhi * 180.0 / Math.PI) % 360.0,
|
|
||||||
height: Math.cos(this.params.sunTheta)
|
|
||||||
};
|
|
||||||
if (this.sunUI.azimuth < 0) this.sunUI.azimuth += 360.0;
|
|
||||||
|
|
||||||
const sunFolder = gui.addFolder('Sun');
|
const sunFolder = gui.addFolder('Sun');
|
||||||
|
// Helper for "Sun Height" cosine slider like C++
|
||||||
sunFolder.add(this.sunUI, 'azimuth', 0.0, 360.0, 0.1).name('Azimuth').listen().onChange(v => {
|
const sunHeightParam = { height: Math.cos(this.params.sunTheta) };
|
||||||
this.params.sunPhi = v * (Math.PI / 180.0);
|
sunFolder.add(sunHeightParam, 'height', -0.2, 1.0).name('Height (Cos)').onChange(v => {
|
||||||
updateSun();
|
|
||||||
});
|
|
||||||
|
|
||||||
sunFolder.add(this.sunUI, 'height', -0.2, 1.0).name('Height (Cos)').listen().onChange(v => {
|
|
||||||
this.params.sunTheta = Math.acos(v);
|
this.params.sunTheta = Math.acos(v);
|
||||||
updateSun();
|
updateSun();
|
||||||
});
|
});
|
||||||
|
sunFolder.add(this.params, 'sunPhi', 0.0, Math.PI * 2).name('Azimuth').onChange(updateSun);
|
||||||
|
// Updated: Controls params.sunIntensity and triggers updateSunIntensity
|
||||||
|
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).onChange(v => this.updateSunIntensity());
|
||||||
|
|
||||||
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).name('Intensity').onChange(v => this.updateSunIntensity());
|
const sunDisk = sunFolder.addFolder('Disk');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const moonFolder = gui.addFolder('Moon');
|
|
||||||
this.mParams = {
|
|
||||||
enabled: true,
|
|
||||||
azimuth: 180.0,
|
|
||||||
height: Math.cos(45.0 * Math.PI / 180.0), // Default 45 degrees elevation -> cos(45) ~ 0.707
|
|
||||||
radius: 1.2,
|
|
||||||
intensity: 6.0
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateMoon = () => {
|
|
||||||
const az = this.mParams.azimuth * (Math.PI / 180.0);
|
|
||||||
const theta = Math.acos(this.mParams.height);
|
|
||||||
const phi = az;
|
|
||||||
|
|
||||||
const x = Math.sin(theta) * Math.cos(phi);
|
|
||||||
const y = Math.cos(theta);
|
|
||||||
const z = Math.sin(theta) * Math.sin(phi);
|
|
||||||
|
|
||||||
sky.setMoonPosition([x, y, z]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial Moon Sync
|
|
||||||
updateMoon();
|
|
||||||
sky.setMoonEnabled(this.mParams.enabled);
|
|
||||||
sky.setMoonRadius(this.mParams.radius);
|
|
||||||
sky.setMoonIntensity(this.mParams.intensity);
|
|
||||||
|
|
||||||
moonFolder.add(this.mParams, 'enabled').name('Enabled').onChange(v => sky.setMoonEnabled(v));
|
|
||||||
moonFolder.add(this.mParams, 'azimuth', 0.0, 360.0, 0.1).name('Azimuth').listen().onChange(updateMoon);
|
|
||||||
moonFolder.add(this.mParams, 'height', -0.2, 1.0).name('Height (Cos)').listen().onChange(updateMoon);
|
|
||||||
moonFolder.add(this.mParams, 'intensity', 0.0, 1000.0).name('Intensity').onChange(v => this.updateSunIntensity());
|
|
||||||
moonFolder.add(this.mParams, 'radius', 0.1, 5.0).name('Radius').onChange(v => sky.setMoonRadius(v));
|
|
||||||
moonFolder.close();
|
|
||||||
|
|
||||||
const mwFolder = gui.addFolder('Milky Way');
|
|
||||||
|
|
||||||
const updateMW = () => {
|
|
||||||
sky.setMilkyWayEnabled(this.mwParams.enabled);
|
|
||||||
sky.setMilkyWayControl(this.mwParams.intensity, this.mwParams.saturation, this.mwParams.blackPoint);
|
|
||||||
|
|
||||||
// Calculate Rotation
|
|
||||||
// V_gal = Rot_Eq_to_Gal * Rot_World_to_Eq * V_world
|
|
||||||
|
|
||||||
// World: Y=Up, X=East, Z=South (Filament Camera Convention is different!)
|
|
||||||
// In Filament Camera: -Z is Forward.
|
|
||||||
// Skybox V direction is World Space direction.
|
|
||||||
// Let's assume standard Horizontal Coordinates:
|
|
||||||
// Y = Zenith.
|
|
||||||
// Z = North? Or South?
|
|
||||||
// Usually Z is South in RH Y-up.
|
|
||||||
|
|
||||||
// LST (Local Sidereal Time) converts Hour Angle to RA.
|
|
||||||
// LST in Radians.
|
|
||||||
const LST = this.mwParams.siderealTime * (Math.PI / 12.0); // Hours to Rad
|
|
||||||
const Lat = this.mwParams.latitude * (Math.PI / 180.0);
|
|
||||||
|
|
||||||
// Rotation World (Horizontal) -> Equatorial
|
|
||||||
// 1. Rotate around X by -(90 - Lat) to align Equatorial Plane.
|
|
||||||
// 2. Rotate around Y (Polar Axis) by -LST.
|
|
||||||
|
|
||||||
// Mat3 Identity
|
|
||||||
const rot = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
||||||
|
|
||||||
// Rotate Z by LST (Earth Rotation).
|
|
||||||
// Actually, transformation from Horizontal (Az, Alt) to Equatorial (HA, Dec):
|
|
||||||
// sin(Dec) = sin(Alt)sin(Lat) - cos(Alt)cos(Lat)cos(Az)
|
|
||||||
// ...
|
|
||||||
// Let's construct matrix directly.
|
|
||||||
// WorldToEq:
|
|
||||||
// Rotate X by (Lat - 90 deg) -> brings Pole to Zenith.
|
|
||||||
// Rotate Y by -LST (or Z?)
|
|
||||||
|
|
||||||
// Filament Space:
|
|
||||||
// +Y = Up
|
|
||||||
// Let's match typical skybox conventions.
|
|
||||||
|
|
||||||
// Rot_World_to_Eq = Rot_Z_LST * Rot_X_Lat
|
|
||||||
// But we need to use Filament matrix ops which are column major.
|
|
||||||
|
|
||||||
// Let's use simple rotations:
|
|
||||||
// 1. Tilt Pole: Rotate X by (Lat - 90).
|
|
||||||
// 2. Spin Earth: Rotate Y by LST.
|
|
||||||
|
|
||||||
// Let's iterate until it looks right visually or trust the math.
|
|
||||||
// Rot_World_To_Equatorial:
|
|
||||||
// R_z(-LST) * R_x(Lat - 90)?
|
|
||||||
|
|
||||||
// Let's build it from scratch in JS using helper.
|
|
||||||
// Start Identity.
|
|
||||||
// Rotate X (Latitude Tilt).
|
|
||||||
// Rotate Y (Sidereal Spin).
|
|
||||||
// Note: rotate functions modify in place.
|
|
||||||
|
|
||||||
const mWorldToEq = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
||||||
|
|
||||||
// 1. Tilt for Latitude (Align Celestial Pole)
|
|
||||||
// At Lat 90 (North Pole), Zenith is Pole. No tilt needed if Y is Pole?
|
|
||||||
// No, Y is Zenith. Pole is Y.
|
|
||||||
// At Lat 0 (Equator), Pole is at Horizon (Z?).
|
|
||||||
// So we rotate X by (Lat - 90).
|
|
||||||
rotateX(mWorldToEq, Lat - Math.PI / 2);
|
|
||||||
|
|
||||||
// 2. Spin for Time (LST)
|
|
||||||
// Rotate around new Pole (Y) by LST.
|
|
||||||
rotateY(mWorldToEq, LST);
|
|
||||||
|
|
||||||
// Combine with Gal Transform
|
|
||||||
// Rot = MAT_EQ_TO_GAL * mWorldToEq
|
|
||||||
const finalRot = multiplyMat3(MAT_EQ_TO_GAL, mWorldToEq);
|
|
||||||
|
|
||||||
sky.setMilkyWayRotation(finalRot);
|
|
||||||
};
|
|
||||||
|
|
||||||
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 5.0).onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'latitude', -90.0, 90.0).name('Latitude').onChange(updateMW);
|
|
||||||
mwFolder.close();
|
|
||||||
|
|
||||||
// Initial MW Update
|
|
||||||
updateMW();
|
|
||||||
|
|
||||||
this.updateMW = updateMW; // Export for sync
|
|
||||||
|
|
||||||
// We need local proxy for sunRadius due to conversion
|
// We need local proxy for sunRadius due to conversion
|
||||||
this.diskParams = {
|
const diskParams = {
|
||||||
radius: 1.2
|
radius: 1.2,
|
||||||
|
enabled: true // Enable sun disk
|
||||||
};
|
};
|
||||||
sky.setSunDiskEnabled(true);
|
sky.setSunDiskEnabled(true);
|
||||||
sky.setSunRadius(1.2);
|
sky.setSunRadius(1.2);
|
||||||
sunFolder.add(this.diskParams, 'radius', 0.0, 5.0).onChange(v => sky.setSunRadius(v));
|
sunDisk.add(diskParams, 'enabled').onChange(v => sky.setSunDiskEnabled(v));
|
||||||
sunFolder.add(sky.sunHalo, 1, 0.0, 2.0).name('Limb Darkening').onChange(v => sky.setSunLimbDarkening(v));
|
sunDisk.add(diskParams, 'radius', 0.0, 5.0).onChange(v => sky.setSunRadius(v));
|
||||||
sunFolder.add(sky.sunHalo, 2, 0.0, 100.0).name('Intensity Boost').onChange(v => sky.setSunDiskIntensity(v));
|
sunDisk.add(sky.sunHalo, 1, 0.0, 2.0).name('Limb Darkening').onChange(v => sky.setSunLimbDarkening(v));
|
||||||
|
sunDisk.add(sky.sunHalo, 2, 0.0, 100.0).name('Intensity Boost').onChange(v => sky.setSunDiskIntensity(v));
|
||||||
|
|
||||||
const atmFolder = gui.addFolder('Atmosphere');
|
const atmFolder = gui.addFolder('Atmosphere');
|
||||||
atmFolder.add(sky, 'turbidity', 1.0, 10.0).onChange(v => sky.setTurbidity(v));
|
atmFolder.add(sky, 'turbidity', 1.0, 10.0).onChange(v => sky.setTurbidity(v));
|
||||||
@@ -420,69 +161,12 @@ class App {
|
|||||||
atmFolder.add(sky, 'ozone', 0.0, 1.0).onChange(v => sky.setOzone(v));
|
atmFolder.add(sky, 'ozone', 0.0, 1.0).onChange(v => sky.setOzone(v));
|
||||||
atmFolder.add(sky, 'mieG', 0.0, 0.999).onChange(v => sky.setMieG(v));
|
atmFolder.add(sky, 'mieG', 0.0, 0.999).onChange(v => sky.setMieG(v));
|
||||||
|
|
||||||
const cloudFolder = gui.addFolder('Clouds');
|
|
||||||
this.cParams = {
|
|
||||||
volumetrics: sky.cloudControl2[1] > 0.5,
|
|
||||||
coverage: 0.4,
|
|
||||||
density: 0.02,
|
|
||||||
height: sky.cloudControl[2],
|
|
||||||
speed: 50.0,
|
|
||||||
evolution: 0.02
|
|
||||||
};
|
|
||||||
// Apply Cloud Defaults
|
|
||||||
sky.setCloudControl(0.4, 0.02, this.cParams.height, 50.0);
|
|
||||||
sky.setCloudShapeEvolution(0.02);
|
|
||||||
|
|
||||||
cloudFolder.add(this.cParams, 'volumetrics').onChange(v => sky.setCloudVolumetricLighting(v));
|
|
||||||
cloudFolder.add(this.cParams, 'coverage', 0.0, 1.0).onChange(v => sky.setCloudControl(v, this.cParams.density, this.cParams.height, this.cParams.speed));
|
|
||||||
cloudFolder.add(this.cParams, 'density', 0.0, 1.0).onChange(v => sky.setCloudControl(this.cParams.coverage, v, this.cParams.height, this.cParams.speed));
|
|
||||||
cloudFolder.add(this.cParams, 'height', 2000.0, 20000.0).onChange(v => sky.setCloudControl(this.cParams.coverage, this.cParams.density, v, this.cParams.speed));
|
|
||||||
// Reverse speed calc: w = speed * (0.05 / 72.0)
|
|
||||||
cloudFolder.add(this.cParams, 'speed', 0.0, 200.0).onChange(v => sky.setCloudControl(this.cParams.coverage, this.cParams.density, this.cParams.height, v));
|
|
||||||
cloudFolder.add(this.cParams, 'evolution', 0.0, 2.0).onChange(v => sky.setCloudShapeEvolution(v));
|
|
||||||
|
|
||||||
const waterFolder = gui.addFolder('Water');
|
|
||||||
this.wParams = {
|
|
||||||
derivativeTrick: true,
|
|
||||||
strength: 50.0,
|
|
||||||
speed: 1.0,
|
|
||||||
octaves: 4.0
|
|
||||||
};
|
|
||||||
// Initialize defaults
|
|
||||||
sky.setWaterControl(50.0, 1.0, 1.0, 4.0); // 1.0 = Derivative Trick On, 4 octaves
|
|
||||||
|
|
||||||
const updateWater = () => {
|
|
||||||
sky.setWaterControl(this.wParams.strength, this.wParams.speed, this.wParams.derivativeTrick ? 1.0 : 0.0, this.wParams.octaves);
|
|
||||||
};
|
|
||||||
|
|
||||||
waterFolder.add(this.wParams, 'derivativeTrick').name('Derivative Trick').onChange(updateWater);
|
|
||||||
waterFolder.add(this.wParams, 'strength', 10.0, 100.0).onChange(updateWater);
|
|
||||||
waterFolder.add(this.wParams, 'speed', 0.0, 5.0).onChange(updateWater);
|
|
||||||
waterFolder.add(this.wParams, 'octaves', 1, 8, 1).name('Octaves').onChange(updateWater);
|
|
||||||
waterFolder.close();
|
|
||||||
|
|
||||||
const starFolder = gui.addFolder('Stars');
|
|
||||||
this.sParams = {
|
|
||||||
enabled: true,
|
|
||||||
density: 0.001
|
|
||||||
};
|
|
||||||
// Initialize defaults (Density 0.001, Enabled True)
|
|
||||||
sky.setStarControl(0.001, true);
|
|
||||||
|
|
||||||
const updateStars = () => {
|
|
||||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
|
||||||
};
|
|
||||||
|
|
||||||
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
|
||||||
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
|
||||||
starFolder.close();
|
|
||||||
|
|
||||||
const artFolder = gui.addFolder('Artistic');
|
const artFolder = gui.addFolder('Artistic');
|
||||||
// Set Horizon Glow default to 0.0
|
// Set Horizon Glow default to 1.0
|
||||||
sky.setHorizonGlow(0.0);
|
sky.setHorizonGlow(1.0);
|
||||||
sky.msFactors[2] = 0.0;
|
sky.msFactors[2] = 1.0;
|
||||||
// Set Contrast default to 1.0
|
// Set Contrast default to 0.85
|
||||||
sky.setContrast(1.0);
|
sky.setContrast(0.85);
|
||||||
|
|
||||||
artFolder.add(sky.msFactors, 0, 0.0, 2.0).name('MS Rayleigh').onChange(v => sky.setMultiScattering(v, sky.msFactors[1]));
|
artFolder.add(sky.msFactors, 0, 0.0, 2.0).name('MS Rayleigh').onChange(v => sky.setMultiScattering(v, sky.msFactors[1]));
|
||||||
artFolder.add(sky.msFactors, 1, 0.0, 2.0).name('MS Mie').onChange(v => sky.setMultiScattering(sky.msFactors[0], v));
|
artFolder.add(sky.msFactors, 1, 0.0, 2.0).name('MS Mie').onChange(v => sky.setMultiScattering(sky.msFactors[0], v));
|
||||||
@@ -499,6 +183,63 @@ class App {
|
|||||||
shmFolder.add(sky.shimmerControl, 1, 1.0, 100.0).name('Frequency').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], v, sky.shimmerControl[2]));
|
shmFolder.add(sky.shimmerControl, 1, 1.0, 100.0).name('Frequency').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], v, sky.shimmerControl[2]));
|
||||||
shmFolder.add(sky.shimmerControl, 2, 0.01, 0.5).name('Mask Height').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], sky.shimmerControl[1], v));
|
shmFolder.add(sky.shimmerControl, 2, 0.01, 0.5).name('Mask Height').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], sky.shimmerControl[1], v));
|
||||||
|
|
||||||
|
const cloudFolder = gui.addFolder('Clouds');
|
||||||
|
const cParams = {
|
||||||
|
volumetrics: sky.cloudControl2[1] > 0.5,
|
||||||
|
coverage: 0.4,
|
||||||
|
density: 0.02,
|
||||||
|
height: sky.cloudControl[2],
|
||||||
|
speed: 50.0,
|
||||||
|
evolution: 0.02
|
||||||
|
};
|
||||||
|
// Apply Cloud Defaults
|
||||||
|
sky.setCloudControl(0.4, 0.02, cParams.height, 50.0);
|
||||||
|
sky.setCloudShapeEvolution(0.02);
|
||||||
|
|
||||||
|
cloudFolder.add(cParams, 'volumetrics').onChange(v => sky.setCloudVolumetricLighting(v));
|
||||||
|
cloudFolder.add(cParams, 'coverage', 0.0, 1.0).onChange(v => sky.setCloudControl(v, cParams.density, cParams.height, cParams.speed));
|
||||||
|
cloudFolder.add(cParams, 'density', 0.0, 1.0).onChange(v => sky.setCloudControl(cParams.coverage, v, cParams.height, cParams.speed));
|
||||||
|
cloudFolder.add(cParams, 'height', 2000.0, 20000.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, v, cParams.speed));
|
||||||
|
// Reverse speed calc: w = speed * (0.05 / 72.0)
|
||||||
|
cloudFolder.add(cParams, 'speed', 0.0, 200.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, cParams.height, v));
|
||||||
|
cloudFolder.add(cParams, 'evolution', 0.0, 2.0).onChange(v => sky.setCloudShapeEvolution(v));
|
||||||
|
|
||||||
|
const waterFolder = gui.addFolder('Water');
|
||||||
|
const wParams = {
|
||||||
|
derivativeTrick: true,
|
||||||
|
strength: 50.0,
|
||||||
|
speed: 1.0,
|
||||||
|
octaves: 4.0
|
||||||
|
};
|
||||||
|
// Initialize defaults
|
||||||
|
sky.setWaterControl(50.0, 1.0, 1.0, 4.0); // 1.0 = Derivative Trick On, 4 octaves
|
||||||
|
|
||||||
|
const updateWater = () => {
|
||||||
|
sky.setWaterControl(wParams.strength, wParams.speed, wParams.derivativeTrick ? 1.0 : 0.0, wParams.octaves);
|
||||||
|
};
|
||||||
|
|
||||||
|
waterFolder.add(wParams, 'derivativeTrick').name('Derivative Trick').onChange(updateWater);
|
||||||
|
waterFolder.add(wParams, 'strength', 10.0, 100.0).onChange(updateWater);
|
||||||
|
waterFolder.add(wParams, 'speed', 0.0, 5.0).onChange(updateWater);
|
||||||
|
waterFolder.add(wParams, 'octaves', 1, 8, 1).name('Octaves').onChange(updateWater);
|
||||||
|
waterFolder.close();
|
||||||
|
|
||||||
|
const starFolder = gui.addFolder('Stars');
|
||||||
|
const sParams = {
|
||||||
|
enabled: true,
|
||||||
|
density: 1.0
|
||||||
|
};
|
||||||
|
// Initialize defaults (Density 1.0, Enabled True)
|
||||||
|
sky.setStarControl(1.0, true);
|
||||||
|
|
||||||
|
const updateStars = () => {
|
||||||
|
sky.setStarControl(sParams.density, sParams.enabled);
|
||||||
|
};
|
||||||
|
|
||||||
|
starFolder.add(sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||||
|
starFolder.add(sParams, 'density', 0.0, 1.0).name('Density').onChange(updateStars);
|
||||||
|
starFolder.close();
|
||||||
|
|
||||||
const camFolder = gui.addFolder('Camera');
|
const camFolder = gui.addFolder('Camera');
|
||||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||||
@@ -506,362 +247,35 @@ class App {
|
|||||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||||
|
|
||||||
const bloomFolder = camFolder.addFolder('Bloom');
|
const bloomFolder = camFolder.addFolder('Bloom');
|
||||||
this.bParams = {
|
const bParams = {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
lensFlare: false
|
lensFlare: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateBloom = () => {
|
const updateBloom = () => {
|
||||||
this.view.setBloomOptions({
|
this.view.setBloomOptions({
|
||||||
enabled: this.bParams.enabled,
|
enabled: bParams.enabled,
|
||||||
lensFlare: this.bParams.lensFlare
|
lensFlare: bParams.lensFlare
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
bloomFolder.add(this.bParams, 'enabled').onChange(updateBloom);
|
bloomFolder.add(bParams, 'enabled').onChange(updateBloom);
|
||||||
bloomFolder.add(this.bParams, 'lensFlare').onChange(updateBloom);
|
bloomFolder.add(bParams, 'lensFlare').onChange(updateBloom);
|
||||||
bloomFolder.close();
|
bloomFolder.close();
|
||||||
|
|
||||||
// Collapse folders by default
|
// Collapse folders by default
|
||||||
|
sunDisk.close();
|
||||||
atmFolder.close();
|
atmFolder.close();
|
||||||
artFolder.close();
|
artFolder.close();
|
||||||
// shmFolder is inside artFolder, so it's hidden, but we can close it too if we want
|
// shmFolder is inside artFolder, so it's hidden, but we can close it too if we want
|
||||||
shmFolder.close();
|
shmFolder.close();
|
||||||
cloudFolder.close();
|
cloudFolder.close();
|
||||||
// camFolder left open by default for convenience.
|
// camFolder left open? User didn't specify, but "Artistic, shimmer and clouds" + "Disk, Atmosphere" were requested.
|
||||||
|
// So Camera might stay open or close. Let's keep Camera open for now as it wasn't listed.
|
||||||
|
|
||||||
// Initial sync
|
// Initial sync
|
||||||
updateSun();
|
updateSun();
|
||||||
this.updateCameraExposure(); // This will trigger updateSunIntensity too
|
this.updateCameraExposure(); // This will trigger updateSunIntensity too
|
||||||
updateBloom();
|
|
||||||
|
|
||||||
// Check URL for config
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
if (urlParams.has('config')) {
|
|
||||||
try {
|
|
||||||
const state = JSON.parse(atob(urlParams.get('config')));
|
|
||||||
this.applyURLState(state);
|
|
||||||
// Update GUI
|
|
||||||
gui.controllers.forEach(c => c.updateDisplay());
|
|
||||||
// Recursive update for folders
|
|
||||||
gui.folders.forEach(f => {
|
|
||||||
f.controllers.forEach(c => c.updateDisplay());
|
|
||||||
// And sub-folders if any (Shimmer/Bloom)
|
|
||||||
f.folders.forEach(sf => sf.controllers.forEach(sc => sc.updateDisplay()));
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to load config:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncFolder = gui.addFolder('Real-Time Sync');
|
|
||||||
this.syncParams = {
|
|
||||||
enabled: false,
|
|
||||||
lat: 0.0,
|
|
||||||
lng: 0.0,
|
|
||||||
status: 'Disabled'
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateSync = () => {
|
|
||||||
if (this.syncParams.enabled) {
|
|
||||||
if (navigator.geolocation) {
|
|
||||||
this.syncParams.status = "Locating...";
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
|
||||||
(pos) => {
|
|
||||||
this.syncParams.lat = pos.coords.latitude;
|
|
||||||
this.syncParams.lng = pos.coords.longitude;
|
|
||||||
this.syncParams.status = "Active";
|
|
||||||
syncFolder.controllers.forEach(c => c.updateDisplay());
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error(err);
|
|
||||||
this.syncParams.status = "Error (See Console)";
|
|
||||||
this.syncParams.enabled = false;
|
|
||||||
syncFolder.controllers.forEach(c => c.updateDisplay());
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.syncParams.status = "Not Supported";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.syncParams.status = "Disabled";
|
|
||||||
}
|
|
||||||
syncFolder.controllers.forEach(c => c.updateDisplay());
|
|
||||||
};
|
|
||||||
|
|
||||||
syncFolder.add(this.syncParams, 'enabled').name('Enable Sync').onChange(updateSync);
|
|
||||||
syncFolder.add(this.syncParams, 'status').name('Status').disable().listen();
|
|
||||||
|
|
||||||
this.syncFolder = syncFolder;
|
|
||||||
|
|
||||||
const shareParams = {
|
|
||||||
copyUrl: () => {
|
|
||||||
const state = this.getURLState();
|
|
||||||
const str = btoa(JSON.stringify(state));
|
|
||||||
const url = `${window.location.origin}${window.location.pathname}?config=${str}`;
|
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
|
||||||
alert("Configuration URL copied to clipboard!");
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('Could not copy text: ', err);
|
|
||||||
prompt("Copy this URL:", url);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
gui.add(shareParams, 'copyUrl').name('Share Configuration');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRealTimeSync() {
|
|
||||||
if (!this.syncParams || !this.syncParams.enabled || !window.SunCalc) return;
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const lat = this.syncParams.lat;
|
|
||||||
const lng = this.syncParams.lng;
|
|
||||||
|
|
||||||
// Sun
|
|
||||||
const sunPos = window.SunCalc.getPosition(now, lat, lng);
|
|
||||||
// Azimuth: South=0, West=PI/2.
|
|
||||||
// Skybox Phi: +X=0, +Z=PI/2.
|
|
||||||
// If +Z is South:
|
|
||||||
// SunAz 0 (South) -> Skybox PI/2 (+Z).
|
|
||||||
// SunAz PI/2 (West) -> Skybox PI (-X).
|
|
||||||
// So Phi = Az + PI/2.
|
|
||||||
const sunPhi = sunPos.azimuth + Math.PI / 2;
|
|
||||||
// Altitude: 0=Horizon, PI/2=Zenith.
|
|
||||||
// Skybox Theta: 0=Zenith, PI/2=Horizon.
|
|
||||||
const sunTheta = Math.PI / 2 - sunPos.altitude;
|
|
||||||
|
|
||||||
this.params.sunPhi = sunPhi;
|
|
||||||
this.params.sunTheta = sunTheta;
|
|
||||||
|
|
||||||
// Moon
|
|
||||||
const moonPos = window.SunCalc.getMoonPosition(now, lat, lng);
|
|
||||||
const moonPhi = moonPos.azimuth + Math.PI / 2;
|
|
||||||
const moonTheta = Math.PI / 2 - moonPos.altitude;
|
|
||||||
|
|
||||||
this.mParams.azimuth = (moonPhi * 180.0 / Math.PI) % 360.0;
|
|
||||||
this.mParams.height = Math.cos(moonTheta);
|
|
||||||
|
|
||||||
// Milky Way Sync
|
|
||||||
const gmst = getGMST(now);
|
|
||||||
const lst = (gmst + lng / 15.0 + 24.0) % 24.0;
|
|
||||||
this.mwParams.siderealTime = lst;
|
|
||||||
this.mwParams.latitude = lat;
|
|
||||||
if (this.updateMW) this.updateMW();
|
|
||||||
|
|
||||||
// Update Skybox
|
|
||||||
const sky = this.skybox;
|
|
||||||
|
|
||||||
// Update Sun Vector
|
|
||||||
const sx = Math.sin(sunTheta) * Math.cos(sunPhi);
|
|
||||||
const sy = Math.cos(sunTheta);
|
|
||||||
const sz = Math.sin(sunTheta) * Math.sin(sunPhi);
|
|
||||||
sky.setSunPosition([sx, sy, sz]);
|
|
||||||
|
|
||||||
// Update Moon Vector
|
|
||||||
const mx = Math.sin(moonTheta) * Math.cos(moonPhi);
|
|
||||||
const my = Math.cos(moonTheta);
|
|
||||||
const mz = Math.sin(moonTheta) * Math.sin(moonPhi);
|
|
||||||
sky.setMoonPosition([mx, my, mz]);
|
|
||||||
|
|
||||||
// Update UI Proxies
|
|
||||||
if (this.sunUI) {
|
|
||||||
this.sunUI.azimuth = (sunPhi * 180.0 / Math.PI) % 360.0;
|
|
||||||
if (this.sunUI.azimuth < 0) this.sunUI.azimuth += 360.0;
|
|
||||||
this.sunUI.height = Math.cos(sunTheta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getURLState() {
|
|
||||||
|
|
||||||
// Update Camera LookAt
|
|
||||||
// Serialize current state (Minified)
|
|
||||||
// Mapping:
|
|
||||||
// p: params (Camera) -> a:aperture, ss:shutterSpeed, i:iso, st:sunTheta, sp:sunPhi, fl:focalLength, si:sunIntensity
|
|
||||||
// c: cParams (Clouds) -> v:volumetrics, co:coverage, d:density, h:height, s:speed, e:evolution
|
|
||||||
// w: wParams (Water) -> dt:derivativeTrick, st:strength, s:speed, o:octaves
|
|
||||||
// s: sParams (Stars) -> e:enabled, d:density
|
|
||||||
// b: bParams (Bloom) -> e:enabled, lf:lensFlare
|
|
||||||
// k: sky (Skybox) -> t:turbidity, r:rayleigh, mc:mieCoefficient, mg:mieG, o:ozone, ms:msFactors, co:contrast, nc:nightColor, sh:shimmerControl, hl:sunHalo
|
|
||||||
|
|
||||||
const p = this.params;
|
|
||||||
const c = this.cParams;
|
|
||||||
const w = this.wParams;
|
|
||||||
const s = this.sParams;
|
|
||||||
const b = this.bParams;
|
|
||||||
const m = this.mParams;
|
|
||||||
const sk = this.skybox;
|
|
||||||
|
|
||||||
return {
|
|
||||||
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
|
||||||
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
|
||||||
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
|
||||||
s: { e: s.enabled, d: s.density },
|
|
||||||
b: { e: b.enabled, lf: b.lensFlare },
|
|
||||||
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
|
||||||
cm: { t: this.camState.theta, p: this.camState.phi },
|
|
||||||
k: {
|
|
||||||
t: sk.turbidity,
|
|
||||||
r: sk.rayleigh,
|
|
||||||
mc: sk.mieCoefficient,
|
|
||||||
mg: sk.mieG,
|
|
||||||
o: sk.ozone,
|
|
||||||
ms: [...sk.msFactors],
|
|
||||||
co: sk.contrast,
|
|
||||||
nc: [...sk.nightColor],
|
|
||||||
sh: [...sk.shimmerControl],
|
|
||||||
hl: [...sk.sunHalo]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
applyURLState(state) {
|
|
||||||
const p = state.p;
|
|
||||||
const c = state.c;
|
|
||||||
const w = state.w;
|
|
||||||
const s = state.s;
|
|
||||||
const b = state.b;
|
|
||||||
const m = state.m;
|
|
||||||
const k = state.k;
|
|
||||||
const cm = state.cm;
|
|
||||||
|
|
||||||
if (p) {
|
|
||||||
if (p.a !== undefined) this.params.aperture = p.a;
|
|
||||||
if (p.ss !== undefined) this.params.shutterSpeed = p.ss;
|
|
||||||
if (p.i !== undefined) this.params.iso = p.i;
|
|
||||||
if (p.st !== undefined) this.params.sunTheta = p.st;
|
|
||||||
if (p.sp !== undefined) this.params.sunPhi = p.sp;
|
|
||||||
if (p.fl !== undefined) this.params.focalLength = p.fl;
|
|
||||||
if (p.si !== undefined) this.params.sunIntensity = p.si;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c) {
|
|
||||||
if (c.v !== undefined) this.cParams.volumetrics = c.v;
|
|
||||||
if (c.co !== undefined) this.cParams.coverage = c.co;
|
|
||||||
if (c.d !== undefined) this.cParams.density = c.d;
|
|
||||||
if (c.h !== undefined) this.cParams.height = c.h;
|
|
||||||
if (c.s !== undefined) this.cParams.speed = c.s;
|
|
||||||
if (c.e !== undefined) this.cParams.evolution = c.e;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (w) {
|
|
||||||
if (w.dt !== undefined) this.wParams.derivativeTrick = w.dt;
|
|
||||||
if (w.st !== undefined) this.wParams.strength = w.st;
|
|
||||||
if (w.s !== undefined) this.wParams.speed = w.s;
|
|
||||||
if (w.o !== undefined) this.wParams.octaves = w.o;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s) {
|
|
||||||
if (s.e !== undefined) this.sParams.enabled = s.e;
|
|
||||||
if (s.d !== undefined) this.sParams.density = s.d;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b) {
|
|
||||||
if (b.e !== undefined) this.bParams.enabled = b.e;
|
|
||||||
if (b.lf !== undefined) this.bParams.lensFlare = b.lf;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sky = this.skybox;
|
|
||||||
if (k) {
|
|
||||||
if (k.t !== undefined) sky.setTurbidity(k.t);
|
|
||||||
if (k.r !== undefined) sky.setRayleigh(k.r);
|
|
||||||
if (k.mc !== undefined) sky.setMieCoefficient(k.mc);
|
|
||||||
if (k.mg !== undefined) sky.setMieG(k.mg);
|
|
||||||
if (k.o !== undefined) sky.setOzone(k.o);
|
|
||||||
|
|
||||||
if (k.ms) { sky.setMultiScattering(k.ms[0], k.ms[1]); sky.setHorizonGlow(k.ms[2]); }
|
|
||||||
if (k.co !== undefined) sky.setContrast(k.co);
|
|
||||||
if (k.nc) sky.setNightColor(k.nc);
|
|
||||||
if (k.sh) sky.setShimmerControl(k.sh[0], k.sh[1], k.sh[2]);
|
|
||||||
|
|
||||||
// Sun Halo
|
|
||||||
const savedHalo = k.hl;
|
|
||||||
if (savedHalo && savedHalo.length === 4) {
|
|
||||||
sky.sunHalo[0] = savedHalo[0];
|
|
||||||
sky.sunHalo[1] = savedHalo[1];
|
|
||||||
sky.sunHalo[2] = savedHalo[2];
|
|
||||||
sky.sunHalo[3] = savedHalo[3];
|
|
||||||
|
|
||||||
// Update derived UI params for Sun Disk
|
|
||||||
const rad = Math.acos(Math.max(-1.0, Math.min(1.0, savedHalo[0])));
|
|
||||||
this.diskParams.radius = rad * (180.0 / Math.PI);
|
|
||||||
this.diskParams.enabled = savedHalo[3] > 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
sky.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cm) {
|
|
||||||
if (cm.t !== undefined) this.camState.theta = cm.t;
|
|
||||||
if (cm.p !== undefined) this.camState.phi = cm.p;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update derived Sun UI
|
|
||||||
if (this.sunUI) {
|
|
||||||
this.sunUI.height = Math.cos(this.params.sunTheta);
|
|
||||||
this.sunUI.azimuth = (this.params.sunPhi * 180.0 / Math.PI) % 360.0;
|
|
||||||
if (this.sunUI.azimuth < 0) this.sunUI.azimuth += 360.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply Local Params via Setters
|
|
||||||
sky.setCloudControl(this.cParams.coverage, this.cParams.density, this.cParams.height, this.cParams.speed);
|
|
||||||
sky.setCloudVolumetricLighting(this.cParams.volumetrics);
|
|
||||||
sky.setCloudShapeEvolution(this.cParams.evolution);
|
|
||||||
|
|
||||||
sky.setWaterControl(this.wParams.strength, this.wParams.speed, this.wParams.derivativeTrick ? 1.0 : 0.0, this.wParams.octaves);
|
|
||||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
if (m.e !== undefined) this.mParams.enabled = m.e;
|
|
||||||
if (m.az !== undefined) this.mParams.azimuth = m.az;
|
|
||||||
// Compat: if 'el' exists (old link) convert to 'h'
|
|
||||||
if (m.h !== undefined) {
|
|
||||||
this.mParams.height = m.h;
|
|
||||||
} else if (m.el !== undefined) {
|
|
||||||
// Convert elevation degrees to height cos
|
|
||||||
this.mParams.height = Math.cos(m.el * Math.PI / 180.0);
|
|
||||||
}
|
|
||||||
if (m.r !== undefined) this.mParams.radius = m.r;
|
|
||||||
if (m.i !== undefined) this.mParams.intensity = m.i;
|
|
||||||
|
|
||||||
// Sync Moon
|
|
||||||
const az = this.mParams.azimuth * (Math.PI / 180.0);
|
|
||||||
const theta = Math.acos(this.mParams.height);
|
|
||||||
const phi = az;
|
|
||||||
const x = Math.sin(theta) * Math.cos(phi);
|
|
||||||
const y = Math.cos(theta);
|
|
||||||
const z = Math.sin(theta) * Math.sin(phi);
|
|
||||||
|
|
||||||
sky.setMoonPosition([x, y, z]);
|
|
||||||
sky.setMoonEnabled(this.mParams.enabled);
|
|
||||||
sky.setMoonRadius(this.mParams.radius);
|
|
||||||
sky.setMoonIntensity(this.mParams.intensity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cm) {
|
|
||||||
if (cm.t !== undefined) this.camState.theta = cm.t;
|
|
||||||
if (cm.p !== undefined) this.camState.phi = cm.p;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.view.setBloomOptions({
|
|
||||||
enabled: this.bParams.enabled,
|
|
||||||
lensFlare: this.bParams.lensFlare
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update Sun Position from Params
|
|
||||||
const theta = this.params.sunTheta;
|
|
||||||
const phi = this.params.sunPhi;
|
|
||||||
const x = Math.sin(theta) * Math.cos(phi);
|
|
||||||
const y = Math.cos(theta);
|
|
||||||
const z = Math.sin(theta) * Math.sin(phi);
|
|
||||||
sky.setSunPosition([x, y, z]);
|
|
||||||
|
|
||||||
// Update Camera Projection (Focal Length) and Exposure (Aperture/Shutter/ISO)
|
|
||||||
this.updateCameraProjection();
|
|
||||||
this.updateCameraExposure();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initControls() {
|
initControls() {
|
||||||
@@ -891,41 +305,10 @@ class App {
|
|||||||
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Touch support
|
|
||||||
this.canvas.addEventListener('touchstart', e => {
|
|
||||||
if (e.touches.length > 0) {
|
|
||||||
e.preventDefault(); // Prevent scroll/long-press
|
|
||||||
this.camState.dragging = true;
|
|
||||||
this.camState.lastX = e.touches[0].clientX;
|
|
||||||
this.camState.lastY = e.touches[0].clientY;
|
|
||||||
}
|
|
||||||
}, { passive: false });
|
|
||||||
|
|
||||||
window.addEventListener('touchend', () => {
|
|
||||||
this.camState.dragging = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('touchmove', e => {
|
|
||||||
if (!this.camState.dragging || e.touches.length === 0) return;
|
|
||||||
e.preventDefault(); // Prevent scrolling
|
|
||||||
const x = e.touches[0].clientX;
|
|
||||||
const y = e.touches[0].clientY;
|
|
||||||
const dx = x - this.camState.lastX;
|
|
||||||
const dy = y - this.camState.lastY;
|
|
||||||
this.camState.lastX = x;
|
|
||||||
this.camState.lastY = y;
|
|
||||||
|
|
||||||
const sensitivity = 0.005;
|
|
||||||
this.camState.theta -= dx * sensitivity;
|
|
||||||
this.camState.phi += dy * sensitivity;
|
|
||||||
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
|
||||||
}, { passive: false });
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.updateRealTimeSync();
|
|
||||||
// Update Camera LookAt
|
// Update Camera LookAt
|
||||||
const r = 1.0;
|
const r = 1.0;
|
||||||
const theta = this.camState.theta;
|
const theta = this.camState.theta;
|
||||||
@@ -958,6 +341,5 @@ class App {
|
|||||||
const aspect = width / height;
|
const aspect = width / height;
|
||||||
// near=0.1, far=5000.0
|
// near=0.1, far=5000.0
|
||||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||||
if (this.skybox) this.skybox.setResolution(height);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
|
|
||||||
import os
|
|
||||||
import urllib.request
|
|
||||||
import ssl
|
|
||||||
from PIL import Image
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
# URL of the Milky Way texture (Gaia EDR3) from ESA
|
|
||||||
# Low Res PNG (2.71 MB) is sufficient for our 1024x512 target
|
|
||||||
URL = "https://www.esa.int/var/esa/storage/images/esa_multimedia/images/2020/12/the_colour_of_the_sky_from_gaia_s_early_data_release_3/22358049-1-eng-GB/The_colour_of_the_sky_from_Gaia_s_Early_Data_Release_3.png"
|
|
||||||
OUTPUT_DIR = "assets"
|
|
||||||
OUTPUT_FILENAME = "milkyway.png"
|
|
||||||
TARGET_WIDTH = 1024
|
|
||||||
TARGET_HEIGHT = 512
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if not os.path.exists(OUTPUT_DIR):
|
|
||||||
os.makedirs(OUTPUT_DIR)
|
|
||||||
|
|
||||||
output_path = os.path.join(OUTPUT_DIR, OUTPUT_FILENAME)
|
|
||||||
temp_path = os.path.join(OUTPUT_DIR, "temp_milkyway.png")
|
|
||||||
|
|
||||||
print(f"Downloading Milky Way texture from {URL}...")
|
|
||||||
|
|
||||||
# Bypass SSL verification globally
|
|
||||||
if hasattr(ssl, '_create_unverified_context'):
|
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context
|
|
||||||
|
|
||||||
try:
|
|
||||||
req = urllib.request.Request(URL, headers={'User-Agent': 'Mozilla/5.0'})
|
|
||||||
with urllib.request.urlopen(req) as response, open(temp_path, 'wb') as out_file:
|
|
||||||
out_file.write(response.read())
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to download: {e}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not os.path.exists(temp_path):
|
|
||||||
print("Error: Download failed.")
|
|
||||||
return
|
|
||||||
|
|
||||||
print("Processing image...")
|
|
||||||
with Image.open(temp_path) as img:
|
|
||||||
# Convert to RGB (remove alpha if present, though this is likely opaque)
|
|
||||||
img = img.convert("RGB")
|
|
||||||
|
|
||||||
# Resize to user requested dimensions
|
|
||||||
print(f"Resizing to {TARGET_WIDTH}x{TARGET_HEIGHT}...")
|
|
||||||
img = img.resize((TARGET_WIDTH, TARGET_HEIGHT), Image.Resampling.LANCZOS)
|
|
||||||
|
|
||||||
# Save
|
|
||||||
print(f"Saving to {output_path}...")
|
|
||||||
img.save(output_path, "PNG")
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
if os.path.exists(temp_path):
|
|
||||||
os.remove(temp_path)
|
|
||||||
|
|
||||||
print("Done!")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Default size
|
|
||||||
SIZE=${1:-256}
|
|
||||||
OUTPUT_COLOR="../assets/moon_disk.png"
|
|
||||||
OUTPUT_NORMAL="../assets/moon_normal.png"
|
|
||||||
|
|
||||||
# Navigate to script directory
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
# Check dependencies
|
|
||||||
if ! python3 -c "import numpy, PIL" 2>/dev/null; then
|
|
||||||
echo "Installing dependencies (numpy, Pillow)..."
|
|
||||||
pip3 install numpy Pillow
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating Moon Asset (Size: ${SIZE}x${SIZE})..."
|
|
||||||
python3 process_moon.py --size $SIZE --supersample 4 --blur 1.0 --output-color $OUTPUT_COLOR --output-normal $OUTPUT_NORMAL
|
|
||||||
|
|
||||||
echo "Generated $OUTPUT_COLOR and $OUTPUT_NORMAL"
|
|
||||||
|
Before Width: | Height: | Size: 447 KiB |
@@ -1,249 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import urllib.request
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import math
|
|
||||||
import ssl
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
# Ensure numpy is available
|
|
||||||
try:
|
|
||||||
import numpy as np
|
|
||||||
except ImportError:
|
|
||||||
print("Error: numpy is required. Please install it with: pip install numpy")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Default URLs
|
|
||||||
COLOR_URL = "https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/lroc_color_2k.jpg"
|
|
||||||
DISP_URL = "https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/ldem_4.tif"
|
|
||||||
|
|
||||||
SRC_COLOR_FILENAME = "lroc_color_2k.jpg"
|
|
||||||
SRC_DISP_FILENAME = "ldem_4.tif"
|
|
||||||
|
|
||||||
def download_file(url, filename):
|
|
||||||
if os.path.exists(filename):
|
|
||||||
print(f"File {filename} already exists. Skipping download.")
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"Downloading {url} to {filename}...")
|
|
||||||
# Create unverified context to avoid potential SSL cert issues
|
|
||||||
context = ssl._create_unverified_context()
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(url, context=context) as response, open(filename, 'wb') as out_file:
|
|
||||||
data = response.read()
|
|
||||||
out_file.write(data)
|
|
||||||
print(f"Download of {filename} complete.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error downloading file {filename}: {e}")
|
|
||||||
# Don't exit hard if it's just one file, maybe?
|
|
||||||
# But for this script, we likely need it.
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def equirectangular_to_orthographic(src_img, size, mode=None):
|
|
||||||
"""
|
|
||||||
Reprojects an equirectangular image to an orthographic projection (sphere view).
|
|
||||||
src_img: PIL Image (Equirectangular)
|
|
||||||
size: Output size (width, height) - usually square
|
|
||||||
"""
|
|
||||||
print(f"Reprojecting to {size}x{size} Orthographic Disk...")
|
|
||||||
|
|
||||||
width, height = size, size
|
|
||||||
src_w, src_h = src_img.size
|
|
||||||
|
|
||||||
# Create coordinate grid centered at 0,0 (-1 to 1)
|
|
||||||
y, x = np.mgrid[size/2:-size/2:-1, -size/2:size/2] # Note: y goes high to low
|
|
||||||
|
|
||||||
# Normalize to -1..1
|
|
||||||
x = x / (size / 2)
|
|
||||||
y = y / (size / 2)
|
|
||||||
|
|
||||||
# Mask for points outside the circle
|
|
||||||
r2 = x*x + y*y
|
|
||||||
mask = r2 <= 1.0
|
|
||||||
|
|
||||||
# Calculate sphere coordinates (z > 0 for front face)
|
|
||||||
z = np.zeros_like(r2)
|
|
||||||
z[mask] = np.sqrt(1.0 - r2[mask])
|
|
||||||
|
|
||||||
# Vector P = (x, y, z) on unit sphere
|
|
||||||
# Lat = asin(y)
|
|
||||||
# Lon = atan2(x, z)
|
|
||||||
|
|
||||||
# Apply mask to avoid invalid calculations
|
|
||||||
lat = np.arcsin(y * mask)
|
|
||||||
lon = np.arctan2(x * mask, z * mask)
|
|
||||||
|
|
||||||
# Map to UV [0, 1]
|
|
||||||
u = (lon / (2 * math.pi)) + 0.5
|
|
||||||
v = (lat / math.pi) + 0.5
|
|
||||||
|
|
||||||
# Map to Source Pixels
|
|
||||||
u = np.clip(u, 0, 1)
|
|
||||||
v = np.clip(v, 0, 1)
|
|
||||||
|
|
||||||
src_x = (u * (src_w - 1)).astype(np.int32)
|
|
||||||
src_y = ((1.0 - v) * (src_h - 1)).astype(np.int32) # Flip V for image coords
|
|
||||||
|
|
||||||
# Sample pixels
|
|
||||||
src_array = np.array(src_img)
|
|
||||||
|
|
||||||
# Handle dimensions
|
|
||||||
if len(src_array.shape) == 2:
|
|
||||||
# Grayscale / Single channel
|
|
||||||
out_channels = 1
|
|
||||||
src_array = src_array[:, :, np.newaxis] # Expand for consistent indexing
|
|
||||||
else:
|
|
||||||
out_channels = src_array.shape[2]
|
|
||||||
|
|
||||||
out_array = np.zeros((height, width, out_channels), dtype=src_array.dtype)
|
|
||||||
|
|
||||||
# Advanced indexing
|
|
||||||
valid_y, valid_x = np.where(mask)
|
|
||||||
|
|
||||||
# Extract coordinates for valid pixels
|
|
||||||
sx = src_x[valid_y, valid_x]
|
|
||||||
sy = src_y[valid_y, valid_x]
|
|
||||||
|
|
||||||
out_array[valid_y, valid_x] = src_array[sy, sx]
|
|
||||||
|
|
||||||
# Squeeze if single channel
|
|
||||||
if out_channels == 1:
|
|
||||||
out_array = out_array.squeeze(axis=2)
|
|
||||||
|
|
||||||
return Image.fromarray(out_array, mode or src_img.mode)
|
|
||||||
|
|
||||||
def compute_normal_map(height_img, scale=1.0, blur_radius=0.0):
|
|
||||||
print("Computing Normal Map from Height Map...")
|
|
||||||
# Convert to float array
|
|
||||||
h = np.array(height_img).astype(np.float32)
|
|
||||||
|
|
||||||
# Apply Blur if requested
|
|
||||||
if blur_radius > 0:
|
|
||||||
try:
|
|
||||||
from scipy.ndimage import gaussian_filter
|
|
||||||
print(f"Applying Gaussian Blur (Radius: {blur_radius})...")
|
|
||||||
h = gaussian_filter(h, sigma=blur_radius)
|
|
||||||
except ImportError:
|
|
||||||
print("Warning: scipy not found. Skipping Gaussian Blur.")
|
|
||||||
|
|
||||||
# Normalize height to 0..1 for consistent gradient scale regardless of input depth
|
|
||||||
h_min, h_max = h.min(), h.max()
|
|
||||||
print(f"Height Map Range: {h_min} to {h_max}")
|
|
||||||
if h_max > h_min:
|
|
||||||
h_norm = (h - h_min) / (h_max - h_min)
|
|
||||||
else:
|
|
||||||
h_norm = h
|
|
||||||
|
|
||||||
# Gradients
|
|
||||||
dy, dx = np.gradient(h_norm)
|
|
||||||
|
|
||||||
# Pre-emphasis scale
|
|
||||||
bump_scale = scale
|
|
||||||
|
|
||||||
# Normal vector components
|
|
||||||
# Map is Top-Down Y.
|
|
||||||
nx = -dx * bump_scale
|
|
||||||
ny = -dy * bump_scale
|
|
||||||
nz = np.ones_like(nx)
|
|
||||||
|
|
||||||
# Mask out normals where r > 0.96 (avoid edge cliff artifacts)
|
|
||||||
rows, cols = h.shape
|
|
||||||
y, x = np.ogrid[:rows, :cols]
|
|
||||||
center_y, center_x = rows/2.0, cols/2.0
|
|
||||||
# max radius is size/2
|
|
||||||
radius_sq = (min(rows, cols) / 2.0)**2
|
|
||||||
dist_sq = (x - center_x)**2 + (y - center_y)**2
|
|
||||||
mask = dist_sq < (radius_sq * 0.96 * 0.96)
|
|
||||||
|
|
||||||
nx[~mask] = 0
|
|
||||||
ny[~mask] = 0
|
|
||||||
nz[~mask] = 1
|
|
||||||
|
|
||||||
# Normalize
|
|
||||||
len_n = np.sqrt(nx*nx + ny*ny + nz*nz)
|
|
||||||
# Avoid divide by zero
|
|
||||||
len_n[len_n == 0] = 1.0
|
|
||||||
|
|
||||||
nx /= len_n
|
|
||||||
ny /= len_n
|
|
||||||
nz /= len_n
|
|
||||||
|
|
||||||
# Pack to 0..255
|
|
||||||
# [-1, 1] -> [0, 255]
|
|
||||||
out_x = ((nx + 1.0) * 0.5 * 255.0).astype(np.uint8)
|
|
||||||
out_y = ((ny + 1.0) * 0.5 * 255.0).astype(np.uint8)
|
|
||||||
out_z = ((nz + 1.0) * 0.5 * 255.0).astype(np.uint8)
|
|
||||||
|
|
||||||
out_rgb = np.dstack((out_x, out_y, out_z))
|
|
||||||
return Image.fromarray(out_rgb, 'RGB')
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description='Process Moon Texture')
|
|
||||||
parser.add_argument('--size', type=int, default=256, help='Output resolution (square)')
|
|
||||||
parser.add_argument('--supersample', type=int, default=2, help='Internal processing resolution multiplier')
|
|
||||||
parser.add_argument('--blur', type=float, default=1.0, help='Gaussian blur radius for height map')
|
|
||||||
parser.add_argument('--bump-scale', type=float, default=60.0, help='Normal map bump scale')
|
|
||||||
parser.add_argument('--output-color', type=str, default='assets/moon_disk.png', help='Output color filename')
|
|
||||||
parser.add_argument('--output-normal', type=str, default='assets/moon_normal.png', help='Output normal filename')
|
|
||||||
parser.add_argument('--skip-download', action='store_true', help='Skip downloading files')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
internal_size = args.size * args.supersample
|
|
||||||
|
|
||||||
# Ensure assets dir exists
|
|
||||||
os.makedirs(os.path.dirname(args.output_color) or '.', exist_ok=True)
|
|
||||||
|
|
||||||
# 1. Download
|
|
||||||
if not args.skip_download:
|
|
||||||
download_file(COLOR_URL, SRC_COLOR_FILENAME)
|
|
||||||
download_file(DISP_URL, SRC_DISP_FILENAME)
|
|
||||||
|
|
||||||
# 2. Process Color
|
|
||||||
print(f"Processing Color Map (Internal Size: {internal_size}x{internal_size})...")
|
|
||||||
try:
|
|
||||||
img_color = Image.open(SRC_COLOR_FILENAME).convert('RGB')
|
|
||||||
out_color = equirectangular_to_orthographic(img_color, internal_size)
|
|
||||||
|
|
||||||
if args.supersample > 1:
|
|
||||||
print(f"Downsampling Color to {args.size}x{args.size}...")
|
|
||||||
out_color = out_color.resize((args.size, args.size), Image.LANCZOS)
|
|
||||||
|
|
||||||
out_color.save(args.output_color)
|
|
||||||
print(f"Saved {args.output_color}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error processing color: {e}")
|
|
||||||
|
|
||||||
# 3. Process Normal
|
|
||||||
print(f"Processing Displacement Map (Internal Size: {internal_size}x{internal_size})...")
|
|
||||||
try:
|
|
||||||
from PIL import ImageFile
|
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
|
||||||
|
|
||||||
# Check if file exists
|
|
||||||
if not os.path.exists(SRC_DISP_FILENAME):
|
|
||||||
print(f"Displacement map {SRC_DISP_FILENAME} not found!")
|
|
||||||
return
|
|
||||||
|
|
||||||
img_disp = Image.open(SRC_DISP_FILENAME)
|
|
||||||
out_disp = equirectangular_to_orthographic(img_disp, internal_size)
|
|
||||||
|
|
||||||
# Compute Normals
|
|
||||||
out_normal = compute_normal_map(out_disp, scale=args.bump_scale, blur_radius=args.blur)
|
|
||||||
|
|
||||||
if args.supersample > 1:
|
|
||||||
print(f"Downsampling Normal to {args.size}x{args.size}...")
|
|
||||||
out_normal = out_normal.resize((args.size, args.size), Image.LANCZOS)
|
|
||||||
|
|
||||||
out_normal.save(args.output_normal)
|
|
||||||
print(f"Saved {args.output_normal}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error processing normal: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
print("Done.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -49,9 +49,6 @@
|
|||||||
"libs/fgviewer/README.md": {
|
"libs/fgviewer/README.md": {
|
||||||
"dest": "dup/fgviewer.md"
|
"dest": "dup/fgviewer.md"
|
||||||
},
|
},
|
||||||
"libs/viewer/README.md": {
|
|
||||||
"dest": "dup/viewer.md"
|
|
||||||
},
|
|
||||||
"ios/CocoaPods/README.md": {
|
"ios/CocoaPods/README.md": {
|
||||||
"dest": "release/cocoapods.md"
|
"dest": "release/cocoapods.md"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
- [gltfio](./dup/gltfio.md)
|
- [gltfio](./dup/gltfio.md)
|
||||||
- [iblprefilter](./dup/iblprefilter.md)
|
- [iblprefilter](./dup/iblprefilter.md)
|
||||||
- [matdbg](./dup/matdbg.md)
|
- [matdbg](./dup/matdbg.md)
|
||||||
- [viewer](./dup/viewer.md)
|
|
||||||
- [uberz](./dup/uberz.md)
|
- [uberz](./dup/uberz.md)
|
||||||
- [Tools](./notes/tools.md)
|
- [Tools](./notes/tools.md)
|
||||||
- [beamsplitter](./dup/beamsplitter.md)
|
- [beamsplitter](./dup/beamsplitter.md)
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
# Building Skybox Assets
|
|
||||||
|
|
||||||
This directory contains the source code for the Simulated Skybox sample. The assets (material and textures) are pre-built in the `assets/` directory.
|
|
||||||
|
|
||||||
If you need to modify the material or regenerate the moon textures, you can use the scripts provided in the `tools/` directory.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- **Filament**: You must have a built version of Filament. The scripts assume a standard CMake build output structure (e.g., `out/cmake-release`).
|
|
||||||
- **Python 3**: Required for texture generation.
|
|
||||||
- **Python Dependencies**: `numpy`, `Pillow` (automatically installed if missing, via pip).
|
|
||||||
|
|
||||||
## Rebuilding the Material
|
|
||||||
|
|
||||||
If you modify `simulated_skybox.mat`, you must recompile it into a `.filamat` file.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run from the sample root or tools directory
|
|
||||||
./tools/build_material.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This will update `assets/simulated_skybox.filamat`.
|
|
||||||
|
|
||||||
## Regenerating Moon Textures
|
|
||||||
|
|
||||||
If you want to change the moon's resolution, bump scale, or blur, you can regenerate the textures.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run from the sample root or tools directory
|
|
||||||
./tools/generate_moon_assets.sh [size]
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```bash
|
|
||||||
./tools/generate_moon_assets.sh 512
|
|
||||||
```
|
|
||||||
|
|
||||||
This will download raw NASA data (if not present) and generate:
|
|
||||||
- `assets/moon_disk.png`
|
|
||||||
- `assets/moon_normal.png`
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# Analytic Skybox Sample
|
|
||||||
|
|
||||||
This sample demonstrates a **fully procedural, single-pass skybox shader** capable of simulating a dynamic day-night cycle, atmospheric scattering, volumetric clouds, and water reflections.
|
|
||||||
|
|
||||||
It is designed for graphics engineers and technical artists who need a lightweight yet physically plausible environment background without relying on static HDRI textures.
|
|
||||||
|
|
||||||
## Features & Performance
|
|
||||||
|
|
||||||
The shader uses a "Uber-Shader" approach where all features are computed per-pixel. Features can be toggled or tuned via uniforms to balance quality vs performance.
|
|
||||||
|
|
||||||
| Feature | Cost | Control | Description |
|
|
||||||
| :--- | :---: | :--- | :--- |
|
|
||||||
| **Atmosphere** | 🟡 **Medium** | `turbidity`, `rayleigh`, `mie` | Analytic Rayleigh & Mie scattering. Physically based colors. |
|
|
||||||
| **Sun Disk** | 🟢 **Low** | `sunHalo` (Radius, Limb) | Analytic sphere intersection with limb darkening. Conservation of energy (Lux). |
|
|
||||||
| **Moon & Earthshine** | 🟢 **Low** | `sunHalo2`, `moonIntensity` | Resolved moon disk with geometric phases and dynamic Earthshine. |
|
|
||||||
| **Stars** | 🟢 **Low** | `starControl` (Density) | High-frequency procedural noise. Occluded by clouds/moon. |
|
|
||||||
| **Clouds** | 🔴 **High** | `cloudControl` (Coverage, Density) | 4-Octave 3D Fractal Brownian Motion (FBM). Dominates the cost when enabled. |
|
|
||||||
| **Heat Shimmer** | 🟡 **Medium** | `shimmerControl` | UV perturbation near the horizon to simulate mirages. |
|
|
||||||
| **Water Reflection** | 🟣 **Very High** | `waterControl` | **Renders the sky twice**. Includes procedural waves (FBM) and fresnel. |
|
|
||||||
|
|
||||||
> **Note**: Rendering water (`V.y < 0`) is significantly more expensive (~2.5x) than the sky because it requires re-evaluating the atmospheric scattering and cloud noise for the reflection vector.
|
|
||||||
|
|
||||||
## Shader Techniques
|
|
||||||
|
|
||||||
### 1. Analytic Atmospheric Scattering
|
|
||||||
Based on the **Hoffman & Preetham** model. It solves the single-scattering integral analytically for air molecules (Rayleigh) and aerosols (Mie).
|
|
||||||
- **Rayleigh**: Produces the deep blue sky and red sunset colors.
|
|
||||||
- **Mie**: Produces the white halo around the sun and general haziness.
|
|
||||||
- **Optimization**: Uses a simplified optical depth approximation ("Air Mass") to avoid expensive ray-marching.
|
|
||||||
|
|
||||||
### 2. Procedural Clouds (3D Noise)
|
|
||||||
Clouds are rendered as a spherical shell at a specific altitude.
|
|
||||||
- **Technique**: Ray-sphere intersection finds the entry point, then **3D FBM Noise** determines density.
|
|
||||||
- **Lighting**: Uses a "Silver Lining" approximation (strong forward scattering) and Beers-Lambert attenuation for dark underbellies.
|
|
||||||
- **Animation**: The noise coordinate logic helps simulate wind drift and shape evolution over time.
|
|
||||||
|
|
||||||
### 3. Infinite Water Ocean
|
|
||||||
When looking below the horizon, the shader switches to "Water Mode".
|
|
||||||
- **Geometry**: A flat plane at $y=0$.
|
|
||||||
- **Waves**: Generated using **Derivative-Based Noise** (or Finite Difference). This creates slope vectors that perturb the normal without needing actual geometry.
|
|
||||||
- **Reflection**: A ray is cast from the water surface back into the sky ($R = \text{reflect}(V, N)$). The sky function is called again with $R$ to get the reflected color.
|
|
||||||
|
|
||||||
### 4. Dynamic Tone Mapping
|
|
||||||
Applies a custom tone mapping curve that varies with Sun Elevation.
|
|
||||||
- **Noon**: Linear/Gamma (Standard).
|
|
||||||
- **Sunset**: Higher contrast curve to compress the dynamic range and enhance the rich sunset oranges/purples.
|
|
||||||
|
|
||||||
## Integration
|
|
||||||
|
|
||||||
To use this in your own Filament application:
|
|
||||||
|
|
||||||
1. **Compile the Material**:
|
|
||||||
Use `matc` to compile `simulated_skybox.mat` into a `.filamat` file.
|
|
||||||
```bash
|
|
||||||
matc -p mobile -a opengl -o assets/simulated_skybox.filamat simulated_skybox.mat
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Load in JavaScript/C++**:
|
|
||||||
Create a Skybox entity and assign the material.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// JavaScript Example
|
|
||||||
const material = engine.createMaterial('assets/simulated_skybox.filamat');
|
|
||||||
const skybox = engine.createSkybox(material);
|
|
||||||
scene.setSkybox(skybox);
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Update Uniforms**:
|
|
||||||
The shader requires specific uniforms (Sun Direction, Time, etc.) to be updated every frame. See `SimulatedSkybox.js` for a reference implementation of the uniform buffer management.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
* **Hoffman & Preetham (2002)**: *"Real-time Light-Atmosphere Interactions"*
|
|
||||||
* **Henyey & Greenstein (1941)**: *"Diffuse radiation in the galaxy"* (Mie Phase Function)
|
|
||||||
* **Kasten & Young (1989)**: *"Revised optical air mass tables"*
|
|
||||||
* **Three.js / Sky.js**: Empirical adjustments for "Golden Hour" aesthetics.
|
|
||||||
@@ -21,31 +21,13 @@ class SimulatedSkybox {
|
|||||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||||
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
this.starControl = [1.0, 1.0]; // x=Density (0-1), y=Enabled (0-1)
|
||||||
this.focalLength = 24.0;
|
|
||||||
this.height = 1000.0;
|
|
||||||
this.planetRadius = 6360.0;
|
this.planetRadius = 6360.0;
|
||||||
|
|
||||||
// Sun Halo
|
// Sun Halo
|
||||||
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
|
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
|
||||||
// Sun Halo
|
|
||||||
this.sunHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.5, 1.0, 1.0];
|
this.sunHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.5, 1.0, 1.0];
|
||||||
|
|
||||||
// Moon Parameters (Mapped to Secondary Sun)
|
|
||||||
this.moonDirection = [-0.2, 0.8, -0.2]; // Default Moon Pos
|
|
||||||
this.moonIntensity = 1.0; // Scale Factor (1.0 = Physical Peak)
|
|
||||||
// x=cos(rad), y=sin(rad) [Precision Fix], z=intensity, w=enabled
|
|
||||||
this.moonHalo = [Math.cos(0.5 * Math.PI / 180.0), Math.sin(0.5 * Math.PI / 180.0), 1.0, 0.0]; // Disabled by default
|
|
||||||
|
|
||||||
this.moonTextureObj = null;
|
|
||||||
this.moonNormalObj = null;
|
|
||||||
this.milkyWayTextureObj = null;
|
|
||||||
|
|
||||||
// Milky Way Parameters
|
|
||||||
// x=Intensity, y=Saturation, z=Unused
|
|
||||||
this.milkyWayControl = [1.0, 1.0, 0.07];
|
|
||||||
this.milkyWayEnabled = true;
|
|
||||||
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
|
||||||
this.initEntity();
|
this.initEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,184 +44,6 @@ class SimulatedSkybox {
|
|||||||
rcm.setMaterialInstanceAt(instance, 0, this.materialInstance);
|
rcm.setMaterialInstanceAt(instance, 0, this.materialInstance);
|
||||||
|
|
||||||
console.log("Material loaded and bound.");
|
console.log("Material loaded and bound.");
|
||||||
|
|
||||||
// Load Moon Texture
|
|
||||||
try {
|
|
||||||
const texUrl = 'assets/moon_disk.png';
|
|
||||||
const Texture = Filament.Texture;
|
|
||||||
const TextureSampler = Filament.TextureSampler;
|
|
||||||
const PixelDataFormat = Filament.PixelDataFormat;
|
|
||||||
const PixelDataType = Filament.PixelDataType;
|
|
||||||
const TextureUsage = Filament.Texture$Usage;
|
|
||||||
const TextureFormat = Filament.Texture$InternalFormat;
|
|
||||||
const MinFilter = Filament.MinFilter;
|
|
||||||
const MagFilter = Filament.MagFilter;
|
|
||||||
const WrapMode = Filament.WrapMode;
|
|
||||||
const texResponse = await fetch(texUrl);
|
|
||||||
const texBlob = await texResponse.blob();
|
|
||||||
const bitmap = await createImageBitmap(texBlob);
|
|
||||||
|
|
||||||
const width = bitmap.width;
|
|
||||||
const height = bitmap.height;
|
|
||||||
|
|
||||||
this.moonTextureObj = Texture.Builder()
|
|
||||||
.width(width)
|
|
||||||
.height(height)
|
|
||||||
.levels(0xff)
|
|
||||||
.format(TextureFormat.SRGB8_A8)
|
|
||||||
.usage(TextureUsage.SAMPLEABLE.value | TextureUsage.UPLOADABLE.value | TextureUsage.GEN_MIPMAPPABLE.value)
|
|
||||||
.build(this.engine);
|
|
||||||
|
|
||||||
// Extract Data using a Canvas
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.drawImage(bitmap, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, width, height);
|
|
||||||
|
|
||||||
// Use RGBA data directly (Uint8ClampedArray)
|
|
||||||
// Filament supports RGBA upload for RGB/SRGB internal formats (drops alpha).
|
|
||||||
// This matches Canvas layout (Top-Down, RGBA) and is 4-byte aligned.
|
|
||||||
const pbd = Filament.PixelBuffer(
|
|
||||||
new Uint8Array(imageData.data.buffer), // Wrap buffer to ensure Uint8Array
|
|
||||||
PixelDataFormat.RGBA,
|
|
||||||
PixelDataType.UBYTE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.moonTextureObj.setImage(this.engine, 0, pbd);
|
|
||||||
this.moonTextureObj.generateMipmaps(this.engine);
|
|
||||||
|
|
||||||
const sampler = new TextureSampler(
|
|
||||||
MinFilter.LINEAR,
|
|
||||||
MagFilter.LINEAR,
|
|
||||||
WrapMode.CLAMP_TO_EDGE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.materialInstance.setTextureParameter('moonTexture', this.moonTextureObj, sampler);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to load moon texture:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load Moon Normal
|
|
||||||
try {
|
|
||||||
const texUrl = 'assets/moon_normal.png';
|
|
||||||
const Texture = Filament.Texture;
|
|
||||||
const TextureSampler = Filament.TextureSampler;
|
|
||||||
const PixelDataFormat = Filament.PixelDataFormat;
|
|
||||||
const PixelDataType = Filament.PixelDataType;
|
|
||||||
const TextureUsage = Filament.Texture$Usage;
|
|
||||||
const TextureFormat = Filament.Texture$InternalFormat;
|
|
||||||
const MinFilter = Filament.MinFilter;
|
|
||||||
const MagFilter = Filament.MagFilter;
|
|
||||||
const WrapMode = Filament.WrapMode;
|
|
||||||
const texResponse = await fetch(texUrl);
|
|
||||||
const texBlob = await texResponse.blob();
|
|
||||||
const bitmap = await createImageBitmap(texBlob);
|
|
||||||
|
|
||||||
const width = bitmap.width;
|
|
||||||
const height = bitmap.height;
|
|
||||||
|
|
||||||
this.moonNormalObj = Texture.Builder()
|
|
||||||
.width(width)
|
|
||||||
.height(height)
|
|
||||||
.levels(0xff)
|
|
||||||
.format(TextureFormat.RGBA8)
|
|
||||||
.usage(TextureUsage.SAMPLEABLE.value | TextureUsage.UPLOADABLE.value | TextureUsage.GEN_MIPMAPPABLE.value)
|
|
||||||
.build(this.engine);
|
|
||||||
|
|
||||||
// Extract Data using a Canvas
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.drawImage(bitmap, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, width, height);
|
|
||||||
|
|
||||||
// Use RGBA data directly
|
|
||||||
const pbd = Filament.PixelBuffer(
|
|
||||||
new Uint8Array(imageData.data.buffer),
|
|
||||||
PixelDataFormat.RGBA,
|
|
||||||
PixelDataType.UBYTE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.moonNormalObj.setImage(this.engine, 0, pbd);
|
|
||||||
this.moonNormalObj.generateMipmaps(this.engine);
|
|
||||||
|
|
||||||
const sampler = new TextureSampler(
|
|
||||||
MinFilter.LINEAR_MIPMAP_LINEAR,
|
|
||||||
MagFilter.LINEAR,
|
|
||||||
WrapMode.CLAMP_TO_EDGE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.materialInstance.setTextureParameter('moonNormal', this.moonNormalObj, sampler);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to load moon normal:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load Milky Way Texture
|
|
||||||
try {
|
|
||||||
const texUrl = 'assets/milkyway.png';
|
|
||||||
const Texture = Filament.Texture;
|
|
||||||
const TextureSampler = Filament.TextureSampler;
|
|
||||||
const PixelDataFormat = Filament.PixelDataFormat;
|
|
||||||
const PixelDataType = Filament.PixelDataType;
|
|
||||||
const TextureUsage = Filament.Texture$Usage;
|
|
||||||
const TextureFormat = Filament.Texture$InternalFormat;
|
|
||||||
const MinFilter = Filament.MinFilter;
|
|
||||||
const MagFilter = Filament.MagFilter;
|
|
||||||
const WrapMode = Filament.WrapMode;
|
|
||||||
const texResponse = await fetch(texUrl);
|
|
||||||
const texBlob = await texResponse.blob();
|
|
||||||
const bitmap = await createImageBitmap(texBlob);
|
|
||||||
|
|
||||||
const width = bitmap.width;
|
|
||||||
const height = bitmap.height;
|
|
||||||
|
|
||||||
this.milkyWayTextureObj = Texture.Builder()
|
|
||||||
.width(width)
|
|
||||||
.height(height)
|
|
||||||
.levels(0xff)
|
|
||||||
.format(TextureFormat.SRGB8_A8)
|
|
||||||
.usage(TextureUsage.SAMPLEABLE.value | TextureUsage.UPLOADABLE.value | TextureUsage.GEN_MIPMAPPABLE.value)
|
|
||||||
.build(this.engine);
|
|
||||||
|
|
||||||
// Extract Data using a Canvas
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.drawImage(bitmap, 0, 0);
|
|
||||||
try {
|
|
||||||
const imageData = ctx.getImageData(0, 0, width, height);
|
|
||||||
|
|
||||||
// Use RGBA data directly
|
|
||||||
const pbd = Filament.PixelBuffer(
|
|
||||||
new Uint8Array(imageData.data.buffer),
|
|
||||||
PixelDataFormat.RGBA,
|
|
||||||
PixelDataType.UBYTE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.milkyWayTextureObj.setImage(this.engine, 0, pbd);
|
|
||||||
this.milkyWayTextureObj.generateMipmaps(this.engine);
|
|
||||||
|
|
||||||
const sampler = new TextureSampler(
|
|
||||||
MinFilter.LINEAR_MIPMAP_LINEAR,
|
|
||||||
MagFilter.LINEAR,
|
|
||||||
WrapMode.REPEAT // Equirectangular wraps horizontally
|
|
||||||
);
|
|
||||||
|
|
||||||
this.materialInstance.setTextureParameter('milkyWayTexture', this.milkyWayTextureObj, sampler);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn("Milky Way texture data access failed (CORS?):", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to load milky way texture:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateCoefficients();
|
this.updateCoefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,16 +84,16 @@ class SimulatedSkybox {
|
|||||||
|
|
||||||
this.ib.setBuffer(this.engine, TRIANGLE_INDICES);
|
this.ib.setBuffer(this.engine, TRIANGLE_INDICES);
|
||||||
|
|
||||||
// Build the Renderable without material; it will be set later by loadMaterial.
|
// We create a dummy material first or wait?
|
||||||
|
// In JS we usually can't block. We'll rely on loadMaterial being called.
|
||||||
|
// For now, we build the Renderable without material, then set it later.
|
||||||
|
|
||||||
RenderableManager.Builder(1)
|
RenderableManager.Builder(1)
|
||||||
.geometry(0, PrimitiveType.TRIANGLES, this.vb, this.ib)
|
.geometry(0, PrimitiveType.TRIANGLES, this.vb, this.ib)
|
||||||
.culling(false)
|
.culling(false)
|
||||||
.castShadows(false)
|
.castShadows(false)
|
||||||
.receiveShadows(false)
|
.receiveShadows(false)
|
||||||
.priority(7) // Skybox priority
|
.priority(7) // Render behind translucent objects? 7 is skybox priority typically.
|
||||||
|
|
||||||
.build(this.engine, this.entity);
|
.build(this.engine, this.entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,95 +215,11 @@ class SimulatedSkybox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setStarControl(density, enabled) {
|
setStarControl(density, enabled) {
|
||||||
// Compensate for grid frequency reduction (350 -> 100)
|
this.starControl[0] = Math.max(0.0, Math.min(1.0, density));
|
||||||
// Fewer cells = fewer stars, so we increase density threshold.
|
|
||||||
// Factor ~ (350/100)^2 = 12.25
|
|
||||||
const compensatedDensity = density * 12.0;
|
|
||||||
this.starControl[0] = Math.max(0.0, Math.min(1.0, compensatedDensity));
|
|
||||||
this.starControl[1] = enabled ? 1.0 : 0.0;
|
this.starControl[1] = enabled ? 1.0 : 0.0;
|
||||||
this.updateCoefficients();
|
this.updateCoefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
setFocalLength(mm) {
|
|
||||||
this.focalLength = Math.max(1.0, mm);
|
|
||||||
this.updateStarFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
setResolution(height) {
|
|
||||||
this.height = Math.max(1.0, height);
|
|
||||||
this.updateStarFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStarFrequency() {
|
|
||||||
// World-Anchored Stars
|
|
||||||
// z = Fixed Frequency (World Space Grid)
|
|
||||||
// w = Pixel Scale (Screen Space Radius)
|
|
||||||
|
|
||||||
// Fixed Frequency: Defines the "Universe" coordinate system.
|
|
||||||
// Reduced to 100.0 to allow larger stars without clipping (square artifacts).
|
|
||||||
this.starControl[2] = 100.0;
|
|
||||||
|
|
||||||
// Pixel Scale in Radians
|
|
||||||
// We use linear scaling (24/f) instead of atan(fov) to ensure star size remains
|
|
||||||
// constant in pixels across all focal lengths (Perspective Projection).
|
|
||||||
const fovFactor = 24.0 / this.focalLength;
|
|
||||||
const pixelScale = (1.0 / this.height) * fovFactor;
|
|
||||||
|
|
||||||
// Pass to shader (w component)
|
|
||||||
// Target radius: 1.3 pixels (Diameter 2.6 pixels)
|
|
||||||
// Visible but sharp.
|
|
||||||
this.starControl[3] = pixelScale * 1.3;
|
|
||||||
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoonPosition(direction) {
|
|
||||||
// normalize
|
|
||||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
|
||||||
if (len > 0) {
|
|
||||||
this.moonDirection = [direction[0] / len, direction[1] / len, direction[2] / len];
|
|
||||||
}
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoonIntensity(intensity) {
|
|
||||||
this.moonIntensity = Math.max(0.0, intensity);
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoonRadius(degrees) {
|
|
||||||
const rad = degrees * (Math.PI / 180.0);
|
|
||||||
this.moonHalo[0] = Math.cos(rad);
|
|
||||||
this.moonHalo[1] = Math.sin(rad);
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMoonEnabled(enabled) {
|
|
||||||
this.moonHalo[3] = enabled ? 1.0 : 0.0;
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMilkyWayControl(intensity, saturation, blackPoint) {
|
|
||||||
this.milkyWayControl[0] = Math.max(0.0, intensity);
|
|
||||||
this.milkyWayControl[1] = Math.max(0.0, saturation);
|
|
||||||
if (blackPoint !== undefined) {
|
|
||||||
this.milkyWayControl[2] = Math.max(0.0, blackPoint);
|
|
||||||
}
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMilkyWayEnabled(enabled) {
|
|
||||||
this.milkyWayEnabled = !!enabled;
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMilkyWayRotation(rotationMatrix) {
|
|
||||||
if (rotationMatrix && rotationMatrix.length === 9) {
|
|
||||||
this.milkyWayRotation = rotationMatrix;
|
|
||||||
this.updateCoefficients();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCoefficients() {
|
updateCoefficients() {
|
||||||
if (!this.materialInstance) {
|
if (!this.materialInstance) {
|
||||||
console.warn("updateCoefficients called before material loaded");
|
console.warn("updateCoefficients called before material loaded");
|
||||||
@@ -577,130 +297,15 @@ class SimulatedSkybox {
|
|||||||
|
|
||||||
this.materialInstance.setFloatParameter('contrast', this.contrast);
|
this.materialInstance.setFloatParameter('contrast', this.contrast);
|
||||||
|
|
||||||
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity); // Lux scaling
|
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity);
|
||||||
this.materialInstance.setFloat3Parameter('nightColor', new Float32Array(nightColorScaled));
|
this.materialInstance.setFloat3Parameter('nightColor', new Float32Array(nightColorScaled));
|
||||||
|
|
||||||
this.materialInstance.setFloat4Parameter('shimmerControl', new Float32Array(shimmerUniform));
|
this.materialInstance.setFloat4Parameter('shimmerControl', new Float32Array(shimmerUniform));
|
||||||
this.materialInstance.setFloat4Parameter('cloudControl', new Float32Array(cloudUniform));
|
this.materialInstance.setFloat4Parameter('cloudControl', new Float32Array(cloudUniform));
|
||||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||||
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
this.materialInstance.setFloat2Parameter('starControl', new Float32Array(this.starControl));
|
||||||
|
|
||||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||||
|
|
||||||
// Moon Upload (Secondary Sun)
|
|
||||||
this.materialInstance.setFloat3Parameter('sunDirection2', new Float32Array(this.moonDirection));
|
|
||||||
|
|
||||||
// Calculate Moon Phase Factor (Lambertian Sphere)
|
|
||||||
// We model the moon as a Lambertian sphere to calculate its integrated brightness (illuminance)
|
|
||||||
// based on the phase angle (angle between Sun-Moon and Observer-Moon vectors).
|
|
||||||
//
|
|
||||||
// Phase Angle (alpha):
|
|
||||||
// For a distant observer (Earth), the phase angle can be approximated as the angle between
|
|
||||||
// the vector to the Sun and the vector to the Earth (from the Moon).
|
|
||||||
// cos(alpha) = -dot(L_moon, L_sun)
|
|
||||||
//
|
|
||||||
// Lambertian Phase Law:
|
|
||||||
// The integrated flux of a lit sphere varies as:
|
|
||||||
// Phi(alpha) = (1/PI) * (sin(alpha) + (PI - alpha) * cos(alpha))
|
|
||||||
// This gives 1.0 at Full Moon (alpha=0) and 0.0 at New Moon (alpha=PI).
|
|
||||||
|
|
||||||
const dotSM = this.sunDirection[0] * this.moonDirection[0] +
|
|
||||||
this.sunDirection[1] * this.moonDirection[1] +
|
|
||||||
this.sunDirection[2] * this.moonDirection[2];
|
|
||||||
|
|
||||||
// Final Intensity = Peak * Scale (No Phase Factor - Phase is handled in Shader via N.L)
|
|
||||||
const MOON_PEAK_LUX = 1.0;
|
|
||||||
const finalMoonIntensity = MOON_PEAK_LUX * this.moonIntensity;
|
|
||||||
|
|
||||||
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
|
||||||
|
|
||||||
// Scale Milky Way by Sun Intensity (Pre-exposed) to match dynamic range
|
|
||||||
// Calibration: 1.0 User Intensity ~ 0.025 Lux Relative (approx 2.5% of Sun Pixel Value at Day)
|
|
||||||
// At Sunny 16 (Sun ~ 2.6), this gives ~0.065 pixel brightness, which is visible but dim.
|
|
||||||
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
|
||||||
const mwUniform = [
|
|
||||||
mwIntensity * this.sunIntensity * 0.025,
|
|
||||||
this.milkyWayControl[1],
|
|
||||||
this.milkyWayControl[2]
|
|
||||||
];
|
|
||||||
this.materialInstance.setFloat3Parameter('milkyWayControl', new Float32Array(mwUniform));
|
|
||||||
this.materialInstance.setMat3Parameter('milkyWayRotation', new Float32Array(this.milkyWayRotation));
|
|
||||||
|
|
||||||
// Moon Halo Upload (Disk Visualization)
|
|
||||||
// Multiplier = 1.0 / SolidAngle
|
|
||||||
const moonSolidAngle = 2.0 * F_PI * (1.0 - this.moonHalo[0]);
|
|
||||||
const moonRadConv = 1.0 / Math.max(1e-9, moonSolidAngle);
|
|
||||||
const moonHaloUpload = [...this.moonHalo];
|
|
||||||
moonHaloUpload[2] *= moonRadConv;
|
|
||||||
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
|
||||||
|
|
||||||
// Solar Eclipse (CPU Calculation)
|
|
||||||
const sunRadius = Math.acos(this.sunHalo[0]);
|
|
||||||
const moonRadius = Math.acos(this.moonHalo[0]);
|
|
||||||
// Dot product of Sun and Moon directions
|
|
||||||
const dot = this.sunDirection[0] * this.moonDirection[0] +
|
|
||||||
this.sunDirection[1] * this.moonDirection[1] +
|
|
||||||
this.sunDirection[2] * this.moonDirection[2];
|
|
||||||
const separation = Math.acos(Math.max(-1.0, Math.min(1.0, dot)));
|
|
||||||
|
|
||||||
let eclipseFactor = 1.0;
|
|
||||||
// Only calculate if moon is enabled
|
|
||||||
if (this.moonHalo[3] > 0.5) {
|
|
||||||
const overlap = this.areaIntersection(sunRadius, moonRadius, separation);
|
|
||||||
const sunArea = Math.PI * sunRadius * sunRadius;
|
|
||||||
// Ensure we don't divide by zero and clamp result
|
|
||||||
const ratio = overlap / Math.max(1e-9, sunArea);
|
|
||||||
eclipseFactor = 1.0 - Math.max(0.0, Math.min(1.0, ratio));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safety check for NaN
|
|
||||||
if (isNaN(eclipseFactor)) {
|
|
||||||
console.warn("SimulatedSkybox: eclipseFactor is NaN, resetting to 1.0");
|
|
||||||
eclipseFactor = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.materialInstance.setFloatParameter('eclipseFactor', eclipseFactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
areaIntersection(r1, r2, d) {
|
|
||||||
// Circle intersection area
|
|
||||||
// r1, r2: radii
|
|
||||||
// d: distance between centers
|
|
||||||
|
|
||||||
// Case 1: Too far apart
|
|
||||||
if (d >= r1 + r2) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
// Case 2: One inside another
|
|
||||||
if (d <= Math.abs(r1 - r2)) {
|
|
||||||
return Math.PI * Math.min(r1, r2) * Math.min(r1, r2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const r1sq = r1 * r1;
|
|
||||||
const r2sq = r2 * r2;
|
|
||||||
|
|
||||||
// Law of Cosines for sector angles
|
|
||||||
// c1 = (d^2 + r1^2 - r2^2) / (2 * d * r1)
|
|
||||||
// c2 = (d^2 + r2^2 - r1^2) / (2 * d * r2)
|
|
||||||
// We clamp to [-1, 1] to avoid NaN from floating point errors
|
|
||||||
const c1 = Math.max(-1.0, Math.min(1.0, (d * d + r1sq - r2sq) / (2.0 * d * r1)));
|
|
||||||
const c2 = Math.max(-1.0, Math.min(1.0, (d * d + r2sq - r1sq) / (2.0 * d * r2)));
|
|
||||||
|
|
||||||
const part1 = r1sq * Math.acos(c1);
|
|
||||||
const part2 = r2sq * Math.acos(c2);
|
|
||||||
|
|
||||||
// Heron's formula for the triangle area * 2
|
|
||||||
// The sqrt term represents the area of the two triangles formed by the chord and centers.
|
|
||||||
|
|
||||||
|
|
||||||
// Robust sqrt
|
|
||||||
const val = (-d + r1 + r2) * (d + r1 - r2) * (d - r1 + r2) * (d + r1 + r2);
|
|
||||||
const part3 = 0.5 * Math.sqrt(Math.max(0.0, val));
|
|
||||||
|
|
||||||
return part1 + part2 - part3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 596 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 119 KiB |
@@ -1,332 +0,0 @@
|
|||||||
/*
|
|
||||||
(c) 2011-2015, Vladimir Agafonkin
|
|
||||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
|
||||||
https://github.com/mourner/suncalc
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function () { 'use strict';
|
|
||||||
|
|
||||||
// shortcuts for easier to read formulas
|
|
||||||
|
|
||||||
var PI = Math.PI,
|
|
||||||
sin = Math.sin,
|
|
||||||
cos = Math.cos,
|
|
||||||
tan = Math.tan,
|
|
||||||
asin = Math.asin,
|
|
||||||
atan = Math.atan2,
|
|
||||||
acos = Math.acos,
|
|
||||||
rad = PI / 180;
|
|
||||||
|
|
||||||
// sun calculations are based on https://aa.quae.nl/en/reken/zonpositie.html formulas
|
|
||||||
|
|
||||||
|
|
||||||
// date/time constants and conversions
|
|
||||||
|
|
||||||
var dayMs = 1000 * 60 * 60 * 24,
|
|
||||||
J1970 = 2440588,
|
|
||||||
J2000 = 2451545;
|
|
||||||
|
|
||||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
|
||||||
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
|
|
||||||
function toDays(date) { return toJulian(date) - J2000; }
|
|
||||||
|
|
||||||
|
|
||||||
// general calculations for position
|
|
||||||
|
|
||||||
var e = rad * 23.4397; // obliquity of the Earth
|
|
||||||
|
|
||||||
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
|
|
||||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
|
||||||
|
|
||||||
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
|
|
||||||
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
|
|
||||||
|
|
||||||
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
|
|
||||||
|
|
||||||
function astroRefraction(h) {
|
|
||||||
if (h < 0) // the following formula works for positive altitudes only.
|
|
||||||
h = 0; // if h = -0.08901179 a div/0 would occur.
|
|
||||||
|
|
||||||
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
|
||||||
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
|
|
||||||
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
|
|
||||||
}
|
|
||||||
|
|
||||||
// general sun calculations
|
|
||||||
|
|
||||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
|
||||||
|
|
||||||
function eclipticLongitude(M) {
|
|
||||||
|
|
||||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
|
||||||
P = rad * 102.9372; // perihelion of the Earth
|
|
||||||
|
|
||||||
return M + C + P + PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sunCoords(d) {
|
|
||||||
|
|
||||||
var M = solarMeanAnomaly(d),
|
|
||||||
L = eclipticLongitude(M);
|
|
||||||
|
|
||||||
return {
|
|
||||||
dec: declination(L, 0),
|
|
||||||
ra: rightAscension(L, 0)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var SunCalc = {};
|
|
||||||
|
|
||||||
|
|
||||||
// calculates sun position for a given date and latitude/longitude
|
|
||||||
|
|
||||||
SunCalc.getPosition = function (date, lat, lng) {
|
|
||||||
|
|
||||||
var lw = rad * -lng,
|
|
||||||
phi = rad * lat,
|
|
||||||
d = toDays(date),
|
|
||||||
|
|
||||||
c = sunCoords(d),
|
|
||||||
H = siderealTime(d, lw) - c.ra;
|
|
||||||
|
|
||||||
return {
|
|
||||||
azimuth: azimuth(H, phi, c.dec),
|
|
||||||
altitude: altitude(H, phi, c.dec)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// sun times configuration (angle, morning name, evening name)
|
|
||||||
|
|
||||||
var times = SunCalc.times = [
|
|
||||||
[-0.833, 'sunrise', 'sunset' ],
|
|
||||||
[-0.3, 'sunriseEnd', 'sunsetStart' ],
|
|
||||||
[-6, 'dawn', 'dusk' ],
|
|
||||||
[-12, 'nauticalDawn', 'nauticalDusk'],
|
|
||||||
[-18, 'nightEnd', 'night' ],
|
|
||||||
[6, 'goldenHourEnd', 'goldenHour' ]
|
|
||||||
];
|
|
||||||
|
|
||||||
// adds a custom time to the times config
|
|
||||||
|
|
||||||
SunCalc.addTime = function (angle, riseName, setName) {
|
|
||||||
times.push([angle, riseName, setName]);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// calculations for sun times
|
|
||||||
|
|
||||||
var J0 = 0.0009;
|
|
||||||
|
|
||||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
|
||||||
|
|
||||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
|
||||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
|
||||||
|
|
||||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
|
||||||
|
|
||||||
// returns set time for the given sun altitude
|
|
||||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
|
||||||
|
|
||||||
var w = hourAngle(h, phi, dec),
|
|
||||||
a = approxTransit(w, lw, n);
|
|
||||||
return solarTransitJ(a, M, L);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
|
||||||
// the observer height (in meters) relative to the horizon
|
|
||||||
|
|
||||||
SunCalc.getTimes = function (date, lat, lng, height) {
|
|
||||||
|
|
||||||
height = height || 0;
|
|
||||||
|
|
||||||
var lw = rad * -lng,
|
|
||||||
phi = rad * lat,
|
|
||||||
|
|
||||||
dh = observerAngle(height),
|
|
||||||
|
|
||||||
d = toDays(date),
|
|
||||||
n = julianCycle(d, lw),
|
|
||||||
ds = approxTransit(0, lw, n),
|
|
||||||
|
|
||||||
M = solarMeanAnomaly(ds),
|
|
||||||
L = eclipticLongitude(M),
|
|
||||||
dec = declination(L, 0),
|
|
||||||
|
|
||||||
Jnoon = solarTransitJ(ds, M, L);
|
|
||||||
|
|
||||||
var result = {
|
|
||||||
solarNoon: fromJulian(Jnoon),
|
|
||||||
nadir: fromJulian(Jnoon - 0.5)
|
|
||||||
};
|
|
||||||
|
|
||||||
var i, len, time, h0, Jset, Jrise;
|
|
||||||
|
|
||||||
for (i = 0, len = times.length; i < len; i += 1) {
|
|
||||||
time = times[i];
|
|
||||||
|
|
||||||
h0 = (time[0] + dh) * rad;
|
|
||||||
|
|
||||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
|
||||||
Jrise = Jnoon - (Jset - Jnoon);
|
|
||||||
|
|
||||||
result[time[1]] = fromJulian(Jrise);
|
|
||||||
result[time[2]] = fromJulian(Jset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
|
|
||||||
|
|
||||||
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
|
|
||||||
|
|
||||||
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
|
|
||||||
M = rad * (134.963 + 13.064993 * d), // mean anomaly
|
|
||||||
F = rad * (93.272 + 13.229350 * d), // mean distance
|
|
||||||
|
|
||||||
l = L + rad * 6.289 * sin(M), // longitude
|
|
||||||
b = rad * 5.128 * sin(F), // latitude
|
|
||||||
dt = 385001 - 20905 * cos(M); // distance to the moon in km
|
|
||||||
|
|
||||||
return {
|
|
||||||
ra: rightAscension(l, b),
|
|
||||||
dec: declination(l, b),
|
|
||||||
dist: dt
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
SunCalc.getMoonPosition = function (date, lat, lng) {
|
|
||||||
|
|
||||||
var lw = rad * -lng,
|
|
||||||
phi = rad * lat,
|
|
||||||
d = toDays(date),
|
|
||||||
|
|
||||||
c = moonCoords(d),
|
|
||||||
H = siderealTime(d, lw) - c.ra,
|
|
||||||
h = altitude(H, phi, c.dec),
|
|
||||||
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
|
||||||
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
|
|
||||||
|
|
||||||
return {
|
|
||||||
azimuth: azimuth(H, phi, c.dec),
|
|
||||||
altitude: h + astroRefraction(h), // altitude correction for refraction,
|
|
||||||
distance: c.dist,
|
|
||||||
parallacticAngle: pa
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// calculations for illumination parameters of the moon,
|
|
||||||
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
|
|
||||||
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
|
||||||
|
|
||||||
SunCalc.getMoonIllumination = function (date) {
|
|
||||||
|
|
||||||
var d = toDays(date || new Date()),
|
|
||||||
s = sunCoords(d),
|
|
||||||
m = moonCoords(d),
|
|
||||||
|
|
||||||
sdist = 149598000, // distance from Earth to Sun in km
|
|
||||||
|
|
||||||
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
|
|
||||||
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
|
|
||||||
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
|
|
||||||
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
|
|
||||||
|
|
||||||
return {
|
|
||||||
fraction: (1 + cos(inc)) / 2,
|
|
||||||
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
|
|
||||||
angle: angle
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function hoursLater(date, h) {
|
|
||||||
return new Date(date.valueOf() + h * dayMs / 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
|
||||||
|
|
||||||
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
|
|
||||||
var t = new Date(date);
|
|
||||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
|
||||||
else t.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
var hc = 0.133 * rad,
|
|
||||||
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
|
|
||||||
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
|
|
||||||
|
|
||||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
|
||||||
for (var i = 1; i <= 24; i += 2) {
|
|
||||||
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
|
||||||
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
|
||||||
|
|
||||||
a = (h0 + h2) / 2 - h1;
|
|
||||||
b = (h2 - h0) / 2;
|
|
||||||
xe = -b / (2 * a);
|
|
||||||
ye = (a * xe + b) * xe + h1;
|
|
||||||
d = b * b - 4 * a * h1;
|
|
||||||
roots = 0;
|
|
||||||
|
|
||||||
if (d >= 0) {
|
|
||||||
dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
|
||||||
x1 = xe - dx;
|
|
||||||
x2 = xe + dx;
|
|
||||||
if (Math.abs(x1) <= 1) roots++;
|
|
||||||
if (Math.abs(x2) <= 1) roots++;
|
|
||||||
if (x1 < -1) x1 = x2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (roots === 1) {
|
|
||||||
if (h0 < 0) rise = i + x1;
|
|
||||||
else set = i + x1;
|
|
||||||
|
|
||||||
} else if (roots === 2) {
|
|
||||||
rise = i + (ye < 0 ? x2 : x1);
|
|
||||||
set = i + (ye < 0 ? x1 : x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rise && set) break;
|
|
||||||
|
|
||||||
h0 = h2;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = {};
|
|
||||||
|
|
||||||
if (rise) result.rise = hoursLater(t, rise);
|
|
||||||
if (set) result.set = hoursLater(t, set);
|
|
||||||
|
|
||||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// observerAngle helper was missing in my copy-paste?
|
|
||||||
// Ah, it was in the chunks. Let me check the chunks again.
|
|
||||||
// Function `observerAngle` was in chunk 0.
|
|
||||||
// `function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }`
|
|
||||||
// I included it.
|
|
||||||
|
|
||||||
// Note: I replaced var with const/let or kept var?
|
|
||||||
// The original used const/let but I used var in my head above?
|
|
||||||
// I will just use the original code but adapted.
|
|
||||||
// Actually, to be safe, I'll use the code I wrote in this block which is adapted to use 'var' (ES5 safe) or just ES6 is fine.
|
|
||||||
// The user Environment is modern (Chrome/ES6 likely).
|
|
||||||
// But for safety:
|
|
||||||
// I'll stick to 'var' definitions I wrote above which seem complete?
|
|
||||||
// Wait, I might have missed `observerAngle` definition in my manual reconstruction above?
|
|
||||||
// No, `observerAngle` is called in `getTimes`.
|
|
||||||
// I MUST define it.
|
|
||||||
// I added it in the loop above? No.
|
|
||||||
// Let me double check the `write_to_file` content I prepared.
|
|
||||||
// I see:
|
|
||||||
// `function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }`
|
|
||||||
// Yes, it is there.
|
|
||||||
|
|
||||||
// export
|
|
||||||
window.SunCalc = SunCalc;
|
|
||||||
|
|
||||||
}());
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
# Result: /Users/mathias/sources/git/filament/out/cmake-release/tools/matc/matc
|
# Result: /Users/mathias/sources/git/filament/out/cmake-release/tools/matc/matc
|
||||||
MATC="../../../../../out/cmake-release/tools/matc/matc"
|
MATC="../../../../out/cmake-release/tools/matc/matc"
|
||||||
|
|
||||||
# Navigate to script directory to ensure relative paths work
|
# Navigate to script directory to ensure relative paths work
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
$MATC -a opengl -p mobile -o ../assets/simulated_skybox.filamat ../simulated_skybox.mat
|
$MATC -a opengl -p mobile -o assets/simulated_skybox.filamat simulated_skybox.mat
|
||||||
echo "Material recompiled to assets/simulated_skybox.filamat"
|
echo "Material recompiled to assets/simulated_skybox.filamat"
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<canvas></canvas>
|
<canvas></canvas>
|
||||||
|
|
||||||
<!-- Filament -->
|
<!-- Filament -->
|
||||||
<script src="filament.js"></script>
|
<script src="filament.js"></script>
|
||||||
<script src="gl-matrix-min.js"></script>
|
<script src="gl-matrix-min.js"></script>
|
||||||
@@ -32,13 +31,9 @@
|
|||||||
<!-- UI -->
|
<!-- UI -->
|
||||||
<script src="lil-gui.js"></script>
|
<script src="lil-gui.js"></script>
|
||||||
|
|
||||||
<!-- Utils -->
|
|
||||||
<script src="assets/suncalc_global.js"></script>
|
|
||||||
|
|
||||||
<!-- App -->
|
<!-- App -->
|
||||||
|
<script src="SimulatedSkybox.js?v=26"></script>
|
||||||
<script src="SimulatedSkybox.js?v=9999"></script>
|
<script src="main.js?v=26"></script>
|
||||||
<script src="main.js?v=9999"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,123 +1,10 @@
|
|||||||
|
|
||||||
// main.js
|
// main.js
|
||||||
|
|
||||||
Filament.init(['assets/simulated_skybox.filamat?v=' + Date.now()], () => {
|
Filament.init(['assets/simulated_skybox.filamat'], () => {
|
||||||
window.app = new App(document.getElementsByTagName('canvas')[0]);
|
window.app = new App(document.getElementsByTagName('canvas')[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper: Julian Date
|
|
||||||
function getJD(date) {
|
|
||||||
return date.getTime() / 86400000.0 + 2440587.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: GMST from Date
|
|
||||||
function getGMST(date) {
|
|
||||||
const JD = getJD(date);
|
|
||||||
const D = JD - 2451545.0;
|
|
||||||
// GMST = 18.697... + 24.0657... * D
|
|
||||||
let gmst = 18.697374558 + 24.06570982441908 * D;
|
|
||||||
gmst = gmst % 24.0;
|
|
||||||
if (gmst < 0) gmst += 24.0;
|
|
||||||
return gmst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Matrix Rotation
|
|
||||||
function rotateX(m, angle) {
|
|
||||||
const c = Math.cos(angle);
|
|
||||||
const s = Math.sin(angle);
|
|
||||||
const m1 = m[1], m2 = m[2];
|
|
||||||
const m4 = m[4], m5 = m[5];
|
|
||||||
const m7 = m[7], m8 = m[8];
|
|
||||||
m[1] = m1 * c - m2 * s;
|
|
||||||
m[2] = m1 * s + m2 * c;
|
|
||||||
m[4] = m4 * c - m5 * s;
|
|
||||||
m[5] = m4 * s + m5 * c;
|
|
||||||
m[7] = m7 * c - m8 * s;
|
|
||||||
m[8] = m7 * s + m8 * c;
|
|
||||||
}
|
|
||||||
function rotateY(m, angle) {
|
|
||||||
const c = Math.cos(angle);
|
|
||||||
const s = Math.sin(angle);
|
|
||||||
const m0 = m[0], m2 = m[2];
|
|
||||||
const m3 = m[3], m5 = m[5];
|
|
||||||
const m6 = m[6], m8 = m[8];
|
|
||||||
m[0] = m0 * c + m2 * s;
|
|
||||||
m[2] = -m0 * s + m2 * c;
|
|
||||||
m[3] = m3 * c + m5 * s;
|
|
||||||
m[5] = -m3 * s + m5 * c;
|
|
||||||
m[6] = m6 * c + m8 * s;
|
|
||||||
m[8] = -m6 * s + m8 * c;
|
|
||||||
}
|
|
||||||
function rotateZ(m, angle) {
|
|
||||||
const c = Math.cos(angle);
|
|
||||||
const s = Math.sin(angle);
|
|
||||||
const m0 = m[0], m1 = m[1];
|
|
||||||
const m3 = m[3], m4 = m[4];
|
|
||||||
const m6 = m[6], m7 = m[7];
|
|
||||||
m[0] = m0 * c - m1 * s;
|
|
||||||
m[1] = m0 * s + m1 * c;
|
|
||||||
m[3] = m3 * c - m4 * s;
|
|
||||||
m[4] = m3 * s + m4 * c;
|
|
||||||
m[6] = m6 * c - m7 * s;
|
|
||||||
m[7] = m6 * s + m7 * c;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Galactic to Equatorial (J2000)
|
|
||||||
// This matrix converts Galactic vectors to Equatorial vectors.
|
|
||||||
// Or effectively, if we want to render Galactic texture from Equatorial View vector V_eq:
|
|
||||||
// V_gal = Inv(Rot_Gal_to_Eq) * V_eq = Rot_Eq_to_Gal * V_eq.
|
|
||||||
// The shader does: V_gal = Rotation * V_world.
|
|
||||||
// So Rotation = Rot_Eq_to_Gal * Rot_World_to_Eq.
|
|
||||||
//
|
|
||||||
// Galactic North Pole (J2000): RA = 192.85948, Dec = 27.12825
|
|
||||||
// Ascending Node: RA = 282.85
|
|
||||||
//
|
|
||||||
// Pre-computed Rotation Matrix (Equatorial -> Galactic)
|
|
||||||
// Based on standard transformation matrices.
|
|
||||||
//
|
|
||||||
// R_eq_gal =
|
|
||||||
// [ -0.054876 -0.873437 -0.483835 ]
|
|
||||||
// [ 0.494109 -0.444830 0.746982 ]
|
|
||||||
// [ -0.867666 -0.198076 0.455984 ]
|
|
||||||
//
|
|
||||||
// Let's use this static definition.
|
|
||||||
const MAT_EQ_TO_GAL = [
|
|
||||||
-0.054876, 0.494109, -0.867666,
|
|
||||||
-0.873437, -0.444830, -0.198076,
|
|
||||||
-0.483835, 0.746982, 0.455984
|
|
||||||
];
|
|
||||||
|
|
||||||
// Matrix multiplication 3x3
|
|
||||||
function multiplyMat3(a, b) {
|
|
||||||
const out = new Float32Array(9);
|
|
||||||
// Row-major or Column-major? Filament is Column-major usually.
|
|
||||||
// GLSL is Column-major.
|
|
||||||
// Mat3 in array: [col0.x, col0.y, col0.z, col1.x, ...]
|
|
||||||
// So a[0] is (0,0), a[1] is (1,0), a[3] is (0,1).
|
|
||||||
//
|
|
||||||
// out = a * b
|
|
||||||
const a00 = a[0], a10 = a[1], a20 = a[2];
|
|
||||||
const a01 = a[3], a11 = a[4], a21 = a[5];
|
|
||||||
const a02 = a[6], a12 = a[7], a22 = a[8];
|
|
||||||
|
|
||||||
const b00 = b[0], b10 = b[1], b20 = b[2];
|
|
||||||
const b01 = b[3], b11 = b[4], b21 = b[5];
|
|
||||||
const b02 = b[6], b12 = b[7], b22 = b[8];
|
|
||||||
|
|
||||||
out[0] = a00 * b00 + a01 * b10 + a02 * b20;
|
|
||||||
out[1] = a10 * b00 + a11 * b10 + a12 * b20;
|
|
||||||
out[2] = a20 * b00 + a21 * b10 + a22 * b20;
|
|
||||||
|
|
||||||
out[3] = a00 * b01 + a01 * b11 + a02 * b21;
|
|
||||||
out[4] = a10 * b01 + a11 * b11 + a12 * b21;
|
|
||||||
out[5] = a20 * b01 + a21 * b11 + a22 * b21;
|
|
||||||
|
|
||||||
out[6] = a00 * b02 + a01 * b12 + a02 * b22;
|
|
||||||
out[7] = a10 * b02 + a11 * b12 + a12 * b22;
|
|
||||||
out[8] = a20 * b02 + a21 * b12 + a22 * b22;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
constructor(canvas) {
|
constructor(canvas) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
@@ -128,10 +15,13 @@ class App {
|
|||||||
this.skybox.entity = this.skybox.entity; // Ensuring access if needed
|
this.skybox.entity = this.skybox.entity; // Ensuring access if needed
|
||||||
this.scene.addEntity(this.skybox.entity);
|
this.scene.addEntity(this.skybox.entity);
|
||||||
|
|
||||||
// Load the material explicitly. SimulatedSkybox.loadMaterial fetches it.
|
// Load the material explicitly since we passed it to init but SimulatedSkybox needs to bind it
|
||||||
|
// Actually SimulatedSkybox.loadMaterial fetches it.
|
||||||
const matUrl = 'assets/simulated_skybox.filamat?v=' + Date.now();
|
// Since we already loaded it in Filament.init, we can arguably just use it if we had a way to access the asset.
|
||||||
this.skybox.loadMaterial(matUrl).then(() => {
|
// But Filament.init assets are for internal or easy access via assets object if configured?
|
||||||
|
// Let's just let SimulatedSkybox fetch it again or use a blob if we wanted.
|
||||||
|
// Simpler: Just let SimulatedSkybox fetch it.
|
||||||
|
this.skybox.loadMaterial('assets/simulated_skybox.filamat').then(() => {
|
||||||
this.initGUI();
|
this.initGUI();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -181,15 +71,6 @@ class App {
|
|||||||
|
|
||||||
this.initControls(); // Initialize controls immediately
|
this.initControls(); // Initialize controls immediately
|
||||||
|
|
||||||
this.mwParams = {
|
|
||||||
enabled: true,
|
|
||||||
intensity: 1.0,
|
|
||||||
saturation: 1.0,
|
|
||||||
blackPoint: 0.07,
|
|
||||||
siderealTime: 0.0, // Hours [0-24]
|
|
||||||
latitude: 34.0, // Default Lat
|
|
||||||
};
|
|
||||||
|
|
||||||
this.resize();
|
this.resize();
|
||||||
window.addEventListener('resize', this.resize.bind(this));
|
window.addEventListener('resize', this.resize.bind(this));
|
||||||
|
|
||||||
@@ -218,12 +99,6 @@ class App {
|
|||||||
const exposure = this.getExposure();
|
const exposure = this.getExposure();
|
||||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||||
this.skybox.setSunIntensity(preExposedIntensity);
|
this.skybox.setSunIntensity(preExposedIntensity);
|
||||||
|
|
||||||
// Moon Exposure
|
|
||||||
if (this.mParams) {
|
|
||||||
const preExposedMoon = this.mParams.intensity * exposure;
|
|
||||||
this.skybox.setMoonIntensity(preExposedMoon);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCameraProjection() {
|
updateCameraProjection() {
|
||||||
@@ -231,7 +106,6 @@ class App {
|
|||||||
const height = this.canvas.height;
|
const height = this.canvas.height;
|
||||||
const aspect = width / height;
|
const aspect = width / height;
|
||||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||||
if (this.skybox) this.skybox.setFocalLength(this.params.focalLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initGUI() {
|
initGUI() {
|
||||||
@@ -240,7 +114,10 @@ class App {
|
|||||||
const sky = this.skybox;
|
const sky = this.skybox;
|
||||||
|
|
||||||
// Initialize local params from skybox defaults
|
// Initialize local params from skybox defaults
|
||||||
|
// Initialize local params from skybox defaults
|
||||||
|
// REMOVED: Do not overwrite this.params from sky.sunDirection (Zenith)
|
||||||
|
// const currentDir = sky.sunDirection;
|
||||||
|
// this.params.sunTheta = ...
|
||||||
|
|
||||||
const updateSun = () => {
|
const updateSun = () => {
|
||||||
const theta = this.params.sunTheta;
|
const theta = this.params.sunTheta;
|
||||||
@@ -251,165 +128,29 @@ class App {
|
|||||||
sky.setSunPosition([x, y, z]);
|
sky.setSunPosition([x, y, z]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sun UI Proxy
|
|
||||||
this.sunUI = {
|
|
||||||
azimuth: (this.params.sunPhi * 180.0 / Math.PI) % 360.0,
|
|
||||||
height: Math.cos(this.params.sunTheta)
|
|
||||||
};
|
|
||||||
if (this.sunUI.azimuth < 0) this.sunUI.azimuth += 360.0;
|
|
||||||
|
|
||||||
const sunFolder = gui.addFolder('Sun');
|
const sunFolder = gui.addFolder('Sun');
|
||||||
|
// Helper for "Sun Height" cosine slider like C++
|
||||||
sunFolder.add(this.sunUI, 'azimuth', 0.0, 360.0, 0.1).name('Azimuth').listen().onChange(v => {
|
const sunHeightParam = { height: Math.cos(this.params.sunTheta) };
|
||||||
this.params.sunPhi = v * (Math.PI / 180.0);
|
sunFolder.add(sunHeightParam, 'height', -0.2, 1.0).name('Height (Cos)').onChange(v => {
|
||||||
updateSun();
|
|
||||||
});
|
|
||||||
|
|
||||||
sunFolder.add(this.sunUI, 'height', -0.2, 1.0).name('Height (Cos)').listen().onChange(v => {
|
|
||||||
this.params.sunTheta = Math.acos(v);
|
this.params.sunTheta = Math.acos(v);
|
||||||
updateSun();
|
updateSun();
|
||||||
});
|
});
|
||||||
|
sunFolder.add(this.params, 'sunPhi', 0.0, Math.PI * 2).name('Azimuth').onChange(updateSun);
|
||||||
|
// Updated: Controls params.sunIntensity and triggers updateSunIntensity
|
||||||
|
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).onChange(v => this.updateSunIntensity());
|
||||||
|
|
||||||
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).name('Intensity').onChange(v => this.updateSunIntensity());
|
const sunDisk = sunFolder.addFolder('Disk');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const moonFolder = gui.addFolder('Moon');
|
|
||||||
this.mParams = {
|
|
||||||
enabled: true,
|
|
||||||
azimuth: 180.0,
|
|
||||||
height: Math.cos(45.0 * Math.PI / 180.0), // Default 45 degrees elevation -> cos(45) ~ 0.707
|
|
||||||
radius: 1.2,
|
|
||||||
intensity: 6.0
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateMoon = () => {
|
|
||||||
const az = this.mParams.azimuth * (Math.PI / 180.0);
|
|
||||||
const theta = Math.acos(this.mParams.height);
|
|
||||||
const phi = az;
|
|
||||||
|
|
||||||
const x = Math.sin(theta) * Math.cos(phi);
|
|
||||||
const y = Math.cos(theta);
|
|
||||||
const z = Math.sin(theta) * Math.sin(phi);
|
|
||||||
|
|
||||||
sky.setMoonPosition([x, y, z]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial Moon Sync
|
|
||||||
updateMoon();
|
|
||||||
sky.setMoonEnabled(this.mParams.enabled);
|
|
||||||
sky.setMoonRadius(this.mParams.radius);
|
|
||||||
sky.setMoonIntensity(this.mParams.intensity);
|
|
||||||
|
|
||||||
moonFolder.add(this.mParams, 'enabled').name('Enabled').onChange(v => sky.setMoonEnabled(v));
|
|
||||||
moonFolder.add(this.mParams, 'azimuth', 0.0, 360.0, 0.1).name('Azimuth').listen().onChange(updateMoon);
|
|
||||||
moonFolder.add(this.mParams, 'height', -0.2, 1.0).name('Height (Cos)').listen().onChange(updateMoon);
|
|
||||||
moonFolder.add(this.mParams, 'intensity', 0.0, 1000.0).name('Intensity').onChange(v => this.updateSunIntensity());
|
|
||||||
moonFolder.add(this.mParams, 'radius', 0.1, 5.0).name('Radius').onChange(v => sky.setMoonRadius(v));
|
|
||||||
moonFolder.close();
|
|
||||||
|
|
||||||
const mwFolder = gui.addFolder('Milky Way');
|
|
||||||
|
|
||||||
const updateMW = () => {
|
|
||||||
sky.setMilkyWayEnabled(this.mwParams.enabled);
|
|
||||||
sky.setMilkyWayControl(this.mwParams.intensity, this.mwParams.saturation, this.mwParams.blackPoint);
|
|
||||||
|
|
||||||
// Calculate Rotation
|
|
||||||
// V_gal = Rot_Eq_to_Gal * Rot_World_to_Eq * V_world
|
|
||||||
|
|
||||||
// World: Y=Up, X=East, Z=South (Filament Camera Convention is different!)
|
|
||||||
// In Filament Camera: -Z is Forward.
|
|
||||||
// Skybox V direction is World Space direction.
|
|
||||||
// Let's assume standard Horizontal Coordinates:
|
|
||||||
// Y = Zenith.
|
|
||||||
// Z = North? Or South?
|
|
||||||
// Usually Z is South in RH Y-up.
|
|
||||||
|
|
||||||
// LST (Local Sidereal Time) converts Hour Angle to RA.
|
|
||||||
// LST in Radians.
|
|
||||||
const LST = this.mwParams.siderealTime * (Math.PI / 12.0); // Hours to Rad
|
|
||||||
const Lat = this.mwParams.latitude * (Math.PI / 180.0);
|
|
||||||
|
|
||||||
// Rotation World (Horizontal) -> Equatorial
|
|
||||||
// 1. Rotate around X by -(90 - Lat) to align Equatorial Plane.
|
|
||||||
// 2. Rotate around Y (Polar Axis) by -LST.
|
|
||||||
|
|
||||||
// Mat3 Identity
|
|
||||||
const rot = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
||||||
|
|
||||||
// Rotate Z by LST (Earth Rotation).
|
|
||||||
// Actually, transformation from Horizontal (Az, Alt) to Equatorial (HA, Dec):
|
|
||||||
// sin(Dec) = sin(Alt)sin(Lat) - cos(Alt)cos(Lat)cos(Az)
|
|
||||||
// ...
|
|
||||||
// Let's construct matrix directly.
|
|
||||||
// WorldToEq:
|
|
||||||
// Rotate X by (Lat - 90 deg) -> brings Pole to Zenith.
|
|
||||||
// Rotate Y by -LST (or Z?)
|
|
||||||
|
|
||||||
// Filament Space:
|
|
||||||
// +Y = Up
|
|
||||||
// Let's match typical skybox conventions.
|
|
||||||
|
|
||||||
// Rot_World_to_Eq = Rot_Z_LST * Rot_X_Lat
|
|
||||||
// But we need to use Filament matrix ops which are column major.
|
|
||||||
|
|
||||||
// Let's use simple rotations:
|
|
||||||
// 1. Tilt Pole: Rotate X by (Lat - 90).
|
|
||||||
// 2. Spin Earth: Rotate Y by LST.
|
|
||||||
|
|
||||||
// Let's iterate until it looks right visually or trust the math.
|
|
||||||
// Rot_World_To_Equatorial:
|
|
||||||
// R_z(-LST) * R_x(Lat - 90)?
|
|
||||||
|
|
||||||
// Let's build it from scratch in JS using helper.
|
|
||||||
// Start Identity.
|
|
||||||
// Rotate X (Latitude Tilt).
|
|
||||||
// Rotate Y (Sidereal Spin).
|
|
||||||
// Note: rotate functions modify in place.
|
|
||||||
|
|
||||||
const mWorldToEq = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
||||||
|
|
||||||
// 1. Tilt for Latitude (Align Celestial Pole)
|
|
||||||
// At Lat 90 (North Pole), Zenith is Pole. No tilt needed if Y is Pole?
|
|
||||||
// No, Y is Zenith. Pole is Y.
|
|
||||||
// At Lat 0 (Equator), Pole is at Horizon (Z?).
|
|
||||||
// So we rotate X by (Lat - 90).
|
|
||||||
rotateX(mWorldToEq, Lat - Math.PI / 2);
|
|
||||||
|
|
||||||
// 2. Spin for Time (LST)
|
|
||||||
// Rotate around new Pole (Y) by LST.
|
|
||||||
rotateY(mWorldToEq, LST);
|
|
||||||
|
|
||||||
// Combine with Gal Transform
|
|
||||||
// Rot = MAT_EQ_TO_GAL * mWorldToEq
|
|
||||||
const finalRot = multiplyMat3(MAT_EQ_TO_GAL, mWorldToEq);
|
|
||||||
|
|
||||||
sky.setMilkyWayRotation(finalRot);
|
|
||||||
};
|
|
||||||
|
|
||||||
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 5.0).onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
|
||||||
mwFolder.add(this.mwParams, 'latitude', -90.0, 90.0).name('Latitude').onChange(updateMW);
|
|
||||||
mwFolder.close();
|
|
||||||
|
|
||||||
// Initial MW Update
|
|
||||||
updateMW();
|
|
||||||
|
|
||||||
this.updateMW = updateMW; // Export for sync
|
|
||||||
|
|
||||||
// We need local proxy for sunRadius due to conversion
|
// We need local proxy for sunRadius due to conversion
|
||||||
this.diskParams = {
|
const diskParams = {
|
||||||
radius: 1.2
|
radius: 1.2,
|
||||||
|
enabled: true // Enable sun disk
|
||||||
};
|
};
|
||||||
sky.setSunDiskEnabled(true);
|
sky.setSunDiskEnabled(true);
|
||||||
sky.setSunRadius(1.2);
|
sky.setSunRadius(1.2);
|
||||||
sunFolder.add(this.diskParams, 'radius', 0.0, 5.0).onChange(v => sky.setSunRadius(v));
|
sunDisk.add(diskParams, 'enabled').onChange(v => sky.setSunDiskEnabled(v));
|
||||||
sunFolder.add(sky.sunHalo, 1, 0.0, 2.0).name('Limb Darkening').onChange(v => sky.setSunLimbDarkening(v));
|
sunDisk.add(diskParams, 'radius', 0.0, 5.0).onChange(v => sky.setSunRadius(v));
|
||||||
sunFolder.add(sky.sunHalo, 2, 0.0, 100.0).name('Intensity Boost').onChange(v => sky.setSunDiskIntensity(v));
|
sunDisk.add(sky.sunHalo, 1, 0.0, 2.0).name('Limb Darkening').onChange(v => sky.setSunLimbDarkening(v));
|
||||||
|
sunDisk.add(sky.sunHalo, 2, 0.0, 100.0).name('Intensity Boost').onChange(v => sky.setSunDiskIntensity(v));
|
||||||
|
|
||||||
const atmFolder = gui.addFolder('Atmosphere');
|
const atmFolder = gui.addFolder('Atmosphere');
|
||||||
atmFolder.add(sky, 'turbidity', 1.0, 10.0).onChange(v => sky.setTurbidity(v));
|
atmFolder.add(sky, 'turbidity', 1.0, 10.0).onChange(v => sky.setTurbidity(v));
|
||||||
@@ -420,69 +161,12 @@ class App {
|
|||||||
atmFolder.add(sky, 'ozone', 0.0, 1.0).onChange(v => sky.setOzone(v));
|
atmFolder.add(sky, 'ozone', 0.0, 1.0).onChange(v => sky.setOzone(v));
|
||||||
atmFolder.add(sky, 'mieG', 0.0, 0.999).onChange(v => sky.setMieG(v));
|
atmFolder.add(sky, 'mieG', 0.0, 0.999).onChange(v => sky.setMieG(v));
|
||||||
|
|
||||||
const cloudFolder = gui.addFolder('Clouds');
|
|
||||||
this.cParams = {
|
|
||||||
volumetrics: sky.cloudControl2[1] > 0.5,
|
|
||||||
coverage: 0.4,
|
|
||||||
density: 0.02,
|
|
||||||
height: sky.cloudControl[2],
|
|
||||||
speed: 50.0,
|
|
||||||
evolution: 0.02
|
|
||||||
};
|
|
||||||
// Apply Cloud Defaults
|
|
||||||
sky.setCloudControl(0.4, 0.02, this.cParams.height, 50.0);
|
|
||||||
sky.setCloudShapeEvolution(0.02);
|
|
||||||
|
|
||||||
cloudFolder.add(this.cParams, 'volumetrics').onChange(v => sky.setCloudVolumetricLighting(v));
|
|
||||||
cloudFolder.add(this.cParams, 'coverage', 0.0, 1.0).onChange(v => sky.setCloudControl(v, this.cParams.density, this.cParams.height, this.cParams.speed));
|
|
||||||
cloudFolder.add(this.cParams, 'density', 0.0, 1.0).onChange(v => sky.setCloudControl(this.cParams.coverage, v, this.cParams.height, this.cParams.speed));
|
|
||||||
cloudFolder.add(this.cParams, 'height', 2000.0, 20000.0).onChange(v => sky.setCloudControl(this.cParams.coverage, this.cParams.density, v, this.cParams.speed));
|
|
||||||
// Reverse speed calc: w = speed * (0.05 / 72.0)
|
|
||||||
cloudFolder.add(this.cParams, 'speed', 0.0, 200.0).onChange(v => sky.setCloudControl(this.cParams.coverage, this.cParams.density, this.cParams.height, v));
|
|
||||||
cloudFolder.add(this.cParams, 'evolution', 0.0, 2.0).onChange(v => sky.setCloudShapeEvolution(v));
|
|
||||||
|
|
||||||
const waterFolder = gui.addFolder('Water');
|
|
||||||
this.wParams = {
|
|
||||||
derivativeTrick: true,
|
|
||||||
strength: 50.0,
|
|
||||||
speed: 1.0,
|
|
||||||
octaves: 4.0
|
|
||||||
};
|
|
||||||
// Initialize defaults
|
|
||||||
sky.setWaterControl(50.0, 1.0, 1.0, 4.0); // 1.0 = Derivative Trick On, 4 octaves
|
|
||||||
|
|
||||||
const updateWater = () => {
|
|
||||||
sky.setWaterControl(this.wParams.strength, this.wParams.speed, this.wParams.derivativeTrick ? 1.0 : 0.0, this.wParams.octaves);
|
|
||||||
};
|
|
||||||
|
|
||||||
waterFolder.add(this.wParams, 'derivativeTrick').name('Derivative Trick').onChange(updateWater);
|
|
||||||
waterFolder.add(this.wParams, 'strength', 10.0, 100.0).onChange(updateWater);
|
|
||||||
waterFolder.add(this.wParams, 'speed', 0.0, 5.0).onChange(updateWater);
|
|
||||||
waterFolder.add(this.wParams, 'octaves', 1, 8, 1).name('Octaves').onChange(updateWater);
|
|
||||||
waterFolder.close();
|
|
||||||
|
|
||||||
const starFolder = gui.addFolder('Stars');
|
|
||||||
this.sParams = {
|
|
||||||
enabled: true,
|
|
||||||
density: 0.001
|
|
||||||
};
|
|
||||||
// Initialize defaults (Density 0.001, Enabled True)
|
|
||||||
sky.setStarControl(0.001, true);
|
|
||||||
|
|
||||||
const updateStars = () => {
|
|
||||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
|
||||||
};
|
|
||||||
|
|
||||||
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
|
||||||
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
|
||||||
starFolder.close();
|
|
||||||
|
|
||||||
const artFolder = gui.addFolder('Artistic');
|
const artFolder = gui.addFolder('Artistic');
|
||||||
// Set Horizon Glow default to 0.0
|
// Set Horizon Glow default to 1.0
|
||||||
sky.setHorizonGlow(0.0);
|
sky.setHorizonGlow(1.0);
|
||||||
sky.msFactors[2] = 0.0;
|
sky.msFactors[2] = 1.0;
|
||||||
// Set Contrast default to 1.0
|
// Set Contrast default to 0.85
|
||||||
sky.setContrast(1.0);
|
sky.setContrast(0.85);
|
||||||
|
|
||||||
artFolder.add(sky.msFactors, 0, 0.0, 2.0).name('MS Rayleigh').onChange(v => sky.setMultiScattering(v, sky.msFactors[1]));
|
artFolder.add(sky.msFactors, 0, 0.0, 2.0).name('MS Rayleigh').onChange(v => sky.setMultiScattering(v, sky.msFactors[1]));
|
||||||
artFolder.add(sky.msFactors, 1, 0.0, 2.0).name('MS Mie').onChange(v => sky.setMultiScattering(sky.msFactors[0], v));
|
artFolder.add(sky.msFactors, 1, 0.0, 2.0).name('MS Mie').onChange(v => sky.setMultiScattering(sky.msFactors[0], v));
|
||||||
@@ -499,6 +183,63 @@ class App {
|
|||||||
shmFolder.add(sky.shimmerControl, 1, 1.0, 100.0).name('Frequency').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], v, sky.shimmerControl[2]));
|
shmFolder.add(sky.shimmerControl, 1, 1.0, 100.0).name('Frequency').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], v, sky.shimmerControl[2]));
|
||||||
shmFolder.add(sky.shimmerControl, 2, 0.01, 0.5).name('Mask Height').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], sky.shimmerControl[1], v));
|
shmFolder.add(sky.shimmerControl, 2, 0.01, 0.5).name('Mask Height').onChange(v => sky.setShimmerControl(sky.shimmerControl[0], sky.shimmerControl[1], v));
|
||||||
|
|
||||||
|
const cloudFolder = gui.addFolder('Clouds');
|
||||||
|
const cParams = {
|
||||||
|
volumetrics: sky.cloudControl2[1] > 0.5,
|
||||||
|
coverage: 0.4,
|
||||||
|
density: 0.02,
|
||||||
|
height: sky.cloudControl[2],
|
||||||
|
speed: 50.0,
|
||||||
|
evolution: 0.02
|
||||||
|
};
|
||||||
|
// Apply Cloud Defaults
|
||||||
|
sky.setCloudControl(0.4, 0.02, cParams.height, 50.0);
|
||||||
|
sky.setCloudShapeEvolution(0.02);
|
||||||
|
|
||||||
|
cloudFolder.add(cParams, 'volumetrics').onChange(v => sky.setCloudVolumetricLighting(v));
|
||||||
|
cloudFolder.add(cParams, 'coverage', 0.0, 1.0).onChange(v => sky.setCloudControl(v, cParams.density, cParams.height, cParams.speed));
|
||||||
|
cloudFolder.add(cParams, 'density', 0.0, 1.0).onChange(v => sky.setCloudControl(cParams.coverage, v, cParams.height, cParams.speed));
|
||||||
|
cloudFolder.add(cParams, 'height', 2000.0, 20000.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, v, cParams.speed));
|
||||||
|
// Reverse speed calc: w = speed * (0.05 / 72.0)
|
||||||
|
cloudFolder.add(cParams, 'speed', 0.0, 200.0).onChange(v => sky.setCloudControl(cParams.coverage, cParams.density, cParams.height, v));
|
||||||
|
cloudFolder.add(cParams, 'evolution', 0.0, 2.0).onChange(v => sky.setCloudShapeEvolution(v));
|
||||||
|
|
||||||
|
const waterFolder = gui.addFolder('Water');
|
||||||
|
const wParams = {
|
||||||
|
derivativeTrick: true,
|
||||||
|
strength: 50.0,
|
||||||
|
speed: 1.0,
|
||||||
|
octaves: 4.0
|
||||||
|
};
|
||||||
|
// Initialize defaults
|
||||||
|
sky.setWaterControl(50.0, 1.0, 1.0, 4.0); // 1.0 = Derivative Trick On, 4 octaves
|
||||||
|
|
||||||
|
const updateWater = () => {
|
||||||
|
sky.setWaterControl(wParams.strength, wParams.speed, wParams.derivativeTrick ? 1.0 : 0.0, wParams.octaves);
|
||||||
|
};
|
||||||
|
|
||||||
|
waterFolder.add(wParams, 'derivativeTrick').name('Derivative Trick').onChange(updateWater);
|
||||||
|
waterFolder.add(wParams, 'strength', 10.0, 100.0).onChange(updateWater);
|
||||||
|
waterFolder.add(wParams, 'speed', 0.0, 5.0).onChange(updateWater);
|
||||||
|
waterFolder.add(wParams, 'octaves', 1, 8, 1).name('Octaves').onChange(updateWater);
|
||||||
|
waterFolder.close();
|
||||||
|
|
||||||
|
const starFolder = gui.addFolder('Stars');
|
||||||
|
const sParams = {
|
||||||
|
enabled: true,
|
||||||
|
density: 1.0
|
||||||
|
};
|
||||||
|
// Initialize defaults (Density 1.0, Enabled True)
|
||||||
|
sky.setStarControl(1.0, true);
|
||||||
|
|
||||||
|
const updateStars = () => {
|
||||||
|
sky.setStarControl(sParams.density, sParams.enabled);
|
||||||
|
};
|
||||||
|
|
||||||
|
starFolder.add(sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||||
|
starFolder.add(sParams, 'density', 0.0, 1.0).name('Density').onChange(updateStars);
|
||||||
|
starFolder.close();
|
||||||
|
|
||||||
const camFolder = gui.addFolder('Camera');
|
const camFolder = gui.addFolder('Camera');
|
||||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||||
@@ -506,362 +247,35 @@ class App {
|
|||||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||||
|
|
||||||
const bloomFolder = camFolder.addFolder('Bloom');
|
const bloomFolder = camFolder.addFolder('Bloom');
|
||||||
this.bParams = {
|
const bParams = {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
lensFlare: false
|
lensFlare: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateBloom = () => {
|
const updateBloom = () => {
|
||||||
this.view.setBloomOptions({
|
this.view.setBloomOptions({
|
||||||
enabled: this.bParams.enabled,
|
enabled: bParams.enabled,
|
||||||
lensFlare: this.bParams.lensFlare
|
lensFlare: bParams.lensFlare
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
bloomFolder.add(this.bParams, 'enabled').onChange(updateBloom);
|
bloomFolder.add(bParams, 'enabled').onChange(updateBloom);
|
||||||
bloomFolder.add(this.bParams, 'lensFlare').onChange(updateBloom);
|
bloomFolder.add(bParams, 'lensFlare').onChange(updateBloom);
|
||||||
bloomFolder.close();
|
bloomFolder.close();
|
||||||
|
|
||||||
// Collapse folders by default
|
// Collapse folders by default
|
||||||
|
sunDisk.close();
|
||||||
atmFolder.close();
|
atmFolder.close();
|
||||||
artFolder.close();
|
artFolder.close();
|
||||||
// shmFolder is inside artFolder, so it's hidden, but we can close it too if we want
|
// shmFolder is inside artFolder, so it's hidden, but we can close it too if we want
|
||||||
shmFolder.close();
|
shmFolder.close();
|
||||||
cloudFolder.close();
|
cloudFolder.close();
|
||||||
// camFolder left open by default for convenience.
|
// camFolder left open? User didn't specify, but "Artistic, shimmer and clouds" + "Disk, Atmosphere" were requested.
|
||||||
|
// So Camera might stay open or close. Let's keep Camera open for now as it wasn't listed.
|
||||||
|
|
||||||
// Initial sync
|
// Initial sync
|
||||||
updateSun();
|
updateSun();
|
||||||
this.updateCameraExposure(); // This will trigger updateSunIntensity too
|
this.updateCameraExposure(); // This will trigger updateSunIntensity too
|
||||||
updateBloom();
|
|
||||||
|
|
||||||
// Check URL for config
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
if (urlParams.has('config')) {
|
|
||||||
try {
|
|
||||||
const state = JSON.parse(atob(urlParams.get('config')));
|
|
||||||
this.applyURLState(state);
|
|
||||||
// Update GUI
|
|
||||||
gui.controllers.forEach(c => c.updateDisplay());
|
|
||||||
// Recursive update for folders
|
|
||||||
gui.folders.forEach(f => {
|
|
||||||
f.controllers.forEach(c => c.updateDisplay());
|
|
||||||
// And sub-folders if any (Shimmer/Bloom)
|
|
||||||
f.folders.forEach(sf => sf.controllers.forEach(sc => sc.updateDisplay()));
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to load config:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncFolder = gui.addFolder('Real-Time Sync');
|
|
||||||
this.syncParams = {
|
|
||||||
enabled: false,
|
|
||||||
lat: 0.0,
|
|
||||||
lng: 0.0,
|
|
||||||
status: 'Disabled'
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateSync = () => {
|
|
||||||
if (this.syncParams.enabled) {
|
|
||||||
if (navigator.geolocation) {
|
|
||||||
this.syncParams.status = "Locating...";
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
|
||||||
(pos) => {
|
|
||||||
this.syncParams.lat = pos.coords.latitude;
|
|
||||||
this.syncParams.lng = pos.coords.longitude;
|
|
||||||
this.syncParams.status = "Active";
|
|
||||||
syncFolder.controllers.forEach(c => c.updateDisplay());
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error(err);
|
|
||||||
this.syncParams.status = "Error (See Console)";
|
|
||||||
this.syncParams.enabled = false;
|
|
||||||
syncFolder.controllers.forEach(c => c.updateDisplay());
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.syncParams.status = "Not Supported";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.syncParams.status = "Disabled";
|
|
||||||
}
|
|
||||||
syncFolder.controllers.forEach(c => c.updateDisplay());
|
|
||||||
};
|
|
||||||
|
|
||||||
syncFolder.add(this.syncParams, 'enabled').name('Enable Sync').onChange(updateSync);
|
|
||||||
syncFolder.add(this.syncParams, 'status').name('Status').disable().listen();
|
|
||||||
|
|
||||||
this.syncFolder = syncFolder;
|
|
||||||
|
|
||||||
const shareParams = {
|
|
||||||
copyUrl: () => {
|
|
||||||
const state = this.getURLState();
|
|
||||||
const str = btoa(JSON.stringify(state));
|
|
||||||
const url = `${window.location.origin}${window.location.pathname}?config=${str}`;
|
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
|
||||||
alert("Configuration URL copied to clipboard!");
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('Could not copy text: ', err);
|
|
||||||
prompt("Copy this URL:", url);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
gui.add(shareParams, 'copyUrl').name('Share Configuration');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRealTimeSync() {
|
|
||||||
if (!this.syncParams || !this.syncParams.enabled || !window.SunCalc) return;
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const lat = this.syncParams.lat;
|
|
||||||
const lng = this.syncParams.lng;
|
|
||||||
|
|
||||||
// Sun
|
|
||||||
const sunPos = window.SunCalc.getPosition(now, lat, lng);
|
|
||||||
// Azimuth: South=0, West=PI/2.
|
|
||||||
// Skybox Phi: +X=0, +Z=PI/2.
|
|
||||||
// If +Z is South:
|
|
||||||
// SunAz 0 (South) -> Skybox PI/2 (+Z).
|
|
||||||
// SunAz PI/2 (West) -> Skybox PI (-X).
|
|
||||||
// So Phi = Az + PI/2.
|
|
||||||
const sunPhi = sunPos.azimuth + Math.PI / 2;
|
|
||||||
// Altitude: 0=Horizon, PI/2=Zenith.
|
|
||||||
// Skybox Theta: 0=Zenith, PI/2=Horizon.
|
|
||||||
const sunTheta = Math.PI / 2 - sunPos.altitude;
|
|
||||||
|
|
||||||
this.params.sunPhi = sunPhi;
|
|
||||||
this.params.sunTheta = sunTheta;
|
|
||||||
|
|
||||||
// Moon
|
|
||||||
const moonPos = window.SunCalc.getMoonPosition(now, lat, lng);
|
|
||||||
const moonPhi = moonPos.azimuth + Math.PI / 2;
|
|
||||||
const moonTheta = Math.PI / 2 - moonPos.altitude;
|
|
||||||
|
|
||||||
this.mParams.azimuth = (moonPhi * 180.0 / Math.PI) % 360.0;
|
|
||||||
this.mParams.height = Math.cos(moonTheta);
|
|
||||||
|
|
||||||
// Milky Way Sync
|
|
||||||
const gmst = getGMST(now);
|
|
||||||
const lst = (gmst + lng / 15.0 + 24.0) % 24.0;
|
|
||||||
this.mwParams.siderealTime = lst;
|
|
||||||
this.mwParams.latitude = lat;
|
|
||||||
if (this.updateMW) this.updateMW();
|
|
||||||
|
|
||||||
// Update Skybox
|
|
||||||
const sky = this.skybox;
|
|
||||||
|
|
||||||
// Update Sun Vector
|
|
||||||
const sx = Math.sin(sunTheta) * Math.cos(sunPhi);
|
|
||||||
const sy = Math.cos(sunTheta);
|
|
||||||
const sz = Math.sin(sunTheta) * Math.sin(sunPhi);
|
|
||||||
sky.setSunPosition([sx, sy, sz]);
|
|
||||||
|
|
||||||
// Update Moon Vector
|
|
||||||
const mx = Math.sin(moonTheta) * Math.cos(moonPhi);
|
|
||||||
const my = Math.cos(moonTheta);
|
|
||||||
const mz = Math.sin(moonTheta) * Math.sin(moonPhi);
|
|
||||||
sky.setMoonPosition([mx, my, mz]);
|
|
||||||
|
|
||||||
// Update UI Proxies
|
|
||||||
if (this.sunUI) {
|
|
||||||
this.sunUI.azimuth = (sunPhi * 180.0 / Math.PI) % 360.0;
|
|
||||||
if (this.sunUI.azimuth < 0) this.sunUI.azimuth += 360.0;
|
|
||||||
this.sunUI.height = Math.cos(sunTheta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getURLState() {
|
|
||||||
|
|
||||||
// Update Camera LookAt
|
|
||||||
// Serialize current state (Minified)
|
|
||||||
// Mapping:
|
|
||||||
// p: params (Camera) -> a:aperture, ss:shutterSpeed, i:iso, st:sunTheta, sp:sunPhi, fl:focalLength, si:sunIntensity
|
|
||||||
// c: cParams (Clouds) -> v:volumetrics, co:coverage, d:density, h:height, s:speed, e:evolution
|
|
||||||
// w: wParams (Water) -> dt:derivativeTrick, st:strength, s:speed, o:octaves
|
|
||||||
// s: sParams (Stars) -> e:enabled, d:density
|
|
||||||
// b: bParams (Bloom) -> e:enabled, lf:lensFlare
|
|
||||||
// k: sky (Skybox) -> t:turbidity, r:rayleigh, mc:mieCoefficient, mg:mieG, o:ozone, ms:msFactors, co:contrast, nc:nightColor, sh:shimmerControl, hl:sunHalo
|
|
||||||
|
|
||||||
const p = this.params;
|
|
||||||
const c = this.cParams;
|
|
||||||
const w = this.wParams;
|
|
||||||
const s = this.sParams;
|
|
||||||
const b = this.bParams;
|
|
||||||
const m = this.mParams;
|
|
||||||
const sk = this.skybox;
|
|
||||||
|
|
||||||
return {
|
|
||||||
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
|
||||||
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
|
||||||
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
|
||||||
s: { e: s.enabled, d: s.density },
|
|
||||||
b: { e: b.enabled, lf: b.lensFlare },
|
|
||||||
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
|
||||||
cm: { t: this.camState.theta, p: this.camState.phi },
|
|
||||||
k: {
|
|
||||||
t: sk.turbidity,
|
|
||||||
r: sk.rayleigh,
|
|
||||||
mc: sk.mieCoefficient,
|
|
||||||
mg: sk.mieG,
|
|
||||||
o: sk.ozone,
|
|
||||||
ms: [...sk.msFactors],
|
|
||||||
co: sk.contrast,
|
|
||||||
nc: [...sk.nightColor],
|
|
||||||
sh: [...sk.shimmerControl],
|
|
||||||
hl: [...sk.sunHalo]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
applyURLState(state) {
|
|
||||||
const p = state.p;
|
|
||||||
const c = state.c;
|
|
||||||
const w = state.w;
|
|
||||||
const s = state.s;
|
|
||||||
const b = state.b;
|
|
||||||
const m = state.m;
|
|
||||||
const k = state.k;
|
|
||||||
const cm = state.cm;
|
|
||||||
|
|
||||||
if (p) {
|
|
||||||
if (p.a !== undefined) this.params.aperture = p.a;
|
|
||||||
if (p.ss !== undefined) this.params.shutterSpeed = p.ss;
|
|
||||||
if (p.i !== undefined) this.params.iso = p.i;
|
|
||||||
if (p.st !== undefined) this.params.sunTheta = p.st;
|
|
||||||
if (p.sp !== undefined) this.params.sunPhi = p.sp;
|
|
||||||
if (p.fl !== undefined) this.params.focalLength = p.fl;
|
|
||||||
if (p.si !== undefined) this.params.sunIntensity = p.si;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c) {
|
|
||||||
if (c.v !== undefined) this.cParams.volumetrics = c.v;
|
|
||||||
if (c.co !== undefined) this.cParams.coverage = c.co;
|
|
||||||
if (c.d !== undefined) this.cParams.density = c.d;
|
|
||||||
if (c.h !== undefined) this.cParams.height = c.h;
|
|
||||||
if (c.s !== undefined) this.cParams.speed = c.s;
|
|
||||||
if (c.e !== undefined) this.cParams.evolution = c.e;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (w) {
|
|
||||||
if (w.dt !== undefined) this.wParams.derivativeTrick = w.dt;
|
|
||||||
if (w.st !== undefined) this.wParams.strength = w.st;
|
|
||||||
if (w.s !== undefined) this.wParams.speed = w.s;
|
|
||||||
if (w.o !== undefined) this.wParams.octaves = w.o;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s) {
|
|
||||||
if (s.e !== undefined) this.sParams.enabled = s.e;
|
|
||||||
if (s.d !== undefined) this.sParams.density = s.d;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b) {
|
|
||||||
if (b.e !== undefined) this.bParams.enabled = b.e;
|
|
||||||
if (b.lf !== undefined) this.bParams.lensFlare = b.lf;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sky = this.skybox;
|
|
||||||
if (k) {
|
|
||||||
if (k.t !== undefined) sky.setTurbidity(k.t);
|
|
||||||
if (k.r !== undefined) sky.setRayleigh(k.r);
|
|
||||||
if (k.mc !== undefined) sky.setMieCoefficient(k.mc);
|
|
||||||
if (k.mg !== undefined) sky.setMieG(k.mg);
|
|
||||||
if (k.o !== undefined) sky.setOzone(k.o);
|
|
||||||
|
|
||||||
if (k.ms) { sky.setMultiScattering(k.ms[0], k.ms[1]); sky.setHorizonGlow(k.ms[2]); }
|
|
||||||
if (k.co !== undefined) sky.setContrast(k.co);
|
|
||||||
if (k.nc) sky.setNightColor(k.nc);
|
|
||||||
if (k.sh) sky.setShimmerControl(k.sh[0], k.sh[1], k.sh[2]);
|
|
||||||
|
|
||||||
// Sun Halo
|
|
||||||
const savedHalo = k.hl;
|
|
||||||
if (savedHalo && savedHalo.length === 4) {
|
|
||||||
sky.sunHalo[0] = savedHalo[0];
|
|
||||||
sky.sunHalo[1] = savedHalo[1];
|
|
||||||
sky.sunHalo[2] = savedHalo[2];
|
|
||||||
sky.sunHalo[3] = savedHalo[3];
|
|
||||||
|
|
||||||
// Update derived UI params for Sun Disk
|
|
||||||
const rad = Math.acos(Math.max(-1.0, Math.min(1.0, savedHalo[0])));
|
|
||||||
this.diskParams.radius = rad * (180.0 / Math.PI);
|
|
||||||
this.diskParams.enabled = savedHalo[3] > 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
sky.updateCoefficients();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cm) {
|
|
||||||
if (cm.t !== undefined) this.camState.theta = cm.t;
|
|
||||||
if (cm.p !== undefined) this.camState.phi = cm.p;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update derived Sun UI
|
|
||||||
if (this.sunUI) {
|
|
||||||
this.sunUI.height = Math.cos(this.params.sunTheta);
|
|
||||||
this.sunUI.azimuth = (this.params.sunPhi * 180.0 / Math.PI) % 360.0;
|
|
||||||
if (this.sunUI.azimuth < 0) this.sunUI.azimuth += 360.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply Local Params via Setters
|
|
||||||
sky.setCloudControl(this.cParams.coverage, this.cParams.density, this.cParams.height, this.cParams.speed);
|
|
||||||
sky.setCloudVolumetricLighting(this.cParams.volumetrics);
|
|
||||||
sky.setCloudShapeEvolution(this.cParams.evolution);
|
|
||||||
|
|
||||||
sky.setWaterControl(this.wParams.strength, this.wParams.speed, this.wParams.derivativeTrick ? 1.0 : 0.0, this.wParams.octaves);
|
|
||||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
if (m.e !== undefined) this.mParams.enabled = m.e;
|
|
||||||
if (m.az !== undefined) this.mParams.azimuth = m.az;
|
|
||||||
// Compat: if 'el' exists (old link) convert to 'h'
|
|
||||||
if (m.h !== undefined) {
|
|
||||||
this.mParams.height = m.h;
|
|
||||||
} else if (m.el !== undefined) {
|
|
||||||
// Convert elevation degrees to height cos
|
|
||||||
this.mParams.height = Math.cos(m.el * Math.PI / 180.0);
|
|
||||||
}
|
|
||||||
if (m.r !== undefined) this.mParams.radius = m.r;
|
|
||||||
if (m.i !== undefined) this.mParams.intensity = m.i;
|
|
||||||
|
|
||||||
// Sync Moon
|
|
||||||
const az = this.mParams.azimuth * (Math.PI / 180.0);
|
|
||||||
const theta = Math.acos(this.mParams.height);
|
|
||||||
const phi = az;
|
|
||||||
const x = Math.sin(theta) * Math.cos(phi);
|
|
||||||
const y = Math.cos(theta);
|
|
||||||
const z = Math.sin(theta) * Math.sin(phi);
|
|
||||||
|
|
||||||
sky.setMoonPosition([x, y, z]);
|
|
||||||
sky.setMoonEnabled(this.mParams.enabled);
|
|
||||||
sky.setMoonRadius(this.mParams.radius);
|
|
||||||
sky.setMoonIntensity(this.mParams.intensity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cm) {
|
|
||||||
if (cm.t !== undefined) this.camState.theta = cm.t;
|
|
||||||
if (cm.p !== undefined) this.camState.phi = cm.p;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.view.setBloomOptions({
|
|
||||||
enabled: this.bParams.enabled,
|
|
||||||
lensFlare: this.bParams.lensFlare
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update Sun Position from Params
|
|
||||||
const theta = this.params.sunTheta;
|
|
||||||
const phi = this.params.sunPhi;
|
|
||||||
const x = Math.sin(theta) * Math.cos(phi);
|
|
||||||
const y = Math.cos(theta);
|
|
||||||
const z = Math.sin(theta) * Math.sin(phi);
|
|
||||||
sky.setSunPosition([x, y, z]);
|
|
||||||
|
|
||||||
// Update Camera Projection (Focal Length) and Exposure (Aperture/Shutter/ISO)
|
|
||||||
this.updateCameraProjection();
|
|
||||||
this.updateCameraExposure();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initControls() {
|
initControls() {
|
||||||
@@ -891,41 +305,10 @@ class App {
|
|||||||
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Touch support
|
|
||||||
this.canvas.addEventListener('touchstart', e => {
|
|
||||||
if (e.touches.length > 0) {
|
|
||||||
e.preventDefault(); // Prevent scroll/long-press
|
|
||||||
this.camState.dragging = true;
|
|
||||||
this.camState.lastX = e.touches[0].clientX;
|
|
||||||
this.camState.lastY = e.touches[0].clientY;
|
|
||||||
}
|
|
||||||
}, { passive: false });
|
|
||||||
|
|
||||||
window.addEventListener('touchend', () => {
|
|
||||||
this.camState.dragging = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('touchmove', e => {
|
|
||||||
if (!this.camState.dragging || e.touches.length === 0) return;
|
|
||||||
e.preventDefault(); // Prevent scrolling
|
|
||||||
const x = e.touches[0].clientX;
|
|
||||||
const y = e.touches[0].clientY;
|
|
||||||
const dx = x - this.camState.lastX;
|
|
||||||
const dy = y - this.camState.lastY;
|
|
||||||
this.camState.lastX = x;
|
|
||||||
this.camState.lastY = y;
|
|
||||||
|
|
||||||
const sensitivity = 0.005;
|
|
||||||
this.camState.theta -= dx * sensitivity;
|
|
||||||
this.camState.phi += dy * sensitivity;
|
|
||||||
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
|
||||||
}, { passive: false });
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.updateRealTimeSync();
|
|
||||||
// Update Camera LookAt
|
// Update Camera LookAt
|
||||||
const r = 1.0;
|
const r = 1.0;
|
||||||
const theta = this.camState.theta;
|
const theta = this.camState.theta;
|
||||||
@@ -958,6 +341,5 @@ class App {
|
|||||||
const aspect = width / height;
|
const aspect = width / height;
|
||||||
// near=0.1, far=5000.0
|
// near=0.1, far=5000.0
|
||||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||||
if (this.skybox) this.skybox.setResolution(height);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
|
|
||||||
import os
|
|
||||||
import urllib.request
|
|
||||||
import ssl
|
|
||||||
from PIL import Image
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
# URL of the Milky Way texture (Gaia EDR3) from ESA
|
|
||||||
# Low Res PNG (2.71 MB) is sufficient for our 1024x512 target
|
|
||||||
URL = "https://www.esa.int/var/esa/storage/images/esa_multimedia/images/2020/12/the_colour_of_the_sky_from_gaia_s_early_data_release_3/22358049-1-eng-GB/The_colour_of_the_sky_from_Gaia_s_Early_Data_Release_3.png"
|
|
||||||
OUTPUT_DIR = "assets"
|
|
||||||
OUTPUT_FILENAME = "milkyway.png"
|
|
||||||
TARGET_WIDTH = 1024
|
|
||||||
TARGET_HEIGHT = 512
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if not os.path.exists(OUTPUT_DIR):
|
|
||||||
os.makedirs(OUTPUT_DIR)
|
|
||||||
|
|
||||||
output_path = os.path.join(OUTPUT_DIR, OUTPUT_FILENAME)
|
|
||||||
temp_path = os.path.join(OUTPUT_DIR, "temp_milkyway.png")
|
|
||||||
|
|
||||||
print(f"Downloading Milky Way texture from {URL}...")
|
|
||||||
|
|
||||||
# Bypass SSL verification globally
|
|
||||||
if hasattr(ssl, '_create_unverified_context'):
|
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context
|
|
||||||
|
|
||||||
try:
|
|
||||||
req = urllib.request.Request(URL, headers={'User-Agent': 'Mozilla/5.0'})
|
|
||||||
with urllib.request.urlopen(req) as response, open(temp_path, 'wb') as out_file:
|
|
||||||
out_file.write(response.read())
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to download: {e}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not os.path.exists(temp_path):
|
|
||||||
print("Error: Download failed.")
|
|
||||||
return
|
|
||||||
|
|
||||||
print("Processing image...")
|
|
||||||
with Image.open(temp_path) as img:
|
|
||||||
# Convert to RGB (remove alpha if present, though this is likely opaque)
|
|
||||||
img = img.convert("RGB")
|
|
||||||
|
|
||||||
# Resize to user requested dimensions
|
|
||||||
print(f"Resizing to {TARGET_WIDTH}x{TARGET_HEIGHT}...")
|
|
||||||
img = img.resize((TARGET_WIDTH, TARGET_HEIGHT), Image.Resampling.LANCZOS)
|
|
||||||
|
|
||||||
# Save
|
|
||||||
print(f"Saving to {output_path}...")
|
|
||||||
img.save(output_path, "PNG")
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
if os.path.exists(temp_path):
|
|
||||||
os.remove(temp_path)
|
|
||||||
|
|
||||||
print("Done!")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Default size
|
|
||||||
SIZE=${1:-256}
|
|
||||||
OUTPUT_COLOR="../assets/moon_disk.png"
|
|
||||||
OUTPUT_NORMAL="../assets/moon_normal.png"
|
|
||||||
|
|
||||||
# Navigate to script directory
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
# Check dependencies
|
|
||||||
if ! python3 -c "import numpy, PIL" 2>/dev/null; then
|
|
||||||
echo "Installing dependencies (numpy, Pillow)..."
|
|
||||||
pip3 install numpy Pillow
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating Moon Asset (Size: ${SIZE}x${SIZE})..."
|
|
||||||
python3 process_moon.py --size $SIZE --supersample 4 --blur 1.0 --output-color $OUTPUT_COLOR --output-normal $OUTPUT_NORMAL
|
|
||||||
|
|
||||||
echo "Generated $OUTPUT_COLOR and $OUTPUT_NORMAL"
|
|
||||||
|
Before Width: | Height: | Size: 447 KiB |
@@ -1,249 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import urllib.request
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import math
|
|
||||||
import ssl
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
# Ensure numpy is available
|
|
||||||
try:
|
|
||||||
import numpy as np
|
|
||||||
except ImportError:
|
|
||||||
print("Error: numpy is required. Please install it with: pip install numpy")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Default URLs
|
|
||||||
COLOR_URL = "https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/lroc_color_2k.jpg"
|
|
||||||
DISP_URL = "https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/ldem_4.tif"
|
|
||||||
|
|
||||||
SRC_COLOR_FILENAME = "lroc_color_2k.jpg"
|
|
||||||
SRC_DISP_FILENAME = "ldem_4.tif"
|
|
||||||
|
|
||||||
def download_file(url, filename):
|
|
||||||
if os.path.exists(filename):
|
|
||||||
print(f"File {filename} already exists. Skipping download.")
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"Downloading {url} to {filename}...")
|
|
||||||
# Create unverified context to avoid potential SSL cert issues
|
|
||||||
context = ssl._create_unverified_context()
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(url, context=context) as response, open(filename, 'wb') as out_file:
|
|
||||||
data = response.read()
|
|
||||||
out_file.write(data)
|
|
||||||
print(f"Download of {filename} complete.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error downloading file {filename}: {e}")
|
|
||||||
# Don't exit hard if it's just one file, maybe?
|
|
||||||
# But for this script, we likely need it.
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def equirectangular_to_orthographic(src_img, size, mode=None):
|
|
||||||
"""
|
|
||||||
Reprojects an equirectangular image to an orthographic projection (sphere view).
|
|
||||||
src_img: PIL Image (Equirectangular)
|
|
||||||
size: Output size (width, height) - usually square
|
|
||||||
"""
|
|
||||||
print(f"Reprojecting to {size}x{size} Orthographic Disk...")
|
|
||||||
|
|
||||||
width, height = size, size
|
|
||||||
src_w, src_h = src_img.size
|
|
||||||
|
|
||||||
# Create coordinate grid centered at 0,0 (-1 to 1)
|
|
||||||
y, x = np.mgrid[size/2:-size/2:-1, -size/2:size/2] # Note: y goes high to low
|
|
||||||
|
|
||||||
# Normalize to -1..1
|
|
||||||
x = x / (size / 2)
|
|
||||||
y = y / (size / 2)
|
|
||||||
|
|
||||||
# Mask for points outside the circle
|
|
||||||
r2 = x*x + y*y
|
|
||||||
mask = r2 <= 1.0
|
|
||||||
|
|
||||||
# Calculate sphere coordinates (z > 0 for front face)
|
|
||||||
z = np.zeros_like(r2)
|
|
||||||
z[mask] = np.sqrt(1.0 - r2[mask])
|
|
||||||
|
|
||||||
# Vector P = (x, y, z) on unit sphere
|
|
||||||
# Lat = asin(y)
|
|
||||||
# Lon = atan2(x, z)
|
|
||||||
|
|
||||||
# Apply mask to avoid invalid calculations
|
|
||||||
lat = np.arcsin(y * mask)
|
|
||||||
lon = np.arctan2(x * mask, z * mask)
|
|
||||||
|
|
||||||
# Map to UV [0, 1]
|
|
||||||
u = (lon / (2 * math.pi)) + 0.5
|
|
||||||
v = (lat / math.pi) + 0.5
|
|
||||||
|
|
||||||
# Map to Source Pixels
|
|
||||||
u = np.clip(u, 0, 1)
|
|
||||||
v = np.clip(v, 0, 1)
|
|
||||||
|
|
||||||
src_x = (u * (src_w - 1)).astype(np.int32)
|
|
||||||
src_y = ((1.0 - v) * (src_h - 1)).astype(np.int32) # Flip V for image coords
|
|
||||||
|
|
||||||
# Sample pixels
|
|
||||||
src_array = np.array(src_img)
|
|
||||||
|
|
||||||
# Handle dimensions
|
|
||||||
if len(src_array.shape) == 2:
|
|
||||||
# Grayscale / Single channel
|
|
||||||
out_channels = 1
|
|
||||||
src_array = src_array[:, :, np.newaxis] # Expand for consistent indexing
|
|
||||||
else:
|
|
||||||
out_channels = src_array.shape[2]
|
|
||||||
|
|
||||||
out_array = np.zeros((height, width, out_channels), dtype=src_array.dtype)
|
|
||||||
|
|
||||||
# Advanced indexing
|
|
||||||
valid_y, valid_x = np.where(mask)
|
|
||||||
|
|
||||||
# Extract coordinates for valid pixels
|
|
||||||
sx = src_x[valid_y, valid_x]
|
|
||||||
sy = src_y[valid_y, valid_x]
|
|
||||||
|
|
||||||
out_array[valid_y, valid_x] = src_array[sy, sx]
|
|
||||||
|
|
||||||
# Squeeze if single channel
|
|
||||||
if out_channels == 1:
|
|
||||||
out_array = out_array.squeeze(axis=2)
|
|
||||||
|
|
||||||
return Image.fromarray(out_array, mode or src_img.mode)
|
|
||||||
|
|
||||||
def compute_normal_map(height_img, scale=1.0, blur_radius=0.0):
|
|
||||||
print("Computing Normal Map from Height Map...")
|
|
||||||
# Convert to float array
|
|
||||||
h = np.array(height_img).astype(np.float32)
|
|
||||||
|
|
||||||
# Apply Blur if requested
|
|
||||||
if blur_radius > 0:
|
|
||||||
try:
|
|
||||||
from scipy.ndimage import gaussian_filter
|
|
||||||
print(f"Applying Gaussian Blur (Radius: {blur_radius})...")
|
|
||||||
h = gaussian_filter(h, sigma=blur_radius)
|
|
||||||
except ImportError:
|
|
||||||
print("Warning: scipy not found. Skipping Gaussian Blur.")
|
|
||||||
|
|
||||||
# Normalize height to 0..1 for consistent gradient scale regardless of input depth
|
|
||||||
h_min, h_max = h.min(), h.max()
|
|
||||||
print(f"Height Map Range: {h_min} to {h_max}")
|
|
||||||
if h_max > h_min:
|
|
||||||
h_norm = (h - h_min) / (h_max - h_min)
|
|
||||||
else:
|
|
||||||
h_norm = h
|
|
||||||
|
|
||||||
# Gradients
|
|
||||||
dy, dx = np.gradient(h_norm)
|
|
||||||
|
|
||||||
# Pre-emphasis scale
|
|
||||||
bump_scale = scale
|
|
||||||
|
|
||||||
# Normal vector components
|
|
||||||
# Map is Top-Down Y.
|
|
||||||
nx = -dx * bump_scale
|
|
||||||
ny = -dy * bump_scale
|
|
||||||
nz = np.ones_like(nx)
|
|
||||||
|
|
||||||
# Mask out normals where r > 0.96 (avoid edge cliff artifacts)
|
|
||||||
rows, cols = h.shape
|
|
||||||
y, x = np.ogrid[:rows, :cols]
|
|
||||||
center_y, center_x = rows/2.0, cols/2.0
|
|
||||||
# max radius is size/2
|
|
||||||
radius_sq = (min(rows, cols) / 2.0)**2
|
|
||||||
dist_sq = (x - center_x)**2 + (y - center_y)**2
|
|
||||||
mask = dist_sq < (radius_sq * 0.96 * 0.96)
|
|
||||||
|
|
||||||
nx[~mask] = 0
|
|
||||||
ny[~mask] = 0
|
|
||||||
nz[~mask] = 1
|
|
||||||
|
|
||||||
# Normalize
|
|
||||||
len_n = np.sqrt(nx*nx + ny*ny + nz*nz)
|
|
||||||
# Avoid divide by zero
|
|
||||||
len_n[len_n == 0] = 1.0
|
|
||||||
|
|
||||||
nx /= len_n
|
|
||||||
ny /= len_n
|
|
||||||
nz /= len_n
|
|
||||||
|
|
||||||
# Pack to 0..255
|
|
||||||
# [-1, 1] -> [0, 255]
|
|
||||||
out_x = ((nx + 1.0) * 0.5 * 255.0).astype(np.uint8)
|
|
||||||
out_y = ((ny + 1.0) * 0.5 * 255.0).astype(np.uint8)
|
|
||||||
out_z = ((nz + 1.0) * 0.5 * 255.0).astype(np.uint8)
|
|
||||||
|
|
||||||
out_rgb = np.dstack((out_x, out_y, out_z))
|
|
||||||
return Image.fromarray(out_rgb, 'RGB')
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description='Process Moon Texture')
|
|
||||||
parser.add_argument('--size', type=int, default=256, help='Output resolution (square)')
|
|
||||||
parser.add_argument('--supersample', type=int, default=2, help='Internal processing resolution multiplier')
|
|
||||||
parser.add_argument('--blur', type=float, default=1.0, help='Gaussian blur radius for height map')
|
|
||||||
parser.add_argument('--bump-scale', type=float, default=60.0, help='Normal map bump scale')
|
|
||||||
parser.add_argument('--output-color', type=str, default='assets/moon_disk.png', help='Output color filename')
|
|
||||||
parser.add_argument('--output-normal', type=str, default='assets/moon_normal.png', help='Output normal filename')
|
|
||||||
parser.add_argument('--skip-download', action='store_true', help='Skip downloading files')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
internal_size = args.size * args.supersample
|
|
||||||
|
|
||||||
# Ensure assets dir exists
|
|
||||||
os.makedirs(os.path.dirname(args.output_color) or '.', exist_ok=True)
|
|
||||||
|
|
||||||
# 1. Download
|
|
||||||
if not args.skip_download:
|
|
||||||
download_file(COLOR_URL, SRC_COLOR_FILENAME)
|
|
||||||
download_file(DISP_URL, SRC_DISP_FILENAME)
|
|
||||||
|
|
||||||
# 2. Process Color
|
|
||||||
print(f"Processing Color Map (Internal Size: {internal_size}x{internal_size})...")
|
|
||||||
try:
|
|
||||||
img_color = Image.open(SRC_COLOR_FILENAME).convert('RGB')
|
|
||||||
out_color = equirectangular_to_orthographic(img_color, internal_size)
|
|
||||||
|
|
||||||
if args.supersample > 1:
|
|
||||||
print(f"Downsampling Color to {args.size}x{args.size}...")
|
|
||||||
out_color = out_color.resize((args.size, args.size), Image.LANCZOS)
|
|
||||||
|
|
||||||
out_color.save(args.output_color)
|
|
||||||
print(f"Saved {args.output_color}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error processing color: {e}")
|
|
||||||
|
|
||||||
# 3. Process Normal
|
|
||||||
print(f"Processing Displacement Map (Internal Size: {internal_size}x{internal_size})...")
|
|
||||||
try:
|
|
||||||
from PIL import ImageFile
|
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
|
||||||
|
|
||||||
# Check if file exists
|
|
||||||
if not os.path.exists(SRC_DISP_FILENAME):
|
|
||||||
print(f"Displacement map {SRC_DISP_FILENAME} not found!")
|
|
||||||
return
|
|
||||||
|
|
||||||
img_disp = Image.open(SRC_DISP_FILENAME)
|
|
||||||
out_disp = equirectangular_to_orthographic(img_disp, internal_size)
|
|
||||||
|
|
||||||
# Compute Normals
|
|
||||||
out_normal = compute_normal_map(out_disp, scale=args.bump_scale, blur_radius=args.blur)
|
|
||||||
|
|
||||||
if args.supersample > 1:
|
|
||||||
print(f"Downsampling Normal to {args.size}x{args.size}...")
|
|
||||||
out_normal = out_normal.resize((args.size, args.size), Image.LANCZOS)
|
|
||||||
|
|
||||||
out_normal.save(args.output_normal)
|
|
||||||
print(f"Saved {args.output_normal}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error processing normal: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
print("Done.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -57,7 +57,6 @@ set(SRCS
|
|||||||
src/materials/colorGrading/colorGrading.cpp
|
src/materials/colorGrading/colorGrading.cpp
|
||||||
src/materials/dof/dof.cpp
|
src/materials/dof/dof.cpp
|
||||||
src/materials/flare/flare.cpp
|
src/materials/flare/flare.cpp
|
||||||
src/materials/fog/fog.cpp
|
|
||||||
src/materials/fsr/fsr.cpp
|
src/materials/fsr/fsr.cpp
|
||||||
src/materials/sgsr/sgsr.cpp
|
src/materials/sgsr/sgsr.cpp
|
||||||
src/materials/ssao/ssao.cpp
|
src/materials/ssao/ssao.cpp
|
||||||
@@ -92,7 +91,6 @@ set(SRCS
|
|||||||
src/MaterialParser.cpp
|
src/MaterialParser.cpp
|
||||||
src/MorphTargetBuffer.cpp
|
src/MorphTargetBuffer.cpp
|
||||||
src/PostProcessManager.cpp
|
src/PostProcessManager.cpp
|
||||||
src/ProgramSpecialization.cpp
|
|
||||||
src/RenderPass.cpp
|
src/RenderPass.cpp
|
||||||
src/RenderPrimitive.cpp
|
src/RenderPrimitive.cpp
|
||||||
src/RenderTarget.cpp
|
src/RenderTarget.cpp
|
||||||
@@ -189,7 +187,6 @@ set(PRIVATE_HDRS
|
|||||||
src/MaterialInstanceManager.h
|
src/MaterialInstanceManager.h
|
||||||
src/PIDController.h
|
src/PIDController.h
|
||||||
src/PostProcessManager.h
|
src/PostProcessManager.h
|
||||||
src/ProgramSpecialization.h
|
|
||||||
src/RenderPass.h
|
src/RenderPass.h
|
||||||
src/RenderPrimitive.h
|
src/RenderPrimitive.h
|
||||||
src/RendererUtils.h
|
src/RendererUtils.h
|
||||||
@@ -284,7 +281,6 @@ set(MATERIAL_SRCS
|
|||||||
src/materials/dof/dofTiles.mat
|
src/materials/dof/dofTiles.mat
|
||||||
src/materials/dof/dofTilesSwizzle.mat
|
src/materials/dof/dofTilesSwizzle.mat
|
||||||
src/materials/flare/flare.mat
|
src/materials/flare/flare.mat
|
||||||
src/materials/fog/fog.mat
|
|
||||||
src/materials/fsr/fsr_easu.mat
|
src/materials/fsr/fsr_easu.mat
|
||||||
src/materials/fsr/fsr_easu_mobile.mat
|
src/materials/fsr/fsr_easu_mobile.mat
|
||||||
src/materials/fsr/fsr_easu_mobileF.mat
|
src/materials/fsr/fsr_easu_mobileF.mat
|
||||||
@@ -314,6 +310,11 @@ set(MATERIAL_FL0_SRCS
|
|||||||
src/materials/skybox.mat
|
src/materials/skybox.mat
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(MATERIAL_MULTIVIEW_SRCS
|
||||||
|
src/materials/defaultMaterial.mat
|
||||||
|
src/materials/skybox.mat
|
||||||
|
)
|
||||||
|
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
# Configuration
|
# Configuration
|
||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
@@ -338,6 +339,11 @@ if (FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
|||||||
add_definitions(-DFILAMENT_ENABLE_FEATURE_LEVEL_0)
|
add_definitions(-DFILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Whether to include MULTIVIEW materials.
|
||||||
|
if (FILAMENT_ENABLE_MULTIVIEW)
|
||||||
|
add_definitions(-DFILAMENT_ENABLE_MULTIVIEW)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Whether to force the profiling mode.
|
# Whether to force the profiling mode.
|
||||||
if (FILAMENT_FORCE_PROFILING_MODE)
|
if (FILAMENT_FORCE_PROFILING_MODE)
|
||||||
add_definitions(-DFILAMENT_FORCE_PROFILING_MODE)
|
add_definitions(-DFILAMENT_FORCE_PROFILING_MODE)
|
||||||
@@ -428,6 +434,21 @@ foreach(mat_dir ${MATERIAL_DIRS})
|
|||||||
list(APPEND FILAMAT_FILES_FOR_GROUP ${output_path_fl0})
|
list(APPEND FILAMAT_FILES_FOR_GROUP ${output_path_fl0})
|
||||||
list(APPEND FILAMAT_TARGETS_FOR_GROUP ${output_path_fl0})
|
list(APPEND FILAMAT_TARGETS_FOR_GROUP ${output_path_fl0})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# --- Multiview variant ---
|
||||||
|
list(FIND MATERIAL_MULTIVIEW_SRCS ${mat_src} index)
|
||||||
|
if (${index} GREATER -1 AND FILAMENT_ENABLE_MULTIVIEW)
|
||||||
|
string(REGEX REPLACE "[.]filamat$" "_multiview.filamat" output_path_multiview ${output_path})
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${output_path_multiview}
|
||||||
|
COMMAND matc ${MATC_BASE_FLAGS} -PstereoscopicType=multiview -o ${output_path_multiview} ${fullname}
|
||||||
|
MAIN_DEPENDENCY ${fullname}
|
||||||
|
DEPENDS matc
|
||||||
|
COMMENT "Compiling material ${fullname} (Multiview)"
|
||||||
|
)
|
||||||
|
list(APPEND FILAMAT_FILES_FOR_GROUP ${output_path_multiview})
|
||||||
|
list(APPEND FILAMAT_TARGETS_FOR_GROUP ${output_path_multiview})
|
||||||
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# Generate a single resource file for the whole group
|
# Generate a single resource file for the whole group
|
||||||
@@ -502,14 +523,6 @@ add_custom_command(
|
|||||||
APPEND
|
APPEND
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT "${MATERIAL_DIR}/fog.filamat"
|
|
||||||
DEPENDS ../shaders/src/surface_fog.fs
|
|
||||||
DEPENDS src/materials/utils/depthUtils.fs
|
|
||||||
DEPENDS src/materials/utils/geometry.fs
|
|
||||||
APPEND
|
|
||||||
)
|
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT "${MATERIAL_DIR}/sao.filamat"
|
OUTPUT "${MATERIAL_DIR}/sao.filamat"
|
||||||
DEPENDS src/materials/ssao/ssaoUtils.fs
|
DEPENDS src/materials/ssao/ssaoUtils.fs
|
||||||
|
|||||||
@@ -305,8 +305,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
|||||||
src/webgpu/WebGPUFence.h
|
src/webgpu/WebGPUFence.h
|
||||||
src/webgpu/WebGPUIndexBuffer.cpp
|
src/webgpu/WebGPUIndexBuffer.cpp
|
||||||
src/webgpu/WebGPUIndexBuffer.h
|
src/webgpu/WebGPUIndexBuffer.h
|
||||||
src/webgpu/WebGPUMemoryMappedBuffer.cpp
|
|
||||||
src/webgpu/WebGPUMemoryMappedBuffer.h
|
|
||||||
src/webgpu/WebGPUMsaaTextureResolver.cpp
|
src/webgpu/WebGPUMsaaTextureResolver.cpp
|
||||||
src/webgpu/WebGPUMsaaTextureResolver.h
|
src/webgpu/WebGPUMsaaTextureResolver.h
|
||||||
src/webgpu/WebGPUPipelineCache.cpp
|
src/webgpu/WebGPUPipelineCache.cpp
|
||||||
@@ -582,7 +580,6 @@ if (APPLE OR LINUX)
|
|||||||
test/test_Blit.cpp
|
test/test_Blit.cpp
|
||||||
test/test_MissingRequiredAttributes.cpp
|
test/test_MissingRequiredAttributes.cpp
|
||||||
test/test_ReadPixels.cpp
|
test/test_ReadPixels.cpp
|
||||||
test/test_ReadTexture.cpp
|
|
||||||
test/test_BufferUpdates.cpp
|
test/test_BufferUpdates.cpp
|
||||||
test/test_Callbacks.cpp
|
test/test_Callbacks.cpp
|
||||||
test/test_JobQueue.cpp
|
test/test_JobQueue.cpp
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include <utils/CString.h>
|
#include <utils/CString.h>
|
||||||
#include <utils/FixedCapacityVector.h>
|
#include <utils/FixedCapacityVector.h>
|
||||||
#include <utils/Invocable.h>
|
#include <utils/Invocable.h>
|
||||||
#include <utils/Slice.h>
|
|
||||||
|
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
|
|
||||||
@@ -146,6 +145,10 @@ public:
|
|||||||
return mSpecializationConstants;
|
return mSpecializationConstants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpecializationConstantsInfo& getSpecializationConstants() noexcept {
|
||||||
|
return mSpecializationConstants;
|
||||||
|
}
|
||||||
|
|
||||||
DescriptorSetInfo& getDescriptorBindings() noexcept {
|
DescriptorSetInfo& getDescriptorBindings() noexcept {
|
||||||
return mDescriptorBindings;
|
return mDescriptorBindings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -617,16 +617,6 @@ DECL_DRIVER_API_N(readPixels,
|
|||||||
uint32_t, height,
|
uint32_t, height,
|
||||||
backend::PixelBufferDescriptor&&, data)
|
backend::PixelBufferDescriptor&&, data)
|
||||||
|
|
||||||
DECL_DRIVER_API_N(readTexture,
|
|
||||||
backend::TextureHandle, src,
|
|
||||||
uint8_t, level,
|
|
||||||
uint16_t, layer,
|
|
||||||
uint32_t, x,
|
|
||||||
uint32_t, y,
|
|
||||||
uint32_t, width,
|
|
||||||
uint32_t, height,
|
|
||||||
backend::PixelBufferDescriptor&&, data)
|
|
||||||
|
|
||||||
DECL_DRIVER_API_N(readBufferSubData,
|
DECL_DRIVER_API_N(readBufferSubData,
|
||||||
backend::BufferObjectHandle, src,
|
backend::BufferObjectHandle, src,
|
||||||
uint32_t, offset,
|
uint32_t, offset,
|
||||||
|
|||||||
@@ -84,9 +84,6 @@ public:
|
|||||||
size_t dstRowOffset, size_t dstColumnOffset,
|
size_t dstRowOffset, size_t dstColumnOffset,
|
||||||
size_t dstBytesPerRow, size_t dstChannelCount,
|
size_t dstBytesPerRow, size_t dstChannelCount,
|
||||||
size_t width, size_t height, bool swizzle) {
|
size_t width, size_t height, bool swizzle) {
|
||||||
// TODO: there's a fast-path where memcpy will work but currently not being taken advantage
|
|
||||||
// of.
|
|
||||||
|
|
||||||
const dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
const dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
||||||
const srcComponentType srcMaxValue = getMaxValue<srcComponentType>();
|
const srcComponentType srcMaxValue = getMaxValue<srcComponentType>();
|
||||||
const size_t minChannelCount = math::min(srcChannelCount, dstChannelCount);
|
const size_t minChannelCount = math::min(srcChannelCount, dstChannelCount);
|
||||||
|
|||||||
@@ -17,11 +17,10 @@
|
|||||||
#include <backend/Program.h>
|
#include <backend/Program.h>
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
|
|
||||||
#include <utils/CString.h>
|
|
||||||
#include <utils/Invocable.h>
|
|
||||||
#include <utils/Panic.h>
|
|
||||||
#include <utils/debug.h>
|
#include <utils/debug.h>
|
||||||
|
#include <utils/CString.h>
|
||||||
#include <utils/ostream.h>
|
#include <utils/ostream.h>
|
||||||
|
#include <utils/Invocable.h>
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
|||||||
@@ -784,46 +784,6 @@ const char* toString(DescriptorFlags flags) {
|
|||||||
return "NONE";
|
return "NONE";
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* toString(DescriptorType type) {
|
|
||||||
#define DESCRIPTOR_TYPE_CASE(TYPE) \
|
|
||||||
case DescriptorType::TYPE: { \
|
|
||||||
return #TYPE; \
|
|
||||||
}
|
|
||||||
switch (type) {
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_FLOAT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_INT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_UINT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_DEPTH)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_FLOAT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_INT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_UINT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_DEPTH)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_FLOAT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_INT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_UINT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_DEPTH)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_FLOAT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_INT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_UINT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_DEPTH)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_3D_FLOAT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_3D_INT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_3D_UINT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_FLOAT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_INT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_UINT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_ARRAY_FLOAT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_ARRAY_INT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_ARRAY_UINT)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SAMPLER_EXTERNAL)
|
|
||||||
DESCRIPTOR_TYPE_CASE(UNIFORM_BUFFER)
|
|
||||||
DESCRIPTOR_TYPE_CASE(SHADER_STORAGE_BUFFER)
|
|
||||||
DESCRIPTOR_TYPE_CASE(INPUT_ATTACHMENT)
|
|
||||||
}
|
|
||||||
#undef DESCRIPTOR_TYPE_CASE
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
|
|
||||||
void MetalDriver::createDescriptorSetLayoutR(
|
void MetalDriver::createDescriptorSetLayoutR(
|
||||||
Handle<HwDescriptorSetLayout> dslh, DescriptorSetLayout&& info, utils::ImmutableCString&& tag) {
|
Handle<HwDescriptorSetLayout> dslh, DescriptorSetLayout&& info, utils::ImmutableCString&& tag) {
|
||||||
#if FILAMENT_METAL_DEBUG_LOG == 1
|
#if FILAMENT_METAL_DEBUG_LOG == 1
|
||||||
@@ -834,15 +794,14 @@ void MetalDriver::createDescriptorSetLayoutR(
|
|||||||
labelStr = arg.c_str();
|
labelStr = arg.c_str();
|
||||||
}
|
}
|
||||||
}, info.label);
|
}, info.label);
|
||||||
std::sort(info.descriptors.begin(), info.descriptors.end(),
|
std::sort(info.bindings.begin(), info.bindings.end(),
|
||||||
[](const auto& a, const auto& b) { return a.binding < b.binding; });
|
[](const auto& a, const auto& b) { return a.binding < b.binding; });
|
||||||
DEBUG_LOG("createDescriptorSetLayoutR(dslh = %d, info = { label = %s,\n", dslh.getId(),
|
DEBUG_LOG("createDescriptorSetLayoutR(dslh = %d, info = { label = %s,\n", dslh.getId(),
|
||||||
labelStr);
|
labelStr);
|
||||||
for (size_t i = 0; i < info.descriptors.size(); i++) {
|
for (size_t i = 0; i < info.bindings.size(); i++) {
|
||||||
DEBUG_LOG(" {binding = %d, type = %s, count = %d, stage = %s, flags = %s},\n",
|
DEBUG_LOG(" {binding = %d, type = %s, count = %d, stage = %s, flags = %s},\n",
|
||||||
info.descriptors[i].binding, toString(info.descriptors[i].type),
|
info.bindings[i].binding, toString(info.bindings[i].type), info.bindings[i].count,
|
||||||
info.descriptors[i].count, toString(info.descriptors[i].stageFlags),
|
toString(info.bindings[i].stageFlags), toString(info.bindings[i].flags));
|
||||||
toString(info.descriptors[i].flags));
|
|
||||||
}
|
}
|
||||||
DEBUG_LOG("})\n");
|
DEBUG_LOG("})\n");
|
||||||
#endif
|
#endif
|
||||||
@@ -1814,12 +1773,6 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetalDriver::readTexture(Handle<HwTexture> src, uint8_t level, uint16_t layer, uint32_t x,
|
|
||||||
uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& p) {
|
|
||||||
// TODO: implement readTexture
|
|
||||||
scheduleDestroy(std::move(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MetalDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
void MetalDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||||
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
|
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
|
||||||
// TODO: implement readBufferSubData
|
// TODO: implement readBufferSubData
|
||||||
|
|||||||
@@ -1663,7 +1663,7 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
|
|||||||
case DescriptorType::SAMPLER_EXTERNAL: {
|
case DescriptorType::SAMPLER_EXTERNAL: {
|
||||||
auto found = textures.find(binding.binding);
|
auto found = textures.find(binding.binding);
|
||||||
if (found == textures.end()) {
|
if (found == textures.end()) {
|
||||||
[encoder setTexture:getOrCreateEmptyTexture(driver->mContext) atIndex:binding.binding * 2];
|
[encoder setTexture:driver->mContext->emptyTexture atIndex:binding.binding * 2];
|
||||||
id<MTLSamplerState> sampler =
|
id<MTLSamplerState> sampler =
|
||||||
driver->mContext->samplerStateCache.getOrCreateState({});
|
driver->mContext->samplerStateCache.getOrCreateState({});
|
||||||
[encoder setSamplerState:sampler atIndex:binding.binding * 2 + 1];
|
[encoder setSamplerState:sampler atIndex:binding.binding * 2 + 1];
|
||||||
|
|||||||
@@ -379,11 +379,6 @@ void NoopDriver::readPixels(Handle<HwRenderTarget> src,
|
|||||||
scheduleDestroy(std::move(p));
|
scheduleDestroy(std::move(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NoopDriver::readTexture(Handle<HwTexture> src, uint8_t level, uint16_t layer, uint32_t x,
|
|
||||||
uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& p) {
|
|
||||||
scheduleDestroy(std::move(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoopDriver::readBufferSubData(BufferObjectHandle boh,
|
void NoopDriver::readBufferSubData(BufferObjectHandle boh,
|
||||||
uint32_t offset, uint32_t size, BufferDescriptor&& p) {
|
uint32_t offset, uint32_t size, BufferDescriptor&& p) {
|
||||||
scheduleDestroy(std::move(p));
|
scheduleDestroy(std::move(p));
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
|
|
||||||
#include <private/utils/Tracing.h>
|
#include <private/utils/Tracing.h>
|
||||||
|
|
||||||
#include <utils/FixedCapacityVector.h>
|
|
||||||
#include <utils/Logger.h>
|
#include <utils/Logger.h>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
@@ -48,9 +47,7 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
|
|||||||
GLuint programId = 0;
|
GLuint programId = 0;
|
||||||
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||||
// TODO(exv): Don't copy these spec constants!
|
BlobCacheKey key{ program.getCacheId(), program.getSpecializationConstants() };
|
||||||
BlobCacheKey::SpecializationConstants constants(program.getSpecializationConstants());
|
|
||||||
BlobCacheKey key{ program.getCacheId(), std::move(constants) };
|
|
||||||
|
|
||||||
// FIXME: use a static buffer to avoid systematic allocation
|
// FIXME: use a static buffer to avoid systematic allocation
|
||||||
// always attempt with 64 KiB
|
// always attempt with 64 KiB
|
||||||
|
|||||||
@@ -2161,7 +2161,7 @@ void OpenGLDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
|||||||
DEBUG_MARKER()
|
DEBUG_MARKER()
|
||||||
GLDescriptorSetLayout const* dsl = handle_cast<GLDescriptorSetLayout*>(dslh);
|
GLDescriptorSetLayout const* dsl = handle_cast<GLDescriptorSetLayout*>(dslh);
|
||||||
construct<GLDescriptorSet>(dsh, mContext, dslh, dsl);
|
construct<GLDescriptorSet>(dsh, mContext, dslh, dsl);
|
||||||
mHandleAllocator.associateTagToHandle(dsh.getId(), std::move(tag));
|
mHandleAllocator.associateTagToHandle(dslh.getId(), std::move(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
|
void OpenGLDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
|
||||||
@@ -3985,8 +3985,10 @@ void OpenGLDriver::stopCapture(int) {
|
|||||||
// Read-back ops
|
// Read-back ops
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
void OpenGLDriver::readPixelsFromBoundFramebuffer(uint32_t const x, uint32_t const y,
|
void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
|
||||||
uint32_t width, uint32_t height, PixelBufferDescriptor&& p) {
|
uint32_t const x, uint32_t const y, uint32_t width, uint32_t height,
|
||||||
|
PixelBufferDescriptor&& p) {
|
||||||
|
DEBUG_MARKER()
|
||||||
auto& gl = mContext;
|
auto& gl = mContext;
|
||||||
|
|
||||||
GLenum const glFormat = getFormat(p.format);
|
GLenum const glFormat = getFormat(p.format);
|
||||||
@@ -4019,6 +4021,8 @@ void OpenGLDriver::readPixelsFromBoundFramebuffer(uint32_t const x, uint32_t con
|
|||||||
* of the buffer.
|
* of the buffer.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
|
||||||
|
|
||||||
using PBD = PixelBufferDescriptor;
|
using PBD = PixelBufferDescriptor;
|
||||||
|
|
||||||
// The PBO only needs to accommodate the area we're reading, with alignment.
|
// The PBO only needs to accommodate the area we're reading, with alignment.
|
||||||
@@ -4028,6 +4032,7 @@ void OpenGLDriver::readPixelsFromBoundFramebuffer(uint32_t const x, uint32_t con
|
|||||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
if (UTILS_UNLIKELY(gl.isES2())) {
|
||||||
void* buffer = malloc(pboSize);
|
void* buffer = malloc(pboSize);
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
|
gl.bindFramebuffer(GL_FRAMEBUFFER, s->gl.fbo_read ? s->gl.fbo_read : s->gl.fbo);
|
||||||
glReadPixels(GLint(x), GLint(y), GLint(width), GLint(height), glFormat, glType, buffer);
|
glReadPixels(GLint(x), GLint(y), GLint(width), GLint(height), glFormat, glType, buffer);
|
||||||
CHECK_GL_ERROR()
|
CHECK_GL_ERROR()
|
||||||
|
|
||||||
@@ -4051,6 +4056,10 @@ void OpenGLDriver::readPixelsFromBoundFramebuffer(uint32_t const x, uint32_t con
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||||
|
// glReadPixel doesn't resolve automatically, but it does with the auto-resolve extension,
|
||||||
|
// which we're always emulating. So if we have a resolved fbo (fbo_read), use that instead.
|
||||||
|
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo_read ? s->gl.fbo_read : s->gl.fbo);
|
||||||
|
|
||||||
GLuint pbo;
|
GLuint pbo;
|
||||||
glGenBuffers(1, &pbo);
|
glGenBuffers(1, &pbo);
|
||||||
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
|
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
|
||||||
@@ -4102,85 +4111,6 @@ void OpenGLDriver::readPixelsFromBoundFramebuffer(uint32_t const x, uint32_t con
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLDriver::readPixels(Handle<HwRenderTarget> src, uint32_t const x, uint32_t const y,
|
|
||||||
uint32_t width, uint32_t height, PixelBufferDescriptor&& p) {
|
|
||||||
DEBUG_MARKER()
|
|
||||||
auto& gl = mContext;
|
|
||||||
|
|
||||||
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
|
|
||||||
|
|
||||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
|
||||||
gl.bindFramebuffer(GL_FRAMEBUFFER, s->gl.fbo_read ? s->gl.fbo_read : s->gl.fbo);
|
|
||||||
readPixelsFromBoundFramebuffer(x, y, width, height, std::move(p));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
||||||
// glReadPixel doesn't resolve automatically, but it does with the auto-resolve extension,
|
|
||||||
// which we're always emulating. So if we have a resolved fbo (fbo_read), use that instead.
|
|
||||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo_read ? s->gl.fbo_read : s->gl.fbo);
|
|
||||||
readPixelsFromBoundFramebuffer(x, y, width, height, std::move(p));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLDriver::readTexture(Handle<HwTexture> src, uint8_t level, uint16_t layer, uint32_t x,
|
|
||||||
uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& p) {
|
|
||||||
DEBUG_MARKER()
|
|
||||||
auto& gl = mContext;
|
|
||||||
|
|
||||||
// readTexture() requires GLES 3.0+ features such as GL_READ_FRAMEBUFFER and
|
|
||||||
// glFramebufferTextureLayer, so we wrap it in FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2.
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
||||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
|
||||||
// This is not supported on ES2 (at least not in this driver)
|
|
||||||
scheduleDestroy(std::move(p));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLTexture const* s = handle_cast<GLTexture*>(src);
|
|
||||||
assert_invariant(s);
|
|
||||||
assert_invariant(s->target != SamplerType::SAMPLER_3D);
|
|
||||||
|
|
||||||
GLuint fbo = 0;
|
|
||||||
glGenFramebuffers(1, &fbo);
|
|
||||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
|
||||||
|
|
||||||
GLenum const attachment = GL_COLOR_ATTACHMENT0;
|
|
||||||
|
|
||||||
switch (s->target) {
|
|
||||||
case SamplerType::SAMPLER_2D:
|
|
||||||
if (any(s->usage & TextureUsage::SAMPLEABLE)) {
|
|
||||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, attachment, GL_TEXTURE_2D, s->gl.id,
|
|
||||||
level);
|
|
||||||
} else {
|
|
||||||
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, attachment, GL_RENDERBUFFER,
|
|
||||||
s->gl.id);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SamplerType::SAMPLER_CUBEMAP:
|
|
||||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, attachment,
|
|
||||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, s->gl.id, level);
|
|
||||||
break;
|
|
||||||
case SamplerType::SAMPLER_2D_ARRAY:
|
|
||||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
|
||||||
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, attachment, s->gl.id, level, layer);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// This should not happen (e.g. 3D textures are excluded)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_GL_FRAMEBUFFER_STATUS(GL_READ_FRAMEBUFFER)
|
|
||||||
|
|
||||||
readPixelsFromBoundFramebuffer(x, y, width, height, std::move(p));
|
|
||||||
|
|
||||||
gl.unbindFramebuffer(GL_READ_FRAMEBUFFER);
|
|
||||||
glDeleteFramebuffers(1, &fbo);
|
|
||||||
#else
|
|
||||||
scheduleDestroy(std::move(p));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenGLDriver::readBufferSubData(BufferObjectHandle boh,
|
void OpenGLDriver::readBufferSubData(BufferObjectHandle boh,
|
||||||
uint32_t const offset, uint32_t size, BufferDescriptor&& p) {
|
uint32_t const offset, uint32_t size, BufferDescriptor&& p) {
|
||||||
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
|
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
|
||||||
@@ -4755,9 +4685,6 @@ void OpenGLDriver::bindDescriptorSet(
|
|||||||
|
|
||||||
if (UTILS_UNLIKELY(!dsh)) {
|
if (UTILS_UNLIKELY(!dsh)) {
|
||||||
mBoundDescriptorSets[set].dsh = dsh;
|
mBoundDescriptorSets[set].dsh = dsh;
|
||||||
#ifndef NDEBUG
|
|
||||||
mBoundDescriptorSets[set].tag = "null";
|
|
||||||
#endif
|
|
||||||
mInvalidDescriptorSetBindings.set(set, true);
|
mInvalidDescriptorSetBindings.set(set, true);
|
||||||
mInvalidDescriptorSetBindingOffsets.set(set, true);
|
mInvalidDescriptorSetBindingOffsets.set(set, true);
|
||||||
return;
|
return;
|
||||||
@@ -4779,9 +4706,6 @@ void OpenGLDriver::bindDescriptorSet(
|
|||||||
// `offsets` data's lifetime will end when this function returns. We have to make a copy.
|
// `offsets` data's lifetime will end when this function returns. We have to make a copy.
|
||||||
// (the data is allocated inside the CommandStream)
|
// (the data is allocated inside the CommandStream)
|
||||||
mBoundDescriptorSets[set].dsh = dsh;
|
mBoundDescriptorSets[set].dsh = dsh;
|
||||||
#ifndef NDEBUG
|
|
||||||
mBoundDescriptorSets[set].tag = mHandleAllocator.getHandleTag(dsh.getId());
|
|
||||||
#endif
|
|
||||||
assert_invariant(offsets.data() != nullptr || ds->getDynamicBufferCount() == 0);
|
assert_invariant(offsets.data() != nullptr || ds->getDynamicBufferCount() == 0);
|
||||||
std::copy_n(offsets.data(), ds->getDynamicBufferCount(),
|
std::copy_n(offsets.data(), ds->getDynamicBufferCount(),
|
||||||
mBoundDescriptorSets[set].offsets.data());
|
mBoundDescriptorSets[set].offsets.data());
|
||||||
|
|||||||
@@ -363,8 +363,6 @@ private:
|
|||||||
bool isDefaultFramebuffer) noexcept;
|
bool isDefaultFramebuffer) noexcept;
|
||||||
|
|
||||||
// Common methods
|
// Common methods
|
||||||
void readPixelsFromBoundFramebuffer(uint32_t x, uint32_t y, uint32_t width, uint32_t height,
|
|
||||||
PixelBufferDescriptor&& p);
|
|
||||||
void createTextureCommon(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
void createTextureCommon(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
||||||
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
|
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
|
||||||
TextureUsage usage, utils::ImmutableCString&& tag);
|
TextureUsage usage, utils::ImmutableCString&& tag);
|
||||||
@@ -409,9 +407,6 @@ private:
|
|||||||
struct {
|
struct {
|
||||||
DescriptorSetHandle dsh;
|
DescriptorSetHandle dsh;
|
||||||
std::array<uint32_t, CONFIG_UNIFORM_BINDING_COUNT> offsets;
|
std::array<uint32_t, CONFIG_UNIFORM_BINDING_COUNT> offsets;
|
||||||
#ifndef NDEBUG
|
|
||||||
utils::ImmutableCString tag;
|
|
||||||
#endif
|
|
||||||
} mBoundDescriptorSets[MAX_DESCRIPTOR_SET_COUNT] = {};
|
} mBoundDescriptorSets[MAX_DESCRIPTOR_SET_COUNT] = {};
|
||||||
|
|
||||||
void clearWithRasterPipe(TargetBufferFlags clearFlags,
|
void clearWithRasterPipe(TargetBufferFlags clearFlags,
|
||||||
|
|||||||
@@ -120,33 +120,27 @@ bool PlatformEGL::isOpenGL() const noexcept {
|
|||||||
PlatformEGL::ExternalImageEGL::~ExternalImageEGL() = default;
|
PlatformEGL::ExternalImageEGL::~ExternalImageEGL() = default;
|
||||||
|
|
||||||
Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& driverConfig) {
|
Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& driverConfig) {
|
||||||
static constexpr int kMaxNumEGLDevices = 32;
|
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
||||||
|
|
||||||
EGLint major, minor;
|
EGLint major, minor;
|
||||||
EGLBoolean initialized = false;
|
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||||
|
|
||||||
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
if (!initialized) {
|
||||||
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
EGLDeviceEXT eglDevice;
|
||||||
PFNEGLGETPLATFORMDISPLAYEXTPROC const getPlatformDisplay =
|
EGLint numDevices;
|
||||||
PFNEGLGETPLATFORMDISPLAYEXTPROC(eglGetProcAddress("eglGetPlatformDisplay"));
|
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
||||||
|
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
||||||
if (eglQueryDevicesEXT != nullptr && getPlatformDisplay != nullptr) {
|
if (eglQueryDevicesEXT != nullptr) {
|
||||||
EGLint numDevices = 0;
|
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
|
||||||
EGLDeviceEXT eglDevices[kMaxNumEGLDevices];
|
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||||
if (eglQueryDevicesEXT(kMaxNumEGLDevices, eglDevices, &numDevices)) {
|
eglGetProcAddress("eglGetPlatformDisplay"))) {
|
||||||
for (int i = 0; i < numDevices && !initialized; ++i) {
|
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
|
||||||
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevices[i], nullptr);
|
|
||||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!initialized) {
|
|
||||||
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
||||||
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
|
||||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UTILS_UNLIKELY(!initialized)) {
|
if (UTILS_UNLIKELY(!initialized)) {
|
||||||
LOG(ERROR) << "eglInitialize failed";
|
LOG(ERROR) << "eglInitialize failed";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
@@ -31,19 +31,22 @@ namespace filament::backend {
|
|||||||
VulkanBufferProxy::VulkanBufferProxy(VulkanContext const& context, VmaAllocator allocator,
|
VulkanBufferProxy::VulkanBufferProxy(VulkanContext const& context, VmaAllocator allocator,
|
||||||
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, VulkanBufferBinding binding,
|
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, VulkanBufferBinding binding,
|
||||||
BufferUsage usage, uint32_t numBytes)
|
BufferUsage usage, uint32_t numBytes)
|
||||||
: mStagingBufferBypassEnabled(context.stagingBufferBypassEnabled()),
|
: mStagingBufferBypassEnabled(context.stagingBufferBypassEnabled()),
|
||||||
mAllocator(allocator),
|
mAllocator(allocator),
|
||||||
mStagePool(stagePool),
|
mStagePool(stagePool),
|
||||||
mBufferCache(bufferCache),
|
mBufferCache(bufferCache),
|
||||||
mBuffer(mBufferCache.acquire(binding, numBytes)),
|
mBuffer(mBufferCache.acquire(binding, numBytes)),
|
||||||
mLastReadAge(0),
|
mLastReadAge(0),
|
||||||
mUsage(usage) {}
|
mUsage(usage) {}
|
||||||
|
|
||||||
void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData,
|
void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData,
|
||||||
uint32_t byteOffset, uint32_t numBytes) {
|
uint32_t byteOffset, uint32_t numBytes) {
|
||||||
|
|
||||||
// This means that the buffer is not currently in use by the GPU
|
// This means that we're recording a write into a command buffer without a previous read, so it
|
||||||
bool const isAvailable = mBuffer->getCount() == 1;
|
// should be safe to
|
||||||
|
// 1) Do a direct memcpy in UMA mode
|
||||||
|
// 2) Skip adding a barrier (to protect the write from writing over a read).
|
||||||
|
bool const isAvailable = commands.age() != mLastReadAge;
|
||||||
|
|
||||||
// Keep track of the VulkanBuffer usage
|
// Keep track of the VulkanBuffer usage
|
||||||
commands.acquire(mBuffer);
|
commands.acquire(mBuffer);
|
||||||
@@ -78,13 +81,9 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
|
|||||||
memcpy(stage->mapping(), cpuData, numBytes);
|
memcpy(stage->mapping(), cpuData, numBytes);
|
||||||
vmaFlushAllocation(mAllocator, stage->memory(), stage->offset(), numBytes);
|
vmaFlushAllocation(mAllocator, stage->memory(), stage->offset(), numBytes);
|
||||||
|
|
||||||
// This means that we're recording a write into a command buffer with a previous read, so it
|
|
||||||
// needs to add a barrier (to protect the write from writing over a read).
|
|
||||||
bool const issueBarrier = commands.age() == mLastReadAge;
|
|
||||||
|
|
||||||
// If there was a previous read, then we need to make sure the following write is properly
|
// If there was a previous read, then we need to make sure the following write is properly
|
||||||
// synced with the previous read.
|
// synced with the previous read.
|
||||||
if (issueBarrier) {
|
if (!isAvailable) {
|
||||||
VkAccessFlags srcAccess = 0;
|
VkAccessFlags srcAccess = 0;
|
||||||
VkPipelineStageFlags srcStage = 0;
|
VkPipelineStageFlags srcStage = 0;
|
||||||
if (getBinding() == VulkanBufferBinding::UNIFORM) {
|
if (getBinding() == VulkanBufferBinding::UNIFORM) {
|
||||||
@@ -133,7 +132,7 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
|
|||||||
dstAccessMask |= VK_ACCESS_INDEX_READ_BIT;
|
dstAccessMask |= VK_ACCESS_INDEX_READ_BIT;
|
||||||
dstStageMask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
|
dstStageMask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
|
||||||
} else if (getBinding() == VulkanBufferBinding::UNIFORM) {
|
} else if (getBinding() == VulkanBufferBinding::UNIFORM) {
|
||||||
dstAccessMask |= VK_ACCESS_UNIFORM_READ_BIT;
|
dstAccessMask |= VK_ACCESS_SHADER_READ_BIT;
|
||||||
dstStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
|
dstStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
|
||||||
} else if (getBinding() == VulkanBufferBinding::SHADER_STORAGE) {
|
} else if (getBinding() == VulkanBufferBinding::SHADER_STORAGE) {
|
||||||
// TODO: implement me
|
// TODO: implement me
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ uint32_t appendBindings(VkDescriptorSetLayoutBinding* toBind, VkDescriptorType t
|
|||||||
|
|
||||||
uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
|
uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
|
||||||
fvkutils::SamplerBitmask const& mask, fvkutils::SamplerBitmask const& external,
|
fvkutils::SamplerBitmask const& mask, fvkutils::SamplerBitmask const& external,
|
||||||
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> const& immutableSamplers) {
|
utils::FixedCapacityVector<VkSampler> const& immutableSamplers) {
|
||||||
using Bitmask = fvkutils::SamplerBitmask;
|
using Bitmask = fvkutils::SamplerBitmask;
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
Bitmask alreadySeen;
|
Bitmask alreadySeen;
|
||||||
@@ -92,7 +92,7 @@ uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
|
|||||||
.descriptorCount = 1,
|
.descriptorCount = 1,
|
||||||
.stageFlags = stages,
|
.stageFlags = stages,
|
||||||
.pImmutableSamplers = external[index] && immutableSamplerCount > immutableIndex
|
.pImmutableSamplers = external[index] && immutableSamplerCount > immutableIndex
|
||||||
? &(immutableSamplers[immutableIndex++].second)
|
? &immutableSamplers[immutableIndex++]
|
||||||
: nullptr,
|
: nullptr,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -100,14 +100,14 @@ uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t computeImmutableSamplerHash(
|
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers) {
|
||||||
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> const& samplers) {
|
|
||||||
size_t const size = samplers.size();
|
size_t const size = samplers.size();
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
|
} else if (size == 1) {
|
||||||
|
return (uint64_t) samplers[0];
|
||||||
}
|
}
|
||||||
//64bit + 64bit per element = 4 words
|
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 2, 0);
|
||||||
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 4, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
@@ -128,7 +128,7 @@ void VulkanDescriptorSetLayoutCache::terminate() noexcept {
|
|||||||
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
||||||
VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||||
fvkutils::SamplerBitmask externalSamplers,
|
fvkutils::SamplerBitmask externalSamplers,
|
||||||
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> immutableSamplers) {
|
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
|
||||||
LayoutKey key = {
|
LayoutKey key = {
|
||||||
.bitmask = bitmasks,
|
.bitmask = bitmasks,
|
||||||
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers),
|
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers),
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public:
|
|||||||
// This method is meant to be used with external samplers
|
// This method is meant to be used with external samplers
|
||||||
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||||
fvkutils::SamplerBitmask externalSamplers,
|
fvkutils::SamplerBitmask externalSamplers,
|
||||||
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> immutableSamplers = {});
|
utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkDevice mDevice;
|
VkDevice mDevice;
|
||||||
|
|||||||