3D camera perspective doesn't work with large volumes (i.e the earth)

6 views (last 30 days)
I'd like to be able to force a realistic perspective when plotting large surface volumes. My issue is if I plot something like a satellite (represented by a small sphere in the example), and then plot something MUCH larger (i.e, the earth), then that small satellite ceases to become something I can view easily with the 3D camera toolbar.
In my example, if you plot the small sphere on its own, it's easy rotate around, zoom in and out etc. Then if you plot the earth, the axes will zoom out to fit both, but then if I place the camera back next to the satellite, it becomes nearly impossible to see unless the cameraviewangle is tiny, or else the camera zoom breaks entirely.
If I reset the axes to be centered around the satellite, (and turn clipping off) I'm still unable to get camera settings to make the earth not completely fill the screen, and behave more as if I was floating next to it. Additionally, the camera zoom seems to get stuck entirely, and is unable to be changed when this happens.
I would like something like the below picture (left), but with the satellite in full fidelity right next to the camera. At the moment, the satellite still seems far away, even though the camera position is right next to it. And if I do zoom in far enough, the satellite has some very odd rendering happening to it (while the earth takes up the entire screen).
In general, I suppose my problem boils down to something similar like "can I place a camera on the surface of the earth and have it look like I'm standing on it?". I feel like I just need to shrink the camera like crazy in order to get the results I'm interested in. Or maybe I just scale up the satellite by 1000x.
Thanks
close all; figure; hold on; grid on; axis equal
clearvars;
[X,Y,Z] = sphere;
radii = 6378137; % In meters
the_earth = {X.*radii, Y.*radii, Z.*radii};
surf(the_earth{1},the_earth{2},the_earth{3});
radii = 100;
shift = 8e7;
object = {shift+X.*radii, shift+Y.*radii, shift+Z.*radii};
surf(object{1},object{2},object{3});
plot3([0, shift],[0,shift],[0,shift],'r-');
set(gca,'cameratarget',[shift, shift, shift]);
set(gca,'cameraposition',[shift+300, shift, shift]);
% axis([8e7-300, 8e7+300, 8e7-300, 8e7+300, 8e7-300, 8e7+300]);
% set(gca,'clipping','off');
cameratoolbar('show');

Answers (1)

Stephen23
Stephen23 on 23 Aug 2025
It has a strong smell of numeric precision issues, I suspect:
  1. Depth precision / near–far plane: when one axes contains numbers as large as 10^8 (Earth center–satellite distance in meters), MATLAB sets very distant near/far clip planes. The Z-buffer loses precision and “zoom” (which changes the camera view angle) becomes useless near your small object.
  2. One axes, wildly different scales: a 100 m sphere drawn in the same axes as a 6,378 km sphere will be either invisible, aliased, or clipped unless you normalize/decouple scales.
You could re-center and rescale the scene (best single-axes fix). This renders everything in a local coordinate frame near the camera (so magnitudes are up to 10^4, not 10^8). Keep the physical ratios, just shift/scale the numbers you send to surf.
fh = figure('Color','w');
ax = axes(fh, 'Projection','perspective');
hold(ax,'on');
grid(ax,'on')
axis(ax,'vis3d');
daspect(ax,[1 1 1])
% Earth (meters)
Re = 6378137;
[Xe,Ye,Ze] = sphere(256);
Xe = Xe*Re;
Ye = Ye*Re;
Ze = Ze*Re;
% Satellite 80,000 km from origin, radius 100 m
alt = 8e7;
Rs = 100;
[Xs,Ys,Zs] = sphere(64);
Xs = Xs*Rs + alt;
Ys = Ys*Rs + alt;
Zs = Zs*Rs + alt;
% Put the camera near the satellite and translate world
t = [alt,alt,alt];
% translate so satellite is near origin
s = 1e-3; % scale meters -> kilometers (keeps numbers modest)
Xe = (Xe - t(1))*s; Ye = (Ye - t(2))*s; Ze = (Ze - t(3))*s;
Xs = (Xs - t(1))*s; Ys = (Ys - t(2))*s; Zs = (Zs - t(3))*s;
se = surf(ax,Xe,Ye,Ze, 'EdgeColor','none','FaceAlpha',1);
colormap(ax, parula);
lighting(ax, 'gouraud')
camlight(ax, 'headlight')
ss = surf(ax,Xs,Ys,Zs, 'EdgeColor','none','FaceColor',[.8 .1 .1]);
% Camera near the satellite (now around the origin, in km)
camtarget(ax,[0,0,0]);
campos(ax,[0.3,0,0]);
camup(ax,[0,0,1]);
camva(ax,40);
% Keep MATLAB from auto-changing the FOV:
set(ax,'CameraViewAngleMode','manual');
% Keep the axes from expanding to Earth scale again:
xlim(ax,[-2,2]);
ylim(ax,[-2,2]);
zlim(ax,[-2,2]); % a small box around the sat (km)
Anotehr approach:
  • make the satellite occupy lots of screen pixels (tight limits + reasonable FOV),
  • keep the Earth at true scale and position, but don’t try to force it all into view,
  • use high tessellation + Phong lighting to avoid faceting.
