Implement Source engine VMT shader types #8

Open
opened 2026-03-17 00:38:28 +00:00 by kit · 5 comments
Owner

Currently all BSP surfaces use a single shader (base texture × lightmap). Source engine VMT files specify different shader types that control how surfaces are rendered. We should parse the shader type from VMT files and create appropriate materials.

Shader types to implement

LightmappedGeneric (most BSP surfaces)

  • Already partially implemented (base × lightmap)
  • Add support for: $translucent, $alphatest, $alphatestreference, $detail, $detailscale, $bumpmap, $envmap, $selfillum

UnlitGeneric (overlays, decals, signs)

  • No lightmap multiplication, just flat texture
  • Support $translucent, $alphatest, $additive

WorldVertexTransition (terrain blending)

  • Two-texture blending using vertex alpha (e.g., dirt → grass transitions)
  • $basetexture, $basetexture2, blended per-vertex

VertexLitGeneric (props/models — lower priority)

  • Used for MDL models, not BSP surfaces
  • Would need vertex lighting or per-entity lighting data

Current state

  • VMT files are already fetched and parsed for $basetexture (see client/src/bsp/vmt.js)
  • The parser would need to extract the shader type (first key in the VMT file) and additional material properties
  • client/src/bsp/material.js has the custom ShaderMaterial — this would become a factory that creates the right material based on VMT shader type

Notes

  • Start with LightmappedGeneric improvements and UnlitGeneric, since those cover the vast majority of BSP surfaces
  • WorldVertexTransition requires vertex alpha data from the BSP, which we may not be extracting yet
  • Gamma/color space handling was just fixed — new shaders should continue working in linear space with sRGB texture inputs
Currently all BSP surfaces use a single shader (base texture × lightmap). Source engine VMT files specify different shader types that control how surfaces are rendered. We should parse the shader type from VMT files and create appropriate materials. ## Shader types to implement **LightmappedGeneric** (most BSP surfaces) - Already partially implemented (base × lightmap) - Add support for: `$translucent`, `$alphatest`, `$alphatestreference`, `$detail`, `$detailscale`, `$bumpmap`, `$envmap`, `$selfillum` **UnlitGeneric** (overlays, decals, signs) - No lightmap multiplication, just flat texture - Support `$translucent`, `$alphatest`, `$additive` **WorldVertexTransition** (terrain blending) - Two-texture blending using vertex alpha (e.g., dirt → grass transitions) - `$basetexture`, `$basetexture2`, blended per-vertex **VertexLitGeneric** (props/models — lower priority) - Used for MDL models, not BSP surfaces - Would need vertex lighting or per-entity lighting data ## Current state - VMT files are already fetched and parsed for `$basetexture` (see `client/src/bsp/vmt.js`) - The parser would need to extract the shader type (first key in the VMT file) and additional material properties - `client/src/bsp/material.js` has the custom ShaderMaterial — this would become a factory that creates the right material based on VMT shader type ## Notes - Start with LightmappedGeneric improvements and UnlitGeneric, since those cover the vast majority of BSP surfaces - WorldVertexTransition requires vertex alpha data from the BSP, which we may not be extracting yet - Gamma/color space handling was just fixed — new shaders should continue working in linear space with sRGB texture inputs
Author
Owner

Related: #11 (material overrides/entity colors). VMT shaders define material behavior, while #11 covers entity-level overrides like rendercolor/renderamt. Both affect final material appearance but at different levels — #8 is the base material, #11 is per-entity runtime overrides.

Related: #11 (material overrides/entity colors). VMT shaders define material behavior, while #11 covers entity-level overrides like `rendercolor`/`renderamt`. Both affect final material appearance but at different levels — #8 is the base material, #11 is per-entity runtime overrides.
Author
Owner

Progress update — shader type implementation

Most VMT shader types are now handled in the material system. Current status:

