commit d81c830a3b8f2e08d0667368786ec8c863d4a5bc
parent bd903388b4b1d7aa8eb4dff5df81c7ed7755c531
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Sun, 29 Oct 2017 11:12:11 +0200
BC5 heightmap/normalmap and BC4 horizonmap for clipmaps
Diffstat:
| clipmap.cc | | | 161 | ++++++++++++++++--------------------------------------------------------------- | 
| make.lua | | | 6 | +++++- | 
| pp2.cc | | | 206 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| shaders/clipmap.glsl | | | 9 | ++++++--- | 
4 files changed, 249 insertions(+), 133 deletions(-)
diff --git a/clipmap.cc b/clipmap.cc
@@ -7,139 +7,46 @@
 #include "gl.h"
 #include "skybox.h"
 
-#include "libs/lodepng/lodepng.h"
+#include "libs/lz4/lz4.h"
 
 static Mesh patch;
 static Texture heightmap;
 static Texture normalmap;
 static Texture horizonmap;
 
-static u32 PATCH_RESOLUTION = 48;
-static u32 NUM_LODS = 7;
+static u32 PATCH_RESOLUTION = 80;
+static u32 NUM_LODS = 5;
 
 static size_t patch2d( size_t x, size_t y ) {
 	return y * ( PATCH_RESOLUTION + 1 ) + x;
 }
 
