#include #include #include #include #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 "cgltf/cgltf.h" #include "render.h" #include "camera.h" #include "game.h" #include "shape.h" #include "model.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, const char *fragShaderFilePath) { char **vertCode = NULL; int vertNumLines; int vertShaderId = CompileShader(vertShaderFilePath, GL_VERTEX_SHADER, &vertCode, &vertNumLines); if (vertShaderId < 0) return vertShaderId; 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); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); gs->window = SDL_CreateWindow("Sandbox", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1920, 1080, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);; gs->glContext = SDL_GL_CreateContext(gs->window); SDL_SetRelativeMouseMode(SDL_TRUE); printf("Created OpenGL window.\n"); if (SDL_GL_SetSwapInterval(1) == 0) printf("Enabled VSync.\n"); else printf("Failed to enable VSync. SDL Error: %s\n", SDL_GetError()); glewInit(); gs->shaderProgramId = LoadShaders("./res/glsl/vertex.glsl", "./res/glsl/fragment.glsl"); gs->modelShaderProgramId = LoadShaders("./res/glsl/model_vertex.glsl", "./res/glsl/fragment.glsl"); if (gs->shaderProgramId < 0 || gs->modelShaderProgramId < 0) { printf("Failed to load shaders.\n"); return false; } 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"); gs->numShapes = 2; Shape *shape = Shape_MakePyramid(5); gs->shapes[0] = shape; glGenVertexArrays(1, &shape->VAO); glGenBuffers(1, &shape->VBO); glGenBuffers(1, &shape->IBO); glGenBuffers(1, &shape->EBO); printf("Created shape 0.\n"); shape = Shape_MakePlane(); gs->shapes[1] = shape; glGenVertexArrays(1, &shape->VAO); glGenBuffers(1, &shape->VBO); glGenBuffers(1, &shape->IBO); glGenBuffers(1, &shape->EBO); printf("Created shape 1.\n"); LoadTexture(gs); printf("Loaded textures.\n"); Camera_Init(&gs->camera); InitShapeBuffers(gs->shapes[0]); InitShapeBuffers(gs->shapes[1]); printf("Initialized buffers.\n"); gs->testModel = Model_LoadGltf("res/model/human.glb"); return true; } void Render_Destroy(GameState *gs) { Model_Free(gs->testModel); 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); } static void Transform(ShapeInstance *instance, mat4 *matrix) { mat4 tempMat; glm_mat4_identity(tempMat); glm_translate(tempMat, instance->position); glm_quat_rotate(tempMat, instance->rotation, tempMat); vec3 scale; scale[0] = instance->scale; scale[1] = instance->scale; scale[2] = instance->scale; glm_scale(tempMat, scale); memcpy(matrix, tempMat, sizeof(mat4)); } static void ApplyAnim(cgltf_animation *anim, float t, float blend) { for (int i = 0; i < anim->channels_count; i++) { cgltf_animation_channel* channel = &anim->channels[i]; cgltf_animation_sampler* sampler = channel->sampler; cgltf_node* node = channel->target_node; // find keyframe interval [k, k+1] size_t k = 0; float alpha = 0.0f; size_t num_keyframes = sampler->input->count; // binary search may be faster for (size_t j = 0; j < num_keyframes - 1; ++j) { float t0, t1; cgltf_accessor_read_float(sampler->input, j, &t0, 1); cgltf_accessor_read_float(sampler->input, j + 1, &t1, 1); if (t >= t0 && t <= t1) { k = j; float range = t1 - t0; alpha = (range > 0.0f) ? (t - t0) / range : 0.0f; break; } } // interpolate switch (channel->target_path) { case cgltf_animation_path_type_translation: { vec3 p0, p1, result; cgltf_accessor_read_float(sampler->output, k, p0, 3); cgltf_accessor_read_float(sampler->output, k + 1, p1, 3); glm_vec3_lerp(p0, p1, alpha, result); // glm_vec3_copy is not used here due to potential struct alignment issues memcpy(p0, node->translation, sizeof(vec3)); glm_vec3_lerp(p0, result, blend, result); memcpy(node->translation, result, sizeof(vec3)); node->has_translation = true; } break; case cgltf_animation_path_type_rotation: { versor q0, q1, result; cgltf_accessor_read_float(sampler->output, k, q0, 4); cgltf_accessor_read_float(sampler->output, k + 1, q1, 4); // slerp (not lerp) for rotation glm_quat_slerp(q0, q1, alpha, result); memcpy(q0, node->rotation, sizeof(versor)); glm_quat_slerp(q0, result, blend, result); memcpy(node->rotation, result, sizeof(versor)); node->has_rotation = true; } break; case cgltf_animation_path_type_scale: { vec3 s0, s1, result; cgltf_accessor_read_float(sampler->output, k, s0, 3); cgltf_accessor_read_float(sampler->output, k + 1, s1, 3); glm_vec3_lerp(s0, s1, alpha, result); memcpy(s0, node->scale, sizeof(vec3)); glm_vec3_lerp(s0, result, blend, result); memcpy(node->scale, result, sizeof(vec3)); node->has_scale = true; } break; default: break; } } } static void DrawModel(GameState *gs) { Model *model = &gs->testModel; ShapeInstance *instance = &model->instance; cgltf_data *data = model->data; cgltf_skin *skin = model->skin; mat4 modelMatrix; glm_mat4_identity(modelMatrix); Transform(instance, &modelMatrix); uint64_t ticks = SDL_GetTicks64(); float t = ticks / 1000.0f; while (t > 1.0f) t -= 1.0f; // apply local transformations for each bone for the current animation frame ApplyAnim(model->idle, t, 1.0f); ApplyAnim(model->run, t, gs->animBlend); if (!gs->input.freeLook) { // make head look where camera is looking cgltf_node *head = model->head; vec3 modelFront, camFront; glm_quat_rotatev(instance->rotation, GLM_FORWARD, modelFront); glm_vec3_copy(gs->camera.front, camFront); modelFront[1] = 0.0f; camFront[1] = 0.0f; versor desiredRotation, temp; glm_quat_from_vecs(modelFront, camFront, desiredRotation); memcpy(temp, head->rotation, sizeof(versor)); glm_quat_mul(temp, desiredRotation, temp); memcpy(head->rotation, temp, sizeof(versor)); } // recalculate the joint matrices for (int i = 0; i < skin->joints_count; i++) { cgltf_node *joint = skin->joints[i]; cgltf_node_transform_world(joint, (float*)model->jointMatrices[i]); mat4 inverseBind; cgltf_accessor_read_float(skin->inverse_bind_matrices, i, (float*)inverseBind, 16); glm_mat4_mul(model->jointMatrices[i], inverseBind, model->jointMatrices[i]); } int shaderId = gs->modelShaderProgramId; glUseProgram(shaderId); glUniformMatrix4fv(glGetUniformLocation(shaderId, "theModelMatrix"), 1, GL_FALSE, (void*)(modelMatrix)); glUniformMatrix4fv(glGetUniformLocation(shaderId, "theViewMatrix"), 1, GL_FALSE, (void*)(gs->viewMatrix)); glUniformMatrix4fv(glGetUniformLocation(shaderId, "theProjMatrix"), 1, GL_FALSE, (void*)(gs->projMatrix)); glUniformMatrix4fv(glGetUniformLocation(shaderId, "theJointMatrices"), 64, GL_FALSE, (void*)(model->jointMatrices)); glUniform1f(glGetUniformLocation(shaderId, "texIndex"), 6.0f); glBindTexture(GL_TEXTURE_2D, gs->textureId); glBindVertexArray(model->vao); glDrawElements(GL_TRIANGLES, model->indexCount, GL_UNSIGNED_SHORT, 0); } 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 }; for (int i = 0; i < gs->numShapes; i++) { Shape *shape = gs->shapes[i]; 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); Transform(instance, matrix); } // 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); } DrawModel(gs); SDL_GL_SwapWindow(gs->window); }