카테고리 없음

뻘짓) 콘솔로 래스터라이제이션 기반 큐브 렌더링하기

파워꽃게맨 2025. 4. 17. 14:43

 

 

한 3시간 걸린 것 같습니다.

재밌네요

 

전체코드

더보기

 

#include <iostream>
#define NOMINMAX
#include <Windows.h>
#include <cassert>
#include <chrono>
#include <vector>
 
#define PI 3.14159265358979323846f
#define DEG2RAD(x) ((x) * PI / 180.f)
 
struct ScreenBuffer
{
    uint8_t* buffer;
    uint32_t width;
    uint32_t height;
};
 
struct Matrix4x4
{
    float m[4][4];
 
    static Matrix4x4 Identity()
    {
        Matrix4x4 result = {};
        result.m[0][0] = 1.0f;
        result.m[1][1] = 1.0f;
        result.m[2][2] = 1.0f;
        result.m[3][3] = 1.0f;
        return result;
    }
};
 
struct Vec3
{
    Vec3 operator- (const Vec3& other) const { return { x - other.x, y - other.y, z - other.z }; }
    Vec3 operator*(const float& scalar) const { return { x * scalar, y * scalar, z * scalar }; }
    Vec3 operator+(const Vec3& other) const { return { x + other.x, y + other.y, z + other.z }; }
    float Dot(const Vec3& other) const { return x * other.x + y * other.y + z * other.z; }
    Vec3 Cross(const Vec3& other) const
    {
        return {
            y * other.z - z * other.y,
            z * other.x - x * other.z,
            x * other.y - y * other.x
        };
    }
    Vec3 Normalize() const
    {
        float length = sqrtf(x * x + y * y + z * z);
        return { x / length, y / length, z / length };
    }
 
    float x, y, z;
};
 
struct Vec4
{
    Vec4() = default;
    Vec4(const Vec3& vec3, float w) : x(vec3.x), y(vec3.y), z(vec3.z), w(w) {}
 
    Vec3 ToVec3() const { return Vec3{ x,y,z }; }
 
    float x, y, z, w;
};
 
 
struct GrayScale
{
    float scale;
};
 
struct Vertex
{
    Vec3 posL;
    Vec3 normal;
    GrayScale color;
};
 
struct PSInput
{
    Vec4 posH;
    Vec3 posW;
    Vec3 normal;
    GrayScale color;
};
 
struct Context
{
    ScreenBuffer frontBuffer;
    float* depthBuffer;
 
    HANDLE mainConsole;
 
    bool prevKeyInput[256];
    bool keyInput[256];
 
    float deltaTime;
    std::chrono::high_resolution_clock::time_point lastTime;
 
    PSInput(*vertexShader)(const Vertex&);
    GrayScale(*pixelShader)(const PSInput&);
} g_context;
 
void InitContext(uint32_t width, uint32_t height);
void ReleaseContext();
void DrawPixel(const ScreenBuffer* in_buffer, uint32_t x, uint32_t y, uint8_t color);
void PresentToConsole(const ScreenBuffer* in_buffer);
char CvtColorToChar(uint8_t color);
void ClearScreenBuffer(const ScreenBuffer* in_buffer, uint8_t color);
 
void InitContext(uint32_t width, uint32_t height)
{
    g_context.mainConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    g_context.frontBuffer.width = width;
    g_context.frontBuffer.height = height;
    g_context.frontBuffer.buffer = new uint8_t[width * height];
    g_context.depthBuffer = new float[width * height];
 
    g_context.lastTime = std::chrono::high_resolution_clock::now();
 
    ClearScreenBuffer(&g_context.frontBuffer, 0);
    PresentToConsole(&g_context.frontBuffer);
}
 
void ReleaseContext()
{
    delete[] g_context.frontBuffer.buffer;
    delete[] g_context.depthBuffer;
}
 
