One of the most exciting topics and endeavors of discovery
for me his been 3d graphics. When I first started RTS Creator I knew absolutely
nothing about 3d math or OpenGL. And a little secret is that most independent
game developers don't. Now days you don't need have to have a PHD in rocket
science and an awkward obsession with low level graphics to create games.
Instead you use a higher level set of functions that handles the mathematics
and calls to the graphics card for you.
But how do these mysterious frameworks work?
3D models consist of triangles, and as graphic power
increases so do the number of triangles in modern models. What does it take to
define a model of triangles? A list of coordinates for each triangle corner and
which 3 coordinates come together to make each triangle. Pretty simple right?
What makes modern graphics "modern" is how these
triangles are processed. 5 years ago graphics were rendered through a process
called the "Fixed Function" pipeline. With the fixed function
pipeline all graphics card commands were made in the application. The problem
came years after the fixed function pipeline was invented, when models became
extraordinarily complex and AAA game studios began to demand more flexibility
and power.
This gave birth to the "Programmable" pipeline,
which actually replaces parts of the native OpenGL rendering pipeline with...
whatever the developer writes! This
amounts to less OpenGL calls to the GPU, resulting in less overhead, and lots
more flexibility as developers are able to write their own rendering equations.
To give a brief example of what you can do now that you
couldn't do before here are some pictures.
Ambient Occlusion
Shadows
Normal Mapping
And the list continues.
You can see how these things come together to produce more realistic
and modern graphics.
RTS Creator was initially developed with the fixed function
pipeline. With lots of research and studying I was able to swap out the fixed
function pipeline and replace it with the programmable pipeline. I then had to
find lots of equations to modernize the rendering and introduce new features
such as Screen Space Ambient Occlusion and Shadows.
To give you a little taste of the final result, here's the
equation and "program" RTS Creator uses to render its graphics.
#version 120
uniform vec3 EntityColor;
uniform bool FullBright;
varying vec3 vertex_normal;
varying vec3 Position;
//SHADOW VARIABLES
uniform bool EnableShadow;
uniform sampler2DShadow ShadowMap;
uniform int ShadowMapSize;
varying vec4 ShadowCoord;
float xPixelOffset = 0.001;
float yPixelOffset = 0.001;
//SHADOW VARIABLES
//DIFFUSE
uniform bool EnableDiffuseMap;
uniform sampler2D texture_diffuse;
//DIFFUSE
//NORMAL MAPPING
uniform bool EnableNormalMap;
uniform sampler2D texture_normal;
varying vec3 ViewDir[7];
varying vec3 LightDir[7];
//NORMAL MAPPING
//LIGHTING
struct LightInfo {
vec3
Position;
vec3
Intensity;
vec3
La;
vec3
Ld;
vec3
Ls;
};
struct MaterialInfo {
vec3
Ka;
vec3
Kd;
vec3
Ks;
float
Shininess;
};
uniform int LightCount;
uniform LightInfo Lights[7];
uniform vec3 LightPositions[7];
uniform MaterialInfo Material;
//LIGHTING
vec3 refl( vec3 s, vec3 n)
{
return
-s + 2 * (dot(s,n)) * n;
//return
2 * (dot(s,n))* n - s;
}
vec3 ads( int LightIndex )
{
vec3 n
= normalize(vertex_normal);
vec3 s
= normalize(Lights[LightIndex].Position - vec3(Position));
vec3 v
= normalize(vec3(-Position));
vec3 h
= refl(-s,n);//normalize(v+s);
return
Lights[LightIndex].Intensity * ((Lights[LightIndex].La * Material.Ka) +
(Lights[LightIndex].Ld * Material.Kd) * max(dot(s,n),0.0) +
(Lights[LightIndex].Ls * Material.Ks) * pow(max(dot(h,n),0.0),Material.Shininess));
}
vec3 adsn( vec3 norm, vec3 diffR, int LightIndex )
{
vec3 n
= normalize(norm);
vec3 s
= normalize(Lights[LightIndex].Position - vec3(Position));
vec3 v
= normalize(vec3(-Position));
vec3 h
= refl(-LightDir[LightIndex],norm);
return
Lights[LightIndex].Intensity * ((Lights[LightIndex].La * Material.Ka) +
(Lights[LightIndex].Ld * Material.Kd) * max(dot(LightDir[LightIndex],n),0.0) +
(Lights[LightIndex].Ls * Material.Ks) *
pow(max(dot(h,ViewDir[LightIndex]),0.0),Material.Shininess));
}
float lookup( vec2 offSet)
{
return
shadow2DProj(ShadowMap, ShadowCoord + vec4(offSet.x * xPixelOffset *
ShadowCoord.w, offSet.y * yPixelOffset * ShadowCoord.w, 0.000025, 0.0) ).w;
}
void main(void) {
vec4
tex = vec4(0.0);
if
(EnableDiffuseMap)
{
tex
= texture2D(texture_diffuse, gl_TexCoord[0].st).rgba;
} else
{
tex
= vec4(EntityColor.rgb,1.0);
}
vec3
lcolor = vec3(0.0);
if
(FullBright)
{
lcolor
= tex.rgb;
} else
{
if
(EnableNormalMap)
{
for(
int i = 0; i < LightCount; i++ )
lcolor +=
adsn(texture2D(texture_normal, gl_TexCoord[0].st).rgb,tex.rgb,i);
}
else {
for(
int i = 0; i < LightCount; i++ )
lcolor
+= ads(i);
}
}
float
shadow;
if
(EnableShadow)
{
//
Avoid counter shadow
if
(ShadowCoord.w > 1.0)
{
float
x,y;
for
(y = -1.5 ; y <=1.5 ; y+=0.50)
for
(x = -1.5 ; x <=1.5 ; x+=0.50)
shadow
+= lookup(vec2(x,y));
shadow
/= 64.0 ;
}
} else
{
shadow
= 0.8;
}
if
(tex.a < 0.15)
{
discard;
} else
{
gl_FragColor
= ((shadow+0.2) * vec4(lcolor*tex.rgb,1.0));
}
}