Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/engine/renderer/glsl_source/lighttile_fp.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ void main() {
or use compute shaders with atomics so we can have a variable amount of lights for each tile. */
for( uint i = uint( u_lightLayer ); i < uint( u_numLights ); i += uint( NUM_LIGHT_LAYERS ) ) {
Light l = GetLight( i );

vec3 center = ( u_ModelMatrix * vec4( l.center, 1.0 ) ).xyz;
float radius = max( 2.0 * l.radius, 2.0 * 32.0 ); // Avoid artifacts with weak light sources

Expand Down
135 changes: 132 additions & 3 deletions src/engine/renderer/tr_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ static Cvar::Cvar<bool> r_clear( "r_clear", "Clear screen before painting over i
Cvar::Cvar<bool> r_drawSky( "r_drawSky", "Draw the sky (clear the sky if disabled)", Cvar::NONE, true );
static Cvar::Cvar<int> r_showEntityBounds(
"r_showEntityBounds", "show bboxes used for culling (1: wireframe; 2: translucent solid)", Cvar::CHEAT, 0);
static Cvar::Cvar<bool> r_showDynamicLights(
"r_showDynamicLights", "visualize dynamic lights with tetrahedrons", Cvar::CHEAT, false );

void GL_Bind( image_t *image )
{
Expand Down Expand Up @@ -1301,7 +1303,7 @@ static void RenderDepthTiles()
{
RB_PrepareForSamplingDepthMap();
}

TransitionMSAAToMain( GL_DEPTH_BUFFER_BIT );

// 1st step
Expand All @@ -1317,7 +1319,7 @@ static void RenderDepthTiles()

gl_depthtile1Shader->SetUniform_zFar( zParams );
gl_depthtile1Shader->SetUniform_DepthMapBindless(
GL_BindToTMU( 0, tr.currentDepthImage )
GL_BindToTMU( 0, tr.currentDepthImage )
);

matrix_t ortho;
Expand Down Expand Up @@ -1727,7 +1729,7 @@ void RB_CameraPostFX() {
gl_cameraEffectsShader->SetUniform_Tonemap( tonemap );

gl_cameraEffectsShader->SetUniform_CurrentMapBindless(
GL_BindToTMU( 0, tr.currentRenderImage[backEnd.currentMainFBO] )
GL_BindToTMU( 0, tr.currentRenderImage[backEnd.currentMainFBO] )
);

if ( r_FXAA.Get() && gl_fxaaShader )
Expand Down Expand Up @@ -2312,6 +2314,132 @@ static void RB_RenderDebugUtils()
Tess_End();
}

if ( r_showDynamicLights.Get() && backEnd.refdef.numLights > 0 )
{
gl_genericShader->SetVertexSkinning( false );
gl_genericShader->SetVertexAnimation( false );
gl_genericShader->SetTCGenEnvironment( false );
gl_genericShader->SetTCGenLightmap( false );
gl_genericShader->SetDepthFade( false );
gl_genericShader->SetDeform( 0 );
gl_genericShader->BindProgram();

GL_State( GLS_DEFAULT );
GL_Cull( cullType_t::CT_TWO_SIDED );

// set uniforms
gl_genericShader->SetUniform_AlphaTest( GLS_ATEST_NONE );
SetUniform_ColorModulateColorGen( gl_genericShader, colorGen_t::CGEN_VERTEX,
alphaGen_t::AGEN_VERTEX );
SetUniform_Color( gl_genericShader, Color::Black );
gl_genericShader->SetUniform_ColorMapBindless( GL_BindToTMU( 0, tr.whiteImage ) );
gl_genericShader->SetUniform_TextureMatrix( matrixIdentity );

// set up the transformation matrix
backEnd.orientation = backEnd.viewParms.world;
GL_LoadModelViewMatrix( backEnd.orientation.modelViewMatrix );
gl_genericShader->SetUniform_ModelViewProjectionMatrix(
glState.modelViewProjectionMatrix[ glState.stackIndex ] );

Tess_Begin( Tess_StageIteratorDebug, nullptr, true, -1, 0 );

const refLight_t *lights = backEnd.refdef.lights;
for ( int i = 0; i < backEnd.refdef.numLights; ++i )
{
const refLight_t &light = lights[ i ];
if ( light.rlType != refLightType_t::RL_PROJ )
{
continue;
}

vec3_t baseOrigin;
VectorCopy( light.origin, baseOrigin );

if ( light.radius <= 0.0f )
{
Log::Warn( "Light with index %d has no radius", i );
}

Color::Color color = Color::LtGrey;

vec3_t dir;
VectorCopy( light.projTarget, dir );
if ( VectorNormalize( dir ) == 0.0f )
{
VectorSet( dir, 0.0f, 0.0f, 1.0f );
}
// Surface normals point outward from the object. And a light is rendered when the product of its
// direction and the surface normal is positive. So the light direction vector has to point away from
// the source for it to work.
// Negate the direction so the skinny end of the tetrahedron points the same direction as the light.
VectorNegate( dir, dir );

vec3_t tip;
VectorMA( baseOrigin, light.radius, dir, tip );

vec3_t tmp;
vec3_t tmp2;
vec3_t tmp3;
PerpendicularVector( tmp, dir );
VectorScale( tmp, light.radius * 0.2f, tmp2 );
VectorMA( tmp2, light.radius * 0.3f, dir, tmp2 );

vec4_t tetraVerts[ 4 ];
for ( int k = 0; k < 3; k++ )
{
RotatePointAroundVector( tmp3, dir, tmp2, k * 120.0f );
VectorAdd( tmp3, baseOrigin, tmp3 );
VectorCopy( tmp3, tetraVerts[ k ] );
tetraVerts[ k ][ 3 ] = 1.0f;
}

VectorCopy( baseOrigin, tetraVerts[ 3 ] );
tetraVerts[ 3 ][ 3 ] = 1.0f;
Tess_AddTetrahedron( tetraVerts, color );

VectorCopy( tip, tetraVerts[ 3 ] );
tetraVerts[ 3 ][ 3 ] = 1.0f;

Tess_AddTetrahedron( tetraVerts, color );
}

Tess_End();
GL_CheckErrors();

const uint32_t savedStateBits = glState.glStateBits;
GL_State( GLS_POLYMODE_LINE );

Tess_Begin( Tess_StageIteratorDebug, nullptr, true, -1, 0 );

for ( int i = 0; i < backEnd.refdef.numLights; ++i )
{
const refLight_t &light = lights[ i ];
if ( light.rlType != refLightType_t::RL_OMNI )
{
continue;
}

vec3_t baseOrigin;
VectorCopy( light.origin, baseOrigin );

if ( light.radius <= 0.0f )
{
Log::Warn( "Light with index %d has no radius", i );
}

vec3_t cubeMins;
vec3_t cubeMaxs;
const float halfSize = light.radius * 0.5f;
VectorSet( cubeMins, -halfSize, -halfSize, -halfSize );
VectorSet( cubeMaxs, halfSize, halfSize, halfSize );
Tess_AddCube( baseOrigin, cubeMins, cubeMaxs, Color::MdGrey );
}

Tess_End();
GL_CheckErrors();
GL_State( savedStateBits );
}

if ( r_showBspNodes->integer )
{
if ( ( backEnd.refdef.rdflags & ( RDF_NOWORLDMODEL ) ) || !tr.world )
Expand Down Expand Up @@ -3486,6 +3614,7 @@ const RenderCommand *SetupLightsCommand::ExecuteSelf() const
default:
break;
}
VectorNormalize( buffer[i].direction );
}

glUnmapBuffer( bufferTarget );
Expand Down
100 changes: 99 additions & 1 deletion src/engine/renderer/tr_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ static void RE_RenderCubeProbeFace( const refdef_t* originalRefdef ) {
Log::Warn( "Cube probe face out of range! (%i/%i)", probeID, tr.cubeProbes.size() );
return;
}

refdef_t refdef{};
const int faceID = globalID % 6;

Expand Down Expand Up @@ -487,6 +487,95 @@ static void RE_RenderCubeProbeFace( const refdef_t* originalRefdef ) {

}

// Debug spot light (projected) injection
static Cvar::Cvar<bool> r_debugProjLight( "r_debugProjLight", "inject a directional sun light each frame", Cvar::CHEAT, false );
static Cvar::Range<Cvar::Cvar<float>> r_debugProjLightYaw( "r_debugProjLightYaw", "debug projected yaw in degrees", Cvar::NONE, 45.0f, -360.0f, 360.0f );
static Cvar::Range<Cvar::Cvar<float>> r_debugProjLightPitch( "r_debugProjLightPitch", "debug projected pitch in degrees", Cvar::NONE, -60.0f, -89.0f, 89.0f );
static Cvar::Cvar<float> r_debugProjLightRadius( "r_debugProjLightRadius", "debug projected radius (size)", Cvar::NONE, 100.0f );
static Cvar::Cvar<float> r_debugProjLightIntensity( "r_debugProjLightIntensity", "debug projected intensity", Cvar::NONE, 1.0f );
static Cvar::Range<Cvar::Cvar<float>> r_debugProjLightR( "r_debugProjLightR", "debug projected color R", Cvar::NONE, 1.0f, 0.0f, 1.0f );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The light intensity shouldn't be capped at 1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added back intensity. I think it's weird to make RGB more than 1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sRGB is in [0,1] but RGB is [0,…]

The lightmap themselves get multiplied by overbright and then are already in a range higher than [0, 1], also our whole pipeline uses HDR framebuffers so is ready for that.

static Cvar::Range<Cvar::Cvar<float>> r_debugProjLightG( "r_debugProjLightG", "debug projected color G", Cvar::NONE, 1.0f, 0.0f, 1.0f );
static Cvar::Range<Cvar::Cvar<float>> r_debugProjLightB( "r_debugProjLightB", "debug projected color B", Cvar::NONE, 1.0f, 0.0f, 1.0f );
static Cvar::Cvar<float> r_debugProjLightOriginX( "r_debugProjLightOriginX", "debug projected origin X", Cvar::NONE, 0.0f );
static Cvar::Cvar<float> r_debugProjLightOriginY( "r_debugProjLightOriginY", "debug projected origin Y", Cvar::NONE, 0.0f );
static Cvar::Cvar<float> r_debugProjLightOriginZ( "r_debugProjLightOriginZ", "debug projected origin Z", Cvar::NONE, 0.0f );
static Cvar::Cvar<float> r_debugProjLightAngle( "r_debugProjLightAngle", "debug projected angle",
Cvar::NONE, 60.0f );

static void AddDebugProjectedLight()
{
if ( r_numLights >= MAX_REF_LIGHTS )
{
return;
}
refLight_t *light = &backEndData[ tr.smpFrame ]->lights[ r_numLights++ ];
*light = {};
light->rlType = refLightType_t::RL_PROJ;
// Compute direction from yaw/pitch cvars (in degrees)
float yaw = DEG2RAD( r_debugProjLightYaw.Get() );
float pitch = DEG2RAD( r_debugProjLightPitch.Get() );
// Right-handed: X forward, Y left, Z up. Direction vector components:
light->projTarget[ 0 ] = cosf( pitch ) * cosf( yaw );
light->projTarget[ 1 ] = cosf( pitch ) * sinf( yaw );
light->projTarget[ 2 ] = sinf( pitch );

vec3_t dir;
VectorCopy( light->projTarget, dir );
VectorNormalize( dir );

PerpendicularVector( light->projUp, dir );

float upLen = VectorLength( light->projUp );
float tgtLen = VectorLength( light->projTarget );

VectorScale( light->projUp, tanf( DEG2RAD( r_debugProjLightAngle.Get() ) ) / upLen / tgtLen,
light->projUp );

// Set properties
float intensity = r_debugProjLightIntensity.Get();
light->color[ 0 ] = r_debugProjLightR.Get() * intensity;
light->color[ 1 ] = r_debugProjLightG.Get() * intensity;
light->color[ 2 ] = r_debugProjLightB.Get() * intensity;
light->radius = r_debugProjLightRadius.Get();
light->origin[ 0 ] = r_debugProjLightOriginX.Get();
light->origin[ 1 ] = r_debugProjLightOriginY.Get();
light->origin[ 2 ] = r_debugProjLightOriginZ.Get();
}

// Debug sun light (directional) injection
Cvar::Cvar<bool> r_debugSun( "r_debugSun", "inject a directional sun light each frame", Cvar::CHEAT, false );
Cvar::Range<Cvar::Cvar<float>> r_debugSunYaw( "r_debugSunYaw", "debug sun yaw in degrees", Cvar::NONE, 45.0f, -360.0f, 360.0f );
Cvar::Range<Cvar::Cvar<float>> r_debugSunPitch( "r_debugSunPitch", "debug sun pitch in degrees", Cvar::NONE, -60.0f, -89.0f, 89.0f );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems counterintuitive for the "sun" to default to illuminating things from below.

Copy link
Copy Markdown
Contributor Author

@DolceTriade DolceTriade Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking from the sun's perspective cuz the sun shines down, but if you want me to change it, I can change it.

Just let me know what you think makes sense.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tried it the ceiling was illuminated but the floor was not!

Cvar::Cvar<float> r_debugSunIntensity( "r_debugSunIntensity", "debug sun intensity", Cvar::NONE, 1.0f );
Cvar::Range<Cvar::Cvar<float>> r_debugSunR( "r_debugSunR", "debug sun color R", Cvar::NONE, 1.0f, 0.0f, 10.0f );
Cvar::Range<Cvar::Cvar<float>> r_debugSunG( "r_debugSunG", "debug sun color G", Cvar::NONE, 1.0f, 0.0f, 10.0f );
Cvar::Range<Cvar::Cvar<float>> r_debugSunB( "r_debugSunB", "debug sun color B", Cvar::NONE, 1.0f, 0.0f, 10.0f );

static void AddDebugSunLight()
{
if ( r_numLights >= MAX_REF_LIGHTS )
{
return;
}
refLight_t *sun = &backEndData[ tr.smpFrame ]->lights[ r_numLights++ ];
*sun = {};
sun->rlType = refLightType_t::RL_DIRECTIONAL;
// Compute direction from yaw/pitch cvars (in degrees)
float yaw = DEG2RAD( r_debugSunYaw.Get() );
float pitch = DEG2RAD( r_debugSunPitch.Get() );
// Right-handed: X forward, Y left, Z up. Direction vector components:
sun->projTarget[ 0 ] = cosf( pitch ) * cosf( yaw );
sun->projTarget[ 1 ] = cosf( pitch ) * sinf( yaw );
sun->projTarget[ 2 ] = sinf( pitch );
VectorNormalize( sun->projTarget );
float intensity = r_debugSunIntensity.Get();
sun->color[ 0 ] = r_debugSunR.Get() * intensity;
sun->color[ 1 ] = r_debugSunG.Get() * intensity;
sun->color[ 2 ] = r_debugSunB.Get() * intensity;
// Max radius to ensure it is always included.
sun->radius = std::numeric_limits<float>::max();
}

/*
@@@@@@@@@@@@@@@@@@@@@
RE_RenderScene
Expand Down Expand Up @@ -562,6 +651,15 @@ void RE_RenderScene( const refdef_t *fd )
}
}

if ( r_debugProjLight.Get() )
{
AddDebugProjectedLight();
}
if ( r_debugSun.Get() )
{
AddDebugSunLight();
}

// derived info
if ( r_forceRendererTime.Get() >= 0 ) {
tr.refdef.floatTime = float( double( r_forceRendererTime.Get() ) * 0.001 );
Expand Down
Loading