char CvtColorToChar(uint8_t color)
{
    uint8_t key = (color / 255.f) * 9.f;
 
    switch (key)
    {
    case 0: return ' ';
    case 1: return '.';
    case 2: return ':';
    case 3: return '-';
    case 4: return '=';
    case 5: return '+';
    case 6: return '*';
    case 7: return '#';
    case 8: return '%';
    case 9: return '@';
    default: return ' ';
    }
}
 
void DrawPixel(const ScreenBuffer* in_buffer, uint32_t x, uint32_t y, uint8_t color)
{
    if (x < in_buffer->width && y < in_buffer->height)
        in_buffer->buffer[y * in_buffer->width + x] = CvtColorToChar(color);
}
 
void PresentToConsole(const ScreenBuffer* in_buffer)
{
    DWORD written;
 
    for (int y = 0; y < in_buffer->height; y++)
    {
        WriteConsoleA(g_context.mainConsole,
            in_buffer->buffer + y * in_buffer->width,
            in_buffer->width,
            &written, nullptr);
 
        WriteConsoleA(g_context.mainConsole,
            "\n",
            1,
            &written, nullptr);
    }
 
    SetConsoleCursorPosition(g_context.mainConsole, { 0, 0 });
}
 
void ClearScreenBuffer(const ScreenBuffer* in_buffer, uint8_t color)
{
    std::memset(in_buffer->buffer, CvtColorToChar(color), in_buffer->width * in_buffer->height);
}
 
void ClearDepthBuffer(const ScreenBuffer* in_buffer, float depth)
{
    std::memset(g_context.depthBuffer, depth, in_buffer->width * in_buffer->height * sizeof(float));
}
 
bool DepthTest(float depth, uint32_t x, uint32_t y)
{
    if (depth > g_context.depthBuffer[y * g_context.frontBuffer.width + x])
    {
        g_context.depthBuffer[y * g_context.frontBuffer.width + x] = depth;
        return true;
    }
    return false;
}
 
void UpdateInput()
{
    for (int i = 0; i < 256; i++)
    {
        if (GetAsyncKeyState(i) & 0x8000)
            g_context.keyInput[i] = true;
        else
            g_context.keyInput[i] = false;
    }
 
    std::memcpy(g_context.prevKeyInput, g_context.keyInput, sizeof(g_context.prevKeyInput));
}
 
void UpdateTimer()
{
    auto now = std::chrono::high_resolution_clock::now();
    g_context.deltaTime = std::chrono::duration<float>(now - g_context.lastTime).count();
    g_context.lastTime = now;
 
}
 
bool IsKeyPressed(int key) { return g_context.keyInput[key] && !g_context.prevKeyInput[key]; }
bool IsKeyReleased(int key) { return !g_context.keyInput[key] && g_context.prevKeyInput[key]; }
bool IsKeyDown(int key) { return g_context.keyInput[key]; }
bool IsKeyUp(int key) { return !g_context.keyInput[key]; }
 
Matrix4x4 Multiply(const Matrix4x4& a, const Matrix4x4& b)
{
    Matrix4x4 result;
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            result.m[i][j] = a.m[i][0] * b.m[0][j] +
                a.m[i][1] * b.m[1][j] +
                a.m[i][2] * b.m[2][j] +
                a.m[i][3] * b.m[3][j];
        }
    }
    return result;
}
 
Matrix4x4 Invert(const Matrix4x4& mat)
{
    Matrix4x4 inv;
    float det = 0.0f;
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            inv.m[i][j] = mat.m[j][i];
        }
    }
    det = mat.m[0][0] * inv.m[1][1] * inv.m[2][2] * inv.m[3][3] +
        mat.m[0][1] * inv.m[1][2] * inv.m[2][3] * inv.m[3][0] +
        mat.m[0][2] * inv.m[1][3] * inv.m[2][0] * inv.m[3][1] +
        mat.m[0][3] * inv.m[1][0] * inv.m[2][1] * inv.m[3][2];
    return inv;
}
 
