Materials and Shaders in Babylon.js
StandardMaterial vs PBRMaterial
The StandardMaterial uses a Blinn-Phong lighting model that was the standard in game rendering for decades. It is fast, simple, and produces acceptable results for stylized or retro visuals. Properties include diffuseColor for base color, specularColor for highlight color, emissiveColor for self-illumination, and corresponding texture slots for per-pixel control.
The PBRMaterial uses a metallic-roughness workflow based on physically correct light interaction. Two properties define the surface: metallic (0.0 for dielectric surfaces like wood and skin, 1.0 for metals like gold and steel) and roughness (0.0 for mirror-smooth, 1.0 for matte). The albedoColor or albedoTexture sets the base color. PBR materials look correct under any lighting because they model how light actually behaves rather than faking it with adjustable highlights.
For new projects, use PBR materials. They match the glTF standard, so imported models display correctly without material adjustments. They respond properly to environment maps, image-based lighting, and area lights. The visual quality difference between PBR and Phong is most apparent in metallic and glossy surfaces, where PBR produces physically correct reflections and energy conservation that Phong materials cannot replicate.
Environment textures provide the ambient lighting that PBR materials need for realistic reflections. Set scene.environmentTexture to an HDR image (in .env or .hdr format) that captures the surrounding lighting environment. This creates reflections on metallic surfaces, subtle environment coloring on diffuse surfaces, and accurate ambient occlusion. Without an environment texture, PBR materials look flat because they have no light to reflect.
The PBRMetallicRoughnessMaterial is a simplified version of the full PBR material that exposes only the properties defined in the glTF specification. It uses baseColor, metallic, and roughness with no additional configuration. Use it when you want exact glTF compliance and do not need the extended properties (clear coat, anisotropy, sub-surface scattering, sheen) that the full PBRMaterial offers. The full PBR material supports these advanced effects for specialized surfaces like car paint with clear coat, brushed metal with directional highlights, and skin with subsurface light scattering.
The Node Material Editor
The Node Material Editor (NME) is a browser-based visual tool for building custom materials without writing shader code. You drag nodes onto a canvas and connect their inputs and outputs to define how the material processes vertex positions, UV coordinates, colors, and lighting. The editor is available at nme.babylonjs.com and produces materials that can be exported as JSON and loaded at runtime.
Common NME workflows include animated water surfaces, holographic effects, dissolve transitions, and stylized toon shading. A water material might connect a noise texture through a time-driven offset to the vertex normal, creating surface waves. A dissolve effect compares a noise texture against an animated threshold, discarding pixels below the threshold to create a burning-away appearance.
The NME supports both vertex and fragment shader stages. Vertex nodes modify geometry positions, which is how you create waves, terrain deformation, or procedural animation. Fragment nodes control pixel color, transparency, and emission, which is where most visual effects happen. The output nodes are clearly labeled, and the preview pane updates in real time as you change connections.
Export a finished material with the Download button, which saves a JSON file. Load it in your game with BABYLON.NodeMaterial.ParseFromFileAsync("name", "material.json", scene). The JSON format is compact and loads quickly. You can also create Node Materials purely in code using the NodeMaterial API, though the visual editor is faster for iterative design.
Custom GLSL Shaders
For effects that the NME cannot express, write GLSL shaders directly using the ShaderMaterial class. Define vertex and fragment shader source code as strings or load them from external files. The ShaderMaterial handles uniform binding, attribute mapping, and WebGL state management, so you focus on the shader logic rather than the rendering pipeline plumbing.
Create a shader material with new BABYLON.ShaderMaterial("custom", scene, { vertex: "shaderName", fragment: "shaderName" }, { attributes: ["position", "normal", "uv"], uniforms: ["worldViewProjection", "time"] }). The attributes list specifies which vertex data the shader receives, and the uniforms list names the values you pass from JavaScript. Set uniform values with material.setFloat("time", performance.now() / 1000) on each frame.
GLSL shaders in Babylon.js work identically whether the backend is WebGL or WebGPU, because the engine translates GLSL to WGSL (WebGPU Shading Language) automatically when running on a WebGPU backend. This means your custom shaders are forward-compatible with WebGPU without rewriting them in a different language.
The Effect.ShadersStore provides an alternative to loading shader files from external URLs. Store your shader source code directly in the ShadersStore object keyed by name, and reference that name when creating the ShaderMaterial. This eliminates extra HTTP requests for shader files and keeps everything in your JavaScript bundle. For projects with many custom shaders, organize them as ES modules that register their code with the ShadersStore on import, so each shader is tree-shakeable and only included when actually used.
Babylon.js includes a library of shader includes for common operations. The #include<helperFunctions> directive in your GLSL code pulls in utility functions for color conversion, noise generation, and coordinate transformations. The #include<lightFragment> directive gives your custom shader access to the scene's light data without manually managing uniform buffers for each light. These includes let you build custom visual effects while still using the engine's built-in lighting and fog calculations.
Post-Processing Effects
Post-processing applies screen-space effects after the scene is rendered. Babylon.js includes a library of built-in effects: bloom adds glow around bright areas, depth of field blurs objects based on distance from a focus point, SSAO (screen-space ambient occlusion) darkens creases and corners for added depth, and motion blur streaks fast-moving objects.
The DefaultRenderingPipeline bundles the most common effects into a single configurable pipeline. Enable it with new BABYLON.DefaultRenderingPipeline("pipeline", true, scene, [camera]), then toggle individual effects: pipeline.bloomEnabled = true, pipeline.depthOfFieldEnabled = true, pipeline.fxaaEnabled = true. Each effect has parameters for strength, quality, and kernel size.
For custom post-processing, write a fragment shader that samples the rendered scene texture and transforms it. The PostProcess class takes a fragment shader and applies it to the camera output. Chain multiple post-processes by creating them in sequence. Each one receives the output of the previous process as its input texture, forming a pipeline.
Performance impact of post-processing is proportional to screen resolution. Bloom and blur effects sample multiple pixels for each output pixel, and at 4K resolution, this becomes expensive. The pipeline supports rendering at a lower internal resolution and upscaling, which keeps effects affordable on weaker GPUs. Set pipeline.imageProcessing.enabled and configure the renderingGroupId to control which objects are affected.
Particle Systems and Visual Effects
The particle system creates billboarded sprites that simulate fire, smoke, sparks, rain, snow, magic effects, and other volumetric visuals. Create a system with new BABYLON.ParticleSystem("particles", 2000, scene), set a texture, configure emission rate, lifetime, speed, size, and color gradients, then start it with system.start().
Particle properties are randomized between min and max values for natural variation. minSize and maxSize control individual particle scale. minLifeTime and maxLifeTime determine how long each particle exists. color1 and color2 define the color range. Gradient functions allow color, size, and speed to change over a particle's lifetime, creating effects like sparks that start bright yellow and fade to dim red.
Sub-emitters create particles that spawn from other particles. A firework effect uses a primary emitter that launches bright particles upward, and a sub-emitter that triggers when those particles die, spawning a burst of smaller colored particles in all directions. Sub-emitters can chain multiple levels deep for complex effects like explosions with sparks that trail smoke.
GPU particles use the GPU for both simulation and rendering, supporting millions of particles at interactive frame rates. Enable them with new BABYLON.GPUParticleSystem("gpu", { capacity: 1000000 }, scene). GPU particles support most of the same properties as CPU particles but cannot use custom update functions or sub-emitters because the simulation runs entirely in shader code. Use GPU particles for dense environmental effects like rain, snow, or ambient dust.
Use PBR materials with an environment texture for realistic rendering, the Node Material Editor for custom effects without shader code, and the DefaultRenderingPipeline for post-processing. Reserve custom GLSL shaders for effects that cannot be achieved through the visual tools.