First step at generating multiple env probes

This commit is contained in:
Robert Beckebans 2020-05-16 17:40:30 +02:00
parent 71d768cceb
commit 804c16d0a4
11 changed files with 482 additions and 14 deletions

View file

@ -47,12 +47,12 @@ struct PS_OUT
};
// *INDENT-ON*
#define USE_CHROMATIC_ABERRATION 0
#define USE_CHROMATIC_ABERRATION 1
#define Chromatic_Amount 0.075
#define USE_TECHNICOLOR 0 // [0 or 1]
#define Technicolor_Amount 0.5 // [0.00 to 1.00]
#define Technicolor_Amount 1.0 // [0.00 to 1.00]
#define Technicolor_Power 4.0 // [0.00 to 8.00]
#define Technicolor_RedNegativeAmount 0.88 // [0.00 to 1.00]
#define Technicolor_GreenNegativeAmount 0.88 // [0.00 to 1.00]
@ -62,9 +62,9 @@ struct PS_OUT
#define Vibrance 0.5 // [-1.00 to 1.00]
#define Vibrance_RGB_Balance float3( 1.0, 1.0, 1.0 )
#define USE_CAS 1
#define USE_CAS 0
#define USE_DITHERING 0
#define USE_DITHERING 1
#define Dithering_QuantizationSteps 8.0 // 8.0 = 2 ^ 3 quantization bits
#define Dithering_NoiseBoost 1.0
#define Dithering_Wide 1.0
@ -604,7 +604,7 @@ void main( PS_IN fragment, out PS_OUT result )
#endif
#if USE_CHROMATIC_ABERRATION
ChromaticAberrationPass2( color );
ChromaticAberrationPass( color );
#endif
#if USE_TECHNICOLOR

View file