Matrix4x4 Transpose(const Matrix4x4& mat)
{
    Matrix4x4 result;
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
            result.m[i][j] = mat.m[j][i];
    }
    return result;
}
 
Vec4 TransformVector(const Matrix4x4& mat, const Vec4& vec)
{
    Vec4 result;
    result.x = mat.m[0][0] * vec.x + mat.m[0][1] * vec.y + mat.m[0][2] * vec.z + mat.m[0][3] * vec.w;
    result.y = mat.m[1][0] * vec.x + mat.m[1][1] * vec.y + mat.m[1][2] * vec.z + mat.m[1][3] * vec.w;
    result.z = mat.m[2][0] * vec.x + mat.m[2][1] * vec.y + mat.m[2][2] * vec.z + mat.m[2][3] * vec.w;
    result.w = mat.m[3][0] * vec.x + mat.m[3][1] * vec.y + mat.m[3][2] * vec.z + mat.m[3][3] * vec.w;
    return result;
}
 
Vec4 TransformVector(const Matrix4x4& mat, const Vec3& vec)
{
    Vec4 result;
 
    result.x = mat.m[0][0] * vec.x + mat.m[0][1] * vec.y + mat.m[0][2] * vec.z + mat.m[0][3] * 1.f;
    result.y = mat.m[1][0] * vec.x + mat.m[1][1] * vec.y + mat.m[1][2] * vec.z + mat.m[1][3] * 1.f;
    result.z = mat.m[2][0] * vec.x + mat.m[2][1] * vec.y + mat.m[2][2] * vec.z + mat.m[2][3] * 1.f;
    result.w = mat.m[3][0] * vec.x + mat.m[3][1] * vec.y + mat.m[3][2] * vec.z + mat.m[3][3] * 1.f;
    return result;
}
 
Matrix4x4 CreateWorld(const Vec3& translate, const Vec3& yawPitchRoll, const Vec3& scale)
{
    Matrix4x4 world;
 
    float cy = cos(yawPitchRoll.y);
    float sy = sin(yawPitchRoll.y);
    float cp = cos(yawPitchRoll.x);
    float sp = sin(yawPitchRoll.x);
    float cr = cos(yawPitchRoll.z);
    float sr = sin(yawPitchRoll.z);
 
    Matrix4x4 rotationYaw = {
        cy, 0.0f, sy, 0.0f,
        0.0f, 1.0f, 0.0f, 0.0f,
        -sy, 0.0f, cy, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f
    };
 
    Matrix4x4 rotationPitch = {
        1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, cp, sp, 0.0f,
        0.0f, -sp, cp, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f
    };
 
    Matrix4x4 rotationRoll = {
        cr, sr, 0.0f, 0.0f,
        -sr, cr, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f
    };
 
    Matrix4x4 rotation = Multiply(rotationYaw, Multiply(rotationPitch, rotationRoll));
 
    world.m[0][0] = scale.x * rotation.m[0][0];
    world.m[0][1] = scale.x * rotation.m[0][1];
    world.m[0][2] = scale.x * rotation.m[0][2];
    world.m[0][3] = translate.x;
 
    world.m[1][0] = scale.y * rotation.m[1][0];
    world.m[1][1] = scale.y * rotation.m[1][1];
    world.m[1][2] = scale.y * rotation.m[1][2];
    world.m[1][3] = translate.y;
 
    world.m[2][0] = scale.z * rotation.m[2][0];
    world.m[2][1] = scale.z * rotation.m[2][1];
    world.m[2][2] = scale.z * rotation.m[2][2];
    world.m[2][3] = translate.z;
 
    world.m[3][0] = 0.0f;
    world.m[3][1] = 0.0f;
    world.m[3][2] = 0.0f;
    world.m[3][3] = 1.0f;
 
    return world;
}
 
