Basic 3D rendering

This commit is contained in:
var
2026-04-01 00:13:07 -05:00
parent 7b40b8702c
commit b4768a885b
14 changed files with 638 additions and 23 deletions

336
src/render.c Normal file
View File

@@ -0,0 +1,336 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SDL2/SDL.h"
#include "GL/glew.h"
#include "SDL2/SDL_opengl.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb/stb_image.h"
#include "cglm/cglm.h"
#include "render.h"
#include "camera.h"
#include "game.h"
#include "shape.h"
static void LoadTextureArray(GLuint texture, const char* filePath, int nCols, int nRows)
{
const int nMipmaps = 1;
int nTiles = nCols * nRows;
int tWidth, tHeight, tChannels;
unsigned char* textureBytes = stbi_load(filePath, &tWidth, &tHeight, &tChannels, 0);
tWidth /= nCols;
tHeight /= nRows;
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, tWidth, tHeight, nTiles, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
int tBytes = 4 * tWidth * tHeight;
unsigned char* buffer = malloc(tBytes * sizeof(unsigned char));
for (int z = 0; z < nTiles; z++)
{
for (int b = 0; b < tBytes; b++)
{
int bWidth = 4 * tWidth; // width of each tile in bytes
int i = ((z / nCols) * nCols * bWidth * tHeight) // size (in bytes) of all tiles above
+ ((b / bWidth) * bWidth * nCols) // size of pixel rows above, within the current tile row
+ ((z % nCols) * bWidth) // width of tiles to the left
+ (b % bWidth); // width from the left side of the current tile
buffer[b] = textureBytes[i];
}
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, z, tWidth, tHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
}
free(buffer);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
stbi_image_free(textureBytes);
}
static void LoadTexture(GameState* gs)
{
glGenTextures(1, &gs->textureId);
glActiveTexture(GL_TEXTURE0);
glUniform1i(glGetUniformLocation(gs->shaderProgramId, "textureSampler"), 0);
LoadTextureArray(gs->textureId, "res/tex/texture.png", 8, 4);
printf("Loaded the texture array.\n");
glBindTexture(GL_TEXTURE_2D, 0);
}
static char **ReadFileLines(const char *path, int *n)
{
const int MAX_LINES = 128;
const int MAX_LINE_LENGTH = 256;
FILE *file = fopen(path, "r");
char line[MAX_LINE_LENGTH];
char **lines = calloc(MAX_LINES, sizeof(char*));
*n = 0;
if (lines == NULL) return NULL;
while (fgets(&(line[0]), MAX_LINE_LENGTH, file) != NULL && *n < MAX_LINES)
{
GLchar *l = strdup(&(line[0]));
if (l == NULL) return NULL;
lines[*n] = l;
(*n)++;
}
fclose(file);
int s = sizeof(char*) * (*n);
lines = realloc(lines, s);
return lines;
}
static void FreeLines(char **lines, int n)
{
for (int i = 0; i < n; i++)
{
free(lines[i]);
}
free(lines);
}
static int CompileShader(GLchar *sourcePath, GLenum shaderType, char ***outCode, int *outNumLines)
{
GLint success;
*outCode = ReadFileLines(sourcePath, outNumLines);
GLuint shaderId = glCreateShader(shaderType);
glShaderSource(shaderId, *outNumLines, (void*)(*outCode), NULL);
glCompileShader(shaderId);
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success);
if (!success)
{
GLchar log[512];
glGetShaderInfoLog(shaderId, 512, NULL, log);
printf("%s\n", log);
return -1;
}
return shaderId;
}
static int LoadShaders()
{
const char *vertShaderFilePath = "./res/glsl/vertex.glsl";
char **vertCode = NULL;
int vertNumLines;
int vertShaderId = CompileShader(vertShaderFilePath, GL_VERTEX_SHADER, &vertCode, &vertNumLines);
if (vertShaderId < 0) return vertShaderId;
const char *fragShaderFilePath = "./res/glsl/fragment.glsl";
char **fragCode = NULL;
int fragNumLines;
int fragShaderId = CompileShader(fragShaderFilePath, GL_FRAGMENT_SHADER, &fragCode, &fragNumLines);
if (fragShaderId < 0 ) return fragShaderId;
GLint success;
GLuint shaderProgramId = glCreateProgram();
glAttachShader(shaderProgramId, vertShaderId);
glAttachShader(shaderProgramId, fragShaderId);
glLinkProgram(shaderProgramId);
glGetProgramiv(shaderProgramId, GL_LINK_STATUS, &success);
if (!success)
{
GLchar log[512];
glGetProgramInfoLog(shaderProgramId, 512, NULL, log);
printf("%s", log);
return -1;
}
glDeleteShader(vertShaderId);
glDeleteShader(fragShaderId);
FreeLines(vertCode, vertNumLines);
FreeLines(fragCode, fragNumLines);
return shaderProgramId;
}
static void InitShapeBuffers(Shape *shape)
{
glBindVertexArray(shape->VAO);
// vertex buffer: contains vertex and texture coords
glBindBuffer(GL_ARRAY_BUFFER, shape->VBO);
glBufferData(GL_ARRAY_BUFFER, shape->numVertices * sizeof(GLfloat), shape->vertices, GL_STATIC_DRAW);
GLsizei stride = 5 * sizeof(GLfloat);
// vertex coordinates
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)0);
// texture coordinates
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(3 * sizeof(GLfloat)));
// instance buffer: contains per-instance texture index and model transformation matrix
// therefore each instance of the same shape, in the same draw call, can have a different texture and transformation
glBindBuffer(GL_ARRAY_BUFFER, shape->IBO);
stride = SHAPE_INSTANCE_SIZE;
// texture index: basically another tex coord in the third dimension because a texture array is used
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, stride, (GLvoid*)0);
glVertexAttribDivisor(2, 1);
// model matrix: contains 16 floats, each vert attrib has space for 4 floats, therefore it spans 4 attrib locations
glEnableVertexAttribArray(3);
glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glEnableVertexAttribArray(6);
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(1 * sizeof(GLfloat)));
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(5 * sizeof(GLfloat)));
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(9 * sizeof(GLfloat)));
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(13 * sizeof(GLfloat)));
glVertexAttribDivisor(3, 1);
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1);
// index buffer: contains vertex indices, reducing the data to be buffered on the GPU
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shape->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, shape->numIndices * sizeof(GLushort), shape->indices, GL_STATIC_DRAW);
glBindVertexArray(0);
}
bool Render_Init(GameState *gs)
{
SDL_Init(SDL_INIT_VIDEO);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
gs->window = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1920, 1080, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);;
gs->glContext = SDL_GL_CreateContext(gs->window);
printf("Created OpenGL window.\n");
glewInit();
gs->shaderProgramId = LoadShaders();
if (gs->shaderProgramId < 0)
{
printf("Failed to load shaders.\n");
}
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
printf("Initialized OpenGL.\n");
Shape *shape = Shape_MakePyramid(3);
gs->testShape = shape;
glGenVertexArrays(1, &shape->VAO);
glGenBuffers(1, &shape->VBO);
glGenBuffers(1, &shape->IBO);
glGenBuffers(1, &shape->EBO);
LoadTexture(gs);
Camera_Init(&gs->camera);
InitShapeBuffers(gs->testShape);
}
void Render_Destroy(GameState *gs)
{
SDL_GL_DeleteContext(gs->glContext);
SDL_DestroyWindow(gs->window);
}
// Sets the GL viewport while maintaining aspect ratio.
// Calculates the projection and view matrices.
static void SetViewport(GameState *gs)
{
const float ratio = 0.5625f; // 1080/1920
int wWidth, wHeight, offsetX, offsetY;
SDL_GL_GetDrawableSize(gs->window, &wWidth, &wHeight);
if ((float)wHeight / (float)wWidth > ratio)
{
int temp = (int)floorf(ratio * (float)wWidth);
offsetX = 0;
offsetY = (wHeight - temp) / 2;
wHeight = temp;
}
else
{
int temp = (int)floorf((float)wHeight / ratio);
offsetX = (wWidth - temp) / 2;
offsetY = 0;
wWidth = temp;
}
glViewport(offsetX, offsetY, wWidth, wHeight);
glm_mat4_identity(gs->projMatrix);
glm_perspective(45.0f, (GLfloat)wWidth / (GLfloat)wHeight, 0.1f, 2000.0f, gs->projMatrix);
glm_mat4_identity(gs->viewMatrix);
Camera_GetViewMatrix(&gs->camera, gs->viewMatrix);
}
void Render_Draw(GameState *gs)
{
glClearColor(0.4f, 0.6f, 0.8f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
SetViewport(gs);
glUseProgram(gs->shaderProgramId);
GLint projLoc = glGetUniformLocation(gs->shaderProgramId, "theProjMatrix");
glUniformMatrix4fv(projLoc, 1, GL_FALSE, (void*)(gs->projMatrix));
GLint viewLoc = glGetUniformLocation(gs->shaderProgramId, "theViewMatrix");
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, (void*)(gs->viewMatrix));
glBindTexture(GL_TEXTURE_2D, gs->textureId);
vec3 scale = { 0, 0, 0 };
Shape *shape = gs->testShape;
glBindVertexArray(shape->VAO);
// apply transformations to each instance
for (int j = 0; j < shape->numInstances; j++)
{
ShapeInstance* instance = shape->instances + j;
mat4* matrix = (void*)(shape->instanceData + (j * 17) + 1);
mat4 tempMat;
glm_mat4_identity(tempMat);
glm_translate(tempMat, instance->position);
glm_rotate_x(tempMat, instance->rotation[0], tempMat);
glm_rotate_y(tempMat, instance->rotation[1], tempMat);
glm_rotate_z(tempMat, instance->rotation[2], tempMat);
scale[0] = instance->scale;
scale[1] = instance->scale;
scale[2] = instance->scale;
glm_scale(tempMat, scale);
memcpy(matrix, tempMat, sizeof(mat4));
}
// re-buffer the instance data because transformations may have changed
glBindBuffer(GL_ARRAY_BUFFER, shape->IBO);
glBufferData(GL_ARRAY_BUFFER, shape->numInstances * SHAPE_INSTANCE_SIZE, shape->instanceData, GL_DYNAMIC_DRAW);
glDrawElementsInstanced(GL_TRIANGLES, shape->numIndices, GL_UNSIGNED_SHORT, 0, shape->numInstances);
SDL_GL_SwapWindow(gs->window);
}