Skip to content

Commit

Permalink
Added limited support for poly-light extraction from MD2/MD3/IQM models.
Browse files Browse the repository at this point in the history
Current limitations:
- No vertex or skeletal animations;
- No multiple skins;
- The emissive parts of models should use a separate material that fills the entire emissive texture with a more-or-less uniform color.
  • Loading branch information
apanteleev committed Aug 24, 2021
1 parent accb818 commit db56a0d
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 42 deletions.
13 changes: 13 additions & 0 deletions inc/refresh/models.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ typedef struct iqm_mesh_s
uint32_t first_influence, num_influences;
} iqm_mesh_t;

typedef struct light_poly_s {
float positions[9]; // 3x vec3_t
vec3_t off_center;
vec3_t color;
struct pbr_material_s* material;
int cluster;
int style;
float emissive_factor;
} light_poly_t;

typedef struct model_s {
enum {
MOD_FREE,
Expand Down Expand Up @@ -142,6 +152,9 @@ typedef struct model_s {
qboolean sprite_vertical;

iqm_model_t* iqmData;

int num_light_polys;
light_poly_t* light_polys;
} model_t;

extern model_t r_models[];
Expand Down
78 changes: 46 additions & 32 deletions src/refresh/vkpt/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1476,7 +1476,7 @@ static int model_entity_id_count[2];
static int world_entity_id_count[2];
static int iqm_matrix_count[2];

#define MAX_MODEL_LIGHTS 1024
#define MAX_MODEL_LIGHTS 16384
static int num_model_lights = 0;
static light_poly_t model_lights[MAX_MODEL_LIGHTS];

Expand Down Expand Up @@ -1588,6 +1588,40 @@ static inline void transform_point(const float* p, const float* matrix, float* r
VectorCopy(transformed, result); // vec4 -> vec3
}

static void instance_model_lights(int num_light_polys, const light_poly_t* light_polys, const float* transform)
{
for (int nlight = 0; nlight < num_light_polys; nlight++)
{
if (num_model_lights >= MAX_MODEL_LIGHTS)
{
assert(!"Model light count overflow");
break;
}

const light_poly_t* src_light = light_polys + nlight;
light_poly_t* dst_light = model_lights + num_model_lights;

// Transform the light's positions and center
transform_point(src_light->positions + 0, transform, dst_light->positions + 0);
transform_point(src_light->positions + 3, transform, dst_light->positions + 3);
transform_point(src_light->positions + 6, transform, dst_light->positions + 6);
transform_point(src_light->off_center, transform, dst_light->off_center);

// Find the cluster based on the center. Maybe it's OK to use the model's cluster, need to test.
dst_light->cluster = BSP_PointLeaf(bsp_world_model->nodes, dst_light->off_center)->cluster;

// We really need to map these lights to a cluster
if (dst_light->cluster < 0)
continue;

// Copy the other light properties
VectorCopy(src_light->color, dst_light->color);
dst_light->material = src_light->material;

num_model_lights++;
}
}

static void process_bsp_entity(const entity_t* entity, int* bsp_mesh_idx, int* instance_idx, int* num_instanced_vert)
{
QVKInstanceBuffer_t* uniform_instance_buffer = &vkpt_refdef.uniform_instance_buffer;
Expand Down Expand Up @@ -1659,37 +1693,8 @@ static void process_bsp_entity(const entity_t* entity, int* bsp_mesh_idx, int* i
((int*)uniform_instance_buffer->model_indices)[*instance_idx] = ~current_bsp_mesh_index;

*num_instanced_vert += mesh_vertex_num;

for (int nlight = 0; nlight < model->num_light_polys; nlight++)
{
if (num_model_lights >= MAX_MODEL_LIGHTS)
{
assert(!"Model light count overflow");
break;
}

const light_poly_t* src_light = model->light_polys + nlight;
light_poly_t* dst_light = model_lights + num_model_lights;

// Transform the light's positions and center
transform_point(src_light->positions + 0, transform, dst_light->positions + 0);
transform_point(src_light->positions + 3, transform, dst_light->positions + 3);
transform_point(src_light->positions + 6, transform, dst_light->positions + 6);
transform_point(src_light->off_center, transform, dst_light->off_center);

// Find the cluster based on the center. Maybe it's OK to use the model's cluster, need to test.
dst_light->cluster = BSP_PointLeaf(bsp_world_model->nodes, dst_light->off_center)->cluster;

// We really need to map these lights to a cluster
if(dst_light->cluster < 0)
continue;

// Copy the other light properties
VectorCopy(src_light->color, dst_light->color);
dst_light->material = src_light->material;

num_model_lights++;
}

instance_model_lights(model->num_light_polys, model->light_polys, transform);

(*bsp_mesh_idx)++;
(*instance_idx)++;
Expand Down Expand Up @@ -1937,6 +1942,15 @@ prepare_entities(EntityUploadInfo* upload_info)
if (contains_masked)
masked_model_indices[masked_model_num++] = i;
}

if (model->num_light_polys > 0)
{
float transform[16];
const qboolean is_viewer_weapon = (entity->flags & RF_WEAPONMODEL) != 0;
create_entity_matrix(transform, (entity_t*)entity, is_viewer_weapon);

instance_model_lights(model->num_light_polys, model->light_polys, transform);
}
}
}

Expand Down
111 changes: 111 additions & 0 deletions src/refresh/vkpt/models.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,111 @@ static void export_obj_frames(model_t* model, const char* path_pattern)
}
}

