added r_transpSort and we can now sort all surface types by depth

also fixed up RE_AddPolyToScene's overflow check
This commit is contained in:
myT 2020-04-16 05:01:48 +02:00
parent 0fcd462244
commit a5e28eb08f
7 changed files with 139 additions and 28 deletions

View file

@ -93,6 +93,13 @@ add: r_lightmapGreyscale <0.0 to 1.0> (default: 0) to control how monochromatic
r_lightmapGreyscale 0 = full color (nothing done)
r_lightmapGreyscale 1 = completely monochrome
add: r_transpSort <0|1> (default: 0) to select the transparency sorting mode
r_transpSort 0 = sort dynamic transparent surfaces
r_transpSort 1 = sort all transparent surfaces
the drawback of r_transpSort 1 is that gameplay legibility might suffer and results might look
quite inconsistent based on the view angles and surface dimensions
example: dropped items in the cpm18r acid with cg_simpleItems 1
add: r_mapBrightness now works on q3map2's external lightmap atlas images
add: the renderer can now batch surfaces with different (but sufficiently similar) shaders

View file

@ -391,6 +391,14 @@ static void ParseFace( const dsurface_t* ds, const drawVert_t* verts, msurface_t
VectorCopy(cv->plane.normal, cv->verts[i].normal);
}
}
vec3_t mins, maxs;
ClearBounds(mins, maxs);
for ( int i = 0 ; i < numVerts ; i++ ) {
AddPointToBounds(cv->verts[i].xyz, mins, maxs);
}
VectorAdd(mins, maxs, cv->localOrigin);
VectorScale(cv->localOrigin, 0.5f, cv->localOrigin);
}
@ -487,6 +495,9 @@ static void ParseTriSurf( const dsurface_t* ds, const drawVert_t* verts, msurfac
R_ColorShiftLightingBytes( verts[i].color, tri->verts[i].rgba );
}
VectorAdd( tri->bounds[0], tri->bounds[1], tri->localOrigin );
VectorScale( tri->localOrigin, 0.5f, tri->localOrigin );
indexes += LittleLong( ds->firstIndex );
for ( i = 0 ; i < numIndexes ; i++ ) {
tri->indexes[i] = LittleLong( indexes[i] );

View file

@ -163,6 +163,14 @@ S_COLOR_VAL " T2 " S_COLOR_HELP "= Tent 2 (1/3 2/3, same as the CPU version)
"dithering noise strength\n" \
"The dithering on/off switch is " S_COLOR_CVAR "r_dither" S_COLOR_HELP "."
#define help_r_transpSort \
"transparency sorting mode\n" \
S_COLOR_VAL " 0 " S_COLOR_HELP "= Sort dynamic transparent surfaces\n" \
S_COLOR_VAL " 1 " S_COLOR_HELP "= Sort all transparent surfaces\n" \
"The drawback of " S_COLOR_CVAR "r_transpSort " S_COLOR_VAL "1 " S_COLOR_HELP "is that gameplay legibility " \
"might suffer and results might look quite inconsistent based on the view angles and surface dimensions.\n" \
"Example: dropped items in the cpm18r acid with " S_COLOR_CVAR "cg_simpleItems " S_COLOR_VAL "1"
#define help_r_rtColorFormat \
"render target color format\n" \
S_COLOR_VAL " 0 " S_COLOR_HELP "= R8G8B8A8\n" \

View file

@ -73,6 +73,7 @@ cvar_t *r_rtColorFormat;
cvar_t *r_mipGenFilter;
cvar_t *r_mipGenGamma;
cvar_t *r_noiseScale;
cvar_t *r_transpSort;
cvar_t *r_gl3_geoStream;
cvar_t *r_d3d11_syncOffsets;
cvar_t *r_d3d11_presentMode;
@ -408,6 +409,7 @@ static const cvarTableItem_t r_cvars[] =
{ &r_gamma, "r_gamma", "1.2", CVAR_ARCHIVE, CVART_FLOAT, "0.5", "3", help_r_gamma },
{ &r_greyscale, "r_greyscale", "0", CVAR_ARCHIVE, CVART_FLOAT, "0", "1", "controls how monochrome the final image looks" },
{ &r_noiseScale, "r_noiseScale", "1.0", CVAR_ARCHIVE, CVART_FLOAT, "0.125", "8.0", help_r_noiseScale },
{ &r_transpSort, "r_transpSort", "0", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL, help_r_transpSort },
{ &r_lodCurveError, "r_lodCurveError", "2000", CVAR_ARCHIVE, CVART_FLOAT, "250", "10000", "curved surfaces LOD scale" },
//

View file

@ -492,10 +492,13 @@ typedef enum {
} surfaceType_t;
struct drawSurf_t {
unsigned sort; // bit combination for fast compares
float depth; // for sorting transparent surfaces
int index; // for sorting transparent surfaces
const surfaceType_t* surface; // any of surface*_t
// we keep the sort key at the top instead of the pointer
// to allow for slightly cleaner code gen in the radix sort code
unsigned sort; // bit combination for fast compares
float depth; // transparent surface's midpoint's depth
const surfaceType_t* surface; // any of surface*_t
int index; // transparent surface's registration order
qhandle_t model; // MD3 model handle
};
extern void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( const void* );
@ -531,6 +534,7 @@ struct srfPoly_t {
int fogIndex;
int numVerts;
polyVert_t* verts;
vec3_t localOrigin;
};
@ -578,6 +582,8 @@ struct srfSurfaceFace_t {
int numVerts;
srfVert_t *verts;
vec3_t localOrigin;
};
@ -996,6 +1002,7 @@ extern cvar_t *r_intensity;
extern cvar_t *r_gamma;
extern cvar_t *r_greyscale;
extern cvar_t *r_noiseScale; // the strength of the dithering noise
extern cvar_t *r_transpSort; // transparency sorting mode
extern cvar_t *r_lightmap; // render lightmaps only
extern cvar_t *r_lightmapGreyscale; // how monochrome the lightmap looks
extern cvar_t *r_fullbright; // avoid lightmap pass

View file

@ -1068,7 +1068,7 @@ static void R_SortLitsurfs( dlight_t* dl )
void R_AddDrawSurf( const surfaceType_t* surface, const shader_t* shader, int fogIndex )
{
// instead of checking for overflow, we just mask the index so it wraps around
int index = tr.refdef.numDrawSurfs++ & DRAWSURF_MASK;
const int index = tr.refdef.numDrawSurfs++ & DRAWSURF_MASK;
// the sort data is packed into a single 32 bit value so it can be
// compared quickly during the qsorting process
tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT)
@ -1076,6 +1076,7 @@ void R_AddDrawSurf( const surfaceType_t* surface, const shader_t* shader, int fo
| (shader->cullType << QSORT_CULLTYPE_SHIFT)
| (shader->polygonOffset << QSORT_POLYOFF_SHIFT);
tr.refdef.drawSurfs[index].surface = surface;
tr.refdef.drawSurfs[index].model = tr.currentModel != NULL ? tr.currentModel->index : 0;
}
@ -1121,20 +1122,94 @@ static float R_ComputePointDepth( const vec3_t point, const float* modelMatrix )
}
static float R_ComputeSurfaceDepth( const surfaceType_t* surf, int entityNum )
static float R_ComputeEntityPointDepth( const vec3_t point, int entityNum )
{
if ( *surf == SF_ENTITY ) {
const refEntity_t* ent = &tr.refdef.entities[entityNum].e;
if ( ent->reType == RT_SPRITE )
return R_ComputePointDepth( ent->origin, tr.viewParms.world.modelMatrix );
if ( ent->reType == RT_LIGHTNING )
return -999666.0f;
}
return 999666.0f;
orientationr_t orient;
if ( entityNum != ENTITYNUM_WORLD )
R_RotateForEntity( &tr.refdef.entities[entityNum], &tr.viewParms, &orient );
else
orient = tr.viewParms.world;
return R_ComputePointDepth( point, orient.modelMatrix );
}
static float R_ComputeSurfaceDepth( const surfaceType_t* surf, int entityNum, qhandle_t model )
{
const float back = 999666.0f;
const float front = -999666.0f;
if ( *surf == SF_ENTITY ) {
const refEntity_t* const ent = &tr.refdef.entities[entityNum].e;
if ( ent->reType == RT_SPRITE ) // CPMA: simple items, rocket explosions, ...
return R_ComputeEntityPointDepth( ent->origin, entityNum );
if ( ent->reType == RT_LIGHTNING ) // CPMA: first-person lightning gun beam
return front;
// note that RT_MODEL not being checked isn't an omission, it's not needed
return back;
}
if ( *surf == SF_POLY ) { // CPMA: impact marks, rocket smoke, ...
const srfPoly_t* const poly = (const srfPoly_t*)surf;
return R_ComputeEntityPointDepth( poly->localOrigin, entityNum );
}
if ( *surf == SF_MD3 ) { // CPMA: spawn points, rocket projectiles, ...
vec3_t mins, maxs, midPoint;
R_ModelBounds( model, mins, maxs );
VectorAdd( mins, maxs, midPoint );
VectorScale( midPoint, 0.5f, midPoint );
return R_ComputeEntityPointDepth( midPoint, entityNum );
}
// If we don't sort them, we let "static" surfaces be drawn behind the "dynamic" ones.
// This helps avoid inconsistent-looking results like CPMA simple items and
// large enough transparent liquid pools (e.g. dropped weapons in the cpm18r acid).
if ( r_transpSort->integer == 0 )
return back;
if ( *surf == SF_FACE ) { // cpm25 water
const srfSurfaceFace_t* const face = (const srfSurfaceFace_t*)surf;
return R_ComputeEntityPointDepth( face->localOrigin, entityNum );
}
if ( *surf == SF_GRID ) { // hektik_b3 item markers
const srfGridMesh_t* const grid = (const srfGridMesh_t*)surf;
return R_ComputeEntityPointDepth( grid->localOrigin, entityNum );
}
if ( *surf == SF_TRIANGLES ) { // cpm18r acid
const srfTriangles_t* const tri = (const srfTriangles_t*)surf;
return R_ComputeEntityPointDepth( tri->localOrigin, entityNum );
}
return back;
}
/*
A few notes on transparency handling:
a) User-specified blend modes and user-created data mean we can't robustly substitute blend modes
with better alternatives that are commutative (e.g. pre-multiplied alpha instead of the "standard" blend).
The amount of corner cases to handle would be a headache and textures would need to be modified.
I'm not even sure if it's possible. I'd gladly be proven wrong, though.
b) The code currently sorts surfaces (triangle groups) back to front.
Sorting individual triangles would obviously be better for many scenarios,
but it would still not be correct at all times (e.g. intersecting triangles, 3-way overlaps).
c) What we really want is true order-independent transparency (OIT).
There are numerous techniques, but this 2-step method is the most promising avenue:
1. Render transparent surfaces into per-pixel linked lists.
2. Do a single "full-screen" pass which sorts and resolves each pixel's list.
This requires support for atomic shader operations.
When atomic shader operations are not available, we could fall back to:
- The current approach.
- Per-pixel fixed-size arrays (there are several methods).
- Depth peeling (there are also several methods).
*/
static int R_CompareDrawSurfDepth( const void* aPtr, const void* bPtr )
{
const drawSurf_t* a = ( const drawSurf_t* )aPtr;
@ -1211,7 +1286,7 @@ static void R_SortDrawSurfs( int firstDrawSurf, int firstLitSurf )
break;
}
drawSurfs[i].depth = R_ComputeSurfaceDepth( drawSurfs[i].surface, entityNum );
drawSurfs[i].depth = R_ComputeSurfaceDepth( drawSurfs[i].surface, entityNum, drawSurfs[i].model );
drawSurfs[i].index = i;
}
@ -1275,7 +1350,7 @@ static void R_AddEntitySurfaces()
break;
case RT_MODEL:
// we must set up parts of tr.or for model culling
// we must set up parts of tr.orient for model culling
R_RotateForEntity( ent, &tr.viewParms, &tr.orient );
tr.currentModel = R_GetModelByHandle( ent->e.hModel );

