Category: OpenGL

Game on

Just a quickie update from me, i got the OpenGL tutorials up and running.
You can find them to the right in the menu.
I also made the site a bit wider to accommodate the code bit’s, i hope you like it.
Please get back to me or post a comment if you like it, find anything wrong, want to make a suggestion or perhaps a request.

Shaders

Yes shading, the holy grail of real time 3d computer graphics, except they are not actually called shaders, programs are the correct term as in vertex programs instead of vertex shaders and fragment programs instead of pixel shaders, and FYI i am not gonna care about the correct terminology in this tut.
Now we are going to take a first step in learning how to use glsl shaders, we will base this tut on tut1 with all the FBO stuff removed, then we are gonna add five functions to help us, oops i lied, i mean they are going to do all the work for us.
The first function is a function that replaces the old draw_cube() function, and although we don’t really need it, since in this tut we could still just use the cube, but we are going to need it in the various other shader tut’s.
What does it do you ask well it’s name is cv90_render(); and it resides in a file called cv90.cpp, and basically contains a pretty decent mesh of a Swedish built CV9040c, that’s right, it’s a tank (well technically a cross between an APC and a light tank), this particular 3d model was made for FHS in a project called Foreign ground which is set in Liberia, hence in the later tutorials where we will be adding texturing you will see it’s has the traditional UN white colors instead of the slightly cooler M90 pattern.
What are the contents then, well first you have six huge arrays containing all the data that i exported using a program called crossroads3d , old but still useful.
The other part is this function to render it all, it’s pretty straightforward.


void cv90_render(void)
{
int faces=sizeof(cv90_face)/sizeof(long);
int i=0,p=1;

while(i<faces)
{
glBegin(GL_POLYGON);
while(p)
{
if(cv90_face[i]==-1) p=0;
else
{
glTexCoord2f(cv90_uv[cv90_uvface[i]].x,cv90_uv[cv90_uvface[i]].y);
glNormal3f(cv90_normal[cv90_nface[i]].x,cv90_normal[cv90_nface[i]].y,
cv90_normal[cv90_nface[i]].z);
glVertex3f(cv90_vertex[cv90_face[i]].x,cv90_vertex[cv90_face[i]].y,
cv90_vertex[cv90_face[i]].z);
}
i++;
}
p=1;
glEnd();
}
}

Then we need a few variables to store our shaders in


GLhandleARB ProgramObject;
GLhandleARB VertexShaderObject;
GLhandleARB FragmentShaderObject;
char* VertexShaderSource;
char* FragmentShaderSource;
unsigned int useshader;

the second one is just a general purpose function for getting the files length, it’s used in the folowing two functions.

unsigned long getFileLength(ifstream& file)
{
if(!file.good()) return 0;

unsigned long pos=file.tellg();
file.seekg(0,ios::end);
unsigned long len = file.tellg();
file.seekg(ios::beg);

return len;
}

The third and fourth function loads the vertex and fragment program source code from a file, they are both identical save from the different variables used so i am only gonna show loadVShade, you have to do loadFShade yourself.

void loadVShade(char filename[160])
{
ifstream file;
file.open(filename, ios::in);
if(!file) {useshader=0; return;}

unsigned long len = getFileLength(file);
if (len==0) {useshader=0; return;}
VertexShaderSource = new char[len+1];

if (VertexShaderSource == 0) {useshader=0; return;}
VertexShaderSource[len] = 0;

unsigned int i=0;
while (file.good())
{
VertexShaderSource[i++] = file.get();
if (i>len) i=len;
}
i--;
VertexShaderSource[i] = 0;
file.close();
return;
}

and now to the most important one, so important that i have to explain it in segments.

First create all the shader objects we need + other variables

void compileShaders(void)
{
int compiled = 0;
int linked = 0;
char str[4096];

useshader=1;

ProgramObject = glCreateProgramObjectARB();
VertexShaderObject = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
FragmentShaderObject = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);