@ -1931,6 +1931,7 @@ void idRenderBackend::StereoRenderExecuteBackEndCommands( const emptyCommand_t*
{
case RC_NOP:
break;
case RC_DRAW_VIEW_GUI:
case RC_DRAW_VIEW_3D:
{
@ -1950,12 +1951,15 @@ void idRenderBackend::StereoRenderExecuteBackEndCommands( const emptyCommand_t*
}
}
break;
case RC_SET_BUFFER:
SetBuffer( cmds );
break;
case RC_COPY_RENDER:
CopyRender( cmds );
break;
case RC_POST_PROCESS:
{
postProcessCommand_t* cmd = ( postProcessCommand_t* )cmds;

View file

@ -4398,6 +4398,7 @@ void idRenderBackend::FogAllLights()
{
return;
}
renderLog.OpenMainBlock( MRB_FOG_ALL_LIGHTS );
renderLog.OpenBlock( "Render_FogAllLights", colorBlue );

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) 2012-2016 Robert Beckebans
Copyright (C) 2012-2020 Robert Beckebans
Copyright (C) 2014-2016 Kot in Action Creative Artel
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
@ -98,6 +98,7 @@ SURFACES
class idRenderWorldLocal;
struct viewEntity_t;
struct viewLight_t;
struct viewEnvprobe_t;
// drawSurf_t structures command the back end to render surfaces
// a given srfTriangles_t may be used with multiple viewEntity_t,
@ -269,9 +270,20 @@ public:
bool archived; // for demo writing
// derived information
idPlane lightProject[4]; // old style light projection where Z and W are flipped and projected lights lightProject[3] is divided by ( zNear + zFar )
idRenderMatrix baseLightProject; // global xyz1 to projected light strq
idRenderMatrix inverseBaseLightProject;// transforms the zero-to-one cube to exactly cover the light in world space
areaReference_t* references; // each area the light is present in will have a lightRef
//idInteraction* firstInteraction; // doubly linked list
//idInteraction* lastInteraction;
idImage* irradianceImage; // cubemap image used for diffuse IBL by backend
idImage* radianceImage; // cubemap image used for specular IBL by backend
// temporary helpers
int viewCount; // if == tr.viewCount, the envprobe is on the viewDef->viewEnvprobes list
viewEnvprobe_t* viewEnvprobe;
};
// RB end
@ -441,6 +453,34 @@ struct viewEntity_t
dynamicShadowVolumeParms_t* dynamicShadowVolumes;
};
// RB: viewEnvprobes are allocated on the frame temporary stack memory
// a viewEnvprobe contains everything that the back end needs out of an RenderEnvprobeLocal,
// which the front end may be modifying simultaniously if running in SMP mode.
// this structure will be especially helpful when we switch RBDOOM-3-BFG to forward cluster shading
// because then we can evaluate all viewEnvprobes properly in each pixel shader along with all other lighting information
struct viewEnvprobe_t
{
viewEnvprobe_t* next;
// back end should NOT reference the lightDef, because it can change when running SMP
RenderEnvprobeLocal* envprobeDef;
// for scissor clipping, local inside renderView viewport
// scissorRect.Empty() is true if the viewEntity_t was never actually
// seen through any portals
idScreenRect scissorRect;
// R_AddSingleEnvprobe() determined that the light isn't actually needed
bool removeFromList;
idVec3 globalOrigin; // global envprobe origin used by backend
idRenderMatrix inverseBaseLightProject; // the matrix for deforming the 'zeroOneCubeModel' to exactly cover the light volume in world space
idImage* irradianceImage; // cubemap image used for diffuse IBL by backend
idImage* radianceImage; // cubemap image used for specular IBL by backend
};
// RB end
const int MAX_CLIP_PLANES = 1; // we may expand this to six for some subview issues
@ -529,7 +569,7 @@ struct viewDef_t
int numDrawSurfs; // it is allocated in frame temporary memory
int maxDrawSurfs; // may be resized
viewLight_t* viewLights; // chain of all viewLights effecting view
viewLight_t* viewLights; // chain of all viewLights effecting view
viewEntity_t* viewEntitys; // chain of all viewEntities effecting view, including off screen ones casting shadows
// we use viewEntities as a check to see if a given view consists solely
// of 2D rendering, which we can optimize in certain ways. A 2D view will
@ -547,6 +587,15 @@ struct viewDef_t
// crossing a closed door. This is used to avoid drawing interactions
// when the light is behind a closed door.
bool* connectedAreas;
// RB: collect environment probes like lights
viewEnvprobe_t* viewEnvprobes;
// RB: nearest probe for now
idRenderMatrix inverseBaseEnvProbeProject; // the matrix for deforming the 'zeroOneCubeModel' to exactly cover the environent probe volume in world space
idImage* irradianceImage; // cubemap image used for diffuse IBL by backend
idImage* radianceImage; // cubemap image used for specular IBL by backend
// RB end
};
@ -1026,6 +1075,7 @@ extern idCVar r_testGammaBias; // draw a grid pattern to test gamma levels
extern idCVar r_singleLight; // suppress all but one light
extern idCVar r_singleEntity; // suppress all but one entity
extern idCVar r_singleEnvprobe; // suppress all but one envprobe
extern idCVar r_singleArea; // only draw the portal area the view is actually in
extern idCVar r_singleSurface; // suppress all but one surface on each entity
extern idCVar r_shadowPolygonOffset; // bias value added to depth test for stencil shadow drawing

View file

