Merge pull request #547 from w23/brush-smoothing

Smooth normals between selected brush surfaces if the angle between them is small enough.

- [x] Per-vertex-per-surface smoothing functionality (differs from qrad, which is per-edge; this gives artifacts as (supposedly) we have higher than lightmap lighting resolution)
- [x] Automatically select surfaces with less than X degrees between normals.
- [x] Make this X threshold adjustable from <map>.bsp.patch
- [ ] ~~Try not joining coplanar surfaces~~ -- doesn't seem to be affecting anything.
- [ ] ~~Think about linking surfaces more. Should we link distant surfaces w/o direct edges to this one?~~ -- it seems that we should be fine for now. Per-surface+vertex vs per-edge smoothing has no clear winner, just different tradeoffs.
- [ ] ~~Scale normals according to surfaces areas, so larger surfaces have more weight (experimental; may improve some artifacts)~~ -- also, non trivial to compute, and may not affect things too much.
- [x] Patch: add explicit smoothing or no-smoothing for given surfaces.

Fixes #139, supersedes #348
This commit is contained in:
Ivan Avdeev 2023-09-05 10:18:46 -07:00 committed by GitHub
commit 29508cd324
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 348 additions and 14 deletions

View File

@ -36,11 +36,14 @@ layout(set = 0, binding = 15, rgba16f) uniform image2D prev_temporal_diffuse;
layout(set = 0, binding = 16, rgba16f) uniform image2D out_temporal_specular;
layout(set = 0, binding = 17, rgba16f) uniform image2D prev_temporal_specular;
const int INDIRECT_SCALE = 2;
//#define DEBUG_TEXTURE normals_gs
//#define DEBUG_TEXTURE emissive
//#define DEBUG_TEXTURE light_point_diffuse
//#define DEBUG_NORMAL
//layout(set = 0, binding = 18, rgba8) uniform readonly image2D material_rmxx;
// Blatantly copypasted from https://www.shadertoy.com/view/XsGfWV
vec3 aces_tonemap(vec3 color){
@ -197,11 +200,15 @@ void main() {
//imageStore(out_dest, pix, vec4(fract(imageLoad(position_t, pix).rgb/10.), 0.)); return;
//imageStore(out_dest, pix, vec4(fract(imageLoad(geometry_prev_position, pix).rgb/50.), 0.)); return;
#if 0
#if defined(DEBUG_NORMAL)
vec3 geometry_normal, shading_normal;
readNormals(pix, geometry_normal, shading_normal);
//imageStore(out_dest, pix, vec4(.5 + geometry_normal * .5, 0.)); return;
imageStore(out_dest, pix, vec4(.5 + shading_normal * .5, 0.)); return;
//const vec4 mat_rmxx = imageLoad(material_rmxx, pix);
//imageStore(out_dest, pix, vec4((.5 + shading_normal * .5)*.8 + .2 * mat_rmxx.a , 0.)); return;
vec3 normal = pix.x < res.x / 2 ? shading_normal : geometry_normal;
imageStore(out_dest, pix, vec4(.5 + normal * .5, 0.)); return;
#endif
/* const uint mi = uint(material_index); */

View File

@ -8,6 +8,7 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#include "ray_primary_common.glsl"
#include "ray_primary_hit.glsl"
//#include "noise.glsl"
#define RAY_PRIMARY_OUTPUTS(X) \
X(10, base_color_a, rgba8) \
@ -97,8 +98,10 @@ void main() {
}
float L = ray.dist;
//uint debug_geometry_index = 0;
if (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT) {
//debug_geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, true);
//debug_geometry_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, true);
primaryRayHit(rq, payload);
L = rayQueryGetIntersectionTEXT(rq, true);
}
@ -108,6 +111,7 @@ void main() {
imageStore(out_position_t, pix, payload.hit_t);
imageStore(out_base_color_a, pix, payload.base_color_a);
imageStore(out_normals_gs, pix, payload.normals_gs);
//imageStore(out_material_rmxx, pix, vec4(payload.material_rmxx.rg, 0, uintToFloat01(xxhash32(debug_geometry_index))));
imageStore(out_material_rmxx, pix, payload.material_rmxx);
imageStore(out_emissive, pix, payload.emissive);
imageStore(out_geometry_prev_position, pix, payload.prev_pos_t);

View File

@ -58,6 +58,21 @@ typedef struct {
int water_indices;
} model_sizes_t;
typedef struct conn_edge_s {
int first_surface;
int count;
} conn_edge_t;
typedef struct linked_value_s {
int value, link;
} linked_value_t;
#define MAX_VERTEX_SURFACES 16
typedef struct conn_vertex_s {
int count;
linked_value_t surfs[MAX_VERTEX_SURFACES];
} conn_vertex_t;
static struct {
struct {
int total_vertices, total_indices;
@ -76,6 +91,16 @@ static struct {
#define MAX_ANIMATED_TEXTURES 256
int updated_textures[MAX_ANIMATED_TEXTURES];
// Smoothed normals comptutation
// Connectome for edges and vertices
struct {
int edges_capacity;
conn_edge_t *edges;
int vertices_capacity;
conn_vertex_t *vertices;
} conn;
} g_brush;
void VK_InitRandomTable( void )
@ -107,8 +132,9 @@ qboolean VK_BrushInit( void )
return true;
}
void VK_BrushShutdown( void )
{
void VK_BrushShutdown( void ) {
if (g_brush.conn.edges)
Mem_Free(g_brush.conn.edges);
}
// speed up sin calculations
@ -763,6 +789,218 @@ typedef struct {
uint16_t *out_indices;
} fill_geometries_args_t;
static void getSurfaceNormal( const msurface_t *surf, vec3_t out_normal) {
if( FBitSet( surf->flags, SURF_PLANEBACK ))
VectorNegate( surf->plane->normal, out_normal );
else
VectorCopy( surf->plane->normal, out_normal );
// TODO scale normal by area -- bigger surfaces should have bigger impact
//VectorScale(normal, surf->plane.
}
static int getSurfaceTexture(const msurface_t *surf, int surface_index) {
const xvk_patch_surface_t *const psurf = R_VkPatchGetSurface(surface_index);
if (psurf && psurf->tex_id >= 0)
return psurf->tex_id;
return surf->texinfo->texture->gl_texturenum;
}
static qboolean shouldSmoothLinkSurfaces(const model_t* mod, int surf1, int surf2) {
//return Q_min(surf1, surf2) == 741 && Q_max(surf1, surf2) == 743;
// Filter explicit exclusion
for (int i = 0; i < g_map_entities.smoothing.excluded_count; i+=2) {
const int cand1 = g_map_entities.smoothing.excluded[i];
const int cand2 = g_map_entities.smoothing.excluded[i+1];
if ((cand1 == surf1 && cand2 == surf2)
|| (cand1 == surf2 && cand2 == surf1))
return false;
}
for (int i = 0; i < g_map_entities.smoothing.groups_count; ++i) {
const xvk_smoothing_group_t *g = g_map_entities.smoothing.groups + i;
uint32_t bits = 0;
for (int j = 0; j < g->count; ++j) {
if (g->surfaces[j] == surf1) {
bits |= 1;
if (bits == 3)
return true;
}
else if (g->surfaces[j] == surf2) {
bits |= 2;
if (bits == 3)
return true;
}
}
}
// Do not join surfaces with different textures. Assume they belong to different objects.
const int t1 = getSurfaceTexture(mod->surfaces + surf1, surf1);
const int t2 = getSurfaceTexture(mod->surfaces + surf2, surf2);
if (t1 != t2)
return false;
vec3_t n1, n2;
getSurfaceNormal(mod->surfaces + surf1, n1);
getSurfaceNormal(mod->surfaces + surf2, n2);
const float dot = DotProduct(n1, n2);
DEBUG("Smoothing: dot(%d, %d) = %f (t=%f)", surf1, surf2, dot, g_map_entities.smoothing.threshold);
return dot >= g_map_entities.smoothing.threshold;
}
static int lvFindValue(const linked_value_t *li, int count, int needle) {
for (int i = 0; i < count; ++i)
if (li[i].value == needle)
return i;
return -1;
}
static int lvFindOrAddValue(linked_value_t *li, int *count, int capacity, int needle) {
const int found = lvFindValue(li, *count, needle);
if (found >= 0)
return found;
if (*count == capacity)
return -1;
li[*count].value = needle;
li[*count].link = *count;
return (*count)++;
}
static int lvFindBaseIndex(const linked_value_t *li, int index) {
while (li[index].link != index)
index = li[index].link;
return index;
}
static void lvFlatten(linked_value_t *li, int count) {
for (int i = 0; i < count; ++i) {
for (int j = i; j < count; ++j) {
if (lvFindBaseIndex(li, j) == i) {
li[j].link = i;
}
}
}
}
static void linkSmoothSurfaces(const model_t* mod, int surf1, int surf2, int vertex_index) {
conn_vertex_t *v = g_brush.conn.vertices + vertex_index;
int i1 = lvFindOrAddValue(v->surfs, &v->count, COUNTOF(v->surfs), surf1);
int i2 = lvFindOrAddValue(v->surfs, &v->count, COUNTOF(v->surfs), surf2);
DEBUG("Link %d(%d)<->%d(%d) v=%d", surf1, i1, surf2, i2, vertex_index);
if (i1 < 0 || i2 < 0) {
ERR("Model %s cannot smooth link surf %d<->%d for vertex %d", mod->name, surf1, surf2, vertex_index);
return;
}
i1 = lvFindBaseIndex(v->surfs, i1);
i2 = lvFindBaseIndex(v->surfs, i2);
// Link them
v->surfs[Q_max(i1, i2)].link = Q_min(i1, i2);
}
static void connectVertices( const model_t *mod ) {
if (mod->numedges > g_brush.conn.edges_capacity) {
if (g_brush.conn.edges)
Mem_Free(g_brush.conn.edges);
g_brush.conn.edges_capacity = mod->numedges;
g_brush.conn.edges = Mem_Calloc(vk_core.pool, sizeof(*g_brush.conn.edges) * g_brush.conn.edges_capacity);
}
if (mod->numvertexes > g_brush.conn.vertices_capacity) {
if (g_brush.conn.vertices)
Mem_Free(g_brush.conn.vertices);
g_brush.conn.vertices_capacity = mod->numvertexes;
g_brush.conn.vertices = Mem_Calloc(vk_core.pool, sizeof(*g_brush.conn.vertices) * g_brush.conn.vertices_capacity);
}
// Find connection edges
for (int i = 0; i < mod->nummodelsurfaces; ++i) {
const int surface_index = mod->firstmodelsurface + i;
const msurface_t *surf = mod->surfaces + surface_index;
for(int k = 0; k < surf->numedges; k++) {
const int iedge_dir = mod->surfedges[surf->firstedge + k];
const int iedge = iedge_dir >= 0 ? iedge_dir : -iedge_dir;
ASSERT(iedge >= 0);
ASSERT(iedge < mod->numedges);
conn_edge_t *cedge = g_brush.conn.edges + iedge;
if (cedge->count == 0) {
cedge->first_surface = surface_index;
} else {
const medge_t *edge = mod->edges + iedge;
if (shouldSmoothLinkSurfaces(mod, cedge->first_surface, surface_index)) {
linkSmoothSurfaces(mod, cedge->first_surface, surface_index, edge->v[0]);
linkSmoothSurfaces(mod, cedge->first_surface, surface_index, edge->v[1]);
}
if (cedge->count > 1) {
WARN("Model %s edge %d has %d surfaces", mod->name, i, cedge->count);
}
}
cedge->count++;
} // for surf->numedges
} // for mod->nummodelsurfaces
int hist[17] = {0};
for (int i = 0; i < mod->numvertexes; ++i) {
conn_vertex_t *vtx = g_brush.conn.vertices + i;
if (vtx->count < 16) {
hist[vtx->count]++;
} else {
hist[16]++;
}
lvFlatten(vtx->surfs, vtx->count);
// Too verbose
#if 0
if (vtx->count) {
DEBUG("Vertex %d linked count %d", i, vtx->count);
for (int j = 0; j < vtx->count; ++j) {
DEBUG(" %d: l=%d v=%d", j, vtx->surfs[j].link, vtx->surfs[j].value);
}
}
#endif
}
for (int i = 0; i < COUNTOF(hist); ++i) {
DEBUG("VTX hist[%d] = %d", i, hist[i]);
}
}
static qboolean getSmoothedNormalFor(const model_t* mod, int vertex_index, int surface_index, vec3_t out_normal) {
const conn_vertex_t *v = g_brush.conn.vertices + vertex_index;
const int index = lvFindValue(v->surfs, v->count, surface_index);
if (index < 0)
return false;
const int base = lvFindBaseIndex(v->surfs, index);
vec3_t normal = {0};
for (int i = 0; i < v->count; ++i) {
if (v->surfs[i].link == base) {
const int surface = v->surfs[i].value;
vec3_t surf_normal = {0};
getSurfaceNormal(mod->surfaces + surface, surf_normal);
VectorAdd(normal, surf_normal, normal);
}
}
VectorNormalize(normal);
VectorCopy(normal, out_normal);
return true;
}
static qboolean fillBrushSurfaces(fill_geometries_args_t args) {
int vertex_offset = 0;
int num_geometries = 0;
@ -772,13 +1010,15 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) {
uint16_t *p_ind = args.out_indices;
int index_offset = args.base_index_offset;
connectVertices(args.mod);
// Load sorted by gl_texturenum
// TODO this does not make that much sense in vulkan (can sort later)
for (int t = 0; t <= args.sizes.max_texture_id; ++t) {
for( int i = 0; i < args.mod->nummodelsurfaces; ++i) {
const int surface_index = args.mod->firstmodelsurface + i;
msurface_t *surf = args.mod->surfaces + surface_index;
mextrasurf_t *info = surf->info;
const mextrasurf_t *info = surf->info;
vk_render_geometry_t *model_geometry = args.out_geometries + num_geometries;
const float sample_size = gEngine.Mod_SampleSizeForFace( surf );
int index_count = 0;
@ -841,11 +1081,18 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) {
VectorCopy(surf->texinfo->vecs[0], tangent);
VectorNormalize(tangent);
vec3_t surf_normal;
getSurfaceNormal(surf, surf_normal);
vk_vertex_t *const pvert_begin = p_vert;
for( int k = 0; k < surf->numedges; k++ )
{
const int iedge = args.mod->surfedges[surf->firstedge + k];
const medge_t *edge = args.mod->edges + (iedge >= 0 ? iedge : -iedge);
const mvertex_t *in_vertex = args.mod->vertexes + (iedge >= 0 ? edge->v[0] : edge->v[1]);
const int iedge_dir = args.mod->surfedges[surf->firstedge + k];
const int iedge = iedge_dir >= 0 ? iedge_dir : -iedge_dir;
const medge_t *edge = args.mod->edges + iedge;
const int vertex_index = iedge_dir >= 0 ? edge->v[0] : edge->v[1];
const mvertex_t *in_vertex = args.mod->vertexes + vertex_index;
vk_vertex_t vertex = {
{in_vertex->position[0], in_vertex->position[1], in_vertex->position[2]},
};
@ -902,9 +1149,10 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) {
vertex.lm_tc[1] = t;
}
if( FBitSet( surf->flags, SURF_PLANEBACK ))
VectorNegate( surf->plane->normal, vertex.normal );
else VectorCopy( surf->plane->normal, vertex.normal );
// Compute smoothed normal if needed
if (!getSmoothedNormalFor(args.mod, vertex_index, surface_index, vertex.normal)) {
VectorCopy(surf_normal, vertex.normal);
}
VectorCopy(tangent, vertex.tangent);
@ -920,11 +1168,11 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) {
index_count += 3;
index_offset += 3;
}
}
} // for surf->numedges
model_geometry->element_count = index_count;
vertex_offset += surf->numedges;
}
} // for mod->nummodelsurfaces
}
ASSERT(args.sizes.num_surfaces == num_geometries);
@ -1081,6 +1329,10 @@ void VK_BrushModelDestroyAll( void ) {
g_brush.stat.total_vertices = 0;
g_brush.stat.total_indices = 0;
g_brush.models_count = 0;
memset(g_brush.conn.edges, 0, sizeof(*g_brush.conn.edges) * g_brush.conn.edges_capacity);
memset(g_brush.conn.vertices, 0, sizeof(*g_brush.conn.vertices) * g_brush.conn.vertices_capacity);
}
static rt_light_add_polygon_t loadPolyLight(const model_t *mod, const int surface_index, const msurface_t *surf, const vec3_t emissive) {

View File

@ -518,7 +518,40 @@ static void patchEntity( const entity_props_t *props, uint32_t have_fields ) {
WARN("vk_mapents: trying to patch unsupported entity %d class %d", ei, ref->class);
}
}
}
static void appendExludedPairs(const entity_props_t *props) {
if (props->_xvk_smoothing_excluded_pairs.num % 2 != 0) {
ERR("vk_mapents: smoothing group exclusion list should be list of pairs -- divisible by 2; cutting the tail");
}
int count = props->_xvk_smoothing_excluded_pairs.num & ~1;
if (g_map_entities.smoothing.excluded_count + count > COUNTOF(g_map_entities.smoothing.excluded)) {
ERR("vk_mapents: smoothing exclusion group capacity exceeded, go complain in github issues");
count = COUNTOF(g_map_entities.smoothing.excluded) - g_map_entities.smoothing.excluded_count;
}
memcpy(g_map_entities.smoothing.excluded + g_map_entities.smoothing.excluded_count, props->_xvk_smoothing_excluded_pairs.values, count * sizeof(int));
g_map_entities.smoothing.excluded_count += count;
}
static void addSmoothingGroup(const entity_props_t *props) {
if (g_map_entities.smoothing.groups_count == MAX_INCLUDED_SMOOTHING_GROUPS) {
ERR("vk_mapents: limit of %d smoothing groups reached", MAX_INCLUDED_SMOOTHING_GROUPS);
return;
}
xvk_smoothing_group_t *g = g_map_entities.smoothing.groups + (g_map_entities.smoothing.groups_count++);
int count = props->_xvk_smoothing_group.num;
if (count > MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP) {
ERR("vk_mapents: too many surfaces in a smoothing group. Max %d, got %d. Culling", MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP, props->_xvk_smoothing_group.num);
count = MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP;
}
memcpy(g->surfaces, props->_xvk_smoothing_group.values, sizeof(int) * count);
g->count = count;
}
static void parseEntities( char *string, qboolean is_patch ) {
@ -570,6 +603,18 @@ static void parseEntities( char *string, qboolean is_patch ) {
addPatchSurface( &values, have_fields );
} else if (have_fields & Field__xvk_ent_id) {
patchEntity( &values, have_fields );
} else {
if (have_fields & Field__xvk_smoothing_threshold) {
g_map_entities.smoothing.threshold = cosf(DEG2RAD(values._xvk_smoothing_threshold));
}
if (have_fields & Field__xvk_smoothing_excluded_pairs) {
appendExludedPairs(&values);
}
if (have_fields & Field__xvk_smoothing_group) {
addSmoothingGroup(&values);
}
}
}
break;
@ -676,6 +721,11 @@ void XVK_ParseMapEntities( void ) {
g_map_entities.single_environment_index = NoEnvironmentLights;
g_map_entities.entity_count = 0;
g_map_entities.func_walls_count = 0;
g_map_entities.smoothing.threshold = cosf(DEG2RAD(45.f));
g_map_entities.smoothing.excluded_count = 0;
for (int i = 0; i < g_map_entities.smoothing.groups_count; ++i)
g_map_entities.smoothing.groups[i].count = 0;
g_map_entities.smoothing.groups_count = 0;
parseEntities( map->entities, false );
orientSpotlights();

View File

@ -26,6 +26,9 @@
X(19, vec2_t, _xvk_tex_offset, Vec2) \
X(20, vec2_t, _xvk_tex_scale, Vec2) \
X(21, string, model, String) \
X(22, float, _xvk_smoothing_threshold, Float) \
X(23, int_array_t, _xvk_smoothing_excluded_pairs, IntArray) \
X(24, int_array_t, _xvk_smoothing_group, IntArray) \
/* NOTE: not used
X(22, int, rendermode, Int) \
@ -109,6 +112,12 @@ typedef struct {
int index;
} xvk_mapent_ref_t;
#define MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP 16
typedef struct {
int count;
int surfaces[MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP];
} xvk_smoothing_group_t;
typedef struct {
int num_lights;
vk_light_entity_t lights[256];
@ -128,6 +137,18 @@ typedef struct {
// TODO find out how to read this from the engine, or make its size dynamic
//#define MAX_MAP_ENTITIES 2048
xvk_mapent_ref_t refs[MAX_MAP_ENTITIES];
struct {
float threshold;
#define MAX_EXCLUDED_SMOOTHING_SURFACES_PAIRS 32
int excluded[MAX_EXCLUDED_SMOOTHING_SURFACES_PAIRS * 2];
int excluded_count;
#define MAX_INCLUDED_SMOOTHING_GROUPS 32
int groups_count;
xvk_smoothing_group_t groups[MAX_INCLUDED_SMOOTHING_GROUPS];
} smoothing;
} xvk_map_entities_t;
extern xvk_map_entities_t g_map_entities;