@@ -2,7 +2,15 @@ import terrainShaderWGSL from './shaders/terrain.wgsl?raw';
22import { Vector2D } from '../../game/types/math-types' ;
33import { WebGPUTerrainState , Vector3D } from '../types/rendering-types' ;
44import { WATER_LEVEL } from '../constants/world-constants' ;
5- import { TERRAIN_DISPLACEMENT_FACTOR } from '../constants/rendering-constants' ;
5+ import {
6+ TERRAIN_DISPLACEMENT_FACTOR ,
7+ GROUND_COLOR ,
8+ SAND_COLOR ,
9+ GRASS_COLOR ,
10+ ROCK_COLOR ,
11+ SNOW_COLOR ,
12+ } from '../constants/rendering-constants' ;
13+ import { BiomeType } from '../types/world-types' ;
614
715function isWebGPUSupported ( ) {
816 return typeof navigator !== 'undefined' && 'gpu' in navigator ;
@@ -23,9 +31,46 @@ function flattenHeightMapToR8(heightMap: number[][]): { data: Uint8Array; width:
2331 return { data, width : w , height : h } ;
2432}
2533
34+ // Flattens the biome map to a Uint8Array where each biome ID [0-4] is
35+ // normalized to the [0-255] range for use in an r8unorm texture.
36+ function flattenBiomeMapToR8Unorm ( biomeMap : BiomeType [ ] [ ] ) : { data : Uint8Array ; width : number ; height : number } {
37+ const h = biomeMap . length ;
38+ const w = biomeMap [ 0 ] ?. length ?? 0 ;
39+ const data = new Uint8Array ( w * h ) ;
40+ let i = 0 ;
41+ for ( let y = 0 ; y < h ; y ++ ) {
42+ const row = biomeMap [ y ] ;
43+ for ( let x = 0 ; x < w ; x ++ ) {
44+ let biomeValue = 0 ; // Default to GROUND
45+ switch ( row [ x ] ) {
46+ case BiomeType . GROUND :
47+ biomeValue = 0 ;
48+ break ;
49+ case BiomeType . SAND :
50+ biomeValue = 1 ;
51+ break ;
52+ case BiomeType . GRASS :
53+ biomeValue = 2 ;
54+ break ;
55+ case BiomeType . ROCK :
56+ biomeValue = 3 ;
57+ break ;
58+ case BiomeType . SNOW :
59+ biomeValue = 4 ;
60+ break ;
61+ }
62+ // Normalize the value from [0, 4] to [0, 255] for r8unorm texture.
63+ // The shader will receive this as a float in [0.0, 1.0].
64+ data [ i ++ ] = Math . round ( ( biomeValue / 4.0 ) * 255 ) ;
65+ }
66+ }
67+ return { data, width : w , height : h } ;
68+ }
69+
2670export async function initWebGPUTerrain (
2771 canvas : HTMLCanvasElement ,
2872 heightMap : number [ ] [ ] ,
73+ biomeMap : BiomeType [ ] [ ] ,
2974 mapDimensions : { width : number ; height : number } ,
3075 cellSize : number ,
3176 lighting ?: { lightDir ?: Vector3D ; heightScale ?: number ; ambient ?: number ; displacementFactor ?: number } ,
@@ -52,6 +97,7 @@ export async function initWebGPUTerrain(
5297 { binding : 0 , visibility : GPUShaderStage . FRAGMENT , buffer : { type : 'uniform' } } ,
5398 { binding : 1 , visibility : GPUShaderStage . FRAGMENT , texture : { sampleType : 'float' } } ,
5499 { binding : 2 , visibility : GPUShaderStage . FRAGMENT , sampler : { type : 'filtering' } } ,
100+ { binding : 3 , visibility : GPUShaderStage . FRAGMENT , texture : { sampleType : 'float' } } ,
55101 ] ,
56102 } ) ;
57103
@@ -64,28 +110,43 @@ export async function initWebGPUTerrain(
64110 primitive : { topology : 'triangle-list' } ,
65111 } ) ;
66112
67- // Uniform buffer (4 vec4 = 64 bytes)
68- const uniformBufferSize = 4 * 4 * 4 ;
113+ // Uniform buffer (8 vec4 = 128 bytes)
114+ const uniformBufferSize = 8 * 4 * 4 ;
69115 const uniformBuffer = device . createBuffer ( {
70116 size : uniformBufferSize ,
71117 usage : GPUBufferUsage . UNIFORM | GPUBufferUsage . COPY_DST ,
72118 } ) ;
73119
74120 // Height map texture
75- const { data, width : gridW , height : gridH } = flattenHeightMapToR8 ( heightMap ) ;
121+ const { data : heightData , width : gridW , height : gridH } = flattenHeightMapToR8 ( heightMap ) ;
76122 const heightTexture = device . createTexture ( {
77123 size : { width : gridW , height : gridH } ,
78124 format : 'r8unorm' ,
79125 usage : GPUTextureUsage . TEXTURE_BINDING | GPUTextureUsage . COPY_DST ,
80126 } ) ;
81127 device . queue . writeTexture (
82128 { texture : heightTexture } ,
83- data . buffer ,
84- { offset : data . byteOffset , bytesPerRow : gridW , rowsPerImage : gridH } ,
129+ heightData . buffer ,
130+ { offset : heightData . byteOffset , bytesPerRow : gridW , rowsPerImage : gridH } ,
85131 { width : gridW , height : gridH } ,
86132 ) ;
87133 const heightTextureView = heightTexture . createView ( ) ;
88134
135+ // Biome map texture
136+ const { data : biomeData } = flattenBiomeMapToR8Unorm ( biomeMap ) ;
137+ const biomeTexture = device . createTexture ( {
138+ size : { width : gridW , height : gridH } ,
139+ format : 'r8unorm' ,
140+ usage : GPUTextureUsage . TEXTURE_BINDING | GPUTextureUsage . COPY_DST ,
141+ } ) ;
142+ device . queue . writeTexture (
143+ { texture : biomeTexture } ,
144+ biomeData . buffer ,
145+ { offset : biomeData . byteOffset , bytesPerRow : gridW , rowsPerImage : gridH } ,
146+ { width : gridW , height : gridH } ,
147+ ) ;
148+ const biomeTextureView = biomeTexture . createView ( ) ;
149+
89150 const sampler = device . createSampler ( {
90151 addressModeU : 'repeat' ,
91152 addressModeV : 'repeat' ,
@@ -99,6 +160,7 @@ export async function initWebGPUTerrain(
99160 { binding : 0 , resource : { buffer : uniformBuffer } } ,
100161 { binding : 1 , resource : heightTextureView } ,
101162 { binding : 2 , resource : sampler } ,
163+ { binding : 3 , resource : biomeTextureView } ,
102164 ] ,
103165 } ) ;
104166
@@ -113,6 +175,8 @@ export async function initWebGPUTerrain(
113175 sampler,
114176 heightTexture,
115177 heightTextureView,
178+ biomeTexture,
179+ biomeTextureView,
116180 gridSize : { width : gridW , height : gridH } ,
117181 mapDimensions,
118182 cellSize,
@@ -158,7 +222,7 @@ export function renderWebGPUTerrain(
158222 const canvasWidth = canvas . width ;
159223 const canvasHeight = canvas . height ;
160224
161- const u = new Float32Array ( 16 ) ;
225+ const u = new Float32Array ( 32 ) ;
162226 // c0: center.x, center.y, zoom, cellSize
163227 u [ 0 ] = center . x ;
164228 u [ 1 ] = center . y ;
@@ -181,6 +245,28 @@ export function renderWebGPUTerrain(
181245 u [ 14 ] = time ;
182246 u [ 15 ] = displacementFactor ;
183247
248+ // c4-c7: Biome colors
249+ // c4: GROUND.rgb, SAND.r
250+ u [ 16 ] = GROUND_COLOR . r ;
251+ u [ 17 ] = GROUND_COLOR . g ;
252+ u [ 18 ] = GROUND_COLOR . b ;
253+ u [ 19 ] = SAND_COLOR . r ;
254+ // c5: SAND.gb, GRASS.rg
255+ u [ 20 ] = SAND_COLOR . g ;
256+ u [ 21 ] = SAND_COLOR . b ;
257+ u [ 22 ] = GRASS_COLOR . r ;
258+ u [ 23 ] = GRASS_COLOR . g ;
259+ // c6: GRASS.b, ROCK.rgb
260+ u [ 24 ] = GRASS_COLOR . b ;
261+ u [ 25 ] = ROCK_COLOR . r ;
262+ u [ 26 ] = ROCK_COLOR . g ;
263+ u [ 27 ] = ROCK_COLOR . b ;
264+ // c7: SNOW.rgb, padding
265+ u [ 28 ] = SNOW_COLOR . r ;
266+ u [ 29 ] = SNOW_COLOR . g ;
267+ u [ 30 ] = SNOW_COLOR . b ;
268+ u [ 31 ] = 0.0 ;
269+
184270 device . queue . writeBuffer ( uniformBuffer , 0 , u . buffer ) ;
185271
186272 const pass = encoder . beginRenderPass ( {
0 commit comments