Matrix4x4 CreateView(const Vec3& cameraPosition, const Vec3& lookDirection)
{
    Vec3 zAxis = (lookDirection * -1.0f).Normalize();
 
    Vec3 up = { 0.0f, 1.0f, 0.0f };
 
    float dot = std::abs(up.Dot(zAxis));
    if (dot > 0.999f)
    {
        up = { 0.0f, 0.0f, 1.0f };
        if (std::abs(zAxis.Dot(up)) > 0.999f)
        {
            up = { 1.0f, 0.0f, 0.0f };
        }
    }
 
    Vec3 xAxis = up.Cross(zAxis).Normalize();
 
    Vec3 yAxis = zAxis.Cross(xAxis).Normalize();
 
    Matrix4x4 view = {
        xAxis.x, xAxis.y, xAxis.z, -xAxis.Dot(cameraPosition),
        yAxis.x, yAxis.y, yAxis.z, -yAxis.Dot(cameraPosition),
        zAxis.x, zAxis.y, zAxis.z, -zAxis.Dot(cameraPosition),
        0.0f, 0.0f, 0.0f, 1.0f
    };
 
    return view;
}
 
Matrix4x4 CreateProjection(float fov, float aspect, float nearZ, float farZ)
{
    float f = 1.0f / tanf(fov * 0.5f);
    float rangeInv = 1.0f / (nearZ - farZ);
    Matrix4x4 projection = {
        f / aspect, 0.0f, 0.0f, 0.0f,
        0.0f, f, 0.0f, 0.0f,
        0.0f, 0.0f, (nearZ + farZ) * rangeInv, -1.0f,
        0.0f, 0.0f, (2 * nearZ * farZ) * rangeInv, 0.0f
    };
    return projection;
}
 
void SetVertexShader(PSInput(*vertexShader)(const Vertex&))
{
    g_context.vertexShader = vertexShader;
}
 
void SetPixelShader(GrayScale(*pixelShader)(const PSInput&))
{
    g_context.pixelShader = pixelShader;
}
 
