Point lights can be culled. #756

This commit is contained in:
Robert Beckebans 2024-08-26 10:26:40 +02:00
parent 5107d12cc5
commit e121472661
8 changed files with 161 additions and 114 deletions

View file

@ -501,9 +501,10 @@ float idConsoleLocal::DrawFPS( float y )
//ImGui::Text( "Cull: %i box in %i box out\n",
// commonLocal.stats_frontend.c_box_cull_in, commonLocal.stats_frontend.c_box_cull_out );
ImGui::TextColored( colorLtGrey, "MASKCULL: tests:%-3i culls:%i maskVerts:%i maskTris:%i",
ImGui::TextColored( colorLtGrey, "MASKCULL: tests:%-3i lightCulls:%i surfCulls:%i verts:%i tris:%i",
commonLocal.stats_frontend.c_mocTests,
commonLocal.stats_frontend.c_mocCulls,
commonLocal.stats_frontend.c_mocCulledLights,
commonLocal.stats_frontend.c_mocCulledSurfaces,
commonLocal.stats_frontend.c_mocVerts,
commonLocal.stats_frontend.c_mocIndexes );

View file

@ -3,7 +3,6 @@
#
set(CMAKE_SUPPRESS_REGENERATION true)
option(USE_AVX512 "Enable experimental AVX-512 support" OFF)
set(CMAKE_CONFIGURATION_TYPES Debug Release)
#
# Lists of all files included in the library

View file

@ -1046,6 +1046,7 @@ public:
idRenderBackend backend;
MaskedOcclusionCulling* maskedOcclusionCulling;
idVec4 maskedUnitCubeVerts[8];
idVec4 maskedZeroOneCubeVerts[8];
unsigned int maskedZeroOneCubeIndexes[36];

View file

@ -129,7 +129,8 @@ struct performanceCounters_t
int c_mocVerts;
int c_mocIndexes;
int c_mocTests;
int c_mocCulls;
int c_mocCulledSurfaces;
int c_mocCulledLights;
uint64 frontEndMicroSec; // sum of time in all RE_RenderScene's in a frame
};

View file

