dynamic lights apply to even more surfaces and have a nicer fall-off

- ditched vertex colors (not wanted) and alpha tests (not needed) in the shaders
- using a Bezier fall-off to get much softer edges
- added no-depth-write transparent surfaces support by adjusting the depth test
- multiplying the diffuse texture's color by its alpha in non-opaque passes
- fixed triangle rejection based on cull type and normal direction
- reflecting normals in shaders to support two-sided surfaces
- rejecting surfaces with no diffuse stage or bad blend states as early as possible
- liquids get lit weaker than other surfaces
This commit is contained in:
myT 2020-02-21 08:26:12 +01:00
parent 8ef496d22b
commit 85583acc9c
9 changed files with 128 additions and 73 deletions

View file

@ -89,13 +89,15 @@ 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
this new optimization works with the output of "Frozen Sand Particle Studio"
add: /modellist /skinlist /imagelist /shaderlist can now filter results with pattern matching
chg: dynamic lights now use a softer fall-off curve to create softer edges
chg: reverted an old change to the Escape key handling for cgame
this fixes demo playback behavior in CPMA, which enables custom binds after pressing Escape once
chg: searching for valid sample counts for MSAA in GL2 and GL3 instead of failing right away
add: /modellist /skinlist /imagelist /shaderlist can now filter results with pattern matching
chg: switched away from stb_image for .tga file loading and now using an updated version of the old code
this lowers the chances of a fatal error from the zone memory allocator when reading large images
@ -106,8 +108,8 @@ chg: removed FreeType 2 and the unused R_REGISTERFONT syscalls that were using i
chg: removed light flares and all related cvars (r_flare*)
chg: r_textureMode <filter> (default: "best") is now latched and only supports 2 modes:
r_textureMode GL_NEAREST = LEGO(R) mode - nearest-neighbor filtering for a subset of surfaces
r_textureMode anything else = normal mode - the engine picks the most appropriate filters
r_textureMode GL_NEAREST = LEGO(R) mode - nearest-neighbor filtering for a subset of surfaces
r_textureMode anything else = normal mode - the engine picks the most appropriate filters
chg: r_measureOverdraw was removed
@ -140,7 +142,7 @@ fix: broken face normals get fixed up on map load, which helps with dynamic ligh
fix: classes of transparent surfaces (sprites, beams, etc) now get drawn in the right order
fix: dynamic lights can now affect transparent surfaces
fix: dynamic lights can now affect transparent surfaces and double-sided surfaces on both sides
fix: dynamic lights no longer "reveal" the diffuse texture when r_lightmap is 1

View file