void Render(const Vertex* vertices, uint32_t vertexCount,
    const uint32_t* indices, uint32_t indexCount)
{
    assert(g_context.vertexShader && "Vertex shader not set");
    assert(g_context.pixelShader && "Pixel shader not set");
 
    std::vector<PSInput> rasterizerInput;
    rasterizerInput.reserve(vertexCount);
 
    // vertex shader
    for (uint32_t i = 0; i < vertexCount; i++)
    {
        Vertex vertex = vertices[i];
        PSInput psInput = g_context.vertexShader(vertex);
 
        // perspective divide
        psInput.posH.x /= psInput.posH.w;
        psInput.posH.y /= psInput.posH.w;
        psInput.posH.z /= psInput.posH.w;
        psInput.posH.w = 1.0f;
 
        // viewport transformation
        psInput.posH.x = (psInput.posH.x + 1.0f) * 0.5f * g_context.frontBuffer.width;
        psInput.posH.y = (-psInput.posH.y + 1.0f) * 0.5f * g_context.frontBuffer.height;
 
        rasterizerInput.emplace_back(psInput);
    }
 
    // rasterization
    for (size_t i = 0; i < indexCount / 3; ++i)
    {
        PSInput psInput1 = rasterizerInput[indices[i * 3]];
        PSInput psInput2 = rasterizerInput[indices[i * 3 + 1]];
        PSInput psInput3 = rasterizerInput[indices[i * 3 + 2]];
 
        // back face culling
        Vec3 edge1 = psInput2.posW - psInput1.posW;
        Vec3 edge2 = psInput3.posW - psInput1.posW;
        Vec3 normal = edge2.Cross(edge1);
        if (normal.z > 0.0f)
            continue;
 
        // bounding box
        int minX = std::min({ psInput1.posH.x, psInput2.posH.x, psInput3.posH.x });
        int maxX = std::max({ psInput1.posH.x, psInput2.posH.x, psInput3.posH.x });
        int minY = std::min({ psInput1.posH.y, psInput2.posH.y, psInput3.posH.y });
        int maxY = std::max({ psInput1.posH.y, psInput2.posH.y, psInput3.posH.y });
        minX = std::max(minX, 0);
        maxX = std::min(maxX, static_cast<int>(g_context.frontBuffer.width) - 1);
        minY = std::max(minY, 0);
        maxY = std::min(maxY, static_cast<int>(g_context.frontBuffer.height) - 1);
 
        // triangle rasterization
        for (int y = minY; y <= maxY; ++y)
        {
            for (int x = minX; x <= maxX; ++x)
            {
                // barycentric coordinates
                float alpha = ((psInput2.posH.y - psInput3.posH.y) * (x - psInput3.posH.x) +
                    (psInput3.posH.x - psInput2.posH.x) * (y - psInput3.posH.y)) /
                    ((psInput2.posH.y - psInput3.posH.y) * (psInput1.posH.x - psInput3.posH.x) +
                        (psInput3.posH.x - psInput2.posH.x) * (psInput1.posH.y - psInput3.posH.y));
 
                float beta = ((psInput3.posH.y - psInput1.posH.y) * (x - psInput3.posH.x) +
                    (psInput1.posH.x - psInput3.posH.x) * (y - psInput3.posH.y)) /
                    ((psInput2.posH.y - psInput3.posH.y) * (psInput1.posH.x - psInput3.posH.x) +
                        (psInput3.posH.x - psInput2.posH.x) * (psInput1.posH.y - psInput3.posH.y));
 
                float gamma = 1.0f - alpha - beta;
 
                if (alpha >= 0 && beta >= 0 && gamma >= 0)
                {
                    // interpolate color
                    uint8_t color = static_cast<uint8_t>(alpha * psInput1.color.scale +
                        beta * psInput2.color.scale +
                        gamma * psInput3.color.scale);
 
                    float z = alpha * psInput1.posH.z +
                        beta * psInput2.posH.z +
                        gamma * psInput3.posH.z;
 
                    if (!DepthTest(z, x, y))
                        continue;
 
                    PSInput input;
                    input.posH.x = x;
                    input.posH.y = y;
                    input.posH.z = z;
                    input.posH.w = 1.0f;
                    input.color.scale = color;
                    input.normal = psInput1.normal * alpha +
                        psInput2.normal * beta +
                        psInput3.normal * gamma;
                    input.posW = psInput1.posW * alpha +
                        psInput2.posW * beta +
                        psInput3.posW * gamma;
 
                    
 
                    // pixel shader
                    GrayScale output = g_context.pixelShader(input);
 
                    uint8_t grayScale = output.scale * 255.f;
                    DrawPixel(&g_context.frontBuffer, x, y, grayScale);
                }
            }
        }
    }
}
 
//////////////////////////////////////////////////////////////////////////
 
// cube
 
