/* * Copyright (C) 2022 - 2023 spnda * This file is part of fastgltf . * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include #include constexpr std::string_view vertexShaderSource = R"( #version 460 core layout(location = 0) in vec3 position; layout(location = 1) in vec2 inTexCoord; uniform mat4 modelMatrix; uniform mat4 viewProjectionMatrix; out vec2 texCoord; void main() { gl_Position = viewProjectionMatrix * modelMatrix * vec4(position, 1.0); texCoord = inTexCoord; } )"; constexpr std::string_view fragmentShaderSource = R"( #version 460 core in vec2 texCoord; out vec4 finalColor; const uint HAS_BASE_COLOR_TEXTURE = 1; layout(location = 0) uniform sampler2D albedoTexture; layout(location = 0, std140) uniform MaterialUniforms { vec4 baseColorFactor; float alphaCutoff; uint flags; } material; float rand(vec2 co){ return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); } void main() { vec4 color = material.baseColorFactor; if ((material.flags & HAS_BASE_COLOR_TEXTURE) == HAS_BASE_COLOR_TEXTURE) { color *= texture(albedoTexture, texCoord); } float factor = (rand(gl_FragCoord.xy) - 0.5) / 8; if (color.a < material.alphaCutoff + factor) discard; finalColor = color; } )"; void glMessageCallback(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam) { if (severity == GL_DEBUG_SEVERITY_HIGH) { std::cerr << message << '\n'; } else { std::cout << message << '\n'; } } bool checkGlCompileErrors(GLuint shader) { GLint success; constexpr int length = 1024; std::string log; log.resize(length); glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (success != GL_TRUE) { glGetShaderInfoLog(shader, length, nullptr, log.data()); std::cout << "Shader compilation error: " << "\n" << log << "\n -- --------------------------------------------------- -- " << '\n'; return false; } return true; } bool checkGlLinkErrors(GLuint target) { GLint success; constexpr int length = 1024; std::string log; log.resize(length); glGetProgramiv(target, GL_LINK_STATUS, &success); if (success != GL_TRUE) { glGetShaderInfoLog(target, length, nullptr, log.data()); std::cout << "Shader program linking error: " << "\n" << log << "\n -- --------------------------------------------------- -- " << '\n'; return false; } return true; } struct IndirectDrawCommand { uint32_t count; uint32_t instanceCount; uint32_t firstIndex; int32_t baseVertex; uint32_t baseInstance; }; struct Primitive { IndirectDrawCommand draw; GLenum primitiveType; GLenum indexType; GLuint vertexArray; size_t materialUniformsIndex; GLuint albedoTexture; }; struct Mesh { GLuint drawsBuffer; std::vector primitives; }; struct Texture { GLuint texture; }; enum MaterialUniformFlags : uint32_t { None = 0 << 0, HasBaseColorTexture = 1 << 0, }; struct MaterialUniforms { glm::fvec4 baseColorFactor; float alphaCutoff; uint32_t flags; }; struct Viewer { fastgltf::Asset asset; std::vector buffers; std::vector bufferAllocations; std::vector meshes; std::vector textures; std::vector materials; std::vector materialBuffers; glm::mat4 viewMatrix = glm::mat4(1.0f); glm::mat4 projectionMatrix = glm::mat4(1.0f); GLint viewProjectionMatrixUniform = GL_NONE; GLint modelMatrixUniform = GL_NONE; float lastFrame = 0.0f; float deltaTime = 0.0f; glm::vec3 accelerationVector = glm::vec3(0.0f); glm::vec3 velocity = glm::vec3(0.0f); glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f); glm::dvec2 lastCursorPosition = glm::dvec2(0.0f); glm::vec3 direction = glm::vec3(0.0f, 0.0f, -1.0f); float yaw = -90.0f; float pitch = 0.0f; bool firstMouse = true; }; void updateCameraMatrix(Viewer* viewer) { glm::mat4 viewProjection = viewer->projectionMatrix * viewer->viewMatrix; glUniformMatrix4fv(viewer->viewProjectionMatrixUniform, 1, GL_FALSE, &viewProjection[0][0]); } void windowSizeCallback(GLFWwindow* window, int width, int height) { void* ptr = glfwGetWindowUserPointer(window); auto* viewer = static_cast(ptr); viewer->projectionMatrix = glm::perspective(glm::radians(75.0f), static_cast(width) / static_cast(height), 0.01f, 1000.0f); glViewport(0, 0, width, height); } void cursorCallback(GLFWwindow* window, double xpos, double ypos) { void* ptr = glfwGetWindowUserPointer(window); auto* viewer = static_cast(ptr); if (viewer->firstMouse) { viewer->lastCursorPosition = { xpos, ypos }; viewer->firstMouse = false; } auto offset = glm::vec2(xpos - viewer->lastCursorPosition.x, viewer->lastCursorPosition.y - ypos); viewer->lastCursorPosition = { xpos, ypos }; offset *= 0.1f; viewer->yaw += offset.x; viewer->pitch += offset.y; viewer->pitch = glm::clamp(viewer->pitch, -89.0f, 89.0f); auto& direction = viewer->direction; direction.x = cos(glm::radians(viewer->yaw)) * cos(glm::radians(viewer->pitch)); direction.y = sin(glm::radians(viewer->pitch)); direction.z = sin(glm::radians(viewer->yaw)) * cos(glm::radians(viewer->pitch)); direction = glm::normalize(direction); } void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { void* ptr = glfwGetWindowUserPointer(window); auto* viewer = static_cast(ptr); constexpr glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); auto& acceleration = viewer->accelerationVector; switch (key) { case GLFW_KEY_W: acceleration += viewer->direction; break; case GLFW_KEY_S: acceleration -= viewer->direction; break; case GLFW_KEY_D: acceleration += glm::normalize(glm::cross(viewer->direction, cameraUp)); break; case GLFW_KEY_A: acceleration -= glm::normalize(glm::cross(viewer->direction, cameraUp)); break; default: break; } } glm::mat4 getTransformMatrix(const fastgltf::Node& node, glm::mat4x4& base) { /** Both a matrix and TRS values are not allowed * to exist at the same time according to the spec */ if (const auto* pMatrix = std::get_if(&node.transform)) { return base * glm::mat4x4(glm::make_mat4x4(pMatrix->data())); } if (const auto* pTransform = std::get_if(&node.transform)) { // Warning: The quaternion to mat4x4 conversion here is not correct with all versions of glm. // glTF provides the quaternion as (x, y, z, w), which is the same layout glm used up to version 0.9.9.8. // However, with commit 59ddeb7 (May 2021) the default order was changed to (w, x, y, z). // You could either define GLM_FORCE_QUAT_DATA_XYZW to return to the old layout, // or you could use the recently added static factory constructor glm::quat::wxyz(w, x, y, z), // which guarantees the parameter order. return base * glm::translate(glm::mat4(1.0f), glm::make_vec3(pTransform->translation.data())) * glm::toMat4(glm::make_quat(pTransform->rotation.data())) * glm::scale(glm::mat4(1.0f), glm::make_vec3(pTransform->scale.data())); } return base; } bool loadGltf(Viewer* viewer, std::string_view cPath) { if (!std::filesystem::exists(cPath)) { std::cout << "Failed to find " << cPath << '\n'; return false; } std::cout << "Loading " << cPath << '\n'; // Parse the glTF file and get the constructed asset { fastgltf::Parser parser(fastgltf::Extensions::KHR_mesh_quantization); auto path = std::filesystem::path{cPath}; constexpr auto gltfOptions = fastgltf::Options::DontRequireValidAssetMember | fastgltf::Options::AllowDouble | fastgltf::Options::LoadGLBBuffers | fastgltf::Options::LoadExternalBuffers | fastgltf::Options::LoadExternalImages | fastgltf::Options::GenerateMeshIndices; fastgltf::GltfDataBuffer data; data.loadFromFile(path); auto type = fastgltf::determineGltfFileType(&data); fastgltf::Expected asset(fastgltf::Error::None); if (type == fastgltf::GltfType::glTF) { asset = parser.loadGLTF(&data, path.parent_path(), gltfOptions); } else if (type == fastgltf::GltfType::GLB) { asset = parser.loadBinaryGLTF(&data, path.parent_path(), gltfOptions); } else { std::cerr << "Failed to determine glTF container" << '\n'; return false; } if (asset.error() != fastgltf::Error::None) { std::cerr << "Failed to load glTF: " << fastgltf::getErrorMessage(asset.error()) << '\n'; return false; } viewer->asset = std::move(asset.get()); } // Some buffers are already allocated during parsing of the glTF, like e.g. base64 buffers // through our callback functions. Therefore, we only resize our output buffer vector, but // create our buffer handles later on. auto& buffers = viewer->asset.buffers; viewer->buffers.reserve(buffers.size()); for (auto& buffer : buffers) { constexpr GLuint bufferUsage = GL_STATIC_DRAW; std::visit(fastgltf::visitor { [](auto& arg) {}, // Covers FilePathWithOffset, BufferView, ... which are all not possible [&](fastgltf::sources::Vector& vector) { GLuint glBuffer; glCreateBuffers(1, &glBuffer); glNamedBufferData(glBuffer, static_cast(buffer.byteLength), vector.bytes.data(), bufferUsage); viewer->buffers.emplace_back(glBuffer); }, [&](fastgltf::sources::CustomBuffer& customBuffer) { // We don't need to do anything special here, the buffer has already been created. viewer->buffers.emplace_back(static_cast(customBuffer.id)); }, }, buffer.data); } return true; } bool loadMesh(Viewer* viewer, fastgltf::Mesh& mesh) { auto& asset = viewer->asset; Mesh outMesh = {}; outMesh.primitives.resize(mesh.primitives.size()); for (auto it = mesh.primitives.begin(); it != mesh.primitives.end(); ++it) { auto* positionIt = it->findAttribute("POSITION"); // A mesh primitive is required to hold the POSITION attribute. assert(positionIt != it->attributes.end()); // We only support indexed geometry. if (!it->indicesAccessor.has_value()) { return false; } // Generate the VAO GLuint vao = GL_NONE; glCreateVertexArrays(1, &vao); // Get the output primitive auto index = std::distance(mesh.primitives.begin(), it); auto& primitive = outMesh.primitives[index]; primitive.primitiveType = fastgltf::to_underlying(it->type); primitive.vertexArray = vao; if (it->materialIndex.has_value()) { primitive.materialUniformsIndex = it->materialIndex.value() + 1; // Adjust for default material auto& material = viewer->asset.materials[it->materialIndex.value()]; if (material.pbrData.baseColorTexture.has_value()) { auto& texture = viewer->asset.textures[material.pbrData.baseColorTexture->textureIndex]; if (!texture.imageIndex.has_value()) return false; primitive.albedoTexture = viewer->textures[texture.imageIndex.value()].texture; } } else { primitive.materialUniformsIndex = 0; } { // Position auto& positionAccessor = asset.accessors[positionIt->second]; if (!positionAccessor.bufferViewIndex.has_value()) continue; glEnableVertexArrayAttrib(vao, 0); glVertexArrayAttribFormat(vao, 0, static_cast(fastgltf::getNumComponents(positionAccessor.type)), fastgltf::getGLComponentType(positionAccessor.componentType), GL_FALSE, 0); glVertexArrayAttribBinding(vao, 0, 0); auto& positionView = asset.bufferViews[positionAccessor.bufferViewIndex.value()]; auto offset = positionView.byteOffset + positionAccessor.byteOffset; if (positionView.byteStride.has_value()) { glVertexArrayVertexBuffer(vao, 0, viewer->buffers[positionView.bufferIndex], static_cast(offset), static_cast(positionView.byteStride.value())); } else { glVertexArrayVertexBuffer(vao, 0, viewer->buffers[positionView.bufferIndex], static_cast(offset), static_cast(fastgltf::getElementByteSize(positionAccessor.type, positionAccessor.componentType))); } } { // Tex coord auto texcoord0 = it->findAttribute("TEXCOORD_0"); auto& texCoordAccessor = asset.accessors[texcoord0->second]; if (!texCoordAccessor.bufferViewIndex.has_value()) continue; glEnableVertexArrayAttrib(vao, 1); glVertexArrayAttribFormat(vao, 1, static_cast(fastgltf::getNumComponents(texCoordAccessor.type)), fastgltf::getGLComponentType(texCoordAccessor.componentType), GL_FALSE, 0); glVertexArrayAttribBinding(vao, 1, 1); auto& texCoordView = asset.bufferViews[texCoordAccessor.bufferViewIndex.value()]; auto offset = texCoordView.byteOffset + texCoordAccessor.byteOffset; if (texCoordView.byteStride.has_value()) { glVertexArrayVertexBuffer(vao, 1, viewer->buffers[texCoordView.bufferIndex], static_cast(offset), static_cast(texCoordView.byteStride.value())); } else { glVertexArrayVertexBuffer(vao, 1, viewer->buffers[texCoordView.bufferIndex], static_cast(offset), static_cast(fastgltf::getElementByteSize(texCoordAccessor.type, texCoordAccessor.componentType))); } } // Generate the indirect draw command auto& draw = primitive.draw; draw.instanceCount = 1; draw.baseInstance = 0; draw.baseVertex = 0; auto& indices = asset.accessors[it->indicesAccessor.value()]; if (!indices.bufferViewIndex.has_value()) return false; draw.count = static_cast(indices.count); auto& indicesView = asset.bufferViews[indices.bufferViewIndex.value()]; draw.firstIndex = static_cast(indices.byteOffset + indicesView.byteOffset) / fastgltf::getElementByteSize(indices.type, indices.componentType); primitive.indexType = getGLComponentType(indices.componentType); glVertexArrayElementBuffer(vao, viewer->buffers[indicesView.bufferIndex]); } // Create the buffer holding all of our primitive structs. glCreateBuffers(1, &outMesh.drawsBuffer); glNamedBufferData(outMesh.drawsBuffer, static_cast(outMesh.primitives.size() * sizeof(Primitive)), outMesh.primitives.data(), GL_STATIC_DRAW); viewer->meshes.emplace_back(outMesh); return true; } bool loadImage(Viewer* viewer, fastgltf::Image& image) { auto getLevelCount = [](int width, int height) -> GLsizei { return static_cast(1 + floor(log2(width > height ? width : height))); }; GLuint texture; glCreateTextures(GL_TEXTURE_2D, 1, &texture); std::visit(fastgltf::visitor { [](auto& arg) {}, [&](fastgltf::sources::URI& filePath) { assert(filePath.fileByteOffset == 0); // We don't support offsets with stbi. assert(filePath.uri.isLocalPath()); // We're only capable of loading local files. int width, height, nrChannels; const std::string path(filePath.uri.path().begin(), filePath.uri.path().end()); // Thanks C++. unsigned char *data = stbi_load(path.c_str(), &width, &height, &nrChannels, 4); glTextureStorage2D(texture, getLevelCount(width, height), GL_RGBA8, width, height); glTextureSubImage2D(texture, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); stbi_image_free(data); }, [&](fastgltf::sources::Vector& vector) { int width, height, nrChannels; unsigned char *data = stbi_load_from_memory(vector.bytes.data(), static_cast(vector.bytes.size()), &width, &height, &nrChannels, 4); glTextureStorage2D(texture, getLevelCount(width, height), GL_RGBA8, width, height); glTextureSubImage2D(texture, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); stbi_image_free(data); }, [&](fastgltf::sources::BufferView& view) { auto& bufferView = viewer->asset.bufferViews[view.bufferViewIndex]; auto& buffer = viewer->asset.buffers[bufferView.bufferIndex]; // Yes, we've already loaded every buffer into some GL buffer. However, with GL it's simpler // to just copy the buffer data again for the texture. Besides, this is just an example. std::visit(fastgltf::visitor { // We only care about VectorWithMime here, because we specify LoadExternalBuffers, meaning // all buffers are already loaded into a vector. [](auto& arg) {}, [&](fastgltf::sources::Vector& vector) { int width, height, nrChannels; unsigned char* data = stbi_load_from_memory(vector.bytes.data() + bufferView.byteOffset, static_cast(bufferView.byteLength), &width, &height, &nrChannels, 4); glTextureStorage2D(texture, getLevelCount(width, height), GL_RGBA8, width, height); glTextureSubImage2D(texture, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); stbi_image_free(data); } }, buffer.data); }, }, image.data); glGenerateTextureMipmap(texture); viewer->textures.emplace_back(Texture { texture }); return true; } bool loadMaterial(Viewer* viewer, fastgltf::Material& material) { MaterialUniforms uniforms = {}; uniforms.alphaCutoff = material.alphaCutoff; uniforms.baseColorFactor = glm::make_vec4(material.pbrData.baseColorFactor.data()); if (material.pbrData.baseColorTexture.has_value()) { uniforms.flags |= MaterialUniformFlags::HasBaseColorTexture; } viewer->materials.emplace_back(uniforms); return true; } void drawMesh(Viewer* viewer, size_t meshIndex, glm::mat4 matrix) { auto& mesh = viewer->meshes[meshIndex]; glBindBuffer(GL_DRAW_INDIRECT_BUFFER, mesh.drawsBuffer); glUniformMatrix4fv(viewer->modelMatrixUniform, 1, GL_FALSE, &matrix[0][0]); for (auto i = 0U; i < mesh.primitives.size(); ++i) { auto& prim = mesh.primitives[i]; auto& material = viewer->materialBuffers[prim.materialUniformsIndex]; glBindTextureUnit(0, prim.albedoTexture); glBindBufferBase(GL_UNIFORM_BUFFER, 0, material); glBindVertexArray(prim.vertexArray); glDrawElementsIndirect(prim.primitiveType, prim.indexType, reinterpret_cast(i * sizeof(Primitive))); } } void drawNode(Viewer* viewer, size_t nodeIndex, glm::mat4 matrix) { auto& node = viewer->asset.nodes[nodeIndex]; matrix = getTransformMatrix(node, matrix); if (node.meshIndex.has_value()) { drawMesh(viewer, node.meshIndex.value(), matrix); } for (auto& child : node.children) { drawNode(viewer, child, matrix); } } int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "No gltf file specified." << '\n'; return -1; } auto gltfFile = std::string_view { argv[1] }; Viewer viewer; if (glfwInit() != GLFW_TRUE) { std::cerr << "Failed to initialize glfw." << '\n'; return -1; } auto* mainMonitor = glfwGetPrimaryMonitor(); const auto* vidMode = glfwGetVideoMode(mainMonitor); glfwWindowHint(GLFW_SAMPLES, 4); GLFWwindow* window = glfwCreateWindow(static_cast(static_cast(vidMode->width) * 0.9f), static_cast(static_cast(vidMode->height) * 0.9f), "gl_viewer", nullptr, nullptr); if (window == nullptr) { std::cerr << "Failed to create window" << '\n'; return -1; } glfwSetWindowUserPointer(window, &viewer); glfwMakeContextCurrent(window); glfwSetKeyCallback(window, keyCallback); glfwSetCursorPosCallback(window, cursorCallback); glfwSetWindowSizeCallback(window, windowSizeCallback); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); if (!gladLoadGL(glfwGetProcAddress)) { std::cerr << "Failed to initialize OpenGL context." << '\n'; return -1; } const auto *glRenderer = glGetString(GL_RENDERER); const auto *glVersion = glGetString(GL_VERSION); std::cout << "GL Renderer: " << glRenderer << "\nGL Version: " << glVersion << '\n'; if (GLAD_GL_VERSION_4_6 != 1) { std::cerr << "Missing support for GL 4.6" << '\n'; return -1; } glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(glMessageCallback, nullptr); // Compile the shaders GLuint program = GL_NONE; { const GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); const GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); const auto* frag = fragmentShaderSource.data(); const auto* vert = vertexShaderSource.data(); auto fragSize = static_cast(fragmentShaderSource.size()); auto vertSize = static_cast(vertexShaderSource.size()); glShaderSource(fragmentShader, 1, &frag, &fragSize); glShaderSource(vertexShader, 1, &vert, &vertSize); glCompileShader(fragmentShader); glCompileShader(vertexShader); if (!checkGlCompileErrors(fragmentShader)) return -1; if (!checkGlCompileErrors(vertexShader)) return -1; program = glCreateProgram(); glAttachShader(program, fragmentShader); glAttachShader(program, vertexShader); glLinkProgram(program); if (!checkGlLinkErrors(program)) return -1; glDeleteShader(fragmentShader); glDeleteShader(vertexShader); } // Load the glTF file auto start = std::chrono::high_resolution_clock::now(); if (!loadGltf(&viewer, gltfFile)) { std::cerr << "Failed to parse glTF" << '\n'; return -1; } // Add a default material auto& defaultMaterial = viewer.materials.emplace_back(); defaultMaterial.baseColorFactor = glm::vec4(1.0f); defaultMaterial.alphaCutoff = 0.0f; defaultMaterial.flags = 0; // We load images first. auto& asset = viewer.asset; for (auto& image : asset.images) { loadImage(&viewer, image); } for (auto& material : asset.materials) { loadMaterial(&viewer, material); } for (auto& mesh : asset.meshes) { loadMesh(&viewer, mesh); } auto diff = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start); std::cout << "Loaded glTF file in " << diff.count() << "ms." << '\n'; // Create the material uniform buffer viewer.materialBuffers.resize(viewer.materials.size(), GL_NONE); glCreateBuffers(static_cast(viewer.materials.size()), viewer.materialBuffers.data()); for (auto i = 0UL; i < viewer.materialBuffers.size(); ++i) { glNamedBufferStorage(viewer.materialBuffers[i], static_cast(sizeof(MaterialUniforms)), &viewer.materials[i], GL_MAP_WRITE_BIT); } viewer.modelMatrixUniform = glGetUniformLocation(program, "modelMatrix"); viewer.viewProjectionMatrixUniform = glGetUniformLocation(program, "viewProjectionMatrix"); glUseProgram(program); { // We just emulate the initial sizing of the window with a manual call. int width, height; glfwGetWindowSize(window, &width, &height); windowSizeCallback(window, width, height); } glEnable(GL_BLEND); glEnable(GL_MULTISAMPLE); glEnable(GL_DEPTH_TEST); viewer.lastFrame = static_cast(glfwGetTime()); while (glfwWindowShouldClose(window) != GLFW_TRUE) { auto currentFrame = static_cast(glfwGetTime()); viewer.deltaTime = currentFrame - viewer.lastFrame; viewer.lastFrame = currentFrame; // Reset the acceleration viewer.accelerationVector = glm::vec3(0.0f); // Updates the acceleration vector and direction vectors. glfwPollEvents(); // Factor the deltaTime into the amount of acceleration viewer.velocity += (viewer.accelerationVector * 50.0f) * viewer.deltaTime; // Lerp the velocity to 0, adding deceleration. viewer.velocity = viewer.velocity + (2.0f * viewer.deltaTime) * (glm::vec3(0.0f) - viewer.velocity); // Add the velocity into the position viewer.position += viewer.velocity * viewer.deltaTime; viewer.viewMatrix = glm::lookAt(viewer.position, viewer.position + viewer.direction, glm::vec3(0.0f, 1.0f, 0.0f)); updateCameraMatrix(&viewer); glClearColor(0.1f, 0.2f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); std::size_t sceneIndex = 0; if (viewer.asset.defaultScene.has_value()) sceneIndex = viewer.asset.defaultScene.value(); auto& scene = viewer.asset.scenes[sceneIndex]; for (auto& node : scene.nodeIndices) { drawNode(&viewer, node, glm::mat4(1.0f)); } glfwSwapBuffers(window); } for (auto& mesh : viewer.meshes) { glDeleteBuffers(1, &mesh.drawsBuffer); for (auto& prim : mesh.primitives) { glDeleteVertexArrays(1, &prim.vertexArray); } } glDeleteProgram(program); glDeleteBuffers(static_cast(viewer.buffers.size()), viewer.buffers.data()); glfwDestroyWindow(window); glfwTerminate(); }