medfall

A super great game engine
Log | Files | Refs

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