static void extract_model_lights(model_t* model)
{
// Count the triangles in the model that have a material with the is_light flag set

int num_lights = 0;

for (int mesh_idx = 0; mesh_idx < model->nummeshes; mesh_idx++)
{
const maliasmesh_t* mesh = model->meshes + mesh_idx;
for (int skin_idx = 0; skin_idx < mesh->numskins; skin_idx++)
{
const pbr_material_t* mat = mesh->materials[skin_idx];
if ((mat->flags & MATERIAL_FLAG_LIGHT) != 0 && mat->image_emissive)
{
if (mesh->numskins != 1)
{
Com_DPrintf("Warning: model %s mesh %d has LIGHT material(s) but more than 1 skin (%d), "
"which is unsupported.\n", model->name, mesh->numskins);
return;
}

num_lights += mesh->numtris;
}
}
}

// If there are no light triangles, there's nothing to do
if (num_lights == 0)
return;

// Validate our current implementation limitations, give warnings if they are hit

if (model->numframes > 1)
{
Com_DPrintf("Warning: model %s has LIGHT material(s) but more than 1 vertex animation frame, "
"which is unsupported.\n", model->name);
return;
}

if (model->iqmData && model->iqmData->blend_weights)
{
Com_DPrintf("Warning: model %s has LIGHT material(s) and skeletal animations, "
"which is unsupported.\n", model->name);
return;
}

// Actually extract the lights now

model->light_polys = Hunk_Alloc(&model->hunk, sizeof(light_poly_t) * num_lights);
model->num_light_polys = num_lights;

num_lights = 0;

for (int mesh_idx = 0; mesh_idx < model->nummeshes; mesh_idx++)
{
const maliasmesh_t* mesh = model->meshes + mesh_idx;
assert(mesh->numskins == 1);
assert(mesh->indices);
assert(mesh->positions);

pbr_material_t* mat = mesh->materials[0];
if ((mat->flags & MATERIAL_FLAG_LIGHT) != 0 && mat->image_emissive)
{
for (int tri_idx = 0; tri_idx < mesh->numtris; tri_idx++)
{
light_poly_t* light = model->light_polys + num_lights;
num_lights++;

int i0 = mesh->indices[tri_idx * 3 + 0];
int i1 = mesh->indices[tri_idx * 3 + 1];
int i2 = mesh->indices[tri_idx * 3 + 2];

assert(i0 < mesh->numverts);
assert(i1 < mesh->numverts);
assert(i2 < mesh->numverts);

memcpy(light->positions + 0, mesh->positions + i0, sizeof(vec3_t));
memcpy(light->positions + 3, mesh->positions + i1, sizeof(vec3_t));
memcpy(light->positions + 6, mesh->positions + i2, sizeof(vec3_t));

// Cluster is assigned after model instancing and transformation
light->cluster = -1;

light->material = mat;

VectorCopy(mat->image_emissive->light_color, light->color);

if (!mat->image_emissive->entire_texture_emissive)
{
// This extraction doesn't support partially emissive textures, so pretend the entire
// texture is uniformly emissive and dim the light according to the area fraction.
light->emissive_factor =
(mat->image_emissive->max_light_texcoord[0] - mat->image_emissive->min_light_texcoord[0]) *
(mat->image_emissive->max_light_texcoord[1] - mat->image_emissive->min_light_texcoord[1]);
}
else
light->emissive_factor = 1.f;

get_triangle_off_center(light->positions, light->off_center, NULL, 1.f);

}
}
}
}

