Newcastle University Graphics for Games

Implementation following "Newcastle's Graphics for Games" course but with Linux support, using SDL2 instead of the Windows API.

Source Code: https://bitbucket.org/williamblair/newcastlegraphicsforgames/src/master/

12/29/2023

Finish "Your First Triangle". Replaced all WinAPI windowing code with SDL2 window/GL context code. Replaced min/max macros with std::min/std::max. Also had to add 'const' to the last argument of OGLRenderer::DebugCallback. Implemented the Mesh and Shader classes from the tutorial. TODO implement keyboard/mouse support.

Finish "The View Matrix". Added Keyboard, Mouse, and Camera classes. Keyboard and Mouse classes take a SDL_Event as Update input instead of Windows raw data. Also, instead of an array to hold key states, use an unordered_map to map the tutorial key/button enums to sdl keycodes:

enum KeyboardKeys
{
    KEYBOARD_TAB = SDLK_TAB,
    KEYBOARD_RETURN = SDLK_RETURN,
    KEYBOARD_ESCAPE = SDLK_ESCAPE,
...
std::unordered_map<KeyboardKeys,bool> keyStates;
...
void Keyboard::Update(const SDL_Event& e)
{
    if (!isAwake) {
        return;
    }

    if (e.type == SDL_KEYDOWN) {
        keyStates[KeyboardKeys(e.key.keysym.sym)] = true;
    } else if (e.type == SDL_KEYUP) {
        keyStates[KeyboardKeys(e.key.keysym.sym)] = false;
    }
}

12/30/2023

Finish "Texture Mapping". Replaced the SOIL library with stb_image as SOIL was segfaulting during texture load:

Thread 1 "main" received signal SIGSEGV, Segmentation fault.
__strchr_avx2 () at ../sysdeps/x86_64/multiarch/strchr-avx2.S:67
67	../sysdeps/x86_64/multiarch/strchr-avx2.S: No such file or directory.
(gdb) bt
#0  __strchr_avx2 () at ../sysdeps/x86_64/multiarch/strchr-avx2.S:67
#1  0x00007ffff74beb0e in __strstr_generic (haystack=, needle=0x7ffff7a11c18 "GL_ARB_texture_non_power_of_two") at ../string/strstr.c:84
#2  0x00007ffff7a0577d in query_NPOT_capability () from /lib/libSOIL.so.1
#3  0x00007ffff7a05b4d in SOIL_internal_create_OGL_texture () from /lib/libSOIL.so.1
#4  0x00007ffff7a07b30 in SOIL_load_OGL_texture () from /lib/libSOIL.so.1
#5  0x0000555555559fa4 in Renderer::Renderer (this=0x7fffffffdec0, parent=...) at src/Renderer.cpp:9
#6  0x000055555555990f in main () at src/main.cpp:10
(gdb) 
        

Instead created function OGLRenderer::LoadGLTextureFromFile:

GLuint OGLRenderer::LoadGLTextureFromFile(const char* fileName)
{
    GLuint texId = 0;
    int width, height, numChannels;
    unsigned char* data = stbi_load(fileName, &width, &height, &numChannels, 0); 
    if (!data) {
        throw std::runtime_error("Failed to load texture: " + std::string(fileName));
    }   
    glGenTextures(1, &texId);
    glBindTexture(GL_TEXTURE_2D, texId);
    glTexImage2D(
        GL_TEXTURE_2D,
        0,  
        numChannels == 3 ? GL_RGB : GL_RGBA,
        width, height,
        0,  
        numChannels == 3 ? GL_RGB : GL_RGBA,
        GL_UNSIGNED_BYTE,
        data
    );  
    glGenerateMipmap(GL_TEXTURE_2D);
    stbi_image_free(data);
    glBindTexture(GL_TEXTURE_2D, 0); 
    return texId;
}
        

01/02/2024

Finish 'Skeletal Animation'. TODO implement the game timer, right now 1000/60 milliseconds per time delta update is hard coded. Only implemented the CPU skinning from the PDF, TODO GPU skinning from the source code. Had to add the std:: prefix to some string variables/arguments.

01/03/2024

Finish 'post processing'. Some typos in the tutorial PDFs: in Renderer.cpp, line 55:

// original
//if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE || !sceneDepthTex || !sceneColourTex[0]) {
// fixed
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE || !bufferDepthTex || !bufferColourTex[0]) {

And in processfrag.glsl, line 9:

in Vertex {
    vec2 texCoord;
    //vec4 colour; // commented out/removed
} IN;

01/04/2024

Finish 'Real Time Lighting'. Small typo in tutorial pdf in PerPixelFragment.glsl:

in Vertex
{
    //vec3 colour; // original
    vec4 colour; // fixed
    vec2 texCoord;
    vec3 normal;
    vec3 worldPos;
} IN;

01/05/2024

Finish 'Real Time Lighting B' bump mapping. Same small typo in fragment shader (BumpFragment.glsl) with the input colour data type vec4 instead of vec3.

01/06/2024

Finish 'Cube Mapping'. In order to get the skybox to show up, had to add the following to DrawSkybox() in Renderer.cpp:

glUniform1i(glGetUniformLocation(currentShader->GetProgram(),"cubeTex"),2);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubeMap);

The cube texture was bound and texture uniform set in the DrawWater function but not this one, resulting in a black background. Also added the function LoadGLCubemapFromFile to OGLRenderer to use stb_image:

