From b5f9c66503cdc0fe7f674aee89600652d9c17178 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 19 Sep 2019 17:24:41 -0700 Subject: [PATCH] model/view matrices on Camera now use double precision This PR only changes the API, internal storage and computation are still done in float, but this paves the way to double precision transforms. --- RELEASE_NOTES.md | 3 ++ .../filament-android/src/main/cpp/Camera.cpp | 35 ++++++++++++-- .../com/google/android/filament/Asserts.java | 15 ++++++ .../com/google/android/filament/Camera.java | 48 +++++++++++++++++++ filament/include/filament/Camera.h | 7 +-- filament/src/Camera.cpp | 44 +++++++++++------ filament/src/Scene.cpp | 6 ++- filament/src/ShadowMap.cpp | 4 +- filament/src/View.cpp | 4 +- filament/src/details/Camera.h | 13 ++--- filament/src/details/Scene.h | 2 +- .../hello-gltf/CameraManipulator.cpp | 2 +- web/filament-js/jsbindings.cpp | 4 +- 13 files changed, 150 insertions(+), 37 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0dd0e581b2..ce2cddd321 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,6 +5,9 @@ A new header is inserted each time a *tag* is created. ## v1.12.1 (currently main branch) +- engine: `double` precision model (and view) matrix on `Camera`. This is only an + API change, internal precision is still `float` [⚠️ **API Change**]. + ## v1.12.0 - engine: Option to automatically compute bent normals from SSAO & apply to specular AO diff --git a/android/filament-android/src/main/cpp/Camera.cpp b/android/filament-android/src/main/cpp/Camera.cpp index a39c4ef9f9..800a4da303 100644 --- a/android/filament-android/src/main/cpp/Camera.cpp +++ b/android/filament-android/src/main/cpp/Camera.cpp @@ -102,10 +102,19 @@ Java_com_google_android_filament_Camera_nSetModelMatrix(JNIEnv *env, jclass, jlong nativeCamera, jfloatArray in_) { Camera* camera = (Camera *) nativeCamera; jfloat *in = env->GetFloatArrayElements(in_, NULL); - camera->setModelMatrix(*reinterpret_cast(in)); + camera->setModelMatrix((math::mat4)*reinterpret_cast(in)); env->ReleaseFloatArrayElements(in_, in, JNI_ABORT); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_Camera_nSetModelMatrixFp64(JNIEnv *env, jclass, + jlong nativeCamera, jdoubleArray in_) { + Camera* camera = (Camera *) nativeCamera; + jdouble *in = env->GetDoubleArrayElements(in_, NULL); + camera->setModelMatrix(*reinterpret_cast(in)); + env->ReleaseDoubleArrayElements(in_, in, JNI_ABORT); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Camera_nGetProjectionMatrix(JNIEnv *env, jclass, jlong nativeCamera, jdoubleArray out_) { @@ -141,21 +150,41 @@ Java_com_google_android_filament_Camera_nGetModelMatrix(JNIEnv *env, jclass, jlong nativeCamera, jfloatArray out_) { Camera *camera = (Camera *) nativeCamera; jfloat *out = env->GetFloatArrayElements(out_, NULL); - const filament::math::mat4f& m = camera->getModelMatrix(); + const filament::math::mat4f& m = (math::mat4f)camera->getModelMatrix(); std::copy_n(&m[0][0], 16, out); env->ReleaseFloatArrayElements(out_, out, 0); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_Camera_nGetModelMatrixFp64(JNIEnv *env, jclass, + jlong nativeCamera, jdoubleArray out_) { + Camera *camera = (Camera *) nativeCamera; + jdouble *out = env->GetDoubleArrayElements(out_, NULL); + const filament::math::mat4& m = camera->getModelMatrix(); + std::copy_n(&m[0][0], 16, out); + env->ReleaseDoubleArrayElements(out_, out, 0); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Camera_nGetViewMatrix(JNIEnv *env, jclass, jlong nativeCamera, jfloatArray out_) { Camera *camera = (Camera *) nativeCamera; jfloat *out = env->GetFloatArrayElements(out_, NULL); - const filament::math::mat4f& m = camera->getViewMatrix(); + const filament::math::mat4f& m = (math::mat4f)camera->getViewMatrix(); std::copy_n(&m[0][0], 16, out); env->ReleaseFloatArrayElements(out_, out, 0); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_Camera_nGetViewMatrixFp64(JNIEnv *env, jclass, jlong nativeCamera, + jdoubleArray out_) { + Camera *camera = (Camera *) nativeCamera; + jdouble *out = env->GetDoubleArrayElements(out_, NULL); + const filament::math::mat4& m = camera->getViewMatrix(); + std::copy_n(&m[0][0], 16, out); + env->ReleaseDoubleArrayElements(out_, out, 0); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Camera_nGetPosition(JNIEnv *env, jclass, jlong nativeCamera, jfloatArray out_) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/Asserts.java b/android/filament-android/src/main/java/com/google/android/filament/Asserts.java index 759b1829f7..ac88bcff06 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Asserts.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Asserts.java @@ -63,12 +63,27 @@ final class Asserts { return out; } + @NonNull @Size(min = 16) + static double[] assertMat4(@Nullable double[] out) { + if (out == null) out = new double[16]; + else if (out.length < 16) { + throw new ArrayIndexOutOfBoundsException("Array length must be at least 16"); + } + return out; + } + static void assertMat4fIn(@NonNull @Size(min = 16) float[] in) { if (in.length < 16) { throw new ArrayIndexOutOfBoundsException("Array length must be at least 16"); } } + static void assertMat4In(@NonNull @Size(min = 16) double[] in) { + if (in.length < 16) { + throw new ArrayIndexOutOfBoundsException("Array length must be at least 16"); + } + } + @NonNull @Size(min = 3) static float[] assertFloat3(@Nullable float[] out) { if (out == null) out = new float[3]; diff --git a/android/filament-android/src/main/java/com/google/android/filament/Camera.java b/android/filament-android/src/main/java/com/google/android/filament/Camera.java index 9a9da5bbc2..ff3c35bfef 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Camera.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Camera.java @@ -419,6 +419,20 @@ public class Camera { nSetModelMatrix(getNativeObject(), viewMatrix); } + /** + * Sets the camera's view matrix. + *

