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