Illusion of Quality
The choice of foundational libraries matters less than shader/post-processing implementation.
Renderlab’s Rendering Stack Hierarchy
-
Layer 2: Model Viewer Libraries Opinionated wrappers around Three.js that preconfigure: • Default lighting setups • Basic environment maps • Simple interaction handlers • Prebaked material presets
-
Layer 1: Three.js Abstracts WebGL into JavaScript objects (Meshes, Materials, Cameras), does exposes: • Built-in shader chunks • Custom shader injection points • Post-processing pass system
-
Layer 0: WebGL The raw GPU interface to deal with vertex/fragment shaders. At this level, I’m writing GLSL code to directly manipulate: • Vertex transformations • Rasterization rules • Fragment lighting calculations • Frame buffer operations
The Illusion of Quality
Sketchfab’s visual “superiority” comes from strategic GPU programming that goes far beyond basic Three.js capabilities:
Material Sophistication
These are implemented as custom shaders. ThreeJS ships with a great shader to render PBR materials, but does not involve most of the existing advanced techniques, e.g:
- PBR Shader
- Multi-lobe GGX BRDF with height-correlated Smith visibility
- Pre-convolved split-sum IBL for environment lighting
- Clear coat layers with wavelength-dependent absorption
- Subsurface scattering approximations using dipole diffusion
- Procedural Textures GPU-generated noise patterns (Perlin, Worley) for:
- Microsurface detail (scratch maps)
- Weathering effects
- Animated material transitions (not sure they actually do that)
Post-Processing Pipeline
A typical Sketchfab frame uses almost a dozen render passes:
Pass | Technique | Shader Math | Purpose |
---|---|---|---|
1 | MBAA (Morphological Anti-Aliasing) | Depth/normal-aware edge detection | Temporal stability |
2 | SSRTGI (Screen-Space Ray Traced GI) | Hemisphere sampling with blue noise dithering | Dynamic global illumination |
3 | Filmic Tonemapping | ACEScg curve with luminance histogram adaptation | HDR handling |
4 | Cinematic Bloom | Pyramidal Gaussian blur with luminance thresholding | Glare simulation |
5 | Bokeh DoF | Stochastic disc sampling with CoC masking | Optical lens emulation |
6 | Temporal SMAA | Velocity buffer reprojection | Anti-aliasing |
7 | SSR (Screen-Space Reflections) | Ray-marched roughness-dependent sampling | Real-time reflective surfaces |
8** | SSAO (Screen-Space Ambient Occlusion) | Depth-based horizon angle estimation | Contact shadow enhancement |
9 | Film Grain | Perlin noise modulated by exposure | Analog camera artifact emulation |
10 | Adaptive Sharpness | Contrast-aware unsharp masking | Detail enhancement |
11 | Chromatic Aberration | Wavelength-dependent distortion offsets | Lens imperfection simulation |
12 | Vignette | Radial gradient with exposure compensation | Focus guidance |
13 | Color Balance | 3D LUT interpolation with temperature/tint | Artistic mood control |
Advanced Lighting Tricks
- Area Light Approximations Using LTC (Linearly Transformed Cosines) matrices to fake rectangle lights
- Volumetric Effects Ray-marched participating media with Henyey-Greenstein phase functions
- Caustics Photon mapping via signed distance fields (SDFs)
Some Shader code
While the jargon is scary, the code is not that complex. Here is some example from basic to more cutting-edge WebGL features:
Basic Illumination
// Lambertian diffuse
float diffuse = max(dot(N, L), 0.0);
Microsurface Models
// GGX Normal Distribution
float D_GGX(float NdotH, float roughness) {
float a = roughness*roughness;
float a2 = a*a;
float denom = (NdotH*NdotH*(a2 - 1.0) + 1.0);
return a2 / (PI * denom * denom);
}
Stochastic Raymarching
// Screen-space reflections
for(int i=0; i<MAX_STEPS; i++) {
hitPos += rayStep;
float depth = getDepth(hitPos);
if(depth < rayDepth) {
// Binary search refinement
for(int j=0; j<5; j++) {
rayStep *= 0.5;
hitPos -= rayStep;
depth = getDepth(hitPos);
if(depth < rayDepth) hitPos -= rayStep;
else hitPos += rayStep;
}
return hitPos;
}
}
WebGL Limitations
There are things webGL, even 2.0 or WebGPU can’t do. As opposed to doing things with OpenGL or other native GPU drivers. Sketchfab works around WebGL 1.0 constraints using tricks like:
- RGBA8 emulation of HDR via Reinhard encoding
- Depth buffer packing into 32-bit textures
- Spherical harmonic compression for IBL probes
While the model-viewer library provide convenience, the gist is:
- Custom material graphs (node-based shader editors)
- Multi-pass compositing pipelines
- Physically-accurate light transport simulations
- Temporal accumulation techniques
To match Sketchfab’s quality, I’d need to implement similar GPU-driven techniques regardless of the base framework.