You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1037 lines
30 KiB
1037 lines
30 KiB
// |
|
// Bump Mapping in Half-Life |
|
// |
|
// Original code copyright (C) Francis "DeathWish" Woodhouse, 2004. |
|
// |
|
// You are free to use this code in your own projects, so long as |
|
// (a) The original author of this code is credited and |
|
// (b) This header remains intact on both of the main source files |
|
// |
|
// I'd be interested to know if you do decide to use this in something, so I'd |
|
// appreciate it if you drop me a line at deathwish@valve-erc.com if you do. |
|
// |
|
|
|
#include <windows.h> |
|
#include <gl/gl.h> |
|
#include <gl/glu.h> |
|
#include <gl/glaux.h> |
|
#include <gl/glext.h> |
|
#include <cg/cg.h> |
|
#include <cg/cgGL.h> |
|
#include "hud.h" |
|
#include "cl_util.h" |
|
#include "cvardef.h" |
|
#include "r_studioint.h" |
|
#include "com_model.h" |
|
#include "bumpmap.h" |
|
|
|
extern globalvars_t* gpGlobals; |
|
|
|
// |
|
// CVARs AVAILABLE |
|
// |
|
// bm_enable 1/0 - enable/disable bumpmapping altogether |
|
// bm_allflat 1/0 - don't use the supplied bumpmaps; render everything as if the surface is flat |
|
// |
|
|
|
#define MAX_STYLE_LEN 100 |
|
#define NUM_STYLES 12 |
|
|
|
// every 10th of a second. 'z' is max light, 'a' is darkness |
|
char g_LightStyles[NUM_STYLES][MAX_STYLE_LEN+1] = |
|
{ |
|
"z", |
|
"mmnmmommommnonmmonqnmmo", |
|
"abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba", |
|
"mmmmmaaaaammmmmaaaaaabcdefgabcdefg", |
|
"mamamamamama", |
|
"jklmnopqrstuvwxyzyxwvutsrqponmlkj", |
|
"nmonqnmomnmomomno", |
|
"mmmaaaabcdefgmmmmaaaammmaamm", |
|
"mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa", |
|
"aaaaaaaazzzzzzzz", |
|
"mmamammmmammamamaaamammma", |
|
"abcdefghijklmnopqrrqponmlkjihgfedcba" |
|
}; |
|
|
|
// we have to do this to stop the linker complaining, since we're using the evil GLaux |
|
extern "C" long _ftol( double ); //defined by VC6 C libs |
|
extern "C" long _ftol2( double dblSource ) { return _ftol( dblSource ); } |
|
|
|
#define M_PI 3.141592653589793238 |
|
|
|
#define DEG2RAD(d) ((d) * (M_PI / 180)) |
|
|
|
inline float Clamp(float f, float low, float high) |
|
{ |
|
if (f < low) |
|
f = low; |
|
if (f > high) |
|
f = high; |
|
return f; |
|
} |
|
|
|
inline Vector RotateX(Vector V, float angle) // angle in degrees |
|
{ |
|
Vector ret; |
|
|
|
angle = DEG2RAD(angle); |
|
|
|
ret.x = V.x; |
|
ret.y = V.y*cos(angle) + V.z*sin(angle); |
|
ret.z = V.z*cos(angle) - V.y*sin(angle); |
|
|
|
return ret; |
|
} |
|
|
|
inline Vector RotateY(Vector V, float angle) // angle in degrees |
|
{ |
|
Vector ret; |
|
|
|
angle = DEG2RAD(angle); |
|
|
|
ret.x = V.x*cos(angle) - V.z*sin(angle); |
|
ret.y = V.y; |
|
ret.z = V.z*cos(angle) + V.x*sin(angle); |
|
|
|
return ret; |
|
} |
|
|
|
inline Vector RotateZ(Vector V, float angle) // angle in degrees |
|
{ |
|
Vector ret; |
|
|
|
angle = DEG2RAD(angle); |
|
|
|
ret.x = V.x*cos(angle) + V.y*sin(angle); |
|
ret.y = V.y*cos(angle) - V.x*sin(angle); |
|
ret.z = V.z; |
|
|
|
return ret; |
|
} |
|
|
|
inline Vector vecNCMVector(int i, int x, int y) |
|
{ |
|
float s = ((float)x + 0.5) / BUMP_NORM_CUBE_MAP_SIZE; |
|
float t = ((float)y + 0.5) / BUMP_NORM_CUBE_MAP_SIZE; |
|
float sc = s*2.0 - 1.0; |
|
float tc = t*2.0 - 1.0; |
|
Vector V; |
|
|
|
switch (i) |
|
{ |
|
case 0: |
|
V.x = 1.0; |
|
V.y = -tc; |
|
V.z = -sc; |
|
break; |
|
case 1: |
|
V.x = -1.0; |
|
V.y = -tc; |
|
V.z = sc; |
|
break; |
|
case 2: |
|
V.x = sc; |
|
V.y = 1.0; |
|
V.z = tc; |
|
break; |
|
case 3: |
|
V.x = sc; |
|
V.y = -1.0; |
|
V.z = -tc; |
|
break; |
|
case 4: |
|
V.x = sc; |
|
V.y = -tc; |
|
V.z = 1.0; |
|
break; |
|
case 5: |
|
V.x = -sc; |
|
V.y = -tc; |
|
V.z = -1.0; |
|
break; |
|
} |
|
|
|
V = V.Normalize(); |
|
|
|
return V; |
|
} |
|
|
|
// Global instance of the bumpmap manager |
|
CBumpmapMgr g_BumpmapMgr; |
|
|
|
// OpenGL multitexture functions |
|
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB; |
|
PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB; |
|
PFNGLMULTITEXCOORD3FVARBPROC glMultiTexCoord3fvARB; |
|
|
|
// OpenGL 3D texture functions |
|
PFNGLTEXIMAGE3DEXTPROC glTexImage3DEXT; |
|
|
|
bool CBumpmapMgr::Initialise(void) |
|
{ |
|
if (m_bInitialised) |
|
Shutdown(); |
|
|
|
gEngfuncs.pfnRegisterVariable("bm_enable", "0", 0); |
|
gEngfuncs.pfnRegisterVariable("bm_allflat", "0", 0); |
|
|
|
m_bFailedInit = true; |
|
|
|
if (!IsHardwareCapable()) |
|
return false; |
|
|
|
if (!InitialiseOGLExtensions()) |
|
return false; |
|
|
|
if (!CreateSceneTextures()) |
|
return false; |
|
|
|
if (!LoadBumpTextures()) |
|
return false; |
|
|
|
if (!InitialiseCg()) |
|
return false; |
|
|
|
if (!LoadFragmentPrograms()) |
|
return false; |
|
|
|
m_bInitialised = true; |
|
m_bFailedInit = false; |
|
|
|
return true; |
|
} |
|
|
|
bool CBumpmapMgr::IsHardwareCapable(void) |
|
{ |
|
// Minimum hardware is a GeForce 3 or a Radeon 9500. I don't see the point in making bumpmapping run |
|
// on GeForce 2 or lower - the cards don't have a fast enough fillrate and much texture memory anyway. |
|
|
|
// grab the string saying which OpenGL extensions are available on this graphics card |
|
std::string sExtensions; |
|
sExtensions.assign((const char*)glGetString(GL_EXTENSIONS), strlen((const char*)glGetString(GL_EXTENSIONS))); |
|
|
|
// check for GL_ARB_multitexture (why the hell are they trying to run this on a Voodoo 2, anyway?) |
|
if (sExtensions.find("GL_ARB_multitexture") == -1) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_ARB_multitexture.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// check for GL_ARB_texture_cube_map |
|
if (sExtensions.find("GL_ARB_texture_cube_map") == -1) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_ARB_texture_cube_map.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// check for GL_NV_texture_rectangle or GL_EXT_texture_rectangle |
|
if (sExtensions.find("GL_NV_texture_rectangle") == -1 && sExtensions.find("GL_EXT_texture_rectangle") == -1) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_NV_texture_rectangle or GL_EXT_texture_rectangle.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// check for GL_EXT_texture3D |
|
if (sExtensions.find("GL_EXT_texture3D") == -1) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_EXT_texture3D.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// see if we have the requisite number of texture units |
|
int iTexUnits; |
|
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &iTexUnits); |
|
if (iTexUnits < 4) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Your hardware does not have four texture units.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// Check if fragment programs can be run (this'll cull the largest proportion of video cards) |
|
// FP20 = GL_NV_register_combiners and GL_NV_texture_shader |
|
// FP30 = GL_NV_fragment_program |
|
// ARBFP1 = GL_ARB_fragment_program |
|
// There's no GL_ATI_fragment_shader support because Cg doesn't include support for compiling to that target. |
|
// You could write a straight shader in shader ASM, but it isn't really worth it for the few extra cards it'd add |
|
// support for. |
|
if (!cgGLIsProfileSupported(CG_PROFILE_FP20) && !cgGLIsProfileSupported(CG_PROFILE_FP30) && !cgGLIsProfileSupported(CG_PROFILE_ARBFP1)) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_NV_register_combiners and GL_NV_texture_shader, GL_NV_fragment_program or GL_ARB_fragment_program.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CBumpmapMgr::CreateSceneTextures(void) |
|
{ |
|
// create the DIFFUSE scene texture |
|
m_uiDiffuseScene = m_uiNextTexIdx++; |
|
|
|
// create the LIGHTMAPPED scene texture |
|
m_uiLightmapScene = m_uiNextTexIdx++; |
|
|
|
// create the BUMPMAPPED scene texture |
|
m_uiBumpScene = m_uiNextTexIdx++; |
|
|
|
// create the NORMALISATION CUBE MAP |
|
MakeNormCubeMap(&m_uiNormCubeMap); |
|
|
|
// create the ATTENUATION MAP |
|
MakeAttenuationMap(&m_uiAttenuationMap); |
|
|
|
// create the NORMAL MAP for FLAT SURFACES |
|
MakeFlatNormalMap(&m_uiFlatSurfaceNorm); |
|
|
|
return true; |
|
} |
|
|
|
bool CBumpmapMgr::LoadBumpTextures(void) |
|
{ |
|
char szFile[256]; |
|
char szTex[256]; |
|
char* pBumpFile; |
|
|
|
std::string sMap(gEngfuncs.pfnGetLevelName()); |
|
|
|
sprintf(szFile, "%s/%s_bump.txt", gEngfuncs.pfnGetGameDirectory(), sMap.substr(0, sMap.rfind('.')).c_str()); |
|
|
|
FILE* pFile = fopen(szFile, "r"); |
|
if (!pFile) |
|
{ |
|
// If we can't find the file, just use the flat bumpmap for everything in this map. |
|
return true; |
|
} |
|
|
|
model_t* pWorld = gEngfuncs.GetEntityByIndex(0)->model; // worldspawn model |
|
unsigned int uiTex; |
|
char szTexPath[256]; |
|
|
|
memset(szTex, 0, 256); |
|
|
|
// go through each line of the file |
|
while (fgets(szTex, 256, pFile)) |
|
{ |
|
if (szTex[strlen(szTex)-1] == '\n') |
|
szTex[strlen(szTex)-1] = '\0'; |
|
|
|
// set pBumpFile to point to the string after the space, and set the space to a null so that szTex only |
|
// references the string before the space |
|
pBumpFile = strchr(szTex, ' '); |
|
*pBumpFile = '\0'; |
|
pBumpFile++; |
|
|
|
// go through each texinfo in the map |
|
for (int i = 0; i < pWorld->numtexinfo; i++) |
|
{ |
|
// we found the texture |
|
if (!_stricmp(pWorld->texinfo[i].texture->name, szTex)) |
|
{ |
|
sprintf(szTexPath, "%s/detail/%s", gEngfuncs.pfnGetGameDirectory(), pBumpFile); |
|
|
|
// load up the specified normal map |
|
if (LoadTexture(szTexPath, &uiTex)) |
|
{ |
|
// shove it into the map of diffuse texture ID to normal map texture ID |
|
m_aNormalMaps.insert(std::make_pair(pWorld->texinfo[i].texture->gl_texturenum, uiTex)); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
memset(szTex, 0, 256); |
|
} |
|
|
|
fclose(pFile); |
|
|
|
return true; |
|
} |
|
|
|
bool CBumpmapMgr::InitialiseOGLExtensions(void) |
|
{ |
|
// MULTITEXTURE EXTENSIONS |
|
// Used for... well, multitexturing. |
|
|
|
glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB"); |
|
if (!glActiveTextureARB) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not acquire pointer to glActiveTextureARB.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB"); |
|
if (!glMultiTexCoord2fARB) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not acquire pointer to glMultiTexCoord2fARB.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
glMultiTexCoord3fvARB = (PFNGLMULTITEXCOORD3FVARBPROC)wglGetProcAddress("glMultiTexCoord3fvARB"); |
|
if (!glMultiTexCoord3fvARB) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not acquire pointer to glMultiTexCoord3fvARB.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// 3D TEXTURE EXTENSIONS |
|
// A 3D texture is used for the attenuation map. |
|
|
|
glTexImage3DEXT = (PFNGLTEXIMAGE3DEXTPROC)wglGetProcAddress("glTexImage3DEXT"); |
|
if (!glTexImage3DEXT) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not acquire pointer to glTexImage3DEXT.", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CBumpmapMgr::InitialiseCg(void) |
|
{ |
|
// create a Cg context |
|
m_cg_Context = cgCreateContext(); |
|
if (!m_cg_Context) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not create Cg context!", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// get the best vertex and fragment program profiles |
|
m_cg_vertProfile = cgGLGetLatestProfile(CG_GL_VERTEX); |
|
m_cg_fragProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT); |
|
|
|
return true; |
|
} |
|
|
|
bool CBumpmapMgr::LoadFragmentPrograms(void) |
|
{ |
|
// fullbright map |
|
if (!LoadCgProgram(&m_fp_Diffuse, m_cg_fragProfile, "render/fp_diffuse.cg")) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not load fp_diffuse.cg!", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// map with only lightmaps |
|
if (!LoadCgProgram(&m_fp_Lightmaps, m_cg_fragProfile, "render/fp_lightmaps.cg")) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not load fp_lightmaps.cg!", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// bumpmapping |
|
if (!LoadCgProgram(&m_fp_Bump, m_cg_fragProfile, "render/fp_bump.cg")) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not load fp_bump.cg!", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// fullbright model |
|
if (!LoadCgProgram(&m_fp_ModelPass0, m_cg_fragProfile, "render/fp_model_p0.cg")) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not load fp_model_p0.cg!", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
// model with diffuse colour |
|
if (!LoadCgProgram(&m_fp_ModelPass1, m_cg_fragProfile, "render/fp_model_p1.cg")) |
|
{ |
|
MessageBox(NULL, "Bumpmapping error: Could not load fp_model_p1.cg!", "Fatal Error", NULL); |
|
return false; |
|
} |
|
|
|
m_parm_BumpLightColour = cgGetNamedParameter(m_fp_Bump, "LightColour"); |
|
m_parm_BumpLightStrength = cgGetNamedParameter(m_fp_Bump, "LightStrength"); |
|
|
|
return true; |
|
} |
|
|
|
bool CBumpmapMgr::LoadCgProgram(CGprogram* pDest, CGprofile eProfile, const char* szFile) |
|
{ |
|
// map the path |
|
char file[512]; |
|
sprintf(file, "%s/%s", gEngfuncs.pfnGetGameDirectory(), szFile); |
|
|
|
// load the program |
|
*pDest = cgCreateProgramFromFile(m_cg_Context, CG_SOURCE, file, eProfile, "main", NULL); |
|
if (!(*pDest)) |
|
return false; |
|
|
|
cgGLLoadProgram(*pDest); |
|
|
|
return true; |
|
} |
|
|
|
bool CBumpmapMgr::LoadTexture(const char* szFile, unsigned int* pTexIdx) |
|
{ |
|
// We're using GLaux for this, because I can't be bothered to code a proper bitmap loading function. |
|
// It works, so no complaining. |
|
|
|
// load the image |
|
AUX_RGBImageRec* pImg = auxDIBImageLoad(szFile); |
|
if (!pImg) |
|
return false; |
|
|
|
// assign a texture ID and set the various parameters |
|
*pTexIdx = m_uiNextTexIdx++; |
|
glBindTexture(GL_TEXTURE_2D, *pTexIdx); |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); |
|
|
|
// load the texture, generating mipmaps for it at the same time |
|
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, pImg->sizeX, pImg->sizeY, GL_RGB, GL_UNSIGNED_BYTE, pImg->data); |
|
|
|
free(pImg->data); |
|
free(pImg); |
|
|
|
return true; |
|
} |
|
|
|
void CBumpmapMgr::Shutdown(void) |
|
{ |
|
cgDestroyProgram(m_fp_Diffuse); |
|
cgDestroyProgram(m_fp_Lightmaps); |
|
cgDestroyProgram(m_fp_Bump); |
|
cgDestroyProgram(m_fp_ModelPass0); |
|
cgDestroyProgram(m_fp_ModelPass1); |
|
|
|
cgDestroyContext(m_cg_Context); |
|
|
|
Reset(); |
|
} |
|
|
|
void CBumpmapMgr::Reset(void) |
|
{ |
|
m_iNumFrameLights = 0; |
|
m_bInitialised = false; |
|
m_bFailedInit = false; |
|
m_iLargestVisFrame = 0; |
|
|
|
m_aBumpedScenes.clear(); |
|
m_aNormalMaps.clear(); |
|
} |
|
|
|
void CBumpmapMgr::Render(int pass) |
|
{ |
|
if (!m_bInitialised && !m_bFailedInit) |
|
Initialise(); |
|
|
|
if (CVAR_GET_FLOAT("bm_enable") == 0 || g_BumpmapMgr.m_bFailedInit) |
|
return; |
|
|
|
int i; |
|
|
|
// We must clear to black to enable non-bumpmapped items to be re-combined with the scene properly, |
|
// and also to enable us to just render the bump mapping with additive blending for every light. |
|
glClearColor(0,0,0,1); |
|
|
|
m_iCurPass = pass; |
|
|
|
switch (pass) |
|
{ |
|
case 0: |
|
// Render the scene with only the DIFFUSE COLOUR (fullbright) |
|
cgGLEnableProfile(m_cg_fragProfile); |
|
cgGLBindProgram(m_fp_Diffuse); |
|
|
|
break; |
|
|
|
case 1: |
|
// In pass 0, we rendered the scene with just the diffuse texture. Grab this to a texture. |
|
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiDiffuseScene); |
|
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth, ScreenHeight, 0); |
|
|
|
// Here is the best time to render the bump-mapped scenes. We make use of the existing z-buffer data |
|
// to (a) stop us having to update the z-buffer and (b) to create black "holes" where items |
|
// such as models and brush entities go. |
|
|
|
glClear(GL_COLOR_BUFFER_BIT); // clear the colour buffer, but NOT the depth buffer |
|
|
|
glDepthMask(GL_FALSE); // disable modification of the depth buffer |
|
glDepthFunc(GL_LEQUAL); |
|
|
|
// Each light adds its contribution to the rest |
|
glEnable(GL_BLEND); |
|
glBlendFunc(GL_ONE, GL_ONE); |
|
|
|
for (i = 0; i < m_aBumpedScenes.size(); i++) |
|
{ |
|
RenderLight(i); |
|
} |
|
|
|
glDisable(GL_BLEND); |
|
glDepthMask(GL_TRUE); // re-enable modification of the depth buffer |
|
glDepthFunc(GL_LESS); |
|
|
|
glEnable(GL_TEXTURE_RECTANGLE_NV); |
|
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiBumpScene); |
|
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth, ScreenHeight, 0); |
|
glDisable(GL_TEXTURE_RECTANGLE_NV); |
|
|
|
glClear(GL_COLOR_BUFFER_BIT); |
|
|
|
// We now render the scene with just the LIGHTMAPS. |
|
cgGLBindProgram(m_fp_Lightmaps); |
|
|
|
break; |
|
|
|
case 2: |
|
// In pass 1, we rendered the scene with just the lightmaps. Grab this to a texture. |
|
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiLightmapScene); |
|
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth, ScreenHeight, 0); |
|
|
|
// Fragment programs aren't needed any more. |
|
cgGLDisableProfile(m_cg_fragProfile); |
|
|
|
// Switch to an orthogonal projection to render full-screen quads and combine all the scenes |
|
// together to give the final result. |
|
DoOrthoProjection(true); |
|
CombineScenes(); |
|
DoOrthoProjection(false); |
|
|
|
break; |
|
|
|
default: |
|
gEngfuncs.Con_Printf("BUMPMAPPING: Unknown pass number %i passed to Render\n", pass); |
|
} |
|
} |
|
|
|
void CBumpmapMgr::RenderLight(int light) |
|
{ |
|
// get a ptr to the light in question |
|
bumplight_t* pLight = &m_aBumpedScenes[light]; |
|
|
|
if (!pLight->enabled) |
|
return; |
|
|
|
// calculate the movewith |
|
if (pLight->moveWithEnt) |
|
{ |
|
// first, adjust the light's position by any change that the movewith entity has undergone |
|
pLight->pos = pLight->origPos + (pLight->moveWithEnt->origin - pLight->entOrigPos); |
|
|
|
// rotate the light around the movewith entity by any change in its angles |
|
Vector anglesChange = pLight->moveWithEnt->angles - pLight->entOrigAngles; |
|
Vector newPos = pLight->pos - pLight->moveWithEnt->origin; |
|
newPos = RotateZ(newPos, -anglesChange.y); |
|
newPos = RotateY(newPos, -anglesChange.x); |
|
newPos = RotateX(newPos, -anglesChange.z); |
|
newPos = newPos + pLight->moveWithEnt->origin; |
|
|
|
pLight->pos = newPos; |
|
} |
|
|
|
float styleStrength = (float)(g_LightStyles[pLight->style][(int)(gpGlobals->time * 10) % strlen(g_LightStyles[pLight->style])] - 'a') / ('z' - 'a'); |
|
|
|
if (styleStrength == 0) |
|
return; |
|
|
|
// bind the right fragment program and set the parameters |
|
cgGLBindProgram(m_fp_Bump); |
|
|
|
cgGLSetParameter1f(m_parm_BumpLightStrength, pLight->strength * styleStrength); |
|
cgGLSetParameter3fv(m_parm_BumpLightColour, pLight->colour); |
|
|
|
// bind the normalisation cube map to tex unit 1 |
|
glActiveTextureARB(GL_TEXTURE1_ARB); |
|
glDisable(GL_TEXTURE_RECTANGLE_NV); |
|
glDisable(GL_TEXTURE_2D); |
|
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, m_uiNormCubeMap); |
|
|
|
// bind the attenuation map to tex unit 2 |
|
glActiveTextureARB(GL_TEXTURE2_ARB); |
|
glEnable(GL_TEXTURE_3D_EXT); |
|
glBindTexture(GL_TEXTURE_3D_EXT, m_uiAttenuationMap); |
|
|
|
// normal maps will be bound to tex unit 0 |
|
glActiveTextureARB(GL_TEXTURE0_ARB); |
|
glDisable(GL_TEXTURE_RECTANGLE_NV); |
|
glEnable(GL_TEXTURE_2D); |
|
|
|
// create a bunch of variables |
|
model_t* pWorld = gEngfuncs.GetEntityByIndex(0)->model; // worldspawn model |
|
msurface_t** pSurf = pWorld->marksurfaces; // ptr to array of surfaces |
|
glpoly_t* pPoly; |
|
Vector N, T, B, L, L2, V, V1, V2, V3, E1, E2, AttenCoords; |
|
TexNormalMaps_It texID; |
|
|
|
glShadeModel(GL_SMOOTH); |
|
|
|
// go through each surface |
|
for (int i = 0; i < pWorld->nummarksurfaces; i++) |
|
{ |
|
// This is a semi-hack to use the pre-calculated visibilty info. We don't have access |
|
// to what the current visframe is, but we can assume that the surfaces with the highest |
|
// visframe are those which are currently being rendered. If the visframe changes, then |
|
// there might be more overdraw for one frame, but this isn't noticeable. |
|
// |
|
// To stop surfaces being rendered twice (which is disastrous, since we are using additive |
|
// blending) we set the visframe to the negative value of itself once we've rendered the surface. |
|
// If we then find a surface that has a negative visframe, we know that we've already |
|
// rendered that one. |
|
|
|
if (pSurf[i]->visframe > 0 && pSurf[i]->visframe >= m_iLargestVisFrame) |
|
{ |
|
m_iLargestVisFrame = pSurf[i]->visframe; |
|
|
|
pPoly = pSurf[i]->polys; |
|
float* vert = pPoly->verts[0]; |
|
|
|
// find the normal map corresponding to this surface's texture (use the flat map if there isn't one) |
|
texID = m_aNormalMaps.find(pSurf[i]->texinfo->texture->gl_texturenum); |
|
if (CVAR_GET_FLOAT("bm_allflat") == 1 || texID == m_aNormalMaps.end()) |
|
glBindTexture(GL_TEXTURE_2D, m_uiFlatSurfaceNorm); |
|
else |
|
glBindTexture(GL_TEXTURE_2D, texID->second); |
|
|
|
// tangent and binormal simply come from the UV vectors |
|
T = pSurf[i]->texinfo->vecs[0]; |
|
B = pSurf[i]->texinfo->vecs[1]; |
|
T = T.Normalize(); |
|
B = B.Normalize(); |
|
|
|
// We must now calculate the normal of the surface. We can't cross-product the tangent and binormal, |
|
// as we can't guarantee which way the normal will be facing (textures can be mirrored both ways). |
|
// So, we must calculate it by doing the cross product of two edge vectors. However, we can't just |
|
// choose any old edge vectors, since some turn out to be colinear, which gives bogus normals. |
|
// The solution is to go through each vertex until we find two edge vectors that aren't colinear. |
|
|
|
V1 = Vector(vert[0], vert[1], vert[2]); |
|
V2 = Vector(vert[VERTEXSIZE], vert[VERTEXSIZE+1], vert[VERTEXSIZE+2]); |
|
|
|
E1 = (V1 - V2).Normalize(); |
|
|
|
bool bFound = false; |
|
|
|
for (int j = 2; j < pPoly->numverts; j++) |
|
{ |
|
V3 = Vector(vert[VERTEXSIZE*j], vert[VERTEXSIZE*j + 1], vert[VERTEXSIZE*j + 2]); |
|
|
|
E2 = (V1 - V3).Normalize(); |
|
|
|
if (fabs(fabs(DotProduct(E1, E2))-1) > 0.01f) // vectors aren't colinear (dp 1 = equal, dp -1 = opposite) - check for > epsilon to account for FP error |
|
{ |
|
N = -1 * CrossProduct(E1, E2).Normalize(); // we can use these edge vectors to calculate the normal |
|
bFound = true; |
|
break; |
|
} |
|
} |
|
|
|
if (!bFound) |
|
N = -1 * CrossProduct(E1, (V1 - Vector(vert + VERTEXSIZE*2)).Normalize()).Normalize(); |
|
|
|
// render this surface |
|
|
|
glBegin(GL_POLYGON); |
|
|
|
for (int v = 0; v < pPoly->numverts; v++, vert+=VERTEXSIZE) |
|
{ |
|
// Since we can't use a vertex program, we have to calculate the light vector here. We first |
|
// calculate the light vector in world space, and then transform it into tangent space |
|
// by doing the equivalent of multiplying it by the TBN matrix, and finally scale it into the range [0,1]. |
|
V = Vector(vert); |
|
L = pLight->pos - V; |
|
L2.x = DotProduct(T, L)*0.5 + 0.5; |
|
L2.y = -DotProduct(B, L)*0.5 + 0.5; // for some reason, the y component of the light vector must be flipped - probably a by-product of the normal maps being flipped on loading |
|
L2.z = DotProduct(N, L)*0.5 + 0.5; |
|
|
|
// We also have to calculate the coordinates in the attenuation texture |
|
AttenCoords = (L / (pLight->radius * 2)) + Vector(0.5,0.5,0.5); |
|
|
|
// send the data to OGL |
|
glTexCoord2f(vert[3], 1 - vert[4]); // the normal maps get flipped on loading, for some reason - reverse that here |
|
glMultiTexCoord3fvARB(GL_TEXTURE1_ARB, L2); |
|
glMultiTexCoord3fvARB(GL_TEXTURE2_ARB, AttenCoords); |
|
glVertex3fv(vert); |
|
} |
|
|
|
glEnd(); |
|
} |
|
|
|
// set the negative visframe to say we've rendered this surface |
|
if (pSurf[i]->visframe > 0) |
|
pSurf[i]->visframe = -pSurf[i]->visframe; |
|
} |
|
|
|
// go back and make all visframes positive again, so that HL can render the map properly |
|
for (i = 0; i < pWorld->nummarksurfaces; i++) |
|
{ |
|
if (pSurf[i]->visframe < 0) |
|
pSurf[i]->visframe = -pSurf[i]->visframe; |
|
} |
|
|
|
glActiveTextureARB(GL_TEXTURE2_ARB); |
|
glDisable(GL_TEXTURE_3D_EXT); |
|
|
|
glActiveTextureARB(GL_TEXTURE0_ARB); |
|
} |
|
|
|
void CBumpmapMgr::CombineScenes(void) |
|
{ |
|
// find out how many texture units we've got to mess with |
|
int iTexUnits; |
|
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &iTexUnits); |
|
|
|
|
|
// Add together the lightmap and the bump mapping, and modulate this by the textures |
|
|
|
for (int i = 0; i < iTexUnits; i++) |
|
{ |
|
glActiveTextureARB(GL_TEXTURE0_ARB + i); |
|
glDisable(GL_TEXTURE_RECTANGLE_NV); |
|
glDisable(GL_TEXTURE_2D); |
|
} |
|
|
|
glColor4f(1,1,1,1); |
|
|
|
glActiveTextureARB(GL_TEXTURE0_ARB); |
|
glDisable(GL_TEXTURE_2D); |
|
glEnable(GL_TEXTURE_RECTANGLE_NV); |
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); |
|
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiLightmapScene); |
|
|
|
glActiveTextureARB(GL_TEXTURE1_ARB); |
|
glDisable(GL_TEXTURE_2D); |
|
glEnable(GL_TEXTURE_RECTANGLE_NV); |
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); |
|
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiBumpScene); |
|
|
|
glActiveTextureARB(GL_TEXTURE2_ARB); |
|
glDisable(GL_TEXTURE_2D); |
|
glEnable(GL_TEXTURE_RECTANGLE_NV); |
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); |
|
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiDiffuseScene); |
|
|
|
RenderScreenQuad((float)ScreenWidth, (float)ScreenHeight, iTexUnits); |
|
|
|
|
|
// Clean up - de-activate texturing on all units (except the first one, on which |
|
// we enable regular 2D texturing, since HL doesn't do this...) |
|
|
|
for (i = 0; i < iTexUnits; i++) |
|
{ |
|
glActiveTextureARB(GL_TEXTURE0_ARB + i); |
|
glDisable(GL_TEXTURE_RECTANGLE_NV); |
|
} |
|
|
|
glActiveTextureARB(GL_TEXTURE0_ARB); |
|
glEnable(GL_TEXTURE_2D); |
|
|
|
glDisable(GL_BLEND); |
|
} |
|
|
|
void CBumpmapMgr::RenderScreenQuad(float texU, float texV, int iNumTexUnits) |
|
{ |
|
int i; |
|
|
|
glBegin(GL_QUADS); |
|
|
|
for (i = 0; i < iNumTexUnits; i++) glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 0, texV); |
|
glVertex2f(0,0); |
|
|
|
for (i = 0; i < iNumTexUnits; i++) glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, texU, texV); |
|
glVertex2f(1,0); |
|
|
|
for (i = 0; i < iNumTexUnits; i++) glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, texU, 0); |
|
glVertex2f(1,1); |
|
|
|
for (i = 0; i < iNumTexUnits; i++) glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 0, 0); |
|
glVertex2f(0,1); |
|
|
|
glEnd(); |
|
} |
|
|
|
void CBumpmapMgr::DoOrthoProjection(bool activate) |
|
{ |
|
if (activate) |
|
{ |
|
glMatrixMode(GL_PROJECTION); |
|
glPushMatrix(); |
|
glLoadIdentity(); |
|
glOrtho(0, 1, 1, 0, -1, 1); |
|
|
|
glMatrixMode(GL_MODELVIEW); |
|
glPushMatrix(); |
|
glLoadIdentity(); |
|
} |
|
else |
|
{ |
|
glMatrixMode(GL_PROJECTION); |
|
glPopMatrix(); |
|
|
|
glMatrixMode(GL_MODELVIEW); |
|
glPopMatrix(); |
|
} |
|
} |
|
|
|
void CBumpmapMgr::AddLight(const char* targetname, Vector pos, Vector colour, float strength, float radius, |
|
bool enabled /*= true*/, int style /*= 0*/, int moveWithEntID /*= -1*/, bool moveWithExtraInfo /*= false*/, |
|
Vector moveWithEntPos, Vector moveWithEntAngles) |
|
{ |
|
bumplight_t light; |
|
|
|
light.enabled = enabled; |
|
light.pos = pos; |
|
light.origPos = pos; |
|
light.colour = colour; |
|
light.strength = strength; |
|
light.radius = radius; |
|
light.style = (style >= NUM_STYLES ? 0 : style); |
|
|
|
memset(light.targetname, 0, 64); |
|
memcpy(light.targetname, targetname, strlen(targetname)); |
|
|
|
if (moveWithEntID == -1) |
|
light.moveWithEnt = NULL; |
|
else |
|
{ |
|
light.moveWithEnt = gEngfuncs.GetEntityByIndex(moveWithEntID); |
|
|
|
if (light.moveWithEnt != NULL) |
|
{ |
|
if (moveWithExtraInfo) |
|
{ |
|
light.entOrigPos = moveWithEntPos; |
|
light.entOrigAngles = moveWithEntAngles; |
|
} |
|
else |
|
{ |
|
light.entOrigPos = light.moveWithEnt->origin; |
|
light.entOrigAngles = light.moveWithEnt->angles; |
|
} |
|
} |
|
} |
|
|
|
m_aBumpedScenes.push_back(light); |
|
|
|
m_iNumFrameLights++; |
|
} |
|
|
|
void CBumpmapMgr::EnableLight(const char* targetname, bool enable) |
|
{ |
|
for (BumpSceneList_It it = m_aBumpedScenes.begin(); it != m_aBumpedScenes.end(); ++it) |
|
{ |
|
if (!_stricmp(targetname, it->targetname)) |
|
it->enabled = enable; |
|
} |
|
} |
|
|
|
void CBumpmapMgr::RenderStudioModel(bool bPreRender) |
|
{ |
|
if (CVAR_GET_FLOAT("bm_enable") == 0 || g_BumpmapMgr.m_bFailedInit) |
|
return; |
|
|
|
// bPreRender is true before rendering a studio model, false afterwards |
|
if (bPreRender) |
|
{ |
|
if (m_iCurPass == 0) |
|
cgGLBindProgram(m_fp_ModelPass0); |
|
else |
|
cgGLBindProgram(m_fp_ModelPass1); |
|
} |
|
else |
|
{ |
|
if (m_iCurPass == 0) |
|
cgGLBindProgram(m_fp_Diffuse); |
|
else |
|
cgGLBindProgram(m_fp_Lightmaps); |
|
} |
|
} |
|
|
|
void CBumpmapMgr::MakeNormCubeMap(unsigned int* pTexIdx) |
|
{ |
|
unsigned char* pTex = new unsigned char[BUMP_NORM_CUBE_MAP_SIZE*BUMP_NORM_CUBE_MAP_SIZE*3]; |
|
|
|
*pTexIdx = m_uiNextTexIdx++; |
|
|
|
// set the texture's parameters |
|
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, *pTexIdx); |
|
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
|
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
|
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP); |
|
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP); |
|
|
|
Vector V; |
|
|
|
// For each face of the cube map, generate a flat texture that gives a normalised version of the |
|
// vector used to access that texel in the cube texture. |
|
for (int i = 0; i < 6; i++) |
|
{ |
|
for (int y = 0; y < BUMP_NORM_CUBE_MAP_SIZE; y++) |
|
{ |
|
for (int x = 0; x < BUMP_NORM_CUBE_MAP_SIZE; x++) |
|
{ |
|
V = vecNCMVector(i, x, y); |
|
pTex[(x + y*BUMP_NORM_CUBE_MAP_SIZE)*3] = (unsigned char)((V.x * 0.5 + 0.5)*255); |
|
pTex[(x + y*BUMP_NORM_CUBE_MAP_SIZE)*3+1] = (unsigned char)((V.y * 0.5 + 0.5)*255); |
|
pTex[(x + y*BUMP_NORM_CUBE_MAP_SIZE)*3+2] = (unsigned char)((V.z * 0.5 + 0.5)*255); |
|
} |
|
} |
|
|
|
// store this face's image |
|
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + i, 0, 3, BUMP_NORM_CUBE_MAP_SIZE, BUMP_NORM_CUBE_MAP_SIZE, |
|
0, GL_RGB, GL_UNSIGNED_BYTE, pTex); |
|
} |
|
|
|
delete[] pTex; |
|
} |
|
|
|
void CBumpmapMgr::MakeAttenuationMap(unsigned int* pTexIdx) |
|
{ |
|
unsigned char* pTex = new unsigned char[BUMP_ATTENUATION_MAP_SIZE*BUMP_ATTENUATION_MAP_SIZE*BUMP_ATTENUATION_MAP_SIZE]; |
|
|
|
*pTexIdx = m_uiNextTexIdx++; |
|
|
|
glBindTexture(GL_TEXTURE_3D_EXT, *pTexIdx); |
|
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
|
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
|
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_R_EXT, GL_CLAMP); |
|
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP); |
|
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP); |
|
|
|
Vector V; |
|
|
|
// For each texel in the attenuation map, calculate x^2 + y^2 + z^2 and store it, clamping to [0,1]. |
|
for (int z = 0; z < BUMP_ATTENUATION_MAP_SIZE; z++) |
|
{ |
|
for (int y = 0; y < BUMP_ATTENUATION_MAP_SIZE; y++) |
|
{ |
|
for (int x = 0; x < BUMP_ATTENUATION_MAP_SIZE; x++) |
|
{ |
|
V = Vector(((float)x / BUMP_ATTENUATION_MAP_SIZE) * 2 - 1, |
|
((float)y / BUMP_ATTENUATION_MAP_SIZE) * 2 - 1, |
|
((float)z / BUMP_ATTENUATION_MAP_SIZE) * 2 - 1) * 1.05; |
|
|
|
pTex[x + y*BUMP_ATTENUATION_MAP_SIZE + z*BUMP_ATTENUATION_MAP_SIZE*BUMP_ATTENUATION_MAP_SIZE] = (unsigned char)Clamp((V.x*V.x + V.y*V.y + V.z*V.z) * 255, 0, 255); |
|
} |
|
} |
|
} |
|
|
|
// load the texture data |
|
glTexImage3DEXT(GL_TEXTURE_3D_EXT, 0, GL_RGB, BUMP_ATTENUATION_MAP_SIZE, BUMP_ATTENUATION_MAP_SIZE, BUMP_ATTENUATION_MAP_SIZE, |
|
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pTex); |
|
|
|
delete[] pTex; |
|
} |
|
|
|
void CBumpmapMgr::MakeFlatNormalMap(unsigned int* pTexIdx) |
|
{ |
|
*pTexIdx = m_uiNextTexIdx++; |
|
|
|
glBindTexture(GL_TEXTURE_2D, *pTexIdx); |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); |
|
|
|
unsigned char pTex[1*1*3]; // it doesn't need any clarity, seeing as it's all one colour - just make it 1x1 |
|
|
|
pTex[0] = 128; // unpacked, this gives 0 |
|
pTex[1] = 128; // unpacked, this gives 0 |
|
pTex[2] = 255; // unpacked, this gives 1 |
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, pTex); |
|
} |