Bump mapping and directional lightmap samples for proper normal-mapped lighting #40

Closed
opened 2026-03-19 14:00:46 +00:00 by kit · 1 comment
Owner

Problem

Normal maps ($bumpmap) are loaded and applied to materials but only used for envmap reflection perturbation and water ripple distortion. They don't affect diffuse or specular lighting, so bumpmapped surfaces appear flat compared to the game.

What's needed

Entity materials (VertexLitGeneric)

  • Pass tangent/bitangent vectors through the vertex shader (TBN matrix)
  • Transform normal map samples from tangent space to world space
  • Use the perturbed normal for diffuse (N·L) and specular lighting calculations
  • This applies to the sun lighting and ambient cube + local lights paths

BSP surfaces (LightmappedGeneric)

Source engine uses 3 directional lightmap samples (basis vectors) for bumped lightmapped surfaces. Each sample captures light from a different direction, and the normal map selects the contribution from each:

  • The BSP stores 3 lightmap layers per bumped face (in addition to the flat lightmap)
  • The shader dots the tangent-space normal with 3 basis vectors and blends the lightmap samples accordingly
  • Without these samples, normal maps on BSP geometry can't affect lightmap-based lighting at all

Implementation considerations

  • Tangent/bitangent attributes need to be computed or extracted from the BSP face data
  • The lightmap atlas would need to include the 3 directional samples (significantly increases atlas size)
  • This is the same system Source uses for $ssbump (self-shadowed bump maps)
  • #8 — VMT shader types (bump mapping is a feature of LightmappedGeneric and VertexLitGeneric)
## Problem Normal maps (`$bumpmap`) are loaded and applied to materials but only used for envmap reflection perturbation and water ripple distortion. They don't affect diffuse or specular lighting, so bumpmapped surfaces appear flat compared to the game. ## What's needed ### Entity materials (VertexLitGeneric) - Pass tangent/bitangent vectors through the vertex shader (TBN matrix) - Transform normal map samples from tangent space to world space - Use the perturbed normal for diffuse (N·L) and specular lighting calculations - This applies to the sun lighting and ambient cube + local lights paths ### BSP surfaces (LightmappedGeneric) Source engine uses **3 directional lightmap samples** (basis vectors) for bumped lightmapped surfaces. Each sample captures light from a different direction, and the normal map selects the contribution from each: - The BSP stores 3 lightmap layers per bumped face (in addition to the flat lightmap) - The shader dots the tangent-space normal with 3 basis vectors and blends the lightmap samples accordingly - Without these samples, normal maps on BSP geometry can't affect lightmap-based lighting at all ### Implementation considerations - Tangent/bitangent attributes need to be computed or extracted from the BSP face data - The lightmap atlas would need to include the 3 directional samples (significantly increases atlas size) - This is the same system Source uses for `$ssbump` (self-shadowed bump maps) ## Related - #8 — VMT shader types (bump mapping is a feature of LightmappedGeneric and VertexLitGeneric)
Author
Owner

Implemented in a27c9ef. Full bump mapping pipeline for BSP LightmappedGeneric surfaces:

  • Lightmap atlas (lightmap.js): Extracts 3 directional lightmap layers (bump basis samples) from the BSP LIGHTING lump alongside the flat layer. Uses the same atlas layout/UVs.
  • Geometry (geometry.js): Computes tangent/bitangent vertex attributes from texinfo.textureVecs for all BSP faces and displacements.
  • Vertex shader: Builds TBN matrix (tangent space → world space) from tangent, bitangent, and normal.
  • Fragment shader: Implements Source's bumped lightmap sampling — dots the tangent-space normal against the 3 bump basis vectors (from bumpvects.h), applies squared falloff, weights the directional lightmap samples, and normalizes by dot(dp, vec3(1)). Perturbed world-space normal is used for envmap reflection direction.
  • Material resolver: Sets hasBumpmap = true when $bumpmap VMT property is defined and the texture loads successfully.

Note: This covers BSP surfaces (LightmappedGeneric). VertexLitGeneric bump mapping for entity models (using the normal for N·L diffuse and specular) is not yet implemented — that's a simpler change but requires the same TBN pipeline on model geometry.

Implemented in `a27c9ef`. Full bump mapping pipeline for BSP LightmappedGeneric surfaces: - **Lightmap atlas** (`lightmap.js`): Extracts 3 directional lightmap layers (bump basis samples) from the BSP LIGHTING lump alongside the flat layer. Uses the same atlas layout/UVs. - **Geometry** (`geometry.js`): Computes tangent/bitangent vertex attributes from `texinfo.textureVecs` for all BSP faces and displacements. - **Vertex shader**: Builds TBN matrix (tangent space → world space) from tangent, bitangent, and normal. - **Fragment shader**: Implements Source's bumped lightmap sampling — dots the tangent-space normal against the 3 bump basis vectors (from `bumpvects.h`), applies squared falloff, weights the directional lightmap samples, and normalizes by `dot(dp, vec3(1))`. Perturbed world-space normal is used for envmap reflection direction. - **Material resolver**: Sets `hasBumpmap = true` when `$bumpmap` VMT property is defined and the texture loads successfully. Note: This covers BSP surfaces (LightmappedGeneric). VertexLitGeneric bump mapping for entity models (using the normal for N·L diffuse and specular) is not yet implemented — that's a simpler change but requires the same TBN pipeline on model geometry.
kit closed this issue 2026-03-20 05:38:34 +00:00
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#40
No description provided.