Vertex vertices[36] = {
 
    // front
    { { -1.f, -1.f, 1.f }, { 0.0f, 0.0f, 1.0f }, { 1.f } },
    { { 1.f, -1.f, 1.f }, { 0.0f, 0.0f, 1.0f }, { 1.f } },
    { { 1.f, 1.f, 1.f }, { 0.0f, 0.0f, 1.0f }, { 1.f } },
    { { -1.f, 1.f, 1.f }, { 0.0f, 0.0f, 1.0f }, { 1.f } },
 
    // right
    { { 1.f, -1.f, 1.f }, { 1.0f, 0.0f, 0.0f }, { 1.f } },
    { { 1.f, -1.f, -1.f }, { 1.0f, 0.0f, 0.0f }, { 1.f } },
    { { 1.f, 1.f, -1.f }, { 1.0f, 0.0f, 0.0f }, { 1.f } },
    { { 1.f, 1.f, 1.f }, { 1.0f, 0.0f, 0.0f }, { 1.f } },
 
    // top
    { { -1.f, 1.f, 1.f }, { 0.0f, 1.0f, 0.0f }, { 1.f } },
    { { 1.f, 1.f, 1.f }, { 0.0f, 1.0f, 0.0f }, { 1.f } },
    { { 1.f, 1.f, -1.f }, { 0.0f, 1.0f, 0.0f }, { 1.f } },
    { { -1.f, 1.f, -1.f }, { 0.0f, 1.0f, 0.0f }, { 1.f } },
 
    // back
    { { 1.f, -1.f, -1.f }, { 0.0f, 0.0f, -1.0f }, { 1.f } },
    { { -1.f, -1.f, -1.f }, { 0.0f, 0.0f, -1.0f }, { 1.f } },
    { { -1.f, 1.f, -1.f }, { 0.0f, 0.0f, -1.0f }, { 1.f } },
    { { 1.f, 1.f, -1.f }, { 0.0f, 0.0f, -1.0f }, { 1.f } },
 
    // left
    { { -1.f, -1.f, -1.f }, { -1.0f, 0.0f, 0.0f }, { 1.f } },
    { { -1.f, -1.f, 1.f }, { -1.0f, 0.0f, 0.0f }, { 1.f } },
    { { -1.f, 1.f, 1.f }, { -1.0f, 0.0f, 0.0f }, { 1.f } },
    { { -1.f, 1.f, -1.f }, { -1.0f, 0.0f, 0.0f }, { 1.f } },
 
    // bottom
    { { -1.f, -1.f, -1.f }, { 0.0f, -1.0f, 0.0f }, { 1.f } },
    { { 1.f, -1.f, -1.f }, { 0.0f, -1.0f, 0.0f }, { 1.f } },
    { { 1.f, -1.f, 1.f }, { 0.0f, -1.0f, 0.0f }, { 1.f } },
    { { -1.f, -1.f, 1.f }, { 0.0f, -1.0f, 0.0f }, { 1.f } }
};
 
uint32_t indices[36] = {
    0, 1, 2, 0, 2, 3,
    4, 5, 6, 4, 6, 7,
    8, 9, 10, 8, 10, 11,
    12, 13, 14, 12, 14, 15,
    16, 17, 18, 16, 18, 19,
    20, 21, 22, 20, 22, 23
};
 
struct Transform
{
    Vec3 translate;
    Vec3 yawPitchRoll;
    Vec3 scale;
} g_transform = { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f } };
Transform g_cameraTransform = { { 0.0f, 0.0f, 15.f }, { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f } };
 
struct Uniforms
{
    Matrix4x4 worldMatrix;
    Matrix4x4 worldInverseTransposeMatrix;
    Matrix4x4 viewMatrix;
    Matrix4x4 projectionMatrix;
 
    Vec3 lightPosition;
    Vec3 cameraPosition;
 
} g_uniforms;
 
PSInput VertexShader(const Vertex& vertex)
{
    Vec3 posW = TransformVector(g_uniforms.worldMatrix, vertex.posL).ToVec3();
    Vec3 normalW = TransformVector(g_uniforms.worldInverseTransposeMatrix, vertex.normal).ToVec3();
    Matrix4x4 projViewMatrix = Multiply(g_uniforms.projectionMatrix, g_uniforms.viewMatrix);
    Vec4 posP = TransformVector(projViewMatrix, posW);
 
    PSInput input;
    input.posH = posP;
    input.posW = posW;
    input.normal = normalW;
    input.color = vertex.color;
 
    return input;
}
 
GrayScale PixelShader(const PSInput& input)
{
    GrayScale output;
    Vec3 normal = input.normal.Normalize();
    Vec3 lightDir = (g_uniforms.lightPosition - input.posW).Normalize();
    Vec3 toEye = (g_uniforms.cameraPosition - input.posW).Normalize();
    Vec3 halfVector = (lightDir + toEye).Normalize();
 
    float diffuse = std::max(normal.Dot(lightDir), 0.0f);
    float ambient = 0.2f;
    output.scale = ambient + diffuse;
    output.scale = std::max(output.scale, 0.0f);
    output.scale = std::min(output.scale, 1.0f);
 
    return output;
}
 