-static u16 bswap16( u16 x ) {
-	return ( x >> 8 ) | ( x << 8 );
-}
-
-static void bswap16s( u16 * xs, size_t n ) {
-	for( size_t i = 0; i < n; i++ ) {
-		xs[ i ] = bswap16( xs[ i ] );
-	}
-}
-
-static float tan_theta( v2 a, v2 b ) {
-	v2 d = a - b;
-	return -d.y / d.x;
-}
-
-static void compute_row_of_horizons(
-	MemoryArena * arena,
-	const array2d< u16 > heightmap, array2d< float > horizons,
-	size_t y
-) {
-	MEMARENA_SCOPED_CHECKPOINT( arena );
-
-	array< v2 > hull = memarena_push_array( arena, v2, heightmap.w );
-	hull[ 0 ] = v2( 0, heightmap( 0, y ) / 256.0f );
-	horizons( 0, y ) = 0.0f;
-
-	size_t hull_size = 1;
-
-	for( size_t x = 1; x < heightmap.w; x++ ) {
-		v2 p( checked_cast< float >( x ), heightmap( x, y ) / 256.0f );
-
-		while( hull_size > 1 && tan_theta( p, hull[ hull_size - 1 ] ) <= tan_theta( hull[ hull_size - 1 ], hull[ hull_size - 2 ] ) ) {
-			hull_size--;
-		}
-
-		horizons( x, y ) = max( 0.0f, tan_theta( p, hull[ hull_size - 1 ] ) );
-
-		hull[ hull_size ] = p;
-		hull_size++;
-	}
-}
-
-static void compute_horizons(
-	MemoryArena * arena,
-	const array2d< u16 > heightmap, array2d< float > horizons
-) {
-	ASSERT( heightmap.w == horizons.w && heightmap.h == horizons.h );
-
-	for( size_t y = 0; y < heightmap.h; y++ ) {
-		compute_row_of_horizons( arena, heightmap, horizons, y );
-	}
-}
-
-static void compute_normals( const array2d< u16 > heightmap, array2d< v3 > normals ) {
-	// TODO: sobel operator?
-	// estimate the gradient at each point by doing central differences on
-	// each axis, then cross the tangent and bitangent to find the normal
-	for( size_t y = 0; y < heightmap.h; y++ ) {
-		for( size_t x = 0; x < heightmap.w; x++ ) {
-			size_t x_plus_one = x + 1;
-			size_t x_minus_one = x - 1;
-			size_t y_plus_one = y + 1;
-			size_t y_minus_one = y - 1;
-
-			if( x == 0 ) x_minus_one = x;
-			if( x == heightmap.w - 1 ) x_plus_one = x;
-			if( y == 0 ) y_minus_one = y;
-			if( y == heightmap.h - 1 ) y_plus_one = y;
-
-			v3 tangent(
-				checked_cast< float >( x_plus_one - x_minus_one ),
-				0,
-				heightmap( x_plus_one, y ) / 256.0f - heightmap( x_minus_one, y ) / 256.0f );
-			v3 bitangent(
-				0,
-				checked_cast< float >( y_plus_one - y_minus_one ),
-				heightmap( x, y_plus_one ) / 256.0f - heightmap( x, y_minus_one ) / 256.0f );
-
-			normals( x, y ) = normalize( cross( tangent, bitangent ) );
-		}
-	}
+static void file_get_contents_and_decompress( const char * path, u8 * decompressed, size_t decompressed_size ) {
+	size_t compressed_size;
+	u8 * compressed = file_get_contents( path, &compressed_size );
+	int ok = LZ4_decompress_safe( ( const char * ) compressed, ( char * ) decompressed, compressed_size, decompressed_size );
+	ASSERT( ok == decompressed_size );
+	free( compressed );
 }
 
 GAME_INIT( game_init ) {
 	{
-		u32 w, h;
-		u16 * heightmap_memory;
-		u32 ok = lodepng_decode_file( ( u8 ** ) &heightmap_memory, &w, &h, "terrains/gta16.png", LCT_GREY, 16 );
-		ASSERT( ok == 0 );
-		bswap16s( heightmap_memory, w * h );
-
-		v3 * normalmap_memory = malloc_array< v3 >( w * h );
-		float * horizonmap_float_memory = malloc_array< float >( w * h );
-		u8 * horizonmap_memory = malloc_array< u8 >( w * h );
-
-		array2d< u16 > heightmap_array( heightmap_memory, w, h );
-		array2d< v3 > normalmap_array( normalmap_memory, w, h );
-		array2d< float > horizonmap_float_array( horizonmap_float_memory, w, h );
-		array2d< u8 > horizonmap_array( horizonmap_memory, w, h );
-
-		printf( "decode done\n" );
-		compute_normals( heightmap_array, normalmap_array );
-		printf( "normals done\n" );
-		compute_horizons( &mem->persistent_arena, heightmap_array, horizonmap_float_array );
-		for( size_t y = 0; y < h; y++ ) {
-			for( size_t x = 0; x < h; x++ ) {
-				float theta01 = atanf( horizonmap_float_array( x, y ) ) * 2.0f / PI;
-				horizonmap_array( x, y ) = quantize01( theta01, 8 );
-			}
-		}
-		printf( "horizons done\n" );
+		MEMARENA_SCOPED_CHECKPOINT( &mem->persistent_arena );
+
+		u8 * heightmap_data = memarena_push_size( &mem->persistent_arena, 4096 * 4096 );
+		u8 * normalmap_data = memarena_push_size( &mem->persistent_arena, 4096 * 4096 );
+		u8 * horizonmap_data = memarena_push_size( &mem->persistent_arena, 4096 * 4096 / 2 );
 
+		file_get_contents_and_decompress( "terrains/gta16.png.parts/heightmap.bc5.lz4", heightmap_data, 4096 * 4096 );
+		file_get_contents_and_decompress( "terrains/gta16.png.parts/normalmap.bc5.lz4", normalmap_data, 4096 * 4096 );
+		file_get_contents_and_decompress( "terrains/gta16.png.parts/horizonmap.bc4.lz4", horizonmap_data, 4096 * 4096 / 2 );
 
 		{
 			TextureConfig texture_config;
-			texture_config.width = w;
-			texture_config.height = h;
-			texture_config.data = heightmap_memory;
-			texture_config.format = TEXFMT_R_U16;
+			texture_config.width = 4096;
+			texture_config.height = 4096;
+			texture_config.data = heightmap_data;
+			texture_config.format = TEXFMT_BC5;
 			texture_config.wrap = TEXWRAP_BORDER;
 			texture_config.border_colour = v4( 0 );
 			heightmap = renderer_new_texture( texture_config );
@@ -147,29 +54,25 @@ GAME_INIT( game_init ) {
 
 		{
 			TextureConfig texture_config;
-			texture_config.width = w;
-			texture_config.height = h;
-			texture_config.data = normalmap_memory;
-			texture_config.format = TEXFMT_RGB_FLOAT;
+			texture_config.width = 4096;
+			texture_config.height = 4096;
+			texture_config.data = normalmap_data;
+			texture_config.format = TEXFMT_BC5;
 			texture_config.wrap = TEXWRAP_BORDER;
-			texture_config.border_colour = v4( 0, 0, 1, 0 );
+			texture_config.border_colour = v4( 0.5f );
 			normalmap = renderer_new_texture( texture_config );
 		}
 
 		{
 			TextureConfig texture_config;
-			texture_config.width = w;
-			texture_config.height = h;
-			texture_config.data = horizonmap_memory;
-			texture_config.format = TEXFMT_R_U8;
+			texture_config.width = 4096;
+			texture_config.height = 4096;
+			texture_config.data = horizonmap_data;
+			texture_config.format = TEXFMT_BC4;
 			texture_config.wrap = TEXWRAP_BORDER;
 			texture_config.border_colour = v4( 0 );
 			horizonmap = renderer_new_texture( texture_config );
 		}
-
-		free( heightmap_memory );
-		free( normalmap_memory );
-		free( horizonmap_memory );
 	}
 
 	{
@@ -184,12 +87,12 @@ GAME_INIT( game_init ) {
 		for( u32 y = 0; y < PATCH_RESOLUTION; y++ ) {
 			for( u32 x = 0; x < PATCH_RESOLUTION; x++ ) {
 				indices.append( patch2d( x, y ) );
-				indices.append( patch2d( x, y + 1 ) );
 				indices.append( patch2d( x + 1, y + 1 ) );
+				indices.append( patch2d( x, y + 1 ) );
 
 				indices.append( patch2d( x, y ) );
-				indices.append( patch2d( x + 1, y + 1 ) );
 				indices.append( patch2d( x + 1, y ) );
+				indices.append( patch2d( x + 1, y + 1 ) );
 			}
 		}
 
diff --git a/make.lua b/make.lua
@@ -46,7 +46,7 @@ bin( "bsp", { "main", "bsp", "bsp_renderer", game_objs }, { game_libs } )
 -- TODO: fix btt build
 -- bin( "btt", { "main", "mod_btt", "btt", "heightmap", "skybox", "lz4", game_objs }, { "stb_image", game_libs } )
 bin( "sm", { "main", "shadow_map", game_objs }, { game_libs } )
-bin( "clipmap", { "main", "clipmap", "skybox", game_objs }, { game_libs } )
+bin( "clipmap", { "main", "clipmap", "skybox", game_objs }, { "lz4", game_libs } )
 
 msvc_bin_ldflags( "bsp", "opengl32.lib gdi32.lib" )
 gcc_bin_ldflags( "bsp", game_ldflags )
@@ -67,6 +67,10 @@ bin( "pp", { "pp", "heightmap", common_objs }, { "lodepng", "lz4", "squish", "st
 gcc_obj_cxxflags( "pp", "-O2" )
 msvc_obj_cxxflags( "pp", "/O2" )
 
+bin( "pp2", { "pp2", common_objs }, { "lz4", "squish", "stb_image", "stb_image_write" } )
+gcc_obj_cxxflags( "pp2", "-O2" )
+msvc_obj_cxxflags( "pp2", "/O2" )
+
 if OS == "linux" then
 	bin( "utils/genkeys/genkeys", { "utils/genkeys/genkeys", common_objs }, { "monocypher" } )
 	bin( "utils/genkeys/sign", { "utils/genkeys/sign", common_objs }, { "monocypher" } )
diff --git a/pp2.cc b/pp2.cc
@@ -0,0 +1,206 @@
+#include "intrinsics.h"
+#include "array.h"
+#include "memory_arena.h"
+#include "linear_algebra.h"
+#include "dynstr.h"
+#include "int_conversions.h"
+#include "autogdb.h"
+
+#include "libs/squish/squish.h"
+
+#include "libs/stb/stb_image.h"
+#include "libs/stb/stb_image_write.h"
+
+#include "libs/lz4/lz4.h"
+#include "libs/lz4/lz4hc.h"
+
+static float tan_theta( v2 a, v2 b ) {
+	v2 d = a - b;
+	return -d.y / d.x;
+}
+
+static void compute_row_of_horizons(
+	MemoryArena * arena,
+	const array2d< u16 > heightmap, array2d< float > horizons,
+	size_t y
+) {
+	MEMARENA_SCOPED_CHECKPOINT( arena );
+
+	array< v2 > hull = memarena_push_array( arena, v2, heightmap.w );
+	hull[ 0 ] = v2( 0, heightmap( 0, y ) / 256.0f );
+	horizons( 0, y ) = 0.0f;
+
+	size_t hull_size = 1;
+
+	for( size_t x = 1; x < heightmap.w; x++ ) {
+		v2 p( checked_cast< float >( x ), heightmap( x, y ) / 256.0f );
+
+		while( hull_size > 1 && tan_theta( p, hull[ hull_size - 1 ] ) <= tan_theta( hull[ hull_size - 1 ], hull[ hull_size - 2 ] ) ) {
+			hull_size--;
+		}
+
+		horizons( x, y ) = max( 0.0f, tan_theta( p, hull[ hull_size - 1 ] ) );
+
+		hull[ hull_size ] = p;
+		hull_size++;
+	}
+}
+
+static void compute_horizons(
+	MemoryArena * arena,
+	const array2d< u16 > heightmap, array2d< float > horizons
+) {
+	ASSERT( heightmap.w == horizons.w && heightmap.h == horizons.h );
+
+	for( size_t y = 0; y < heightmap.h; y++ ) {
+		compute_row_of_horizons( arena, heightmap, horizons, y );
+	}
+}
+
+static void compute_normals( const array2d< u16 > heightmap, array2d< v3 > normals ) {
+	// TODO: sobel operator?
+	// estimate the gradient at each point by doing central differences on
+	// each axis, then cross the tangent and bitangent to find the normal
+	for( size_t y = 0; y < heightmap.h; y++ ) {
+		for( size_t x = 0; x < heightmap.w; x++ ) {
+			size_t x_plus_one = x + 1;
+			size_t x_minus_one = x - 1;
+			size_t y_plus_one = y + 1;
+			size_t y_minus_one = y - 1;
+
+			if( x == 0 ) x_minus_one = x;
+			if( x == heightmap.w - 1 ) x_plus_one = x;
+			if( y == 0 ) y_minus_one = y;
+			if( y == heightmap.h - 1 ) y_plus_one = y;
+
+			v3 tangent(
+				checked_cast< float >( x_plus_one - x_minus_one ),
+				0,
+				heightmap( x_plus_one, y ) / 256.0f - heightmap( x_minus_one, y ) / 256.0f );
+			v3 bitangent(
+				0,
+				checked_cast< float >( y_plus_one - y_minus_one ),
+				heightmap( x, y_plus_one ) / 256.0f - heightmap( x, y_minus_one ) / 256.0f );
+
+			normals( x, y ) = normalize( cross( tangent, bitangent ) );
+		}
+	}
+}
+
+static void write_compressed_file( MemoryArena * arena, const DynamicString & path, const void * data, size_t len ) {
+	MEMARENA_SCOPED_CHECKPOINT( arena );
+
+	const int COMPRESSION_LEVEL = 6;
+	int lz4_bound = LZ4_compressBound( len );
+	char * lz4 = memarena_push_many( arena, char, lz4_bound );
+	int lz4_size = LZ4_compress_HC( ( const char * ) data, lz4, len, lz4_bound, COMPRESSION_LEVEL );
+
+	FILE * file = fopen( path.c_str(), "wb" );
+	ASSERT( file != NULL );
+	fwrite( lz4, 1, lz4_size, file );
+	ASSERT( ferror( file ) == 0 );
+	fclose( file );
+}
+
+struct RGBA { u8 r, g, b, a; };
+
+static void write_compressed_texture( MemoryArena * arena, const DynamicString & path, const array2d< RGBA > data, int squish_flags ) {
+	MEMARENA_SCOPED_CHECKPOINT( arena );
+
+	int w = checked_cast< int >( data.w );
+	int h = checked_cast< int >( data.h );
+
+	int dxt_size = squish::GetStorageRequirements( w, h, squish_flags );
+	void * dxt = memarena_push_size( arena, dxt_size );
+	squish::CompressImage( ( const u8 * ) data.ptr(), w, h, dxt, squish_flags );
+
+	write_compressed_file( arena, path, dxt, dxt_size );
+
+	// DynamicString png_path( "{}.png", path );
+	// stbi_write_png( png_path.c_str(), data.w, data.h, 4, data.ptr(), 0 );
+}
+
+int main( int argc, char ** argv ) {
+	install_debug_signal_handlers( true );
+
+	static u8 arena_memory[ megabytes( 512 ) ];
+	MemoryArena arena;
+	memarena_init( &arena, arena_memory, ARRAY_COUNT( arena_memory ) );
+
+	DynamicString input_path( "terrains/gta16.png" );
+	DynamicString output_path( "{}.parts", input_path );
+
+	printf( "decoding\n" );
+	int w, h;
+	u16 * png = ( u16 * ) stbi_load_16( input_path.c_str(), &w, &h, NULL, 1 );
+	ASSERT( png != NULL );
+
+	array2d< u16 > heightmap( png, w, h );
+	array2d< RGBA > heightmap_split = alloc_array2d< RGBA >( &arena, w, h );
+
+	array2d< v3 > normalmap_v3 = alloc_array2d< v3 >( &arena, w, h );
+	array2d< RGBA > normalmap = alloc_array2d< RGBA >( &arena, w, h );
+
+	array2d< float > horizonmap_float = alloc_array2d< float >( &arena, w, h );
+	array2d< RGBA > horizonmap = alloc_array2d< RGBA >( &arena, w, h );
+
+	{
+		MEMARENA_SCOPED_CHECKPOINT( &arena );
+		printf( "computing heightmaps\n" );
+		for( int y = 0; y < h; y++ ) {
+			for( int x = 0; x < w; x++ ) {
+				heightmap_split( x, y ).r = heightmap( x, y ) / 256;
+				heightmap_split( x, y ).g = heightmap( x, y ) % 256;
+				heightmap_split( x, y ).b = 0;
+				heightmap_split( x, y ).a = 255;
+			}
+		}
+
+		printf( "compressing\n" );
+		DynamicString heightmap_path( "{}/heightmap.bc5.lz4", output_path );
+		write_compressed_texture( &arena, heightmap_path, heightmap_split, squish::kBc5 );
+	}
+
+	{
+		MEMARENA_SCOPED_CHECKPOINT( &arena );
+		printf( "computing normalmap\n" );
+
+		compute_normals( heightmap, normalmap_v3 );
+		for( int y = 0; y < h; y++ ) {
+			for( int x = 0; x < w; x++ ) {
+				normalmap( x, y ).r = quantize01( ( normalmap_v3( x, y ).x + 1.0f ) / 2.0f, 8 );
+				normalmap( x, y ).g = quantize01( ( normalmap_v3( x, y ).y + 1.0f ) / 2.0f, 8 );
+				normalmap( x, y ).b = 0;
+				normalmap( x, y ).a = 255;
+			}
+		}
+
+		printf( "compressing\n" );
+		DynamicString normalmap_path( "{}/normalmap.bc5.lz4", output_path );
+		write_compressed_texture( &arena, normalmap_path, normalmap, squish::kBc5 );
+	}
+
+	{
+		MEMARENA_SCOPED_CHECKPOINT( &arena );
+		printf( "computing horizonmap\n" );
+
+		compute_horizons( &arena, heightmap, horizonmap_float );
+		for( int y = 0; y < h; y++ ) {
+			for( int x = 0; x < w; x++ ) {
+				float theta01 = atanf( horizonmap_float( x, y ) ) * 2.0f / PI;
+				horizonmap( x, y ).r = quantize01( theta01, 8 );
+				horizonmap( x, y ).g = 0;
+				horizonmap( x, y ).b = 0;
+				horizonmap( x, y ).a = 255;
+			}
+		}
+
+		printf( "compressing\n" );
+		DynamicString horizonmap_path( "{}/horizonmap.bc4.lz4", output_path );
+		write_compressed_texture( &arena, horizonmap_path, horizonmap, squish::kBc4 );
+	}
+
+	stbi_image_free( png );
+
+	return 0;
+}
diff --git a/shaders/clipmap.glsl b/shaders/clipmap.glsl
@@ -33,9 +33,12 @@ out VSOut v2f;
 void main() {
 	vec2 xy = offset + position.xy * scale + camera_pos.xy;
 	vec2 uv = floor( xy / scale ) * scale / textureSize( heightmap, 0 ) + 0.5;
-	float z = 255.0 * texture( heightmap, uv ).r;
+	float z = 255.0 * texture( heightmap, uv ).r + texture( heightmap, uv ).g;
+
+	vec2 normal_xy = texture( normalmap, uv ).xy * 2.0 - 1.0;
+	float normal_z = sqrt( 1.0 - normal_xy.x * normal_xy.x - normal_xy.y * normal_xy.y );
+	vec3 normal = vec3( normal_xy.x, normal_xy.y, normal_z );
 
-	vec3 normal = normalize( texture( normalmap, uv ).rgb );
 	float horizon_angle = texture( horizonmap, uv ).r;
 
 	v2f.pos = vec3( xy, z );
@@ -85,7 +88,7 @@ void main() {
 	vec3 dither_noise = texture( blue_noise, gl_FragCoord.xy / textureSize( blue_noise, 0 ) ).xxx;
 	dither_noise = ( dither_noise - vec3( 0.5 ) ) / 128.0;
 
-	screen_colour = vec4( c + dither_noise, 1.0 );
+	screen_colour = vec4( linear_to_srgb( c + dither_noise ), 1.0 );
 }
 
 #endif