#include <windows.h>
#include <gl/gl.h>
Notice that we're not using gl/glext.h, despite the fact that we'll be using an OpenGL extension. This is because all we need is one constant, which we can add in ourselves. Below #define DLLEXPORT
, add this:
#define GL_TEXTURE_RECTANGLE_NV 0x84F5
Then, after the extern "C"
block, add this block of code:
#include "r_studioint.h"
extern engine_studio_api_t IEngineStudio;
We need IEngineStudio
to retrieve which renderer is being used - software, OpenGL or Direct3D. After this, add in this block of definitions:
// TEXTURES
unsigned int g_uiScreenTex = 0;
unsigned int g_uiGlowTex = 0;
// FUNCTIONS
bool InitScreenGlow(void);
void RenderScreenGlow(void);
void DrawQuad(int width, int height, int ofsX = 0, int ofsY = 0);
Now that we've added all the definitions that we require, we can move on to creating the functions. I'll go through them step-by-step, explaining as we go along.bool InitScreenGlow(void)
{
// register the CVARs
gEngfuncs.pfnRegisterVariable("glow_blur_steps", "4", 0);
gEngfuncs.pfnRegisterVariable("glow_darken_steps", "3", 0);
gEngfuncs.pfnRegisterVariable("glow_strength", "2", 0);
These three CVARs will be used to control the glow. The first one controls how much the screen is blurred, the second one controls how much darker colours are decreased by before blurring, and the third one controls how strongly the glow is applied to the scene.
// create a load of blank pixels to create textures with
unsigned char* pBlankTex = new unsigned char[ScreenWidth*ScreenHeight*3];
memset(pBlankTex, 0, ScreenWidth*ScreenHeight*3);
We create a block of memory with which the textures we create will be initialised.
// Create the SCREEN-HOLDING TEXTURE
glGenTextures(1, &g;_uiScreenTex);
glBindTexture(GL_TEXTURE_RECTANGLE_NV, g_uiScreenTex);
glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB8, ScreenWidth, ScreenHeight, 0, GL_RGB8, GL_UNSIGNED_BYTE, pBlankTex);
This is the texture we use to grab the screen and store it so that we can re-combine it with the glow later on. Notice that we're using rectangular textures, as the screen is never a size that is a power of two in both dimensions.
// Create the BLURRED TEXTURE
glGenTextures(1, &g;_uiGlowTex);
glBindTexture(GL_TEXTURE_RECTANGLE_NV, g_uiGlowTex);
glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB8, ScreenWidth/2, ScreenHeight/2, 0, GL_RGB8, GL_UNSIGNED_BYTE, pBlankTex);
This texture is used to store the blurred and glowing version of the scene. It's half the size of the screen to conserve fillrate.
// free the memory
delete[] pBlankTex;
return true;
}
All that's left is to delete the memory we allocated.void DrawQuad(int width, int height, int ofsX, int ofsY)
{
glTexCoord2f(ofsX,ofsY);
glVertex3f(0, 1, -1);
glTexCoord2f(ofsX,height+ofsY);
glVertex3f(0, 0, -1);
glTexCoord2f(width+ofsX,height+ofsY);
glVertex3f(1, 0, -1);
glTexCoord2f(width+ofsX,ofsY);
glVertex3f(1, 1, -1);
}
This function is fairly self-evident. All it does is draw a quad with dimensions 1x1, and applies the specified texture coordinates with offsets if required. (Remember that the offsets are given a default value of 0 in the prototype for this function.)void RenderScreenGlow(void)
{
// check to see if (a) we can render it, and (b) we're meant to render it
if (IEngineStudio.IsHardware() != 1)
return;
if ((int)gEngfuncs.pfnGetCvarFloat("glow_blur_steps") == 0 || (int)gEngfuncs.pfnGetCvarFloat("glow_strength") == 0)
return;
// enable some OpenGL stuff
glEnable(GL_TEXTURE_RECTANGLE_NV);
glColor3f(1,1,1);
glDisable(GL_DEPTH_TEST);
We first check to see whether the user's running OpenGL mode - if not, then we just bail and leave the screen as-is. We also check to see if the effect should be rendered at all, by looking at the CVAR settings. Finally, some OpenGL commands are issued that are required for the upcoming stages.
// STEP 1: Grab the screen and put it into a texture
glBindTexture(GL_TEXTURE_RECTANGLE_NV, g_uiScreenTex);
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth, ScreenHeight, 0);
We need to store the original scene's image so that we can re-combine it with the glowing version later on. If we didn't do this, we'd have nothing to combine the blur with, since the on-screen image gets mangled when we do the blurring.
// STEP 2: Set up an orthogonal projection
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0, 1, 1, 0, 0.1, 100);
The orthogonal projection makes it much easier to render quads exactly across the whole screen. We map the screen from (0,0) to (1,1), meaning the DrawQuad
function draws across the whole screen.
// STEP 3: Render the current scene to a new, lower-res texture, darkening non-bright areas of the scene
// by multiplying it with itself a few times.
glViewport(0, 0, ScreenWidth/2, ScreenHeight/2);
glBindTexture(GL_TEXTURE_RECTANGLE_NV, g_uiScreenTex);
glBlendFunc(GL_DST_COLOR, GL_ZERO);
glDisable(GL_BLEND);
glBegin(GL_QUADS);
DrawQuad(ScreenWidth, ScreenHeight);
glEnd();
glEnable(GL_BLEND);
glBegin(GL_QUADS);
for (int i = 0; i < (int)gEngfuncs.pfnGetCvarFloat("glow_darken_steps"); i++)
DrawQuad(ScreenWidth, ScreenHeight);
glEnd();
glBindTexture(GL_TEXTURE_RECTANGLE_NV, g_uiGlowTex);
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth/2, ScreenHeight/2, 0);
To make darker colours have much less of a glow, we multiply the scene by itself a few times (as determined by the glow_darken_steps
CVAR) to make darker colours get even darker, leaving brighter colours still relatively bright (0.2*0.2 = 0.04, which is a 0.16 difference, but 0.9*0.9 = 0.89, which is a 0.01 difference). The result is then stored in the glow texture.
// STEP 4: Blur the now darkened scene in the horizontal direction.
float blurAlpha = 1 / (gEngfuncs.pfnGetCvarFloat("glow_blur_steps")*2 + 1);
glColor4f(1,1,1,blurAlpha);
glBlendFunc(GL_SRC_ALPHA, GL_ZERO);
glBegin(GL_QUADS);
DrawQuad(ScreenWidth/2, ScreenHeight/2);
glEnd();
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glBegin(GL_QUADS);
for (i = 1; i <= (int)gEngfuncs.pfnGetCvarFloat("glow_blur_steps"); i++) {
DrawQuad(ScreenWidth/2, ScreenHeight/2, -i, 0);
DrawQuad(ScreenWidth/2, ScreenHeight/2, i, 0);
}
glEnd();
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth/2, ScreenHeight/2, 0);
Now that we've corrected the colours, the next step is to blur the image. We first blur it in the horizontal direction by averaging the pixels around each other. The number of pixels sampled is controlled by the glow_blur_steps
CVAR.
// STEP 5: Blur the horizontally blurred image in the vertical direction.
glBlendFunc(GL_SRC_ALPHA, GL_ZERO);
glBegin(GL_QUADS);
DrawQuad(ScreenWidth/2, ScreenHeight/2);
glEnd();
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glBegin(GL_QUADS);
for (i = 1; i <= (int)gEngfuncs.pfnGetCvarFloat("glow_blur_steps"); i++) {
DrawQuad(ScreenWidth/2, ScreenHeight/2, 0, -i);
DrawQuad(ScreenWidth/2, ScreenHeight/2, 0, i);
}
glEnd();
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth/2, ScreenHeight/2, 0);
To complete the blur, we take the horizontally blurred image and blur it in the vertical direction. This gives a Gaussian blur-like appearance, with the number of pixels sampled again controlled by glow_blur_steps
.
// STEP 6: Combine the blur with the original image.
glViewport(0, 0, ScreenWidth, ScreenHeight);
glDisable(GL_BLEND);
glBegin(GL_QUADS);
DrawQuad(ScreenWidth/2, ScreenHeight/2);
glEnd();
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glBegin(GL_QUADS);
for (i = 1; i < (int)gEngfuncs.pfnGetCvarFloat("glow_strength"); i++) {
DrawQuad(ScreenWidth/2, ScreenHeight/2);
}
glEnd();
glBindTexture(GL_TEXTURE_RECTANGLE_NV, g_uiScreenTex);
glBegin(GL_QUADS);
DrawQuad(ScreenWidth, ScreenHeight);
glEnd();
All that's left is to combine the blurred version with the original to give the finished glow effect. The glow_strength
CVAR controls the number of times that the blurred version is added to the original (try setting it to 50 to get some weird psychadelic effects!).
// STEP 7: Restore the original projection and modelview matrices and disable rectangular textures.
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glDisable(GL_TEXTURE_RECTANGLE_NV);
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
}
Finally, we clear up after ourselvs by resetting the projection matrix back to what it was and enabling or disabling what we disabled or enabled, respectively.extern "C"
block, add this:
// SCREEN GLOW
extern bool InitScreenGlow();
extern void RenderScreenGlow();
Then, go to HUD_VidInit
(around line 170) and after VGui_Startup()
is called, add this:
// SCREEN GLOW
if (!InitScreenGlow())
gEngfuncs.Con_Printf("ERROR: Could not initialise screen glow!");
Last but definately not least, go to HUD_Redraw
(around line 210) and add this before gHUD.Redraw()
is called:
RenderScreenGlow(); // SCREEN GLOW
That's it. You now have the "TRON 2.0"-esque glow effect without any of the vertex or pixel shaders involved in the previous article. All that remains is to compile it, run it, and bask in the warm glow!
You must log in to post a comment. You can login or register a new account.