From af2fc39bad8e410aeca6ddf1791ad6730e23a257 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Fri, 21 Aug 2020 10:21:31 -0700 Subject: [PATCH] Add JNI bindings for CSM (#2979) * Add JNI bindings for CSM * Fix Windows build * Fix jovadoc --- .../src/main/cpp/LightManager.cpp | 66 ++++++--- .../google/android/filament/LightManager.java | 127 +++++++++++++++++- filament/include/filament/LightManager.h | 12 +- 3 files changed, 175 insertions(+), 30 deletions(-) diff --git a/android/filament-android/src/main/cpp/LightManager.cpp b/android/filament-android/src/main/cpp/LightManager.cpp index 8a34162568..1c71d1bf71 100644 --- a/android/filament-android/src/main/cpp/LightManager.cpp +++ b/android/filament-android/src/main/cpp/LightManager.cpp @@ -20,6 +20,8 @@ #include +#include + using namespace filament; using namespace utils; @@ -71,22 +73,32 @@ Java_com_google_android_filament_LightManager_nBuilderCastShadows(JNIEnv*, jclas } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv*, jclass, - jlong nativeBuilder, jint mapSize, jfloat constantBias, jfloat normalBias, jfloat shadowFar, - jfloat shadowNearHint, jfloat shadowFarHint, jboolean stable, - jboolean screenSpaceContactShadows, jint stepCount, jfloat maxShadowDistance) { +Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env, jclass, + jlong nativeBuilder, jint mapSize, jint cascades, jfloatArray splitPositions, + jfloat constantBias, jfloat normalBias, jfloat shadowFar, jfloat shadowNearHint, + jfloat shadowFarHint, jboolean stable, jboolean screenSpaceContactShadows, jint stepCount, + jfloat maxShadowDistance) { LightManager::Builder *builder = (LightManager::Builder *) nativeBuilder; - builder->shadowOptions( - LightManager::ShadowOptions{.mapSize = (uint32_t)mapSize, - .constantBias = constantBias, - .normalBias = normalBias, - .shadowFar = shadowFar, - .shadowNearHint = shadowNearHint, - .shadowFarHint = shadowFarHint, - .stable = (bool)stable, - .screenSpaceContactShadows = (bool)screenSpaceContactShadows, - .stepCount = uint8_t(stepCount), - .maxShadowDistance = maxShadowDistance}); + LightManager::ShadowOptions shadowOptions { + .mapSize = (uint32_t)mapSize, + .shadowCascades = (uint8_t)cascades, + .constantBias = constantBias, + .normalBias = normalBias, + .shadowFar = shadowFar, + .shadowNearHint = shadowNearHint, + .shadowFarHint = shadowFarHint, + .stable = (bool)stable, + .screenSpaceContactShadows = (bool)screenSpaceContactShadows, + .stepCount = uint8_t(stepCount), + .maxShadowDistance = maxShadowDistance + }; + jfloat *nativeSplits = env->GetFloatArrayElements(splitPositions, NULL); + const jsize splitCount = std::min((jsize) 3, env->GetArrayLength(splitPositions)); + for (jsize i = 0; i < splitCount; i++) { + shadowOptions.cascadeSplitPositions[i] = nativeSplits[i]; + } + env->ReleaseFloatArrayElements(splitPositions, nativeSplits, 0); + builder->shadowOptions(shadowOptions); } extern "C" JNIEXPORT void JNICALL @@ -173,6 +185,30 @@ Java_com_google_android_filament_LightManager_nBuilderHaloFalloff(JNIEnv*, jclas builder->sunHaloFalloff(haloFalloff); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_LightManager_nComputeUniformSplits(JNIEnv* env, jclass, + jfloatArray splitPositions, jint cascades) { + jfloat *nativeSplits = env->GetFloatArrayElements(splitPositions, NULL); + LightManager::ShadowCascades::computeUniformSplits(nativeSplits, (uint8_t) cascades); + env->ReleaseFloatArrayElements(splitPositions, nativeSplits, 0); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_LightManager_nComputeLogSplits(JNIEnv* env, jclass, + jfloatArray splitPositions, jint cascades, jfloat near, jfloat far) { + jfloat *nativeSplits = env->GetFloatArrayElements(splitPositions, NULL); + LightManager::ShadowCascades::computeLogSplits(nativeSplits, (uint8_t) cascades, near, far); + env->ReleaseFloatArrayElements(splitPositions, nativeSplits, 0); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_LightManager_nComputePracticalSplits(JNIEnv* env, jclass, + jfloatArray splitPositions, jint cascades, jfloat near, jfloat far, jfloat lambda) { + jfloat *nativeSplits = env->GetFloatArrayElements(splitPositions, NULL); + LightManager::ShadowCascades::computePracticalSplits(nativeSplits, (uint8_t) cascades, near, far, lambda); + env->ReleaseFloatArrayElements(splitPositions, nativeSplits, 0); +} + extern "C" JNIEXPORT jboolean JNICALL Java_com_google_android_filament_LightManager_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder, jlong nativeEngine, jint entity) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/LightManager.java b/android/filament-android/src/main/java/com/google/android/filament/LightManager.java index d522813de8..c51c26f035 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/LightManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/LightManager.java @@ -16,6 +16,7 @@ package com.google.android.filament; +import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Size; @@ -190,6 +191,54 @@ public class LightManager { /** Size of the shadow map in texels. Must be a power-of-two. */ public int mapSize = 1024; + /** + * Number of shadow cascades to use for this light. Must be between 1 and 4 (inclusive). + * A value greater than 1 turns on cascaded shadow mapping (CSM). + * Only applicable to Type.SUN or Type.DIRECTIONAL lights. + * + *

+ * When using shadow cascades, {@link ShadowOptions#cascadeSplitPositions} must also be set. + *

+ * + * @see ShadowOptions#cascadeSplitPositions + */ + @IntRange(from = 1, to = 4) + public int shadowCascades = 1; + + /** + * The split positions for shadow cascades. + * + *

+ * Cascaded shadow mapping (CSM) partitions the camera frustum into cascades. These values + * determine the planes along the camera's Z axis to split the frustum. The camera near + * plane is represented by 0.0f and the far plane represented by 1.0f. + *

+ * + *

+ * For example, if using 4 cascades, these values would set a uniform split scheme: + * { 0.25f, 0.50f, 0.75f } + *

+ * + *

+ * For N cascades, N - 1 split positions will be read from this array. + *

+ * + *

+ * Filament provides utility methods inside {@link ShadowCascades} to help set these values. + * For example, to use a uniform split scheme: + *

+ * + *
+         * LightManager.ShadowCascades.computeUniformSplits(options.cascadeSplitPositions, 4);
+         * 
+ * + * @see ShadowCascades#computeUniformSplits + * @see ShadowCascades#computeLogSplits + * @see ShadowCascades#computePracticalSplits + */ + @NonNull + @Size(min = 3) + public float[] cascadeSplitPositions = { 0.25f, 0.50f, 0.75f }; /** Constant bias in world units (e.g. meters) by which shadows are moved away from the * light. 1mm by default. @@ -258,6 +307,71 @@ public class LightManager { public float maxShadowDistance = 0.3f; } + public static class ShadowCascades { + /** + * Utility method to compute {@link ShadowOptions#cascadeSplitPositions} according to a + * uniform split scheme. + * + * @param splitPositions a float array of at least size (cascades - 1) to write the split + * positions into + * @param cascades the number of shadow cascades, at most 4 + */ + public static void computeUniformSplits(@NonNull @Size(min = 1) float[] splitPositions, + @IntRange(from = 1, to = 4) int cascades) { + if (splitPositions.length < cascades - 1) { + throw new ArrayIndexOutOfBoundsException( + String.format("splitPositions array length must be at least %d", cascades - 1)); + } + nComputeUniformSplits(splitPositions, cascades); + } + + /** + * Utility method to compute {@link ShadowOptions#cascadeSplitPositions} according to a + * logarithmic split scheme. + * + * @param splitPositions a float array of at least size (cascades - 1) to write the split + * positions into + * @param cascades the number of shadow cascades, at most 4 + * @param near the camera near plane + * @param far the camera far plane + */ + public static void computeLogSplits(@NonNull @Size(min = 1) float[] splitPositions, + @IntRange(from = 1, to = 4) int cascades, float near, float far) { + if (splitPositions.length < cascades - 1) { + throw new ArrayIndexOutOfBoundsException( + String.format("splitPositions array length must be at least %d", cascades - 1)); + } + nComputeLogSplits(splitPositions, cascades, near, far); + } + + /** + * Utility method to compute {@link ShadowOptions#cascadeSplitPositions} according to a + * practical split scheme. + * + *

+ * The practical split scheme uses uses a lambda value to interpolate between the logrithmic + * and uniform split schemes. Start with a lambda value of 0.5f and adjust for your scene. + *

+ * + * See: Zhang et al 2006, "Parallel-split shadow maps for large-scale virtual environments" + * + * @param splitPositions a float array of at least size (cascades - 1) to write the split + * positions into + * @param cascades the number of shadow cascades, at most 4 + * @param near the camera near plane + * @param far the camera far plane + * @param lambda a float in the range [0, 1] that interpolates between log and + * uniform split schemes + */ + public static void computePracticalSplits(@NonNull @Size(min = 1) float[] splitPositions, + @IntRange(from = 1, to = 4) int cascades, float near, float far, float lambda) { + if (splitPositions.length < cascades - 1) { + throw new ArrayIndexOutOfBoundsException( + String.format("splitPositions array length must be at least %d", cascades - 1)); + } + nComputePracticalSplits(splitPositions, cascades, near, far, lambda); + } + } /** Typical efficiency of an incandescent light bulb (2.2%) */ public static final float EFFICIENCY_INCANDESCENT = 0.0220f; @@ -316,9 +430,10 @@ public class LightManager { @NonNull public Builder shadowOptions(@NonNull ShadowOptions options) { nBuilderShadowOptions(mNativeBuilder, - options.mapSize, options.constantBias, options.normalBias, options.shadowFar, - options.shadowNearHint, options.shadowFarHint, options.stable, - options.screenSpaceContactShadows, options.stepCount, options.maxShadowDistance); + options.mapSize, options.shadowCascades, options.cascadeSplitPositions, + options.constantBias, options.normalBias, options.shadowFar, options.shadowNearHint, + options.shadowFarHint, options.stable, options.screenSpaceContactShadows, + options.stepCount, options.maxShadowDistance); return this; } @@ -944,7 +1059,7 @@ public class LightManager { private static native void nDestroyBuilder(long nativeBuilder); private static native boolean nBuilderBuild(long nativeBuilder, long nativeEngine, int entity); private static native void nBuilderCastShadows(long nativeBuilder, boolean enable); - private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize, float constantBias, float normalBias, float shadowFar, float shadowNearHint, float shadowFarhint, boolean stable, boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance); + private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize, int cascades, float[] splitPositions, float constantBias, float normalBias, float shadowFar, float shadowNearHint, float shadowFarhint, boolean stable, boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance); private static native void nBuilderCastLight(long nativeBuilder, boolean enabled); private static native void nBuilderPosition(long nativeBuilder, float x, float y, float z); private static native void nBuilderDirection(long nativeBuilder, float x, float y, float z); @@ -958,6 +1073,10 @@ public class LightManager { private static native void nBuilderHaloSize(long nativeBuilder, float haloSize); private static native void nBuilderHaloFalloff(long nativeBuilder, float haloFalloff); + private static native void nComputeUniformSplits(float[] splitPositions, int cascades); + private static native void nComputeLogSplits(float[] splitPositions, int cascades, float near, float far); + private static native void nComputePracticalSplits(float[] splitPositions, int cascades, float near, float far, float lambda); + private static native int nGetType(long nativeLightManager, int i); private static native void nSetPosition(long nativeLightManager, int i, float x, float y, float z); private static native void nGetPosition(long nativeLightManager, int i, float[] out); diff --git a/filament/include/filament/LightManager.h b/filament/include/filament/LightManager.h index 0aab4dc49a..ce487df72b 100644 --- a/filament/include/filament/LightManager.h +++ b/filament/include/filament/LightManager.h @@ -199,9 +199,7 @@ public: * * When using shadow cascades, cascadeSplitPositions must also be set. * - * @see ShadowCascades::cascadeSplitPositions - * - * @warning This API is still experimental and subject to change. + * @see ShadowOptions::cascadeSplitPositions */ uint8_t shadowCascades = 1; @@ -227,8 +225,6 @@ public: * @see ShadowCascades::computeUniformSplits * @see ShadowCascades::computeLogSplits * @see ShadowCascades::computePracticalSplits - * - * @warning This API is still experimental and subject to change. */ float cascadeSplitPositions[3] = { 0.25f, 0.50f, 0.75f }; @@ -319,8 +315,6 @@ public: * Utility method to compute ShadowOptions::cascadeSplitPositions according to a uniform * split scheme. * - * @warning This API is still experimental and subject to change. - * * @param splitPositions a float array of at least size (cascades - 1) to write the split * positions into * @param cascades the number of shadow cascades, at most 4 @@ -331,8 +325,6 @@ public: * Utility method to compute ShadowOptions::cascadeSplitPositions according to a logarithmic * split scheme. * - * @warning This API is still experimental and subject to change. - * * @param splitPositions a float array of at least size (cascades - 1) to write the split * positions into * @param cascades the number of shadow cascades, at most 4 @@ -351,8 +343,6 @@ public: * * See: Zhang et al 2006, "Parallel-split shadow maps for large-scale virtual environments" * - * @warning This API is still experimental and subject to change. - * * @param splitPositions a float array of at least size (cascades - 1) to write the split * positions into * @param cascades the number of shadow cascades, at most 4