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