@ -941,7 +941,6 @@ In split screen mode the rendering size is also smaller.
*/
void idRenderSystemLocal::PerformResolutionScaling( int& newWidth, int& newHeight )
{
float xScale = 1.0f;
float yScale = 1.0f;
resolutionScale.GetCurrentResolutionScale( xScale, yScale );
@ -1057,7 +1056,7 @@ void idRenderSystemLocal::CaptureRenderToImage( const char* imageName, bool clea
common->Printf( "write DC_CAPTURE_RENDER: %s\n", imageName );
}
}
idImage* image = globalImages->GetImage( imageName );
idImage* image = globalImages->GetImage( imageName );
if( image == NULL )
{
image = globalImages->AllocImage( imageName );
@ -1093,6 +1092,7 @@ void idRenderSystemLocal::CaptureRenderToFile( const char* fileName, bool fixAlp
guiModel->EmitFullScreen();
guiModel->Clear();
RenderCommandBuffers( frameData->cmdHead );
#if !defined(USE_VULKAN)

View file

@ -175,6 +175,7 @@ idCVar r_screenFraction( "r_screenFraction", "100", CVAR_RENDERER | CVAR_INTEGER
idCVar r_usePortals( "r_usePortals", "1", CVAR_RENDERER | CVAR_BOOL, " 1 = use portals to perform area culling, otherwise draw everything" );
idCVar r_singleLight( "r_singleLight", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one light" );
idCVar r_singleEntity( "r_singleEntity", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one entity" );
idCVar r_singleEnvprobe( "r_singleEnvprobe", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one environment probe" );
idCVar r_singleSurface( "r_singleSurface", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one surface on each entity" );
idCVar r_singleArea( "r_singleArea", "0", CVAR_RENDERER | CVAR_BOOL, "only draw the portal area the view is actually in" );
idCVar r_orderIndexes( "r_orderIndexes", "1", CVAR_RENDERER | CVAR_BOOL, "perform index reorganization to optimize vertex use" );
@ -1210,7 +1211,6 @@ void R_EnvShot_f( const idCmdArgs& args )
for( i = 0 ; i < 6 ; i++ )
{
ref = primary.renderView;
extension = envDirection[ i ];
@ -1219,7 +1219,7 @@ void R_EnvShot_f( const idCmdArgs& args )
ref.viewaxis = axis[i];
fullname.Format( "env/%s%s", baseName, extension );
tr.TakeScreenshot( size, size, fullname, blends, &ref, TGA );
tr.TakeScreenshot( size, size, fullname, blends, &ref, PNG );
}
// restore the original resolution, axis and fov
@ -1851,7 +1851,6 @@ to skybox textures ( forward, back, left, right, up, down)
*/
void R_TransformEnvToSkybox_f( const idCmdArgs& args )
{
if( args.Argc() != 2 )
{
common->Printf( "USAGE: envToSky <basename>\n" );

View file

@ -1947,7 +1947,7 @@ void idRenderWorldLocal::GenerateAllInteractions()
int size = interactionTableWidth * interactionTableHeight * sizeof( *interactionTable );
interactionTable = ( idInteraction** )R_ClearedStaticAlloc( size );
// itterate through all lights
// iterate through all lights
int count = 0;
for( int i = 0; i < this->lightDefs.Num(); i++ )
{

View file

@ -0,0 +1,315 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
Copyright (C) 2020 Robert Beckebans
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "precompiled.h"
#include "RenderCommon.h"
/*
=============
R_SetEnvprobeDefViewEnvprobe
If the envprobeDef is not already on the viewEnvprobe list, create
a viewEnvprobe and add it to the list with an empty scissor rect.
=============
*/
viewEnvprobe_t* R_SetEnvprobeDefViewEnvprobe( RenderEnvprobeLocal* probe )
{
if( probe->viewCount == tr.viewCount )
{
// already set up for this frame
return probe->viewEnvprobe;
}
probe->viewCount = tr.viewCount;
// add to the view light chain
viewEnvprobe_t* vProbe = ( viewEnvprobe_t* )R_ClearedFrameAlloc( sizeof( *vProbe ), FRAME_ALLOC_VIEW_LIGHT );
vProbe->envprobeDef = probe;
// the scissorRect will be expanded as the envprobe bounds is accepted into visible portal chains
// and the scissor will be reduced in R_AddSingleEnvprobe based on the screen space projection
vProbe->scissorRect.Clear();
// link the view light
vProbe->next = tr.viewDef->viewEnvprobes;
tr.viewDef->viewEnvprobes = vProbe;
probe->viewEnvprobe = vProbe;
return vProbe;
}
/*
================
CullEnvprobeByPortals
Return true if the light frustum does not intersect the current portal chain.
================
*/
bool idRenderWorldLocal::CullEnvprobeByPortals( const RenderEnvprobeLocal* probe, const portalStack_t* ps )
{
if( r_useLightPortalCulling.GetInteger() == 1 )
{
ALIGNTYPE16 frustumCorners_t corners;
idRenderMatrix::GetFrustumCorners( corners, probe->inverseBaseLightProject, bounds_zeroOneCube );
for( int i = 0; i < ps->numPortalPlanes; i++ )
{
if( idRenderMatrix::CullFrustumCornersToPlane( corners, ps->portalPlanes[i] ) == FRUSTUM_CULL_FRONT )
{
return true;
}
}
}
else if( r_useLightPortalCulling.GetInteger() >= 2 )
{
idPlane frustumPlanes[6];
idRenderMatrix::GetFrustumPlanes( frustumPlanes, probe->baseLightProject, true, true );
// exact clip of light faces against all planes
for( int i = 0; i < 6; i++ )
{
// the light frustum planes face inward, so the planes that have the
// view origin on the positive side will be the "back" faces of the light,
// which must have some fragment inside the the portal stack planes to be visible
if( frustumPlanes[i].Distance( tr.viewDef->renderView.vieworg ) <= 0.0f )
{
continue;
}
// calculate a winding for this frustum side
idFixedWinding w;
w.BaseForPlane( frustumPlanes[i] );
for( int j = 0; j < 6; j++ )
{
if( j == i )
{
continue;
}
if( !w.ClipInPlace( frustumPlanes[j], ON_EPSILON ) )
{
break;
}
}
if( w.GetNumPoints() <= 2 )
{
continue;
}
assert( ps->numPortalPlanes <= MAX_PORTAL_PLANES );
assert( w.GetNumPoints() + ps->numPortalPlanes < MAX_POINTS_ON_WINDING );
// now clip the winding against each of the portalStack planes
// skip the last plane which is the last portal itself
for( int j = 0; j < ps->numPortalPlanes - 1; j++ )
{
if( !w.ClipInPlace( -ps->portalPlanes[j], ON_EPSILON ) )
{
break;
}
}
if( w.GetNumPoints() > 2 )
{
// part of the winding is visible through the portalStack,
// so the light is not culled
return false;
}
}
// nothing was visible
return true;
}
return false;
}
/*
===================
AddAreaViewEnvprobes
This is the only point where lights get added to the viewLights list.
Any lights that are visible through the current portalStack will have their scissor rect updated.
===================
*/
void idRenderWorldLocal::AddAreaViewEnvprobes( int areaNum, const portalStack_t* ps )
{
portalArea_t* area = &portalAreas[ areaNum ];
for( areaReference_t* lref = area->envprobeRefs.areaNext; lref != &area->envprobeRefs; lref = lref->areaNext )
{
RenderEnvprobeLocal* probe = lref->envprobe;
// debug tool to allow viewing of only one light at a time
if( r_singleEnvprobe.GetInteger() >= 0 && r_singleEnvprobe.GetInteger() != probe->index )
{
continue;
}
// check for being closed off behind a door
// a light that doesn't cast shadows will still light even if it is behind a door
if( r_useLightAreaCulling.GetBool() //&& !envprobe->LightCastsShadows()
&& probe->areaNum != -1 && !tr.viewDef->connectedAreas[ probe->areaNum ] )
{
continue;
}
// cull frustum
if( CullEnvprobeByPortals( probe, ps ) )
{
// we are culled out through this portal chain, but it might
// still be visible through others
continue;
}
viewEnvprobe_t* vProbe = R_SetEnvprobeDefViewEnvprobe( probe );
// expand the scissor rect
vProbe->scissorRect.Union( ps->rect );
}
}
CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", idCmdSystem::ArgCompletion_MapName )
{
idStr fullname;
idStr baseName;
idMat3 axis[6], oldAxis;
idVec3 oldPosition;
renderView_t ref;
viewDef_t primary;
int blends;
const char* extension;
int size;
int res_w, res_h, old_fov_x, old_fov_y;
static const char* envDirection[6] = { "_px", "_nx", "_py", "_ny", "_pz", "_nz" };
res_w = renderSystem->GetWidth();
res_h = renderSystem->GetHeight();
baseName = tr.primaryWorld->mapName;
baseName.StripFileExtension();
size = 256;
blends = 1;
if( !tr.primaryView )
{
common->Printf( "No primary view.\n" );
return;
}
primary = *tr.primaryView;
memset( &axis, 0, sizeof( axis ) );
// +X
axis[0][0][0] = 1;
axis[0][1][2] = 1;
axis[0][2][1] = 1;
// -X
axis[1][0][0] = -1;
axis[1][1][2] = -1;
axis[1][2][1] = 1;
// +Y
axis[2][0][1] = 1;
axis[2][1][0] = -1;
axis[2][2][2] = -1;
// -Y
axis[3][0][1] = -1;
axis[3][1][0] = -1;
axis[3][2][2] = 1;
// +Z
axis[4][0][2] = 1;
axis[4][1][0] = -1;
axis[4][2][1] = 1;
// -Z
axis[5][0][2] = -1;
axis[5][1][0] = 1;
axis[5][2][1] = 1;
// let's get the game window to a "size" resolution
if( ( res_w != size ) || ( res_h != size ) )
{
cvarSystem->SetCVarInteger( "r_windowWidth", size );
cvarSystem->SetCVarInteger( "r_windowHeight", size );
R_SetNewMode( false ); // the same as "vid_restart"
} // FIXME that's a hack!!
// so we return to that axis and fov after the fact.
oldPosition = primary.renderView.vieworg;
oldAxis = primary.renderView.viewaxis;
old_fov_x = primary.renderView.fov_x;
old_fov_y = primary.renderView.fov_y;
for( int i = 0; i < tr.primaryWorld->envprobeDefs.Num(); i++ )
{
RenderEnvprobeLocal* def = tr.primaryWorld->envprobeDefs[i];
if( def == NULL )
{
continue;
}
for( int j = 0 ; j < 6 ; j++ )
{
ref = primary.renderView;
extension = envDirection[ j ];
ref.fov_x = ref.fov_y = 90;
ref.vieworg = def->parms.origin;
ref.viewaxis = axis[j];
fullname.Format( "env/%s_envprobe%i%s", baseName.c_str(), i, extension );
tr.TakeScreenshot( size, size, fullname, blends, &ref, PNG );
//tr.CaptureRenderToFile( fullname, false );
}
}
// restore the original resolution, axis and fov
ref.vieworg = oldPosition;
ref.viewaxis = oldAxis;
ref.fov_x = old_fov_x;
ref.fov_y = old_fov_y;
cvarSystem->SetCVarInteger( "r_windowWidth", res_w );
cvarSystem->SetCVarInteger( "r_windowHeight", res_h );
R_SetNewMode( false ); // the same as "vid_restart"
common->Printf( "Wrote a env set with the name %s\n", baseName );
}

View file

@ -234,10 +234,31 @@ public:
//--------------------------
// RenderWorld_portals.cpp
// if we hit this many planes, we will just stop cropping the
// view down, which is still correct, just conservative
static const int MAX_PORTAL_PLANES = 20;
struct portalStack_t
{
const portal_t* p;
const portalStack_t* next;
// positive side is outside the visible frustum
int numPortalPlanes;
idPlane portalPlanes[MAX_PORTAL_PLANES + 1];
idScreenRect rect;
};
bool CullEntityByPortals( const idRenderEntityLocal* entity, const portalStack_t* ps );
void AddAreaViewEntities( int areaNum, const portalStack_t* ps );
bool CullLightByPortals( const idRenderLightLocal* light, const portalStack_t* ps );
void AddAreaViewLights( int areaNum, const portalStack_t* ps );
// RB begin
bool CullEnvprobeByPortals( const RenderEnvprobeLocal* probe, const portalStack_t* ps );
void AddAreaViewEnvprobes( int areaNum, const portalStack_t* ps );
// RB end
void AddAreaToView( int areaNum, const portalStack_t* ps );
idScreenRect ScreenRectFromWinding( const idWinding* w, const viewEntity_t* space );
bool PortalIsFoggedOut( const portal_t* p );

View file

@ -414,6 +414,7 @@ void idRenderWorldLocal::AddAreaToView( int areaNum, const portalStack_t* ps )
// add the models and lights, using more precise culling to the planes
AddAreaViewEntities( areaNum, ps );
AddAreaViewLights( areaNum, ps );
AddAreaViewEnvprobes( areaNum, ps ); // RB
}
/*
@ -759,6 +760,7 @@ void idRenderWorldLocal::FindViewLightsAndEntities()
// clear the visible lightDef and entityDef lists
tr.viewDef->viewLights = NULL;
tr.viewDef->viewEntitys = NULL;
tr.viewDef->viewEnvprobes = NULL; // RB
// all areas are initially not visible, but each portal
// chain that leads to them will expand the visible rectangle

View file

@ -321,7 +321,6 @@ static void R_RemoteRender( const drawSurf_t* surf, textureStage_t* stage )
parms->isSubview = true;
parms->isMirror = false;
tr.CropRenderSize( stageWidth, stageHeight );
tr.GetCroppedViewport( &parms->viewport );
@ -512,9 +511,11 @@ bool R_GenerateSurfaceSubview( const drawSurf_t* drawSurf )
case DI_REMOTE_RENDER:
R_RemoteRender( drawSurf, const_cast<textureStage_t*>( &stage->texture ) );
break;
case DI_MIRROR_RENDER:
R_MirrorRender( drawSurf, const_cast<textureStage_t*>( &stage->texture ), scissor );
break;
case DI_XRAY_RENDER:
R_XrayRender( drawSurf, const_cast<textureStage_t*>( &stage->texture ), scissor );
break;
@ -543,6 +544,61 @@ bool R_GenerateSurfaceSubview( const drawSurf_t* drawSurf )
return true;
}
/*
=================
R_EnvironmentProbeRender
=================
*/
static void R_EnvironmentProbeRender( const RenderEnvprobeLocal* )
{
#if 0
// remote views can be reused in a single frame
//if( stage->dynamicFrameCount == tr.frameCount )
//{
// return;
//}
// issue a new view command
// copy the viewport size from the original
viewDef_t* parms = ( viewDef_t* )R_FrameAlloc( sizeof( *parms ) );
*parms = *tr.viewDef;
parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons
parms->isSubview = true;
//parms->isXraySubview = true;
if( parms == NULL )
{
return;
}
int stageWidth = 256;
int stageHeight = 256;
tr.CropRenderSize( stageWidth, stageHeight );
tr.GetCroppedViewport( &parms->viewport );
parms->scissor.x1 = 0;
parms->scissor.y1 = 0;
parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1;
parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1;
parms->superView = tr.viewDef;
//parms->subviewSurface = surf;
// generate render commands for it
R_RenderView( parms );
// copy this rendering to the image
//stage->dynamicFrameCount = tr.frameCount;
//stage->image = globalImages->scratchImage2;
tr.CaptureRenderToImage( globalImages->scratchImage2->GetName(), true );
tr.UnCrop();
#endif
}
/*
================
R_GenerateSubViews
@ -583,5 +639,25 @@ bool R_GenerateSubViews( const drawSurf_t* const drawSurfs[], const int numDrawS
}
}
// RB: generate subviews for environment probes that need an update
if( tr.viewDef->areaNum != -1 )
{
// go through each visible probe
int numViewProbes = 0;
for( viewEnvprobe_t* vProbe = tr.viewDef->viewEnvprobes; vProbe != NULL; vProbe = vProbe->next )
{
numViewProbes++;
if( !vProbe->envprobeDef->irradianceImage )
{
R_EnvironmentProbeRender( vProbe->envprobeDef );
subviews = true;
}
}
}
// RB end
return subviews;
}