Next, transfer the shader source to the shader objects, after this is done we don’t need it anymore so lets delete the memory containing the source.

glShaderSourceARB(VertexShaderObject, 1, (const char **)&VertexShaderSource, NULL);
glShaderSourceARB(FragmentShaderObject, 1, (const char **)&FragmentShaderSource, NULL);

delete[] VertexShaderSource;
delete[] FragmentShaderSource;

This step compiles each shader source independently and then checks for compilation errors

glCompileShaderARB(VertexShaderObject);
glGetObjectParameterivARB(VertexShaderObject,
GL_OBJECT_COMPILE_STATUS_ARB, &compiled);

if (!compiled)
{
glGetInfoLogARB( VertexShaderObject, sizeof(str), NULL, str );
MessageBox( NULL, str, "vertex Shader Compile Error", MB_OK|MB_ICONEXCLAMATION );
useshader=0;
return;
}

glCompileShaderARB(FragmentShaderObject);
glGetObjectParameterivARB(FragmentShaderObject,
GL_OBJECT_COMPILE_STATUS_ARB, &compiled);

if (!compiled)
{
glGetInfoLogARB( FragmentShaderObject, sizeof(str), NULL, str );
MessageBox( NULL, str, "Fragment Shader Compile Error",
MB_OK|MB_ICONEXCLAMATION );
useshader=0;
return;
}

when it’s all compiled and well we attach the shader objects to the program objects, now the program object is what we actually call when we want to “bind” a shader.
In this step we can also delete the shader objects, we wouldn’t want to hog all that memory anyway, not after that half meg array the cv90_render() function got.

glAttachObjectARB(ProgramObject,VertexShaderObject);
glAttachObjectARB(ProgramObject,FragmentShaderObject);

glDeleteObjectARB(VertexShaderObject);
glDeleteObjectARB(FragmentShaderObject);

Finally we link the program object and check if something went wrong, if not then we got a shader ready for use.

glLinkProgramARB(ProgramObject);
glGetObjectParameterivARB(ProgramObject, GL_OBJECT_LINK_STATUS_ARB, &linked);
if (!linked)
{
MessageBox (HWND_DESKTOP, "can't link shaders", "Error",
MB_OK | MB_ICONEXCLAMATION);
useshader=0;
return;
}

return;
}

So after all that the end is rather anticlimactic, well i said the functions where gonna take care of it for us, so in the Initialize function just add these line to load and compile the shaders.

loadVShade("glsl.vert");
loadFShade("glsl.frag");
compileShaders();

and to render we use this

[code]if (useshader) glUseProgramObjectARB(ProgramObject);
cv90_render();
if (useshader) glUseProgramObjectARB(0);

There is nothing more to it, oops i lied again, we need the glsl.vert and glsl.frag files


// glsl.vert
varying vec4 pos;

void main( void )
{
pos=gl_Vertex;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
}

// glsl.vert
varying vec4 pos;

void main( void )
{
gl_FragColor = vec4(sin(pos.x*10.0),cos(pos.y*10.0),sin(pos.z*10.0),
0)+vec4(gl_TexCoord[0].xy,0,0);
//gl_FragColor = vec4(sin(gl_TexCoord[0].x*2.0),cos(gl_TexCoord[0].y*2.0),0,0);
}

I have included two patterns the default striped pattern and one based on the UV coordinates, just comment and uncomment the two gl_FragColor lines in glsl.frag to switch between them.
Now these shaders are not that advanced, that's for another tut to teach so for now and as always check the source code for more info, comments and just plainly monkeying around with it and see if you can make something cool.
Download this tutorial for MSVCPP 6.0

FBO feeedbackbuffer