+ * Helper method to set the camera's entity transform component. + * Remember that the Camera "looks" towards its -z axis. + *

+ * + * @param viewMatrix The camera position and orientation provided as a rigid transform matrix. + */ + public void setModelMatrix(@NonNull @Size(min = 16) double[] viewMatrix) { + Asserts.assertMat4In(viewMatrix); + nSetModelMatrixFp64(getNativeObject(), viewMatrix); + } + /** * Sets the camera's view matrix. * @@ -516,6 +530,22 @@ public class Camera { return out; } + /** + * Retrieves the camera's model matrix. The model matrix encodes the camera position and + * orientation, or pose. + * + * @param out A 16-double array where the model matrix will be stored, or null in which + * case a new array is allocated. + * + * @return A 16-double array containing the camera's pose as a column-major matrix. + */ + @NonNull @Size(min = 16) + public double[] getModelMatrix(@Nullable @Size(min = 16) double[] out) { + out = Asserts.assertMat4(out); + nGetModelMatrixFp64(getNativeObject(), out); + return out; + } + /** * Retrieves the camera's view matrix. The view matrix is the inverse of the model matrix. * @@ -531,6 +561,21 @@ public class Camera { return out; } + /** + * Retrieves the camera's view matrix. The view matrix is the inverse of the model matrix. + * + * @param out A 16-double array where the model view will be stored, or null in which + * case a new array is allocated. + * + * @return A 16-double array containing the camera's view as a column-major matrix. + */ + @NonNull @Size(min = 16) + public double[] getViewMatrix(@Nullable @Size(min = 16) double[] out) { + out = Asserts.assertMat4(out); + nGetViewMatrixFp64(getNativeObject(), out); + return out; + } + /** * Retrieves the camera position in world space. * @@ -740,6 +785,7 @@ public class Camera { 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 nSetModelMatrix(long nativeCamera, float[] in); + private static native void nSetModelMatrixFp64(long nativeCamera, double[] in); 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 float nGetNear(long nativeCamera); private static native float nGetCullingFar(long nativeCamera); @@ -747,7 +793,9 @@ public class Camera { private static native void nGetCullingProjectionMatrix(long nativeCamera, double[] out); private static native void nGetScaling(long nativeCamera, double[] out); private static native void nGetModelMatrix(long nativeCamera, float[] out); + private static native void nGetModelMatrixFp64(long nativeCamera, double[] out); private static native void nGetViewMatrix(long nativeCamera, float[] out); + private static native void nGetViewMatrixFp64(long nativeCamera, double[] out); private static native void nGetPosition(long nativeCamera, float[] out); private static native void nGetLeftVector(long nativeCamera, float[] out); private static native void nGetUpVector(long nativeCamera, float[] out); diff --git a/filament/include/filament/Camera.h b/filament/include/filament/Camera.h index 1b012807bd..f05740205e 100644 --- a/filament/include/filament/Camera.h +++ b/filament/include/filament/Camera.h @@ -329,7 +329,8 @@ public: * * @warning \p view must be a rigid transform */ - void setModelMatrix(const math::mat4f& view) noexcept; + void setModelMatrix(const math::mat4& view) noexcept; + void setModelMatrix(const math::mat4f& view) noexcept; //!< \overload /** Sets the camera's view matrix * @@ -362,10 +363,10 @@ public: * @return The camera's pose in world space as a rigid transform. Parent transforms, if any, * are taken into account. */ - math::mat4f getModelMatrix() const noexcept; + math::mat4 getModelMatrix() const noexcept; //! Returns the camera's view matrix (inverse of the model matrix) - math::mat4f getViewMatrix() const noexcept; + math::mat4 getViewMatrix() const noexcept; //! Returns the camera's position in world space math::float3 getPosition() const noexcept; diff --git a/filament/src/Camera.cpp b/filament/src/Camera.cpp index ff7603cb1b..9be1a79f99 100644 --- a/filament/src/Camera.cpp +++ b/filament/src/Camera.cpp @@ -196,16 +196,26 @@ void UTILS_NOINLINE FCamera::setModelMatrix(const mat4f& modelMatrix) noexcept { transformManager.setTransform(transformManager.getInstance(mEntity), modelMatrix); } +void UTILS_NOINLINE FCamera::setModelMatrix(const mat4& modelMatrix) noexcept { + FTransformManager& transformManager = mEngine.getTransformManager(); + // TODO: eventually setTransform() will accept mat4 directly + transformManager.setTransform(transformManager.getInstance(mEntity), mat4f(modelMatrix)); +} + void FCamera::lookAt(const float3& eye, const float3& center, const float3& up) noexcept { - setModelMatrix(mat4f::lookAt(eye, center, up)); + FTransformManager& transformManager = mEngine.getTransformManager(); + // TODO: eventually setTransform() will accept mat4 + transformManager.setTransform(transformManager.getInstance(mEntity), + mat4f::lookAt(eye, center, up)); } -mat4f const& FCamera::getModelMatrix() const noexcept { +mat4 FCamera::getModelMatrix() const noexcept { FTransformManager const& transformManager = mEngine.getTransformManager(); - return transformManager.getWorldTransform(transformManager.getInstance(mEntity)); + // TODO: eventually getWorldTransform() will return mat4 directly + return (mat4)transformManager.getWorldTransform(transformManager.getInstance(mEntity)); } -mat4f UTILS_NOINLINE FCamera::getViewMatrix() const noexcept { +mat4 UTILS_NOINLINE FCamera::getViewMatrix() const noexcept { return FCamera::getViewMatrix(getModelMatrix()); } @@ -275,13 +285,13 @@ math::details::TMat44 inverseProjection(const math::details::TMat44& p) no } UTILS_NOINLINE -mat4f FCamera::getViewMatrix(mat4f const& model) noexcept { +mat4 FCamera::getViewMatrix(mat4 const& model) noexcept { // We can't use rigidTransformInverse here. The camera's model matrix might have scaling, which // would make it non-rigid. return inverse(model); } -Frustum FCamera::getFrustum(mat4 const& projection, mat4f const& viewMatrix) noexcept { +Frustum FCamera::getFrustum(mat4 const& projection, mat4 const& viewMatrix) noexcept { return Frustum(mat4f{ projection * viewMatrix }); } @@ -290,8 +300,8 @@ Frustum FCamera::getFrustum(mat4 const& projection, mat4f const& viewMatrix) noe CameraInfo::CameraInfo(FCamera const& camera) noexcept { projection = mat4f{ camera.getProjectionMatrix() }; cullingProjection = mat4f{ camera.getCullingProjectionMatrix() }; - model = camera.getModelMatrix(); - view = camera.getViewMatrix(); + model = mat4f{ camera.getModelMatrix() }; + view = mat4f{camera.getViewMatrix() }; zn = camera.getNear(); zf = camera.getCullingFar(); ev100 = Exposure::ev100(camera); @@ -300,12 +310,12 @@ CameraInfo::CameraInfo(FCamera const& camera) noexcept { d = std::max(zn, camera.getFocusDistance()); } -CameraInfo::CameraInfo(FCamera const& camera, const math::mat4f& worldOriginCamera) noexcept { - const mat4f modelMatrix{ worldOriginCamera * camera.getModelMatrix() }; +CameraInfo::CameraInfo(FCamera const& camera, const math::mat4& worldOriginCamera) noexcept { + const mat4 modelMatrix{ worldOriginCamera * camera.getModelMatrix() }; projection = mat4f{ camera.getProjectionMatrix() }; cullingProjection = mat4f{ camera.getCullingProjectionMatrix() }; - model = modelMatrix; - view = FCamera::getViewMatrix(model); + model = mat4f{ modelMatrix }; + view = mat4f{ FCamera::getViewMatrix(modelMatrix) }; zn = camera.getNear(); zf = camera.getCullingFar(); ev100 = Exposure::ev100(camera); @@ -313,7 +323,7 @@ CameraInfo::CameraInfo(FCamera const& camera, const math::mat4f& worldOriginCame A = f / camera.getAperture(); d = std::max(zn, camera.getFocusDistance()); worldOffset = camera.getPosition(); - worldOrigin = worldOriginCamera; + worldOrigin = mat4f{ worldOriginCamera }; } // ------------------------------------------------------------------------------------------------ @@ -383,6 +393,10 @@ float Camera::getCullingFar() const noexcept { return upcast(this)->getCullingFar(); } +void Camera::setModelMatrix(const mat4& modelMatrix) noexcept { + upcast(this)->setModelMatrix(modelMatrix); +} + void Camera::setModelMatrix(const mat4f& modelMatrix) noexcept { upcast(this)->setModelMatrix(modelMatrix); } @@ -395,11 +409,11 @@ void Camera::lookAt(const float3& eye, const float3& center) noexcept { upcast(this)->lookAt(eye, center, {0, 1, 0}); } -mat4f Camera::getModelMatrix() const noexcept { +mat4 Camera::getModelMatrix() const noexcept { return upcast(this)->getModelMatrix(); } -mat4f Camera::getViewMatrix() const noexcept { +mat4 Camera::getViewMatrix() const noexcept { return upcast(this)->getViewMatrix(); } diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index 6ea8144b67..aaeb2bfad0 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -47,7 +47,7 @@ FScene::FScene(FEngine& engine) : FScene::~FScene() noexcept = default; -void FScene::prepare(const mat4f& worldOriginTransform, bool shadowReceiversAreCasters) noexcept { +void FScene::prepare(const mat4& worldOriginTransform, bool shadowReceiversAreCasters) noexcept { // TODO: can we skip this in most cases? Since we rely on indices staying the same, // we could only skip, if nothing changed in the RCM. @@ -110,7 +110,9 @@ void FScene::prepare(const mat4f& worldOriginTransform, bool shadowReceiversAreC // get the world transform auto ti = tcm.getInstance(e); - const mat4f worldTransform = worldOriginTransform * tcm.getWorldTransform(ti); + // this is where we go from double to float for our transforms + // (in the future, getWorldTransform() will have a double version) + const mat4f worldTransform{ worldOriginTransform * tcm.getWorldTransform(ti) }; const bool reversedWindingOrder = det(worldTransform.upperLeft()) < 0; // don't even draw this object if it doesn't have a transform (which shouldn't happen diff --git a/filament/src/ShadowMap.cpp b/filament/src/ShadowMap.cpp index df67ce00e4..6e31de182f 100644 --- a/filament/src/ShadowMap.cpp +++ b/filament/src/ShadowMap.cpp @@ -453,7 +453,7 @@ void ShadowMap::computeShadowCameraDirectional( // The model matrix below is in fact inverted to get the view matrix and passed to the // shader as 'viewFromWorldMatrix', and is used in the VSM case to compute the depth metric. // (see depth_main.fs). Note that in the case of VSM, 'b' below is identity. - mCamera->setModelMatrix(FCamera::rigidTransformInverse(Mv * b)); + mCamera->setModelMatrix(mat4{ FCamera::rigidTransformInverse(Mv * b) }); mCamera->setCustomProjection(mat4(F * W * L * Mp), znear, zfar); // for the debug camera, we need to undo the world origin @@ -508,7 +508,7 @@ void ShadowMap::computeShadowCameraSpot(math::float3 const& position, math::floa // The model matrix below is in fact inverted to get the view matrix and passed to the // shader as 'viewFromWorldMatrix', and is used in the VSM case to compute the depth metric. // (see depth_main.fs). Note that in the case of VSM, 'b' below is identity. - mCamera->setModelMatrix(FCamera::rigidTransformInverse(Mv * b)); + mCamera->setModelMatrix(mat4{ FCamera::rigidTransformInverse(Mv * b) }); mCamera->setCustomProjection(mat4(Mp), nearPlane, farPlane); // for the debug camera, we need to undo the world origin diff --git a/filament/src/View.cpp b/filament/src/View.cpp index d77bcfd8e3..18d710f3d0 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -394,13 +394,13 @@ void FView::prepare(FEngine& engine, backend::DriverApi& driver, ArenaScope& are * The "world origin" could also be useful for other things, like keeping the origin * close to the camera position to improve fp precision in the shader for large scenes. */ - mat4f worldOriginScene; + mat4 worldOriginScene; FIndirectLight const* const ibl = scene->getIndirectLight(); if (ibl) { // the IBL transformation must be a rigid transform mat3f rotation{ scene->getIndirectLight()->getRotation() }; // for a rigid-body transform, the inverse is the transpose - worldOriginScene = mat4f{ transpose(rotation) }; + worldOriginScene = mat4{ transpose(rotation) }; } /* diff --git a/filament/src/details/Camera.h b/filament/src/details/Camera.h index ab69ec3938..500f18b255 100644 --- a/filament/src/details/Camera.h +++ b/filament/src/details/Camera.h @@ -89,16 +89,17 @@ public: float getCullingFar() const noexcept { return mFar; } // sets the camera's view matrix (must be a rigid transform) + void setModelMatrix(const math::mat4& modelMatrix) noexcept; void setModelMatrix(const math::mat4f& modelMatrix) noexcept; // sets the camera's view matrix void lookAt(const math::float3& eye, const math::float3& center, const math::float3& up = { 0, 1, 0 }) noexcept; // returns the view matrix - math::mat4f const& getModelMatrix() const noexcept; + math::mat4 getModelMatrix() const noexcept; // returns the inverse of the view matrix - math::mat4f getViewMatrix() const noexcept; + math::mat4 getViewMatrix() const noexcept; template static math::details::TMat44 rigidTransformInverse(math::details::TMat44 const& v) noexcept { @@ -111,7 +112,7 @@ public: return math::details::TMat44(rt, -t); } - math::float3 const& getPosition() const noexcept { + math::double3 const& getPosition() const noexcept { return getModelMatrix()[3].xyz; } @@ -182,8 +183,8 @@ public: return mEntity; } - static math::mat4f getViewMatrix(math::mat4f const& model) noexcept; - static Frustum getFrustum(math::mat4 const& projection, math::mat4f const& viewMatrix) noexcept; + static math::mat4 getViewMatrix(math::mat4 const& model) noexcept; + static Frustum getFrustum(math::mat4 const& projection, math::mat4 const& viewMatrix) noexcept; private: FEngine& mEngine; @@ -206,7 +207,7 @@ private: struct CameraInfo { CameraInfo() noexcept = default; explicit CameraInfo(FCamera const& camera) noexcept; - CameraInfo(FCamera const& camera, const math::mat4f& worldOriginCamera) noexcept; + CameraInfo(FCamera const& camera, const math::mat4& worldOriginCamera) noexcept; math::mat4f projection; // projection matrix for drawing (infinite zfar) math::mat4f cullingProjection; // projection matrix for culling diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index 33895af75c..9dd9deca11 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -84,7 +84,7 @@ public: ~FScene() noexcept; void terminate(FEngine& engine); - void prepare(const math::mat4f& worldOriginTransform, bool shadowReceiversAreCasters) noexcept; + void prepare(const math::mat4& worldOriginTransform, bool shadowReceiversAreCasters) noexcept; void prepareDynamicLights(const CameraInfo& camera, ArenaScope& arena, backend::Handle lightUbh) noexcept; diff --git a/ios/samples/hello-gltf/hello-gltf/CameraManipulator.cpp b/ios/samples/hello-gltf/hello-gltf/CameraManipulator.cpp index 9386134c5b..208b8942f7 100644 --- a/ios/samples/hello-gltf/hello-gltf/CameraManipulator.cpp +++ b/ios/samples/hello-gltf/hello-gltf/CameraManipulator.cpp @@ -118,7 +118,7 @@ void CameraManipulator::updateCameraTransform() { mat4 rotate_y = mat4::rotation(mRotation.y, double3(0, 1, 0)); mat4 translate = mat4::translation(mTranslation); mat4 view = translate * (rotate_y * rotate_x * rotate_z); - mCamera->setModelMatrix(mat4f(view)); + mCamera->setModelMatrix(view); if (mCameraChanged) { mCameraChanged(mCamera); } diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index febd79b244..5f42abd097 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -782,11 +782,11 @@ class_("Camera") }), allow_raw_pointers()) .function("getModelMatrix", EMBIND_LAMBDA(flatmat4, (Camera* self), { - return flatmat4 { self->getModelMatrix() }; + return flatmat4 { (filament::math::mat4f)self->getModelMatrix() }; }), allow_raw_pointers()) .function("getViewMatrix", EMBIND_LAMBDA(flatmat4, (Camera* self), { - return flatmat4 { self->getViewMatrix() }; + return flatmat4 { (filament::math::mat4f)self->getViewMatrix() }; }), allow_raw_pointers()) .function("getPosition", &Camera::getPosition)