@ -1945,7 +1945,7 @@ static srfTriangles_t* R_MakeZeroOneCubeTris()
// RB begin
static void R_MakeZeroOneCubeTrisForMaskedOcclusionCulling()
{
const float low = -1.0f;
const float low = 0.0f;
const float high = 1.0f;
idVec3 center( 0.0f );
@ -1975,6 +1975,40 @@ static void R_MakeZeroOneCubeTrisForMaskedOcclusionCulling()
verts[5].w = 1;
verts[6].w = 1;
verts[7].w = 1;
}
static void R_MakeUnitCubeTrisForMaskedOcclusionCulling()
{
const float low = -1.0f;
const float high = 1.0f;
idVec3 center( 0.0f );
idVec3 mx( low, 0.0f, 0.0f );
idVec3 px( high, 0.0f, 0.0f );
idVec3 my( 0.0f, low, 0.0f );
idVec3 py( 0.0f, high, 0.0f );
idVec3 mz( 0.0f, 0.0f, low );
idVec3 pz( 0.0f, 0.0f, high );
idVec4* verts = tr.maskedUnitCubeVerts;
verts[0].ToVec3() = center + mx + my + mz;
verts[1].ToVec3() = center + px + my + mz;
verts[2].ToVec3() = center + px + py + mz;
verts[3].ToVec3() = center + mx + py + mz;
verts[4].ToVec3() = center + mx + my + pz;
verts[5].ToVec3() = center + px + my + pz;
verts[6].ToVec3() = center + px + py + pz;
verts[7].ToVec3() = center + mx + py + pz;
verts[0].w = 1;
verts[1].w = 1;
verts[2].w = 1;
verts[3].w = 1;
verts[4].w = 1;
verts[5].w = 1;
verts[6].w = 1;
verts[7].w = 1;
unsigned int* indexes = tr.maskedZeroOneCubeIndexes;
@ -2262,6 +2296,7 @@ void idRenderSystemLocal::Init()
maskedOcclusionCulling = MaskedOcclusionCulling::Create();
R_MakeZeroOneCubeTrisForMaskedOcclusionCulling();
R_MakeUnitCubeTrisForMaskedOcclusionCulling();
// make sure the command buffers are ready to accept the first screen update
SwapCommandBuffers( NULL, NULL, NULL, NULL, NULL, NULL );

View file

@ -3,7 +3,7 @@
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
Copyright (C) 2013-2014 Robert Beckebans
Copyright (C) 2013-2024 Robert Beckebans
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
@ -30,6 +30,8 @@ If you have questions concerning this license or the applicable additional terms
#include "precompiled.h"
#pragma hdrstop
#include "../libs/moc/MaskedOcclusionCulling.h"
#include "RenderCommon.h"
extern idCVar r_useAreasConnectedForShadowCulling;
@ -264,6 +266,86 @@ static void R_AddSingleLight( viewLight_t* vLight )
vLight->scissorRect.zmin = projected[0][2];
vLight->scissorRect.zmax = projected[1][2];
const bool viewInsideLight = !idRenderMatrix::CullPointToMVP( light->baseLightProject, viewDef->renderView.vieworg, true );
// RB: test surface visibility by drawing the triangles of the bounds
// FIXME spot light projections are too short
if( r_useMaskedOcclusionCulling.GetBool() && !viewInsideLight && vLight->pointLight )
{
#if 1
idVec4 triVerts[3];
unsigned int triIndices[] = { 0, 1, 2 };
tr.pc.c_mocIndexes += 36;
tr.pc.c_mocVerts += 8;
idRenderMatrix invProjectMVPMatrix;
idRenderMatrix::Multiply( viewDef->worldSpace.unjitteredMVP, light->inverseBaseLightProject, invProjectMVPMatrix );
tr.pc.c_mocTests += 1;
bool maskVisible = false;
// NOTE: zeroToOne cube is only for lights and models need the unit cube
idVec4* verts = tr.maskedZeroOneCubeVerts;
unsigned int* indexes = tr.maskedZeroOneCubeIndexes;
for( int i = 0, face = 0; i < 36; i += 3, face++ )
{
const idVec4& v0 = verts[indexes[i + 0]];
const idVec4& v1 = verts[indexes[i + 1]];
const idVec4& v2 = verts[indexes[i + 2]];
// transform to clip space
invProjectMVPMatrix.TransformPoint( v0, triVerts[0] );
invProjectMVPMatrix.TransformPoint( v1, triVerts[1] );
invProjectMVPMatrix.TransformPoint( v2, triVerts[2] );
#if 1
// backface none so objects are still visible where we run into
MaskedOcclusionCulling::CullingResult result = tr.maskedOcclusionCulling->TestTriangles( ( float* )triVerts, triIndices, 1, NULL, MaskedOcclusionCulling::BACKFACE_NONE );
if( result == MaskedOcclusionCulling::VISIBLE )
{
maskVisible = true;
}
#else
// draw for debugging
tr.maskedOcclusionCulling->RenderTriangles( ( float* )triVerts, triIndices, 1, NULL, MaskedOcclusionCulling::BACKFACE_NONE );
maskVisible = true;
#endif
}
if( !maskVisible )
{
tr.pc.c_mocCulledLights += 1;
return;
}
#else
// scissor test alternative
// I would prefer this method however lights become visible again when the distance increases to the occluder surface
// source scissor rectangle has GL convention and starts in the lower left corner
// convert to NDC values
float x1 = -1.0f + ( float( vLight->scissorRect.x1 ) / screenWidth ) * 2.0f;
float x2 = -1.0f + ( float( vLight->scissorRect.x2 ) / screenWidth ) * 2.0f;
float y1 = -1.0f + ( float( vLight->scissorRect.y1 ) / screenHeight ) * 2.0f;
float y2 = -1.0f + ( float( vLight->scissorRect.y2 ) / screenHeight ) * 2.0f;
//float y2 = -1.0f + ( float( screenHeight - vLight->scissorRect.y1 ) / screenHeight ) * 2.0f;
//float y1 = -1.0f + ( float( screenHeight - vLight->scissorRect.y2 ) / screenHeight ) * 2.0f;
double zmin = 1.0 - vLight->scissorRect.zmin; // reverse depth
double wmin = 1.0 / zmin;
MaskedOcclusionCulling::CullingResult result = tr.maskedOcclusionCulling->TestRect( x1, y1, x2, y2, wmin );
if( result != MaskedOcclusionCulling::VISIBLE )
{
tr.pc.c_mocCulledLights += 1;
return;
}
#endif
}
// RB end
// RB: calculate shadow LOD similar to Q3A .md3 LOD code
// -1 means no shadows

View file

@ -675,8 +675,13 @@ void R_AddSingleModel( viewEntity_t* vEntity )
// RB: added check wether GPU skinning is available at all
const bool gpuSkinned = ( tri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() );
idRenderMatrix cullSurfaceProject;
idRenderMatrix::InverseOffsetScaleForBounds( renderMatrix_identity, tri->bounds, cullSurfaceProject );
const bool viewInsideSurface = !idRenderMatrix::CullPointToMVP( cullSurfaceProject, localViewOrigin, false );
// RB: test surface visibility by drawing the triangles of the bounds
if( r_useMaskedOcclusionCulling.GetBool() )
if( r_useMaskedOcclusionCulling.GetBool() && !viewInsideSurface )
{
#if 1
if( !model->IsStaticWorldModel() && !renderEntity->weaponDepthHack && renderEntity->modelDepthHack == 0.0f )
@ -687,9 +692,6 @@ void R_AddSingleModel( viewEntity_t* vEntity )
tr.pc.c_mocIndexes += 36;
tr.pc.c_mocVerts += 8;
idRenderMatrix modelRenderMatrix;
idRenderMatrix::CreateFromOriginAxis( renderEntity->origin, renderEntity->axis, modelRenderMatrix );
const float size = 16.0f;
idBounds debugBounds( idVec3( -size ), idVec3( size ) );
//debugBounds = vEntity->entityDef->localReferenceBounds;
@ -705,6 +707,9 @@ void R_AddSingleModel( viewEntity_t* vEntity )
debugBounds = tri->bounds;
}
idRenderMatrix modelRenderMatrix;
idRenderMatrix::CreateFromOriginAxis( renderEntity->origin, renderEntity->axis, modelRenderMatrix );
idRenderMatrix inverseBaseModelProject;
idRenderMatrix::OffsetScaleForBounds( modelRenderMatrix, debugBounds, inverseBaseModelProject );
@ -714,7 +719,8 @@ void R_AddSingleModel( viewEntity_t* vEntity )
tr.pc.c_mocTests += 1;
bool maskVisible = false;
idVec4* verts = tr.maskedZeroOneCubeVerts;
// NOTE: unit cube instead of zeroToOne cube
idVec4* verts = tr.maskedUnitCubeVerts;
unsigned int* indexes = tr.maskedZeroOneCubeIndexes;
for( int i = 0, face = 0; i < 36; i += 3, face++ )
{
@ -737,7 +743,7 @@ void R_AddSingleModel( viewEntity_t* vEntity )
if( !maskVisible )
{
tr.pc.c_mocCulls += 1;
tr.pc.c_mocCulledSurfaces += 1;
surfaceDirectlyVisible = false;
}
}

View file

@ -140,105 +140,8 @@ void R_RenderSingleModel( viewEntity_t* vEntity )
const bool addInteractions = modelIsVisible && ( !viewDef->isXraySubview || entityDef->parms.xrayIndex == 2 );
const int entityIndex = entityDef->index;
extern idCVar r_lodMaterialDistance;
//---------------------------
// Find which of the visible lights contact this entity
//
// If the entity doesn't accept light or cast shadows from any surface,
// this can be skipped.
//
// OPTIMIZE: world areas can assume all referenced lights are used
//---------------------------
int numContactedLights = 0;
static const int MAX_CONTACTED_LIGHTS = 128;
viewLight_t* contactedLights[MAX_CONTACTED_LIGHTS];
idInteraction* staticInteractions[MAX_CONTACTED_LIGHTS];
if( renderEntity->hModel == NULL ||
renderEntity->hModel->ModelHasInteractingSurfaces() ||
renderEntity->hModel->ModelHasShadowCastingSurfaces() )
{
SCOPED_PROFILE_EVENT( "Find lights" );
for( viewLight_t* vLight = viewDef->viewLights; vLight != NULL; vLight = vLight->next )
{
if( vLight->scissorRect.IsEmpty() )
{
continue;
}
if( vLight->entityInteractionState != NULL )
{
// new code path, everything was done in AddLight
if( vLight->entityInteractionState[entityIndex] == viewLight_t::INTERACTION_YES )
{
contactedLights[numContactedLights] = vLight;
staticInteractions[numContactedLights] = world->interactionTable[vLight->lightDef->index * world->interactionTableWidth + entityIndex];
if( ++numContactedLights == MAX_CONTACTED_LIGHTS )
{
break;
}
}
continue;
}
const idRenderLightLocal* lightDef = vLight->lightDef;
if( !lightDef->globalLightBounds.IntersectsBounds( entityDef->globalReferenceBounds ) )
{
continue;
}
if( R_CullModelBoundsToLight( lightDef, entityDef->localReferenceBounds, entityDef->modelRenderMatrix ) )
{
continue;
}
if( !modelIsVisible )
{
// some lights have their center of projection outside the world
if( lightDef->areaNum != -1 )
{
// if no part of the model is in an area that is connected to
// the light center (it is behind a solid, closed door), we can ignore it
bool areasConnected = false;
for( areaReference_t* ref = entityDef->entityRefs; ref != NULL; ref = ref->ownerNext )
{
if( world->AreasAreConnected( lightDef->areaNum, ref->area->areaNum, PS_BLOCK_VIEW ) )
{
areasConnected = true;
break;
}
}
if( areasConnected == false )
{
// can't possibly be seen or shadowed
continue;
}
}
// check more precisely for shadow visibility
idBounds shadowBounds;
R_ShadowBounds( entityDef->globalReferenceBounds, lightDef->globalLightBounds, lightDef->globalLightOrigin, shadowBounds );
// this doesn't say that the shadow can't effect anything, only that it can't
// effect anything in the view
if( idRenderMatrix::CullBoundsToMVP( viewDef->worldSpace.mvp, shadowBounds ) )
{
continue;
}
}
contactedLights[numContactedLights] = vLight;
staticInteractions[numContactedLights] = world->interactionTable[vLight->lightDef->index * world->interactionTableWidth + entityIndex];
if( ++numContactedLights == MAX_CONTACTED_LIGHTS )
{
break;
}
}
}
// if we aren't visible and none of the shadows stretch into the view,
// we don't need to do anything else
if( !modelIsVisible && numContactedLights == 0 )
// if we aren't visible we don't need to do anything else
if( !modelIsVisible )
{
return;
}
@ -281,6 +184,8 @@ void R_RenderSingleModel( viewEntity_t* vEntity )
idVec3 localViewOrigin;
R_GlobalPointToLocal( vEntity->modelMatrix, viewDef->renderView.vieworg, localViewOrigin );
extern idCVar r_lodMaterialDistance;
//---------------------------
// add all the model surfaces
//---------------------------
@ -422,7 +327,8 @@ void R_RenderSingleModel( viewEntity_t* vEntity )
{
// render to masked occlusion buffer
if( !gpuSkinned ) //model->IsStaticWorldModel() )
if( !gpuSkinned )
//if( model->IsStaticWorldModel() )
{
// super simple bruteforce
idVec4 triVerts[3];
@ -484,7 +390,8 @@ void R_RenderSingleModel( viewEntity_t* vEntity )
idRenderMatrix::Multiply( viewDef->worldSpace.unjitteredMVP, inverseBaseModelProject, invProjectMVPMatrix );
#if 1
idVec4* verts = tr.maskedZeroOneCubeVerts;
// NOTE: unit cube instead of zeroToOne cube
idVec4* verts = tr.maskedUnitCubeVerts;
unsigned int* indexes = tr.maskedZeroOneCubeIndexes;
for( int i = 0, face = 0; i < 36; i += 3, face++ )
{
@ -671,7 +578,22 @@ CONSOLE_COMMAND( maskShot, "Dumping masked occlusion culling buffer", NULL )
float* perPixelZBuffer = new float[width * height];
tr.maskedOcclusionCulling->ComputePixelDepthBuffer( perPixelZBuffer, false );
// Tonemap the image
halfFloat_t* halfImage = new halfFloat_t[width * height * 3];
for( int i = 0; i < ( width * height ); i++ )
{
float depth = perPixelZBuffer[i];
halfFloat_t f16Depth = F32toF16( depth );
halfImage[ i * 3 + 0 ] = f16Depth;
halfImage[ i * 3 + 1 ] = f16Depth;
halfImage[ i * 3 + 2 ] = f16Depth;
}
// write raw values
R_WriteEXR( "screenshots/soft_occlusion_buffer.exr", halfImage, 3, width, height, "fs_basepath" );
// tonemap the image
unsigned char* image = new unsigned char[width * height * 3];
TonemapDepth( perPixelZBuffer, image, width, height );