One room, thirteen kinds of 3D
Every 3D product we could build from a single iPhone LiDAR scan, side by side (2026-10-14)
- 524 video frames (1920×1440) + the camera position and orientation the iPhone recorded for each (real metres)
- 44 high-resolution photos (2016×1512) + camera poses
- 12,132,505 LiDAR points (with colour)
- The app's own mesh, 58,319 triangles
Open 3D tour (inside · dollhouse · floor plan) →
End-to-end pipeline
flowchart LR A["iPhone + Modelar app
~11 min walking scan"] --> B["One 544 MB archive
Windows D: → arcAI server"] B --> C["Format decoding
Cloud.log · Mesh.scn · camera json"] C --> M1["① App mesh
extracted as is"] C --> M2["② LiDAR points
padding removed"] C --> P["568 camera poses
ARKit → RS · COLMAP conventions"] P --> M3["③ RealityScan
locked poses → mesh → LiDAR colours"] P --> M4["④ Gaussian splat
gsplat 30k steps"] M2 --> M4 M2 --> M5["⑤ Splat + LiDAR depth
+ pose refinement"] P --> M5 M2 --> M6["⑥ LiDAR mesh
Open3D Poisson"] P --> M7["⑦ WebODM (ODM)
iPhone poses as fake GPS"] P --> M8["⑧ 2D Gaussian splat
surface-aligned"] M2 --> M8 C --> M9["⑨ VGGT (AI)
depth AI · pose iPhone · scale LiDAR"] P --> M9 M2 --> M9 M9 --> M10["⑩ Splat from VGGT points
+ depth + pose refinement"] M2 --> M11["⑪ TSDF mesh from 1,572 LiDAR depth frames + photo texture
texrecon area (ODM texture engine)"] P --> M11 M11 --> M12["⑫ ⑪ + holes filled with VGGT depth only
(where LiDAR missed, e.g. black objects)"] M9 --> M12 M11 --> M13["⑬ ⑪ as 3D Tiles
(original 4096 textures up close)"] M1 & M2 & M3 & M4 & M5 & M6 & M7 & M8 & M9 & M10 & M11 & M12 & M13 --> W["Web compression
GLB · PLY · splat · 3D Tiles"] W --> X["Cloudflare Pages
this page"] classDef app fill:#fef3c7,stroke:#f59e0b,stroke-width:3px,color:#78350f classDef lidar fill:#d1fae5,stroke:#10b981,stroke-width:3px,color:#064e3b classDef rs fill:#dbeafe,stroke:#3b82f6,stroke-width:3px,color:#1e3a8a classDef gs fill:#fce7f3,stroke:#ec4899,stroke-width:3px,color:#831843 classDef gsd fill:#ede9fe,stroke:#8b5cf6,stroke-width:3px,color:#4c1d95 classDef lmesh fill:#ccfbf1,stroke:#14b8a6,stroke-width:3px,color:#134e4a classDef odm fill:#fee2e2,stroke:#ef4444,stroke-width:3px,color:#7f1d1d classDef gs2d fill:#e0f2fe,stroke:#0ea5e9,stroke-width:3px,color:#0c4a6e classDef vggt fill:#ecfccb,stroke:#84cc16,stroke-width:3px,color:#365314 classDef gsf fill:#fae8ff,stroke:#d946ef,stroke-width:3px,color:#701a75 classDef best fill:#ffe4e6,stroke:#e11d48,stroke-width:3px,color:#881337 classDef fill fill:#ccfbf1,stroke:#0d9488,stroke-width:3px,color:#134e4a classDef tiles fill:#e0f2fe,stroke:#0284c7,stroke-width:3px,color:#0c4a6e class M1 app class M2 lidar class M3 rs class M4 gs class M5 gsd class M6 lmesh class M7 odm class M8 gs2d class M9 vggt class M10 gsf class M11 best class M12 fill class M13 tiles
Compare in 3D
Pick a tab to load that method. Drag to rotate, wheel to zoom, right-drag to pan. Two fingers on a phone. Splats (④⑤⑧) look most photographic near where photos were taken, so they open at the 'Capture viewpoint'.
Each method in detail
① App mesh
How it was made: The mesh Modelar builds live during capture, extracted as is. Its Mesh.scn is an Apple SceneKit archive no other tool reads, so its structure was decoded and the 94 parts merged (read_modelar_mesh.py).
- Zero extra processing; exactly what the app shows
- Smallest file (1.2 MB)
- Overall shape (room size, wall positions) is right
- Only 58k triangles; built on a 5 cm voxel grid
- Colour only at vertices, so objects blur
- No official way to export it; had to be decoded
What to look at
- Bird's-eye: can you read the room outline and furniture layout
- How furniture turns into blobs
- Treat it as the baseline: what the app alone gives you
Numbers: 33,363 vertices / 58,319 triangles
② LiDAR points
How it was made: The app's LiDAR point file Cloud.log was decoded byte by byte (no public spec) and verified by matching the block point total to the app's own count (21,029,180). 8,896,675 of them (42.3%) were (0,0,0) padding and were removed. 1,300,000 random points are shown on the web.
- Measured by the sensor, so scale is real metres from the start
- Every other result here shares its coordinates (RS mesh within a 1.5 cm median)
- The most trustworthy source for measurements and floor plans
- Points, so gaps show up close
- Range (5 m app setting) and resolution blur small object edges
- Glass, mirrors and black surfaces drop or scatter points
What to look at
- Hide ceiling + bird's-eye: do walls come out straight like a floor plan
- Holes or stray points at monitors and windows
- Go inside and look at point spacing up close
Numbers: 12,132,505 points (web 1,300,000, 19.5 MB)
③ RealityScan mesh
How it was made: 568 images went into RealityScan 2.2 for Linux on arcAI. Photos alone aligned only 65 images into one component, so the iPhone poses were converted to RS prior files (XMP) and locked. The iPhone rotation convention (column-major, camera-to-world, ARKit axes) was confirmed against the 65 images RS had aligned (median rotation difference 1.68°). Then mesh (Normal) → simplify to 1.5M triangles. RS exported only white, so each vertex takes the colour of its four nearest LiDAR points (97.7% of vertices).
- The densest surface; edges and small objects keep their shape
- A mesh, so measuring and exporting to other tools is easy
- Same frame as LiDAR, 1.5 cm median difference
- Textureless white walls and floors are weak for photogrammetry: bumpy or missing
- Texture export fails in the Linux command line ('Feature not implemented') and vertex colours came out white, so LiDAR colours were borrowed: text and patterns blur
- Poses were locked, so iPhone pose error stays in
What to look at
- Small objects on the desk such as the keyboard and monitor stand (compare with ①)
- Bumps on white walls and ceiling (compare with ② LiDAR)
- Areas past the door: LiDAR stops at 5 m, photos see further
Numbers: 568/568 aligned · reprojection 1.03 px · align 60 s · mesh 192 s · colour 74 s · 1,499,250 triangles · web 6.35 MB
④ Gaussian splat
How it was made: Gaussian splatting represents the scene as millions of small translucent colour blobs trained to reproduce the photos. The iPhone poses were used directly (no photo alignment) and training started from 388,339 LiDAR points (2 cm grid). The gsplat 1.5.3 engine has no prebuilt package for this GPU, so it was built with CUDA 13. 30k steps; one image in eight was held out for evaluation. For the web it was reduced to .splat, base colour only.
- Looks like the photos, including shine, translucency and thin things (cables)
- You can truly walk around inside
- Smooth even where photogrammetry struggles, such as white walls
- Blobs, not surfaces, so hard to use for measuring or plans
- Off the captured viewpoints you see blurry floaters
- iPhone pose error becomes blur
- Needs a GPU and tens of minutes
What to look at
- Inside view: do monitor glow, chair mesh and cables look photographic
- Floaters when you turn toward the ceiling or corners
- Switch with ③ at the same spot: looks versus shape
Numbers: PSNR 15.43 · SSIM 0.76 · 2,723,832 gaussians · 35 min · web 48.0 MB
⑤ Splat + LiDAR depth
How it was made: Same data as ④ with two additions. (1) LiDAR points visible in each photo are linked so training also matches the distance to those points (depth supervision). (2) The iPhone camera poses are refined during training. Same idea as DN-Splatter, which research uses for indoor iPhone LiDAR. Same 30k steps and held-out images.
- Walls and floor should stick to LiDAR, reducing floaters
- Pose refinement should reduce blur
- Directly comparable with ④ (same held-out images)
- Still blobs, not for measuring
- Where LiDAR is wrong (glass, black surfaces) it pulls toward wrong depths
- One more setting, slightly slower
What to look at
- Toggle with ④ at the same spot: fewer floaters on white walls and floor
- Sharper corners and door frames
- The PSNR difference in the table below
Numbers: PSNR 18.33 · SSIM 0.8 · 3,385,261 gaussians · 55 min · web 48.0 MB
⑥ LiDAR mesh
How it was made: ② LiDAR points were thinned to a 1 cm grid (1.73M points), normals were estimated and flipped toward the nearest iPhone camera, and Open3D Poisson reconstruction wrapped a smooth surface. Fake surfaces in sparse areas (lowest 3% density) were removed and the mesh reduced to 1M triangles. Colours come from the LiDAR points.
- Surfaces from LiDAR alone, no photogrammetry: white walls come out straight and smooth
- Dimensions are LiDAR's, ready for plans and areas (total area 94.7 m²)
- Fast (CPU 2.7 min)
- Poisson tends to bulge surfaces to close holes, so phantom surfaces can appear past the door or behind furniture
- Small items (cables, pens) are below LiDAR resolution and blur
- Colour from points, blurrier than photos
What to look at
- Same spot as ③ RS: ragged white walls in RS versus straight here?
- Bulged phantom surfaces behind furniture or past the door
- Toggle with ② points to see how well the surface follows them
Numbers: 1,000,000 triangles · 2.7 min (CPU) · web 4.5 MB
⑦ WebODM
How it was made: The WebODM engine already on arcAI (ODM 3.8.3) processed the same 568 images. ODM only takes camera positions as GPS, so the iPhone poses were written into image copies as GPS around an arbitrary origin (lat 1°, lon 1°, unrelated to the real location) and run with --force-gps at 5 cm. No orthophoto indoors.
- Free and open source, mesh and texture from one command
- Textures come straight from the photos (what RS on Linux could not do)
- Same engine as the drone run, easy to compare
- CPU only, slow
- Same photogrammetry limits as RS indoors on white walls
- Needs the fake-GPS workaround for the iPhone poses
What to look at
- Compare shape with ③ RS at the same spot
- Are text and patterns sharper than RS thanks to textures
- Holes and sheets on white walls
Numbers: 568장 중 304장 정렬 · 텍스처 메시 81만 삼각형(149 조각) · 라이다까지 중앙 2.2 cm (90% 8.6 cm) · 60 min · web 12.5 MB
⑧ 2D Gaussian
How it was made: 2D Gaussian splatting (2DGS) trains flat discs that stick to surfaces. Run with gsplat's 2DGS trainer on the same data and steps (30k) as ④.
- Discs stick to surfaces, so shape is crisper than ④ (per the literature)
- Fewer floaters on white walls and floors
- Slightly less photographic than ④
- The web viewer is not 2DGS-specific, so it is approximated as regular splats
What to look at
- Toggle with ④: are walls and floor flatter
- Are corners sharper
Numbers: PSNR 18.06 · SSIM 0.79 · 66 min · web 48.0 MB
⑨ VGGT (AI)
How it was made: VGGT (Meta, 2025) estimates camera poses and depth from a set of photos in one pass. Only about 16 photos fit the 16 GB GPU, so two variants were tried. (a) Chunk stitching: 22 overlapping 16-frame chunks, with the AI poses fitted to the iPhone poses; six chunks off by more than 15 cm or 15° were dropped. (b) Fill (this tab): the AI poses are ignored and each photo is placed with depth from VGGT, position from the iPhone, scale from LiDAR. LiDAR points projected into each photo give the VGGT-to-LiDAR depth ratio; photos where the ratio varies by more than 15% are dropped (343 of 524 used). Same idea as MapAnything and Prompt Depth Anything from the research: photos + known poses + LiDAR together.
- Sharper objects than LiDAR, at photo resolution
- Fast: whole room in 5 min 14 s (about 10 s per chunk)
- Depth itself comes from photos alone; works for phones and cameras without LiDAR
- On its own it has no metric scale and cannot join chunks; the iPhone poses and LiDAR did that here
- Photos per pass are limited by GPU memory
- 35% of photos (181) disagreed with LiDAR and were dropped; AI pose estimation failed for 27% of chunks and entirely with spread-out frames (99 cm, 127°)
What to look at
- Objects, text and colour are sharper than ② LiDAR because depth is estimated at photo resolution
- Are the gaps from chunk stitching filled (LiDAR surface covered 60.0% → 84.7%)
- Toggle with ② LiDAR: does it sit in the same place (1.1 cm median)
Numbers: 채움 방식: 524장 중 343장 · 라이다 표면 84.7% 덮음 · 라이다까지 중앙 1.1 cm (90% 4.2 cm) · 5분 14초 / 묶음 방식: 60.0% · 3.6 cm (90% 19.0 cm)
⑩ Splat + VGGT
How it was made: An experiment from the research (VGGT-X: 'splats need a good starting point'). Same training as ⑤ (depth supervision + pose refinement, 30k steps), but the starting points and depth reference are ⑨'s VGGT fill points (photo resolution, LiDAR-scaled, 1 cm grid, 1.05M points) instead of LiDAR. That is the only change, so it compares directly with ⑤.
- Dense, photo-resolution starting points settle small objects early
- Combines VGGT's sharpness with splats' photographic look
- Sparse start where VGGT dropped photos (35%)
- Training time similar to ⑤
What to look at
- Toggle with ⑤ at the same 'Capture viewpoint'
- Small objects and text on the desk
- Result: ⑤ scores slightly higher on held-out photos (PSNR 18.3 vs 17.8). Here VGGT starting points did not beat LiDAR ones; likely because VGGT points are missing where 35% of photos were dropped and its depth reference is noisier (our reading)
Numbers: PSNR 17.82 · SSIM 0.79 · 58 min · web 48.0 MB
⑪ LiDAR mesh + photos
How it was made: The strengths of the ten methods combined. Shape: 1,572 LiDAR depth frames decoded from inside the Modelar export and fused with TSDF (improved 2026-10-15. The first version used ⑥'s point-based Poisson mesh, which invented surfaces in empty space: floating fragments and a phantom slab. TSDF also records 'this far was empty', so such surfaces do not form), camera poses from the iPhone record (within 1.68° and 2 cm of RS), and photo texturing by the texture engine inside ⑦ WebODM (texrecon), run on its own. For each triangle it picks the view among 568 photos that saw it largest (closest and most head-on: the 'area' rule, which matched the real photos better than the default 'sharpest view' rule), copies those pixels into texture atlases, rejects inconsistent colours (reflections, moved objects) and evens out brightness at seams. A camera file (.cam) was written for every photo. On the web, 'Walk mode' lets you walk at eye height.
- LiDAR shape, so dimensions are right and walls straight
- Photo-resolution colour: text and patterns show (a texture, not vertex colours or points)
- Fast: texturing in 7 min
- A single mesh file, ready for game engines and other tools
- Where LiDAR reads poorly (black desk surfaces) the surface is thin or missing (97.7% screen coverage)
- Objects smaller than LiDAR resolution (cables, pens) have no shape, only a painted image
- Lighting and shadows are baked into the photos (no relighting like a game)
- Textures were reduced for the web size limit
What to look at
- Walk around in 'Walk mode' and read the desk and shelf labels
- Toggle with ⑥ LiDAR mesh (blurry colour): same shape, photos added
- Toggle with ⑦ WebODM: are the walls straighter
Numbers: 1,390,645 triangles · texture atlases 94 · 7 min · web 19.0 MB · photo match PSNR 15.76 → 17.55 (sharp photos 16.18)
⑫ ⑪ + holes filled
How it was made: ⑪ with only the holes filled. Black cabinets, the black desk and speakers return too little light to the LiDAR, so their depth is missing and the mesh has holes. Per-photo depth from ⑨ VGGT (an AI that estimates depth from photos), scaled with LiDAR, was saved; then ⑪'s mesh was ray-cast from each view and only pixels where the mesh is missing or an object sits more than 8 cm in front were added to the same TSDF (276 views). Mixing VGGT in where LiDAR already measured creates double surfaces (in a 2026-10-15 test the score fell 15.76 → 14.80). Texturing is the same as ⑪.
- Screen coverage 97.7% → 99.2%: most holes at black objects are filled
- Where LiDAR measured, shape and dimensions are unchanged from ⑪
- Filled areas use AI-estimated depth, so they are less accurate; ragged edges on boxes
- Photo match is slightly lower (17.55 → 17.27): the newly filled areas (black objects) are hard to match and now count
What to look at
- Toggle with ⑪ around the black cabinet, under the desk and the speakers
- Whether the ragged box edges bother you
Numbers: 1,484,931 triangles · texture atlases 118 · 13 min · web 21.7 MB · photo match PSNR 17.27 · coverage 99.2%
⑬ ⑪ as 3D Tiles (original resolution)
How it was made: ⑪'s mesh and texture split into map-like tiles. ⑪'s web file reduced textures to 2048 px for the 25 MiB limit; here 4 regions first show with small textures (512 px), and moving closer swaps a region for its 94 pieces with original 4096 px textures. The pieces texrecon already split per atlas are used directly as tiles (tiles_from_pieces.py, first built for the pool scan).
- Text and patterns are sharper than ⑪ up close
- Small initial download
- Brief flicker as tiles swap
- Same shape and seams as ⑪
What to look at
- Use 'Inside the room' and toggle with ⑪ on the desk and shelf labels
Numbers: regions 4 · pieces 94 · files 98 · total 39.6 MB
At a glance
| Method | Extra processing | Appearance | Measuring / plans | Web size |
|---|---|---|---|---|
| ① App mesh | 0 | Coarse | Rough | 1.2 MB |
| ② LiDAR points | Decoding only | Points, gaps up close | Most trustworthy | 19.5 MB |
| ③ RealityScan | 6 min | Good shape, blurry colour | Good (mesh) | 6.35 MB |
| ④ Splat | 35 min | Photographic | Hard | 48.0 MB |
| ⑤ Splat + depth | 55 min | Photographic | Hard | 48.0 MB |
| ⑥ LiDAR mesh | 2.7 min | Smooth, blurry colour | Good (LiDAR dimensions) | 4.5 MB |
| ⑦ WebODM | 60 min | 텍스처 있음 | Good (mesh) | 12.5 MB |
| ⑧ 2D splat | 66 min | Photographic, crisper surfaces | Hard | 48.0 MB |
| ⑨ VGGT (AI) | 5.2 min | Sharp points | LiDAR-scaled (1.1 cm median) | 22.5 MB |
| ⑩ Splat + VGGT | 58 min | Photographic | Hard | 48.0 MB |
| ⑪ LiDAR mesh + photos | 7 min | Photo texture, walkable | Good (LiDAR dimensions) | 19.0 MB |
| ⑫ ⑪ + holes filled | 13 min | ⑪ + black objects filled | Good (filled parts AI-estimated) | 21.7 MB |
| ⑬ ⑪ as 3D Tiles | 3 min | ⑪ + original resolution up close | Good (LiDAR dimensions) | 39.6 MB |
What went wrong on the way (kept as is)
- With photos only, the largest RS component was 65 of 568 images; with iPhone poses as initial priors, 62. Only locked poses aligned all 568. Indoor white walls and blurry video frames lack features
- Texture image export from the RS Linux command line failed every time with 'Feature not implemented' (PNG, JPG, explicit codecs, single texture off). Vertex colouring ran (74 s) but the exported colours were all white, so nearest-LiDAR colours were used. 742 stray triangles (edges up to 7.9 m) were also removed
- 42.3% of the LiDAR point file was (0,0,0) padding, not points. The first decoded count (21,029,180) matched the app's record, which nearly hid it; a median of exactly 0 gave it away
- While installing the training tools another package silently upgraded torch from 2.11 to 2.12, breaking the CUDA extensions; reverted and rebuilt
- The pip CUDA 13 compiler (13.0) pulled companion parts (nvvm, crt) at 13.4, breaking compilation; fixed by pinning versions
- The COLMAP text reader bundled with the trainer is broken under Python 3, so the binary format was written instead
- VGGT with 16 frames spread over the whole capture failed (99 cm position, 127° rotation); 16 overlapping frames reached 6.8 cm and 5°
- WebODM (ODM) cannot align everything from photos alone either, so the iPhone poses were fed in as fake GPS around an arbitrary origin
Next
- Measure two or three walls with a tape to quantify ② LiDAR, ⑥ LiDAR mesh, ⑦ WebODM and ⑨ VGGT error
- ⑨ next: feed iPhone poses and LiDAR into MapAnything (Meta) or Depth Anything 3 instead of VGGT to raise the depth pass rate (65% now) and accuracy; large checkpoints of both are non-commercial, so check for business use
- Fuse per-photo depth into a TSDF mesh (a candidate to replace ③ RS and ⑥ Poisson for measuring and plans)
- Show view-dependent colour (SH) for splats on the web (larger formats such as SOG)
- Wire into the vacant-space service: send from the app and arcAI runs all of this and refreshes the page