RakNet: Client Server Basics
Posted by Torin Kampa | Filed under RakNet
RakNet is the best open-source multiplayer networking library I’ve come across. In my experience, it doesn’t make any sense to reinvent the wheel by coding your own networking (except maybe as an exercise).
This article presents a barebones RakNet skeleton to connect multiple clients to a server.
Startup
RakPeerInterface is RakNet’s central class which allows you to dis/connect and send packets. Use RakNetworkFactor::GetRakPeerInterface() rather than directly instantiating a RakPeerInterface.
// create a new peer interface rakPeerInterface = RakNetworkFactory::GetRakPeerInterface();
For clients, we call Startup() with an empty descriptor. Once our port is opened, we try to connect the server at the given port/ip.
void initClient(const char* ip, unsigned short port)
{
// use an empty descriptor to let raknet choose local port/ip
SocketDescriptor socketDescriptor;
rakPeerInterface->Startup(1, 100, &socketDescriptor, 1);
rakPeerInterface->Connect(ip, port, 0, 0, 0);
printf("Connecting...\n");
}
For the server, we open a specific port and tell RakNet to allow a maximum of 32 connections.
void initServer(unsigned short port)
{
// have the server use the given port
SocketDescriptor socketDescriptor;
socketDescriptor.port = port;
// startup allow max 32 connections
rakPeerInterface->Startup(32, 100, &socketDescriptor, 1);
rakPeerInterface->SetMaximumIncomingConnections(32);
}
The second parameter of Startup() is how many milliseconds RakNet sleeps between update cycles. We use 100ms for this example, but 30ms or less would be more appropriate in realtime networking environments.
Packet Processing
Once we initialize the RakPeerInterface, we can starting polling for incoming packets. The first byte of every packet is designated as the type identifier, which can be RakNet-specific or user-defined. In this case, we output the name and sender of some of the core packet types.
// process all available incoming packets
Packet* packet = rakPeerInterface->Receive();
while (packet != NULL)
{
// the first packet byte is the type identifier
switch (packet->data[0])
{
case ID_CONNECTION_REQUEST_ACCEPTED:
printf(”ID_CONNECTION_REQUEST_ACCEPTED from %s\n”,
packet->systemAddress.ToString());
break;
case ID_CONNECTION_ATTEMPT_FAILED:
quit = true;
printf(”ID_CONNECTION_ATTEMPT_FAILED from %s\n”,
packet->systemAddress.ToString());
break;
case ID_NEW_INCOMING_CONNECTION:
printf(”ID_NEW_INCOMING_CONNECTION from %s\n”,
packet->systemAddress.ToString());
break;
case ID_NO_FREE_INCOMING_CONNECTIONS:
quit = true;
printf(”ID_NO_FREE_INCOMING_CONNECTIONS from %s\n”,
packet->systemAddress.ToString());
break;
case ID_DISCONNECTION_NOTIFICATION:
printf(”ID_DISCONNECTION_NOTIFICATION from %s\n”,
packet->systemAddress.ToString());
break;
case ID_CONNECTION_LOST:
printf(”ID_CONNECTION_LOST from %s\n”,
packet->systemAddress.ToString());
break;
}
// get the next packet for processing
rakPeerInterface->DeallocatePacket(packet);
packet = rakPeerInterface->Receive();
}
Here, we process all received packets every update cycle. However, if we are constantly receiving packets in a high-traffic system, we will never break from the packet processing loop - causing a stall. A more robust solution would impose a maximum number of packets processed per update cycle.
Shutdown
Upon exit, we shutdown and destroy the RakPeerInterface.
// wait 100ms to receive remain messages then shutdown rakPeerInterface->Shutdown(100, 0); RakNetworkFactory::DestroyRakPeerInterface(rakPeerInterface);
Download: bin, src
Tags: c++, game networking, incoming packets, multiplayer, multiplayer game, networking, networking environments, networking library, packet types, RakNet, tutorial
GLSL: Phong Multi-Light
Posted by Torin Kampa | Filed under GLSL
This shader program aims to completely replicate OpenGL’s fixed function lighting pipeline using per-pixel calculations. For more explanation, see my previous article on Per-Pixel Lighting (PPL).
Vertex Shader
This is a simple vertex shader that transforms the vertex position and normal into the appropriate spaces. All the lighting calculations are done per-pixel in the fragment shader.
varying vec3 normal;
varying vec3 position;
void main()
{
gl_Position = ftransform();
normal = gl_NormalMatrix * gl_Normal;
position = gl_ModelViewMatrix * gl_Vertex;
}
Fragment Shader
The fragment shader calculates and accumulates ambient, diffuse, and specular for each light. See this link for more information on how the specular component is calculated.
varying vec3 normal;
varying vec3 position;
void main()
{
// normalize the vertex normal and the view vector
normal = normalize(normal);
vec3 view = normalize(-position);
// these variables will accumulate for each light
vec4 ambient = gl_FrontLightModelProduct.sceneColor;
vec4 diffuse = 0;
vec4 specular = 0;
// accumulate ambient, diffuse, and specular for each light
for (int i = 0; i < gl_MaxLights; i++)
{
// early out if the current light is disabled
if (gl_LightSource[i].diffuse[3] == 0)
continue;
// determine the light and light reflection vectors
vec3 light =
normalize(gl_LightSource[i].position - position);
vec3 reflected = -reflect(light, normal);
// add the current light's ambient value
ambient += gl_FrontLightProduct[i].ambient;
// calculate and add the current light's diffuse value
vec4 calculatedDiffuse = max(dot(normal, light), 0.0);
diffuse += gl_FrontLightProduct[i].diffuse *
calculatedDiffuse;
// calculate and add the current light's specular value
vec4 calculatedSpecular = pow(max(dot(reflected, view), 0.0),
0.3 * gl_FrontMaterial.shininess);
specular += clamp(gl_FrontLightProduct[i].specular *
calculatedSpecular, 0.0, 1.0);
}
gl_FragColor = ambient + diffuse + specular;
}
The nifty part of the shader is its early out condition. GLSL provides many light constants but lacks an enabled boolean. Fortunately, we can get around that. A light’s diffuse color is a vec4 with the first three components used for red, green, and blue. In this shader, we hijack the unused fourth component and treat it as an light enabled/disabled bit. The following C/C++ function shows how modify this fourth component without affecting the light’s diffuse color.
void setLightEnabled(GLushort light, bool enabled)
{
// enable/disable the light for fixed function
if (enabled)
glEnable(light);
else
glDisable(light);
// use the light's 4th diffuse component to store an enabled bit
GLfloat lightDiffuse[4];
glGetLightfv(light, GL_DIFFUSE, lightDiffuse);
lightDiffuse[3] = enabled ? 1 : 0;
glLightfv(light, GL_DIFFUSE, lightDiffuse);
}
See Also
Download: bin, src
Tags: c++, fragment, glew, GLSL, glut, light reflection, lighting calculations, opengl, ppl, shader, shaders, shading language, specular component, tutorial, vertex, vertex position, vertex shader
GLSL: Per-Pixel Lighting (PPL)
Posted by Torin Kampa | Filed under GLSL
In OpenGL’s fixed function pipeline, lighting (N dot L) is computed per-vertex and interpolated over the surface of the polygon. The result is decent when using high-poly models and can be further disguised by texturing. Per-vertex lighting is, however, unacceptable on some low-poly models such as single-quad floors or walls. In this case, you must resort to using baked textures or per-pixel lighting.
Using PPL, lighting (N dot L) is computed per-pixel. Regardless of the polygon density, the light “falls off” naturally. Minor artifacts may remain if an object is particularly low-poly and approximates a curved surface such as a sphere. There is a performance hit when using PPL but that cost is minimized on newer hardware.
Vertex Shader
The vertex shader is executed for each vertex in the OpenGL pipeline. GLSL’s ftransfrom() convenience function is used transform the vertex position into model-view-projection space. We transform the normal into world normal space and the vertex in the world space so that the fragment shader can compute the lighting vector. The keyword varying allows a variable to be accessed in the fragment shader.
varying vec3 normal;
varying vec3 position;
void main()
{
gl_Position = ftransform();
normal = gl_NormalMatrix * gl_Normal;
position = gl_ModelViewMatrix * gl_Vertex;
}
Fragment Shader
The fragment shader is executed for each fragment (pixel). Whatever is assigned to gl_FragColor will be the resulting color of the pixel. The varying variables that were defined in the vertex shader are interpolated between vertices. Therefore, even though “position” is only calculated for each vertex, it is interpolated and actually corresponds to the position of each fragment.
In the fragment shader, we calculate the light vector which is the light position minus the fragment position. We can then determine the diffuse component by taking the dot product of the normal vector and the light vector.
varying vec3 normal;
varying vec3 position;
void main()
{
vec3 light = gl_LightSource[0].position - position;
gl_FragColor = dot(normalize(normal), normalize(light));
}
Note: This fragment shader is a simple example and does not take into account any light properties (ambient, diffuse, specular, attenuation) or material properties (color, texture), and only uses one light (LIGHT0).
C/C++ Implementation
Using GLSL shaders in C/C++ is straightforward. During initialization, both the vertex and fragment shaders must be compiled and then linked together into a “program.”
// load and compile the ppl vertex shader
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
const GLchar* vertexShaderSource =
readFile("..\\..\\Content\\ppl_v.glsl");
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
free((void*)vertexShaderSource);
// load and compile the ppl fragment shader
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
const GLchar* fragmentShaderSource =
readFile("..\\..\\Content\\ppl_f.glsl");
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
free((void*)fragmentShaderSource);
// create and link the ppl shader program
pplProgram = glCreateProgram();
glAttachShader(pplProgram, fragmentShader);
glAttachShader(pplProgram, vertexShader);
glLinkProgram(pplProgram);
Once the shaders are compiled and linked, you can enable/disable the shader program by calling glUseProgram.
void onKeyboard(unsigned char key, int x, int y)
{
switch (key)
{
case '1':
glUseProgram(NULL);
break;
case '2':
glUseProgram(pplProgram);
break;
}
}
See Also
Download: bin, src
Tags: c++, diffuse component, dot product, fragment, glew, GLSL, glut, light position, model view, opengl, ppl, shader, shaders, shading language, tutorial, vertex, vertex lighting, vertex position, vertex shader


