medfall

A super great game engine
Log | Files | Refs

commit 4b28975f4ea49f30d0b239cbbc4414353b18cb0f
parent 81f084a14db336768a5e83d0df99b2f99333a510
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Sat, 21 Oct 2017 13:39:01 +0300

Clipmap terrain uses normals/horizons now

Diffstat:
clipmap.cc | 157++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
shaders.cc | 3+++
shaders/clipmap.glsl | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
3 files changed, 206 insertions(+), 26 deletions(-)

diff --git a/clipmap.cc b/clipmap.cc @@ -1,4 +1,5 @@ #include "intrinsics.h" +#include "int_conversions.h" #include "game.h" #include "renderer.h" #include "shaders.h" @@ -10,6 +11,8 @@ static Mesh patch; static Texture heightmap; +static Texture normalmap; +static Texture horizonmap; static u32 PATCH_RESOLUTION = 48; static u32 NUM_LODS = 7; @@ -28,6 +31,79 @@ static void bswap16s( u16 * xs, size_t n ) { } } +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 ) ); + } + } +} + GAME_INIT( game_init ) { { u32 w, h; @@ -36,15 +112,67 @@ GAME_INIT( game_init ) { ASSERT( ok == 0 ); bswap16s( heightmap_memory, w * h ); - TextureConfig texture_config; - texture_config.width = w; - texture_config.height = h; - texture_config.data = heightmap_memory; - texture_config.format = TEXFMT_R_U16; - texture_config.wrap = TEXWRAP_BORDER; - texture_config.border_colour = v4( 0 ); - texture_config.srgb = false; - heightmap = renderer_new_texture( texture_config ); + 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" ); + + + { + TextureConfig texture_config; + texture_config.width = w; + texture_config.height = h; + texture_config.data = heightmap_memory; + texture_config.format = TEXFMT_R_U16; + texture_config.wrap = TEXWRAP_BORDER; + texture_config.border_colour = v4( 0 ); + texture_config.srgb = false; + heightmap = renderer_new_texture( texture_config ); + } + + { + TextureConfig texture_config; + texture_config.width = w; + texture_config.height = h; + texture_config.data = normalmap_memory; + texture_config.format = TEXFMT_RGB_FLOAT; + texture_config.wrap = TEXWRAP_BORDER; + texture_config.border_colour = v4( 0, 0, 1, 0 ); + texture_config.srgb = false; + 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.wrap = TEXWRAP_BORDER; + texture_config.border_colour = v4( 0 ); + texture_config.srgb = false; + horizonmap = renderer_new_texture( texture_config ); + } + + free( heightmap_memory ); + free( normalmap_memory ); + free( horizonmap_memory ); } { @@ -100,12 +228,16 @@ GAME_FRAME( game_frame ) { game->pitch = clamp( game->pitch + dpitch * dt * 100, -89.9f, 89.9f ); game->yaw += dyaw * dt * 100; + const float dsun = ( input->keys[ KEY_EQUALS ] - input->keys[ KEY_MINUS ] ) * dt * 0.25f; + game->sun_angle += dsun; + const v3 world_up = v3( 0, 0, 1 ); v3 forward = v3_forward( game->pitch, game->yaw ); v3 right = normalize( cross( forward, world_up ) ); v3 up = normalize( cross( right, forward ) ); const float speed = 100.0f; + // const float speed = 1.0f; game->pos += forward * dt * fb * speed; game->pos += right * dt * lr * speed; game->pos.z += dt * dz * speed; @@ -121,6 +253,7 @@ GAME_FRAME( game_frame ) { skybox_render( &game->skybox, Vsky, P, game->sun_angle, sun_dir ); UniformBinding view_uniforms = renderer_uniforms( V, P, game->pos ); + UniformBinding sun_uniforms = renderer_uniforms( sun_dir, game->sun_angle ); for( u32 l = 0; l < NUM_LODS; l++ ) { float scale = 1.0f; @@ -141,9 +274,13 @@ GAME_FRAME( game_frame ) { RenderState render_state; render_state.shader = get_shader( SHADER_CLIPMAP ); render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms; + render_state.uniforms[ UNIFORMS_SUN ] = sun_uniforms; render_state.uniforms[ UNIFORMS_CLIPMAP ] = renderer_uniforms( offset, scale ); render_state.textures[ 0 ] = heightmap; - render_state.wireframe = true; + render_state.textures[ 1 ] = normalmap; + render_state.textures[ 2 ] = horizonmap; + render_state.textures[ 3 ] = renderer_blue_noise(); + // render_state.wireframe = true; renderer_draw_mesh( patch, render_state ); } } diff --git a/shaders.cc b/shaders.cc @@ -71,6 +71,9 @@ void shaders_init() { shaders[ SHADER_CLIPMAP ].path = "shaders/clipmap.glsl"; shaders[ SHADER_CLIPMAP ].texture_uniform_names[ 0 ] = "heightmap"; + shaders[ SHADER_CLIPMAP ].texture_uniform_names[ 1 ] = "normalmap"; + shaders[ SHADER_CLIPMAP ].texture_uniform_names[ 2 ] = "horizonmap"; + shaders[ SHADER_CLIPMAP ].texture_uniform_names[ 3 ] = "blue_noise"; int failed = hotload_shaders(); if( failed != 0 ) { diff --git a/shaders/clipmap.glsl b/shaders/clipmap.glsl @@ -4,17 +4,26 @@ layout( std140 ) uniform view { vec3 camera_pos; }; +layout( std140 ) uniform sun { + vec3 sun_dir; + float sun_angle; +}; + layout( std140 ) uniform clipmap { vec2 offset; float scale; }; struct VSOut { - vec3 world_pos; - vec3 world_normal; + vec3 pos; + vec3 normal; + float horizon; }; uniform sampler2D heightmap; +uniform sampler2D normalmap; +uniform sampler2D horizonmap; +uniform sampler2D blue_noise; #ifdef VERTEX_SHADER @@ -23,18 +32,15 @@ out VSOut v2f; void main() { vec2 xy = offset + position.xy * scale + camera_pos.xy; - vec2 uv = floor( xy ) / textureSize( heightmap, 0 ) + 0.5; - float z = 255.0 * texture2D( heightmap, uv ).r; - - vec3 normal = normalize( cross( - vec3( 2.0, 0.0, ( 255.0 * texture2D( heightmap, ( floor( xy ) + vec2( 1.0, 0.0 ) ) / textureSize( heightmap, 0 ) + 0.5 ).r - - texture2D( heightmap, ( floor( xy ) - vec2( 1.0, 0.0 ) ) / textureSize( heightmap, 0 ) + 0.5 ).r ) ), - vec3( 0.0, 2.0, ( 255.0 * texture2D( heightmap, ( floor( xy ) + vec2( 0.0, 1.0 ) ) / textureSize( heightmap, 0 ) + 0.5 ).r - - texture2D( heightmap, ( floor( xy ) - vec2( 0.0, 1.0 ) ) / textureSize( heightmap, 0 ) + 0.5 ).r ) ) - ) ); - - v2f.world_pos = vec3( xy, z ); - v2f.world_normal = normal; + vec2 uv = floor( xy / scale ) * scale / textureSize( heightmap, 0 ) + 0.5; + float z = 255.0 * texture( heightmap, uv ).r; + + vec3 normal = normalize( texture( normalmap, uv ).rgb ); + float horizon_angle = texture( horizonmap, uv ).r; + + v2f.pos = vec3( xy, z ); + v2f.normal = normal; + v2f.horizon = horizon_angle; gl_Position = P * V * vec4( floor( xy ), z, 1.0 ); } @@ -44,8 +50,42 @@ in VSOut v2f; out vec4 screen_colour; void main() { - vec4 colour = vec4( 0.3, 0.3, 1.0, 1.0 ); - screen_colour = colour * v2f.world_normal.z; + // ground colour + vec3 ground; + if( v2f.pos.z > 175 ) { + // snow/rocks + if( v2f.normal.z > 0.5 ) { + ground = vec3( 0.8, 0.8, 0.8 ); + } + else { + ground = vec3( 0.6, 0.6, 0.6 ); + } + } + else if( v2f.pos.z < 5 ) { + ground = vec3( 0.0, 0.25, 1.0 ); + } + else { + if( v2f.normal.z > 0.8 ) { + ground = vec3( 0.4, 1.0, 0.4 ); + } + else { + ground = vec3( 0.7, 0.7, 0.5 ); + } + } + + // sunlight + sky ambient lighting + // area of a chord is somewhat similar to smoothstep + float sun_visible_fraction = smoothstep( -0.2, 0.2, sun_angle - v2f.horizon ); + float sunlight_lambert = max( 0, dot( v2f.normal, sun_dir ) ); + vec3 sunlight = sun_visible_fraction * sunlight_lambert * vec3( 0.9, 0.9, 0.5 ); + vec3 ambient = vec3( 0.05, 0.05, 0.15 ); + + vec3 c = ( sunlight + ambient ) * ground; + + 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 ); } #endif