qerror_t MOD_LoadMD2_RTX(model_t *model, const void *rawdata, size_t length, const char* mod_name)
{
dmd2header_t header;
Expand Down Expand Up @@ -357,6 +462,8 @@ qerror_t MOD_LoadMD2_RTX(model_t *model, const void *rawdata, size_t length, con
dst_mesh->indices[i + 2] = tmp;
}

extract_model_lights(model);

Hunk_End(&model->hunk);
return Q_ERR_SUCCESS;

Expand Down Expand Up @@ -565,6 +672,8 @@ qerror_t MOD_LoadMD3_RTX(model_t *model, const void *rawdata, size_t length, con
//if (strstr(model->name, "v_blast"))
// export_obj_frames(model, "export/v_blast_%d.obj");

extract_model_lights(model);

Hunk_End(&model->hunk);
return Q_ERR_SUCCESS;

Expand Down Expand Up @@ -633,6 +742,8 @@ qerror_t MOD_LoadIQM_RTX(model_t* model, const void* rawdata, size_t length, con
mesh->numskins = 1; // looks like IQM only supports one skin?
}

extract_model_lights(model);

Hunk_End(&model->hunk);

return Q_ERR_SUCCESS;
Expand Down
24 changes: 24 additions & 0 deletions src/refresh/vkpt/vertex_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,27 @@ inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, ligh
max_cluster_model_lights[c] = max(max_cluster_model_lights[c], cluster_light_counts[c]);
}

// Count the total required list size

int required_size = bsp_mesh->cluster_light_offsets[bsp_mesh->num_clusters];
for (int c = 0; c < bsp_mesh->num_clusters; c++)
{
required_size += max_cluster_model_lights[c];
}

// See if we have enough room in the interaction buffer

if (required_size > MAX_LIGHT_LIST_NODES)
{
Com_WPrintf("Insufficient light interaction buffer size (%d needed). Increase MAX_LIGHT_LIST_NODES.\n", required_size);

// Copy the BSP light lists verbatim
memcpy(dst_lists, bsp_mesh->cluster_lights, sizeof(uint32_t) * bsp_mesh->cluster_light_offsets[bsp_mesh->num_clusters]);
memcpy(dst_list_offsets, bsp_mesh->cluster_light_offsets, sizeof(uint32_t) * (bsp_mesh->num_clusters + 1));

return;
}

// Copy the static light lists, and make room in these lists to inject the model lights

int tail = 0;
Expand All @@ -209,6 +230,9 @@ inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, ligh
dst_list_offsets[c] = tail;
memcpy(dst_lists + tail, bsp_mesh->cluster_lights + bsp_mesh->cluster_light_offsets[c], sizeof(uint32_t) * original_size);
tail += original_size;

assert(tail + max_cluster_model_lights[c] < MAX_LIGHT_LIST_NODES);

if (max_cluster_model_lights[c] > 0) {
memset(dst_lists + tail, 0xff, sizeof(uint32_t) * max_cluster_model_lights[c]);
}
Expand Down
10 changes: 0 additions & 10 deletions src/refresh/vkpt/vkpt.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,16 +321,6 @@ LIST_EXTENSIONS_INSTANCE

#define MAX_SKY_CLUSTERS 1024

typedef struct light_poly_s {
float positions[9]; // 3x vec3_t
vec3_t off_center;
vec3_t color;
struct pbr_material_s* material;
int cluster;
int style;
float emissive_factor;
} light_poly_t;

typedef struct bsp_model_s {
uint32_t idx_offset;
uint32_t idx_count;
Expand Down

0 comments on commit db56a0d

Please sign in to comment.