Implemented and working

  • LightmappedGeneric — lightmap × base texture, detail blending (12 modes), envmap with corrected Fresnel formula, self-illumination, alpha test/blend/additive, vertex color/alpha
  • VertexLitGeneric — ambient cube + local lights, sun lighting fallback, $halflambert support
  • UnlitGeneric — fullbright, no lighting
  • WorldVertexTransition — two-texture blend via vertex alpha
  • Water — Fresnel reflection, normal map ripples, planar reflection RT, underwater fog
  • Refract — displays $refracttinttexture semi-transparently as approximation (no real screen-space distortion)
  • DecalModulate / Modulate — multiply blending, mod2x variant
  • Cable — unlit, alpha tested
  • Sky / Sky_HDR — fullbright, no fog
  • Sprite / Spritecard — unlit
  • Patch, WorldVertexAlpha, WorldTwoTextureBlend, Skin, TreeLeaf, ShatteredGlass, Cloud, Shadow/ShadowModel, MonitorScreen, LightmappedReflective, UnlitTwoTexture, Wireframe, Eyes/Teeth/EyeRefract, DepthWrite/WriteZ/SetZ

Bug fixes applied

  • Envmap Fresnel formula was inverted — now matches Source's pow(1-NdotV,5) * (1-fresnelReflection) + fresnelReflection
  • vViewDir was not normalized in envmap section — produced NaN/zero Fresnel
  • Envmap application order now matches Source: mask → tint → contrast → saturation → fresnel
  • $nofog, $halflambert, $normalmapalphaenvmapmask now wired up from VMT parser to shader
  • Patch VMT resolution now inherits shader name from included base material
  • Entity material override envmaps now resolve when skybox cubemap loads

