Replies: 9 comments 4 replies
-
|
Hi, |
Beta Was this translation helpful? Give feedback.
-
|
I need this feature in order to be able to have clean views when I have a 3D reconstruction of a flat/house/home. For example, if I have a room seen from the top, I want to see only the floor, I don't want to see the walls because of the perspective effect. Thanks a lot. |
Beta Was this translation helpful? Give feedback.
-
|
Hi. Any update on this topic? Would be really great to have a camera with orthogonal projection. See for example the three.js OrthoraphicCamera object: https://threejs.org/docs/#api/en/cameras/OrthographicCamera Would be really great to have the same in Sceneform or a trick to make the camera orthographic. Thanks. |
Beta Was this translation helpful? Give feedback.
-
|
Hi, |
Beta Was this translation helpful? Give feedback.
-
|
Answer here: google/filament#4948 |
Beta Was this translation helpful? Give feedback.
-
|
If you are on a 3D only (no AR usage), try this: sceneView.getRenderer().getCamera().setProjection(Camera.Projection.ORTHO, double left, double right, double bottom, double top, double near, double far); |
Beta Was this translation helpful? Give feedback.
-
|
Thanks for your answer. I do : But it doesn't seem to work as it changes nothing to the 3D view. Any idea ? Thanks for your help. |
Beta Was this translation helpful? Give feedback.
-
|
Thanks. I just tried to execute the code at each frame. Tried at the beginning or and of the onUpdate() loop but changes nothing unfortunately. Any clue how to solve this problem ? Thanks. |
Beta Was this translation helpful? Give feedback.
-
|
With the help from AI (opus 4.5), i managed to get it working in a hacky way. Android. Java 11. Below are AI generated solution, worked for me. public class EngineerX3dModelFragment extends Fragment {
private CameraProjectionHelper cameraProjectionHelper;
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
cameraProjectionHelper = new CameraProjectionHelper(binding.sceneView);
}
private void setupSnapshotControls() {
// Toggle Orthographic/Perspective projection
binding.btnToggleProjection.setOnClickListener(v -> {
boolean isOrtho = cameraProjectionHelper.toggle();
binding.btnToggleProjection.setText(isOrtho ? "📐 Persp" : "📐 Ortho");
log("Projection: " + (isOrtho ? "ORTHOGRAPHIC" : "PERSPECTIVE"));
});
}import android.util.Log;
import com.google.ar.sceneform.FrameTime;
import com.google.ar.sceneform.Scene;
import com.google.ar.sceneform.SceneView;
import com.google.ar.sceneform.math.Matrix;
import java.lang.reflect.Method;
/**
* Camera Projection Helper - Switches between Perspective and Orthographic.
*
* Uses reflection to access Sceneform Camera's internal methods:
* - setProjectionMatrix(Matrix) for setting orthographic projection
* - refreshProjectionMatrix() for restoring perspective projection
*/
public class CameraProjectionHelper {
private static final String TAG = "CamProj";
private final SceneView sceneView;
private boolean isOrthographic = false;
// Orthographic settings
private double orthoHeight = 2.5;
private final Scene.OnUpdateListener updateListener;
private int frameCount = 0;
// Reflection cache
private Method setProjectionMatrixMethod = null;
private Method refreshProjectionMatrixMethod = null;
private boolean reflectionFailed = false;
public CameraProjectionHelper(SceneView sceneView) {
this.sceneView = sceneView;
this.updateListener = this::onSceneUpdate;
sceneView.getScene().addOnUpdateListener(this.updateListener);
initReflection();
Log.d(TAG, "Initialized. setProjectionMatrix: " + (setProjectionMatrixMethod != null) +
", refreshProjectionMatrix: " + (refreshProjectionMatrixMethod != null));
}
private void initReflection() {
try {
Class<?> cameraClass = com.google.ar.sceneform.Camera.class;
for (Method m : cameraClass.getDeclaredMethods()) {
if (m.getName().equals("setProjectionMatrix") && m.getParameterCount() == 1) {
m.setAccessible(true);
setProjectionMatrixMethod = m;
Log.d(TAG, "Found setProjectionMatrix");
}
if (m.getName().equals("refreshProjectionMatrix") && m.getParameterCount() == 0) {
m.setAccessible(true);
refreshProjectionMatrixMethod = m;
Log.d(TAG, "Found refreshProjectionMatrix");
}
}
} catch (Exception e) {
Log.e(TAG, "Reflection init failed: " + e.getMessage());
reflectionFailed = true;
}
}
public boolean toggle() {
isOrthographic = !isOrthographic;
Log.d(TAG, "=== TOGGLE === Now: " + (isOrthographic ? "ORTHO" : "PERSP"));
if (isOrthographic) {
applyOrthoProjection();
} else {
restorePerspectiveProjection();
}
return isOrthographic;
}
public void setOrthographicMode(boolean enable) {
if (this.isOrthographic != enable) {
this.isOrthographic = enable;
if (enable) {
applyOrthoProjection();
} else {
restorePerspectiveProjection();
}
}
}
public boolean isOrthographic() {
return isOrthographic;
}
public void setOrthoHeight(double height) {
this.orthoHeight = height;
if (isOrthographic) applyOrthoProjection();
}
private void onSceneUpdate(FrameTime frameTime) {
frameCount++;
// Keep enforcing ortho projection every frame (Sceneform fights us)
if (isOrthographic) {
applyOrthoProjection();
}
// Debug every 120 frames
if (frameCount % 120 == 0) {
debugLogProjection();
}
}
private void applyOrthoProjection() {
if (reflectionFailed || setProjectionMatrixMethod == null) return;
com.google.ar.sceneform.Camera camera = sceneView.getScene().getCamera();
if (camera == null) return;
int width = sceneView.getWidth();
int height = sceneView.getHeight();
if (width <= 0 || height <= 0) return;
double aspect = (double) width / (double) height;
double near = camera.getNearClipPlane();
double far = camera.getFarClipPlane();
double halfH = orthoHeight / 2.0;
double halfW = halfH * aspect;
float[] orthoData = buildOrthoMatrix(-halfW, halfW, -halfH, halfH, near, far);
try {
Matrix orthoMatrix = new Matrix();
orthoMatrix.set(orthoData);
setProjectionMatrixMethod.invoke(camera, orthoMatrix);
if (frameCount % 60 == 0) {
Log.d(TAG, "[ORTHO] Applied. halfW=" + String.format("%.2f", halfW) +
" halfH=" + String.format("%.2f", halfH));
}
} catch (Exception e) {
Log.e(TAG, "setProjectionMatrix failed: " + e.getMessage());
}
}
private void restorePerspectiveProjection() {
com.google.ar.sceneform.Camera camera = sceneView.getScene().getCamera();
if (camera == null) return;
// Method 1: Call refreshProjectionMatrix via reflection
if (refreshProjectionMatrixMethod != null) {
try {
refreshProjectionMatrixMethod.invoke(camera);
Log.d(TAG, "[PERSP] Restored via refreshProjectionMatrix");
return;
} catch (Exception e) {
Log.e(TAG, "refreshProjectionMatrix failed: " + e.getMessage());
}
}
// Method 2: Fallback - manually build perspective matrix
Log.d(TAG, "[PERSP] Using fallback perspective matrix");
int width = sceneView.getWidth();
int height = sceneView.getHeight();
if (width <= 0 || height <= 0) return;
float aspect = (float) width / (float) height;
float fov = camera.getVerticalFovDegrees();
float near = camera.getNearClipPlane();
float far = camera.getFarClipPlane();
float[] perspData = buildPerspectiveMatrix(fov, aspect, near, far);
try {
Matrix perspMatrix = new Matrix();
perspMatrix.set(perspData);
setProjectionMatrixMethod.invoke(camera, perspMatrix);
Log.d(TAG, "[PERSP] Applied fallback. fov=" + fov + " aspect=" + aspect);
} catch (Exception e) {
Log.e(TAG, "Fallback perspective failed: " + e.getMessage());
}
}
/**
* Build orthographic projection matrix (column-major).
*/
private float[] buildOrthoMatrix(double left, double right, double bottom, double top, double near, double far) {
double rml = right - left;
double tmb = top - bottom;
double fmn = far - near;
float[] m = new float[16];
m[0] = (float)(2.0 / rml);
m[5] = (float)(2.0 / tmb);
m[10] = (float)(-2.0 / fmn);
m[12] = (float)(-(right + left) / rml);
m[13] = (float)(-(top + bottom) / tmb);
m[14] = (float)(-(far + near) / fmn);
m[15] = 1.0f;
return m;
}
/**
* Build perspective projection matrix (column-major).
* Matches Sceneform's setPerspective implementation.
*/
private float[] buildPerspectiveMatrix(float verticalFovDegrees, float aspect, float near, float far) {
double fovRadians = Math.toRadians(verticalFovDegrees);
float top = (float)(Math.tan(fovRadians * 0.5) * near);
float bottom = -top;
float right = top * aspect;
float left = -right;
float reciprocalWidth = 1.0f / (right - left);
float reciprocalHeight = 1.0f / (top - bottom);
float reciprocalDepth = 1.0f / (far - near);
float[] m = new float[16];
m[0] = 2.0f * near * reciprocalWidth;
m[5] = 2.0f * near * reciprocalHeight;
m[8] = (right + left) * reciprocalWidth;
m[9] = (top + bottom) * reciprocalHeight;
m[10] = -(far + near) * reciprocalDepth;
m[11] = -1.0f;
m[14] = -2.0f * far * near * reciprocalDepth;
return m;
}
private void debugLogProjection() {
try {
com.google.android.filament.Camera filamentCamera =
sceneView.getRenderer().getFilamentView().getCamera();
if (filamentCamera != null) {
double[] proj = filamentCamera.getProjectionMatrix(null);
Log.d(TAG, "[DEBUG] Frame " + frameCount +
" | Mode: " + (isOrthographic ? "ORTHO" : "PERSP") +
" | proj[0]=" + String.format("%.4f", proj[0]) +
" | proj[5]=" + String.format("%.4f", proj[5]) +
" | proj[10]=" + String.format("%.4f", proj[10]));
}
} catch (Exception e) {
// Ignore
}
}
public void destroy() {
try {
sceneView.getScene().removeOnUpdateListener(this.updateListener);
} catch (Exception e) {
Log.e(TAG, "Error removing listener", e);
}
}
}Technical Report: Implementing Orthographic Projection in Sceneform1. ObjectiveEnable runtime switching between Perspective and Orthographic camera projection modes within a Sceneform (Android) application. 2. Technical Context & Prerequisites
3. The Journey: Traps, Failures, and DiagnosesAttempt 1: Direct Filament API (The "Obvious" Way)Approach: Access the underlying Filament Camera and set the projection directly. sceneView.getRenderer().getFilamentView().getCamera().setProjection(Projection.ORTHO, ...);
Attempt 2: The
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi,
The projection is a perspective projection. I wonder how to change the camera projection to othographic/orthogonal.
Thanks for your help.
Beta Was this translation helpful? Give feedback.
All reactions