diff --git a/bind/v4k.lua b/bind/v4k.lua index 184ecf8..309983b 100644 --- a/bind/v4k.lua +++ b/bind/v4k.lua @@ -1387,6 +1387,8 @@ typedef struct model_t { int num_verts; void *tris; vec3 *meshcenters; + aabb *meshbounds; + float *meshradii; int num_tris; handle vao, ibo, vbo, vao_instanced; int* lod_collapse_map; @@ -1400,6 +1402,8 @@ typedef struct model_t { unsigned num_instances; int stored_flags; renderstate_t rs[NUM_RENDER_PASSES]; + bool frustum_enabled; + frustum frustum_state; } model_t; enum BILLBOARD_MODE { BILLBOARD_X = 0x1, @@ -1423,6 +1427,10 @@ enum BILLBOARD_MODE { void model_render_instanced_pass(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count, int pass); void model_render_pass(model_t m, mat44 proj, mat44 view, mat44 model, int shader, int pass); void model_set_texture(model_t*, texture_t t); + bool model_has_transparency_mesh(model_t m, int mesh); + bool model_has_transparency(model_t m); + void model_set_frustum(model_t *m, frustum f); + void model_clear_frustum(model_t *m); bool model_get_bone_pose(model_t m, unsigned joint, mat34 *out); void model_destroy(model_t); unsigned model_getpass(); @@ -1555,6 +1563,11 @@ typedef struct object_t { float anim_speed; aabb bounds; unsigned billboard; + bool disable_frustum_check; + handle* old_texture_ids; + texture_t* old_textures; + float distance; + bool skip_draw; bool light_cached; } object_t; object_t object(); diff --git a/demos/06-scene-sorting.c b/demos/06-scene-sorting.c new file mode 100644 index 0000000..55901e2 --- /dev/null +++ b/demos/06-scene-sorting.c @@ -0,0 +1,58 @@ +// render map + +#include "v4k.h" + +int main() { + window_create(80, WINDOW_MSAA8); + window_title(__FILE__); + window_fps_unlock(); + + // load skybox + skybox_t sky = skybox("bridge3", 0); + + // load static scene + model_t map, prop; + map = model(option("--model","sorting_test.obj"), 0); // MODEL_NO_TEXTURES); + prop = model(option("--model","sorting_test2.obj"), 0); // MODEL_NO_TEXTURES); + shader_bind(map.program); + skybox_sh_shader(&sky); + prop.program = map.program; + + // define scene + object_t *prop_obj = scene_spawn(); + object_model(prop_obj, prop); + + object_t *map_obj = scene_spawn(); + object_model(map_obj, map); + + // camera + camera_t cam = camera(); + cam.speed *= 0.05f; + + // demo loop + while (window_swap()) + { + // input + if( input_down(KEY_ESC) ) break; + if( input_down(KEY_F5) ) window_reload(); + if( input_down(KEY_F11) ) window_fullscreen( window_has_fullscreen() ^ 1 ); + if( input_down(KEY_X) ) window_screenshot(__FILE__ ".png"); + if( input_down(KEY_Z) ) window_record(__FILE__ ".mp4"); + + // fps camera + bool active = ui_active() || ui_hover() || gizmo_active() ? false : input(MOUSE_L) || input(MOUSE_M) || input(MOUSE_R); + if( active ) cam.speed = clampf(cam.speed + input_diff(MOUSE_W) / 10, 0.05f, 5.0f); + vec2 mouse = scale2(vec2(input_diff(MOUSE_X), -input_diff(MOUSE_Y)), 0.2f * active); + vec3 wasdecq = scale3(vec3(input(KEY_D)-input(KEY_A),input(KEY_E)-(input(KEY_C)||input(KEY_Q)),input(KEY_W)-input(KEY_S)), cam.speed); + camera_moveby(&cam, wasdecq); + camera_fps(&cam, mouse.x,mouse.y); + window_cursor( !active ); + + // draw skybox + profile("Skybox") { + skybox_render(&sky, cam.proj, cam.view); + } + + scene_render(SCENE_FOREGROUND); + } +} diff --git a/demos/06-sorting.c b/demos/06-sorting.c index 44144e6..1848b79 100644 --- a/demos/06-sorting.c +++ b/demos/06-sorting.c @@ -7,21 +7,14 @@ int main() { window_title(__FILE__); window_fps_unlock(); - // load all fx files - fx_load("fx**.fs"); - // load skybox - // skybox_t sky = skybox(flag("--mie") ? 0 : "hdr/Tokyo_BigSight_1k.hdr", 0); // --mie for rayleigh/mie scattering - skybox_t sky = skybox("bridge3", 0); // --mie for rayleigh/mie scattering + skybox_t sky = skybox("bridge3", 0); // load static scene model_t map; map = model(option("--model","sorting_test.obj"), 0); // MODEL_NO_TEXTURES); shader_bind(map.program); skybox_sh_shader(&sky); - // translation44(map.pivot, 0,-1,0); - // rotate44(map.pivot, -90,1,0,0); - // scale44(map.pivot, 10,10,10); // camera camera_t cam = camera(); @@ -53,12 +46,6 @@ int main() { mat44 M; copy44(M, map.pivot);// translate44(M, 0,0,0); scale44(M, scale,scale,scale); - // apply post-fxs from here - fx_begin(); - - model_render_pass(map, cam.proj, cam.view, M, 0, -1); - - // post-fxs end here - fx_end(0); + model_render(map, cam.proj, cam.view, M, 0); } } diff --git a/demos/art/meshes/sorting_test2.mtl b/demos/art/meshes/sorting_test2.mtl new file mode 100644 index 0000000..66e6861 --- /dev/null +++ b/demos/art/meshes/sorting_test2.mtl @@ -0,0 +1,52 @@ +# Blender 4.2.1 LTS MTL File: 'None' +# www.blender.org + +newmtl Material.006 +Ns 250.000000 +Ka 1.000000 1.000000 1.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.500000 +d 1.000000 +illum 2 +map_Kd E:/v4games/v4k/demos/art/matcaps/material3.jpg + +newmtl Material.007 +Ns 250.000000 +Ka 1.000000 1.000000 1.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd E:/v4games/v4k/demos/art/matcaps/test_steel.jpg + +newmtl Material.008 +Ns 250.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.059941 0.801453 0.027650 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.500000 +d 0.228070 +illum 9 + +newmtl Material.009 +Ns 250.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.015818 0.013069 0.801024 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.500000 +d 0.294737 +illum 9 + +newmtl Material.010 +Ns 250.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800063 0.776954 0.209224 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.500000 +d 0.480702 +illum 9 diff --git a/demos/art/meshes/sorting_test2.obj b/demos/art/meshes/sorting_test2.obj new file mode 100644 index 0000000..e40f0ed --- /dev/null +++ b/demos/art/meshes/sorting_test2.obj @@ -0,0 +1,70 @@ +# Blender 4.2.1 LTS +# www.blender.org +mtllib sorting_test2.mtl +o Cube.001 +v 6.691334 -10.979122 -7.520660 +v 6.691334 -10.979120 -11.912354 +v -6.691334 -10.979122 -7.520660 +v -6.691334 -10.979120 -11.912354 +v 6.691333 -15.370816 -7.520660 +v 6.691333 -15.370816 -11.912354 +v -6.691335 -15.370814 -7.520660 +v -6.691335 -15.370814 -11.912354 +v 6.691334 -1.734141 -7.520659 +v 6.691334 -1.734141 -11.912354 +v -6.691334 -1.734140 -7.520659 +v -6.691334 -1.734140 -11.912354 +v 6.691334 -6.125834 -7.520660 +v 6.691334 -6.125834 -11.912353 +v -6.691333 -6.125834 -7.520660 +v -6.691333 -6.125834 -11.912353 +v 6.691335 6.836245 -7.520658 +v 6.691335 6.836244 -11.912353 +v -6.691332 6.836244 -7.520658 +v -6.691332 6.836243 -11.912353 +v 6.691335 2.444547 -7.520658 +v 6.691335 2.444549 -11.912353 +v -6.691333 2.444548 -7.520658 +v -6.691333 2.444550 -11.912353 +vn -0.0000 1.0000 -0.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -0.0000 -0.0000 -1.0000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.625000 1.000000 +vt 0.375000 1.000000 +vt 0.125000 0.500000 +vt 0.125000 0.750000 +vt 0.875000 0.500000 +vt 0.875000 0.750000 +s 0 +usemtl Material.008 +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/4/2 4/3/2 8/5/2 7/6/2 +f 7/6/3 8/5/3 6/7/3 5/8/3 +f 5/8/4 6/7/4 2/9/4 1/10/4 +f 3/11/5 7/6/5 5/8/5 1/12/5 +f 8/5/6 4/13/6 2/14/6 6/7/6 +usemtl Material.009 +f 9/1/1 10/2/1 12/3/1 11/4/1 +f 11/4/2 12/3/2 16/5/2 15/6/2 +f 15/6/3 16/5/3 14/7/3 13/8/3 +f 13/8/4 14/7/4 10/9/4 9/10/4 +f 11/11/5 15/6/5 13/8/5 9/12/5 +f 16/5/6 12/13/6 10/14/6 14/7/6 +usemtl Material.010 +f 17/1/1 18/2/1 20/3/1 19/4/1 +f 19/4/2 20/3/2 24/5/2 23/6/2 +f 23/6/3 24/5/3 22/7/3 21/8/3 +f 21/8/4 22/7/4 18/9/4 17/10/4 +f 19/11/5 23/6/5 21/8/5 17/12/5 +f 24/5/6 20/13/6 18/14/6 22/7/6 diff --git a/engine/joint/v4k.h b/engine/joint/v4k.h index 3cda235..f3e0e9b 100644 --- a/engine/joint/v4k.h +++ b/engine/joint/v4k.h @@ -17645,6 +17645,8 @@ typedef struct model_t { int num_verts; void *tris; vec3 *meshcenters; + aabb *meshbounds; + float *meshradii; int num_tris; handle vao, ibo, vbo, vao_instanced; @@ -17662,6 +17664,9 @@ typedef struct model_t { int stored_flags; renderstate_t rs[NUM_RENDER_PASSES]; + + bool frustum_enabled; + frustum frustum_state; } model_t; enum BILLBOARD_MODE { @@ -17688,6 +17693,10 @@ API void model_render_instanced(model_t, mat44 proj, mat44 view, mat44 *mode API void model_render_instanced_pass(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count, int pass); API void model_render_pass(model_t m, mat44 proj, mat44 view, mat44 model, int shader, int pass); API void model_set_texture(model_t*, texture_t t); +API bool model_has_transparency_mesh(model_t m, int mesh); +API bool model_has_transparency(model_t m); +API void model_set_frustum(model_t *m, frustum f); +API void model_clear_frustum(model_t *m); API bool model_get_bone_pose(model_t m, unsigned joint, mat34 *out); API void model_destroy(model_t); @@ -17884,6 +17893,13 @@ typedef struct object_t { float anim_speed; aabb bounds; unsigned billboard; // [0..7] x(4),y(2),z(1) masks + bool disable_frustum_check; + + // internal states + array(handle) old_texture_ids; + array(texture_t) old_textures; + float distance; + bool skip_draw; bool light_cached; //< used by scene to update light data } object_t; @@ -385244,12 +385260,19 @@ bool model_load_meshes(iqm_t *q, const struct iqmheader *hdr, model_t *m) { q->textures = CALLOC(hdr->num_meshes * 8, sizeof(GLuint)); q->colormaps = CALLOC(hdr->num_meshes * 8, sizeof(vec4)); m->meshcenters = CALLOC(hdr->num_meshes, sizeof(vec3)); + m->meshbounds = CALLOC(hdr->num_meshes, sizeof(aabb)); + m->meshradii = CALLOC(hdr->num_meshes, sizeof(float)); for(int i = 0; i < (int)hdr->num_meshes; i++) { int invalid = texture_checker().id; q->textures[i] = invalid; + struct iqmmesh *mesh = &q->meshes[i]; +#if 1 GLfloat *pos = verts[q->meshes[i].first_vertex].position; m->meshcenters[i] = vec3(pos[0], pos[1], pos[2]); +#else + // for (int j = 0; j < ) +#endif } const char *str = hdr->ofs_text ? (char *)&q->buf[hdr->ofs_text] : ""; @@ -385876,6 +385899,46 @@ int drawcall_compare(const void *a, const void *b) { return da->order < db->order ? 1 : da->order > db->order ? -1 : 0; } +bool model_has_transparency_mesh(model_t m, int mesh) { + if(!m.iqm) return false; + iqm_t *q = m.iqm; + + if (m.flags & MODEL_TRANSPARENT) { + return true; + } + if (m.materials[mesh].layer[0].map.color.a < 1 || (m.materials[mesh].layer[0].map.texture && m.materials[mesh].layer[0].map.texture->transparent)) { + return true; + } + if (m.shading == SHADING_PBR && (m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.color.a < 1 || (m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.texture && m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.texture->transparent))){ + return true; + } + + return false; +} + +bool model_has_transparency(model_t m) { + if(!m.iqm) return false; + iqm_t *q = m.iqm; + + for (int i = 0; i < q->nummeshes; i++) { + if (model_has_transparency_mesh(m, i)) { + return true; + } + } + + return false; +} + + +void model_set_frustum(model_t *m, frustum f) { + m->frustum_enabled = 1; + m->frustum_state = f; +} + +void model_clear_frustum(model_t *m) { + m->frustum_enabled = 0; +} + static void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat) { if(!m.iqm) return; @@ -385897,13 +385960,7 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_ required_rs[i] = rs_idx; if (required_rs[i] < RENDER_PASS_OVERRIDES_BEGIN) { - if (m.flags & MODEL_TRANSPARENT) { - required_rs[i] = RENDER_PASS_TRANSPARENT; - } - if (m.materials[i].layer[0].map.color.a < 1 || (m.materials[i].layer[0].map.texture && m.materials[i].layer[0].map.texture->transparent)) { - required_rs[i] = RENDER_PASS_TRANSPARENT; - } - if (m.shading == SHADING_PBR && (m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.color.a < 1 || (m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture && m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture->transparent))){ + if (model_has_transparency_mesh(m, i)) { required_rs[i] = RENDER_PASS_TRANSPARENT; } } @@ -385943,7 +386000,7 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_ // calculate distance from camera // @todo: improve me, uses first mesh triangle { - call.distance = len3(sub3(cam_pos, transform344(model_mat, m.meshcenters[i]))); + call.distance = len3sq(sub3(cam_pos, transform344(model_mat, m.meshcenters[i]))); } if (m.shading == SHADING_PBR) @@ -386372,6 +386429,8 @@ void model_destroy(model_t m) { } array_free(m.texture_names); FREE(m.meshcenters); + FREE(m.meshbounds); + FREE(m.meshradii); iqm_t *q = m.iqm; // if(m.mesh) mesh_destroy(m.mesh); @@ -388020,8 +388079,16 @@ light_t* scene_index_light(unsigned light_index) { return &last_scene->lights[light_index]; } +static +int scene_obj_distance_compare(const void *a, const void *b) { + const object_t *da = a, *db = b; + return da->distance < db->distance ? 1 : da->distance > db->distance ? -1 : 0; +} + void scene_render(int flags) { camera_t *cam = camera_get_active(); + mat44 projview; multiply44x2(projview, cam->proj, cam->view); + frustum frustum_state = frustum_build(projview); if(flags & SCENE_BACKGROUND) { if(last_scene->skybox.program) { @@ -388047,18 +388114,18 @@ void scene_render(int flags) { model_t *model = &obj->model; anim_t *anim = &obj->anim; mat44 *views = (mat44*)(&cam->view); + + obj->skip_draw = !obj->disable_frustum_check && !frustum_test_aabb(frustum_state, model_aabb(*model, obj->transform)); - // @todo: avoid heap allocs here? - static array(handle) old_texture_ids = 0; - static array(texture_t) old_textures = 0; + if (obj->skip_draw) continue; int do_retexturing = model->iqm && model->shading != SHADING_PBR && array_count(obj->textures) > 0; if( do_retexturing ) { for(int i = 0; i < model->iqm->nummeshes; ++i) { - array_push(old_texture_ids, model->iqm->textures[i]); + array_push(obj->old_texture_ids, model->iqm->textures[i]); model->iqm->textures[i] = (*array_back(obj->textures)).id; if (model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture) { - array_push(old_textures, *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture); + array_push(obj->old_textures, *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture); *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = (*array_back(obj->textures)); } } @@ -388072,32 +388139,80 @@ void scene_render(int flags) { if ( flags&SCENE_UPDATE_SH_COEF ) { shader_bind(model->program); - shader_vec3v("u_coefficients_sh", 9, last_scene->skybox.cubemap.sh); + skybox_sh_shader(&last_scene->skybox); } - + model_skybox(model, last_scene->skybox, 0); + if (!obj->disable_frustum_check) + model_set_frustum(model, frustum_state); + else + model_clear_frustum(model); + if (anim) { float delta = window_delta() * obj->anim_speed; model->curframe = model_animate_clip(*model, model->curframe + delta, anim->from, anim->to, anim->flags & ANIM_LOOP ); } - model->billboard = obj->billboard; - model->rs[RENDER_PASS_OPAQUE].cull_face_enabled = flags&SCENE_CULLFACE ? 1 : 0; - model->rs[RENDER_PASS_OPAQUE].polygon_mode_draw = flags&SCENE_WIREFRAME ? GL_LINE : GL_FILL; - model_render(*model, cam->proj, cam->view, obj->transform, model->program); + for (int p = 0; p < RENDER_PASS_OVERRIDES_BEGIN; ++p) { + model->rs[p].cull_face_enabled = flags&SCENE_CULLFACE ? 1 : 0; + model->rs[p].polygon_mode_draw = flags&SCENE_WIREFRAME ? GL_LINE : GL_FILL; + } + } + + /* Collect all transparency enabled models and sort them by distance */ + static array(object_t*) transparent_objects = 0; + for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) { + object_t *obj = scene_index(j); + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + if (model_has_transparency(*model)) { + obj->distance = len3sq(sub3(cam->position, transform344(model->pivot, add3(obj->pos, model->meshcenters[0])))); + array_push(transparent_objects, obj); + } + } + + array_sort(transparent_objects, scene_obj_distance_compare); + + /* Opaque pass */ + for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) { + object_t *obj = scene_index(j); + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + model_render_pass(*model, cam->proj, cam->view, obj->transform, model->program, RENDER_PASS_OPAQUE); + } + + /* Transparency pass */ + for (unsigned j = 0; j < array_count(transparent_objects); ++j) { + object_t *obj = transparent_objects[j]; + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + model_render_pass(*model, cam->proj, cam->view, obj->transform, model->program, RENDER_PASS_TRANSPARENT); + } + + array_resize(transparent_objects, 0); + + for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) { + object_t *obj = scene_index(j); + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + int do_retexturing = model->iqm && model->shading != SHADING_PBR && array_count(obj->textures) > 0; if( do_retexturing ) { for(int i = 0; i < model->iqm->nummeshes; ++i) { - model->iqm->textures[i] = old_texture_ids[i]; - if (i < array_count(old_textures)) { + model->iqm->textures[i] = obj->old_texture_ids[i]; + if (i < array_count(obj->old_textures)) { if (model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture) - *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = old_textures[i]; + *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = obj->old_textures[i]; } } - array_resize(old_texture_ids, 0); - array_resize(old_textures, 0); + array_resize(obj->old_texture_ids, 0); + array_resize(obj->old_textures, 0); } } glBindVertexArray(0); diff --git a/engine/split/v4k_render.c b/engine/split/v4k_render.c index 6de2093..e705ac4 100644 --- a/engine/split/v4k_render.c +++ b/engine/split/v4k_render.c @@ -3604,12 +3604,19 @@ bool model_load_meshes(iqm_t *q, const struct iqmheader *hdr, model_t *m) { q->textures = CALLOC(hdr->num_meshes * 8, sizeof(GLuint)); q->colormaps = CALLOC(hdr->num_meshes * 8, sizeof(vec4)); m->meshcenters = CALLOC(hdr->num_meshes, sizeof(vec3)); + m->meshbounds = CALLOC(hdr->num_meshes, sizeof(aabb)); + m->meshradii = CALLOC(hdr->num_meshes, sizeof(float)); for(int i = 0; i < (int)hdr->num_meshes; i++) { int invalid = texture_checker().id; q->textures[i] = invalid; + struct iqmmesh *mesh = &q->meshes[i]; +#if 1 GLfloat *pos = verts[q->meshes[i].first_vertex].position; m->meshcenters[i] = vec3(pos[0], pos[1], pos[2]); +#else + // for (int j = 0; j < ) +#endif } const char *str = hdr->ofs_text ? (char *)&q->buf[hdr->ofs_text] : ""; @@ -4236,6 +4243,46 @@ int drawcall_compare(const void *a, const void *b) { return da->order < db->order ? 1 : da->order > db->order ? -1 : 0; } +bool model_has_transparency_mesh(model_t m, int mesh) { + if(!m.iqm) return false; + iqm_t *q = m.iqm; + + if (m.flags & MODEL_TRANSPARENT) { + return true; + } + if (m.materials[mesh].layer[0].map.color.a < 1 || (m.materials[mesh].layer[0].map.texture && m.materials[mesh].layer[0].map.texture->transparent)) { + return true; + } + if (m.shading == SHADING_PBR && (m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.color.a < 1 || (m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.texture && m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.texture->transparent))){ + return true; + } + + return false; +} + +bool model_has_transparency(model_t m) { + if(!m.iqm) return false; + iqm_t *q = m.iqm; + + for (int i = 0; i < q->nummeshes; i++) { + if (model_has_transparency_mesh(m, i)) { + return true; + } + } + + return false; +} + + +void model_set_frustum(model_t *m, frustum f) { + m->frustum_enabled = 1; + m->frustum_state = f; +} + +void model_clear_frustum(model_t *m) { + m->frustum_enabled = 0; +} + static void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat) { if(!m.iqm) return; @@ -4257,13 +4304,7 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_ required_rs[i] = rs_idx; if (required_rs[i] < RENDER_PASS_OVERRIDES_BEGIN) { - if (m.flags & MODEL_TRANSPARENT) { - required_rs[i] = RENDER_PASS_TRANSPARENT; - } - if (m.materials[i].layer[0].map.color.a < 1 || (m.materials[i].layer[0].map.texture && m.materials[i].layer[0].map.texture->transparent)) { - required_rs[i] = RENDER_PASS_TRANSPARENT; - } - if (m.shading == SHADING_PBR && (m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.color.a < 1 || (m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture && m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture->transparent))){ + if (model_has_transparency_mesh(m, i)) { required_rs[i] = RENDER_PASS_TRANSPARENT; } } @@ -4303,7 +4344,7 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_ // calculate distance from camera // @todo: improve me, uses first mesh triangle { - call.distance = len3(sub3(cam_pos, transform344(model_mat, m.meshcenters[i]))); + call.distance = len3sq(sub3(cam_pos, transform344(model_mat, m.meshcenters[i]))); } if (m.shading == SHADING_PBR) @@ -4732,6 +4773,8 @@ void model_destroy(model_t m) { } array_free(m.texture_names); FREE(m.meshcenters); + FREE(m.meshbounds); + FREE(m.meshradii); iqm_t *q = m.iqm; // if(m.mesh) mesh_destroy(m.mesh); diff --git a/engine/split/v4k_render.h b/engine/split/v4k_render.h index 992f785..96b1a03 100644 --- a/engine/split/v4k_render.h +++ b/engine/split/v4k_render.h @@ -677,6 +677,8 @@ typedef struct model_t { int num_verts; void *tris; vec3 *meshcenters; + aabb *meshbounds; + float *meshradii; int num_tris; handle vao, ibo, vbo, vao_instanced; @@ -694,6 +696,9 @@ typedef struct model_t { int stored_flags; renderstate_t rs[NUM_RENDER_PASSES]; + + bool frustum_enabled; + frustum frustum_state; } model_t; enum BILLBOARD_MODE { @@ -720,6 +725,10 @@ API void model_render_instanced(model_t, mat44 proj, mat44 view, mat44 *mode API void model_render_instanced_pass(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count, int pass); API void model_render_pass(model_t m, mat44 proj, mat44 view, mat44 model, int shader, int pass); API void model_set_texture(model_t*, texture_t t); +API bool model_has_transparency_mesh(model_t m, int mesh); +API bool model_has_transparency(model_t m); +API void model_set_frustum(model_t *m, frustum f); +API void model_clear_frustum(model_t *m); API bool model_get_bone_pose(model_t m, unsigned joint, mat34 *out); API void model_destroy(model_t); diff --git a/engine/split/v4k_scene.c b/engine/split/v4k_scene.c index bc821eb..f2d6158 100644 --- a/engine/split/v4k_scene.c +++ b/engine/split/v4k_scene.c @@ -505,8 +505,16 @@ light_t* scene_index_light(unsigned light_index) { return &last_scene->lights[light_index]; } +static +int scene_obj_distance_compare(const void *a, const void *b) { + const object_t *da = a, *db = b; + return da->distance < db->distance ? 1 : da->distance > db->distance ? -1 : 0; +} + void scene_render(int flags) { camera_t *cam = camera_get_active(); + mat44 projview; multiply44x2(projview, cam->proj, cam->view); + frustum frustum_state = frustum_build(projview); if(flags & SCENE_BACKGROUND) { if(last_scene->skybox.program) { @@ -532,18 +540,18 @@ void scene_render(int flags) { model_t *model = &obj->model; anim_t *anim = &obj->anim; mat44 *views = (mat44*)(&cam->view); + + obj->skip_draw = !obj->disable_frustum_check && !frustum_test_aabb(frustum_state, model_aabb(*model, obj->transform)); - // @todo: avoid heap allocs here? - static array(handle) old_texture_ids = 0; - static array(texture_t) old_textures = 0; + if (obj->skip_draw) continue; int do_retexturing = model->iqm && model->shading != SHADING_PBR && array_count(obj->textures) > 0; if( do_retexturing ) { for(int i = 0; i < model->iqm->nummeshes; ++i) { - array_push(old_texture_ids, model->iqm->textures[i]); + array_push(obj->old_texture_ids, model->iqm->textures[i]); model->iqm->textures[i] = (*array_back(obj->textures)).id; if (model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture) { - array_push(old_textures, *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture); + array_push(obj->old_textures, *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture); *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = (*array_back(obj->textures)); } } @@ -557,32 +565,80 @@ void scene_render(int flags) { if ( flags&SCENE_UPDATE_SH_COEF ) { shader_bind(model->program); - shader_vec3v("u_coefficients_sh", 9, last_scene->skybox.cubemap.sh); + skybox_sh_shader(&last_scene->skybox); } - + model_skybox(model, last_scene->skybox, 0); + if (!obj->disable_frustum_check) + model_set_frustum(model, frustum_state); + else + model_clear_frustum(model); + if (anim) { float delta = window_delta() * obj->anim_speed; model->curframe = model_animate_clip(*model, model->curframe + delta, anim->from, anim->to, anim->flags & ANIM_LOOP ); } - model->billboard = obj->billboard; - model->rs[RENDER_PASS_OPAQUE].cull_face_enabled = flags&SCENE_CULLFACE ? 1 : 0; - model->rs[RENDER_PASS_OPAQUE].polygon_mode_draw = flags&SCENE_WIREFRAME ? GL_LINE : GL_FILL; - model_render(*model, cam->proj, cam->view, obj->transform, model->program); + for (int p = 0; p < RENDER_PASS_OVERRIDES_BEGIN; ++p) { + model->rs[p].cull_face_enabled = flags&SCENE_CULLFACE ? 1 : 0; + model->rs[p].polygon_mode_draw = flags&SCENE_WIREFRAME ? GL_LINE : GL_FILL; + } + } + + /* Collect all transparency enabled models and sort them by distance */ + static array(object_t*) transparent_objects = 0; + for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) { + object_t *obj = scene_index(j); + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + if (model_has_transparency(*model)) { + obj->distance = len3sq(sub3(cam->position, transform344(model->pivot, add3(obj->pos, model->meshcenters[0])))); + array_push(transparent_objects, obj); + } + } + + array_sort(transparent_objects, scene_obj_distance_compare); + + /* Opaque pass */ + for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) { + object_t *obj = scene_index(j); + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + model_render_pass(*model, cam->proj, cam->view, obj->transform, model->program, RENDER_PASS_OPAQUE); + } + + /* Transparency pass */ + for (unsigned j = 0; j < array_count(transparent_objects); ++j) { + object_t *obj = transparent_objects[j]; + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + model_render_pass(*model, cam->proj, cam->view, obj->transform, model->program, RENDER_PASS_TRANSPARENT); + } + + array_resize(transparent_objects, 0); + + for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) { + object_t *obj = scene_index(j); + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + int do_retexturing = model->iqm && model->shading != SHADING_PBR && array_count(obj->textures) > 0; if( do_retexturing ) { for(int i = 0; i < model->iqm->nummeshes; ++i) { - model->iqm->textures[i] = old_texture_ids[i]; - if (i < array_count(old_textures)) { + model->iqm->textures[i] = obj->old_texture_ids[i]; + if (i < array_count(obj->old_textures)) { if (model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture) - *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = old_textures[i]; + *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = obj->old_textures[i]; } } - array_resize(old_texture_ids, 0); - array_resize(old_textures, 0); + array_resize(obj->old_texture_ids, 0); + array_resize(obj->old_textures, 0); } } glBindVertexArray(0); diff --git a/engine/split/v4k_scene.h b/engine/split/v4k_scene.h index b1bee26..f95a8e0 100644 --- a/engine/split/v4k_scene.h +++ b/engine/split/v4k_scene.h @@ -48,6 +48,13 @@ typedef struct object_t { float anim_speed; aabb bounds; unsigned billboard; // [0..7] x(4),y(2),z(1) masks + bool disable_frustum_check; + + // internal states + array(handle) old_texture_ids; + array(texture_t) old_textures; + float distance; + bool skip_draw; bool light_cached; //< used by scene to update light data } object_t; diff --git a/engine/v4k.c b/engine/v4k.c index 5a45bd6..28215fd 100644 --- a/engine/v4k.c +++ b/engine/v4k.c @@ -20403,12 +20403,19 @@ bool model_load_meshes(iqm_t *q, const struct iqmheader *hdr, model_t *m) { q->textures = CALLOC(hdr->num_meshes * 8, sizeof(GLuint)); q->colormaps = CALLOC(hdr->num_meshes * 8, sizeof(vec4)); m->meshcenters = CALLOC(hdr->num_meshes, sizeof(vec3)); + m->meshbounds = CALLOC(hdr->num_meshes, sizeof(aabb)); + m->meshradii = CALLOC(hdr->num_meshes, sizeof(float)); for(int i = 0; i < (int)hdr->num_meshes; i++) { int invalid = texture_checker().id; q->textures[i] = invalid; + struct iqmmesh *mesh = &q->meshes[i]; +#if 1 GLfloat *pos = verts[q->meshes[i].first_vertex].position; m->meshcenters[i] = vec3(pos[0], pos[1], pos[2]); +#else + // for (int j = 0; j < ) +#endif } const char *str = hdr->ofs_text ? (char *)&q->buf[hdr->ofs_text] : ""; @@ -21035,6 +21042,46 @@ int drawcall_compare(const void *a, const void *b) { return da->order < db->order ? 1 : da->order > db->order ? -1 : 0; } +bool model_has_transparency_mesh(model_t m, int mesh) { + if(!m.iqm) return false; + iqm_t *q = m.iqm; + + if (m.flags & MODEL_TRANSPARENT) { + return true; + } + if (m.materials[mesh].layer[0].map.color.a < 1 || (m.materials[mesh].layer[0].map.texture && m.materials[mesh].layer[0].map.texture->transparent)) { + return true; + } + if (m.shading == SHADING_PBR && (m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.color.a < 1 || (m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.texture && m.materials[mesh].layer[MATERIAL_CHANNEL_ALBEDO].map.texture->transparent))){ + return true; + } + + return false; +} + +bool model_has_transparency(model_t m) { + if(!m.iqm) return false; + iqm_t *q = m.iqm; + + for (int i = 0; i < q->nummeshes; i++) { + if (model_has_transparency_mesh(m, i)) { + return true; + } + } + + return false; +} + + +void model_set_frustum(model_t *m, frustum f) { + m->frustum_enabled = 1; + m->frustum_state = f; +} + +void model_clear_frustum(model_t *m) { + m->frustum_enabled = 0; +} + static void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_mat) { if(!m.iqm) return; @@ -21056,13 +21103,7 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_ required_rs[i] = rs_idx; if (required_rs[i] < RENDER_PASS_OVERRIDES_BEGIN) { - if (m.flags & MODEL_TRANSPARENT) { - required_rs[i] = RENDER_PASS_TRANSPARENT; - } - if (m.materials[i].layer[0].map.color.a < 1 || (m.materials[i].layer[0].map.texture && m.materials[i].layer[0].map.texture->transparent)) { - required_rs[i] = RENDER_PASS_TRANSPARENT; - } - if (m.shading == SHADING_PBR && (m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.color.a < 1 || (m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture && m.materials[i].layer[MATERIAL_CHANNEL_ALBEDO].map.texture->transparent))){ + if (model_has_transparency_mesh(m, i)) { required_rs[i] = RENDER_PASS_TRANSPARENT; } } @@ -21102,7 +21143,7 @@ void model_draw_call(model_t m, int shader, int pass, vec3 cam_pos, mat44 model_ // calculate distance from camera // @todo: improve me, uses first mesh triangle { - call.distance = len3(sub3(cam_pos, transform344(model_mat, m.meshcenters[i]))); + call.distance = len3sq(sub3(cam_pos, transform344(model_mat, m.meshcenters[i]))); } if (m.shading == SHADING_PBR) @@ -21531,6 +21572,8 @@ void model_destroy(model_t m) { } array_free(m.texture_names); FREE(m.meshcenters); + FREE(m.meshbounds); + FREE(m.meshradii); iqm_t *q = m.iqm; // if(m.mesh) mesh_destroy(m.mesh); @@ -23179,8 +23222,16 @@ light_t* scene_index_light(unsigned light_index) { return &last_scene->lights[light_index]; } +static +int scene_obj_distance_compare(const void *a, const void *b) { + const object_t *da = a, *db = b; + return da->distance < db->distance ? 1 : da->distance > db->distance ? -1 : 0; +} + void scene_render(int flags) { camera_t *cam = camera_get_active(); + mat44 projview; multiply44x2(projview, cam->proj, cam->view); + frustum frustum_state = frustum_build(projview); if(flags & SCENE_BACKGROUND) { if(last_scene->skybox.program) { @@ -23206,18 +23257,18 @@ void scene_render(int flags) { model_t *model = &obj->model; anim_t *anim = &obj->anim; mat44 *views = (mat44*)(&cam->view); + + obj->skip_draw = !obj->disable_frustum_check && !frustum_test_aabb(frustum_state, model_aabb(*model, obj->transform)); - // @todo: avoid heap allocs here? - static array(handle) old_texture_ids = 0; - static array(texture_t) old_textures = 0; + if (obj->skip_draw) continue; int do_retexturing = model->iqm && model->shading != SHADING_PBR && array_count(obj->textures) > 0; if( do_retexturing ) { for(int i = 0; i < model->iqm->nummeshes; ++i) { - array_push(old_texture_ids, model->iqm->textures[i]); + array_push(obj->old_texture_ids, model->iqm->textures[i]); model->iqm->textures[i] = (*array_back(obj->textures)).id; if (model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture) { - array_push(old_textures, *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture); + array_push(obj->old_textures, *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture); *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = (*array_back(obj->textures)); } } @@ -23231,32 +23282,80 @@ void scene_render(int flags) { if ( flags&SCENE_UPDATE_SH_COEF ) { shader_bind(model->program); - shader_vec3v("u_coefficients_sh", 9, last_scene->skybox.cubemap.sh); + skybox_sh_shader(&last_scene->skybox); } - + model_skybox(model, last_scene->skybox, 0); + if (!obj->disable_frustum_check) + model_set_frustum(model, frustum_state); + else + model_clear_frustum(model); + if (anim) { float delta = window_delta() * obj->anim_speed; model->curframe = model_animate_clip(*model, model->curframe + delta, anim->from, anim->to, anim->flags & ANIM_LOOP ); } - model->billboard = obj->billboard; - model->rs[RENDER_PASS_OPAQUE].cull_face_enabled = flags&SCENE_CULLFACE ? 1 : 0; - model->rs[RENDER_PASS_OPAQUE].polygon_mode_draw = flags&SCENE_WIREFRAME ? GL_LINE : GL_FILL; - model_render(*model, cam->proj, cam->view, obj->transform, model->program); + for (int p = 0; p < RENDER_PASS_OVERRIDES_BEGIN; ++p) { + model->rs[p].cull_face_enabled = flags&SCENE_CULLFACE ? 1 : 0; + model->rs[p].polygon_mode_draw = flags&SCENE_WIREFRAME ? GL_LINE : GL_FILL; + } + } + + /* Collect all transparency enabled models and sort them by distance */ + static array(object_t*) transparent_objects = 0; + for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) { + object_t *obj = scene_index(j); + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + if (model_has_transparency(*model)) { + obj->distance = len3sq(sub3(cam->position, transform344(model->pivot, add3(obj->pos, model->meshcenters[0])))); + array_push(transparent_objects, obj); + } + } + + array_sort(transparent_objects, scene_obj_distance_compare); + + /* Opaque pass */ + for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) { + object_t *obj = scene_index(j); + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + model_render_pass(*model, cam->proj, cam->view, obj->transform, model->program, RENDER_PASS_OPAQUE); + } + + /* Transparency pass */ + for (unsigned j = 0; j < array_count(transparent_objects); ++j) { + object_t *obj = transparent_objects[j]; + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + model_render_pass(*model, cam->proj, cam->view, obj->transform, model->program, RENDER_PASS_TRANSPARENT); + } + + array_resize(transparent_objects, 0); + + for(unsigned j = 0, obj_count = scene_count(); j < obj_count; ++j ) { + object_t *obj = scene_index(j); + model_t *model = &obj->model; + if (obj->skip_draw) continue; + + int do_retexturing = model->iqm && model->shading != SHADING_PBR && array_count(obj->textures) > 0; if( do_retexturing ) { for(int i = 0; i < model->iqm->nummeshes; ++i) { - model->iqm->textures[i] = old_texture_ids[i]; - if (i < array_count(old_textures)) { + model->iqm->textures[i] = obj->old_texture_ids[i]; + if (i < array_count(obj->old_textures)) { if (model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture) - *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = old_textures[i]; + *model->materials[i].layer[MATERIAL_CHANNEL_DIFFUSE].map.texture = obj->old_textures[i]; } } - array_resize(old_texture_ids, 0); - array_resize(old_textures, 0); + array_resize(obj->old_texture_ids, 0); + array_resize(obj->old_textures, 0); } } glBindVertexArray(0); diff --git a/engine/v4k.h b/engine/v4k.h index 2146995..1f761af 100644 --- a/engine/v4k.h +++ b/engine/v4k.h @@ -3712,6 +3712,8 @@ typedef struct model_t { int num_verts; void *tris; vec3 *meshcenters; + aabb *meshbounds; + float *meshradii; int num_tris; handle vao, ibo, vbo, vao_instanced; @@ -3729,6 +3731,9 @@ typedef struct model_t { int stored_flags; renderstate_t rs[NUM_RENDER_PASSES]; + + bool frustum_enabled; + frustum frustum_state; } model_t; enum BILLBOARD_MODE { @@ -3755,6 +3760,10 @@ API void model_render_instanced(model_t, mat44 proj, mat44 view, mat44 *mode API void model_render_instanced_pass(model_t m, mat44 proj, mat44 view, mat44* models, int shader, unsigned count, int pass); API void model_render_pass(model_t m, mat44 proj, mat44 view, mat44 model, int shader, int pass); API void model_set_texture(model_t*, texture_t t); +API bool model_has_transparency_mesh(model_t m, int mesh); +API bool model_has_transparency(model_t m); +API void model_set_frustum(model_t *m, frustum f); +API void model_clear_frustum(model_t *m); API bool model_get_bone_pose(model_t m, unsigned joint, mat34 *out); API void model_destroy(model_t); @@ -3951,6 +3960,13 @@ typedef struct object_t { float anim_speed; aabb bounds; unsigned billboard; // [0..7] x(4),y(2),z(1) masks + bool disable_frustum_check; + + // internal states + array(handle) old_texture_ids; + array(texture_t) old_textures; + float distance; + bool skip_draw; bool light_cached; //< used by scene to update light data } object_t;