Still needed

  • Screen-space refraction (grab pass) for Refract shader — currently we display $refracttinttexture as a semi-transparent approximation, but Source reads the framebuffer behind the surface and distorts it via normal map. This requires rendering the scene to a texture first, then sampling it with UV distortion in the Refract fragment shader. Similar pattern to water reflection RT but for the view behind the surface.
  • Per-surface env_cubemap (#21) — most envmap surfaces reference $envmap "env_cubemap" which needs BSP-baked cubemaps, not just the skybox fallback
  • Bump mapping (#40) — normal maps loaded but only used for envmap/water perturbation, not diffuse/specular lighting. Requires TBN matrix and directional lightmap samples for BSP surfaces.
## Progress update — shader type implementation Most VMT shader types are now handled in the material system. Current status: ### Implemented and working - **LightmappedGeneric** — lightmap × base texture, detail blending (12 modes), envmap with corrected Fresnel formula, self-illumination, alpha test/blend/additive, vertex color/alpha - **VertexLitGeneric** — ambient cube + local lights, sun lighting fallback, $halflambert support - **UnlitGeneric** — fullbright, no lighting - **WorldVertexTransition** — two-texture blend via vertex alpha - **Water** — Fresnel reflection, normal map ripples, planar reflection RT, underwater fog - **Refract** — displays $refracttinttexture semi-transparently as approximation (no real screen-space distortion) - **DecalModulate / Modulate** — multiply blending, mod2x variant - **Cable** — unlit, alpha tested - **Sky / Sky_HDR** — fullbright, no fog - **Sprite / Spritecard** — unlit - Patch, WorldVertexAlpha, WorldTwoTextureBlend, Skin, TreeLeaf, ShatteredGlass, Cloud, Shadow/ShadowModel, MonitorScreen, LightmappedReflective, UnlitTwoTexture, Wireframe, Eyes/Teeth/EyeRefract, DepthWrite/WriteZ/SetZ ### Bug fixes applied - Envmap Fresnel formula was inverted — now matches Source's `pow(1-NdotV,5) * (1-fresnelReflection) + fresnelReflection` - vViewDir was not normalized in envmap section — produced NaN/zero Fresnel - Envmap application order now matches Source: mask → tint → contrast → saturation → fresnel - $nofog, $halflambert, $normalmapalphaenvmapmask now wired up from VMT parser to shader - Patch VMT resolution now inherits shader name from included base material - Entity material override envmaps now resolve when skybox cubemap loads ### Still needed - **Screen-space refraction (grab pass)** for Refract shader — currently we display $refracttinttexture as a semi-transparent approximation, but Source reads the framebuffer behind the surface and distorts it via normal map. This requires rendering the scene to a texture first, then sampling it with UV distortion in the Refract fragment shader. Similar pattern to water reflection RT but for the view behind the surface. - **Per-surface env_cubemap** (#21) — most envmap surfaces reference `$envmap "env_cubemap"` which needs BSP-baked cubemaps, not just the skybox fallback - **Bump mapping** (#40) — normal maps loaded but only used for envmap/water perturbation, not diffuse/specular lighting. Requires TBN matrix and directional lightmap samples for BSP surfaces.
Author
Owner

Update — envmap and cubemap work complete

Per-surface env_cubemap selection (#21) is now fully implemented:

  • BSP cubemap lump parsed, VTFs extracted from pakfile ZIP
  • Cubemap face orientation corrected (per-face rotation/flip transforms)
  • VTF v7.1-7.4 spheremap face accounted for in mip-skip calculation
  • Proximity-based and explicit-path cubemap selection for BSP surfaces and entities
  • Dynamic entity cubemap updates when crossing BSP leaf boundaries
  • CubeTexture GPU upload fix (requires ImageData, not plain objects)
  • $basealphaenvmapmask polarity fixed (inverted to match Source: 1.0 - alpha)
  • ENV_MAP_SCALE applied with gamma-aware linearize→scale→encode

Remaining for this issue

  • Linear color pipeline — the shader mixes gamma and linear space operations, causing envmap brightness to not match Source. Needs a full conversion to linear-space math with a single gamma encode at output. Filed as new issue.
  • Screen-space refraction for Refract shader (grab pass)
  • Bump mapping (#40) for proper normal-mapped lighting
## Update — envmap and cubemap work complete Per-surface env_cubemap selection (#21) is now fully implemented: - BSP cubemap lump parsed, VTFs extracted from pakfile ZIP - Cubemap face orientation corrected (per-face rotation/flip transforms) - VTF v7.1-7.4 spheremap face accounted for in mip-skip calculation - Proximity-based and explicit-path cubemap selection for BSP surfaces and entities - Dynamic entity cubemap updates when crossing BSP leaf boundaries - CubeTexture GPU upload fix (requires ImageData, not plain objects) - `$basealphaenvmapmask` polarity fixed (inverted to match Source: `1.0 - alpha`) - `ENV_MAP_SCALE` applied with gamma-aware linearize→scale→encode ### Remaining for this issue - **Linear color pipeline** — the shader mixes gamma and linear space operations, causing envmap brightness to not match Source. Needs a full conversion to linear-space math with a single gamma encode at output. Filed as new issue. - **Screen-space refraction** for Refract shader (grab pass) - **Bump mapping** (#40) for proper normal-mapped lighting
Author
Owner

Note: the shader now operates in a full linear color pipeline (36368a2, #44). All texture inputs are linearized, lighting math is done in linear space, and there's a single gamma encode at output. Any new shader features added here should work in linear space — no per-path gamma conversions needed.

Note: the shader now operates in a full linear color pipeline (36368a2, #44). All texture inputs are linearized, lighting math is done in linear space, and there's a single gamma encode at output. Any new shader features added here should work in linear space — no per-path gamma conversions needed.
Author
Owner

Progress update — bump mapping ($bumpmap) is now implemented for LightmappedGeneric BSP surfaces as of a27c9ef:

  • Directional lightmap sampling with Source's bump basis vectors
  • Tangent/bitangent vertex attributes from texinfo
  • TBN matrix for tangent→world space normal transformation
  • Perturbed normals used for envmap reflection direction

Also in the same batch: $color is now applied as a gamma-space lightmap multiplier (matching Source's g_TintValuesAndLightmapScale), and $selfillumtint handling matches Source's g_SelfIllumTint * albedo pattern.

Remaining from this issue's checklist: $detail (partially done — blend modes implemented, $detailtint not yet), $envmap improvements are ongoing (#45 just fixed).

Progress update — bump mapping (`$bumpmap`) is now implemented for LightmappedGeneric BSP surfaces as of `a27c9ef`: - Directional lightmap sampling with Source's bump basis vectors - Tangent/bitangent vertex attributes from texinfo - TBN matrix for tangent→world space normal transformation - Perturbed normals used for envmap reflection direction Also in the same batch: `$color` is now applied as a gamma-space lightmap multiplier (matching Source's `g_TintValuesAndLightmapScale`), and `$selfillumtint` handling matches Source's `g_SelfIllumTint * albedo` pattern. Remaining from this issue's checklist: `$detail` (partially done — blend modes implemented, `$detailtint` not yet), `$envmap` improvements are ongoing (#45 just fixed).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kit/gmod-web-stream#8
No description provided.