/*############################################################################### # # Copyright 2021 NVIDIA Corporation # # 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 "OpenGLMeshRenderer.h" #include #include #include #include #ifdef _MSC_VER #include "glad/glad.h" #define strcasecmp _stricmp #else #include #endif // _MSC_VER #include "FaceIO.h" #include "GLFW/glfw3.h" #include "glm/glm.hpp" #include "glm/gtc/matrix_transform.hpp" #include "glm/gtc/quaternion.hpp" #include #include "GLMaterial.h" #include "GLMesh.h" #include "GLShaders.h" #include "GLSpectrum.h" #include "nvAR_defs.h" #include "nvCVOpenCV.h" #include "opencv2/highgui/highgui.hpp" #include "SimpleFaceModel.h" //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// ///// SUPPORT MACROS AND FUNCTIONS ///// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// #define BAIL_IF_ERR(err) do { if ((err) != 0) { goto bail; } } while(0) #define BAIL(err, code) do { err = code; goto bail; } while(0) #ifndef __BYTE_ORDER__ /* How bytes are packed into a 32 bit word */ #define __ORDER_LITTLE_ENDIAN__ 3210 /* First byte in the least significant position */ #define __ORDER_BIG_ENDIAN__ 0123 /* First byte in the most significant position */ #if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) || _MSC_VER #define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ #endif /* _MSC_VER */ #endif /* __BYTE_ORDER__ */ /******************************************************************************** * glfwErrorCallback ********************************************************************************/ static void glfwErrorCallback(int error, const char *description) { fprintf(stderr, "Error %d: %s\n", error, description); } /******************************************************************************** * MakeGLContext ********************************************************************************/ static NvCV_Status MakeGLContext(int width, int height, const char *title, GLFWwindow **pWindow) { NvCV_Status nvErr = NVCV_SUCCESS; GLFWwindow *window; /* Get a context */ glfwSetErrorCallback(glfwErrorCallback); if (!glfwInit()) { // Initialization failed fprintf(stderr, "Unable to initialize glfw\n"); return NVCV_ERR_INITIALIZATION; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); window = glfwCreateWindow(width, height, title, /*GLFWmonitor */NULL, /*GLFWwindow*/NULL); if (!window) { // Window or OpenGL context creation failed fprintf(stderr, "Unable to create glfw window\n"); BAIL(nvErr, NVCV_ERR_INITIALIZATION); } int winWidth, winHeight; glfwGetWindowSize(window, &winWidth, &winHeight); if (winWidth != width || winHeight != height) { fprintf(stderr, "getWindowSize(%u x %u) != (%u x %u)\n", winWidth, winHeight, width, height); } glfwMakeContextCurrent(window); #ifdef _MSC_VER if (!gladLoadGL()) { fprintf(stderr, "Unable to load GL\n"); BAIL(nvErr, NVCV_ERR_INITIALIZATION); } fprintf(stderr, "OpenGL Version %d.%d loaded\n", GLVersion.major, GLVersion.minor); #endif // _MSC_VER *pWindow = window; bail: return nvErr; } /******************************************************************************** * CloseGLContext ********************************************************************************/ static void CloseGLContext(GLFWwindow *window) { if (window) glfwDestroyWindow(window); glfwTerminate(); } /******************************************************************************** * ComputeDualTopologyFromAdjacencies ********************************************************************************/ static NvCV_Status ComputeDualTopologyFromAdjacencies(const SimpleFaceModelAdapter *fma, GLMesh *mesh) { union IVF { unsigned i; struct VF { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ unsigned short face, vertex; // Vertex in most significant position #else // __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ unsigned short vertex, face; // Vertex in most significant position #endif // __BYTE_ORDER__ } vf; bool operator<( const IVF& other) { return i < other.i; } bool operator==(const IVF& other) { return i == other.i; } }; std::vector topo; topo.reserve(mesh->numVertices() * 6 * 2); // Assume valence-6, duplicated const unsigned short *adjVertices = const_cast(fma)->getAdjacentVertices(0), *adjFaces = const_cast(fma)->getAdjacentFaces(0); unsigned n = fma->getAdjacentVerticesSize(); if (n != fma->getAdjacentFacesSize()) return NVCV_ERR_MISMATCH; for (unsigned ej = 0; ej < n; ej += 2) { // 2 adjacencies per edge for (unsigned vx = 0; vx < 2; ++vx) { // for every vertex on the edge for (unsigned fc = 0; fc < 2; ++fc) { // and every face on the edge IVF vf; vf.vf.vertex = adjVertices[ej + vx]; vf.vf.face = adjFaces[ej + fc]; if (vf.vf.vertex && vf.vf.face) { // if a real vertex and a real face --vf.vf.vertex; // convert from 1-based index ... --vf.vf.face; // ... to 0-based index topo.push_back(vf); } } } } std::sort(topo.begin(), topo.end()); topo.erase(std::unique(topo.begin(), topo.end()), topo.end()); mesh->resizeDualIndices(unsigned(topo.size())); unsigned short *dual = mesh->getDualIndices(), *numFaces = mesh->getVertexFaceCounts(); memset(numFaces, 0, mesh->numVertices() * sizeof(*numFaces)); for (unsigned i = 0; i < topo.size(); ++i) { numFaces[topo[i].vf.vertex]++; dual[i] = topo[i].vf.face; } return NVCV_SUCCESS; } /******************************************************************************** * MakeMesh ********************************************************************************/ NvCV_Status MakeMesh(const SimpleFaceModelAdapter *fma, GLMesh *mesh) { mesh->clear(); mesh->addVertices(fma->getShapeMeanSize() / 3, const_cast(fma)->getShapeMean(0)); mesh->addFaces(fma->getTriangleListSize() / 3, 3, const_cast(fma)->getTriangleList(0), 0, 0); NvCV_Status err = ComputeDualTopologyFromAdjacencies(fma, mesh); // This make vertex normal computation lightning fast if (NVCV_SUCCESS != err) return err; mesh->computeVertexNormals(); if (fma->fm.partitions.size()) { std::vector parts(fma->fm.partitions.size()); for (unsigned i = unsigned(parts.size()); i--;) { const SimpleFaceModel::Partition& fr = fma->fm.partitions[i]; GLMesh::Partition& to = parts[fr.partitionIndex]; //to.partitionIndex = fr.partitionIndex; // to doesn't have a partitionIndex to.faceIndex = fr.faceIndex; to.numFaces = fr.numFaces; to.vertexIndex = fr.vertexIndex; to.numVertexIndices = fr.numVertexIndices; to.name = fr.name; to.materialName = fr.materialName; to.smooth = fr.smoothingGroup; } mesh->partitionMesh(unsigned(parts.size()), parts.data()); } return NVCV_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// ///// RENDER CONTEXT ///// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// #define LAMBERTIAN_NUM_LIGHTS 2 class RenderContext { public: RenderContext() { m_win = nullptr; } ~RenderContext() { if (m_win) CloseGLContext(m_win); m_lam.shutdown(); m_txr.shutdown(); } NvCV_Status init() { if (0 != m_lam.startup()) return NVCV_ERR_OPENGL; if (0 != m_txr.startup()) return NVCV_ERR_OPENGL; return NVCV_SUCCESS; } void setClearColor(float r, float g, float b, float a = 1.f) { glClearColor(r, g, b, a); } void setClearColor(unsigned char r, unsigned char g, unsigned char b) { glClearColor(r * (1.f / 255.f), g * (1.f / 255.f), b * (1.f / 255.f), 1.f); } NvCV_Status makeWindowContext(int wd, int ht, const char *title) { NvCV_Status err = MakeGLContext(wd, ht, title, &m_win); if (NVCV_SUCCESS == err) { m_width = wd; m_height = ht; glfwMakeContextCurrent(m_win); glViewport(0, 0, wd, ht); glEnable(GL_DEPTH_TEST); glClearColor(0.f, 0.f, 0.f, 1.f); if (/*FLAG_orientation*/0) { glEnable(GL_CULL_FACE); glCullFace((/*FLAG_orientation*/0 > 0) ? GL_BACK : GL_FRONT); } else { glDisable(GL_CULL_FACE); } } return err; } void computeInverseViewMatrix() { #if 0 Vinv = glm::inverse(V); #else // Inversion is simple because we know that it is a rigid transform m_Vinv = glm::transpose(m_V); m_Vinv[3][0] = -(m_Vinv[0][0] * m_V[3][0] + m_Vinv[1][0] * m_V[3][1] + m_Vinv[2][0] * m_V[3][2]); m_Vinv[3][1] = -(m_Vinv[0][1] * m_V[3][0] + m_Vinv[1][1] * m_V[3][1] + m_Vinv[2][1] * m_V[3][2]); m_Vinv[3][2] = -(m_Vinv[0][2] * m_V[3][0] + m_Vinv[1][2] * m_V[3][1] + m_Vinv[2][2] * m_V[3][2]); m_Vinv[0][3] = 0.f; m_Vinv[1][3] = 0.f; m_Vinv[2][3] = 0.f; m_Vinv[3][3] = 1.f; #endif } void setViewMatrix(const glm::mat4x4& viewMatrix) { m_V = viewMatrix; computeInverseViewMatrix(); } void setViewMatrix(const glm::vec3& fromPoint, const glm::vec3& toPoint, const glm::vec3& upVector) { m_V = glm::lookAt(fromPoint, toPoint, upVector); computeInverseViewMatrix(); } void setOrthoCamera(float hither, float yon) { m_P = glm::orthoLH_NO(m_width * -.5f, m_width * +.5f, m_height * -.5f, m_height * +.5f, hither, yon); } void setOrthoCamera(float wd, float ht, float hither, float yon) { m_P = glm::orthoLH_NO(wd * -.5f, wd * +.5f, ht * -.5f, ht * +.5f, hither, yon); } void setLights(const glm::vec4 locs[LAMBERTIAN_NUM_LIGHTS], const GLSpectrum3f colors[LAMBERTIAN_NUM_LIGHTS]) { memcpy(m_lightLoc, locs, sizeof(m_lightLoc)); memcpy(m_lightColor, colors, sizeof(m_lightColor)); } void setViewOfBound(const GLMesh::BoundingSphere& bsph, const glm::vec3& lookAt, const glm::vec3& up, float vfov, float fracFill, float yDir = 1.f, float z_near = 0.0f, float z_far = 0.0f) { float r = bsph.radius() / fracFill, aspect = (float)m_width / (float)m_height, signZ = -yDir, dist; if (vfov > 0) { // Perspective dist = r * .5f / tanf(vfov * .5f); if (z_near == 0.0f && z_far == 0.0f) { // If z_near and z_far are both 0, use default values z_near = (dist - r) * 0.2f; z_far = (dist + r) * 2.0f; } m_P = glm::perspective(vfov, aspect, z_near, z_far); } else { // Orthographic float w = r, h = r; if (aspect < 1.f) h /= aspect; // Wide else w *= aspect; // Tall dist = r * 2.f; m_P = glm::orthoLH_NO(-w, +w, -h, +h, (dist - r) * signZ, (dist + r) * signZ); } m_V = glm::lookAt(bsph.center() - glm::normalize(lookAt) * dist, bsph.center(), up); computeInverseViewMatrix(); } void setViewOfBound(const GLMesh::BoundingBox& bbox, const glm::vec3& lookAt, const glm::vec3& up, float vfov, float fracFill, float yDir = 1.f, float yOff = 0.f, float z_near = 0.0f, float z_far = 0.0f) { glm::vec3 boxSize = bbox.max() - bbox.min(); glm::vec3 boxCenter = bbox.center(); float borderFrac = ((1.f - fracFill) / fracFill), dx = boxSize.x * (1.f + borderFrac), // border on left and right dy = boxSize.y * (1.f + borderFrac) * (1.f - fabsf(yOff)), r = ((dx > dy) ? dx : dy), // radius of bounding sphere aspectGeom = dx / dy, aspectWind = (float)m_width / (float)m_height, signZ = -yDir, dist; if (vfov > 0) { // Perspective dist = r * .5f / tanf(vfov * .5f); if (z_near == 0.0f && z_far == 0.0f) { // If z_near and z_far are both 0, use default values z_near = dist - r; z_far = dist + r; } m_P = glm::perspective(vfov, aspectWind, z_near, z_far); } else { // Orthographic if (aspectGeom > aspectWind) dy *= aspectGeom / aspectWind; else dx *= aspectWind / aspectGeom; dist = r * 2.f; dx *= .5f; dy *= .5f; m_P = glm::orthoLH_NO(-dx, +dx, -dy, +dy, (dist - r) * signZ, (dist + r) * signZ); } boxCenter.y += boxSize.y * yOff; m_V = glm::lookAt(boxCenter - glm::normalize(lookAt) * dist, boxCenter, up); computeInverseViewMatrix(); } NvCV_Status renderPolyMesh(const GLMesh& mesh, const glm::mat4x4& M, const char *materialOverride = nullptr) { NvCV_Status nvErr = NVCV_SUCCESS; glm::mat4x4 VP = m_P * m_V; const GLSpectrum3f defaultDiffuse = { 0.77f, 0.63f, 0.55f }, defaultAmbient = defaultDiffuse * 0.3f; #ifdef DEBUG_RENDERING unsigned why = mesh.notRenderable(0); if (why) { if (FLAG_debug) printf("Mesh %p is not renderable: %s: %s\n", &mesh, ((why & GLMesh::NOT_TRIMESH) ? "not a TriMesh" : ""), ((why & GLMesh::COMPLEX_TOPOLOGY) ? "complex topology" : "") ); return keErrGeometry; } #endif // DEBUG_RENDERING // Set lights for all shaders m_lam.setLights(&m_lightLoc[0].x, m_lightColor[0].data()); // TODO: set this elsewhere for (unsigned ix = 0, numPartitions = mesh.numPartitions(); ix < numPartitions; ++ix) { GLMesh::Partition pt; nvErr = mesh.getPartition(ix, pt); BAIL_IF_ERR(nvErr); const GLMaterial *mtl = m_mtlLib.getMaterial(materialOverride ? materialOverride : pt.materialName.c_str()); if (mesh.numNormals()) { // We should check for textures, too const GLSpectrum3f *difColor, *ambColor; if (mtl) { difColor = &mtl->diffuseColor; ambColor = &mtl->ambientColor; } else { difColor = &defaultDiffuse; ambColor = &defaultAmbient; } m_lam.drawTriMesh(mesh.numVertices(), &mesh.getVertices()->x, &mesh.getNormals()->x, pt.numVertexIndices, mesh.getVertexIndices() + pt.vertexIndex, &M[0][0], &VP[0][0], ambColor->data(), difColor->data()); } } bail: return nvErr; } unsigned m_width, m_height; ///< The dimensions of the viewport. GLFWwindow *m_win; ///< The window context. GLMaterialLibrary m_mtlLib; ///< The material library. glm::mat4x4 m_V, m_Vinv; ///< The viewing matrix and its inverse. glm::mat4x4 m_P; ///< The projection matrix. glm::vec4 m_lightLoc[LAMBERTIAN_NUM_LIGHTS]; ///< The light locations. GLSpectrum3f m_lightColor[LAMBERTIAN_NUM_LIGHTS]; ///< The light colors. LambertianRenderer m_lam; ///< The Lambertian renderer. TextureRenderer m_txr; ///< The texture renderer. }; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// ///// OPENGL MESH RENDERER ///// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// class OpenGLMeshRenderer : public MeshRenderer { public: ~OpenGLMeshRenderer(); static NvCV_Status initDispatch(MeshRenderer::Dispatch *dispatch); static NvCV_Status unload(); private: OpenGLMeshRenderer(); SimpleFaceModelAdapter _sfma; RenderContext _ctx; GLMesh _mesh; glm::vec3 _ctrRot; // C-style object-oriented member functions that are usually loaded from DLL, although in this implementation // the OpenGLMeshRenderer is compiled directly into the ExpressionApp, and the MeshRendererBroker is // automatically adding it to its portfolio of renderers without creating a separate DLL. static NvCV_Status create(MeshRenderer **han); static void destroy(MeshRenderer *han); static NvCV_Status name(const char **str); static NvCV_Status info(const char **str); static NvCV_Status read(MeshRenderer *han, const char *modelFile); static NvCV_Status init(MeshRenderer *han, unsigned width, unsigned height, const char *windowName); static NvCV_Status setCamera(MeshRenderer *han, const float locPt[3], const float lookVec[3], const float upVec[3], float vfov, float near_z = 0.0f, float far_z = 0.0f); static NvCV_Status render(MeshRenderer *han, const float exprs[53], const float qrot[4], const float tran[3], NvCVImage *result); NvCV_Status setFOV(float radians, float near_z = 0.0, float far_z = 0.0f); }; NvCV_Status OpenGLMeshRenderer_InitDispatch(MeshRenderer::Dispatch *dispatch) { return OpenGLMeshRenderer::initDispatch(dispatch); } NvCV_Status OpenGLMeshRenderer_Unload() { return OpenGLMeshRenderer::unload(); } /******************************************************************************** * DeformModel ********************************************************************************/ static NvCV_Status DeformModel(const SimpleFaceModel& model, const float *identCoeffs, const float *exprCoeffs, GLMesh *mesh) { unsigned size = unsigned(model.shapeMean.size()) * 3, // the number of floats in the mesh vector numCoeffs, i; float *const dst0 = &mesh->getVertices()->x, // begin *const dst1 = dst0 + size; // end float const *src; float *dst, c; memcpy(dst0, model.shapeMean.data(), size * sizeof(*dst0)); // Initialize if (identCoeffs) { for (i = 0, numCoeffs = unsigned(model.shapeEigenValues.size()), src = model.shapeModes.data()->vec; i < numCoeffs; ++i, ++identCoeffs) { if ((c = *identCoeffs) != 0.f) { for (dst = dst0; dst != dst1;) *dst++ += *src++ * c; } else { src += size; } } } for (i = 0, numCoeffs = unsigned(model.blendShapes.size()); i < numCoeffs; ++i, ++exprCoeffs) { if ((c = *exprCoeffs) != 0.f) { for (dst = dst0, src = model.blendShapes[i].shape.data()->vec; dst != dst1;) *dst++ += *src++ * c; } } mesh->computeVertexNormals(); return NVCV_SUCCESS; } OpenGLMeshRenderer::OpenGLMeshRenderer() { /*NvCV_Status err =*/ (void)initDispatch(&this->m_dispatch); } OpenGLMeshRenderer::~OpenGLMeshRenderer() { } NvCV_Status OpenGLMeshRenderer::name(const char **str) { static const char name[] = "OpenGL"; *str = name; return NVCV_SUCCESS; } NvCV_Status OpenGLMeshRenderer::info(const char **str) { static const char info[] = "OpenGL renderer using local illumination"; *str = info; return NVCV_SUCCESS; } NvCV_Status OpenGLMeshRenderer::create(MeshRenderer **han) { *han = new OpenGLMeshRenderer(); return NVCV_SUCCESS; } void OpenGLMeshRenderer::destroy(MeshRenderer* /*han*/) { } NvCV_Status OpenGLMeshRenderer::read(MeshRenderer *han, const char *modelFile) { OpenGLMeshRenderer *ren = static_cast(han); size_t z = strlen(modelFile); if (z < 5) return NVCV_ERR_FILE; if (!strcasecmp(".nvf", modelFile + z - 4)) { FaceIOErr ioErr = ReadNVFFaceModel(modelFile, &ren->_sfma); // TODO clear _sfma first if (kIOErrNone != ioErr) { printf("Error: \"%s\": %s\n", modelFile, FaceIOErrorStringFromCode(ioErr)); return NVCV_ERR_READ; } NvCV_Status nvErr; nvErr = MakeMesh(&ren->_sfma, &ren->_mesh); if (NVCV_SUCCESS != nvErr) return nvErr; std::string mtlFile; mtlFile.assign(modelFile, 0, strlen(modelFile) - 3); mtlFile += "mtl"; nvErr = ren->_ctx.m_mtlLib.read(mtlFile.c_str()); unsigned why = ren->_mesh.notRenderable(0); if (why) { printf("Mesh \"%s\" is not renderable: %s: %s\n", modelFile, ((why & GLMesh::NOT_TRIMESH) ? "not a TriMesh" : ""), ((why & GLMesh::COMPLEX_TOPOLOGY) ? "complex topology" : "") ); return NVCV_ERR_MISMATCH; } return NVCV_SUCCESS; } // else if (!strcasecmp(".obj", file + z - 4)) { read obj files } else { return NVCV_ERR_FILE; } } NvCV_Status OpenGLMeshRenderer::init(MeshRenderer *han, unsigned width, unsigned height, const char *windowName) { OpenGLMeshRenderer *ren = static_cast(han); static const GLSpectrum3f lightColor[LAMBERTIAN_NUM_LIGHTS] = { { 1.f, 1.f, 1.f }, { .8f, .1f, .1f } }; static const glm::vec4 lightLoc[LAMBERTIAN_NUM_LIGHTS] = { { 0, 0, +1000, 0}, { 100, -200, -500, 0 } }; NvCV_Status nvErr; nvErr = ren->_ctx.makeWindowContext(width, height, windowName); nvErr = ren->_ctx.init(); ren->_ctx.setClearColor(0.2f, 0.2f, 0.2f, 1.f); ren->_ctx.setLights(lightLoc, lightColor); ren->setFOV(0.f); // Default orthographic return NVCV_SUCCESS; } NvCV_Status OpenGLMeshRenderer::setFOV(float fov, float near_z, float far_z) { if (0 == _mesh.numVertices()) return NVCV_ERR_MODEL; GLMesh::BoundingBox bbox; _mesh.getBoundingBox(&bbox); _ctrRot = bbox.center(); _ctrRot.y = bbox.min().y; // Assume that assets are designed with Y-up. float vShift = (_mesh.numVertices() > 10000) ? 0.15f : 0.0f; // Heuristic to determine whether there is a neck _ctx.setViewOfBound(bbox, glm::vec3(0.f, 0.f, -1.f), glm::vec3(0.f, +1.f, 0.f), fov, .7f, +1, vShift, near_z, far_z); return NVCV_SUCCESS; } NvCV_Status OpenGLMeshRenderer::setCamera(MeshRenderer *han, const float locPt[3], const float lookVec[3], const float upVec[3], float vfov, float near_z, float far_z) { if (locPt || lookVec || upVec) { if (!locPt || !lookVec || !upVec || vfov <= 0.0f || (near_z == 0.0f && far_z == 0.0f)) return NVCV_ERR_PARAMETER; auto &ctx = static_cast(han)->_ctx; const float aspect = static_cast(ctx.m_width) / static_cast(ctx.m_height); ctx.m_P = glm::perspective(vfov, aspect, near_z, far_z); ctx.setViewMatrix(glm::lookAt(glm::make_vec3(locPt), glm::make_vec3(lookVec), glm::make_vec3(upVec))); return NVCV_SUCCESS; } // Default to camera based on bounding box if not enough input arguments are provided return static_cast(han)->setFOV(vfov, near_z, far_z); } NvCV_Status OpenGLMeshRenderer::render(MeshRenderer *han, const float exprs[53], const float qrot[4], const float* trans, NvCVImage *result) { OpenGLMeshRenderer *ren = static_cast(han); NvCV_Status nvErr; glm::mat4x4 M; glm::quat q; // Convert quaternion from {x,y,z,w} --> GLM's {w,x,y,z} if (NVCV_RGBA != result->pixelFormat) return NVCV_ERR_PIXELFORMAT; if (qrot) { q.x = qrot[0]; q.y = qrot[1]; q.z = qrot[2]; q.w = qrot[3]; } else { q.x = 0.0f; q.y = 0.0f; q.z = 0.0f; q.w = 1.0f; } M = glm::mat4_cast(q); if (trans) { M = glm::translate(glm::mat4x4(1.f), *((const glm::vec3 *)(trans))) * M; } else { M = glm::translate(glm::mat4x4(1.f), -ren->_ctrRot); M = glm::mat4_cast(q) * M; M = glm::translate(M, ren->_ctrRot); } nvErr = DeformModel(ren->_sfma.fm, nullptr, exprs, &ren->_mesh); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); nvErr = ren->_ctx.renderPolyMesh(ren->_mesh, M, NULL); glReadPixels(0, 0, result->width, result->height, GL_RGBA, GL_UNSIGNED_BYTE, result->pixels); GLenum glErr = glGetError(); if (glErr) return NVCV_ERR_OPENGL; // GL returns an image upside-down, but we can use the NvCVImage_FlipY in the caller to flip it with no overhead return NVCV_SUCCESS; } NvCV_Status OpenGLMeshRenderer::initDispatch(MeshRenderer::Dispatch *dispatch) { dispatch->name = &OpenGLMeshRenderer::name; dispatch->info = &OpenGLMeshRenderer::info; dispatch->create = &OpenGLMeshRenderer::create; dispatch->destroy = &OpenGLMeshRenderer::destroy; dispatch->read = &OpenGLMeshRenderer::read; dispatch->init = &OpenGLMeshRenderer::init; dispatch->setCamera = &OpenGLMeshRenderer::setCamera; dispatch->render = &OpenGLMeshRenderer::render; return NVCV_SUCCESS; } NvCV_Status OpenGLMeshRenderer::unload() { return NVCV_SUCCESS; }