commit 997116b06f319a6d1732fd1f9a4123234901b4a7 parent 9d5cd5e29fa76144e4bcf0ea1c622c478e04c915 Author: Michael Savage <mikejsavage@gmail.com> Date: Sun May 21 16:17:31 +0300 Add trees to the terrain renderer Diffstat:
hm.cc | | | 46 | +++++++++++++++++++++++++++++++++++++++++++--- |
pp.cc | | | 154 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
renderer.cc | | | 37 | +++++++++++++++++++++++++++++++++---- |
renderer.h | | | 1 | + |
shaders/tree.glsl | | | 54 | ++++++++++++++++++++++++++++++++++++++++++++---------- |
terrain_manager.cc | | | 8 | +------- |
diff --git a/hm.cc b/hm.cc @@ -1,6 +1,3 @@ -#include <stdlib.h> -#include <math.h> - #include "game.h" #include "intrinsics.h" #include "log.h" @@ -15,6 +12,7 @@ #include "hashtable.h" #include "stream.h" #include "http.h" +#include "obj.h" #include "profiler.h" #include "platform_io.h" #include "platform_network.h" @@ -25,6 +23,31 @@ static double last_connection_attempt = -5.0; // TODO static bool connected = false; static struct sockaddr_in addr; static UB ub_view; +static UB ub_model; +static UB ub_sun; +static VB instance_data; +static u32 num_trees; + +static Mesh tree_mesh; + +static void load_trees( MemoryArena * arena ) { + MEMARENA_SCOPED_CHECKPOINT( arena ); + + array< v3 > trees = file_get_array< v3 >( "terrains/gta.png.parts/trees.lz4" ); + array< m4 > tree_matrices = memarena_push_array( arena, m4, trees.n ); + + m4 rot = m4_rotx( deg_to_rad( 90 ) ); + for( size_t i = 0; i < trees.n; i++ ) { + tree_matrices[ i ] = m4_translation( trees[ i ] ) * rot; + } + + instance_data = renderer_new_vb( tree_matrices ); + + free( trees.ptr() ); + + tree_mesh = load_obj( "models/trees/PineTree.obj", arena ); + num_trees = trees.n; +} extern "C" GAME_INIT( game_init ) { net_init(); @@ -39,6 +62,8 @@ extern "C" GAME_INIT( game_init ) { game->test_sun = 0.3f; ub_view = renderer_new_ub(); + ub_model = renderer_new_ub(); + ub_sun = renderer_new_ub(); const size_t triangles = 65536; ImmediateTriangle * immediate_memory = memarena_push_many( &mem->persistent_arena, ImmediateTriangle, triangles ); @@ -76,6 +101,8 @@ extern "C" GAME_INIT( game_init ) { skybox_init( &game->skybox ); + load_trees( &mem->persistent_arena ); + sock_init( &sock, NET_IPV4, NET_UDP, NET_NONBLOCKING ); struct sockaddr_storage addrs = { }; @@ -168,6 +195,19 @@ extern "C" GAME_FRAME( game_frame ) { struct { m4 V; m4 P; } uniforms = { V, P }; renderer_ub_data( ub_view, &uniforms, sizeof( uniforms ) ); + { + RenderState render_state; + render_state.shader = get_shader( SHADER_TREE ); + render_state.ubs[ UB_VIEW ] = ub_view; + render_state.ubs[ UB_MODEL ] = ub_model; + render_state.ubs[ UB_FS_HOT ] = ub_sun; + render_state.cull_face = CULLFACE_FRONT; + + renderer_ub_easy( ub_sun, game->test_sun ); + + renderer_draw_instances( tree_mesh, render_state, num_trees, instance_data ); + } + RenderState immediate_render_state; immediate_render_state.shader = get_shader( SHADER_SHIT ); immediate_render_state.depth_func = DEPTHFUNC_DISABLED; diff --git a/pp.cc b/pp.cc @@ -4,8 +4,6 @@ // 2. parallelise horizon/normal computations #include <string> -#include <stdio.h> - #include "intrinsics.h" #include "memory_arena.h" #include "array.h" @@ -14,6 +12,9 @@ #include "heightmap.h" #include "platform_io.h" #include "linear_algebra.h" +#include "renderer.h" + +#include "rng/well512.h" #include "lz4.h" #include "lz4hc.h" @@ -79,10 +80,10 @@ static void compute_normals( const array2d< u8 > heightmap, array2d< v3 > normal v3 tangent( checked_cast< float >( x_plus_one - x_minus_one ), - 0.0f, + 0, checked_cast< float >( heightmap( x_plus_one, y ) - heightmap( x_minus_one, y ) ) ); v3 bitangent( - 0.0f, + 0, checked_cast< float >( y_plus_one - y_minus_one ), checked_cast< float >( heightmap( x, y_plus_one ) - heightmap( x, y_minus_one ) ) ); @@ -91,23 +92,93 @@ static void compute_normals( const array2d< u8 > heightmap, array2d< v3 > normal } } +static void hammersley( size_t n, array< v2 > points ) { + for( size_t i = 0; i < n; i++ ) { + float u = 0; + float v = ( i + 0.5f ) / n; + float p = 0.5; + + size_t x = i; + while( x > 0 ) { + if( x % 2 == 1 ) u += p; + p /= 2; + x /= 2; + } + + points[ i ] = v2( u, v ); + } +} + const int TILE_SIZE = 512; const int OUT_SIZE = TILE_SIZE + 1; +const size_t MAX_TREES = 262144 / 4; + +static bool ok_tree_position( RNGWell512 * rng, const array2d< u8 > heightmap, const array2d< v3 > normals, v2 pos, v3 * tree ) { + u8 height = bilerp01( heightmap, pos.x, pos.y ); + double p = 1; + if( height <= 5 ) return false; + if( height > 235 ) return false; + if( height >= 200 ) { + p *= double( 235 - height ) / double( 235 - 200 ); + } + + v3 normal = normalize( bilerp01( normals, pos.x, pos.y ) ); + if( normal.z < 0.3 ) return false; + + p *= ( normal.z - 0.3 ) / 0.7; + + if( !rng_p( rng, p ) ) return false; + + *tree = v3( pos.x * heightmap.w, pos.y * heightmap.h, height ); + return true; +} + +static size_t place_trees( MemoryArena * arena, const array2d< u8 > heightmap, const array2d< v3 > normals, array< v3 > trees ) { + MEMARENA_SCOPED_CHECKPOINT( arena ); + + array< v2 > points = memarena_push_array( arena, v2, trees.n ); + hammersley( MAX_TREES, points ); + + RNGWell512 rng; + rng_well512_init( &rng ); + + size_t placed = 0; + for( v2 point : points ) { + v3 pos; + if( ok_tree_position( &rng, heightmap, normals, point, &pos ) ) { + trees[ placed ] = pos; + placed++; + } + } + + return placed; +} + +template< typename... Rest > +static void write_file( const void * data, size_t len, const char * fmt, Rest... rest ) { + str< 2048 > path( fmt, rest... ); + + // write + FILE * file = fopen( path.c_str(), "wb" ); + ASSERT( file != NULL ); + fwrite( data, 1, len, file ); + ASSERT( ferror( file ) == 0 ); + fclose( file ); +} + +template< typename... Rest > +static void write_compressed_file( MemoryArena * arena, const void * data, size_t len, const char * fmt, Rest... rest ) { + MEMARENA_SCOPED_CHECKPOINT( arena ); -static void write_compressed_file( const void * data, size_t len, const char * fmt, ... ) { - str< 2048 > path; - va_list argp; - va_start( argp, fmt ); - path.sprintf( fmt, argp ); - va_end( argp ); + str< 2048 > path( fmt, rest... ); // compress const int COMPRESSION_LEVEL = 6; - char * compressed = new char[ LZ4_compressBound( len ) ]; + char * compressed = memarena_push_many( arena, char, LZ4_compressBound( len ) ); size_t compressed_len = LZ4_compress_HC( ( const char * ) data, compressed, len, LZ4_compressBound( len ), COMPRESSION_LEVEL ); - ggprint( "{}: compressed {} to {} ({.2})\n", path, len, compressed_len, 100.0f * compressed_len / len ); + ggprint( "{}: compressed {} to {} ({.2}%)\n", path, len, compressed_len, 100.0f * compressed_len / len ); // write FILE * file = fopen( path.c_str(), "wb" ); @@ -115,9 +186,6 @@ static void write_compressed_file( const void * data, size_t len, const char * f fwrite( compressed, 1, compressed_len, file ); ASSERT( ferror( file ) == 0 ); fclose( file ); - - // clean up - delete[] compressed; } template< typename T > @@ -146,11 +214,11 @@ static void write_tile( } ggprint( "writing {}_{}{}... ", tx, ty, extension.c_str() ); - write_compressed_file( tile.ptr(), tile.num_bytes(), + write_compressed_file( arena, tile.ptr(), tile.num_bytes(), "{}/{}_{}{}", dir.c_str(), tx, ty, extension.c_str() ); } -static void write_tile_quadtree( +static void write_quadtree( MemoryArena * arena, const std::string & dir, const array2d< u8 > heightmap, int tx, int ty ) { @@ -182,7 +250,7 @@ static void write_tile_quadtree( HeightmapQuadTree qt = heightmap_build_quadtree( &hm, nodes ); ggprint( "writing {}_{}_quadtree.lz4... ", tx, ty ); - write_compressed_file( qt.nodes.ptr(), qt.nodes.num_bytes(), + write_compressed_file( arena, qt.nodes.ptr(), qt.nodes.num_bytes(), "{}/{}_{}_quadtree.lz4", dir.c_str(), tx, ty ); } @@ -195,7 +263,7 @@ int main( int argc, char ** argv ) { install_debug_signal_handlers( true ); #endif - static u8 arena_memory[ megabytes( 64 ) ]; + static u8 arena_memory[ megabytes( 512 ) ]; MemoryArena arena; memarena_init( &arena, arena_memory, ARRAY_COUNT( arena_memory ) ); @@ -216,51 +284,63 @@ int main( int argc, char ** argv ) { const array2d< u8 > heightmap( heightmap_memory, w, h ); + int xtiles = ( w + TILE_SIZE - 2 ) / TILE_SIZE; + int ytiles = ( h + TILE_SIZE - 2 ) / TILE_SIZE; + { - for( int ty = 0; ty < h / TILE_SIZE; ty++ ) { - for( int tx = 0; tx < w / TILE_SIZE; tx++ ) { - write_tile( &arena, dir, "_heightmap.lz4", heightmap, u8( 0 ), tx, ty ); + for( int ty = 0; ty < ytiles; ty++ ) { + for( int tx = 0; tx < xtiles; tx++ ) { + write_tile( &arena, dir, "_heightmap.lz4", heightmap, u8( 0 ), tx, ty ); } } } printf( "generating horizons\n" ); { - // TODO: quantize horizons. maybe we only need to store the convex hull too - float * horizons_memory = new float[ w * h ]; - array2d< float > horizons( horizons_memory, w, h ); + MEMARENA_SCOPED_CHECKPOINT( &arena ); + array2d< float > horizons = memarena_push_array2d( &arena, float, w, h ); + // TODO: quantize horizons. maybe we only need to store the convex hull too compute_horizons( &arena, heightmap, horizons ); - for( int ty = 0; ty < h / TILE_SIZE; ty++ ) { - for( int tx = 0; tx < w / TILE_SIZE; tx++ ) { + for( int ty = 0; ty < ytiles; ty++ ) { + for( int tx = 0; tx < xtiles; tx++ ) { + // write_horizons( &arena, dir, horizons, tx, ty ); write_tile( &arena, dir, "_horizons.lz4", horizons, 0.0f, tx, ty ); } } - - delete[] horizons_memory; } printf( "generating normals\n" ); { - // TODO: normals can be v2 since z >= 0 on a heightmap - v3 * normals_memory = new v3[ w * h ]; - array2d< v3 > normals( normals_memory, w, h ); + MEMARENA_SCOPED_CHECKPOINT( &arena ); + array2d< v3 > normals = memarena_push_array2d( &arena, v3, w, h ); + // TODO: normals can be v2 since z >= 0 on a heightmap compute_normals( heightmap, normals ); - for( int ty = 0; ty < h / TILE_SIZE; ty++ ) { - for( int tx = 0; tx < w / TILE_SIZE; tx++ ) { + for( int ty = 0; ty < ytiles; ty++ ) { + for( int tx = 0; tx < xtiles; tx++ ) { write_tile( &arena, dir, "_normals.lz4", normals, v3( 0.0f, 0.0f, 1.0f ), tx, ty ); } } + + printf( "placing trees\n" ); + { + v3 * trees_memory = new v3[ MAX_TREES ]; + array< v3 > trees( trees_memory, MAX_TREES ); + size_t num_trees = place_trees( &arena, heightmap, normals, trees ); + + printf( "writing trees\n" ); + write_file( trees_memory, num_trees * sizeof( v3 ), "{}/trees.lz4", dir.c_str() ); + } } printf( "generating collision quadtrees\n" ); { - for( int ty = 0; ty < h / TILE_SIZE; ty++ ) { - for( int tx = 0; tx < w / TILE_SIZE; tx++ ) { - write_tile_quadtree( &arena, dir, heightmap, tx, ty ); + for( int ty = 0; ty < ytiles; ty++ ) { + for( int tx = 0; tx < xtiles; tx++ ) { + write_quadtree( &arena, dir, heightmap, tx, ty ); } } } diff --git a/renderer.cc b/renderer.cc @@ -20,6 +20,7 @@ static const GLuint ATTR_NORMAL = 1; static const GLuint ATTR_TEX_COORD0 = 2; static const GLuint ATTR_TEX_COORD1 = 3; static const GLuint ATTR_COLOUR = 4; +static const GLuint ATTR_MODEL_TO_WORLD_COL0 = 5; void renderer_begin_frame( ClearColourBool clear_colour, ClearDepthBool clear_depth ) { GLbitfield clear_mask = 0; @@ -291,6 +292,7 @@ Shader renderer_new_shader( ShaderConfig config ) { glBindAttribLocation( program, ATTR_TEX_COORD0, "tex_coord0" ); glBindAttribLocation( program, ATTR_TEX_COORD1, "tex_coord1" ); glBindAttribLocation( program, ATTR_COLOUR, "colour" ); + glBindAttribLocation( program, ATTR_MODEL_TO_WORLD_COL0, "model_to_world" ); glLinkProgram( program ); @@ -627,11 +629,10 @@ void renderer_clear_fb( FB fb ) { glClear( fb.attachment == FB_COLOUR ? GL_COLOR_BUFFER_BIT : GL_DEPTH_BUFFER_BIT ); } -void renderer_draw_mesh( const Mesh & mesh, const RenderState & state ) { +static void set_render_state( const RenderState & state ) { if( state.shader != previous_render_state.shader ) { glUseProgram( state.shader ); } - glBindVertexArray( mesh.vao ); // uniforms for( GLuint i = 0; i < RENDERER_MAX_UBS; i++ ) { @@ -712,6 +713,13 @@ void renderer_draw_mesh( const Mesh & mesh, const RenderState & state ) { } } + previous_render_state = state; +} + +void renderer_draw_mesh( const Mesh & mesh, const RenderState & state ) { + set_render_state( state ); + + glBindVertexArray( mesh.vao ); GLenum primitive = primitivetype_to_glenum( mesh.primitive_type ); if( mesh.indices != 0 ) { glDrawElements( primitive, mesh.num_vertices, GL_UNSIGNED_INT, 0 ); @@ -719,8 +727,29 @@ void renderer_draw_mesh( const Mesh & mesh, const RenderState & state ) { else { glDrawArrays( primitive, 0, mesh.num_vertices ); } - glBindVertexArray( 0 ); +} - previous_render_state = state; +void renderer_draw_instances( const Mesh & mesh, const RenderState & state, u32 num_instances, VB instance_data ) { + set_render_state( state ); + + glBindVertexArray( mesh.vao ); + glBindBuffer( GL_ARRAY_BUFFER, instance_data ); + + for( int i = 0; i < 4; i++ ) { + GLuint loc = ATTR_MODEL_TO_WORLD_COL0 + i; + glEnableVertexAttribArray( loc ); + glVertexAttribPointer( loc, 4, GL_FLOAT, GL_FALSE, sizeof( m4 ), ( GLvoid * ) ( i * sizeof( v4 ) ) ); + glVertexAttribDivisor( loc, 1 ); + } + + GLenum primitive = primitivetype_to_glenum( mesh.primitive_type ); + if( mesh.indices != 0 ) { + glDrawElementsInstanced( primitive, mesh.num_vertices, GL_UNSIGNED_INT, 0, checked_cast< s32 >( num_instances ) ); + } + else { + glDrawArraysInstanced( primitive, 0, mesh.num_vertices, checked_cast< s32 >( num_instances ) ); + } + + glBindVertexArray( 0 ); } diff --git a/renderer.h b/renderer.h @@ -206,6 +206,7 @@ void renderer_delete_texture( Texture texture ); Mesh renderer_new_mesh( MeshConfig config ); void renderer_draw_mesh( const Mesh & mesh, const RenderState & state ); +void renderer_draw_instances( const Mesh & mesh, const RenderState & state, u32 num_instances, VB instace_data ); void renderer_delete_mesh( const Mesh & mesh ); /* diff --git a/shaders/tree.glsl b/shaders/tree.glsl @@ -1,27 +1,61 @@ -#ifdef VERTEX_SHADER - -in vec3 position; - -out vec3 frag_colour; +layout( std140 ) uniform model { + mat4 M; +}; layout( std140 ) uniform view { mat4 V; mat4 P; }; +layout( std140 ) uniform f_hot { + float sun; +}; + +struct VSOut { + vec3 view_position; + vec3 normal; + vec3 colour; +}; + +#ifdef VERTEX_SHADER + +in vec3 position; +in vec3 normal; +in vec3 colour; +in mat4 model_to_world; +out VSOut v2f; + void main() { - gl_Position = P * V * vec4( position, 1.0 ); - frag_colour = vec3( 0.4, 0.3, 0.0 ); + vec4 view_position = V * model_to_world * vec4( position, 1.0 ); + v2f.view_position = vec3( view_position ); + v2f.normal = mat3( model_to_world ) * normal; + v2f.colour = colour; + gl_Position = P * view_position; } #else -in vec3 frag_colour; - +in VSOut v2f; out vec4 screen_colour; +float sq( float x ) { + return x * x; +} + void main() { - screen_colour = vec4( frag_colour, 1.0 ); + // sunlight + vec3 sun_direction = normalize( vec3( -1, 0, sun ) ); + float lambert = dot( sun_direction, v2f.normal ); + float half_lambert = sq( lambert * 0.5 + 0.5 ); + vec3 c = v2f.colour * half_lambert; + + // fog + vec3 fog_colour = vec3( 0.5, 0.6, 0.7 ); + float fog_strength = 0.0005; + float fog_distance_bias = 100.0; + float fog_t = 1.0 - exp( -fog_strength * max( ( length( v2f.view_position ) - fog_distance_bias ), 0.0 ) ); + + screen_colour = vec4( mix( c, fog_colour, fog_t ), 1.0 ); } #endif diff --git a/terrain_manager.cc b/terrain_manager.cc @@ -265,11 +265,6 @@ void terrain_update( TerrainManager * tm, v3 position ) { } } -struct FSData { - v2 dimensions; - float sun; -}; - void terrain_render( TerrainManager * tm, const m4 & V, const m4 & P, float sun, double current_time ) { PROFILE_FUNCTION(); @@ -296,8 +291,7 @@ void terrain_render( TerrainManager * tm, const m4 & V, const m4 & P, float sun, } renderer_ub_easy( tm->ub_view, V, P ); - FSData fsdata = { v2( TILE_SIZE, TILE_SIZE ), sun }; - renderer_ub_data( tm->fragment_uniforms, &fsdata, sizeof( fsdata ) ); + renderer_ub_easy( tm->fragment_uniforms, v2( TILE_SIZE, TILE_SIZE ), sun ); /* start lighting */ float tbo1[ 15 ] = {