int main()
{
    InitContext(240, 80);
 
    g_uniforms.lightPosition = { 5.0f, 5.0f, 5.0f };
    SetVertexShader(VertexShader);
    SetPixelShader(PixelShader);
 
    while (true)
    {
        UpdateInput();
        UpdateTimer();
 
        if (IsKeyDown(VK_ESCAPE))
            break;
 
        ClearScreenBuffer(&g_context.frontBuffer, 0);
        ClearDepthBuffer(&g_context.frontBuffer, 0.f);
 
        // Translate
        if (IsKeyDown(VK_LEFT))
            g_transform.translate.x -= 1.0f * g_context.deltaTime;
 
        if (IsKeyDown(VK_RIGHT))
            g_transform.translate.x += 1.0f * g_context.deltaTime;
 
        if (IsKeyDown(VK_UP))
            g_transform.translate.y += 1.0f * g_context.deltaTime;
 
        if (IsKeyDown(VK_DOWN))
            g_transform.translate.y -= 1.0f * g_context.deltaTime;
 
        // Rotate
        if (IsKeyDown('A'))
            g_transform.yawPitchRoll.y -= 1.0f * g_context.deltaTime;
 
        if (IsKeyDown('D'))
            g_transform.yawPitchRoll.y += 1.0f * g_context.deltaTime;
 
        if (IsKeyDown('W'))
            g_transform.yawPitchRoll.x -= 1.0f * g_context.deltaTime;
 
        if (IsKeyDown('S'))
            g_transform.yawPitchRoll.x += 1.0f * g_context.deltaTime;
 
        // Scale
        if (IsKeyDown('Q'))
        {
            g_transform.scale.x -= 1.0f * g_context.deltaTime;
            g_transform.scale.y -= 1.0f * g_context.deltaTime;
        }
 
        if (IsKeyDown('E'))
        {
            g_transform.scale.x += 1.0f * g_context.deltaTime;
            g_transform.scale.y += 1.0f * g_context.deltaTime;
        }
 
        // camera position
        if (IsKeyDown('Z'))
            g_cameraTransform.translate.z -= 1.0f * g_context.deltaTime;
 
        if (IsKeyDown('X'))
            g_cameraTransform.translate.z += 1.0f * g_context.deltaTime;
 
        // light position
        if (IsKeyDown('I'))
            g_uniforms.lightPosition.z -= 1.0f * g_context.deltaTime;
 
        if (IsKeyDown('K'))
            g_uniforms.lightPosition.z += 1.0f * g_context.deltaTime;
 
        if (IsKeyDown('J'))
            g_uniforms.lightPosition.x -= 1.0f * g_context.deltaTime;
 
        if (IsKeyDown('L'))
            g_uniforms.lightPosition.x += 1.0f * g_context.deltaTime;
 
        if (IsKeyDown('U'))
            g_uniforms.lightPosition.y -= 1.0f * g_context.deltaTime;
 
        if (IsKeyDown('O'))
            g_uniforms.lightPosition.y += 1.0f * g_context.deltaTime;
 
        g_uniforms.worldMatrix = CreateWorld(g_transform.translate, g_transform.yawPitchRoll, g_transform.scale);
        g_uniforms.worldInverseTransposeMatrix = Invert(Transpose(g_uniforms.worldMatrix));
        g_uniforms.viewMatrix = CreateView(g_cameraTransform.translate, { 0.0f, 0.0f, -1.0f });
        g_uniforms.projectionMatrix = CreateProjection(DEG2RAD(60.f),
            1.66f, 0.1f, 100.f);
        g_uniforms.cameraPosition = g_cameraTransform.translate;
 
        Render(vertices, std::size(vertices), indices, std::size(indices));
 
        PresentToConsole(&g_context.frontBuffer);
    }
 
    ReleaseContext();
}