openmohaa/code/renderergl1/tr_shade.c

1877 lines
47 KiB
C
Raw Normal View History

2023-05-08 19:53:53 +02:00
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena 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 2 of the License,
or (at your option) any later version.
Quake III Arena 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
2024-12-01 15:26:01 +01:00
along with Foobar; if not, write to the Free Software
2023-05-08 19:53:53 +02:00
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// tr_shade.c
2024-12-01 15:26:01 +01:00
#include "tr_local.h"
2023-05-08 19:53:53 +02:00
/*
THIS ENTIRE FILE IS BACK END
This file deals with applying shaders to surface data in the tess struct.
*/
/*
================
R_ArrayElementDiscrete
This is just for OpenGL conformance testing, it should never be the fastest
================
*/
static void APIENTRY R_ArrayElementDiscrete( GLint index ) {
qglColor4ubv( tess.svars.colors[ index ] );
if ( glState.currenttmu ) {
qglMultiTexCoord2fARB( 0, tess.svars.texcoords[ 0 ][ index ][0], tess.svars.texcoords[ 0 ][ index ][1] );
qglMultiTexCoord2fARB( 1, tess.svars.texcoords[ 1 ][ index ][0], tess.svars.texcoords[ 1 ][ index ][1] );
} else {
qglTexCoord2fv( tess.svars.texcoords[ 0 ][ index ] );
}
qglVertex3fv( tess.xyz[ index ] );
}
/*
===================
R_DrawStripElements
===================
*/
static int c_vertexes; // for seeing how long our average strips are
static int c_begins;
static void R_DrawStripElements( int numIndexes, const glIndex_t *indexes, void ( APIENTRY *element )(GLint) ) {
int i;
int last[3] = { -1, -1, -1 };
qboolean even;
c_begins++;
if ( numIndexes <= 0 ) {
return;
}
qglBegin( GL_TRIANGLE_STRIP );
// prime the strip
element( indexes[0] );
element( indexes[1] );
element( indexes[2] );
c_vertexes += 3;
last[0] = indexes[0];
last[1] = indexes[1];
last[2] = indexes[2];
even = qfalse;
for ( i = 3; i < numIndexes; i += 3 )
{
// odd numbered triangle in potential strip
if ( !even )
{
// check previous triangle to see if we're continuing a strip
if ( ( indexes[i+0] == last[2] ) && ( indexes[i+1] == last[1] ) )
{
element( indexes[i+2] );
c_vertexes++;
assert( indexes[i+2] < tess.numVertexes );
even = qtrue;
}
// otherwise we're done with this strip so finish it and start
// a new one
else
{
qglEnd();
qglBegin( GL_TRIANGLE_STRIP );
c_begins++;
element( indexes[i+0] );
element( indexes[i+1] );
element( indexes[i+2] );
c_vertexes += 3;
even = qfalse;
}
}
else
{
// check previous triangle to see if we're continuing a strip
if ( ( last[2] == indexes[i+1] ) && ( last[0] == indexes[i+0] ) )
{
element( indexes[i+2] );
c_vertexes++;
even = qfalse;
}
// otherwise we're done with this strip so finish it and start
// a new one
else
{
qglEnd();
qglBegin( GL_TRIANGLE_STRIP );
c_begins++;
element( indexes[i+0] );
element( indexes[i+1] );
element( indexes[i+2] );
c_vertexes += 3;
even = qfalse;
}
}
// cache the last three vertices
last[0] = indexes[i+0];
last[1] = indexes[i+1];
last[2] = indexes[i+2];
}
qglEnd();
}
/*
==================
R_DrawElements
Optionally performs our own glDrawElements that looks for strip conditions
instead of using the single glDrawElements call that may be inefficient
without compiled vertex arrays.
==================
*/
2024-12-01 15:26:01 +01:00
static void R_DrawElements( int numIndexes, const glIndex_t *indexes ) {
2023-05-08 19:53:53 +02:00
int primitives;
primitives = r_primitives->integer;
// default is to use triangles if compiled vertex arrays are present
if ( primitives == 0 ) {
if ( qglLockArraysEXT ) {
primitives = 2;
} else {
primitives = 1;
}
}
if ( primitives == 2 ) {
qglDrawElements( GL_TRIANGLES,
numIndexes,
GL_INDEX_TYPE,
indexes );
return;
}
if ( primitives == 1 ) {
R_DrawStripElements( numIndexes, indexes, qglArrayElement );
return;
}
if ( primitives == 3 ) {
R_DrawStripElements( numIndexes, indexes, R_ArrayElementDiscrete );
return;
}
// anything else will cause no drawing
}
/*
=============================================================
SURFACE SHADERS
=============================================================
*/
shaderCommands_t tess;
static qboolean setArraysOnce;
/*
=================
R_BindAnimatedImage
=================
*/
static void R_BindAnimatedImage( textureBundle_t *bundle ) {
2024-12-01 15:26:01 +01:00
int index;
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
if (bundle->numImageAnimations <= 1) {
GL_Bind(bundle->image[0]);
2023-05-08 19:53:53 +02:00
return;
}
// it is necessary to do this messy calc to make sure animations line up
// exactly with waveforms of the same frequency
2024-12-01 15:26:01 +01:00
index = myftol((tess.shaderTime + bundle->imageAnimationPhase) * bundle->imageAnimationSpeed * FUNCTABLE_SIZE);
2023-05-08 19:53:53 +02:00
index >>= FUNCTABLE_SIZE2;
2024-12-01 15:26:01 +01:00
if (index < 0) {
2023-05-08 19:53:53 +02:00
index = 0; // may happen with shader time offsets
}
2024-12-01 15:26:01 +01:00
if (!(bundle->flags & BUNDLE_ANIMATE_ONCE)) {
index %= bundle->numImageAnimations;
} else if (index >= bundle->numImageAnimations) {
index = bundle->numImageAnimations - 1;
2023-05-08 19:53:53 +02:00
}
2024-12-01 15:26:01 +01:00
GL_Bind(bundle->image[index]);
2023-05-08 19:53:53 +02:00
}
/*
================
DrawTris
Draws triangle outlines for debugging
================
*/
2024-12-01 15:26:01 +01:00
static void DrawTris (shaderCommands_t *input, int flags) {
2023-05-08 19:53:53 +02:00
GL_Bind( tr.whiteImage );
qglColor3f (1,1,1);
2024-12-01 15:26:01 +01:00
if (flags == 1) {
GL_State(GLS_POLYMODE_LINE);
} else {
GL_State(GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE);
qglDepthRange(0, 0);
}
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
if (flags < 3) {
qglDisableClientState(GL_COLOR_ARRAY);
qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
qglVertexPointer(3, GL_FLOAT, 16, input->xyz); // padded for SIMD
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
if (qglLockArraysEXT) {
qglLockArraysEXT(0, input->numVertexes);
GLimp_LogComment("glLockArraysEXT\n");
}
R_DrawElements(input->numIndexes, input->indexes);
if (qglUnlockArraysEXT) {
qglUnlockArraysEXT();
GLimp_LogComment("glUnlockArraysEXT\n");
}
qglDepthRange(0, 1);
2023-05-08 19:53:53 +02:00
}
2024-12-01 15:26:01 +01:00
if (flags > 1) {
vec3_t start, end;
start[1] = start[0] = 0.0;
end[1] = end[0] = 0.0;
GL_State(GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE);
qglDepthRange(0, 0);
qglDisable(GL_TEXTURE_2D);
qglBegin(GL_LINES);
qglColor4f(1.0, 0.0, 0.0, 1.0);
start[2] = 0.0 - 5.0;
end[2] = 0.0 + 5.0;
qglVertex3fv(start);
qglVertex3fv(end);
qglColor4f(0.0, 1.0, 0.0, 1.0);
start[2] = start[2] + 5.0;
end[2] = end[2] - 5.0;
start[1] = start[1] - 5.0;
end[1] = end[1] + 5.0;
qglVertex3fv(start);
qglVertex3fv(end);
qglColor4f(0.0, 0.0, 1.0, 1.0);
start[1] = start[1] + 5.0;
start[0] = start[0] - 5.0;
end[0] = end[0] + 5.0;
qglVertex3fv(start);
qglVertex3fv(end);
start[0] = start[0] + 5.0;
end[0] = end[0] - 5.0;
qglEnd();
qglEnable(GL_TEXTURE_2D);
qglDepthRange(0.0, 1.0);
2023-05-08 19:53:53 +02:00
}
}
/*
================
DrawNormals
Draws vertex normals for debugging
================
*/
static void DrawNormals (shaderCommands_t *input) {
int i;
vec3_t temp;
GL_Bind( tr.whiteImage );
qglColor3f (1,1,1);
qglDepthRange( 0, 0 ); // never occluded
GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE );
qglBegin (GL_LINES);
for (i = 0 ; i < input->numVertexes ; i++) {
qglVertex3fv (input->xyz[i]);
VectorMA (input->xyz[i], 2, input->normal[i], temp);
qglVertex3fv (temp);
}
qglEnd ();
qglDepthRange( 0, 1 );
}
/*
==============
RB_BeginSurface
We must set some things up before beginning any tesselation,
because a surface may be forced to perform a RB_End due
to overflow.
==============
*/
2024-12-01 15:26:01 +01:00
void RB_BeginSurface( shader_t *shader ) {
2023-05-08 19:53:53 +02:00
tess.numIndexes = 0;
tess.numVertexes = 0;
2024-12-01 15:26:01 +01:00
tess.shader = shader;
2023-05-08 19:53:53 +02:00
tess.dlightBits = 0; // will be OR'd in by surface functions
2024-12-01 15:26:01 +01:00
tess.vertexColorValid = r_vertexLight->integer != 0;
tess.xstages = shader->unfoggedStages;
tess.numPasses = shader->numUnfoggedPasses;
tess.currentStageIteratorFunc = shader->optimalStageIteratorFunc;
2023-05-08 19:53:53 +02:00
}
/*
===================
DrawMultitextured
output = t0 * t1 or t0 + t1
t0 = most upstream according to spec
t1 = most downstream according to spec
===================
*/
static void DrawMultitextured( shaderCommands_t *input, int stage ) {
shaderStage_t *pStage;
pStage = tess.xstages[stage];
GL_State( pStage->stateBits );
// this is an ugly hack to work around a GeForce driver
// bug with multitexture and clip planes
if ( backEnd.viewParms.isPortal ) {
qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}
//
// base
//
GL_SelectTexture( 0 );
qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] );
R_BindAnimatedImage( &pStage->bundle[0] );
2024-12-01 15:26:01 +01:00
/*
// was removed since 2.0
if (pStage->stateBits & GLS_MULTITEXTURE_ENV)
{
glState.cntnvblendmode = pStage->multitextureEnv;
glState.cntTexEnvExt = GLS_MULTITEXTURE_ENV;
if (pStage->multitextureEnv == GL_ADD)
{
qglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);
qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0);
qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, 0);
qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_ONE_MINUS_SRC_COLOR);
qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE1);
qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR);
qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE3_RGB, 0);
qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND3_RGB, GL_ONE_MINUS_SRC_COLOR);
}
else if (pStage->multitextureEnv == GL_MODULATE)
{
qglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0);
qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE1);
qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
}
else
{
ri.Printf(3, "Unknown MT mode for: %s\n", input->shader);
}
}
*/
2023-05-08 19:53:53 +02:00
//
// lightmap/secondary pass
//
GL_SelectTexture( 1 );
qglEnable( GL_TEXTURE_2D );
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
2024-12-01 15:26:01 +01:00
if ( r_lightmap->integer && pStage->bundle[1].isLightmap ) {
2023-05-08 19:53:53 +02:00
GL_TexEnv( GL_REPLACE );
2024-12-01 15:26:01 +01:00
/*
// was removed since 2.0
} else if (pStage->stateBits & GLS_MULTITEXTURE_ENV) {
glState.cntTexEnvExt = GLS_MULTITEXTURE_ENV;
GL_TexEnv(GL_COMBINE);
qglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);
qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PRIMARY_COLOR);
qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS);
qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
qglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_SRC_ALPHA);
qglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA);
*/
2023-05-08 19:53:53 +02:00
} else {
2024-12-01 15:26:01 +01:00
GL_TexEnv( pStage->multitextureEnv );
2023-05-08 19:53:53 +02:00
}
qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[1] );
2024-12-01 15:26:01 +01:00
if (!input->dlightMap || !pStage->bundle[1].isLightmap) {
R_BindAnimatedImage(&pStage->bundle[1]);
} else {
GL_Bind(tr.dlightImages[input->dlightMap - 1]);
}
2023-05-08 19:53:53 +02:00
R_DrawElements( input->numIndexes, input->indexes );
2024-12-01 15:26:01 +01:00
qglDisable(GL_TEXTURE_2D);
2023-05-08 19:53:53 +02:00
//
// disable texturing on TEXTURE1, then select TEXTURE0
//
2024-12-01 15:26:01 +01:00
qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
2023-05-08 19:53:53 +02:00
GL_SelectTexture( 0 );
}
/*
===================
ProjectDlightTexture
Perform dynamic lighting with another rendering pass
===================
*/
2024-12-01 15:26:01 +01:00
static void ProjectDlightTexture( void ) {
2023-05-08 19:53:53 +02:00
int i, l;
2024-12-01 15:26:01 +01:00
#if idppc_altivec
vec_t origin0, origin1, origin2;
float texCoords0, texCoords1;
vector float floatColorVec0, floatColorVec1;
vector float modulateVec, colorVec, zero;
vector short colorShort;
vector signed int colorInt;
vector unsigned char floatColorVecPerm, modulatePerm, colorChar;
vector unsigned char vSel = (vector unsigned char)(0x00, 0x00, 0x00, 0xff,
0x00, 0x00, 0x00, 0xff,
0x00, 0x00, 0x00, 0xff,
0x00, 0x00, 0x00, 0xff);
#else
2023-05-08 19:53:53 +02:00
vec3_t origin;
2024-12-01 15:26:01 +01:00
vec3_t normal;
#endif
2023-05-08 19:53:53 +02:00
float *texCoords;
byte *colors;
byte clipBits[SHADER_MAX_VERTEXES];
2024-12-01 15:26:01 +01:00
MAC_STATIC float texCoordsArray[SHADER_MAX_VERTEXES][2];
2023-05-08 19:53:53 +02:00
byte colorArray[SHADER_MAX_VERTEXES][4];
2024-12-01 15:26:01 +01:00
unsigned hitIndexes[SHADER_MAX_INDEXES];
2023-05-08 19:53:53 +02:00
int numIndexes;
2024-12-01 15:26:01 +01:00
int planetype;
2023-05-08 19:53:53 +02:00
float scale;
float radius;
vec3_t floatColor;
2024-12-01 15:26:01 +01:00
float modulate;
2023-05-08 19:53:53 +02:00
if ( !backEnd.refdef.num_dlights ) {
return;
}
2024-12-01 15:26:01 +01:00
#if idppc_altivec
// There has to be a better way to do this so that floatColor
// and/or modulate are already 16-byte aligned.
floatColorVecPerm = vec_lvsl(0,(float *)floatColor);
modulatePerm = vec_lvsl(0,(float *)&modulate);
modulatePerm = (vector unsigned char)vec_splat((vector unsigned int)modulatePerm,0);
zero = (vector float)vec_splat_s8(0);
#endif
2023-05-08 19:53:53 +02:00
for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) {
dlight_t *dl;
if ( !( tess.dlightBits & ( 1 << l ) ) ) {
2024-12-01 15:26:01 +01:00
continue; // this surface definately doesn't have any of this light
2023-05-08 19:53:53 +02:00
}
texCoords = texCoordsArray[0];
colors = colorArray[0];
dl = &backEnd.refdef.dlights[l];
2024-12-01 15:26:01 +01:00
#if idppc_altivec
origin0 = dl->transformed[0];
origin1 = dl->transformed[1];
origin2 = dl->transformed[2];
#else
2023-05-08 19:53:53 +02:00
VectorCopy( dl->transformed, origin );
2024-12-01 15:26:01 +01:00
#endif
radius = dl->radius * 1.75f;
scale = 1.75f / radius;
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
floatColor[0] = dl->color[0] * 255.0f;
floatColor[1] = dl->color[1] * 255.0f;
floatColor[2] = dl->color[2] * 255.0f;
#if idppc_altivec
floatColorVec0 = vec_ld(0, floatColor);
floatColorVec1 = vec_ld(11, floatColor);
floatColorVec0 = vec_perm(floatColorVec0,floatColorVec0,floatColorVecPerm);
#endif
2023-05-08 19:53:53 +02:00
for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) {
2024-12-01 15:26:01 +01:00
#if idppc_altivec
vec_t dist0, dist1, dist2;
#else
2023-05-08 19:53:53 +02:00
vec3_t dist;
2024-12-01 15:26:01 +01:00
#endif
float vertdist;
int clip;
2023-05-08 19:53:53 +02:00
backEnd.pc.c_dlightVertexes++;
2024-12-01 15:26:01 +01:00
#if idppc_altivec
//VectorSubtract( origin, tess.xyz[i], dist );
dist0 = origin0 - tess.xyz[i][0];
dist1 = origin1 - tess.xyz[i][1];
dist2 = origin2 - tess.xyz[i][2];
texCoords0 = 0.5f + dist0 * scale;
texCoords1 = 0.5f + dist1 * scale;
clip = 0;
if ( texCoords0 < 0.0f ) {
clip |= 1;
} else if ( texCoords0 > 1.0f ) {
clip |= 2;
}
if ( texCoords1 < 0.0f ) {
clip |= 4;
} else if ( texCoords1 > 1.0f ) {
clip |= 8;
}
texCoords[0] = texCoords0;
texCoords[1] = texCoords1;
// modulate the strength based on the height and color
if ( dist2 > radius ) {
clip |= 16;
modulate = 0.0f;
} else if ( dist2 < -radius ) {
clip |= 32;
modulate = 0.0f;
} else {
dist2 = Q_fabs(dist2);
if ( dist2 < radius * 0.5f ) {
modulate = 1.0f;
} else {
modulate = 2.0f * (radius - dist2) * scale;
}
}
clipBits[i] = clip;
modulateVec = vec_ld(0,(float *)&modulate);
modulateVec = vec_perm(modulateVec,modulateVec,modulatePerm);
colorVec = vec_madd(floatColorVec0,modulateVec,zero);
colorInt = vec_cts(colorVec,0); // RGBx
colorShort = vec_pack(colorInt,colorInt); // RGBxRGBx
colorChar = vec_packsu(colorShort,colorShort); // RGBxRGBxRGBxRGBx
colorChar = vec_sel(colorChar,vSel,vSel); // RGBARGBARGBARGBA replace alpha with 255
vec_ste((vector unsigned int)colorChar,0,(unsigned int *)colors); // store color
#else
VectorCopy(tess.normal[i], normal);
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
if (fabs(normal[0]) > fabs(normal[1])) {
if (fabs(normal[2]) < fabs(normal[0])) {
planetype = 0;
} else {
planetype = 1;
}
2023-05-08 19:53:53 +02:00
} else {
2024-12-01 15:26:01 +01:00
if (fabs(normal[2]) < fabs(normal[1])) {
planetype = 1;
2023-05-08 19:53:53 +02:00
}
2024-12-01 15:26:01 +01:00
else {
planetype = 2;
2023-05-08 19:53:53 +02:00
}
2024-12-01 15:26:01 +01:00
}
VectorSubtract( origin, tess.xyz[i], dist );
if (planetype == 0)
{
texCoords[0] = 0.5f + dist[1] * scale;
texCoords[1] = 0.5f + dist[2] * scale;
}
else if (planetype == 1)
{
texCoords[0] = 0.5f + dist[0] * scale;
texCoords[1] = 0.5f + dist[2] * scale;
}
else
{
texCoords[0] = 0.5f + dist[0] * scale;
texCoords[1] = 0.5f + dist[1] * scale;
}
vertdist = DotProduct(dist, normal);
clip = 0;
if ( texCoords[0] < 0.0f ) {
clip |= 1;
} else if ( texCoords[0] > 1.0f ) {
clip |= 2;
}
if ( texCoords[1] < 0.0f ) {
clip |= 4;
} else if ( texCoords[1] > 1.0f ) {
clip |= 8;
}
// modulate the strength based on the height and color
if (vertdist > radius) {
clip |= 16;
modulate = 0.0f;
}
else if (vertdist < -radius) {
clip |= 32;
modulate = 0.0f;
}
else {
dist[2] = Q_fabs(dist[2]);
if (dist[2] < radius * 0.5f) {
modulate = 1.0f;
}
else {
modulate = 2.0f * (radius - dist[2]) * scale;
2023-05-08 19:53:53 +02:00
}
}
clipBits[i] = clip;
2024-12-01 15:26:01 +01:00
colors[0] = myftol(floatColor[0] * modulate);
colors[1] = myftol(floatColor[1] * modulate);
colors[2] = myftol(floatColor[2] * modulate);
2023-05-08 19:53:53 +02:00
colors[3] = 255;
2024-12-01 15:26:01 +01:00
#endif
2023-05-08 19:53:53 +02:00
}
// build a list of triangles that need light
numIndexes = 0;
for ( i = 0 ; i < tess.numIndexes ; i += 3 ) {
int a, b, c;
a = tess.indexes[i];
b = tess.indexes[i+1];
c = tess.indexes[i+2];
if ( clipBits[a] & clipBits[b] & clipBits[c] ) {
continue; // not lighted
}
hitIndexes[numIndexes] = a;
hitIndexes[numIndexes+1] = b;
hitIndexes[numIndexes+2] = c;
numIndexes += 3;
}
if ( !numIndexes ) {
continue;
}
2024-12-01 15:26:01 +01:00
qglEnableClientState(GL_COLOR_ARRAY);
qglEnableClientState(GL_TEXTURE_COORD_ARRAY);
qglColorPointer(4, GL_UNSIGNED_BYTE, 0, colorArray);
qglTexCoordPointer(2, GL_FLOAT, 0, texCoordsArray[0]);
2023-05-08 19:53:53 +02:00
GL_Bind( tr.dlightImage );
// include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light
// where they aren't rendered
2024-12-01 15:26:01 +01:00
if ( dl->type & additive) {
2023-05-08 19:53:53 +02:00
GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
}
else {
GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
}
R_DrawElements( numIndexes, hitIndexes );
backEnd.pc.c_totalIndexes += numIndexes;
backEnd.pc.c_dlightIndexes += numIndexes;
}
}
/*
===============
ComputeColors
===============
*/
static void ComputeColors( shaderStage_t *pStage )
{
int i;
//
// rgbGen
//
switch ( pStage->rgbGen )
{
case CGEN_IDENTITY:
Com_Memset( tess.svars.colors, 0xff, tess.numVertexes * 4 );
break;
default:
case CGEN_IDENTITY_LIGHTING:
Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 );
break;
2024-12-01 15:26:01 +01:00
case CGEN_ENTITY:
RB_CalcColorFromEntity((unsigned char*)tess.svars.colors);
2023-05-08 19:53:53 +02:00
break;
2024-12-01 15:26:01 +01:00
case CGEN_ONE_MINUS_ENTITY:
RB_CalcColorFromOneMinusEntity((unsigned char*)tess.svars.colors);
2023-05-08 19:53:53 +02:00
break;
2024-12-01 15:26:01 +01:00
case CGEN_EXACT_VERTEX:
if (!tess.vertexColorValid) {
static qboolean bWarned = qfalse;
if (!bWarned) {
// mohta and mohtt shows the warning once
bWarned = qtrue;
ri.Printf(
PRINT_WARNING,
"Vertex color specified for shader '%s', but vertex colors are not valid for this model\n",
tess.shader->name
);
}
break;
2023-05-08 19:53:53 +02:00
}
2024-12-01 15:26:01 +01:00
Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) );
2023-05-08 19:53:53 +02:00
break;
case CGEN_VERTEX:
2024-12-01 15:26:01 +01:00
if (!tess.vertexColorValid) {
static qboolean bWarned = qfalse;
if (!bWarned) {
bWarned = qtrue;
ri.Printf(
PRINT_WARNING,
"Vertex color specified for shader '%s', but vertex colors are not valid for this model\n",
tess.shader->name);
}
break;
}
if (tr.identityLight == 1)
2023-05-08 19:53:53 +02:00
{
2024-12-01 15:26:01 +01:00
Com_Memcpy(tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof(tess.vertexColors[0]));
2023-05-08 19:53:53 +02:00
}
else
{
2024-12-01 15:26:01 +01:00
for (i = 0; i < tess.numVertexes; i++)
2023-05-08 19:53:53 +02:00
{
tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight;
tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight;
tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight;
tess.svars.colors[i][3] = tess.vertexColors[i][3];
}
}
break;
case CGEN_ONE_MINUS_VERTEX:
if ( tr.identityLight == 1 )
{
for ( i = 0; i < tess.numVertexes; i++ )
{
tess.svars.colors[i][0] = 255 - tess.vertexColors[i][0];
tess.svars.colors[i][1] = 255 - tess.vertexColors[i][1];
tess.svars.colors[i][2] = 255 - tess.vertexColors[i][2];
}
}
else
{
for ( i = 0; i < tess.numVertexes; i++ )
{
tess.svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight;
tess.svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight;
tess.svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight;
}
}
break;
2024-12-01 15:26:01 +01:00
case CGEN_WAVEFORM:
RB_CalcWaveColor(&pStage->rgbWave, (unsigned char*)tess.svars.colors, NULL);
break;
case CGEN_MULTIPLY_BY_WAVEFORM:
RB_CalcWaveColor(&pStage->rgbWave, (unsigned char*)tess.svars.colors, pStage->colorConst);
break;
case CGEN_LIGHTING_GRID:
RB_CalcLightGridColor((unsigned char*)tess.svars.colors);
break;
case CGEN_LIGHTING_SPHERICAL:
case CGEN_STATIC:
if (!r_drawspherelights->integer) {
break;
}
if (!backEnd.currentStaticModel) {
if (backEnd.currentSphere->TessFunction) {
backEnd.currentSphere->TessFunction((unsigned char*)tess.svars.colors);
}
break;
}
if (!tess.vertexColorValid) {
ri.Printf(PRINT_WARNING, "Vertex color specified for shader '%s', but vertex colors are not valid for this model\n", tess.shader->name);
}
if (backEnd.currentStaticModel->useSpecialLighting)
2023-05-08 19:53:53 +02:00
{
2024-12-01 15:26:01 +01:00
for (i = 0; i < tess.numVertexes; i++)
{
int j;
vec3_t colorout;
int r, g, b;
colorout[0] = tess.vertexColors[i][0];
colorout[1] = tess.vertexColors[i][1];
colorout[2] = tess.vertexColors[i][2];
for (j = 0; j < backEnd.currentStaticModel->numdlights; j++)
{
float ooLightDistSquared;
float dot;
vec3_t diff;
dlight_t* dl;
dl = &backEnd.refdef.dlights[backEnd.currentStaticModel->dlights[j].index];
VectorSubtract(backEnd.currentStaticModel->dlights[j].transformed, tess.xyz[i], diff);
dot = DotProduct(diff, tess.normal[i]);
if (dot >= 0)
{
float ooLen;
ooLen = 1.0 / VectorLengthSquared(diff);
ooLightDistSquared = dot * (7500.0 * dl->radius * ooLen * sqrt(ooLen));
colorout[0] = dl->color[0] * ooLightDistSquared + colorout[0];
colorout[1] = dl->color[1] * ooLightDistSquared + colorout[1];
colorout[2] = dl->color[2] * ooLightDistSquared + colorout[2];
}
}
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
r = colorout[0];
g = colorout[1];
b = colorout[2];
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
if (tr.overbrightShift)
{
r = (int)((float)r * tr.overbrightMult);
g = (int)((float)g * tr.overbrightMult);
b = (int)((float)b * tr.overbrightMult);
}
if (r > 0xFF || g > 0xFF || b > 0xFF)
{
float t;
t = 255.0 / (float)Q_max(r, Q_max(g, b));
r = (int)((float)r * t);
g = (int)((float)g * t);
b = (int)((float)b * t);
}
tess.svars.colors[i][0] = r;
tess.svars.colors[i][1] = g;
tess.svars.colors[i][2] = b;
// Fixed in OPM
// This sets the alpha channel which will prevent vertices from hiding
tess.svars.colors[i][3] = tess.vertexColors[i][3];
}
}
else
{
if (tr.identityLight == 1.0)
{
Com_Memcpy(tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof(tess.vertexColors[0]));
}
else
{
for (i = 0; i < tess.numVertexes; i++) {
tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight;
tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight;
tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight;
}
2023-05-08 19:53:53 +02:00
}
}
break;
2024-12-01 15:26:01 +01:00
case CGEN_CONSTANT:
RB_CalcColorFromConstant((unsigned char*)tess.svars.colors, pStage->colorConst);
2023-05-08 19:53:53 +02:00
break;
2024-12-01 15:26:01 +01:00
case CGEN_GLOBAL_COLOR:
RB_CalcColorFromConstant((unsigned char*)tess.svars.colors, backEnd.color2D);
2023-05-08 19:53:53 +02:00
break;
2024-12-01 15:26:01 +01:00
case CGEN_SCOORD:
RB_CalcRGBFromTexCoords(
(unsigned char*)tess.svars.colors,
pStage->alphaMin,
pStage->alphaMax,
pStage->alphaConstMin,
pStage->alphaConst,
1.0,
0.0,
tess.svars.texcoords[0][0]
);
break;
case CGEN_TCOORD:
RB_CalcRGBFromTexCoords(
(unsigned char*)tess.svars.colors,
pStage->alphaMin,
pStage->alphaMax,
pStage->alphaConstMin,
pStage->alphaConst,
0.0,
1.0,
tess.svars.texcoords[0][0]
);
break;
case CGEN_DOT:
RB_CalcRGBFromDot((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax);
break;
case CGEN_ONE_MINUS_DOT:
RB_CalcRGBFromOneMinusDot((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax);
2023-05-08 19:53:53 +02:00
break;
}
//
// alphaGen
//
switch ( pStage->alphaGen )
{
case AGEN_SKIP:
break;
case AGEN_IDENTITY:
2024-12-01 15:26:01 +01:00
if ( pStage->rgbGen != CGEN_IDENTITY
&& pStage->rgbGen != CGEN_LIGHTING_GRID
&& pStage->rgbGen != CGEN_LIGHTING_SPHERICAL)
{
if ( pStage->rgbGen != CGEN_VERTEX || tr.identityLight != 1 ) {
2023-05-08 19:53:53 +02:00
for ( i = 0; i < tess.numVertexes; i++ ) {
tess.svars.colors[i][3] = 0xff;
}
}
}
break;
case AGEN_ENTITY:
2024-12-01 15:26:01 +01:00
RB_CalcAlphaFromEntity((unsigned char*)tess.svars.colors);
2023-05-08 19:53:53 +02:00
break;
case AGEN_ONE_MINUS_ENTITY:
2024-12-01 15:26:01 +01:00
RB_CalcAlphaFromOneMinusEntity((unsigned char*)tess.svars.colors);
2023-05-08 19:53:53 +02:00
break;
2024-12-01 15:26:01 +01:00
case AGEN_VERTEX:
if (pStage->rgbGen != CGEN_VERTEX) {
for (i = 0; i < tess.numVertexes; i++) {
2023-05-08 19:53:53 +02:00
tess.svars.colors[i][3] = tess.vertexColors[i][3];
}
}
2024-12-01 15:26:01 +01:00
break;
case AGEN_ONE_MINUS_VERTEX:
for (i = 0; i < tess.numVertexes; i++)
{
2023-05-08 19:53:53 +02:00
tess.svars.colors[i][3] = 255 - tess.vertexColors[i][3];
2024-12-01 15:26:01 +01:00
}
break;
case AGEN_LIGHTING_SPECULAR:
RB_CalcSpecularAlpha((unsigned char*)tess.svars.colors, pStage->alphaMax, pStage->specOrigin);
break;
case AGEN_WAVEFORM:
RB_CalcWaveAlpha(&pStage->alphaWave, (unsigned char*)tess.svars.colors);
break;
2023-05-08 19:53:53 +02:00
case AGEN_PORTAL:
{
unsigned char alpha;
for ( i = 0; i < tess.numVertexes; i++ )
{
float len;
vec3_t v;
2024-12-01 15:26:01 +01:00
VectorSubtract( tess.xyz[i], backEnd.viewParms.ori.origin, v );
2023-05-08 19:53:53 +02:00
len = VectorLength( v );
len /= tess.shader->portalRange;
if ( len < 0 )
{
alpha = 0;
}
else if ( len > 1 )
{
alpha = 0xff;
}
else
{
alpha = len * 0xff;
}
tess.svars.colors[i][3] = alpha;
}
}
break;
2024-12-01 15:26:01 +01:00
case AGEN_DOT:
RB_CalcAlphaFromDot((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax);
return;
case AGEN_ONE_MINUS_DOT:
RB_CalcAlphaFromOneMinusDot((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax);
return;
case AGEN_CONSTANT:
RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, pStage->colorConst[3]);
break;
case AGEN_GLOBAL_ALPHA:
RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, backEnd.color2D[3]);
break;
case AGEN_SKYALPHA:
RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, tr.refdef.sky_alpha * 255.0);
break;
case AGEN_ONE_MINUS_SKYALPHA:
RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, (1.0 - tr.refdef.sky_alpha) * 255.0);
break;
case AGEN_SCOORD:
RB_CalcAlphaFromTexCoords(
(unsigned char*)tess.svars.colors,
pStage->alphaMin,
pStage->alphaMax,
pStage->alphaConstMin,
pStage->alphaConst,
1.0,
0.0,
tess.svars.texcoords[0][0]
);
break;
case AGEN_TCOORD:
RB_CalcAlphaFromTexCoords(
(unsigned char*)tess.svars.colors,
pStage->alphaMin,
pStage->alphaMax,
pStage->alphaConstMin,
pStage->alphaConst,
0.0,
1.0,
tess.svars.texcoords[0][0]
);
break;
case AGEN_DIST_FADE:
if (backEnd.currentStaticModel)
2023-05-08 19:53:53 +02:00
{
2024-12-01 15:26:01 +01:00
unsigned char alpha;
for (i = 0; i < tess.numVertexes; i++) {
float len;
vec3_t org, v;
VectorSubtract(backEnd.viewParms.ori.origin, backEnd.currentStaticModel->origin, v);
org[0] = tess.xyz[i][0] - DotProduct(v, backEnd.currentStaticModel->axis[0]);
org[1] = tess.xyz[i][1] - DotProduct(v, backEnd.currentStaticModel->axis[1]);
org[2] = tess.xyz[i][2] - DotProduct(v, backEnd.currentStaticModel->axis[2]);
len = (VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange;
if (len < 0) {
alpha = 0xff;
} else if (len > 1) {
alpha = 0;
} else {
alpha = ((1.0 - len) * 255.0);
}
tess.svars.colors[i][3] = alpha;
}
2023-05-08 19:53:53 +02:00
}
2024-12-01 15:26:01 +01:00
else
{
unsigned char alpha;
for (i = 0; i < tess.numVertexes; i++) {
float len;
vec3_t org, v;
if (backEnd.currentEntity) {
VectorSubtract(backEnd.viewParms.ori.origin, backEnd.currentEntity->e.origin, v);
org[0] = tess.xyz[i][0] - DotProduct(v, backEnd.currentEntity->e.axis[0]);
org[1] = tess.xyz[i][1] - DotProduct(v, backEnd.currentEntity->e.axis[1]);
org[2] = tess.xyz[i][2] - DotProduct(v, backEnd.currentEntity->e.axis[2]);
} else {
VectorCopy(backEnd.viewParms.ori.origin, org);
}
len = (VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange;
if (len < 0) {
alpha = 0xff;
} else if (len > 1) {
alpha = 0;
} else {
alpha = ((1.0 - len) * 255.0);
}
tess.svars.colors[i][3] = alpha;
}
}
break;
case AGEN_ONE_MINUS_DIST_FADE:
if (backEnd.currentStaticModel)
2023-05-08 19:53:53 +02:00
{
2024-12-01 15:26:01 +01:00
unsigned char alpha;
for (i = 0; i < tess.numVertexes; i++) {
float len;
vec3_t org, v;
VectorSubtract(backEnd.viewParms.ori.origin, backEnd.currentStaticModel->origin, v);
org[0] = tess.xyz[i][0] - DotProduct(v, backEnd.currentStaticModel->axis[0]);
org[1] = tess.xyz[i][1] - DotProduct(v, backEnd.currentStaticModel->axis[1]);
org[2] = tess.xyz[i][2] - DotProduct(v, backEnd.currentStaticModel->axis[2]);
len = (VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange;
if (len < 0) {
alpha = 0;
} else if (len > 1) {
alpha = 0xff;
} else {
alpha = (len * 255.0);
}
tess.svars.colors[i][3] = alpha;
}
2023-05-08 19:53:53 +02:00
}
2024-12-01 15:26:01 +01:00
else
{
unsigned char alpha;
for (i = 0; i < tess.numVertexes; i++) {
float len;
vec3_t org, v;
if (backEnd.currentEntity) {
VectorSubtract(backEnd.viewParms.ori.origin, backEnd.currentEntity->e.origin, v);
org[0] = tess.xyz[i][0] - DotProduct(v, backEnd.currentEntity->e.axis[0]);
org[1] = tess.xyz[i][1] - DotProduct(v, backEnd.currentEntity->e.axis[1]);
org[2] = tess.xyz[i][2] - DotProduct(v, backEnd.currentEntity->e.axis[2]);
} else {
VectorCopy(backEnd.viewParms.ori.origin, org);
}
len = (VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange;
if (len < 0) {
alpha = 0;
} else if (len > 1) {
alpha = 0xff;
} else {
alpha = (len * 255.0);
}
tess.svars.colors[i][3] = alpha;
}
}
break;
case AGEN_TIKI_DIST_FADE:
case AGEN_ONE_MINUS_TIKI_DIST_FADE:
2023-05-08 19:53:53 +02:00
{
2024-12-01 15:26:01 +01:00
unsigned char alpha;
if (backEnd.currentStaticModel)
{
float lenSqr;
float fDistNearSqr;
float fDistFarSqr;
vec3_t org;
VectorSubtract(backEnd.currentStaticModel->origin, backEnd.viewParms.ori.origin, org);
lenSqr = VectorLengthSquared(org);
fDistNearSqr = tess.shader->fDistNear * tess.shader->fDistNear;
fDistFarSqr = (tess.shader->fDistNear + tess.shader->fDistRange) * (tess.shader->fDistNear + tess.shader->fDistRange);
if (lenSqr <= fDistNearSqr) {
alpha = 0;
} else if (lenSqr >= fDistFarSqr) {
alpha = 0xff;
} else {
float len;
len = VectorLength(org);
alpha = (((VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange) * 255.0);
}
if (pStage->alphaGen == AGEN_TIKI_DIST_FADE) {
alpha = 0xff - alpha;
}
}
else if (backEnd.currentEntity)
{
float lenSqr;
float fDistNearSqr;
float fDistFarSqr;
vec3_t org;
VectorSubtract(backEnd.currentEntity->e.origin, backEnd.viewParms.ori.origin, org);
lenSqr = VectorLengthSquared(org);
fDistNearSqr = tess.shader->fDistNear * tess.shader->fDistNear;
fDistFarSqr = (tess.shader->fDistNear + tess.shader->fDistRange) * (tess.shader->fDistNear + tess.shader->fDistRange);
if (lenSqr <= fDistNearSqr) {
alpha = 0;
} else if (lenSqr >= fDistFarSqr) {
alpha = 0xff;
} else {
float len;
len = VectorLength(org);
alpha = (((VectorLength(org) - tess.shader->fDistNear) / tess.shader->fDistRange) * 255.0);
}
if (pStage->alphaGen == AGEN_TIKI_DIST_FADE) {
alpha = 0xff - alpha;
}
}
else
{
const char* type;
if (pStage->alphaGen == AGEN_TIKI_DIST_FADE) {
type = "tikiDistFade";
} else {
type = "oneMinusTikiDistFade";
}
ri.Error(ERR_DROP, "ERROR: '%s' shader command used on a non-tiki\n", type);
alpha = 0xff;
}
RB_CalcAlphaFromConstant((unsigned char*)tess.svars.colors, alpha);
2023-05-08 19:53:53 +02:00
}
2024-12-01 15:26:01 +01:00
break;
case AGEN_DOT_VIEW:
RB_CalcAlphaFromDotView((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax);
break;
case AGEN_ONE_MINUS_DOT_VIEW:
RB_CalcAlphaFromOneMinusDotView((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax);
break;
case AGEN_HEIGHT_FADE:
RB_CalcAlphaFromHeightFade((unsigned char*)tess.svars.colors, pStage->alphaMin, pStage->alphaMax);
break;
default:
break;
2023-05-08 19:53:53 +02:00
}
}
/*
===============
ComputeTexCoords
===============
*/
static void ComputeTexCoords( shaderStage_t *pStage ) {
int i;
int b;
2024-12-01 15:26:01 +01:00
for ( b = 0; b < NUM_TEXTURE_BUNDLES && pStage->bundle[b].image[0]; b++ ) {
2023-05-08 19:53:53 +02:00
int tm;
//
// generate the texture coordinates
//
switch ( pStage->bundle[b].tcGen )
{
case TCGEN_IDENTITY:
Com_Memset( tess.svars.texcoords[b], 0, sizeof( float ) * 2 * tess.numVertexes );
break;
case TCGEN_TEXTURE:
for ( i = 0 ; i < tess.numVertexes ; i++ ) {
tess.svars.texcoords[b][i][0] = tess.texCoords[i][0][0];
tess.svars.texcoords[b][i][1] = tess.texCoords[i][0][1];
}
break;
case TCGEN_LIGHTMAP:
for ( i = 0 ; i < tess.numVertexes ; i++ ) {
tess.svars.texcoords[b][i][0] = tess.texCoords[i][1][0];
tess.svars.texcoords[b][i][1] = tess.texCoords[i][1][1];
}
break;
2024-12-01 15:26:01 +01:00
case TCGEN_ENVIRONMENT_MAPPED:
RB_CalcEnvironmentTexCoords( ( float * ) tess.svars.texcoords[b] );
break;
2023-05-08 19:53:53 +02:00
case TCGEN_VECTOR:
2024-12-01 15:26:01 +01:00
for (i = 0; i < tess.numVertexes; i++) {
tess.svars.texcoords[b][i][0] = DotProduct(tess.xyz[i], pStage->bundle[b].tcGenVectors[0]);
tess.svars.texcoords[b][i][1] = DotProduct(tess.xyz[i], pStage->bundle[b].tcGenVectors[1]);
2023-05-08 19:53:53 +02:00
}
break;
2024-12-01 15:26:01 +01:00
case TCGEN_ENVIRONMENT_MAPPED2:
RB_CalcEnvironmentTexCoords2((float*)tess.svars.texcoords[b]);
break;
case TCGEN_SUN_REFLECTION:
RB_CalcSunReflectionTexCoords(tess.svars.texcoords[b][0]);
2023-05-08 19:53:53 +02:00
break;
2024-12-01 15:26:01 +01:00
default:
ri.Printf(
PRINT_DEVELOPER,
"WARNING: invalid tcGen '%d' specified for shader '%s'\n",
pStage->bundle[b].tcGen,
tess.shader->name
);
2023-05-08 19:53:53 +02:00
break;
}
//
// alter texture coordinates
//
for ( tm = 0; tm < pStage->bundle[b].numTexMods ; tm++ ) {
switch ( pStage->bundle[b].texMods[tm].type )
{
case TMOD_NONE:
tm = TR_MAX_TEXMODS; // break out of for loop
break;
case TMOD_TURBULENT:
RB_CalcTurbulentTexCoords( &pStage->bundle[b].texMods[tm].wave,
( float * ) tess.svars.texcoords[b] );
break;
case TMOD_ENTITY_TRANSLATE:
RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord,
( float * ) tess.svars.texcoords[b] );
break;
case TMOD_SCROLL:
RB_CalcScrollTexCoords( pStage->bundle[b].texMods[tm].scroll,
( float * ) tess.svars.texcoords[b] );
break;
case TMOD_SCALE:
RB_CalcScaleTexCoords( pStage->bundle[b].texMods[tm].scale,
( float * ) tess.svars.texcoords[b] );
break;
case TMOD_STRETCH:
RB_CalcStretchTexCoords( &pStage->bundle[b].texMods[tm].wave,
( float * ) tess.svars.texcoords[b] );
break;
case TMOD_TRANSFORM:
RB_CalcTransformTexCoords( &pStage->bundle[b].texMods[tm],
( float * ) tess.svars.texcoords[b] );
break;
case TMOD_ROTATE:
RB_CalcRotateTexCoords( pStage->bundle[b].texMods[tm].rotateSpeed,
2024-12-01 15:26:01 +01:00
pStage->bundle[b].texMods[tm].rotateCoef,
( float * ) tess.svars.texcoords[b],
pStage->bundle[b].texMods[tm].rotateStart );
break;
case TMOD_OFFSET:
RB_CalcOffsetTexCoords(pStage->bundle[b].texMods[tm].scroll, tess.svars.texcoords[b][0]);
2023-05-08 19:53:53 +02:00
break;
2024-12-01 15:26:01 +01:00
case TMOD_PARALLAX:
RB_CalcParallaxTexCoords(pStage->bundle[b].texMods[tm].rate, tess.svars.texcoords[b][0]);
break;
case TMOD_MACRO:
RB_CalcMacroTexCoords(pStage->bundle[b].texMods[tm].scale, tess.svars.texcoords[b][0]);
break;
case TMOD_WAVETRANS:
RB_CalcTransWaveTexCoords(&pStage->bundle[b].texMods[tm].wave, tess.svars.texcoords[b][0]);
break;
case TMOD_WAVETRANT:
RB_CalcTransWaveTexCoordsT(&pStage->bundle[b].texMods[tm].wave, tess.svars.texcoords[b][0]);
break;
case TMOD_BULGETRANS:
RB_CalcBulgeTexCoords(&pStage->bundle[b].texMods[tm].wave, tess.svars.texcoords[b][0]);
break;
2023-05-08 19:53:53 +02:00
default:
2024-12-01 15:26:01 +01:00
ri.Printf(PRINT_WARNING, "WARNING: invalid tcMod '%d' specified for shader '%s'\n", pStage->bundle[b].texMods[tm].type, tess.shader->name );
2023-05-08 19:53:53 +02:00
break;
}
}
}
}
/*
** RB_IterateStagesGeneric
*/
static void RB_IterateStagesGeneric( shaderCommands_t *input )
{
int stage;
2024-12-01 15:26:01 +01:00
for (stage = 0; stage < MAX_SHADER_STAGES; stage++)
2023-05-08 19:53:53 +02:00
{
2024-12-01 15:26:01 +01:00
shaderStage_t* pStage = tess.xstages[stage];
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
if (!pStage)
2023-05-08 19:53:53 +02:00
{
break;
}
2024-12-01 15:26:01 +01:00
if (pStage->alphaGen == AGEN_SKYALPHA && tr.refdef.sky_alpha < 0.01) {
continue;
}
else if (pStage->alphaGen == AGEN_ONE_MINUS_SKYALPHA && tr.refdef.sky_alpha > 0.99) {
continue;
}
ComputeTexCoords(pStage);
ComputeColors(pStage);
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
if (!setArraysOnce)
2023-05-08 19:53:53 +02:00
{
2024-12-01 15:26:01 +01:00
qglEnableClientState(GL_COLOR_ARRAY);
qglColorPointer(4, GL_UNSIGNED_BYTE, 0, input->svars.colors);
2023-05-08 19:53:53 +02:00
}
//
// do multitexture
//
2024-12-01 15:26:01 +01:00
if (pStage->bundle[1].image[0] != 0)
2023-05-08 19:53:53 +02:00
{
2024-12-01 15:26:01 +01:00
DrawMultitextured(input, stage);
2023-05-08 19:53:53 +02:00
}
else
{
2024-12-01 15:26:01 +01:00
if (backEnd.in2D) {
GL_State(pStage->stateBits | GLS_DEPTHTEST_DISABLE);
}
else {
GL_State(pStage->stateBits);
}
if (!setArraysOnce)
2023-05-08 19:53:53 +02:00
{
2024-12-01 15:26:01 +01:00
qglTexCoordPointer(2, GL_FLOAT, 0, input->svars.texcoords[0]);
2023-05-08 19:53:53 +02:00
}
//
// set state
//
2024-12-01 15:26:01 +01:00
if (pStage->bundle[0].vertexLightmap && r_vertexLight->integer && r_lightmap->integer)
{
GL_Bind(tr.whiteImage);
}
else {
if (input->dlightMap && tess.xstages[stage]->bundle[0].isLightmap) {
GL_Bind(tr.dlightImages[input->dlightMap - 1]);
}
else {
R_BindAnimatedImage(&pStage->bundle[0]);
}
}
2023-05-08 19:53:53 +02:00
//
// draw
//
2024-12-01 15:26:01 +01:00
R_DrawElements(input->numIndexes, input->indexes);
2023-05-08 19:53:53 +02:00
}
// allow skipping out to show just lightmaps during development
2024-12-01 15:26:01 +01:00
if (r_lightmap->integer && (pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap))
2023-05-08 19:53:53 +02:00
{
break;
}
}
}
/*
** RB_StageIteratorGeneric
*/
void RB_StageIteratorGeneric( void )
{
shaderCommands_t *input;
input = &tess;
2024-12-01 15:26:01 +01:00
input->shaderTime = backEnd.refdef.floatTime;
if ((input->shader->flags & 1) != 0) {
input->shaderTime = backEnd.refdef.floatTime - backEnd.shaderStartTime;
}
2023-05-08 19:53:53 +02:00
RB_DeformTessGeometry();
//
// log this call
//
if ( r_logFile->integer )
{
// don't just call LogComment, or we will get
// a call to va() every frame!
GLimp_LogComment( va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) );
}
//
// set face culling appropriately
//
2024-12-01 15:26:01 +01:00
GL_Cull( input->shader->cullType );
2023-05-08 19:53:53 +02:00
// set polygon offset if necessary
2024-12-01 15:26:01 +01:00
if ( input->shader->polygonOffset )
2023-05-08 19:53:53 +02:00
{
qglEnable( GL_POLYGON_OFFSET_FILL );
qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
}
//
// if there is only a single pass then we can enable color
// and texture arrays before we compile, otherwise we need
// to avoid compiling those arrays since they will change
// during multipass rendering
//
2024-12-01 15:26:01 +01:00
if (tess.numPasses > 1 || (tess.xstages[0] && tess.xstages[0]->multitextureEnv))
2023-05-08 19:53:53 +02:00
{
setArraysOnce = qfalse;
2024-12-01 15:26:01 +01:00
qglDisableClientState(GL_COLOR_ARRAY);
qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
2023-05-08 19:53:53 +02:00
}
else
{
setArraysOnce = qtrue;
2024-12-01 15:26:01 +01:00
qglEnableClientState(GL_COLOR_ARRAY);
qglColorPointer(4, GL_UNSIGNED_BYTE, 0, tess.svars.colors);
2023-05-08 19:53:53 +02:00
2024-12-01 15:26:01 +01:00
qglEnableClientState(GL_TEXTURE_COORD_ARRAY);
qglTexCoordPointer(2, GL_FLOAT, 0, tess.svars.texcoords[0]);
2023-05-08 19:53:53 +02:00
}
//
// lock XYZ
//
qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD
if (qglLockArraysEXT)
{
qglLockArraysEXT(0, input->numVertexes);
GLimp_LogComment( "glLockArraysEXT\n" );
}
//
// enable color and texcoord arrays after the lock if necessary
//
if ( !setArraysOnce )
{
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
qglEnableClientState( GL_COLOR_ARRAY );
}
//
// call shader function
//
RB_IterateStagesGeneric( input );
//
// now do any dynamic lighting needed
//
2024-12-01 15:26:01 +01:00
if (tess.dlightBits && !tess.dlightMap && tess.shader->sort <= SS_OPAQUE) {
2023-05-08 19:53:53 +02:00
ProjectDlightTexture();
}
//
// unlock arrays
//
if (qglUnlockArraysEXT)
{
qglUnlockArraysEXT();
GLimp_LogComment( "glUnlockArraysEXT\n" );
}
//
// reset polygon offset
//
2024-12-01 15:26:01 +01:00
if ( input->shader->polygonOffset )
2023-05-08 19:53:53 +02:00
{
qglDisable( GL_POLYGON_OFFSET_FILL );
}
}
/*
** RB_StageIteratorVertexLitTexture
*/
2024-12-01 15:26:01 +01:00
void RB_StageIteratorVertexLitTextureUnfogged( void )
2023-05-08 19:53:53 +02:00
{
shaderCommands_t *input;
shader_t *shader;
input = &tess;
2024-12-01 15:26:01 +01:00
2023-05-08 19:53:53 +02:00
shader = input->shader;
2024-12-01 15:26:01 +01:00
if (backEnd.currentSphere->TessFunction && r_drawspherelights->integer)
{
//
// compute colors using the sphere function
//
backEnd.currentSphere->TessFunction((unsigned char*)tess.svars.colors);
}
else if (backEnd.currentStaticModel)
{
//
// compute colors
//
ComputeColors(input->xstages[0]);
}
else
{
//
// calculate using previously calculated lighting grid
// for the entity
//
RB_CalcLightGridColor((unsigned char*)tess.svars.colors);
}
2023-05-08 19:53:53 +02:00
//
// log this call
//
if ( r_logFile->integer )
{
// don't just call LogComment, or we will get
// a call to va() every frame!
GLimp_LogComment( va("--- RB_StageIteratorVertexLitTexturedUnfogged( %s ) ---\n", tess.shader->name) );
}
//
// set face culling appropriately
//
2024-12-01 15:26:01 +01:00
GL_Cull( input->shader->cullType );
2023-05-08 19:53:53 +02:00
//
// set arrays and lock
//
qglEnableClientState( GL_COLOR_ARRAY);
qglEnableClientState( GL_TEXTURE_COORD_ARRAY);
qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors );
qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] );
qglVertexPointer (3, GL_FLOAT, 16, input->xyz);
if ( qglLockArraysEXT )
{
qglLockArraysEXT(0, input->numVertexes);
GLimp_LogComment( "glLockArraysEXT\n" );
}
//
// call special shade routine
//
R_BindAnimatedImage( &tess.xstages[0]->bundle[0] );
GL_State( tess.xstages[0]->stateBits );
R_DrawElements( input->numIndexes, input->indexes );
//
// now do any dynamic lighting needed
//
2024-12-01 15:26:01 +01:00
if ( tess.dlightBits && !tess.dlightMap && tess.shader->sort <= SS_OPAQUE ) {
2023-05-08 19:53:53 +02:00
ProjectDlightTexture();
}
//
// unlock arrays
//
if (qglUnlockArraysEXT)
{
qglUnlockArraysEXT();
GLimp_LogComment( "glUnlockArraysEXT\n" );
}
}
//define REPLACE_MODE
2024-12-01 15:26:01 +01:00
void RB_StageIteratorLightmappedMultitextureUnfogged( void ) {
2023-05-08 19:53:53 +02:00
shaderCommands_t *input;
input = &tess;
//
// log this call
//
if ( r_logFile->integer ) {
// don't just call LogComment, or we will get
// a call to va() every frame!
GLimp_LogComment( va("--- RB_StageIteratorLightmappedMultitexture( %s ) ---\n", tess.shader->name) );
}
//
// set face culling appropriately
//
2024-12-01 15:26:01 +01:00
GL_Cull( input->shader->cullType );
2023-05-08 19:53:53 +02:00
//
// set color, pointers, and lock
//
2024-12-01 15:26:01 +01:00
GL_State( GLS_DEFAULT | (tess.xstages[0]->stateBits & (GLS_ATEST_BITS | GLS_FOG_BITS)));
2023-05-08 19:53:53 +02:00
qglVertexPointer( 3, GL_FLOAT, 16, input->xyz );
#ifdef REPLACE_MODE
qglDisableClientState( GL_COLOR_ARRAY );
qglColor3f( 1, 1, 1 );
qglShadeModel( GL_FLAT );
#else
qglEnableClientState( GL_COLOR_ARRAY );
qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.constantColor255 );
#endif
//
// select base stage
//
GL_SelectTexture( 0 );
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
R_BindAnimatedImage( &tess.xstages[0]->bundle[0] );
qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] );
//
// configure second stage
//
GL_SelectTexture( 1 );
qglEnable( GL_TEXTURE_2D );
if ( r_lightmap->integer ) {
GL_TexEnv( GL_REPLACE );
} else {
GL_TexEnv( GL_MODULATE );
}
2024-12-01 15:26:01 +01:00
if (tess.dlightMap && tess.xstages[0]->bundle[1].isLightmap) {
GL_Bind(tr.dlightImages[tess.dlightMap - 1]);
} else {
R_BindAnimatedImage(&tess.xstages[0]->bundle[1]);
}
2023-05-08 19:53:53 +02:00
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][1] );
//
// lock arrays
//
if ( qglLockArraysEXT ) {
qglLockArraysEXT(0, input->numVertexes);
GLimp_LogComment( "glLockArraysEXT\n" );
}
R_DrawElements( input->numIndexes, input->indexes );
//
// disable texturing on TEXTURE1, then select TEXTURE0
//
qglDisable( GL_TEXTURE_2D );
qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
GL_SelectTexture( 0 );
#ifdef REPLACE_MODE
GL_TexEnv( GL_MODULATE );
qglShadeModel( GL_SMOOTH );
#endif
//
// now do any dynamic lighting needed
//
2024-12-01 15:26:01 +01:00
if ( tess.dlightBits && !tess.dlightMap && tess.shader->sort <= SS_OPAQUE ) {
2023-05-08 19:53:53 +02:00
ProjectDlightTexture();
}
//
// unlock arrays
//
if ( qglUnlockArraysEXT ) {
qglUnlockArraysEXT();
GLimp_LogComment( "glUnlockArraysEXT\n" );
}
}
/*
** RB_EndSurface
*/
void RB_EndSurface( void ) {
shaderCommands_t *input;
input = &tess;
if (input->numIndexes == 0) {
return;
}
if (input->indexes[SHADER_MAX_INDEXES-1] != 0) {
ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit");
}
if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) {
ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit");
}
2024-12-01 15:26:01 +01:00
if (tess.xyz[SHADER_MAX_VERTEXES - 1][0] && r_shadows->integer != 3) {
tess.xyz[SHADER_MAX_VERTEXES - 1][0] = 0;
}
2023-05-08 19:53:53 +02:00
if ( tess.shader == tr.shadowShader ) {
2024-12-01 15:26:01 +01:00
RB_ComputeShadowVolume();
2023-05-08 19:53:53 +02:00
return;
}
// for debugging of sort order issues, stop rendering after a given sort value
if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) {
return;
}
//
// update performance counters
//
backEnd.pc.c_shaders++;
backEnd.pc.c_vertexes += tess.numVertexes;
backEnd.pc.c_indexes += tess.numIndexes;
backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses;
//
// call off to shader specific tess end function
//
tess.currentStageIteratorFunc();
2024-12-01 15:26:01 +01:00
if (!(tr.refdef.rdflags & RDF_NOWORLDMODEL) && !backEnd.in2D)
{
//
// draw debugging stuff
//
if (r_showtris->integer && r_developer->integer) {
2024-12-01 15:26:01 +01:00
DrawTris(input, r_showtris->integer);
}
if (r_shownormals->integer && r_developer->integer) {
2024-12-01 15:26:01 +01:00
DrawNormals(input);
}
2023-05-08 19:53:53 +02:00
}
2024-12-01 15:26:01 +01:00
2023-05-08 19:53:53 +02:00
// clear shader so we can tell we don't have any unclosed surfaces
tess.numIndexes = 0;
GLimp_LogComment( "----------\n" );
}