GLuint OGLRenderer::LoadGLCubemapFromFile(
    const char* west,
    const char* east,
    const char* up,
    const char* down,
    const char* south,
    const char* north,
    bool flipVertically
)
{
    stbi_set_flip_vertically_on_load(flipVertically);
    GLuint axis[6] = {
        GL_TEXTURE_CUBE_MAP_POSITIVE_X,
        GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
        GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
        GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
        GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
        GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
    };
    const char* fileNames[6] = {
        west,
        east,
        up,
        down,
        south,
        north
    };
    GLuint skyBoxCubeMap;
    glGenTextures(1, &skyBoxCubeMap);
    glBindTexture(GL_TEXTURE_CUBE_MAP, skyBoxCubeMap);

    for (int i=0; i<6; ++i) {
        int width, height, numChannels;
        unsigned char* data = stbi_load(fileNames[i], &width, &height, &numChannels, 0);
        if (!data) {
            throw std::runtime_error("Failed to load texture: " + std::string(fileNames[i]));
        }
        glTexImage2D(
            axis[i],
            0,
            numChannels == 3 ? GL_RGB : GL_RGBA,
            width, height,
            0,
            numChannels == 3 ? GL_RGB : GL_RGBA,
            GL_UNSIGNED_BYTE,
            data
        );
        stbi_image_free(data);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP);
    }

    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
    return skyBoxCubeMap;
}

01/07/2024

Finish 'Shadow Mapping'. Added biasMatrix to DrawShadowScene() as it was missing in the pdf code:

Matrix4 biasMatrix;
biasMatrix.ToIdentity();
biasMatrix.values[0] = 0.5f; // scale
biasMatrix.values[5] = 0.5f;
biasMatrix.values[10] = 0.5f;
biasMatrix.values[12] = 0.5f; // translation
biasMatrix.values[13] = 0.5f;
biasMatrix.values[14] = 0.5f;

01/08/2024

Started porting to the PS Vita using https://github.com/Rinnegatamante/vitaGL. I got the first triangle and skeletal animation examples working (without input control or the game timer).

Some changes that were required to get it running:

Sense you can't view stdout while running on the Vita, I also made a logging-to-file function for use during debugging:

void VitaLog(const char* fileName, const unsigned int line, const char* msg, ...)
{
    FILE* fp = fopen("ux0:VPK/VitaLog.txt", "a");
    if (fp) {
        va_list args;
        va_start(args, msg);
        fprintf(fp, "%s:%u ", fileName, line);
        vfprintf(fp, msg, args);
        va_end(args);
        fflush(fp);
        fclose(fp);
    }   
}

I also tried to use a macro to simplify calls to it but couldn't seem to get macro varargs to compile:

//#define VITALOG(msg, ...) VitaLog(__FILE__, __LINE__, msg, __VA_ARGS__)
#define VITALOG(msg) VitaLog(__FILE__, __LINE__, msg)

01/14/2024

Finished tutorial 2 the view matrix to Vita port. Started Keyboard class impl to Vita buttons, by removing/renaming 'keyboard' keys to via controls:

enum KeyboardKeys
{
    KEYBOARD_SELECT = 0,
    KEYBOARD_START,
    KEYBOARD_LEFT,
    KEYBOARD_UP,
    KEYBOARD_RIGHT,
    KEYBOARD_DOWN,
    KEYBOARD_L, // left shoulder pad
    KEYBOARD_R, // right shoulder pad
    KEYBOARD_TRIANGLE,
    KEYBOARD_CIRCLE,
    KEYBOARD_CROSS,
    KEYBOARD_SQUARE,
    KEYBOARD_MAX
};

Then to update the 'keys':

void Keyboard::Update()
{
    if (!isAwake) {
        return;
    }

    static uint32_t old_buttons = 0;
    SceCtrlData pad;
    sceCtrlPeekBufferPositive(0, &pad, 1);

#define UPDATEBTN(vitaBtn, nclglBtn) \
    if (CHECK_BTN(vitaBtn)) { keyStates[nclglBtn] = true; } \
    else { keyStates[nclglBtn] = false; }

    UPDATEBTN(SCE_CTRL_LEFT, KEYBOARD_LEFT)
    UPDATEBTN(SCE_CTRL_UP, KEYBOARD_UP)
    UPDATEBTN(SCE_CTRL_RIGHT, KEYBOARD_RIGHT)
    UPDATEBTN(SCE_CTRL_DOWN, KEYBOARD_DOWN)
    UPDATEBTN(SCE_CTRL_SELECT, KEYBOARD_SELECT)
    UPDATEBTN(SCE_CTRL_START, KEYBOARD_START)
    UPDATEBTN(SCE_CTRL_L2, KEYBOARD_L)
    UPDATEBTN(SCE_CTRL_R2, KEYBOARD_R)
    UPDATEBTN(SCE_CTRL_TRIANGLE, KEYBOARD_TRIANGLE)
    UPDATEBTN(SCE_CTRL_CIRCLE, KEYBOARD_CIRCLE)
    UPDATEBTN(SCE_CTRL_CROSS, KEYBOARD_CROSS)
    UPDATEBTN(SCE_CTRL_SQUARE, KEYBOARD_SQUARE)

    old_buttons = pad.buttons;

}

And also rewrote the shaders to the .vert/.frag syntax:

// MatrixVertex.vert
uniform float4x4 modelMatrix;
uniform float4x4 viewMatrix;
uniform float4x4 projMatrix;

void main(
    float3 position,
    float4 colour,
    float4 out wColour : TEXCOORD0,
    float4 out gl_Position : POSITION
)
{
    wColour = colour;
    gl_Position = mul(
        mul(
            mul(float4(position,1.0f), modelMatrix),
            viewMatrix
        ),
        projMatrix
    );
}
// colourFragment.frag
float4 main(
    float4 wColour : TEXCOORD0
)
{
    return wColour;
}
<-- Back to Home