📄 main.cpp
字号:
#include "framework.h"
#include "mesh.h"
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <float.h> // For FLT_MAX
#include <stdlib.h> // For qsort
#include "make_world.h"
#include "viewpoint.h"
#include "os_specific_opengl_headers.h"
#include "framework/rendering.h"
#include "visibility.h"
#include "render_state.h"
#include "seam_database.h"
#include "mesh_seam.h"
char *app_name = "Unified Rendering LOD #5";
// Globals to control how we render and behave...
bool wireframe = false;
bool draw_solid_blocks = true;
bool obvious_seams = false;
bool moving = false;
bool color_code_by_lod = false;
bool draw_hud = true;
bool should_draw_ground = false;
bool explode_seams = false;
bool draw_fading_in_blocks = true;
bool draw_fading_out_blocks = true;
bool freeze_blocks = false;
bool textures_disabled = false;
bool using_vertex_buffers = false;
const int DISABLE_LOD_FADING = 0;
Client_Globals client_globals;
int global_current_lod_frame_index = 0;
void *global_hinstance;
const int NUM_FRAMES_WITHOUT_INPUT = 3;
Elevation_Map *the_elevation_map;
float camera_theta, camera_phi;
float viewpoint_speed = 0;
int mesh_triangles = 0;
int mesh_triangles_rendered = 0;
int seam_triangles_rendered = 0;
int high_res_triangle_count = 0;
World_Block *the_world_tree;
char *global_world_file_name;
Loaded_Texture_Info white_texture;
Loaded_Texture_Info red_texture;
Seam_Database *seam_database;
// Forward-declared functions...
void recursively_init_rendering_data(World_Block *node);
void recursively_compute_lod_distances(World_Block *node, float parameter, float fade_radius);
// Okay, here we go.
// Here are just a bunch of functions to draw the
// text display... they aren't related to the core
// algorithm.
void draw_mesh_info(float x, float y) {
Font *font = client_globals.big_font;
const float TIME_TO_CONVERGE_WITHIN_90_PERCENT = 0.5f; // In seconds
float dt = app_shell->get_dt();
float factor = pow(0.1f, dt / TIME_TO_CONVERGE_WITHIN_90_PERCENT);
static bool first_update = true;
static float frame_dt = 0;
if (first_update) {
first_update = false;
frame_dt = dt;
} else {
frame_dt = frame_dt * factor + dt * (1 - factor);
}
float frame_rate;
if (frame_dt > 0) {
frame_rate = 1.0f / frame_dt;
} else {
frame_rate = 0;
}
char buffer[256];
app_shell->text_mode_begin(font);
sprintf(buffer, "Total mesh at hi-res would be %d triangles.", high_res_triangle_count);
app_shell->draw_text(font, x, y, buffer);
y -= font->character_height;
sprintf(buffer, "instantiated triangles: %6d ; rendered (not culled): %6d", mesh_triangles, mesh_triangles_rendered);
app_shell->draw_text(font, x, y, buffer);
y -= font->character_height;
sprintf(buffer, "seam triangles rendered: %4d", seam_triangles_rendered);
app_shell->draw_text(font, x, y, buffer);
y -= font->character_height;
if (!using_vertex_buffers) {
sprintf(buffer, "WARNING: slow rendering (vertex buffer extension not available!)");
app_shell->draw_text(font, x, y, buffer, 1.0f, 0.3f, 0.3f);
y -= font->character_height;
}
app_shell->text_mode_end();
}
char *on_off_string(bool value) {
if (value) return "ON";
return "OFF";
}
void draw_world_view_hud() {
if (!draw_hud) return;
Font *big_font = client_globals.big_font;
Font *small_font = client_globals.small_font;
const float PAD = 10;
float x = PAD;
float y = app_shell->screen_height - big_font->character_height - PAD;
app_shell->text_mode_begin(big_font);
app_shell->draw_text(big_font, x, y, "World Demo");
y -= big_font->character_height - PAD;
char buf[512]; // Static buffers are lame and you should never use them in real code!
if (the_world_tree == NULL) {
y -= 3 * big_font->character_height;
sprintf(buf, "World file '%s' not found!", global_world_file_name);
app_shell->draw_text(big_font, x, y, buf);
app_shell->text_mode_end();
return;
}
app_shell->text_mode_end();
app_shell->text_mode_begin(small_font);
app_shell->draw_text(small_font, x, y, "Press a number key (0-9) to change speed.");
y -= small_font->character_height;
sprintf(buf, "Current speed is: %.1f", viewpoint_speed);
app_shell->draw_text(small_font, x, y, buf);
y -= small_font->character_height;
if (moving) {
app_shell->draw_text(small_font, x, y, "Press the spacebar to STOP moving.");
} else {
app_shell->draw_text(small_font, x, y, "Press the spacebar to START moving.");
}
y -= small_font->character_height;
char *wireframe_string = on_off_string(wireframe);
char *solid_blocks_string = on_off_string(draw_solid_blocks);
char *use_textures_string = on_off_string(!textures_disabled);
char *obvious_seams_string = on_off_string(obvious_seams);
sprintf(buf, "W: wireframe (%s), S: draw solid blocks (%s), T: use textures (%s), O: obvious seams (%s)",
wireframe_string, solid_blocks_string, use_textures_string,
obvious_seams_string);
app_shell->draw_text(small_font, x, y, buf);
y -= small_font->character_height;
if (color_code_by_lod) {
app_shell->draw_text(small_font, x, y, "Press 'C' to color blocks using texture mapping.");
} else {
app_shell->draw_text(small_font, x, y, "Press 'C' to color-code the blocks by LOD scale.");
}
y -= small_font->character_height;
app_shell->draw_text(small_font, x, y, "Use the mouse to aim the camera.");
y -= small_font->character_height;
app_shell->text_mode_end();
// Extra spacing
y -= small_font->character_height;
draw_mesh_info(x, y);
}
/*
Given two blocks and a seam, 'draw_world_view_seam'
does the appropriate coordinate generation and all
that stuff.
*/
void draw_world_view_seam(Mesh_Seam *seam) {
seam_triangles_rendered += seam->num_faces;
// @Incomplete: Right now we assume that the seam only uses
// one texture, and that it's the first texture in its A mesh.
// In other words this is a total hack just to work with
// simple monotextured landscape. That's fine, because when
// we upgrade this code to handle polygon soups, this problem
// will be solved naturally.
int tileno = seam->block_membership[0]->mesh->material_info[0].texture_index;
if (obvious_seams || explode_seams) {
glBindTexture(GL_TEXTURE_2D, red_texture.texture_handle);
} else {
glBindTexture(GL_TEXTURE_2D, tileno);
}
glBegin(GL_TRIANGLES);
Vector3 block_position;
int i;
for (i = 0; i < seam->num_faces * 3; i++) {
Seam_Index *index = &seam->indices[i];
World_Block *block = seam->block_membership[index->which_mesh];
Triangle_List_Mesh *mesh = block->mesh;
Vector3 pos = mesh->vertices[index->vertex_index] + block->position;
Vector3 normal(0, 0, 1);
normal.rotate(mesh->tangent_frames[index->vertex_index]);
glNormal3fv((float *)&normal);
glTexCoord2fv((float *)&index->uv);
glVertex3fv((float *)&pos);
}
glEnd();
}
// If we're drawing the scene with each block colored to clearly
// show its LOD, this function gives us the appropriate color.
// A leaf distance of 0 indicates a leaf block; every integer
// higher than that is one level up the tree.
void switch_lod_color(World_Block *tree, float alpha) {
Clamp(alpha, 0, 1);
switch (tree->leaf_distance) {
case 0:
glColor4f(1, 0, 0, alpha);
break;
case 1:
glColor4f(1, 1, 0, alpha);
break;
case 2:
glColor4f(0, 1, 0, alpha);
break;
case 3:
glColor4f(0, 1, 1, alpha);
break;
case 4:
glColor4f(0, 0, 1, alpha);
break;
case 5:
default:
glColor4f(1, 0, 1, alpha);
break;
}
}
void draw_single_block(World_Block *tree) {
if (tree == NULL) return;
World_Block *block = tree;
mesh_triangles_rendered += block->mesh->num_faces;
// Push the block's position onto the transform stack.
// We store a position in worldspace, apart from the
// block's vertex coordinates, mainly because it
// helps us instance blocks (though we don't use
// instancing in this demo!)
Transformer *transformer = client_globals.transformer;
Matrix4 tm;
tm.identity();
tm.translate(block->position);
transformer->push(&tm);
// Give the new transform to OpenGL
rendering_3d(transformer->current_transform, client_globals.view_projector);
// If we are color-coding blocks to make LOD obvious,
// change the color now...
if (color_code_by_lod) switch_lod_color(tree, tree->opacity);
// Actually output the mesh...
emit_mesh(block->mesh);
transformer->pop();
}
// Function called by qsort() to sort blocks by distance.
int compare_world_blocks(const void *b1, const void *b2) {
World_Block *node1 = *(World_Block **)b1;
World_Block *node2 = *(World_Block **)b2;
if (node1->distance_from_viewpoint > node2->distance_from_viewpoint) return -1;
if (node1->distance_from_viewpoint < node2->distance_from_viewpoint) return +1;
return 0;
}
// Compute distance to each block, then sort the blocks by that value.
void sort_world(Auto_Array <World_Block *> *array) {
World_Block *node;
Array_Foreach(array, node) {
node->distance_from_viewpoint = distance(node->position, client_globals.camera_position);
} Endeach;
qsort(array->data, array->live_items, sizeof(array->data[0]), compare_world_blocks);
}
const float SQRT_10 = 3.17;
// If we are fading some child blocks in, we want those
// blocks to all appear with synchronized amounts of
// translucency. This just sets that value on each block.
void set_child_properties(World_Block *tree,
float opacity, int frame_index,
Lod_Instance_State state) {
int i;
for (i = 0; i < MAX_CHILDREN; i++) {
World_Block *child = tree->children[i];
if (!child) continue;
child->opacity = opacity;
child->frame_index = frame_index;
child->lod_instance_state = state;
}
}
// count_child_triangles can be precomputed if you care!
int count_child_triangles(World_Block *tree) {
int sum = 0;
int i;
for (i = 0; i < MAX_CHILDREN; i++) {
World_Block *child = tree->children[i];
if (!child) continue;
sum += child->mesh->num_faces;
}
return sum;
}
/*
Okay. So to draw the world, first we start at the root
and traverse the tree recursively; we figure out which LOD
we want for each block, and mark the blocks accordingly.
Then later on, we start at the root again and do another
recursive traversal to compute visibility; if a block is
outside the frustum we decide not to draw it, and stop
recursing.
Now it would appear that this is a little bit redundant
and we should do both at the same time. For a simple
demo like this one, with no higher ambitions, that might
be true. However, for a real game we might want to do
something more advanced, like stream environment
geometry from disk. In that case, we want to be able
to load the appropriate LODs of various blocks even
if they are outside the frustum (because the user might
turn his head very quickly, so we need the appropriate
geometry to appear almost instantly). In such a system
it makes a lot more sense to compute LOD and perform
streaming requests in a pre-pass, as I have it structured
here.
*/
void select_world_lods(World_Block *tree) {
// Here we select which LOD to use for each block.
// This is somewhat ad-hoc since we don't really
// relate the choice function to pixel error, or
// even to block size; so if you radically change
// the size of the blocks during the preprocess
// phase, you will get different world quality
// levels here. We'll fix this in a future version.
if (tree == NULL) return;
if (freeze_blocks && (global_current_lod_frame_index > 1)) return;
int frame_index = global_current_lod_frame_index;
tree->frame_index = frame_index;
Vector3 block_pos = tree->position;
Vector3 camera_pos = client_globals.camera_position;
float dist = distance(block_pos, camera_pos);
float ds = tree->bounding_box_extents.x;
if (tree->bounding_box_extents.y > ds) ds = tree->bounding_box_extents.y;
if (tree->bounding_box_extents.z > ds) ds = tree->bounding_box_extents.z;
float r = ds * 0.5f;
float k = SQRT_10 * 1.2f;
float range_mid = k*r;
float diff_stride = range_mid*0.2f;
float range_for_children = tree->distance_at_which_i_am_gone;
float range_for_me = tree->distance_at_which_i_begin_subdividing;
float range_diff = range_for_me - range_for_children;
float t = (dist - range_for_children) / range_diff;
if (t < 0) t = 0;
if (t > 1) t = 1;
if (tree->num_children == 0) t = 1;
if (tree->mesh == NULL) t = 0;
tree->existence_parameter = t;
if (t == 1) {
tree->lod_instance_state = I_AM_SINGLE;
tree->opacity = 1;
mesh_triangles += tree->mesh->num_faces;
return;
}
if (DISABLE_LOD_FADING) t = 0;
if (t == 0) {
tree->lod_instance_state = I_AM_NOT_INVOLVED;
tree->opacity = 0;
select_world_lods(tree->children[0]);
select_world_lods(tree->children[1]);
select_world_lods(tree->children[2]);
select_world_lods(tree->children[3]);
return;
}
float my_opacity, child_opacity;
if (t >= 0.5f) {
my_opacity = 1;
child_opacity = 1.0f - (t - 0.5f) * 2;
Clamp(child_opacity, 0, 1);
} else {
my_opacity = t * 2;
Clamp(my_opacity, 0, 1);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -