--- name: shader-authoring-glsl-wgsl description: Writing GLSL and WGSL shaders that compile on both, and the precision and uniform traps that only appear on some hardware. when_to_use: You are the shader author on a three.js/WebGL team, writing or porting a shader. tags: [threejs, shaders] --- # GLSL and WGSL are the same ideas, spelled differently WebGL2 takes GLSL ES 3.0; WebGPU takes WGSL. Porting is mostly mechanical, and the mechanical parts are not where the bugs are. | | GLSL ES 3.0 | WGSL | |---|---|---| | entry | `void main()` | `@fragment fn fs_main(...) -> @location(0) vec4f` | | varyings | `in`/`out` at global scope | struct fields with `@location(n)` | | uniforms | `uniform` block | `var` in a bind group | | texture | `texture(sampler2D, uv)` | `textureSample(t, s, uv)` — texture and sampler are SEPARATE | | vec | `vec3` | `vec3f` (alias of `vec3`) | The separated texture/sampler split is the one that changes structure: WGSL binds them independently, so a GLSL shader using four samplers becomes four textures plus (often) one shared sampler. ## Precision is not decoration `mediump` in a fragment shader means **at least** 10 bits of mantissa, and on mobile GPUs it means exactly that. A computation that is fine on a desktop where `mediump` is silently promoted to 32-bit will band, banding-clamp or NaN on a phone. Rules that avoid the whole class: - World-space positions and time accumulators are `highp`. Always. A `mediump` time uniform visibly stutters within minutes of page load. - Normalise in `highp`, then downcast. - Test on a real mobile device or an emulator that honours precision. Desktop Chrome will not show you this bug. ## Uniforms are a budget Each uniform, varying and texture unit is a hardware-limited slot, and the limits are much lower than desktop defaults suggest (`MAX_VARYING_VECTORS` can be 8). Pack related scalars into a `vec4` rather than declaring four floats, and query the limits rather than assuming. ## Shader compile errors are silent by default three.js logs a compile failure to the console and renders black. That is indistinguishable from a material bug, a camera bug or a culling bug. When something renders black, check the shader log FIRST — it is a two-second check that eliminates a large fraction of the search space. Keep a flat-colour fallback: a shader that fails to compile should show magenta, never black. Black is a colour the scene might legitimately be; magenta is not.