@ -33,7 +33,6 @@ struct VIn
{
float4 position : POSITION;
float4 normal : NORMAL;
float4 color : COLOR0;
float2 texCoords : TEXCOORD0;
};
@ -41,7 +40,6 @@ struct VOut
{
float4 position : SV_Position;
float3 normal : NORMAL;
float4 color : COLOR0;
float2 texCoords : TEXCOORD0;
float3 osLightVec : TEXCOORD1;
float3 osEyeVec : TEXCOORD2;
@ -55,7 +53,6 @@ VOut vs_main(VIn input)
VOut output;
output.position = mul(projectionMatrix, positionVS);
output.normal = input.normal.xyz;
output.color = input.color;
output.texCoords = input.texCoords;
output.osLightVec = osLightPos.xyz - input.position.xyz;
output.osEyeVec = osEyePos.xyz - input.position.xyz;
@ -68,36 +65,39 @@ cbuffer PixelShaderBuffer
{
float3 lightColor;
float lightRadius;
uint alphaTest;
uint dummy[3];
float opaque;
float intensity;
float dummy[2];
};
Texture2D texture0 : register(t0);
SamplerState sampler0 : register(s0);
float BezierEase(float t)
{
return t * t * (3.0 - 2.0 * t);
}
float4 ps_main(VOut input) : SV_TARGET
{
float4 base = texture0.Sample(sampler0, input.texCoords) * input.color;
if((alphaTest == 1 && base.a == 0.0) ||
(alphaTest == 2 && base.a >= 0.5) ||
(alphaTest == 3 && base.a < 0.5))
discard;
float4 base = texture0.Sample(sampler0, input.texCoords);
float3 nL = normalize(input.osLightVec); // normalized object-space light vector
float3 nV = normalize(input.osEyeVec); // normalized object-space view vector
float3 nN = input.normal; // normalized object-space normal vector
// light intensity
float intensFactor = dot(input.osLightVec, input.osLightVec) * lightRadius;
float3 intens = lightColor * (1.0 - intensFactor);
float intensFactor = min(dot(input.osLightVec, input.osLightVec) * lightRadius, 1.0);
float3 intens = lightColor * BezierEase(1.0 - sqrt(intensFactor));
// specular reflection term (N.H)
float specFactor = clamp(dot(nN, normalize(nL + nV)), 0.0, 1.0);
float specFactor = min(abs(dot(nN, normalize(nL + nV))), 1.0);
float spec = pow(specFactor, 16.0) * 0.25;
// Lambertian diffuse reflection term (N.L)
float diffuse = clamp(dot(nN, nL), 0.0, 1.0);
float4 final = (base * float4(diffuse.rrr, 1.0) + float4(spec.rrr, 1.0)) * float4(intens.rgb, 1.0);
float diffuse = min(abs(dot(nN, nL)), 1.0);
float3 color = (base.rgb * diffuse.rrr + spec.rrr) * intens * intensity;
float alpha = lerp(opaque, 1.0, base.a);
float4 final = float4(color.rgb * alpha, alpha);
return final;
}

View file

@ -390,8 +390,10 @@ static void RB_RenderLitSurfList( dlight_t* dl, qbool opaque )
double originalTime = backEnd.refdef.floatTime;
// draw everything
const int liquidFlags = CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER;
oldEntityNum = -1;
backEnd.currentEntity = &tr.worldEntity;
backEnd.dlOpaque = opaque;
oldDepthRange = qfalse;
depthRange = qfalse;
tess.light = dl;
@ -418,6 +420,15 @@ static void RB_RenderLitSurfList( dlight_t* dl, qbool opaque )
RB_EndSurface();
RB_BeginSurface( shader, fogNum );
// stage index is guaranteed valid by R_AddLitSurface
const int stageIndex = tess.shader->lightingStages[ST_DIFFUSE];
const shaderStage_t* const stage = tess.xstages[stageIndex];
backEnd.dlIntensity = (shader->contentFlags & liquidFlags) != 0 ? 0.5f : 1.0f;
backEnd.dlStateBits =
(opaque || (stage->stateBits & GLS_ATEST_BITS) != 0) ?
(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL):
(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);
sort = litSurf->sort;
//

View file

@ -177,8 +177,9 @@ struct DynamicLightPSData
{
float lightColor[3];
float lightRadius;
uint32_t alphaTest; // AlphaTest enum
uint32_t dummy[3];
float opaque;
float intensity;
float dummy[2];
};
struct PostVSData
@ -673,9 +674,10 @@ static void UploadPendingShaderData()
memcpy(vsData.clipPlane, d3d.clipPlane, sizeof(vsData.clipPlane));
memcpy(vsData.osEyePos, d3d.osEyePos, sizeof(vsData.osEyePos));
memcpy(vsData.osLightPos, d3d.osLightPos, sizeof(vsData.osLightPos));
psData.alphaTest = d3d.alphaTest;
memcpy(psData.lightColor, d3d.lightColor, sizeof(psData.lightColor));
psData.lightRadius = d3d.lightRadius;
psData.opaque = backEnd.dlOpaque ? 1.0f : 0.0f;
psData.intensity = backEnd.dlIntensity;
ResetShaderData(pipeline->vertexBuffer, &vsData, sizeof(vsData));
ResetShaderData(pipeline->pixelBuffer, &psData, sizeof(psData));
}
@ -2268,7 +2270,6 @@ static void DrawDynamicLight()
AppendVertexData(&d3d.vertexBuffers[VB_POSITION], tess.xyz, tess.numVertexes);
AppendVertexData(&d3d.vertexBuffers[VB_NORMAL], tess.normal, tess.numVertexes);
AppendVertexData(&d3d.vertexBuffers[VB_TEXCOORD], tess.svars[stageIndex].texcoordsptr, tess.numVertexes);
AppendVertexData(&d3d.vertexBuffers[VB_COLOR], tess.svars[stageIndex].colors, tess.numVertexes);
}
else
{
@ -2277,13 +2278,11 @@ static void DrawDynamicLight()
pointers[VB_NORMAL] = tess.normal;
pointers[VB_TEXCOORD] = tess.svars[stageIndex].texcoordsptr;
pointers[VB_TEXCOORD2] = NULL;
pointers[VB_COLOR] = tess.svars[stageIndex].colors;
pointers[VB_COLOR] = NULL;
AppendVertexDataGroup(pointers, tess.numVertexes);
}
const int oldAlphaTestBits = stage->stateBits & GLS_ATEST_BITS;
const int newBits = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL;
ApplyState(oldAlphaTestBits | newBits, tess.shader->cullType, tess.shader->polygonOffset);
ApplyState(backEnd.dlStateBits, tess.shader->cullType, tess.shader->polygonOffset);
BindBundle(0, &stage->bundle);
UploadPendingShaderData();