This is a continuation of the first FBO tutorial, this time we will use two of them, one for rendering and the other will be used as a feedback buffer, witch if you have seen a disco music video from the seventies you will know what i mean.
Basically a feedback buffer allows you to overdraw this frames rendering with all the previous frames renderings using an alpha blending.
Now normally one would think, why not do it directly into the back buffer, well that’s because you can’t be sure that you will get the previous rendering there after swapping the buffer, and if your triple buffering your in trouble.
A feedback FBO is a comfortable way of getting around that, since data is not shuffled around in them when you swap the buffers.
Although this is a neat effect, but because of all the low precision errors we get we will actually start using a little bit of HDR rendering, int’s not a lot, but it will remove all those artifacts we get but, it’s not full HDR rendering, that is a whole other tutorial with.

So what do you need them,
1.in the init function double up on the number of frame buffers with a simple copy paste and then just add a “2″ after each FBO variable.
And because we don’t want any precision down sampling errors we set the internal format on the FBO#2 color buffer texture to GL_RGBA16F_ARB instead of GL_RGBA, this will make the blur look as smooth as ever.

2. just render to FBO#1 as you did in the first tutorial.

3. move the contents of FBO#1 to FBO#2 with an alpha blending like this

glViewport (0, 0, 512, 512);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb2);
checkfbo();
glClear (GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
ViewOrtho(512,512);

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, color_tex);

glBegin(GL_QUADS);
glColor4f(1.0f, 1.0f, 1.0f, 0.1f);
glTexCoord2f(0,1);
glVertex3f(0,0,0);
glTexCoord2f(0,0);
glVertex3f(0,512,0);
glTexCoord2f(1,0);
glVertex3f(512,512,0);
glTexCoord2f(1,1);
glVertex3f(512,0,0);
glEnd();

glDisable(GL_BLEND);
ViewPerspective();

Now about ViewOrtho() and ViewPerspective(), you should already be familiar with them if you did the NeHe tutorials, they enable and disable orthographic rendering, very handy indeed.

4. Finally, render FBO#2 to the frame buffer using most of the same stuff besides the blending part that is, note that i also draw a cube instead and that the full screen render has no blue component, this is because i want the cube to be visible.

glViewport (0, 0, (GLsizei)(g_window->init.width), (GLsizei)(g_window->init.height));
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, o);
checkfbo();
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
ViewOrtho(512,512);

glBindTexture(GL_TEXTURE_2D, color_tex2);

glBegin(GL_QUADS);
glColor4f(1.0f, 1.0f, 0.0f, 0.1f);
glTexCoord2f(0,1);
glVertex3f(0,0,0);
glTexCoord2f(0,0);
glVertex3f(0,512,0);
glTexCoord2f(1,0);
glVertex3f(512,512,0);
glTexCoord2f(1,1);
glVertex3f(512,0,0);
glEnd();

glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);

glLoadIdentity ();
glTranslatef (6.0f, -4.0f, -16.0f);
glRotatef(angle,0.0f,1.0f,0.0f);
glRotatef(angle,1.0f,0.0f,0.0f);
glRotatef(angle,0.0f,0.0f,1.0f);
glColor3f(1,1,1);
drawBox();

ViewPerspective();
glDisable(GL_TEXTURE_2D);

There are many other uses for FBO buffers, using them for post processing or advanced blending is not unheard of, multi pass rendering likes them and for HDR it’s almost must.
The double FBO method i use here is good way of doing things, first render to buffer 1 and then post process them in the second one, it’s fairly fast and leaves few artifacts, but this is only if your GPU can take it that is, there are a few new extensions that deals directly with transferring and blending data between two FBOs, but i won’t include them here, not until we get gl 2.1 where these and the fbo extension is merged.
Sit tight, because the next one is about GLSL shaders, and as always check the source for more info and comments on the code.
Download this tutorial for MSVCPP 6.0

Simple FBO rendering