fh = figure('Color','w', 'Renderer','opengl');
% ------------ camera-centric transform ------------
t = [alt,alt,alt]; % translate world so satellite center -> origin
s = 1e-3; % meters -> kilometers (keeps numbers modest)
% ------------ meshes (high enough for smoothness) ------------
[Xe,Ye,Ze] = sphere(1024); % Earth resolution
[Xs,Ys,Zs] = sphere(256); % Satellite resolution
% Earth in background (true scale, translated+scaled)
Xe = (Xe*Re - t(1))*s;
Ye = (Ye*Re - t(2))*s;
Ze = (Ze*Re - t(3))*s;
% Satellite near camera (true scale, translated+scaled)
Xs = (Xs*Rs + alt - t(1))*s;
Ys = (Ys*Rs + alt - t(2))*s;
Zs = (Zs*Rs + alt - t(3))*s;
ax = axes(fh, 'Projection','perspective');
hold(ax,'on');
grid(ax,'on')
axis(ax,'vis3d'); daspect(ax,[1 1 1])
se = surf(ax, Xe,Ye,Ze, 'EdgeColor','none', 'FaceColor',[0.75,0.85,1.0], 'SpecularStrength',0.2, 'SpecularExponent',10);
ss = surf(ax, Xs,Ys,Zs, 'EdgeColor','none', 'FaceColor',[0.85,0.20,0.2], 'SpecularStrength',0.6, 'SpecularExponent',40);
% ------------ camera: close to satellite ------------
% Satellite is now at [0 0 0] (km). Place camera outside it.
camproj(ax,'perspective');
camtarget(ax,[0,0,0]);
campos(ax,[0.6,0,0]); % ~600 m away (since units are km)
camup(ax,[0,0,1]);
camva(ax,30); % modest FOV; keeps sat large on screen
set(ax,'CameraViewAngleMode','manual');
% Keep the numeric range tight around the satellite so it fills pixels
pad = 5*Rs*s; % a few satellite radii (km)
xlim(ax,[-pad,pad]);
ylim(ax,[-pad,pad]);
zlim(ax,[-pad,pad]);
% ------------ make it look 3-D, not faceted ------------
shading(ax,'interp');
lighting(ax,'phong'); % per-pixel normals (smooth highlights)
%delete(findobj(ax,'Type','light'));
light(ax,'Position',[-3*Re*s,-2*Re*s,+1*Re*s],'Style','infinite'); % “sun”
light(ax,'Position',[+3*Re*s,+2*Re*s,-1*Re*s],'Style','infinite','Color',[.25 .25 .3]);
Other things to consider:
  • Avoid axis equal while MATLAB is deciding the overall data range; it can expand limits to include the whole Earth and force terrible near/far planes. Use daspect([1,1,1]) and then set explicit xlim/ylim/zlim for the local view.
  • Prefer camzoom/camva over the 2-D zoom tool for 3-D scenes.
  • Make sure you’re on hardware OpenGL: opengl hardware. (Software can exaggerate Z-precision issues.)
  • There’s no public property to set near/far planes directly; controlling magnitudes, limits, and camera view angle is the way to manage precision.

Categories

Find more on Reference Applications in Help Center and File Exchange

Tags

Products


Release

R2016b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!