Sunday, March 2, 2014

Modern Graphics. A layman Explanation.

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));
                }

}

No comments:

Post a Comment