#include #include #include #include #include #include #include #include #include #include #include #if defined(_MSC_VER) #include // _getcwd #endif // GL // // glad must be included before glfw3.h // TODO: Use imgui's imgui_impl_opengl3_loader.h #if defined(_WIN32) #if !defined(NOMINMAX) #define NOMINMAX #endif #include #endif #include "glad/glad.h" // #include // GUI common #include "../common/imgui/imgui.h" #include "../common/imgui/imgui_impl_glfw.h" #include "../common/imgui/imgui_impl_opengl3.h" #include "../common/trackball.h" #include "../common/viewport_camera.hh" // TinyUSDZ // include relative to openglviewer example cmake top dir for clangd lsp. #include "external/linalg.h" #include "io-util.hh" #include "linear-algebra.hh" #include "pprinter.hh" // import to_string(tinyusdz::value::***) #include "tinyusdz.hh" #include "tydra/render-data.hh" #include "tydra/scene-access.hh" #include "value-pprint.hh" // import to_string(tinyusdz::value::***) // local #include "shader.hh" // variable name must match in shaders/***.vert constexpr auto kAttribPoints = "input_position"; constexpr auto kAttribNormals = "input_normal"; constexpr auto kAttribTexCoordBase = "input_uv"; constexpr auto kAttribTexCoord0 = "input_uv"; constexpr auto kMaxTexCoords = 1; // TODO: multi texcoords constexpr auto kUniformModelMatrix = "modelMatrix"; constexpr auto kUniformNormalMatrix = "normalMatrix"; constexpr auto kUniformMVPMatrix = "mvp"; constexpr auto kUniformDiffuseTex = "diffuseTex"; constexpr auto kUniformDiffuseTexTransform = "diffuseTexTransform"; constexpr auto kUniformDiffuseTexScaleAndBias = "diffuseTexScaleAndBias"; // (sx, sy, bx, by) constexpr auto kUniformNormaTex = "normalTex"; constexpr auto kUniformNormaTexTransform = "normalTexTransform"; constexpr auto kUniformNormalTexScaleAndBias = "normalTexScaleAndBias"; // (sx, sy, bx, by) constexpr auto kUniformOcclusionTex = "occlusionlTex"; constexpr auto kUniformOcclusionTexTransform = "occlusionlTexTransform"; constexpr auto kUniformOcclusionTexScaleAndBias = "occlusionTexScaleAndBias"; // (sx, sy, bx, by) // Embedded shaders #include "shaders/no_skinning.vert_inc.hh" #include "shaders/normals.frag_inc.hh" #include "shaders/usdpreviewsurface.frag_inc.hh" #include "shaders/world_fragment.frag_inc.hh" #define CHECK_GL(tag) \ do { \ GLenum err = glGetError(); \ if (err != GL_NO_ERROR) { \ std::cerr << "[" << tag << "] " << __FILE__ << ":" << __LINE__ << ":" \ << __func__ << " code " << std::to_string(int(err)) << "\n"; \ } \ } while (0) struct GLTexParams { // std::map uniforms; GLenum wrapS{GL_REPEAT}; GLenum wrapT{GL_REPEAT}; std::array borderCol{0.0f, 0.0f, 0.0f, 0.0f}; // transparent black // Use 3x3 mat to support pivot transform. tinyusdz::tydra::mat3 uv_transform{tinyusdz::tydra::mat3::identity()}; }; struct GLTexState { GLTexParams texParams; std::string sampler_name; uint32_t slot_id{0}; GLuint tex_id; // glBindTexture id GLint u_tex{-1}; // sampler glUniform location GLint u_transform; // texcoord transform }; template struct GLTexOrFactor { GLTexOrFactor(const T &v) : factor(v) {} GLTexState tex; T factor; GLint u_factor{-1}; }; template struct GLUniformFactor { GLUniformFactor(const T &v) : factor(v) {} T factor; GLint u_factor{-1}; }; struct GLUsdPreviewSurfaceState { static constexpr auto kDiffuseColor = "diffuseColor"; static constexpr auto kEmissiveColor = "emissiveColor"; static constexpr auto kSpecularColor = "specularColor"; static constexpr auto kUseSpecularWorkflow = "useSpecularWorkflow"; static constexpr auto kMetallic = "metallic"; static constexpr auto kRoughness = "roughness"; static constexpr auto kClearcoat = "clearcoat"; static constexpr auto kClearcoatRoughness = "clearcoatRoughness"; static constexpr auto kOpacity = "opacity"; static constexpr auto kOpacityThreshold = "opacityThreshold"; static constexpr auto kIor = "ior"; static constexpr auto kNormal = "normal"; static constexpr auto kOcclusion = "occlusion"; GLTexOrFactor diffuseColor{{0.18f, 0.18f, 0.18f}}; GLTexOrFactor emissiveColor{{0.0f, 0.0f, 0.0f}}; GLUniformFactor useSpecularWorkflow{0}; // non-texturable GLTexOrFactor specularColor{ {0.0f, 0.0f, 0.0f}}; // useSpecularWorkflow = 1 GLTexOrFactor metallic{0.0f}; // useSpecularWorkflow = 0 GLTexOrFactor roughness{0.5f}; GLTexOrFactor clearcoat{0.0f}; GLTexOrFactor clearcoatRoughness{0.01f}; GLTexOrFactor opacity{1.0f}; GLTexOrFactor opacityThreshold{0.0f}; GLTexOrFactor ior{1.5f}; GLTexOrFactor normal{ {0.0f, 0.0f, 1.0f}}; // normal map // No displacement mapping on OpenGL // GLTexOrFactor displacement{0.0f}; GLTexOrFactor occlusion{1.0f}; }; template bool SetupGLUsdPreviewSurfaceParam(const GLuint prog_id, const tinyusdz::tydra::RenderScene &scene, const std::string &base_shadername, const tinyusdz::tydra::ShaderParam &s, GLTexOrFactor &dst) { if (s.is_texture()) { { std::string u_name = base_shadername + "Tex"; GLint loc = glGetUniformLocation(prog_id, u_name.c_str()); dst.tex.u_tex = loc; } { std::string u_name = base_shadername + "TexTransform"; GLint loc = glGetUniformLocation(prog_id, u_name.c_str()); dst.tex.u_transform = loc; if (s.textureId < 0 || s.textureId >= scene.textures.size()) { std::cerr << "Invalid txtureId for " << base_shadername + "\n"; } else { const tinyusdz::tydra::UVTexture &uvtex = scene.textures[size_t(s.textureId)]; dst.tex.texParams.uv_transform = uvtex.transform; } } } else { GLint loc = glGetUniformLocation(prog_id, base_shadername.c_str()); if (loc < 0) { std::cerr << base_shadername << " uniform not found in the shader.\n"; } dst.u_factor = loc; dst.factor = s.value; } return true; } bool ReloadShader(GLuint prog_id, const std::string &vert_filepath, const std::string &frag_filepath) { std::string vert_str; std::string frag_str; if (vert_filepath.size() && tinyusdz::io::FileExists(vert_filepath)) { std::vector bytes; std::string err; if (!tinyusdz::io::ReadWholeFile(&bytes, &err, vert_filepath)) { std::cerr << "Read vertg shader failed: " << err << "\n"; return false; } vert_str = std::string(reinterpret_cast(bytes.data()), bytes.size()); std::cout << "VERT:\n" << vert_str << "\n"; } if (frag_filepath.size() && tinyusdz::io::FileExists(frag_filepath)) { std::vector bytes; std::string err; if (!tinyusdz::io::ReadWholeFile(&bytes, &err, frag_filepath)) { std::cerr << "Read frag shader failed: " << err << "\n"; return false; } frag_str = std::string(reinterpret_cast(bytes.data()), bytes.size()); std::cout << "FRAG:\n" << frag_str << "\n"; } // TODO return true; } bool SetupGLUsdPreviewSurface(GLuint prog_id, tinyusdz::tydra::RenderScene &scene, tinyusdz::tydra::RenderMaterial &m, GLUsdPreviewSurfaceState &dst) { const auto surfaceShader = m.surfaceShader; if (!SetupGLUsdPreviewSurfaceParam( prog_id, scene, GLUsdPreviewSurfaceState::kDiffuseColor, surfaceShader.diffuseColor, dst.diffuseColor)) { return false; } if (!SetupGLUsdPreviewSurfaceParam( prog_id, scene, GLUsdPreviewSurfaceState::kEmissiveColor, surfaceShader.emissiveColor, dst.emissiveColor)) { return false; } if (!SetupGLUsdPreviewSurfaceParam( prog_id, scene, GLUsdPreviewSurfaceState::kSpecularColor, surfaceShader.specularColor, dst.specularColor)) { return false; } if (!SetupGLUsdPreviewSurfaceParam(prog_id, scene, GLUsdPreviewSurfaceState::kMetallic, surfaceShader.metallic, dst.metallic)) { return false; } if (!SetupGLUsdPreviewSurfaceParam(prog_id, scene, GLUsdPreviewSurfaceState::kRoughness, surfaceShader.roughness, dst.roughness)) { return false; } if (!SetupGLUsdPreviewSurfaceParam(prog_id, scene, GLUsdPreviewSurfaceState::kClearcoat, surfaceShader.clearcoat, dst.clearcoat)) { return false; } if (!SetupGLUsdPreviewSurfaceParam( prog_id, scene, GLUsdPreviewSurfaceState::kClearcoatRoughness, surfaceShader.clearcoatRoughness, dst.clearcoatRoughness)) { return false; } if (!SetupGLUsdPreviewSurfaceParam(prog_id, scene, GLUsdPreviewSurfaceState::kOpacity, surfaceShader.opacity, dst.opacity)) { return false; } if (!SetupGLUsdPreviewSurfaceParam( prog_id, scene, GLUsdPreviewSurfaceState::kOpacityThreshold, surfaceShader.opacityThreshold, dst.opacityThreshold)) { return false; } if (!SetupGLUsdPreviewSurfaceParam(prog_id, scene, GLUsdPreviewSurfaceState::kIor, surfaceShader.ior, dst.ior)) { return false; } if (!SetupGLUsdPreviewSurfaceParam(prog_id, scene, GLUsdPreviewSurfaceState::kOcclusion, surfaceShader.occlusion, dst.occlusion)) { return false; } if (!SetupGLUsdPreviewSurfaceParam(prog_id, scene, GLUsdPreviewSurfaceState::kNormal, surfaceShader.normal, dst.normal)) { return false; } { GLint loc = glGetUniformLocation( prog_id, GLUsdPreviewSurfaceState::kUseSpecularWorkflow); if (loc < 0) { std::cerr << GLUsdPreviewSurfaceState::kUseSpecularWorkflow << " uniform not found in the shader.\n"; } dst.useSpecularWorkflow.factor = surfaceShader.useSpecularWorkFlow ? 1.0f : 0.0f; dst.useSpecularWorkflow.u_factor = loc; } // TODO: `displacement` param return true; } struct GLVertexUniformState { GLint u_model{-1}; GLint u_normal{-1}; GLint u_mvp{-1}; std::array modelMatrix[16]; std::array normalMatrix[9]; // 3x3 transpose(inverse(model * view)) std::array mvp[16]; // modeviewprojection }; // TODO: Use handle_id for key struct GLMeshState { std::map attribs; std::vector diffuseTexHandles; GLuint vertex_array_object{0}; // vertex array object GLuint num_triangles{0}; // up to 4GB triangles }; struct GLNodeState { GLVertexUniformState gl_v_uniform_state; GLMeshState gl_mesh_state; GLTexState gl_tex_state; }; struct GLProgramState { // std::map uniforms; std::map shaders; }; struct GLScene { std::vector gl_nodes; // scene bounding box std::array bmin; std::array bmax; }; struct GUIContext { enum AOV { AOV_COLOR = 0, AOV_NORMAL, AOV_POSITION, AOV_DEPTH, AOV_TEXCOORD, AOV_VARYCOORD, AOV_VERTEXCOLOR }; AOV aov{AOV_COLOR}; int width = 1024; int height = 768; int mouse_x = -1; int mouse_y = -1; bool mouse_left_down = false; bool shift_pressed = false; bool ctrl_pressed = false; bool tab_pressed = false; float curr_quat[4] = {0.0f, 0.0f, 0.0f, 1.0f}; float prev_quat[4] = {0.0f, 0.0f, 0.0f, 1.0f}; float xrotate = 0.0f; // in degree float yrotate = 0.0f; // in degree float fov = 45.0f; // in degree float znear = 0.01f; float zfar = 1000.0f; std::array eye = {0.0f, 0.5f, -5.0f}; std::array lookat = {0.0f, 0.0f, 0.0f}; std::array up = {0.0f, 1.0f, 0.0f}; example::Camera camera; GLUsdPreviewSurfaceState *selected_surfaceShader{nullptr}; std::vector surfaceShaders; // for ImGui std::vector surfaceShaderNames; std::string selected_surfaceShaderName; std::string usd_filepath; std::string converter_info; std::string converter_warn; std::string vert_filename{"../shaders/no_skinning.vert"}; std::string frag_filename{"../shaders/usdpreviewsurface.frag"}; }; GUIContext gCtx; // // Combo with std::vector // static bool ImGuiComboUI(const std::string &caption, std::string ¤t_item, const std::vector &items) { bool changed = false; if (ImGui::BeginCombo(caption.c_str(), current_item.c_str())) { for (int n = 0; n < items.size(); n++) { bool is_selected = (current_item == items[n]); if (ImGui::Selectable(items[n].c_str(), is_selected)) { current_item = items[n]; changed = true; } if (is_selected) { // Set the initial focus when opening the combo (scrolling + for // keyboard navigation support in the upcoming navigation branch) ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } return changed; } static void MaterialUI() { ImGui::Begin("Material"); ImGuiComboUI("surfaceShader", gCtx.selected_surfaceShaderName, gCtx.surfaceShaderNames); ImGui::End(); } static void UsdPreviewSurfaceParamUI( GLUsdPreviewSurfaceState &state) { bool changed = false; ImGui::Begin("Shader param"); changed |= ImGui::ColorEdit3("diffuseColor", &state.diffuseColor.factor[0]); changed |= ImGui::ColorEdit3("emissiveColor", &state.emissiveColor.factor[0]); bool specWorkflow = state.useSpecularWorkflow.factor > 0 ? true : false; changed |= ImGui::Checkbox("useSpecularWorkflow", &specWorkflow); state.useSpecularWorkflow.factor = specWorkflow; if (specWorkflow) { changed |= ImGui::ColorEdit3("specularColor", &state.specularColor.factor[0]); } else { changed |= ImGui::SliderFloat("metallic", &state.metallic.factor, 0.0f, 1.0f); } changed |= ImGui::SliderFloat("clearcoat", &state.clearcoat.factor, 0.0f, 1.0f); changed |= ImGui::SliderFloat("clearcoatRoughness", &state.clearcoatRoughness.factor, 0.0f, 1.0f); changed |= ImGui::SliderFloat("opacity", &state.opacity.factor, 0.0f, 1.0f); changed |= ImGui::SliderFloat("opacityThreshold", &state.opacityThreshold.factor, 0.0f, 1.0f); changed |= ImGui::SliderFloat("ior", &state.ior.factor, 0.0f, 6.0f); changed |= ImGui::SliderFloat("occlusion", &state.occlusion.factor, 0.0f, 1.0f); ImGui::End(); } // --- glfw ---------------------------------------------------- static void error_callback(int error, const char *description) { std::cerr << "GLFW Error : " << error << ", " << description << std::endl; } static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) { (void)scancode; ImGuiIO &io = ImGui::GetIO(); if (io.WantCaptureKeyboard) { return; } if ((key == GLFW_KEY_LEFT_SHIFT) || (key == GLFW_KEY_RIGHT_SHIFT)) { auto *param = reinterpret_cast(glfwGetWindowUserPointer(window)); param->shift_pressed = (action == GLFW_PRESS); } if ((key == GLFW_KEY_LEFT_CONTROL) || (key == GLFW_KEY_RIGHT_CONTROL)) { auto *param = reinterpret_cast(glfwGetWindowUserPointer(window)); param->ctrl_pressed = (action == GLFW_PRESS); } // ctrl-q if ((key == GLFW_KEY_Q) && (action == GLFW_PRESS) && (mods & GLFW_MOD_CONTROL)) { glfwSetWindowShouldClose(window, GLFW_TRUE); } // esc if ((key == GLFW_KEY_ESCAPE) && (action == GLFW_PRESS)) { glfwSetWindowShouldClose(window, GLFW_TRUE); } } static void mouse_move_callback(GLFWwindow *window, double x, double y) { auto param = reinterpret_cast(glfwGetWindowUserPointer(window)); assert(param); if (param->mouse_left_down) { float w = static_cast(param->width); float h = static_cast(param->height); float x_offset = param->width - w; float y_offset = param->height - h; if (param->ctrl_pressed) { const float dolly_scale = 0.1f; param->eye[2] += dolly_scale * (param->mouse_y - static_cast(y)); param->lookat[2] += dolly_scale * (param->mouse_y - static_cast(y)); } else if (param->shift_pressed) { const float trans_scale = 0.02f; param->eye[0] += trans_scale * (param->mouse_x - static_cast(x)); param->eye[1] -= trans_scale * (param->mouse_y - static_cast(y)); param->lookat[0] += trans_scale * (param->mouse_x - static_cast(x)); param->lookat[1] -= trans_scale * (param->mouse_y - static_cast(y)); } else { #if 0 // Adjust y. trackball(param->prev_quat, (2.f * (param->mouse_x - x_offset) - w) / static_cast(w), (h - 2.f * (param->mouse_y - y_offset)) / static_cast(h), (2.f * (static_cast(x) - x_offset) - w) / static_cast(w), (h - 2.f * (static_cast(y) - y_offset)) / static_cast(h)); add_quats(param->prev_quat, param->curr_quat, param->curr_quat); #else const float rotation_amp = 1.0f; param->xrotate += rotation_amp * (param->mouse_y - static_cast(y)); param->yrotate += rotation_amp * (param->mouse_x - static_cast(x)); // limit rotation around X axis. if (param->xrotate < -89) { param->xrotate = -89; } if (param->xrotate > 89) { param->xrotate = 89; } #endif } } param->mouse_x = static_cast(x); param->mouse_y = static_cast(y); } static void mouse_button_callback(GLFWwindow *window, int button, int action, int mods) { ImGuiIO &io = ImGui::GetIO(); if (io.WantCaptureMouse || io.WantCaptureKeyboard) { return; } auto param = reinterpret_cast(glfwGetWindowUserPointer(window)); assert(param); // left button if (button == 0) { if (action) { param->mouse_left_down = true; trackball(param->prev_quat, 0.0f, 0.0f, 0.0f, 0.0f); } else { param->mouse_left_down = false; } } } static void resize_callback(GLFWwindow *window, int width, int height) { auto param = reinterpret_cast(glfwGetWindowUserPointer(window)); assert(param); param->width = width; param->height = height; } // ------------------------------------------------ namespace { void SetupVertexUniforms(GLVertexUniformState &gl_state, const tinyusdz::value::matrix4d &worldmatd, const tinyusdz::value::matrix4f &viewproj) { using namespace tinyusdz::value; // implicitly casts matrix4d to matrix4f; matrix4f worldmat = worldmatd; matrix4d invtransmatd = tinyusdz::inverse(tinyusdz::upper_left_3x3_only(worldmatd)); matrix3d invtransmat33d = tinyusdz::to_matrix3x3(invtransmatd); matrix3f invtransmat33 = invtransmat33d; memcpy(gl_state.modelMatrix, &worldmat.m[0][0], sizeof(float) * 16); memcpy(gl_state.normalMatrix, &invtransmat33.m[0][0], sizeof(float) * 9); // NOTE: USD uses pre-multiply matmul matrix4f mvp = viewproj * worldmat; // FIXME: memcpy(gl_state.mvp, &mvp.m[0][0], sizeof(float) * 16); } void SetVertexUniforms(const GLVertexUniformState &gl_state) { if (gl_state.u_model > -1) { glUniformMatrix4fv(gl_state.u_model, 1, GL_FALSE, gl_state.modelMatrix->data()); CHECK_GL("UniformMatrix u_modelview"); } if (gl_state.u_normal > -1) { glUniformMatrix3fv(gl_state.u_normal, 1, GL_FALSE, gl_state.normalMatrix->data()); CHECK_GL("UniformMatrix u_normal"); } if (gl_state.u_mvp > -1) { glUniformMatrix4fv(gl_state.u_mvp, 1, GL_FALSE, gl_state.mvp->data()); CHECK_GL("UniformMatrix u_mvp"); } } void SetTexUniforms(const GLuint prog_id, const GLTexState &gl_tex) { glActiveTexture(GL_TEXTURE0 + gl_tex.slot_id); glBindTexture(GL_TEXTURE_2D, gl_tex.tex_id); GLint loc = glGetUniformLocation(prog_id, gl_tex.sampler_name.c_str()); if (loc > -1) { glUniform1i(loc, gl_tex.slot_id); } CHECK_GL("glUniform1i u_modelview"); } bool LoadShaders(GLProgramState &gl_state) { // default = show normal vector as color. std::string vert_str(reinterpret_cast(shaders_no_skinning_vert), shaders_no_skinning_vert_len); std::string frag_str(reinterpret_cast(shaders_usdpreviewsurface_frag), shaders_usdpreviewsurface_frag_len); example::shader default_shader("default", vert_str, frag_str); gl_state.shaders["default"] = std::move(default_shader); return true; } bool SetupGLUniforms(GLuint prog_id, GLVertexUniformState &gl_v_uniform_state) { GLint model_loc = glGetUniformLocation(prog_id, kUniformModelMatrix); if (model_loc < 0) { std::cerr << kUniformModelMatrix << " not found in the vertex shader.\n"; // return false; } else { gl_v_uniform_state.u_model = model_loc; } GLint norm_loc = glGetUniformLocation(prog_id, kUniformNormalMatrix); if (norm_loc < 0) { std::cerr << kUniformNormalMatrix << " not found in the vertex shader.\n"; // return false; } else { gl_v_uniform_state.u_normal = norm_loc; } GLint mvp_loc = glGetUniformLocation(prog_id, kUniformMVPMatrix); if (mvp_loc < 0) { std::cerr << kUniformMVPMatrix << " not found in the vertex shader.\n"; // return false; } else { gl_v_uniform_state.u_mvp = mvp_loc; } return true; } bool SetupTexture(const tinyusdz::tydra::RenderScene &scene, tinyusdz::tydra::UVTexture &tex) { auto glwrapmode = [](const tinyusdz::tydra::UVTexture::WrapMode mode) { if (mode == tinyusdz::tydra::UVTexture::WrapMode::CLAMP_TO_EDGE) { return GL_CLAMP_TO_EDGE; } else if (mode == tinyusdz::tydra::UVTexture::WrapMode::REPEAT) { return GL_REPEAT; } else if (mode == tinyusdz::tydra::UVTexture::WrapMode::MIRROR) { return GL_MIRRORED_REPEAT; } else if (mode == tinyusdz::tydra::UVTexture::WrapMode::CLAMP_TO_BORDER) { return GL_CLAMP_TO_BORDER; } // Just in case: Fallback to REPEAT return GL_REPEAT; }; GLTexState texState; GLTexParams texParams; texParams.wrapS = glwrapmode(tex.wrapS); texParams.wrapT = glwrapmode(tex.wrapT); GLuint texid{0}; glGenTextures(1, &texid); 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, texParams.wrapS); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texParams.wrapT); // Transparent black For `black` wrap mode. // https://github.com/PixarAnimationStudios/OpenUSD/commit/2cf6612b2b1d5a1a1031bc153867116c5963e605 texParams.borderCol = {0.0f, 0.0f, 0.0f, 0.0f}; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, &texParams.borderCol[0]); CHECK_GL("texture_id[" << std::to_string(tex.texture_image_id) << "] glTexParameters"); texState.texParams = std::move(texParams); int64_t image_id = tex.texture_image_id; if (image_id >= 0) { if (image_id < scene.images.size()) { const tinyusdz::tydra::TextureImage &image = scene.images[size_t(image_id)]; if ((image.width < 1) || (image.height < 1) || (image.channels < 1)) { std::cerr << "Texture image is not loaded(texture file not found?).\n"; glBindTexture(GL_TEXTURE_2D, 0); return false; } uint32_t bytesperpixel = 1; GLenum format{GL_LUMINANCE}; if (image.channels == 1) { format = GL_LUMINANCE; bytesperpixel = 1; } else if (image.channels == 2) { format = GL_LUMINANCE_ALPHA; bytesperpixel = 2; } else if (image.channels == 3) { format = GL_RGB; bytesperpixel = 3; } else if (image.channels == 4) { format = GL_RGBA; bytesperpixel = 4; } GLenum type{GL_BYTE}; if (image.texelComponentType == tinyusdz::tydra::ComponentType::UInt8) { type = GL_BYTE; bytesperpixel *= 1; } else if (image.texelComponentType == tinyusdz::tydra::ComponentType::Half) { type = GL_SHORT; bytesperpixel *= 2; } else if (image.texelComponentType == tinyusdz::tydra::ComponentType::UInt32) { type = GL_UNSIGNED_INT; bytesperpixel *= 4; } else if (image.texelComponentType == tinyusdz::tydra::ComponentType::Float) { type = GL_FLOAT; bytesperpixel *= 4; } else { std::cout << "Unsupported texelComponentType: " << tinyusdz::tydra::to_string(image.texelComponentType) << "\n"; } int64_t buffer_id = image.buffer_id; if ((buffer_id >= 0) && (buffer_id < scene.buffers.size())) { const tinyusdz::tydra::BufferData &buffer = scene.buffers[size_t(buffer_id)]; // byte length check. if (size_t(image.width) * size_t(image.height) * size_t(bytesperpixel) > buffer.data.size()) { std::cerr << "Insufficient texel data. : " << "width: " << image.width << ", height " << image.height << ", bytesperpixel " << bytesperpixel << ", requested bytes: " << size_t(image.width) * size_t(image.height) * size_t(bytesperpixel) << ", buffer bytes: " << std::to_string(buffer.data.size()) << "\n"; // continue anyway } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0, format, type, buffer.data.data()); CHECK_GL("texture_id[" << std::to_string(image_id) << "] glTexImage2D"); } } } } tex.handle = texid; glBindTexture(GL_TEXTURE_2D, 0); return true; } static void BuildFacevaryingGeometricNormals( const std::vector &points, std::vector &geom_facevarying_normals); static bool SetupMesh(const tinyusdz::Axis &stageUpAxis, const tinyusdz::tydra::RenderMesh &mesh, GLuint program_id, GLMeshState &gl_state) // [out] { std::cout << "program_id " << program_id << "\n"; std::vector indices; if (mesh.faceVertexCounts.empty()) { // assume all triangulaged faces. if ((mesh.faceVertexIndices.size() % 3) != 0) { std::cerr << "mesh <" << mesh.abs_name << "> faceVertexIndices.size " << std::to_string(mesh.faceVertexIndices.size()) << " must be multiple of 3\n"; } for (size_t f = 0; f < mesh.faceVertexIndices.size() / 3; f++) { for (size_t c = 0; c < 3; c++) { indices.push_back(mesh.faceVertexIndices[3 * f + c]); } } } else { size_t faceVertexIndexOffset = 0; // Currently all faces must be triangle. for (size_t f = 0; f < mesh.faceVertexCounts.size(); f++) { if (mesh.faceVertexCounts[f] != 3) { std::cerr << "mesh <" << mesh.abs_name << "> Non triangle face found at faceVertexCounts[" << f << "] (" << mesh.faceVertexCounts[f] << ")\n"; return false; } size_t fvCounts = mesh.faceVertexCounts[f]; for (size_t c = 0; c < fvCounts; c++) { indices.push_back(mesh.faceVertexIndices[faceVertexIndexOffset + c]); } faceVertexIndexOffset += fvCounts; } } glGenVertexArrays(1, &gl_state.vertex_array_object); CHECK_GL(mesh.abs_name << "GenVertexArrays"); glBindVertexArray(gl_state.vertex_array_object); CHECK_GL(mesh.abs_name << "BindVertexArray"); // // Current settings // - position // - normals // - texcoords0 // // all vertex attribs are represented as facevarying data. // // - Static mesh(STATIC_DRAW) only std::vector facevaryingVertices; { // position // expand position to facevarying data. // assume faces are all triangle. gl_state.num_triangles = indices.size() / 3; for (size_t i = 0; i < indices.size() / 3; i++) { size_t vi0 = indices[3 * i + 0]; size_t vi1 = indices[3 * i + 1]; size_t vi2 = indices[3 * i + 2]; if (vi0 >= mesh.points.size()) { std::cerr << "indices[" << (3 * i + 0) << "(" << vi0 << ") exceeds mesh.points.size()(" << mesh.points.size() << ")\n"; return false; } if (vi1 >= mesh.points.size()) { std::cerr << "indices[" << (3 * i + 1) << "(" << vi1 << ") exceeds mesh.points.size()(" << mesh.points.size() << ")\n"; return false; } if (vi2 >= mesh.points.size()) { std::cerr << "indices[" << (3 * i + 2) << "(" << vi2 << ") exceeds mesh.points.size()(" << mesh.points.size() << ")\n"; return false; } if (stageUpAxis == tinyusdz::Axis::Z) { tinyusdz::tydra::vec3 p0; p0[0] = mesh.points[vi0][0]; p0[1] = mesh.points[vi0][2]; p0[2] = mesh.points[vi0][1]; tinyusdz::tydra::vec3 p1; p1[0] = mesh.points[vi1][0]; p1[1] = mesh.points[vi1][2]; p1[2] = mesh.points[vi1][1]; tinyusdz::tydra::vec3 p2; p2[0] = mesh.points[vi2][0]; p2[1] = mesh.points[vi2][2]; p2[2] = mesh.points[vi2][1]; facevaryingVertices.push_back(p0); facevaryingVertices.push_back(p1); facevaryingVertices.push_back(p2); } else { // TODO: upAxis X facevaryingVertices.push_back(mesh.points[vi0]); facevaryingVertices.push_back(mesh.points[vi1]); facevaryingVertices.push_back(mesh.points[vi2]); } } GLuint vb; glGenBuffers(1, &vb); glBindBuffer(GL_ARRAY_BUFFER, vb); glBufferData(GL_ARRAY_BUFFER, facevaryingVertices.size() * sizeof(tinyusdz::tydra::vec3), facevaryingVertices.data(), GL_STATIC_DRAW); CHECK_GL("Set facevaryingVertices buffer data"); GLint loc = glGetAttribLocation(program_id, kAttribPoints); if (loc > -1) { glEnableVertexAttribArray(loc); glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, /* stride */ sizeof(GLfloat) * 3, 0); CHECK_GL("VertexAttribPointer"); } else { std::cerr << "vertex positions: " << kAttribPoints << " attribute not found in vertex shader.\n"; return false; } } std::vector facevaryingNormals; if (mesh.facevaryingNormals.size()) { facevaryingNormals = mesh.facevaryingNormals; } else { BuildFacevaryingGeometricNormals(facevaryingVertices, facevaryingNormals); } if (facevaryingNormals.size()) { // normals GLuint vb; glGenBuffers(1, &vb); glBindBuffer(GL_ARRAY_BUFFER, vb); glBufferData(GL_ARRAY_BUFFER, mesh.facevaryingNormals.size() * sizeof(tinyusdz::tydra::vec3), mesh.facevaryingNormals.data(), GL_STATIC_DRAW); CHECK_GL("Set facevaryingNormals buffer data"); GLint loc = glGetAttribLocation(program_id, kAttribNormals); if (loc > -1) { glEnableVertexAttribArray(loc); glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, /* stride */ sizeof(GLfloat) * 3, 0); CHECK_GL("VertexAttribPointer"); } else { std::cerr << "vertex normals: " << kAttribNormals << " attribute not found in vertex shader. Shader does not use it?\n"; // may ok } } // texcoords0 only // TODO: multi texcoords if (mesh.facevaryingTexcoords.size() == 1) { for (const auto it : mesh.facevaryingTexcoords) { uint32_t slot_id = it.first; if (slot_id >= kMaxTexCoords) { std::cerr << "Texcoord slot id " << slot_id << " must be less than kMaxTexCoords " << kMaxTexCoords << "\n"; return false; } GLuint vb; glGenBuffers(1, &vb); glBindBuffer(GL_ARRAY_BUFFER, vb); glBufferData(GL_ARRAY_BUFFER, it.second.size() * sizeof(tinyusdz::tydra::vec2), it.second.data(), GL_STATIC_DRAW); CHECK_GL("Set facevaryingTexcoord0 buffer data"); std::string texattr = kAttribTexCoordBase + std::to_string(slot_id); GLint loc = glGetAttribLocation(program_id, texattr.c_str()); if (loc > -1) { glEnableVertexAttribArray(loc); glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, /* stride */ sizeof(GLfloat) * 2, 0); CHECK_GL("VertexAttribPointer"); } else { std::cerr << "Texture UV0: " << texattr << " attribute not found in vertex shader.\n"; // may OK } } } // We build facevarying vertex data, so no index buffers. #if 0 // Build index buffer. GLuint elementbuffer; glGenBuffers(1, &elementbuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW); CHECK_GL(mesh.abs_name << "ElementArrayBuffer"); #endif glBindVertexArray(0); CHECK_GL(mesh.abs_name << "UnBind VAO"); return true; } static void DrawMesh(const GLMeshState &gl_state) { // Simply bind vertex array object and call glDrawArrays. glBindVertexArray(gl_state.vertex_array_object); glDrawArrays(GL_TRIANGLES, 0, gl_state.num_triangles * 3); CHECK_GL("DrawArrays"); glBindVertexArray(0); } static void DrawNode(GLNodeState &gl_node, const tinyusdz::value::matrix4f &viewproj) { // FIXME tinyusdz::value::matrix4d identm = tinyusdz::value::matrix4d::identity(); tinyusdz::value::double3 trans = {0.0, 0.0, 0.0}; tinyusdz::value::double3 rotate = {0.0, 0.0, 0.0}; tinyusdz::value::double3 scale = {1.0, 1.0, -1.0}; tinyusdz::value::matrix4d rotm = tinyusdz::trs_angle_xyz(trans, rotate, scale); tinyusdz::value::matrix4d modelm = rotm * identm; SetupVertexUniforms(gl_node.gl_v_uniform_state, modelm, viewproj); SetVertexUniforms(gl_node.gl_v_uniform_state); // SetTexUniforms(gl_node.gl_tex_state); DrawMesh(gl_node.gl_mesh_state); } static void ConvertMatrix(example::mat4 &m, tinyusdz::value::matrix4f &dst) { for (size_t i = 0; i < 4; i++) { for (size_t j = 0; j < 4; j++) { dst.m[i][j] = m[i][j]; } } } static void DrawScene(const example::shader &shader, GLScene &scene) { // // Use single shader for the scene // tinyusdz::value::matrix4f view; ConvertMatrix(gCtx.camera.matrices.view, view); tinyusdz::value::matrix4f proj; ConvertMatrix(gCtx.camera.matrices.perspective, proj); tinyusdz::value::matrix4f viewproj = view * proj; // bind program shader.use(); CHECK_GL("shader.use"); for (size_t i = 0; i < scene.gl_nodes.size(); i++) { auto &gl_node = scene.gl_nodes[i]; DrawNode(gl_node, viewproj); } glUseProgram(0); CHECK_GL("glUseProgram(0)"); } static void ComputeBoundingBox(const tinyusdz::tydra::RenderMesh &mesh, std::array &bmin, std::array &bmax) { bmin = {std::numeric_limits::infinity(), std::numeric_limits::infinity(), std::numeric_limits::infinity()}; bmax = {-std::numeric_limits::infinity(), -std::numeric_limits::infinity(), -std::numeric_limits::infinity()}; for (const auto &p : mesh.points) { bmin[0] = (std::min)(bmin[0], p[0]); bmin[1] = (std::min)(bmin[1], p[1]); bmin[2] = (std::min)(bmin[2], p[2]); bmax[0] = (std::max)(bmax[0], p[0]); bmax[1] = (std::max)(bmax[1], p[1]); bmax[2] = (std::max)(bmax[2], p[2]); } } static bool ProcScene(const example::shader &gl_shader, const tinyusdz::Stage &stage, std::string &asset_search_path, GLScene *scene) { tinyusdz::Axis upAxis{tinyusdz::Axis::Y}; if (stage.metas().upAxis.authored()) { upAxis = stage.metas().upAxis.get_value(); } std::cout << "upAxis " << tinyusdz::to_string(upAxis) << "\n"; // // Stage to Renderable Scene // tinyusdz::tydra::RenderSceneConverter converter; converter.set_search_paths({asset_search_path}); tinyusdz::tydra::RenderScene renderScene; bool ret = converter.ConvertToRenderScene(stage, &renderScene); if (converter.GetWarning().size()) { std::cout << "ConvertToRenderScene WARN: " << converter.GetWarning() << "\n"; gCtx.converter_warn = converter.GetWarning(); } if (!ret) { std::cerr << "Failed to convert USD Stage to OpenGL-like data structure: " << converter.GetError() << "\n"; exit(-1); } std::cout << "# of meshes: " << renderScene.meshes.size() << "\n"; std::cout << "# of textures: " << renderScene.textures.size() << "\n"; gCtx.converter_info = converter.GetInfo(); std::array scene_bmin; std::array scene_bmax; scene_bmin = {std::numeric_limits::infinity(), std::numeric_limits::infinity(), std::numeric_limits::infinity()}; scene_bmax = {-std::numeric_limits::infinity(), -std::numeric_limits::infinity(), -std::numeric_limits::infinity()}; tinyusdz::value::matrix4f view; ConvertMatrix(gCtx.camera.matrices.view, view); tinyusdz::value::matrix4f proj; ConvertMatrix(gCtx.camera.matrices.perspective, proj); tinyusdz::value::matrix4f viewproj = proj * view; std::map gl_tex_state_map; for (size_t i = 0; i < renderScene.textures.size(); i++) { SetupTexture(renderScene, renderScene.textures[i]); } // TODO: Material for (size_t i = 0; i < renderScene.meshes.size(); i++) { std::array bmin; std::array bmax; ComputeBoundingBox(renderScene.meshes[i], bmin, bmax); std::cout << "mesh[" << i << "].bmin " << bmin[0] << ", " << bmin[1] << ", " << bmin[2] << "\n"; std::cout << "mesh[" << i << "].bmax " << bmax[0] << ", " << bmax[1] << ", " << bmax[2] << "\n"; // TODO: accounnt for xform scene_bmin[0] = (std::min)(bmin[0], scene_bmin[0]); scene_bmin[1] = (std::min)(bmin[1], scene_bmin[1]); scene_bmin[2] = (std::min)(bmin[2], scene_bmin[2]); scene_bmax[0] = (std::max)(bmax[0], scene_bmax[0]); scene_bmax[1] = (std::max)(bmax[1], scene_bmax[1]); scene_bmax[2] = (std::max)(bmax[2], scene_bmax[2]); GLMeshState gl_mesh; if (!SetupMesh(upAxis, renderScene.meshes[i], gl_shader.get_program(), gl_mesh)) { std::cerr << "SetupMesh for mesh[" << i << "] failed.\n"; exit(-1); } GLNodeState gl_node; gl_node.gl_mesh_state = gl_mesh; // FIXME: tinyusdz::value::double3 scene_center; scene_center[0] = scene_bmin[0] + 0.5 * (scene_bmax[0] - scene_bmin[0]); scene_center[1] = scene_bmin[1] + 0.5 * (scene_bmax[1] - scene_bmin[1]); scene_center[2] = scene_bmin[2] + 0.5 * (scene_bmax[2] - scene_bmin[2]); // FIXME tinyusdz::value::matrix4d identm = tinyusdz::value::matrix4d::identity(); tinyusdz::value::double3 trans = {scene_center[0], scene_center[1], scene_center[2]}; tinyusdz::value::double3 rotate = {0.0, 0.0, 0.0}; tinyusdz::value::double3 scale = {1.0, 1.0, -1.0}; tinyusdz::value::matrix4d rotm = tinyusdz::trs_angle_xyz(trans, rotate, scale); tinyusdz::value::matrix4d modelm = rotm * identm; std::cout << "global matrix: " << identm << "\n"; SetupVertexUniforms(gl_node.gl_v_uniform_state, modelm, viewproj); SetupGLUniforms(gl_shader.get_program(), gl_node.gl_v_uniform_state); scene->gl_nodes.emplace_back(gl_node); } scene->bmin = scene_bmin; scene->bmax = scene_bmax; // TODO return true; } static void vnormalize(float v[3]) { float r; r = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); if (r == 0.0) return; v[0] /= r; v[1] /= r; v[2] /= r; } static void vcross(float v1[3], float v2[3], float result[3]) { result[0] = v1[1] * v2[2] - v1[2] * v2[1]; result[1] = v1[2] * v2[0] - v1[0] * v2[2]; result[2] = v1[0] * v2[1] - v1[1] * v2[0]; } // based on mesa. static void MygluLookAt(float eye[3], float center[3], float up[3]) { float forward[3], side[3]; GLfloat m[4][4]; forward[0] = center[0] - eye[0]; forward[1] = center[1] - eye[1]; forward[2] = center[2] - eye[2]; vnormalize(forward); /* Side = forward x up */ vcross(forward, up, side); vnormalize(side); /* Recompute up as: up = side x forward */ vcross(side, forward, up); memset(m, 0, sizeof(GLfloat) * 16); m[0][0] = 1.0f; m[1][1] = 1.0f; m[2][2] = 1.0f; m[3][3] = 1.0f; m[0][0] = side[0]; m[1][0] = side[1]; m[2][0] = side[2]; m[0][1] = up[0]; m[1][1] = up[1]; m[2][1] = up[2]; m[0][2] = -forward[0]; m[1][2] = -forward[1]; m[2][2] = -forward[2]; glMultMatrixf(&m[0][0]); // TODO: Use glTranslated? glTranslatef(-eye[0], -eye[1], -eye[2]); } std::string find_file(const std::string basefile, int max_parents = 8) { if (max_parents > 16) { return std::string(); } std::string filepath = basefile; for (size_t i = 0; i < max_parents; i++) { if (tinyusdz::io::FileExists(filepath)) { return filepath; } filepath = "../" + filepath; } return std::string(); } static void BuildFacevaryingGeometricNormals( const std::vector &points, std::vector &geom_facevarying_normals) { if ((points.size() % 3) != 0) { return; } size_t npoints = points.size() / 3; for (size_t i = 0; i < npoints; i++) { tinyusdz::value::point3f p0; p0.x = points[3 * i + 0][0]; p0.y = points[3 * i + 0][1]; p0.z = points[3 * i + 0][2]; tinyusdz::value::point3f p1; p1.x = points[3 * i + 1][0]; p1.y = points[3 * i + 1][1]; p1.z = points[3 * i + 1][2]; tinyusdz::value::point3f p2; p2.x = points[3 * i + 2][0]; p2.y = points[3 * i + 2][1]; p2.z = points[3 * i + 2][2]; tinyusdz::value::point3f p10 = p1 - p0; tinyusdz::value::point3f p20 = p2 - p0; // CCW tinyusdz::value::point3f Ng = tinyusdz::vcross(p10, p20); Ng = tinyusdz::vnormalize(Ng); tinyusdz::tydra::vec3 nf; nf[0] = Ng.x; nf[1] = Ng.y; nf[2] = Ng.z; geom_facevarying_normals.push_back(nf); geom_facevarying_normals.push_back(nf); geom_facevarying_normals.push_back(nf); } } static void ImMatrix4Display(const char *label, const float *m) { static std::string labels[4]; labels[0] = std::string(label) + " m0"; labels[1] = std::string(label) + " m1"; labels[2] = std::string(label) + " m2"; labels[3] = std::string(label) + " m3"; ImGui::InputFloat4(labels[0].c_str(), const_cast(m), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat4(labels[1].c_str(), const_cast(m + 4), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat4(labels[2].c_str(), const_cast(m + 8), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat4(labels[3].c_str(), const_cast(m + 12), "%.3f", ImGuiInputTextFlags_ReadOnly); } static void ImMatrix3Display(const char *label, const float *m) { static std::string labels[3]; labels[0] = std::string(label) + " m0"; labels[1] = std::string(label) + " m1"; labels[2] = std::string(label) + " m2"; ImGui::InputFloat3(labels[0].c_str(), const_cast(m), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3(labels[1].c_str(), const_cast(m + 3), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3(labels[2].c_str(), const_cast(m + 6), "%.3f", ImGuiInputTextFlags_ReadOnly); } } // namespace int main(int argc, char **argv) { // Setup window glfwSetErrorCallback(error_callback); if (!glfwInit()) { exit(EXIT_FAILURE); } // Decide GL+GLSL versions #if defined(IMGUI_IMPL_OPENGL_ES2) // GL ES 2.0 + GLSL 100 const char *glsl_version = "#version 100"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); #elif defined(__APPLE__) // GL 3.2 + GLSL 150 const char *glsl_version = "#version 150"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac #else // GL 3.0 + GLSL 130 const char *glsl_version = "#version 130"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ // only glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only #endif float highDPIscaleFactor = 1.0f; float xscale=1.0f; float yscale=1.0f; #if defined(_WIN32) || defined(__linux__) // if it's a HighDPI monitor, try to scale everything GLFWmonitor *monitor = glfwGetPrimaryMonitor(); glfwGetMonitorContentScale(monitor, &xscale, &yscale); std::cout << "monitor xscale, yscale = " << xscale << ", " << yscale << "\n"; if (xscale > 1 || yscale > 1) { highDPIscaleFactor = xscale; glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); } #elif __APPLE__ glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); #endif #ifdef _DEBUG_OPENGL glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); #endif std::string filename = "models/suzanne.usdc"; // std::string filename = "models/texture-cat-plane.usdc"; // std::string filename = "models/texturedcube.usdc"; // std::string filename = "models/simple-plane.usdz"; #if defined(_MSC_VER) std::cout << "cwd: " << _getcwd(nullptr, 0) << "\n"; #endif if (argc > 1) { filename = std::string(argv[1]); } std::string full_filepath = find_file(filename); if (full_filepath.empty()) { std::cerr << "cannot find or file not exists: " << filename << "\n"; } std::cout << "Loading USD file " << full_filepath << "\n"; std::string warn; std::string err; tinyusdz::Stage stage; bool ret = tinyusdz::LoadUSDFromFile(full_filepath, &stage, &warn, &err); if (!warn.empty()) { std::cerr << "WARN : " << warn << "\n"; return EXIT_FAILURE; } if (!err.empty()) { std::cerr << "ERR : " << err << "\n"; return EXIT_FAILURE; } std::string basedir = tinyusdz::io::GetBaseDir(full_filepath); std::cout << "basedir = " << basedir << "\n"; gCtx.usd_filepath = full_filepath; GLFWwindow *window{nullptr}; window = glfwCreateWindow(gCtx.width, gCtx.height, "Simple USDZ GL viewer", nullptr, nullptr); glfwMakeContextCurrent(window); glfwSwapInterval(1); // vsync on if (!gladLoadGLLoader(reinterpret_cast(glfwGetProcAddress))) { std::cerr << "Failed to load OpenGL functions with gladLoadGL\n"; exit(EXIT_FAILURE); } std::cout << "OpenGL " << GLVersion.major << '.' << GLVersion.minor << '\n'; if (GLVersion.major < 2) { std::cerr << "OpenGL 2. or later should be available." << std::endl; exit(EXIT_FAILURE); } #ifdef _DEBUG_OPENGL glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(reinterpret_cast(glDebugOutput), nullptr); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); #endif GUIContext gui_ctx; glfwSetWindowUserPointer(window, &gui_ctx); glfwSetKeyCallback(window, key_callback); glfwSetCursorPosCallback(window, mouse_move_callback); glfwSetMouseButtonCallback(window, mouse_button_callback); glfwSetFramebufferSizeCallback(window, resize_callback); bool done = false; ImGui::CreateContext(); ImGui::StyleColorsDark(); ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version); GLProgramState gl_progs; if (!LoadShaders(gl_progs)) { exit(-1); } GLScene gl_scene; if (!ProcScene(gl_progs.shaders["default"], stage, /* asset search path */ basedir, &gl_scene)) { exit(-1); } std::cout << "scene bmin: " << gl_scene.bmin[0] << ", " << gl_scene.bmin[1] << ", " << gl_scene.bmin[2] << "\n"; std::cout << "scene bmax: " << gl_scene.bmax[0] << ", " << gl_scene.bmax[1] << ", " << gl_scene.bmax[2] << "\n"; int display_w, display_h; ImVec4 clear_color = {0.1f, 0.18f, 0.3f, 1.0f}; { float cxscale, cyscale; glfwGetWindowContentScale(window, &cxscale, &cyscale); std::cout << "xscale, yscale = " << cxscale << ", " << cyscale << "\n"; ImGuiIO &io = ImGui::GetIO(); io.DisplayFramebufferScale = {2.0f, 2.0f}; // HACK ImFontConfig font_config; font_config.SizePixels = 16.0f * xscale; io.Fonts->AddFontDefault(&font_config); } std::array bmin = {-100.0f, -100.0f, -100.0f}; std::array bmax = {100.0f, 100.0f, 100.0f}; float maxExtent = 0.5f * (bmax[0] - bmin[0]); if (maxExtent < 0.5f * (bmax[1] - bmin[1])) { maxExtent = 0.5f * (bmax[1] - bmin[1]); } if (maxExtent < 0.5f * (bmax[2] - bmin[2])) { maxExtent = 0.5f * (bmax[2] - bmin[2]); } float eye[3], lookat[3], up[3]; up[0] = 0.0f; up[1] = 1.0f; up[2] = 0.0f; const example::shader &curr_shader = gl_progs.shaders["default"]; while (!done) { glfwPollEvents(); ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); ImGui::Begin("Info"); ImGui::Text("View control"); ImGui::Text("ctrl + left mouse"); ImGui::Text("shift + left mouse"); ImGui::Text("left mouse"); ImGui::End(); ImGui::Begin("Scene"); ImGui::InputText("filename", const_cast(gCtx.usd_filepath.c_str()), gCtx.usd_filepath.size(), ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("scene bmin", &gl_scene.bmin[0], "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("scene bmax", &gl_scene.bmax[0], "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::End(); ImGui::Begin("Material"); ImGui::End(); if (gCtx.selected_surfaceShader) { UsdPreviewSurfaceParamUI(*gCtx.selected_surfaceShader); } ImGui::Begin("RenderScene converter log"); ImGui::InputTextMultiline("info", const_cast(gCtx.converter_info.c_str()), gCtx.converter_info.size(), ImVec2(800, 300), ImGuiInputTextFlags_ReadOnly); ImGui::InputTextMultiline("warn", const_cast(gCtx.converter_warn.c_str()), gCtx.converter_warn.size(), ImVec2(800, 300), ImGuiInputTextFlags_ReadOnly); ImGui::End(); // For developers only ImGui::Begin("dev"); { static bool compile_ok{true}; if (ImGui::Button("Reload shader")) { compile_ok = ReloadShader(curr_shader.get_program(), gCtx.vert_filename, gCtx.frag_filename); } if (compile_ok) { ImGui::TextColored(ImVec4(0.3, 1.0, 0.4, 1.0), "Shader Compile OK"); } else { ImGui::TextColored(ImVec4(1.0, 0.2, 0.1, 1.0), "Shader Compile Failed"); } ImGui::End(); } ImGui::Begin("Camera"); ImGui::SliderFloat("fov", &gCtx.fov, 0.0f, 178.0f); ImGui::InputFloat("znear", &gCtx.znear); ImGui::InputFloat("zfar", &gCtx.zfar); ImGui::InputFloat3("eye", &gCtx.eye[0]); ImGui::Separator(); ImGui::SliderFloat("xrot", &gCtx.xrotate, -89.9f, 89.9f); ImGui::SliderFloat("yrot", &gCtx.yrotate, -180.0f, 180.0f); ImGui::Separator(); if (ImGui::Button("Reset rotation")) { gCtx.xrotate = 0.0f; gCtx.yrotate = 0.0f; } ImGui::End(); glfwGetFramebufferSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); glEnable(GL_DEPTH_TEST); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); float aspect = float(display_w) / float(display_h); // view gCtx.camera.setPosition(gCtx.eye); gCtx.camera.setRotation({gCtx.xrotate, gCtx.yrotate, 0.0f}); gCtx.camera.setPerspective(gCtx.fov, aspect, gCtx.znear, gCtx.zfar); ImGui::Begin("View matrix"); { tinyusdz::value::matrix4f view; ConvertMatrix(gCtx.camera.matrices.view, view); tinyusdz::value::matrix4f proj; ConvertMatrix(gCtx.camera.matrices.perspective, proj); // example::mat4 viewproj = linalg::mul(gCtx.camera.matrices.perspective, // gCtx.camera.matrices.view); tinyusdz::value::matrix4f viewproj = view * proj; ImMatrix4Display("view", &gCtx.camera.matrices.view[0][0]); ImGui::Separator(); ImMatrix4Display("perspective", &gCtx.camera.matrices.perspective[0][0]); ImGui::Separator(); // ImMatrix4Display("viewproj", &viewproj.x[0]); ImMatrix4Display("viewproj", &viewproj.m[0][0]); ImGui::End(); } DrawScene(curr_shader, gl_scene); #if 0 // Draw scene if ((scene.default_root_node >= 0) && (scene.default_root_node < scene.nodes.size())) { DrawNode(scene, scene.nodes[scene.default_root_node]); } else { static bool printed = false; if (!printed) { std::cerr << "USD scene does not contain root node. or has invalid root node ID\n"; std::cerr << " # of nodes in the scene: " << scene.nodes.size() << "\n"; std::cerr << " scene.default_root_node: " << scene.default_root_node << "\n"; printed = true; } } #endif // Imgui ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); // glFlush(); glfwSwapBuffers(window); static int frameCount = 0; static double currentTime = glfwGetTime(); static double previousTime = currentTime; static char title[256]; frameCount++; currentTime = glfwGetTime(); const auto deltaTime = currentTime - previousTime; if (deltaTime >= 1.0) { sprintf(title, "Simple GL USDC/USDA/USDZ viewer [%dFPS]", frameCount); glfwSetWindowTitle(window, title); frameCount = 0; previousTime = currentTime; } done = glfwWindowShouldClose(window); }; std::cout << "Close window\n"; ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); return EXIT_SUCCESS; }