Compare commits

...

4 Commits

Author SHA1 Message Date
Syoyo Fujita
98eef6ee75 Fix non-KTX texture was not handled correctly.
Experience file drop API of glfw(not working yet).
2019-07-06 15:13:13 +09:00
Syoyo Fujita
c7bf800075 Support KTX texture of R8G8B8 format in basic example. 2019-07-05 16:20:53 +09:00
Syoyo Fujita
006ab90c67 Initial support of KTX texture load using TinyKTX. 2019-07-04 14:30:28 +09:00
Syoyo Fujita
6f7518255f Add tinyktx.
Add workaround for OpenGL3 context creation in `basic` example.
2019-07-03 16:41:11 +09:00
10 changed files with 2564 additions and 373 deletions

25
LICENSE.tinyktx Normal file
View File

@@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2019, DeanoC
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -7,6 +7,7 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
## Status
- v2.3.0 release(Support loading KTX image through tiny_ktx)
- v2.2.0 release(Support loading 16bit PNG. Sparse accessor support)
- v2.1.0 release(Draco support)
- v2.0.0 release(22 Aug, 2018)!
@@ -52,7 +53,8 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
* [x] Image load
* [x] Image save
* Extensions
* [x] Draco mesh decoding
* [x] Draco mesh decoding(`TINYGLTF_ENABLE_DRACO` required)
* [x] KTX image support(no mipmap. `TINYGLTF_ENABLE_KTX` required)
## Examples
@@ -81,6 +83,7 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch.
* [ ] OpenEXR extension through TinyEXR.
* [ ] 16bit PNG support in Serialization
* [ ] Write example and tests for `animation` and `skin`
* [ ] mipmap support for KTX image.
## Licenses
@@ -92,12 +95,15 @@ TinyGLTF uses the following third party libraries.
* base64 : Copyright (C) 2004-2008 René Nyffenegger
* stb_image.h : v2.08 - public domain image loader - [Github link](https://github.com/nothings/stb/blob/master/stb_image.h)
* stb_image_write.h : v1.09 - public domain image writer - [Github link](https://github.com/nothings/stb/blob/master/stb_image_write.h)
* tinyktx.h : Copyright (c) 2019, DeanoC. Licensed under 2 clause BSD license.
## Build and example
Copy `stb_image.h`, `stb_image_write.h`, `json.hpp` and `tiny_gltf.h` to your project.
If you enable KTX support(`TINYGLTF_ENABLE_KTX`), Copy `tinyktx.h` to your project.
### Loading glTF 2.0 model
```c++
@@ -143,6 +149,8 @@ if (!ret) {
* `TINYGLTF_NO_INCLUDE_JSON `: Disable including `json.hpp` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
* `TINYGLTF_NO_INCLUDE_STB_IMAGE `: Disable including `stb_image.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
* `TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE `: Disable including `stb_image_write.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
* `TINYGLTF_ENABLE_KTX` : Enable loading KTX images( https://www.khronos.org/opengles/sdk/tools/KTX/ ) using `tiny_ktx.h`. Supported MIME is `image/ktx` ( https://github.com/KhronosGroup/glTF/issues/835 ). See `models/Cube-KTX` for details. Application also defined `TINYKTX_IMPLEMENTATION` in **one** .cc file.
* `TINYGLTF_NO_INCLUDE_TINYKTX` : Disable including `tinyktx.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
### Saving gltTF 2.0 model
@@ -181,8 +189,6 @@ $ ./tester
$ ./tester_noexcept
```
## Third party licenses
## Third party licenses used in unit tests
* json.hpp : Licensed under the MIT License <http://opensource.org/licenses/MIT>. Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
* stb_image : Public domain.
* catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0.

View File

@@ -1,365 +1,460 @@
#include <fstream>
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/gtc/matrix_transform.hpp>
#include "shaders.h"
#include "window.h"
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_NOEXCEPTION
#define JSON_NOEXCEPTION
#include "../../tiny_gltf.h"
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
bool loadModel(tinygltf::Model &model, const char *filename) {
tinygltf::TinyGLTF loader;
std::string err;
std::string warn;
bool res = loader.LoadASCIIFromFile(&model, &err, &warn, filename);
if (!warn.empty()) {
std::cout << "WARN: " << warn << std::endl;
}
if (!err.empty()) {
std::cout << "ERR: " << err << std::endl;
}
if (!res)
std::cout << "Failed to load glTF: " << filename << std::endl;
else
std::cout << "Loaded glTF: " << filename << std::endl;
return res;
}
std::map<int, GLuint> bindMesh(std::map<int, GLuint> vbos,
tinygltf::Model &model, tinygltf::Mesh &mesh) {
for (size_t i = 0; i < model.bufferViews.size(); ++i) {
const tinygltf::BufferView &bufferView = model.bufferViews[i];
if (bufferView.target == 0) { // TODO impl drawarrays
std::cout << "WARN: bufferView.target is zero" << std::endl;
continue; // Unsupported bufferView.
/*
From spec2.0 readme:
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
... drawArrays function should be used with a count equal to
the count property of any of the accessors referenced by the
attributes property (they are all equal for a given
primitive).
*/
}
tinygltf::Buffer buffer = model.buffers[bufferView.buffer];
std::cout << "bufferview.target " << bufferView.target << std::endl;
GLuint vbo;
glGenBuffers(1, &vbo);
vbos[i] = vbo;
glBindBuffer(bufferView.target, vbo);
std::cout << "buffer.data.size = " << buffer.data.size()
<< ", bufferview.byteOffset = " << bufferView.byteOffset
<< std::endl;
glBufferData(bufferView.target, bufferView.byteLength,
&buffer.data.at(0) + bufferView.byteOffset, GL_STATIC_DRAW);
}
for (size_t i = 0; i < mesh.primitives.size(); ++i) {
tinygltf::Primitive primitive = mesh.primitives[i];
tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
for (auto &attrib : primitive.attributes) {
tinygltf::Accessor accessor = model.accessors[attrib.second];
int byteStride =
accessor.ByteStride(model.bufferViews[accessor.bufferView]);
glBindBuffer(GL_ARRAY_BUFFER, vbos[accessor.bufferView]);
int size = 1;
if (accessor.type != TINYGLTF_TYPE_SCALAR) {
size = accessor.type;
}
int vaa = -1;
if (attrib.first.compare("POSITION") == 0) vaa = 0;
if (attrib.first.compare("NORMAL") == 0) vaa = 1;
if (attrib.first.compare("TEXCOORD_0") == 0) vaa = 2;
if (vaa > -1) {
glEnableVertexAttribArray(vaa);
glVertexAttribPointer(vaa, size, accessor.componentType,
accessor.normalized ? GL_TRUE : GL_FALSE,
byteStride, BUFFER_OFFSET(accessor.byteOffset));
} else
std::cout << "vaa missing: " << attrib.first << std::endl;
}
GLuint texid;
glGenTextures(1, &texid);
tinygltf::Texture &tex = model.textures[0];
tinygltf::Image &image = model.images[tex.source];
glBindTexture(GL_TEXTURE_2D, texid);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
GLenum format = GL_RGBA;
if (image.component == 1) {
format = GL_RED;
} else if (image.component == 2) {
format = GL_RG;
} else if (image.component == 3) {
format = GL_RGB;
} else {
// ???
}
GLenum type = GL_UNSIGNED_BYTE;
if (image.bits == 8) {
// ok
} else if (image.bits == 16) {
type = GL_UNSIGNED_SHORT;
} else {
// ???
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0,
format, type, &image.image.at(0));
}
return vbos;
}
// bind models
void bindModelNodes(std::map<int, GLuint> vbos, tinygltf::Model &model,
tinygltf::Node &node) {
bindMesh(vbos, model, model.meshes[node.mesh]);
for (size_t i = 0; i < node.children.size(); i++) {
bindModelNodes(vbos, model, model.nodes[node.children[i]]);
}
}
GLuint bindModel(tinygltf::Model &model) {
std::map<int, GLuint> vbos;
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
const tinygltf::Scene &scene = model.scenes[model.defaultScene];
for (size_t i = 0; i < scene.nodes.size(); ++i) {
bindModelNodes(vbos, model, model.nodes[scene.nodes[i]]);
}
glBindVertexArray(0);
// cleanup vbos
for (size_t i = 0; i < vbos.size(); ++i) {
glDeleteBuffers(1, &vbos[i]);
}
return vao;
}
void drawMesh(tinygltf::Model &model, tinygltf::Mesh &mesh) {
for (size_t i = 0; i < mesh.primitives.size(); ++i) {
tinygltf::Primitive primitive = mesh.primitives[i];
tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
glDrawElements(primitive.mode, indexAccessor.count,
indexAccessor.componentType,
BUFFER_OFFSET(indexAccessor.byteOffset));
}
}
// recursively draw node and children nodes of model
void drawModelNodes(tinygltf::Model &model, tinygltf::Node &node) {
drawMesh(model, model.meshes[node.mesh]);
for (size_t i = 0; i < node.children.size(); i++) {
drawModelNodes(model, model.nodes[node.children[i]]);
}
}
void drawModel(GLuint vao, tinygltf::Model &model) {
glBindVertexArray(vao);
const tinygltf::Scene &scene = model.scenes[model.defaultScene];
for (size_t i = 0; i < scene.nodes.size(); ++i) {
drawModelNodes(model, model.nodes[scene.nodes[i]]);
}
glBindVertexArray(0);
}
void dbgModel(tinygltf::Model &model) {
for (auto &mesh : model.meshes) {
std::cout << "mesh : " << mesh.name << std::endl;
for (auto &primitive : mesh.primitives) {
const tinygltf::Accessor &indexAccessor =
model.accessors[primitive.indices];
std::cout << "indexaccessor: count " << indexAccessor.count << ", type "
<< indexAccessor.componentType << std::endl;
tinygltf::Material &mat = model.materials[primitive.material];
for (auto &mats : mat.values) {
std::cout << "mat : " << mats.first.c_str() << std::endl;
}
for (auto &image : model.images) {
std::cout << "image name : " << image.uri << std::endl;
std::cout << " size : " << image.image.size() << std::endl;
std::cout << " w/h : " << image.width << "/" << image.height
<< std::endl;
}
std::cout << "indices : " << primitive.indices << std::endl;
std::cout << "mode : "
<< "(" << primitive.mode << ")" << std::endl;
for (auto &attrib : primitive.attributes) {
std::cout << "attribute : " << attrib.first.c_str() << std::endl;
}
}
}
}
glm::mat4 genView(glm::vec3 pos, glm::vec3 lookat) {
// Camera matrix
glm::mat4 view = glm::lookAt(
pos, // Camera in World Space
lookat, // and looks at the origin
glm::vec3(0, 1, 0) // Head is up (set to 0,-1,0 to look upside-down)
);
return view;
}
glm::mat4 genMVP(glm::mat4 view_mat, glm::mat4 model_mat, float fov, int w,
int h) {
glm::mat4 Projection =
glm::perspective(glm::radians(fov), (float)w / (float)h, 0.01f, 1000.0f);
// Or, for an ortho camera :
// glm::mat4 Projection = glm::ortho(-10.0f,10.0f,-10.0f,10.0f,0.0f,100.0f);
// // In world coordinates
glm::mat4 mvp = Projection * view_mat * model_mat;
return mvp;
}
void displayLoop(Window &window, const std::string &filename) {
Shaders shader = Shaders();
glUseProgram(shader.pid);
// grab uniforms to modify
GLuint MVP_u = glGetUniformLocation(shader.pid, "MVP");
GLuint sun_position_u = glGetUniformLocation(shader.pid, "sun_position");
GLuint sun_color_u = glGetUniformLocation(shader.pid, "sun_color");
tinygltf::Model model;
if (!loadModel(model, filename.c_str())) return;
GLuint vao = bindModel(model);
// dbgModel(model); return;
// Model matrix : an identity matrix (model will be at the origin)
glm::mat4 model_mat = glm::mat4(1.0f);
glm::mat4 model_rot = glm::mat4(1.0f);
glm::vec3 model_pos = glm::vec3(-3, 0, -3);
// generate a camera view, based on eye-position and lookAt world-position
glm::mat4 view_mat = genView(glm::vec3(2, 2, 20), model_pos);
glm::vec3 sun_position = glm::vec3(3.0, 10.0, -5.0);
glm::vec3 sun_color = glm::vec3(1.0);
while (!window.Close()) {
window.Resize();
glClearColor(0.2, 0.2, 0.2, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 trans =
glm::translate(glm::mat4(1.0f), model_pos); // reposition model
model_rot = glm::rotate(model_rot, glm::radians(0.8f),
glm::vec3(0, 1, 0)); // rotate model on y axis
model_mat = trans * model_rot;
// build a model-view-projection
GLint w, h;
glfwGetWindowSize(window.window, &w, &h);
glm::mat4 mvp = genMVP(view_mat, model_mat, 45.0f, w, h);
glUniformMatrix4fv(MVP_u, 1, GL_FALSE, &mvp[0][0]);
glUniform3fv(sun_position_u, 1, &sun_position[0]);
glUniform3fv(sun_color_u, 1, &sun_color[0]);
drawModel(vao, model);
glfwSwapBuffers(window.window);
glfwPollEvents();
}
}
static void error_callback(int error, const char *description) {
(void)error;
fprintf(stderr, "Error: %s\n", description);
}
int main(int argc, char **argv) {
std::string filename = "../../models/Cube/Cube.gltf";
if (argc > 1) {
filename = argv[1];
}
glfwSetErrorCallback(error_callback);
if (!glfwInit()) return -1;
// Force create OpenGL 3.3
// NOTE(syoyo): Linux + NVIDIA driver segfaults for some reason? commenting out glfwWindowHint will work.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
#endif
Window window = Window(800, 600, "TinyGLTF basic example");
glfwMakeContextCurrent(window.window);
#ifdef __APPLE__
// https://stackoverflow.com/questions/50192625/openggl-segmentation-fault
glewExperimental = GL_TRUE;
#endif
glewInit();
std::cout << glGetString(GL_RENDERER) << ", " << glGetString(GL_VERSION)
<< std::endl;
if (!GLEW_VERSION_3_3) {
std::cerr << "OpenGL 3.3 is required to execute this app." << std::endl;
return EXIT_FAILURE;
}
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
displayLoop(window, filename);
glfwTerminate();
return 0;
}
#include <fstream>
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/gtc/matrix_transform.hpp>
#include "shaders.h"
#include "window.h"
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#endif
// Inlude tinyktx.h before tiny_gltf.h
// to get TKTX_*** definitions
#define TINYKTX_IMPLEMENTATION
#include "../../tinyktx.h"
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_NOEXCEPTION
#define JSON_NOEXCEPTION
#define TINYGLTF_ENABLE_KTX
// tinyktx.h is already included above,
// so let tiny_gltf.h know do not include tinyktx.h anymore
#define TINYGLTF_NO_INCLUDE_TINY_KTX
#include "../../tiny_gltf.h"
//#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
#define BUFFER_OFFSET(i) \
(reinterpret_cast<void *>(i)) // TODO(syoyo): Is this right way?
bool loadModel(tinygltf::Model &model, const char *filename) {
tinygltf::TinyGLTF loader;
std::string err;
std::string warn;
bool res = loader.LoadASCIIFromFile(&model, &err, &warn, filename);
if (!warn.empty()) {
std::cout << "WARN: " << warn << std::endl;
}
if (!err.empty()) {
std::cout << "ERR: " << err << std::endl;
}
if (!res)
std::cout << "Failed to load glTF: " << filename << std::endl;
else
std::cout << "Loaded glTF: " << filename << std::endl;
return res;
}
static bool GetOpenGLFormatFromKTX(int ktx_fmt, GLint *internal_format, GLenum *format, GLenum *type) {
#if defined(TINYGLTF_ENABLE_KTX)
bool ret = true;
std::cout << "fmt = " << ktx_fmt << ", rgb8 fmt = " << TKTX_R8G8B8_UNORM << "\n";
if (ktx_fmt == TKTX_R8G8B8_UNORM) {
(*internal_format) = GL_RGB;
(*format) = GL_RGB;
(*type) = GL_UNSIGNED_BYTE;
} else {
// TODO(syoyo): Support more KTX formats.
ret = false;
}
return ret;
#else
(void)fmt;
(void)internal_format;
(void)internal_format;
return false;
#endif
}
std::map<int, GLuint> bindMesh(std::map<int, GLuint> vbos,
tinygltf::Model &model, tinygltf::Mesh &mesh) {
for (size_t i = 0; i < model.bufferViews.size(); ++i) {
const tinygltf::BufferView &bufferView = model.bufferViews[i];
if (bufferView.target == 0) { // TODO impl drawarrays
std::cout << "WARN: bufferView.target is zero" << std::endl;
continue; // Unsupported bufferView.
/*
From spec2.0 readme:
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
... drawArrays function should be used with a count equal to
the count property of any of the accessors referenced by the
attributes property (they are all equal for a
given primitive).
*/
}
tinygltf::Buffer buffer = model.buffers[bufferView.buffer];
std::cout << "bufferview.target " << bufferView.target << std::endl;
GLuint vbo;
glGenBuffers(1, &vbo);
vbos[i] = vbo;
glBindBuffer(bufferView.target, vbo);
std::cout << "buffer.data.size = " << buffer.data.size()
<< ", bufferview.byteOffset = " << bufferView.byteOffset
<< std::endl;
glBufferData(bufferView.target, bufferView.byteLength,
&buffer.data.at(0) + bufferView.byteOffset, GL_STATIC_DRAW);
}
for (size_t i = 0; i < mesh.primitives.size(); ++i) {
tinygltf::Primitive primitive = mesh.primitives[i];
tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
for (auto &attrib : primitive.attributes) {
tinygltf::Accessor accessor = model.accessors[attrib.second];
int byteStride =
accessor.ByteStride(model.bufferViews[accessor.bufferView]);
glBindBuffer(GL_ARRAY_BUFFER, vbos[accessor.bufferView]);
int size = 1;
if (accessor.type != TINYGLTF_TYPE_SCALAR) {
size = accessor.type;
}
int vaa = -1;
if (attrib.first.compare("POSITION") == 0) vaa = 0;
if (attrib.first.compare("NORMAL") == 0) vaa = 1;
if (attrib.first.compare("TEXCOORD_0") == 0) vaa = 2;
if (vaa > -1) {
glEnableVertexAttribArray(vaa);
glVertexAttribPointer(vaa, size, accessor.componentType,
accessor.normalized ? GL_TRUE : GL_FALSE,
byteStride, BUFFER_OFFSET(accessor.byteOffset));
} else
std::cout << "Unsupported vertex attribute: " << attrib.first << std::endl;
}
GLuint texid;
glGenTextures(1, &texid);
tinygltf::Texture &tex = model.textures[0];
tinygltf::Image &image = model.images[tex.source];
glBindTexture(GL_TEXTURE_2D, texid);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
GLint internal_format = GL_RGBA;
GLenum format = GL_RGBA;
GLenum type = GL_UNSIGNED_BYTE;
bool valid = false;
// KTX extension
if (image.extras.Has("ktx_format")) {
valid = GetOpenGLFormatFromKTX(image.extras.Get("ktx_format").Get<int>(), &internal_format, &format, &type);
if (valid) {
std::cout << "ktx_format: " << image.extras.Get("ktx_format").Get<int>() << std::endl;
std::cout << "ktx image size: " << image.width << ", " << image.height << std::endl;
}
} else {
valid = true;
if (image.component == 1) {
format = GL_RED;
} else if (image.component == 2) {
format = GL_RG;
} else if (image.component == 3) {
format = GL_RGB;
} else if (image.component == 4) {
format = GL_RGBA;
} else {
valid = false;
}
if (image.bits == 8) {
type = GL_UNSIGNED_BYTE;
} else if (image.bits == 16) {
type = GL_UNSIGNED_SHORT;
} else {
valid = false;
}
}
if (valid) {
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, image.width, image.height, 0,
format, type, &image.image.at(0));
}
}
return vbos;
}
// bind models
void bindModelNodes(std::map<int, GLuint> vbos, tinygltf::Model &model,
tinygltf::Node &node) {
bindMesh(vbos, model, model.meshes[node.mesh]);
for (size_t i = 0; i < node.children.size(); i++) {
bindModelNodes(vbos, model, model.nodes[node.children[i]]);
}
}
GLuint bindModel(tinygltf::Model &model) {
std::map<int, GLuint> vbos;
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
const tinygltf::Scene &scene = model.scenes[model.defaultScene];
for (size_t i = 0; i < scene.nodes.size(); ++i) {
bindModelNodes(vbos, model, model.nodes[scene.nodes[i]]);
}
glBindVertexArray(0);
// cleanup vbos
for (size_t i = 0; i < vbos.size(); ++i) {
glDeleteBuffers(1, &vbos[i]);
}
return vao;
}
void drawMesh(tinygltf::Model &model, tinygltf::Mesh &mesh) {
for (size_t i = 0; i < mesh.primitives.size(); ++i) {
tinygltf::Primitive primitive = mesh.primitives[i];
tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
glDrawElements(primitive.mode, indexAccessor.count,
indexAccessor.componentType,
BUFFER_OFFSET(indexAccessor.byteOffset));
}
}
// recursively draw node and children nodes of model
void drawModelNodes(tinygltf::Model &model, tinygltf::Node &node) {
drawMesh(model, model.meshes[node.mesh]);
for (size_t i = 0; i < node.children.size(); i++) {
drawModelNodes(model, model.nodes[node.children[i]]);
}
}
void drawModel(GLuint vao, tinygltf::Model &model) {
glBindVertexArray(vao);
const tinygltf::Scene &scene = model.scenes[model.defaultScene];
for (size_t i = 0; i < scene.nodes.size(); ++i) {
drawModelNodes(model, model.nodes[scene.nodes[i]]);
}
glBindVertexArray(0);
}
void dbgModel(tinygltf::Model &model) {
for (auto &mesh : model.meshes) {
std::cout << "mesh : " << mesh.name << std::endl;
for (auto &primitive : mesh.primitives) {
const tinygltf::Accessor &indexAccessor =
model.accessors[primitive.indices];
std::cout << "indexaccessor: count " << indexAccessor.count << ", type "
<< indexAccessor.componentType << std::endl;
tinygltf::Material &mat = model.materials[primitive.material];
for (auto &mats : mat.values) {
std::cout << "mat : " << mats.first.c_str() << std::endl;
}
for (auto &image : model.images) {
std::cout << "image name : " << image.uri << std::endl;
std::cout << " size : " << image.image.size() << std::endl;
std::cout << " w/h : " << image.width << "/" << image.height
<< std::endl;
}
std::cout << "indices : " << primitive.indices << std::endl;
std::cout << "mode : "
<< "(" << primitive.mode << ")" << std::endl;
for (auto &attrib : primitive.attributes) {
std::cout << "attribute : " << attrib.first.c_str() << std::endl;
}
}
}
}
glm::mat4 genView(glm::vec3 pos, glm::vec3 lookat) {
// Camera matrix
glm::mat4 view = glm::lookAt(
pos, // Camera in World Space
lookat, // and looks at the origin
glm::vec3(0, 1, 0) // Head is up (set to 0,-1,0 to look upside-down)
);
return view;
}
glm::mat4 genMVP(glm::mat4 view_mat, glm::mat4 model_mat, float fov, int w,
int h) {
glm::mat4 Projection =
glm::perspective(glm::radians(fov), (float)w / (float)h, 0.01f, 1000.0f);
// Or, for an ortho camera :
// glm::mat4 Projection = glm::ortho(-10.0f,10.0f,-10.0f,10.0f,0.0f,100.0f);
// // In world coordinates
glm::mat4 mvp = Projection * view_mat * model_mat;
return mvp;
}
void displayLoop(Window &window, const std::string &filename) {
Shaders shader = Shaders();
glUseProgram(shader.pid);
// grab uniforms to modify
GLuint MVP_u = glGetUniformLocation(shader.pid, "MVP");
GLuint sun_position_u = glGetUniformLocation(shader.pid, "sun_position");
GLuint sun_color_u = glGetUniformLocation(shader.pid, "sun_color");
tinygltf::Model model;
if (!loadModel(model, filename.c_str())) return;
GLuint vao = bindModel(model);
// dbgModel(model); return;
// Model matrix : an identity matrix (model will be at the origin)
glm::mat4 model_mat = glm::mat4(1.0f);
glm::mat4 model_rot = glm::mat4(1.0f);
glm::vec3 model_pos = glm::vec3(-3, 0, -3);
// generate a camera view, based on eye-position and lookAt world-position
glm::mat4 view_mat = genView(glm::vec3(2, 2, 20), model_pos);
glm::vec3 sun_position = glm::vec3(3.0, 10.0, -5.0);
glm::vec3 sun_color = glm::vec3(1.0);
while (!window.Close()) {
window.Resize();
glClearColor(0.2, 0.2, 0.2, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 trans =
glm::translate(glm::mat4(1.0f), model_pos); // reposition model
model_rot = glm::rotate(model_rot, glm::radians(0.8f),
glm::vec3(0, 1, 0)); // rotate model on y axis
model_mat = trans * model_rot;
// build a model-view-projection
GLint w, h;
glfwGetWindowSize(window.window, &w, &h);
glm::mat4 mvp = genMVP(view_mat, model_mat, 45.0f, w, h);
glUniformMatrix4fv(MVP_u, 1, GL_FALSE, &mvp[0][0]);
glUniform3fv(sun_position_u, 1, &sun_position[0]);
glUniform3fv(sun_color_u, 1, &sun_color[0]);
drawModel(vao, model);
glfwSwapBuffers(window.window);
glfwPollEvents();
}
}
static void error_callback(int error, const char *description) {
(void)error;
fprintf(stderr, "Error: %s\n", description);
}
static void drop_callback(GLFWwindow *window, int num, const char **paths) {
(void)window;
printf("dropCB %d\n", num);
for (int i = 0; i < num; i++) {
printf("%s\n", paths[i]);
}
}
int main(int argc, char **argv) {
std::string filename = "../../models/Cube/Cube.gltf";
if (argc > 1) {
filename = argv[1];
}
glfwSetErrorCallback(error_callback);
if (!glfwInit()) return -1;
// NOTE(syoyo): For some reason, Linux + NVIDIA driver + apt-installed
// glew(1.13) cannot initialize some ARB functions when CONTEXT_VERSION are
// explicitly given. Proably we need to compile app with recent glfw and
// glew(or use glad) package
#if !defined(__linux__)
// Try to create OpenGL 3.3 context on Windows and macOS
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
#endif
// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
Window window = Window(800, 600, "TinyGLTF basic example");
glfwSetDropCallback(window.window, drop_callback);
glfwMakeContextCurrent(window.window);
#ifdef __APPLE__
// https://stackoverflow.com/questions/50192625/openggl-segmentation-fault
glewExperimental = GL_TRUE;
#endif
if (glewInit() != GLEW_OK) {
std::cerr << "Failed to initialie glew." << std::endl;
return EXIT_FAILURE;
}
std::cout << glGetString(GL_RENDERER) << ", " << glGetString(GL_VERSION)
<< std::endl;
if (!GLEW_ARB_vertex_array_object) {
std::cerr << "GLEW_ARB_vertex_array_object was not available." << std::endl;
return EXIT_FAILURE;
}
if (!GLEW_VERSION_3_3) {
std::cerr << "OpenGL 3.3 is required to execute this app." << std::endl;
return EXIT_FAILURE;
}
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
displayLoop(window, filename);
glfwTerminate();
return 0;
}

BIN
models/Cube-KTX/Cube.bin Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

193
models/Cube-KTX/Cube.gltf Normal file
View File

@@ -0,0 +1,193 @@
{
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5123,
"count" : 36,
"max" : [
35
],
"min" : [
0
],
"type" : "SCALAR"
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
1.000000,
1.000001
],
"min" : [
-1.000000,
-1.000000,
-1.000000
],
"type" : "VEC3"
},
{
"bufferView" : 2,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
1.000000,
1.000000
],
"min" : [
-1.000000,
-1.000000,
-1.000000
],
"type" : "VEC3"
},
{
"bufferView" : 3,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
-0.000000,
-0.000000,
1.000000
],
"min" : [
0.000000,
-0.000000,
-1.000000,
-1.000000
],
"type" : "VEC4"
},
{
"bufferView" : 4,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
1.000000
],
"min" : [
-1.000000,
-1.000000
],
"type" : "VEC2"
}
],
"asset" : {
"generator" : "VKTS glTF 2.0 exporter",
"version" : "2.0"
},
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 72,
"byteOffset" : 0,
"target" : 34963
},
{
"buffer" : 0,
"byteLength" : 432,
"byteOffset" : 72,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 432,
"byteOffset" : 504,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 576,
"byteOffset" : 936,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 288,
"byteOffset" : 1512,
"target" : 34962
}
],
"buffers" : [
{
"byteLength" : 1800,
"uri" : "Cube.bin"
}
],
"images" : [
{
"uri" : "Cube_BaseColor.ktx"
},
{
"uri" : "Cube_MetallicRoughness.ktx"
}
],
"materials" : [
{
"name" : "Cube",
"pbrMetallicRoughness" : {
"baseColorTexture" : {
"index" : 0
},
"metallicRoughnessTexture" : {
"index" : 1
}
}
}
],
"meshes" : [
{
"name" : "Cube",
"primitives" : [
{
"attributes" : {
"NORMAL" : 2,
"POSITION" : 1,
"TANGENT" : 3,
"TEXCOORD_0" : 4
},
"indices" : 0,
"material" : 0,
"mode" : 4
}
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "Cube"
}
],
"samplers" : [
{}
],
"scene" : 0,
"scenes" : [
{
"nodes" : [
0
]
}
],
"textures" : [
{
"sampler" : 0,
"source" : 0
},
{
"sampler" : 0,
"source" : 1
}
]
}

Binary file not shown.

Binary file not shown.

12
models/Cube-KTX/README.md Normal file
View File

@@ -0,0 +1,12 @@
License: Donated by Norbert Nopper for glTF testing.
https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Cube
----
Converted .png to .ktx image by Syoyo Fujita.
.png -> .ppm using image magic(resize with 25% to reduce file size)
.ppm -> .ktx using `toktx` in KTX-Software https://github.com/KhronosGroup/KTX-Software

View File

@@ -492,14 +492,19 @@ struct Image {
std::string name;
int width;
int height;
int component;
//
// For KTX image, component, bits and pixel_type will be set to `0`, and KTX attributes are stored in `extras`.
// FIXME(syoyo): Find more proper way to handle KTX and non-KTX image by keeping backward-compatibility as much as possible.
//
int component; // The number of channel components in the image.
int bits; // bit depth per channel. 8(byte), 16 or 32.
int pixel_type; // pixel type(TINYGLTF_COMPONENT_TYPE_***). usually
// UBYTE(bits = 8) or USHORT(bits = 16)
std::vector<unsigned char> image;
int bufferView; // (required if no uri)
std::string mimeType; // (required if no uri) ["image/jpeg", "image/png",
// "image/bmp", "image/gif"]
// "image/bmp", "image/gif", etc]
std::string uri; // (required if no mimeType)
Value extras;
ExtensionMap extensions;
@@ -1124,6 +1129,12 @@ class TinyGLTF {
#include "draco/core/decoder_buffer.h"
#endif
#ifdef TINYGLTF_ENABLE_KTX
#ifndef TINYGLTF_NO_INCLUDE_TINY_KTX
#include "tiny_ktx.h"
#endif
#endif
#ifndef TINYGLTF_NO_STB_IMAGE
#ifndef TINYGLTF_NO_INCLUDE_STB_IMAGE
#include "stb_image.h"
@@ -1679,8 +1690,8 @@ void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) {
load_image_user_data_ = user_data;
}
#ifndef TINYGLTF_NO_STB_IMAGE
bool LoadImageData(Image *image, const int image_idx, std::string *err,
#if !defined(TINYGLTF_NO_STB_IMAGE)
bool LoadImageDataSTB(Image *image, const int image_idx, std::string *err,
std::string *warn, int req_width, int req_height,
const unsigned char *bytes, int size, void *user_data) {
(void)user_data;
@@ -1777,6 +1788,232 @@ bool LoadImageData(Image *image, const int image_idx, std::string *err,
}
#endif
#if defined(TINYGLTF_ENABLE_KTX)
struct SimpleVFile {
std::string *err = nullptr;
const uint8_t *data = nullptr;
size_t pos = 0;
size_t len = 0;
};
static void tinyktxCallbackError(void *user, char const *msg) {
SimpleVFile *ss = reinterpret_cast<SimpleVFile *>(user);
if (ss->err) {
(*ss->err) += "Tiny_Ktx ERROR: " + std::string(msg) + "\n";
}
}
static void *tinyktxCallbackAlloc(void *user, size_t size) {
(void)user;
//std::cerr << "Alloc : " << std::to_string(size) << "\n";
return malloc(size);
};
static void tinyktxCallbackFree(void *user, void *data) {
(void)user;
free(data);
};
static size_t tinyktxCallbackRead(void *user, void* data, size_t size) {
SimpleVFile *ss = reinterpret_cast<SimpleVFile *>(user);
if ((ss->pos + size) > ss->len) {
if (ss->err) {
std::stringstream msg;
msg << "KTX read: Invalid data length. pos = " << ss->pos << ", len = " << ss->len << ", requested size to read = " << size << "\n";
(*ss->err) += msg.str();
}
return size_t(0);
}
memcpy(data, ss->data + ss->pos, size);
// Advance pos
ss->pos += size;
return size;
};
static bool tinyktxCallbackSeek(void *user, int64_t offset) {
SimpleVFile *ss = reinterpret_cast<SimpleVFile *>(user);
if ((offset < 0) || (size_t(offset) >= ss->len)) {
if (ss->err) {
std::stringstream msg;
msg << "KTX read: Invalid data offset. len = " << ss->len << ", requested offset = " << offset << "\n";
(*ss->err) += msg.str();
}
return false;
}
ss->pos = offset;
return true;
}
static int64_t tinyktxCallbackTell(void *user) {
SimpleVFile *ss = reinterpret_cast<SimpleVFile *>(user);
return int64_t(ss->pos);
};
bool LoadImageDataKTX(Image *image, const int image_idx, std::string *err,
std::string *warn, int req_width, int req_height,
const unsigned char *bytes, int size, void *user_data) {
(void)user_data;
(void)warn;
(void)req_width;
(void)req_height;
TinyKtx_Callbacks callbacks {
&tinyktxCallbackError,
&tinyktxCallbackAlloc,
&tinyktxCallbackFree,
&tinyktxCallbackRead,
&tinyktxCallbackSeek,
&tinyktxCallbackTell
};
SimpleVFile vfile;
vfile.err = err;
vfile.pos = 0;
vfile.data = bytes;
vfile.len = size;
auto ctx = TinyKtx_CreateContext( &callbacks, reinterpret_cast<void *>(&vfile) );
TinyKtx_ReadHeader(ctx);
uint32_t w = TinyKtx_Width(ctx);
uint32_t h = TinyKtx_Height(ctx);
uint32_t d = TinyKtx_Depth(ctx);
TinyKtx_Format fmt = TinyKtx_GetFormat(ctx);
if(fmt == TKTX_UNDEFINED) {
TinyKtx_DestroyContext(ctx);
return false;
}
int num_mipmaps = TinyKtx_NumberOfMipmaps(ctx);
if (num_mipmaps < 1) {
// ???
if (err) {
(*err) += "Number of mipmaps are zero for KTX image[" + std::to_string(image_idx) + ".\n";
}
TinyKtx_DestroyContext(ctx);
return false;
}
{
// TODO(syoyo): Support mipmap.
int miplevel = 0;
size_t byte_count = TinyKtx_ImageSize(ctx, miplevel);
if (byte_count == 0) {
// ???
if (err) {
(*err) += "KTX image [" + std::to_string(image_idx) + "] has zero bytes.\n";
}
TinyKtx_DestroyContext(ctx);
return false;
}
const void *src_ptr = TinyKtx_ImageRawData(ctx, miplevel);
if (src_ptr == nullptr) {
TinyKtx_DestroyContext(ctx);
return false;
}
image->image.resize(byte_count);
memcpy(image->image.data(), TinyKtx_ImageRawData(ctx, miplevel), byte_count);
TinyKtx_DestroyContext(ctx);
// Set to 0 for KTX image.
image->pixel_type = 0;
image->bits = 0;
image->component = 0;
// extras for KTX
Value::Object extras;
extras["ktx_format"] = Value(int(fmt)); // TODO(syoyo): Store OpenGL format
extras["depth"] = Value(int(d));
extras["byteCount"] = Value(int(byte_count)); // Assume image size is < 2GB
image->extras = Value(extras);
// TODO(syoyo): Store more KTX attributes.
}
image->width = w;
image->height = h;
return true;
}
#endif
bool LoadImageData(Image *image, const int image_idx, std::string *err,
std::string *warn, int req_width, int req_height,
const unsigned char *bytes, int size, void *user_data) {
bool ret = false;
std::string stb_err;
std::string stb_warn;
#if !defined(TINYGLTF_NO_STB_IMAGE)
// Try to load images using stb_image(png, gif, bmp, tga, ...)
ret = tinygltf::LoadImageDataSTB(image, image_idx, &stb_err, &stb_warn, req_width, req_height,
bytes, size, user_data);
if (ret) {
return true;
}
#endif
std::string ktx_err;
std::string ktx_warn;
#if defined(TINYGLTF_ENABLE_KTX)
// Try KTX image
ret = tinygltf::LoadImageDataKTX(image, image_idx, &ktx_err, &ktx_warn, req_width, req_height,
bytes, size, user_data);
if (ret) {
if (warn) {
(*warn) = ktx_warn;
}
return true;
}
#endif
if (err) {
(*err) = stb_err + ktx_err;
}
if (warn) {
(*warn) = stb_warn + ktx_warn;
}
(void)image;
(void)image_idx;
(void)req_width;
(void)req_height;
(void)bytes;
(void)size;
(void)user_data;
return false;
}
void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) {
WriteImageData = func;
write_image_user_data_ = user_data;
@@ -1800,6 +2037,8 @@ bool WriteImageData(const std::string *basepath, const std::string *filename,
std::string header;
std::vector<unsigned char> data;
// TODO(LTE): Support ktx
if (ext == "png") {
if ((image->bits != 8) ||
(image->pixel_type != TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)) {
@@ -2047,6 +2286,10 @@ static std::string MimeToExt(const std::string &mimeType) {
return "bmp";
} else if (mimeType == "image/gif") {
return "gif";
#if defined(TINYGLTF_ENABLE_KTX)
} else if (mimeType == "image/ktx") {
return "ktx";
#endif
}
return "";
@@ -2107,6 +2350,13 @@ bool IsDataURI(const std::string &in) {
return true;
}
#if defined(TINYGLTF_ENABLE_KTX)
header = "data:image/ktx;base64,";
if (in.find(header) == 0) {
return true;
}
#endif
header = "data:text/plain;base64,";
if (in.find(header) == 0) {
return true;
@@ -2160,6 +2410,16 @@ bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
}
}
#if defined(TINYGLTF_ENABLE_KTX)
if (data.empty()) {
header = "data:image/ktx;base64,";
if (in.find(header) == 0) {
mime_type = "image/ktx";
data = base64_decode(in.substr(header.size())); // cut mime string.
}
}
#endif
if (data.empty()) {
header = "data:text/plain;base64,";
if (in.find(header) == 0) {

1600
tinyktx.h Normal file

File diff suppressed because it is too large Load Diff