Frame buffer objects is something new to openGL, it didn’t make it in time to 2.0 but it will probably be in 2.1 so it’s likely that it will change a little, but this tut should still remain valid for the most part.
Now if you don’t know what frame buffer objects are then i will tell you, the frame buffer is where you do all of your rendering, it is the memory area where the actual pixel data is stored, now previously you had only one (4 actually, but the other ones are rarely used) and if you wanted more then you had to use p-buffers, but p-buffers are something that is definitely problematic, I’m not gonna go into details but my advice it to stay as far away from them as possible.

This is where Frame Buffer Objects come into the picture, an FBO is a custom memory area in where you could render stuff to and provide a quick and easy render to texture solution where you could render directly to a depth texture or use odd formats and sizes.
FBO’s also makes High Dynamic Rendering (HDR) practical as you only have to create a texture that uses this format witch is as easy as typing GL_RGBA16F_ARB.

Now on to the cody bits.
The stuff i am gonna ignore this tutorial is the drawBox() func, the IsExtensionSupported() func, the checkfbo() func and all the extension init code, they are pretty much self explanatory and dull as dry bread, if you want to know what’s in it then you have to download the code and take a look.

So the first thing we have to do is to initialize the frame buffer and the associated textures and render buffers then binding the frame buffer so that we can work with it.
fb, color_tex and depth_rb are just unsigned ints, nothing complicated.

// generate namespace for the frame buffer, colorbuffer and depthbuffer
glGenFramebuffersEXT(1, &fb);
glGenTextures(1, &color_tex);
glGenRenderbuffersEXT(1, &depth_rb);

//switch to our fbo so we can bind stuff to it
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);

Then bind and create the texture color_tex, the same way as with regular texturing but with a NULL or 0 instead of the data

//create the colorbuffer texture and attach it to the frame buffer
glBindTexture(GL_TEXTURE_2D, color_tex);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0,
GL_RGBA, GL_INT, NULL)

Now attach the texture to the frame buffer to the first color attachment, if you need to in conjunction with MRT it’s possible to have more than one color buffer

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, color_tex, 0);

Do the same thing with the render buffer and bind it to the depth attachment.
A render buffer is basically the same as the texture above except that it cant be used anywhere else so it’s primarily used as throwaway depth buffer.

// create a render buffer as our depth buffer and attach it
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth_rb);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,
GL_DEPTH_COMPONENT24,512, 512);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
GL_DEPTH_ATTACHMENT_EXT,
GL_RENDERBUFFER_EXT, depth_rb);

// Go back to regular frame buffer rendering
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

And thats it, use the checkfbo() function to see if you where successfull in creating the FBO, if it passes you can now start using it.

So we have now managed to create it, but in order for you to be able to render to this frame buffer you need to do two things, first make sure the color buffer texture or any other texture bound to the fbo is not bound at the moment and then switch to FBO rendering.

glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);

Simple, and to render the texture you do the reverse.

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glBindTexture(GL_TEXTURE_2D, color_tex);

And thats all there is to it, naturally the render loop is a bit more complicated and i am gonna include the tutorials full draw func just to show you how it’s done, note that you have to call glClear two times, this is because the clear only works on the currently bound frame buffer and not all of them at once, you also need to set the viewport to the size of the current framebuffer or things will look odd.

// FBO render pass
glViewport (0, 0, 512, 512);
// set The Current Viewport to the fbo size glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
glClearColor (1.0f, 0.0f, 0.0f, 0.5f);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity ();
glTranslatef (0.0f, 0.0f, -6.0f);
glRotatef(angle,0.0f,1.0f,0.0f);
glRotatef(angle,1.0f,0.0f,0.0f);
glRotatef(angle,0.0f,0.0f,1.0f);
glColor3f(1,1,0);
drawBox()

// Framebuffer render pass

glEnable(GL_TEXTURE_2D);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glBindTexture(GL_TEXTURE_2D, color_tex);

