diff --git a/docs/Complete_list_of_command_line_parameters.htm b/docs/Complete_list_of_command_line_parameters.htm
index 775d2240..f88b166f 100644
--- a/docs/Complete_list_of_command_line_parameters.htm
+++ b/docs/Complete_list_of_command_line_parameters.htm
@@ -260,6 +260,7 @@ td.formatted_questions ol { margin-top: 0px; margin-bottom: 0px; }
-lomem: Low memory but slower lighting mode
-lowquality: Low quality floodlight (appears to currently break floodlight)
-minsamplesize N: Sets minimum lightmap resolution in luxels/qu
+ -multiSun N: Restrict sun and skylights to being emitted from surfaces carrying the shader they were defined in (as opposed to globally)
-nobouncestore: Do not store BSP, lightmap and shader files between bounces
-nocollapse: Do not collapse identical lightmaps
-nodeluxe, -nodeluxemap: Disable deluxemapping
diff --git a/docs/shaderManual/q3map-global-directives.html b/docs/shaderManual/q3map-global-directives.html
index f9970fc3..d71ae56a 100644
--- a/docs/shaderManual/q3map-global-directives.html
+++ b/docs/shaderManual/q3map-global-directives.html
@@ -401,6 +401,11 @@ textures/eerie/ironcrosslt2_10000
sample_colorDefault = 1: sample color of each individual light from skybox images, if they are present. 0: use shader color, set by q3map_lightRGB/q3map_lightImage/_up skybox image/qer_editorImage.
+
+ Design Notes:
+ Normally, all suns and skylights are emitted from all sky surfaces in a map. If you wish to restrict them to being emitted from surfaces carrying the exact shader that defined them, e.g. to have different sun/sky lighting in different areas of the map, compile your map with the -multiSun keyword in the light stage.
+
+
q3map_splotchFix
This is used on lightmapped model shaders if splotched lighting artifacts appear. Any shadows at the ambient/dark level will be flooded from neighbouring luxels. This gets rid of shadow acne, but a surface must be more or less uniformly lit or this looks ugly. Try using q3map_lightmapSampleOffset first before using this as a last resort.
@@ -428,6 +433,8 @@ textures/eerie/ironcrosslt2_10000
Sky shaders should probably still have a q3map_surfaceLight or preferred q3map_skylight value. The "sun" gives a strong directional light, but doesn't necessarily give the fill light needed to soften and illuminate shadows. Skies with clouds should probably have a weaker q3map_sun value and a higher q3map_surfaceLight or q3map_skylight value. Heavy clouds diffuse light and weaken shadows. The opposite is true of a cloudless or nearly cloudless sky. In such cases, the "sun" or "moon" will cast stronger shadows that have a greater degree of contrast. This is also why q3map_sunExt is preferred. It gives the designer greater control over shadow contrast with a penumbra effect.
Design Notes:
Not certain what color formula you want to use for the sun's light? Try this. Create a light entity. Use the Radiant editor's color selection tools to pick a color. The light's _color key's value will be the normalized RGB formula. Copy it from the value line in the editor (CTRL+c) and paste it into your shader.
+ Design Notes:
+ Normally, all suns and skylights are emitted from all sky surfaces in a map. If you wish to restrict them to being emitted from surfaces carrying the exact shader that defined them, e.g. to have different sun/sky lighting in different areas of the map, compile your map with the -multiSun keyword in the light stage.
q3map_surfaceLight value
diff --git a/tools/quake3/q3map2/help.cpp b/tools/quake3/q3map2/help.cpp
index dc045f2b..bee03e91 100644
--- a/tools/quake3/q3map2/help.cpp
+++ b/tools/quake3/q3map2/help.cpp
@@ -226,6 +226,7 @@ static void HelpLight()
{ "-lomem", "Low memory but slower lighting mode" },
{ "-lowquality", "Low quality floodlight (appears to currently break floodlight)" },
{ "-minsamplesize ", "Sets minimum lightmap resolution in luxels/qu" },
+ { "-multisun", "Restrict sun and skylights to being emitted from surfaces carrying the shader they were defined in (as opposed to globally)" },
{ "-nobouncestore", "Do not store BSP, lightmap and shader files between bounces" },
{ "-nocollapse", "Do not collapse identical lightmaps" },
{ "-nodeluxe, -nodeluxemap", "Disable deluxemapping" },
diff --git a/tools/quake3/q3map2/light.cpp b/tools/quake3/q3map2/light.cpp
index b24b8780..82c87788 100644
--- a/tools/quake3/q3map2/light.cpp
+++ b/tools/quake3/q3map2/light.cpp
@@ -100,6 +100,7 @@ static void CreateSunLight( sun_t& sun ){
light.falloffTolerance = falloffTolerance;
light.filterRadius = sun.filterRadius / sun.numSamples;
light.style = noStyles ? LS_NORMAL : sun.style;
+ light.environmentLightIndex = sun.environmentLightIndex;
/* set the light's position out to infinity */
light.origin = direction * ( MAX_WORLD_COORD * 8.0f ); /* MAX_WORLD_COORD * 2.0f */
@@ -234,6 +235,7 @@ static void CreateSkyLights( const skylight_t& skylight, const Vector3& color, f
sun.deviance = 0.0f;
sun.filterRadius = filterRadius;
sun.numSamples = 1;
+ sun.environmentLightIndex = skylight.environmentLightIndex;
sun.style = noStyles ? LS_NORMAL : style;
/* setup */
@@ -599,6 +601,16 @@ static void CreateSurfaceLights(){
if ( !si->suns.empty() && !nss ) {
auto* si_ = const_cast( si ); /* FIXME: hack! */
Sys_FPrintf( SYS_VRB, "Sun: %s\n", si->shader.c_str() );
+ if(multiSun){
+ if(si->environmentEmitterIndex == -1){
+ // this shader is actually emitting sun/skylight now, so give it an environment emitter index
+ si_->environmentEmitterIndex = nextEnvironmentLightIndex++;
+ }
+ // let the suns know
+ for( auto sunIt = si_->suns.begin(); sunIt != si_->suns.end(); sunIt++ ){
+ sunIt->environmentLightIndex = si_->environmentEmitterIndex;
+ }
+ }
std::ranges::for_each( si_->suns, CreateSunLight );
si_->suns.clear(); /* FIXME: hack! */
}
@@ -606,6 +618,17 @@ static void CreateSurfaceLights(){
/* sky light? */
if ( !si->skylights.empty() ) {
Sys_FPrintf( SYS_VRB, "Sky: %s\n", si->shader.c_str() );
+ if(multiSun){
+ shaderInfo_t* si_ = const_cast( si ); /* FIXME: hack! */
+ if(si->environmentEmitterIndex == -1){
+ // this shader is actually emitting sun/skylight now, so give it an environment emitter index
+ si_->environmentEmitterIndex = nextEnvironmentLightIndex++;
+ }
+ // let the skies know
+ for( auto skyIt = si_->skylights.begin(); skyIt != si_->skylights.end(); skyIt++ ){
+ skyIt->environmentLightIndex = si_->environmentEmitterIndex;
+ }
+ }
for( const skylight_t& skylight : si->skylights )
CreateSkyLights( skylight, si->color, si->lightFilterRadius, si->lightStyle, si->skyParmsImageBase );
const_cast( si )->skylights.clear(); /* FIXME: hack! */
@@ -1098,7 +1121,7 @@ int LightContributionToSample( trace_t *trace ){
/* trace */
TraceLine( trace );
trace->forceSubsampling *= add;
- if ( !( trace->compileFlags & C_SKY ) || trace->opaque ) {
+ if ( !( trace->compileFlags & C_SKY ) || trace->opaque || multiSun && light->environmentLightIndex != -1 && light->environmentLightIndex < MULTISUN_MAX && !bit_is_enabled(trace->skyEnvironmentLightIndices,light->environmentLightIndex) ) {
trace->color.set( 0 );
trace->directionContribution.set( 0 );
@@ -1394,7 +1417,7 @@ static bool LightContributionToPoint( trace_t *trace ){
if ( trace->testOcclusion && !trace->forceSunlight ) {
/* trace */
TraceLine( trace );
- if ( !( trace->compileFlags & C_SKY ) || trace->opaque ) {
+ if ( !( trace->compileFlags & C_SKY ) || trace->opaque || multiSun && light->environmentLightIndex != -1 && light->environmentLightIndex < MULTISUN_MAX && !bit_is_enabled(trace->skyEnvironmentLightIndices,light->environmentLightIndex) ) {
trace->color.set( 0 );
return false;
}
@@ -2496,6 +2519,10 @@ int LightMain( Args& args ){
Sys_Printf( "Approximating lightmaps within a byte tolerance of %d\n", approximateTolerance );
}
}
+ while ( args.takeArg( "-multisun" ) ) {
+ multiSun = true;
+ Sys_Printf( "Multi-sun support activated\n" );
+ }
while ( args.takeArg( "-deluxe", "-deluxemap" ) ) {
deluxemap = true;
Sys_Printf( "Generating deluxemaps for average light direction\n" );
diff --git a/tools/quake3/q3map2/light_trace.cpp b/tools/quake3/q3map2/light_trace.cpp
index 1ffa80df..ccdd3970 100644
--- a/tools/quake3/q3map2/light_trace.cpp
+++ b/tools/quake3/q3map2/light_trace.cpp
@@ -1351,6 +1351,9 @@ static bool TraceTriangle( traceInfo_t *ti, traceTriangle_t *tt, trace_t *trace
/* don't trace against sky */
if ( si->compileFlags & C_SKY ) {
+ if(multiSun && trace->light && trace->light->environmentLightIndex != -1 && si->environmentEmitterIndex != -1 && si->environmentEmitterIndex < MULTISUN_MAX){
+ bit_enable(trace->skyEnvironmentLightIndices,si->environmentEmitterIndex);
+ }
return false;
}
@@ -1597,6 +1600,9 @@ void TraceLine( trace_t *trace ){
trace->passSolid = false;
trace->opaque = false;
trace->compileFlags = 0;
+ if(multiSun){
+ memset(trace->skyEnvironmentLightIndices,0,sizeof(trace->skyEnvironmentLightIndices));
+ }
trace->numTestNodes = 0;
/* early outs */
diff --git a/tools/quake3/q3map2/q3map2.h b/tools/quake3/q3map2/q3map2.h
index 9593492c..9c9a1d13 100644
--- a/tools/quake3/q3map2/q3map2.h
+++ b/tools/quake3/q3map2/q3map2.h
@@ -155,6 +155,9 @@ enum class EBrushType
/* light */
+#define MULTISUN_MAX 2048 // TA: multi sun. this is how many separate suns can be drawn separately and only where they should
+#define MULTISUN_MAX_BYTES (MULTISUN_MAX/8) // it's a bitmask. 256 bytes in the end.
+
#define MAX_TRACE_TEST_NODES 256
#define DEFAULT_INHIBIT_RADIUS 1.5f
@@ -414,6 +417,7 @@ struct sun_t
Vector3 direction, color;
float photons, deviance, filterRadius;
int numSamples, style;
+ int environmentLightIndex = -1;
};
struct skylight_t
@@ -423,6 +427,7 @@ struct skylight_t
int horizon_min = 0;
int horizon_max = 90;
bool sample_color = true;
+ int environmentLightIndex = -1;
};
@@ -579,6 +584,7 @@ struct shaderInfo_t_data
std::vector skylights; /* ydnar */
std::vector suns; /* ydnar */
+ int environmentEmitterIndex = -1; /* TA: multisun: shaders that end up being used and actually emit sunlight get a unique emitter index */
Vector3 color{ 0 }; /* normalized color */
Color4f averageColor = { 0, 0, 0, 0 };
@@ -1083,6 +1089,8 @@ struct light_t
float falloffTolerance; /* ydnar: minimum attenuation threshold */
float filterRadius; /* ydnar: lightmap filter radius in world units, 0 == default */
+
+ int environmentLightIndex = -1; /* TA: For sun/skylights. Check if particular sky triangle emits a given light */
};
@@ -1106,9 +1114,12 @@ struct trace_t
float inhibitRadius; /* sphere in which occluding geometry is ignored */
/* per-light input */
- const light_t *light;
+ const light_t *light = nullptr;
Vector3 end;
+ /* TA: multisun support */
+ byte skyEnvironmentLightIndices[MULTISUN_MAX_BYTES];
+
/* calculated input */
Vector3 displacement, direction;
float distance;
@@ -2026,6 +2037,10 @@ inline const bool exactPointToPolygon = true;
inline const float formFactorValueScale = 3.0f;
inline const float linearScale = 1.0f / 8000.0f;
+/* TA: multisun stuff (ability to have mutliple skyboxes with separate suns/skylights). May not handle _skybox properly */
+inline bool multiSun = false;
+inline int nextEnvironmentLightIndex = 0; // each shader environment light (sun/sky) gets a unique index. thus we can check whether a sky surface should be considered as a successful trace
+
inline std::list lights;
inline int numPointLights;
inline int numSpotLights;