View file

@ -91,9 +91,6 @@ void R_AddPolygonSurfaces()
void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t* verts, int numPolys )
{
int i, j;
vec3_t bounds[2];
if ( !tr.registered ) {
return;
}
@ -106,12 +103,12 @@ void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t* verts
// make sure the entire set will fit, rather than taking as many as we can
// both ways have downsides, but if we ARE hitting the cap we're already screwed
// and it's better to avoid something degenerate than to squeeze in 3 extra snowflakes
if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) {
if ( r_numpolyverts + numPolys * numVerts > max_polyverts || r_numpolys + numPolys > max_polys ) {
ri.Printf( PRINT_DEVELOPER, "WARNING: RE_AddPolyToScene: r_max_polys or r_max_polyverts reached\n" );
return;
}
for ( j = 0; j < numPolys; j++ ) {
for ( int j = 0; j < numPolys; j++ ) {
srfPoly_t* poly = &backEndData->polys[r_numpolys];
poly->surfaceType = SF_POLY;
poly->hShader = hShader;
@ -123,15 +120,19 @@ void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t* verts
r_numpolys++;
r_numpolyverts += numVerts;
vec3_t bounds[2];
VectorCopy( poly->verts[0].xyz, bounds[0] );
VectorCopy( poly->verts[0].xyz, bounds[1] );
for ( int i = 1 ; i < poly->numVerts ; i++ ) {
AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] );
}
VectorAdd(bounds[0], bounds[1], poly->localOrigin);
VectorScale(poly->localOrigin, 0.5f, poly->localOrigin);
poly->fogIndex = 0;
// find which fog volume the poly is in (if any)
if (tr.world && (tr.world->numfogs > 1)) {
VectorCopy( poly->verts[0].xyz, bounds[0] );
VectorCopy( poly->verts[0].xyz, bounds[1] );
for ( i = 1 ; i < poly->numVerts ; i++ ) {
AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] );
}
for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
for ( int i = 1 ; i < tr.world->numfogs ; i++ ) {
const fog_t* fog = &tr.world->fogs[i];
if ( bounds[1][0] >= fog->bounds[0][0]
&& bounds[1][1] >= fog->bounds[0][1]