glClearColor (0.0f, 0.0f, 0.0f, 0.5f);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glViewport (0, 0, (GLsizei)(g_window->init.width), (GLsizei)(g_window->init.height));
glLoadIdentity ();
glTranslatef (0.0f, 0.0f, -6.0f);
glRotatef(angle,0.0f,1.0f,0.0f);
glRotatef(angle,1.0f,0.0f,0.0f);
glRotatef(angle,0.0f,0.0f,1.0f)
glColor3f(1,1,1);
drawBox();
glDisable(GL_TEXTURE_2D);
glFlush ();


That’s all for this time make sure you download the tutorial and look at the code to understand the context a bit better.
Download this tutorial for MSVCPP 6.0

Base code

Now i want to get one thing straight before i start explaining things, this tutorial is not a full sized tutorial, no it’s just a small entry of misc comments about the changes of the nehe base code, it does not contain all changes, like variables and such.
Also this tut is mainly for win32 systems, my guess is that if you have another system then you might just use this tut as an inspiration.
Also there is not comments in the code below, just download the base code from the bottom of the page and look in that

Ok, lets begin.
The first change i made was that i added not one but four timing systems, the timing system is what makes the animations smooth and it dictates the speed of things.
I have included the following timing codes

Full delta time: it runs as fast as it can and reports the delta time to the update function, it gives max performance and works well for most things except physics

Full delta time paused: as above, but includes a sleep function in order to try and cap the framerate since it’s useless to run at a higher framerate than the monitor refresh rate, this also saves in on a lot of cpu time.

Set delta time: runs the update function at a set framerate: this saves in a little on performance and will work well with physics

Set delta time paused : again as above but also caps the actual rendered framerate with the use of the sleep function.

Here is the code itself


// timing functions
tickCount = GetTickCount ();
window.deltaTime = ((float)(tickCount - window.lastTickCount))/1000;
window.lastTickCount = tickCount;

switch(timingFormula)
{
case TIMER_FULLDT:
Update (window.deltaTime);
Draw ();
SwapBuffers (window.hDC);
break;

case TIMER_SETDT:
dtTemp+=window.deltaTime;

while (dtTemp>window.frameRate)
{
Update (window.frameRate);
dtTemp-=window.frameRate;
}

Draw ();
SwapBuffers (window.hDC);
break;

case TIMER_SETDT_PAUSED:
dtTemp+=window.deltaTime;

while (dtTemp>window.frameRate)
{
Update (window.frameRate);
dtTemp-=window.frameRate;
}
Draw ();
SwapBuffers (window.hDC);
timeSpent=(float)(GetTickCount()-window.lastTickCount)/1000;

if (timeSpent
{ Sleep((unsigned long)((window.frameRate-timeSpent)*900)); }
break;

case TIMER_FULLDT_PAUSED:
Update (window.deltaTime);
Draw ();
SwapBuffers (window.hDC);
timeSpent=(float)(GetTickCount()-window.lastTickCount)/1000;
if (timeSpent
{ Sleep((unsigned long)((window.frameRate-timeSpent)*900)); }
break;

default:
Update (window.deltaTime);
Draw ();
SwapBuffers (window.hDC);
break;
}

A bit much perhaps, but in essence it all about supplying the right values to the update function.
This in coincidently the only thing i changed with the interface to the actual application ( the init, draw and update functions), i changed it do that it sends the delta time in seconds instead of milliseconds.
Its better that way, it’s more clear in how to use it and it is a SI (Systeme Internationale) standard.

Using set delta time you have the possibility to do some cool time effects, normally the update function updates at the same rate as is given to it but if you change the value it gets to lets say a tenth of the update rate, you get some kind of bullet time effect, this is something you could play around with later on.

Now for the second thing i changed.
The original base code could switch from windowed to fullscreen at the push of a button, this is good, but unfortunately it was done in such a way that the context has to be released, this means that all the nice little textures you have loaded disappears.
The way i do it means that the render context does not get released, but this was only possible if i sacrificed one thing, the border around the window, but that’s ok, i don’t need it and it actually makes the window smaller then intended.

To change thing then first make sure the two top lines in the CreateWindowGL function looks like this.

DWORD windowStyle = WS_POPUPWINDOW;
DWORD windowExtendedStyle = WS_EX_APPWINDOW;

Next in WindowProc replace everything between case WM_TOGGLEFULLSCREEN: and the following break; with this

g_createFullScreen = (g_createFullScreen == TRUE) ? FALSE : TRUE;

if (g_createFullScreen== TRUE)
{
ShowCursor (FALSE);
SetWindowLong(hWnd,GWL_EXSTYLE,WS_EX_TOPMOST); ChangeScreenResolution (window->init.width, window->init.height,
window->init.bitsPerPixel);
SetWindowPos(hWnd,HWND_TOPMOST,0,0,window->init.width,
window->init.height,SWP_NOZORDER);
}
else
{
ShowCursor (TRUE);
SetWindowLong(hWnd,GWL_EXSTYLE,WS_EX_APPWINDOW); ChangeDisplaySettings (NULL,0); SetWindowPos(hWnd,HWND_TOPMOST,window->x,window->y,
window->init.width,window->init.height,SWP_NOZORDER);
}

Now this code will now toggle fullscreen/windowed without releasing the render context.
It will Also make sure the window is topmost, has not mouse cursor and is always positioned at the upper left corner when in fullscreen mode.
As a bonus the code will also return the window as it was when switching back from fullscreen.

Download the MSVCPP 6.0 base code here.

OpenGL wishlist

I know almost everything about openGL and real time 3d graphics, and i am proud of that, but sometimes there are a few wee bits that i feel is missing.
So what better place than here to bring it up, well ok i admit there are better places, but this is my place and here i reign supreme.

1. Fix the damn z buffer
This problem is a two parter, it’s simple but annoying.
a) 64 bit z buffer, it will reduce distance z fighting, that isn’t to much to ask for, is it?
b) Currently the way graphics accelerators calculate the z value is by interpolating the vertics z-values, this is fast and works well 99.99% of the time, but on the flip side it is the reason why z fighting appears in the first place.
Instead use a standard plane equation for the z values, this will force the z values into submission and in the process reduce the near plane z-fighting.

