跪拜 Guibai
← All articles
Frontend · JavaScript · Vue.js

A 3D Corvette C8 Showroom in the Browser, Built with Three.js and Vue

By Mh ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Embedding interactive 3D product configurators in web apps is increasingly common, but the gap between a flat model and a convincing showcase is wide. This walkthrough surfaces the concrete steps — tone mapping, multi-light setups, PBR paint materials, and manual GPU resource disposal — that turn a raw .glb into a production-quality browser showroom without leaking memory.

Summary

A full 3D product showcase drops a detailed Corvette C8 model straight into a Vue page. The model, sourced from Sketchfab under a CC Attribution license, is loaded via GLTFLoader and placed into a scene with a PerspectiveCamera, directional and area lights mimicking a photo studio, and an environment map for realistic reflections. A grid-only stage replaces a distracting floor ring, and the initial camera position is pulled closer for a tighter product-shot feel.

Car paint is handled through MeshPhysicalMaterial with per-color PBR parameters — metalness, roughness, clearcoat, and iridescence — so switching colors updates the shared body material without reloading the model. The built-in animations are clamped to their closed state on load, and the car auto-rotates inside a wrapper Group while OrbitControls handles user drag with damping.

Resource disposal is explicit: on Vue component unmount, the render loop is cancelled, geometries and materials are traversed and freed, the environment map and renderer are disposed, and the canvas DOM element is removed. This prevents WebGL memory leaks across route changes in a SPA.

Takeaways
Model paths use `new URL('./models/…', import.meta.url)` so Vite treats them as module assets and paths survive folder moves.
After loading a .glb, the model is scaled via its bounding box longest side, centered, and grounded — not assumed to be correctly sized or positioned.
Car paint is a MeshPhysicalMaterial with clearcoat, not a flat color; each paint option carries its own metalness, roughness, clearcoat roughness, reflectivity, envMap intensity, and iridescence values.
All body meshes share one material instance, so a paint switch updates that single material and the whole car changes instantly without reloading.
Body meshes are identified by exact material names (`body_color`, `painted_black`) rather than keyword guessing, keeping glass and tires untouched.
ACESFilmicToneMapping with an exposure of 0.6 and SRGBColorSpace output prevents washed-out or gray-shifted colors in the browser.
A RoomEnvironment generates the reflection map via PMREMGenerator; without it, car paint and glass appear flat.
The lighting rig combines key, fill, rim, and hemisphere lights with two RectAreaLights acting as softboxes for broad, smooth highlights.
Built-in animations are clamped to their final frame on load so doors and hood start closed, not open.
On Vue unmount, `disposeObject` traverses the scene to free every geometry and material, the renderer and environment map are disposed, and the canvas element is removed from the DOM.
`controls.enableDamping = true` requires calling `controls.update()` every animation frame, or drag inertia won't work.
Resize handling must update both `renderer.setSize` and `camera.aspect` with `updateProjectionMatrix`, or the view stretches.
Conclusions

Treating the car body as a single shared material rather than per-mesh instances is a deliberate architectural choice that makes real-time configurators cheap: one property update propagates across every painted surface with zero traversal.

The shift from a TorusGeometry ring to a bare GridHelper is a small visual decision with an outsized effect — removing a competing geometric element lets the car's own reflections and contours dominate the frame.

Explicit material-name matching (`body_color`, `painted_black`) is more fragile across different models than keyword heuristics, but far more precise; it bets on a known asset rather than generalizing, which is the right tradeoff for a single-product showcase.

The cleanup routine is unusually thorough for a demo — traversing and disposing every geometry and material, plus removing the DOM canvas — and reflects the reality that SPAs with route-based 3D views will leak WebGL contexts without manual teardown.

Concepts & terms
PMREMGenerator
A Three.js utility that pre-filters an environment map into a Mipmapped Radiance Environment Map, allowing materials to sample blurred reflections at different roughness levels efficiently at runtime.
ACESFilmicToneMapping
A tone mapping curve based on the Academy Color Encoding System that maps HDR render values to SDR display ranges while preserving highlight detail and color saturation — widely used in film and game rendering.
MeshPhysicalMaterial clearcoat
A Three.js material property that simulates a transparent clear-coat layer over the base color, with its own roughness control; essential for realistic car paint where a glossy lacquer sits atop the pigment.
RectAreaLight
A Three.js light type that emits from a rectangular area rather than a point, producing soft, directional illumination similar to a studio softbox. Requires RectAreaLightUniformsLib for shader support.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