View file

@ -93,6 +93,7 @@ struct GLSL_DynLightProgramAttribs {
// pixel shader:
GLint texture; // 2D texture
GLint lightColorRadius; // 4f, w = 1 / (r^2)
GLint opaqueIntensity; // 2f
};
static GLSL_DynLightProgramAttribs dynLightProgAttribs;
@ -120,6 +121,8 @@ static void DrawDynamicLight()
if ( tess.shader->polygonOffset )
glEnable( GL_POLYGON_OFFSET_FILL );
glUniform2f( dynLightProgAttribs.opaqueIntensity, backEnd.dlOpaque ? 1.0f : 0.0f, backEnd.dlIntensity );
const int stage = tess.shader->lightingStages[ST_DIFFUSE];
const shaderStage_t* pStage = tess.xstages[ stage ];
@ -136,9 +139,7 @@ static void DrawDynamicLight()
glVertexPointer( 3, GL_FLOAT, 16, tess.xyz );
glLockArraysEXT( 0, tess.numVertexes );
const int oldAlphaTestBits = pStage->stateBits & GLS_ATEST_BITS;
const int newBits = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL;
GL_State( oldAlphaTestBits | newBits );
GL_State( backEnd.dlStateBits );
GL_SelectTexture( 0 );
R_BindAnimatedImage( &pStage->bundle );
@ -300,25 +301,33 @@ static const char* dynLightVS =
static const char* dynLightFS =
"uniform sampler2D texture;\n"
"uniform vec4 lightColorRadius;" // w = 1 / (r^2)
"uniform vec4 lightColorRadius;\n" // w = 1 / (r^2)
"uniform vec2 opaqueIntensity;\n"
"varying vec4 L;\n" // object-space light vector
"varying vec4 V;\n" // object-space view vector
"varying vec3 nN;\n" // normalized object-space normal vector
"\n"
"float BezierEase(float t)\n"
"{\n"
" return t * t * (3.0 - 2.0 * t);\n"
"}\n"
"\n"
"void main()\n"
"{\n"
" vec4 base = texture2D(texture, gl_TexCoord[0].xy);\n"
" vec3 nL = normalize(L.xyz);\n" // normalized light vector
" vec3 nV = normalize(V.xyz);\n" // normalized view vector
// light intensity
" float intensFactor = dot(L.xyz, L.xyz) * lightColorRadius.w;"
" vec3 intens = lightColorRadius.rgb * (1.0 - intensFactor);\n"
" float intensFactor = min(dot(L.xyz, L.xyz) * lightColorRadius.w, 1.0);"
" vec3 intens = lightColorRadius.rgb * BezierEase(1.0 - sqrt(intensFactor));\n"
// specular reflection term (N.H)
" float specFactor = clamp(dot(nN, normalize(nL + nV)), 0.0, 1.0);\n"
" float specFactor = min(abs(dot(nN, normalize(nL + nV))), 1.0);\n"
" float spec = pow(specFactor, 16.0) * 0.25;\n"
// Lambertian diffuse reflection term (N.L)
" float diffuse = clamp(dot(nN, nL), 0.0, 1.0);\n"
" gl_FragColor = vec4((base.rgb * vec3(diffuse) + vec3(spec)) * vec3(intens), base.a);\n"
" float diffuse = min(abs(dot(nN, nL)), 1.0);\n"
" vec3 color = (base.rgb * vec3(diffuse) + vec3(spec)) * intens * opaqueIntensity.y;\n"
" float alpha = mix(opaqueIntensity.x, 1.0, base.a);\n"
" gl_FragColor = vec4(color.rgb * alpha, alpha);\n"
"}\n"
"";
@ -696,6 +705,7 @@ static qbool GL2_Init()
dynLightProgAttribs.osLightPos = glGetUniformLocation( dynLightProg.p, "osLightPos" );
dynLightProgAttribs.texture = glGetUniformLocation( dynLightProg.p, "texture" );
dynLightProgAttribs.lightColorRadius = glGetUniformLocation( dynLightProg.p, "lightColorRadius" );
dynLightProgAttribs.opaqueIntensity = glGetUniformLocation( dynLightProg.p, "opaqueIntensity" );
if ( !GL2_CreateProgram( gammaProg, gammaVS, gammaFS ) ) {
ri.Printf( PRINT_ERROR, "Failed to compile gamma correction shaders\n" );

View file

@ -161,7 +161,8 @@ enum DynamicLightUniform
DU_LIGHT_POS,
DU_EYE_POS,
DU_LIGHT_COLOR_RADIUS,
DU_ALPHA_TEST,
DU_OPAQUE,
DU_INTENSITY,
DU_COUNT
};
@ -233,6 +234,8 @@ struct OpenGL3
qbool enableClipPlane;
qbool prevEnableClipPlane;
AlphaTest alphaTest;
qbool dlOpaque;
float dlIntensity;
ArrayBuffer arrayBuffers[VB_COUNT];
ArrayBuffer indexBuffer;
@ -400,11 +403,9 @@ static const char* dl_vs =
"in vec4 position;\n"
"in vec4 normal;\n"
"in vec2 texCoords1;\n"
"in vec4 color;\n"
"\n"
"out vec3 normalFS;\n"
"out vec2 texCoords1FS;\n"
"out vec4 colorFS;\n"
"out vec3 L;\n"
"out vec3 V;\n"
"\n"
@ -415,7 +416,6 @@ static const char* dl_vs =
" gl_ClipDistance[0] = dot(positionVS, clipPlane);\n"
" normalFS = normal.xyz;\n"
" texCoords1FS = texCoords1;\n"
" colorFS = color;\n"
" L = osLightPos - position.xyz;\n"
" V = osEyePos - position.xyz;\n"
"}\n";
@ -424,40 +424,41 @@ static const char* dl_fs =
"uniform sampler2D texture1;\n"
"\n"
"uniform vec4 lightColorRadius;\n"
"uniform uint alphaTest;\n"
"uniform float opaque;\n"
"uniform float intensity;\n"
"\n"
"in vec3 normalFS;\n"
"in vec2 texCoords1FS;\n"
"in vec4 colorFS;\n"
"in vec3 L;\n"
"in vec3 V;\n"
"\n"
"out vec4 fragColor;\n"
"\n"
"float BezierEase(float t)\n"
"{\n"
" return t * t * (3.0 - 2.0 * t);\n"
"}\n"
"\n"
"void main()\n"
"{\n"
" vec4 base = colorFS * texture2D(texture1, texCoords1FS);\n"
" vec4 base = texture2D(texture1, texCoords1FS);\n"
" vec3 nL = normalize(L);\n"
" vec3 nV = normalize(V);\n"
"\n"
" // light intensity\n"
" float intensFactor = dot(L, L) * lightColorRadius.w;\n"
" vec3 intens = lightColorRadius.rgb * (1.0 - intensFactor);\n"
" float intensFactor = min(dot(L, L) * lightColorRadius.w, 1.0);\n"
" vec3 intens = lightColorRadius.rgb * BezierEase(1.0 - sqrt(intensFactor));\n"
"\n"
" // specular reflection term (N.H)\n"
" float specFactor = clamp(dot(normalFS, normalize(nL + nV)), 0.0, 1.0);\n"
" float specFactor = min(abs(dot(normalFS, normalize(nL + nV))), 1.0);\n"
" float spec = pow(specFactor, 16.0) * 0.25;\n"
"\n"
" // Lambertian diffuse reflection term (N.L)\n"
" float diffuse = clamp(dot(normalFS, nL), 0.0, 1.0);\n"
" vec4 r = vec4((base.rgb * vec3(diffuse) + vec3(spec)) * intens, base.a);\n"
" float diffuse = min(abs(dot(normalFS, nL)), 1.0);\n"
" vec3 color = (base.rgb * vec3(diffuse) + vec3(spec)) * intens * intensity;\n"
" float alpha = mix(opaque, 1.0, base.a);\n"
"\n"
" if( (alphaTest == uint(1) && r.a == 0.0) ||\n"
" (alphaTest == uint(2) && r.a >= 0.5) ||\n"
" (alphaTest == uint(3) && r.a < 0.5))\n"
" discard;\n"
"\n"
" fragColor = r;\n"
" fragColor = vec4(color.rgb * alpha, alpha);\n"
"}\n";
static const char* sprite_vs =
@ -1412,10 +1413,6 @@ static void ApplyAlphaTest(AlphaTest alphaTest)
{
gl.pipelines[PID_GENERIC].uniformsDirty[GU_ALPHA_TEX] = qtrue;
}
else if(gl.pipelineId == PID_DYNAMIC_LIGHT)
{
gl.pipelines[PID_DYNAMIC_LIGHT].uniformsDirty[DU_ALPHA_TEST] = qtrue;
}
else if(gl.pipelineId == PID_SOFT_SPRITE)
{
gl.pipelines[PID_SOFT_SPRITE].uniformsDirty[SU_ALPHA_TEST] = qtrue;
@ -1884,15 +1881,14 @@ static void Init()
gl.pipelines[PID_DYNAMIC_LIGHT].arrayBuffers[VB_NORMAL].attribName = "normal";
gl.pipelines[PID_DYNAMIC_LIGHT].arrayBuffers[VB_TEXCOORD].enabled = qtrue;
gl.pipelines[PID_DYNAMIC_LIGHT].arrayBuffers[VB_TEXCOORD].attribName = "texCoords1";
gl.pipelines[PID_DYNAMIC_LIGHT].arrayBuffers[VB_COLOR].enabled = qtrue;
gl.pipelines[PID_DYNAMIC_LIGHT].arrayBuffers[VB_COLOR].attribName = "color";
gl.pipelines[PID_DYNAMIC_LIGHT].uniformNames[DU_MODELVIEW] = "modelView";
gl.pipelines[PID_DYNAMIC_LIGHT].uniformNames[DU_PROJECTION] = "projection";
gl.pipelines[PID_DYNAMIC_LIGHT].uniformNames[DU_CLIP_PLANE] = "clipPlane";
gl.pipelines[PID_DYNAMIC_LIGHT].uniformNames[DU_LIGHT_POS] = "osLightPos";
gl.pipelines[PID_DYNAMIC_LIGHT].uniformNames[DU_EYE_POS] = "osEyePos";
gl.pipelines[PID_DYNAMIC_LIGHT].uniformNames[DU_LIGHT_COLOR_RADIUS] = "lightColorRadius";
gl.pipelines[PID_DYNAMIC_LIGHT].uniformNames[DU_ALPHA_TEST] = "alphaTest";
gl.pipelines[PID_DYNAMIC_LIGHT].uniformNames[DU_OPAQUE] = "opaque";
gl.pipelines[PID_DYNAMIC_LIGHT].uniformNames[DU_INTENSITY] = "intensity";
gl.pipelines[PID_SOFT_SPRITE].arrayBuffers[VB_POSITION].enabled = qtrue;
gl.pipelines[PID_SOFT_SPRITE].arrayBuffers[VB_POSITION].attribName = "position";
@ -2209,14 +2205,23 @@ static void DrawDynamicLight()
UploadVertexArray(VB_POSITION, tess.xyz);
UploadVertexArray(VB_NORMAL, tess.normal);
UploadVertexArray(VB_TEXCOORD, tess.svars[stageIndex].texcoordsptr);
UploadVertexArray(VB_COLOR, tess.svars[stageIndex].colors);
UploadIndices(tess.dlIndexes, tess.dlNumIndexes);
const int oldAlphaTestBits = stage->stateBits & GLS_ATEST_BITS;
const int newBits = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL;
ApplyState(oldAlphaTestBits | newBits, tess.shader->cullType, tess.shader->polygonOffset);
ApplyState(backEnd.dlStateBits, tess.shader->cullType, tess.shader->polygonOffset);
BindBundle(0, &stage->bundle);
if(backEnd.dlOpaque != gl.dlOpaque)
{
gl.dlOpaque = backEnd.dlOpaque;
pipeline->uniformsDirty[DU_OPAQUE] = qtrue;
}
if(backEnd.dlIntensity != gl.dlIntensity)
{
gl.dlIntensity = backEnd.dlIntensity;
pipeline->uniformsDirty[DU_INTENSITY] = qtrue;
}
if(pipeline->uniformsDirty[DU_MODELVIEW])
{
glUniformMatrix4fv(pipeline->uniformLocations[DU_MODELVIEW], 1, GL_FALSE, gl.modelViewMatrix);
@ -2229,9 +2234,13 @@ static void DrawDynamicLight()
{
glUniform4fv(pipeline->uniformLocations[DU_CLIP_PLANE], 1, gl.clipPlane);
}
if(pipeline->uniformsDirty[DU_ALPHA_TEST])
if(pipeline->uniformsDirty[DU_OPAQUE])
{
glUniform1ui(pipeline->uniformLocations[DU_ALPHA_TEST], gl.alphaTest);
glUniform1f(pipeline->uniformLocations[DU_OPAQUE], gl.dlOpaque ? 1.0f : 0.0f);
}
if(pipeline->uniformsDirty[DU_INTENSITY])
{
glUniform1f(pipeline->uniformLocations[DU_INTENSITY], gl.dlIntensity);
}
memset(pipeline->uniformsDirty, 0, sizeof(pipeline->uniformsDirty));

View file

@ -815,6 +815,15 @@ typedef struct {
byte color2D[4];
trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering
// dynamic lights data set by the back-end for the GAL
qbool dlOpaque; // qtrue when drawing an opaque surface
float dlIntensity; // 1 for most surfaces, but can be scaled down for liquids etc.
unsigned int dlStateBits; // the state bits to apply for this draw call
// quick explanation on why dlOpaque is useful in the first place:
// - opaque surfaces can have a diffuse texture whose alpha isn't 255 everywhere
// - when that happens and we multiply the color by the the alpha (DL uses additive blending),
// we get "light holes" in opaque surfaces, which is not what we want
int* pc; // current stats set, depending on projection2D
int pc2D[RB_STATS_MAX];
int pc3D[RB_STATS_MAX];

View file

@ -57,19 +57,19 @@ void RB_BeginSurface( const shader_t* shader, int fogNum )
static void RB_DrawDynamicLight()
{
if (tess.shader->lightingStages[ST_DIFFUSE] == -1)
return;
backEnd.pc[RB_LIT_VERTICES_LATECULLTEST] += tess.numVertexes;
static byte clipBits[SHADER_MAX_VERTEXES];
const dlight_t* dl = tess.light;
const cullType_t cullType = tess.shader->cullType;
for (int i = 0; i < tess.numVertexes; ++i) {
vec3_t dist;
VectorSubtract(dl->transformed, tess.xyz[i], dist);
if (DotProduct(dist, tess.normal[i]) <= 0.0f) {
const float dp = DotProduct(dist, tess.normal[i]);
if (cullType == CT_FRONT_SIDED && dp <= 0.0f ||
cullType == CT_BACK_SIDED && dp >= 0.0f) {
clipBits[i] = byte(-1);
continue;
}

View file

@ -331,12 +331,27 @@ static void R_AddLitSurface( msurface_t* surf, const dlight_t* light )
if ( surf->shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) )
return;
// reject mirrors, portals, sky boxes, etc.
if ( surf->shader->sort < SS_OPAQUE )
return;
if ( surf->lightCount == tr.lightCount )
return; // already in the lit list (or already culled) for this light
const int stageIndex = surf->shader->lightingStages[ST_DIFFUSE];
if ( stageIndex < 0 )
return;
const shaderStage_t* const stage = surf->shader->stages[stageIndex];
const int srcBits = stage->stateBits & GLS_SRCBLEND_BITS;
const int dstBits = stage->stateBits & GLS_DSTBLEND_BITS;
// we can't use a texture that was used with such a blend mode
// since the final color could look nothing like the texture itself
if ( srcBits == GLS_SRCBLEND_ONE_MINUS_DST_COLOR ||
dstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR )
return;
surf->lightCount = tr.lightCount;
if ( R_LightCullSurface( surf->data, light ) ) {