2. Virtual texturing.
Or full virtualisation of the texture memory as John Carmack put it, it will fix the damn batch problem and give all graphics artists more freedom to work with, all in one swift blow.

3. Increased parallelism.
Real time computer graphics is the champion of parallelism, but it is also it’s strongest opponent.
Everyone in the business knows that computer games rarely have any huge benefit of multiple processors, this is because the render pipeline is horribly linear.
I love to see a parallel capable graphics API/graphics accelerator but i just don’t see that happening in the near future, perhaps in 3-5 years, but not before that.
I know how it could be done, i know the benefits of doing so, but they more or less laughed at me for suggesting anything like that on the openGL forums.
I know i am right on this one, so give me my damn parallelism before i hurt someone.

4. layered rendering.
I know, this one is a bit of a stretch, but it will make everything look so much nicer.
Let me explain what i mean.
in layered rendering you have a special color and z buffer, they have 8 or more layers in them.
when a pixel is written to the buffer a layer is chosen for it to be written to, if it is opaque it is always clamped to the deepest layer, if it’s transparent it chooses a layer before that depending a little on depth.
If a a pixel needs to occupy the space between layers(since they all have a z value) it pushes (and merges if needed) all other pixels put of the way.
If you understood what this means in terms of what it can do, you will also understand that it is somewhat of a wet dream for game makers.
But i admit, it is a bit of a stretch.

5. Raytracing.
It’s time for a change, it’s time to bring out the heavy artillery (and not just because i did that in the army) it’s time to introduce raytracing as an realistic addition to real time computer graphics, it can be done, it’s relatively easy to implement in shader programs and it will kick ass.
Not to mention the relative jump in visual quality games will get.

Well that is that, if you know a member of the ARB or the core Nvidia dev team, let them know about this, please.

WordPress Themes