3D Model Viewer
A 3D model viewer in ComfyUI that renders you a depth map too
- rendered_image
- depth_map
Most 3D work in ComfyUI happens the boring way: you load a depth map or a mesh in some other program, then bring the pixels in. The 3D Model Viewer node flips that. It embeds a real interactive Three.js viewport right inside a node, lets you spin a .glb model around with proper camera controls, and hands you two actual IMAGE tensors at the end - the color render and a normalized depth map. It's from the small beta pack aesethtics/comfyui-3d-model-viewer, and it's exactly what the description says: a personal-workflow tool with zero impressions to its name. Don't expect a polished product or rapid updates. Do expect the lowest-friction way to get a real GLB into your graph.
How it actually works
The mechanism is worth understanding because it explains the one gotcha that will absolutely trip you up. This is a frontend/backend split. You give the node a model_path, and on queue the Python side validates the file, copies it into ComfyUI/web/temp_models (with an md5-hashed filename so duplicates don't collide), and serves it over a local /temp_models/ route. The browser side - Three.js r170, loaded from a CDN - renders the model to a WebGL canvas.
Then the clever part: it does a dual-pass render. Pass one draws the scene as you see it. Pass two re-renders into a WebGLRenderTarget with a real depth texture, runs a custom shader that inverts and normalizes it against your near/far planes so closer is brighter, and produces a clean grayscale depth map. Both passes are captured as base64 PNGs and POSTed back to /threejs_capture_image and /threejs_capture_depth, where ComfyUI-side handlers convert them into float image tensors and stash them in globals. The next time the workflow runs, the node returns those stored tensors.
Which leads to the gotcha: the first run returns a blank black 512×512 image. There's nothing captured yet, so the node hands you torch.zeros. The intended flow is: set your path, hit the Initialize Preview button on the node, mouse around until the view is good, then queue - and the second run carries real pixels out through the outputs.
The inputs that matter
Eleven inputs, and honestly you'll set three of them most of the time:
- model_path - the one that matters. A full absolute filepath to a
.glb(or.gltf) file, no quotes. The README's example workflow points atassets/axis-checker-v02.glb. - canvas_width / canvas_height - output resolution, 512–2048. Pixel mapping is 1:1, so these match the output exactly.
- focal_length - 10–200mm, default 50. That's 35mm-equivalent: wide angles show more scene, telephoto zooms in. The other sliders (
camera_x/y/z,lookat_x/y/z,camera_near/far) are there when you need a specific angle or to fix a clipped depth range.
Outputs
Two IMAGE tensors: rendered_image (the color render) and depth_map (the normalized grayscale). Both wire straight into a Save Image node, or - the actually interesting part - depth_map feeds a ControlNet depth preprocessor for structure-preserving generation, or a parallax/displacement workflow. Getting a mesh-accurate depth map without any 2D depth-estimation model is this node's real selling point; a standard depth estimator has to guess at geometry, this one just reads the depth buffer.
Install
ComfyUI Manager is the easy path - search "3D Model Viewer" under this pack. Or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/aesethtics/comfyui-3d-model-viewer.git
pip install -r comfyui-3d-model-viewer/requirements.txt
Restart ComfyUI and the node appears under aesethtics/3D. Dependencies are almost nothing: Pillow, numpy, torch - all things a working ComfyUI already has. The real requirement nobody warns you about is that Three.js loads from a CDN, so an offline ComfyUI instance renders nothing. And you supply your own GLB; there are no model downloads.
Troubleshooting
- First run is black - not broken, that's the capture cycle. Preview, then re-queue.
- Nothing loads - GLB/GLTF only, and the path must be absolute. Also note the README's beta blurb says "GLB files only," so treat
.gltfas the less-tested path even though the code accepts it. - View looks weird or broken - the README's fix is click Initialize Preview; it reloads the model and fixes most display issues.
- Changing canvas size doesn't re-render - a known quirk; nudge any camera value (e.g.
camera_near0.10 → 0.11) to force it.
It's a beta tool from a solo dev, so don't be shy about opening issues. And if you need transform gizmos, normal passes, and six render outputs, the bigger Three.js viewer packs are where the community has gone - but for "show me this GLB and hand me its depth map," this one stays surprisingly hard to beat for weight.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| model_path | STRING | — | |
| canvas_width | INT | 512512–2048 | — |
| canvas_height | INT | 512512–2048 | — |
| camera_x | FLOAT | 0.0-50–50 | — |
| camera_y | FLOAT | 0.0-50–50 | — |
| camera_z | FLOAT | 0.0-50–50 | — |
| lookat_x | FLOAT | 0.0-50–50 | — |
| lookat_y | FLOAT | 0.0-50–50 | — |
| lookat_z | FLOAT | 0.0-50–50 | — |
| camera_near | FLOAT | 0.100.01–10 | — |
| camera_far | FLOAT | 101–100 | — |
| focal_length | FLOAT | 5010–200 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| rendered_image | IMAGE | — |
| depth_map | IMAGE | — |