Files

752 lines
28 KiB
C++

/*
* Copyright (C) 2022 - 2023 spnda
* This file is part of fastgltf <https://github.com/spnda/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 <chrono>
#include <iostream>
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/quaternion.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <fastgltf/parser.hpp>
#include <fastgltf/types.hpp>
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<Primitive> 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<GLuint> buffers;
std::vector<GLuint> bufferAllocations;
std::vector<Mesh> meshes;
std::vector<Texture> textures;
std::vector<MaterialUniforms> materials;
std::vector<GLuint> 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<Viewer*>(ptr);
viewer->projectionMatrix = glm::perspective(glm::radians(75.0f),
static_cast<float>(width) / static_cast<float>(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<Viewer*>(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<Viewer*>(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<fastgltf::Node::TransformMatrix>(&node.transform)) {
return base * glm::mat4x4(glm::make_mat4x4(pMatrix->data()));
}
if (const auto* pTransform = std::get_if<fastgltf::Node::TRS>(&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<fastgltf::Asset> 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<int64_t>(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<GLuint>(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<GLint>(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<GLintptr>(offset),
static_cast<GLsizei>(positionView.byteStride.value()));
} else {
glVertexArrayVertexBuffer(vao, 0, viewer->buffers[positionView.bufferIndex],
static_cast<GLintptr>(offset),
static_cast<GLsizei>(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<GLint>(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<GLintptr>(offset),
static_cast<GLsizei>(texCoordView.byteStride.value()));
} else {
glVertexArrayVertexBuffer(vao, 1, viewer->buffers[texCoordView.bufferIndex],
static_cast<GLintptr>(offset),
static_cast<GLsizei>(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<uint32_t>(indices.count);
auto& indicesView = asset.bufferViews[indices.bufferViewIndex.value()];
draw.firstIndex = static_cast<uint32_t>(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<GLsizeiptr>(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<GLsizei>(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<int>(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<int>(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<const void*>(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<int>(static_cast<float>(vidMode->width) * 0.9f), static_cast<int>(static_cast<float>(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<GLint>(fragmentShaderSource.size());
auto vertSize = static_cast<GLint>(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::milliseconds>(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<GLsizei>(viewer.materials.size()), viewer.materialBuffers.data());
for (auto i = 0UL; i < viewer.materialBuffers.size(); ++i) {
glNamedBufferStorage(viewer.materialBuffers[i], static_cast<GLsizeiptr>(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<float>(glfwGetTime());
while (glfwWindowShouldClose(window) != GLFW_TRUE) {
auto currentFrame = static_cast<float>(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<GLint>(viewer.buffers.size()), viewer.buffers.data());
glfwDestroyWindow(window);
glfwTerminate();
}