// ---------------------------------------------------------------------------- // - Open3D: www.open3d.org - // ---------------------------------------------------------------------------- // The MIT License (MIT) // // Copyright (c) 2018 www.open3d.org // // 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 "Open3D/Visualization/Shader/TexturePhongShader.h" #include "Open3D/Geometry/Image.h" #include "Open3D/Geometry/PointCloud.h" #include "Open3D/Geometry/TriangleMesh.h" #include "Open3D/Visualization/Shader/Shader.h" #include "Open3D/Visualization/Utility/ColorMap.h" namespace open3d { namespace visualization { namespace glsl { bool TexturePhongShader::Compile() { if (CompileShaders(TexturePhongVertexShader, NULL, TexturePhongFragmentShader) == false) { PrintShaderWarning("Compiling shaders failed."); return false; } vertex_position_ = glGetAttribLocation(program_, "vertex_position"); vertex_normal_ = glGetAttribLocation(program_, "vertex_normal"); vertex_uv_ = glGetAttribLocation(program_, "vertex_uv"); MVP_ = glGetUniformLocation(program_, "MVP"); V_ = glGetUniformLocation(program_, "V"); M_ = glGetUniformLocation(program_, "M"); light_position_world_ = glGetUniformLocation(program_, "light_position_world_4"); light_color_ = glGetUniformLocation(program_, "light_color_4"); light_diffuse_power_ = glGetUniformLocation(program_, "light_diffuse_power_4"); light_specular_power_ = glGetUniformLocation(program_, "light_specular_power_4"); light_specular_shininess_ = glGetUniformLocation(program_, "light_specular_shininess_4"); light_ambient_ = glGetUniformLocation(program_, "light_ambient"); diffuse_texture_ = glGetUniformLocation(program_, "diffuse_texture"); return true; } void TexturePhongShader::Release() { UnbindGeometry(); ReleaseProgram(); } bool TexturePhongShader::BindGeometry(const geometry::Geometry &geometry, const RenderOption &option, const ViewControl &view) { // If there is already geometry, we first unbind it. // We use GL_STATIC_DRAW. When geometry changes, we clear buffers and // rebind the geometry. Note that this approach is slow. If the geometry is // changing per frame, consider implementing a new ShaderWrapper using // GL_STREAM_DRAW, and replace UnbindGeometry() with Buffer Object // Streaming mechanisms. UnbindGeometry(); // Prepare data to be passed to GPU std::vector points; std::vector normals; std::vector uvs; if (PrepareBinding(geometry, option, view, points, normals, uvs) == false) { PrintShaderWarning("Binding failed when preparing data."); return false; } // Create buffers and bind the geometry for (int mi = 0; mi < num_materials_; ++mi) { glGenBuffers(1, &vertex_position_buffers_[mi]); glBindBuffer(GL_ARRAY_BUFFER, vertex_position_buffers_[mi]); glBufferData(GL_ARRAY_BUFFER, draw_array_sizes_[mi] * sizeof(Eigen::Vector3f), points.data() + array_offsets_[mi], GL_STATIC_DRAW); glGenBuffers(1, &vertex_normal_buffers_[mi]); glBindBuffer(GL_ARRAY_BUFFER, vertex_normal_buffers_[mi]); glBufferData(GL_ARRAY_BUFFER, draw_array_sizes_[mi] * sizeof(Eigen::Vector3f), normals.data() + array_offsets_[mi], GL_STATIC_DRAW); glGenBuffers(1, &vertex_uv_buffers_[mi]); glBindBuffer(GL_ARRAY_BUFFER, vertex_uv_buffers_[mi]); glBufferData(GL_ARRAY_BUFFER, draw_array_sizes_[mi] * sizeof(Eigen::Vector2f), uvs.data() + array_offsets_[mi], GL_STATIC_DRAW); } bound_ = true; return true; } bool TexturePhongShader::RenderGeometry(const geometry::Geometry &geometry, const RenderOption &option, const ViewControl &view) { if (PrepareRendering(geometry, option, view) == false) { PrintShaderWarning("Rendering failed during preparation."); return false; } glUseProgram(program_); for (int mi = 0; mi < num_materials_; ++mi) { glUniformMatrix4fv(MVP_, 1, GL_FALSE, view.GetMVPMatrix().data()); glUniformMatrix4fv(V_, 1, GL_FALSE, view.GetViewMatrix().data()); glUniformMatrix4fv(M_, 1, GL_FALSE, view.GetModelMatrix().data()); glUniformMatrix4fv(light_position_world_, 1, GL_FALSE, light_position_world_data_.data()); glUniformMatrix4fv(light_color_, 1, GL_FALSE, light_color_data_.data()); glUniform4fv(light_diffuse_power_, 1, light_diffuse_power_data_.data()); glUniform4fv(light_specular_power_, 1, light_specular_power_data_.data()); glUniform4fv(light_specular_shininess_, 1, light_specular_shininess_data_.data()); glUniform4fv(light_ambient_, 1, light_ambient_data_.data()); glUniform1i(diffuse_texture_, 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, diffuse_texture_buffers_[mi]); glEnableVertexAttribArray(vertex_position_); glBindBuffer(GL_ARRAY_BUFFER, vertex_position_buffers_[mi]); glVertexAttribPointer(vertex_position_, 3, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(vertex_normal_); glBindBuffer(GL_ARRAY_BUFFER, vertex_normal_buffers_[mi]); glVertexAttribPointer(vertex_normal_, 3, GL_FLOAT, GL_FALSE, 0, NULL); glEnableVertexAttribArray(vertex_uv_); glBindBuffer(GL_ARRAY_BUFFER, vertex_uv_buffers_[mi]); glVertexAttribPointer(vertex_uv_, 2, GL_FLOAT, GL_FALSE, 0, NULL); glDrawArrays(draw_arrays_mode_, 0, draw_array_sizes_[mi]); glDisableVertexAttribArray(vertex_position_); glDisableVertexAttribArray(vertex_normal_); glDisableVertexAttribArray(vertex_uv_); glBindTexture(GL_TEXTURE_2D, 0); } return true; } void TexturePhongShader::UnbindGeometry() { if (bound_) { for (auto buf : vertex_position_buffers_) { glDeleteBuffers(1, &buf); } for (auto buf : vertex_normal_buffers_) { glDeleteBuffers(1, &buf); } for (auto buf : vertex_uv_buffers_) { glDeleteBuffers(1, &buf); } for (auto buf : diffuse_texture_buffers_) { glDeleteTextures(1, &buf); } vertex_position_buffers_.clear(); vertex_normal_buffers_.clear(); vertex_uv_buffers_.clear(); diffuse_texture_buffers_.clear(); draw_array_sizes_.clear(); array_offsets_.clear(); num_materials_ = 0; bound_ = false; } } void TexturePhongShader::SetLighting(const ViewControl &view, const RenderOption &option) { const auto &box = view.GetBoundingBox(); light_position_world_data_.setOnes(); light_color_data_.setOnes(); for (int i = 0; i < 4; i++) { light_position_world_data_.block<3, 1>(0, i) = box.GetCenter().cast() + (float)box.GetMaxExtent() * ((float)option.light_position_relative_[i](0) * view.GetRight() + (float)option.light_position_relative_[i](1) * view.GetUp() + (float)option.light_position_relative_[i](2) * view.GetFront()); light_color_data_.block<3, 1>(0, i) = option.light_color_[i].cast(); } if (option.light_on_) { light_diffuse_power_data_ = Eigen::Vector4d(option.light_diffuse_power_).cast(); light_specular_power_data_ = Eigen::Vector4d(option.light_specular_power_).cast(); light_specular_shininess_data_ = Eigen::Vector4d(option.light_specular_shininess_) .cast(); light_ambient_data_.block<3, 1>(0, 0) = option.light_ambient_color_.cast(); light_ambient_data_(3) = 1.0f; } else { light_diffuse_power_data_ = GLHelper::GLVector4f::Zero(); light_specular_power_data_ = GLHelper::GLVector4f::Zero(); light_specular_shininess_data_ = GLHelper::GLVector4f::Ones(); light_ambient_data_ = GLHelper::GLVector4f(1.0f, 1.0f, 1.0f, 1.0f); } } bool TexturePhongShaderForTriangleMesh::PrepareRendering( const geometry::Geometry &geometry, const RenderOption &option, const ViewControl &view) { if (geometry.GetGeometryType() != geometry::Geometry::GeometryType::TriangleMesh && geometry.GetGeometryType() != geometry::Geometry::GeometryType::HalfEdgeTriangleMesh) { PrintShaderWarning("Rendering type is not geometry::TriangleMesh."); return false; } if (option.mesh_show_back_face_) { glDisable(GL_CULL_FACE); } else { glEnable(GL_CULL_FACE); } glEnable(GL_DEPTH_TEST); glDepthFunc(GLenum(option.GetGLDepthFunc())); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if (option.mesh_show_wireframe_) { glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(1.0, 1.0); } else { glDisable(GL_POLYGON_OFFSET_FILL); } SetLighting(view, option); return true; } bool TexturePhongShaderForTriangleMesh::PrepareBinding( const geometry::Geometry &geometry, const RenderOption &option, const ViewControl &view, std::vector &points, std::vector &normals, std::vector &uvs) { if (geometry.GetGeometryType() != geometry::Geometry::GeometryType::TriangleMesh && geometry.GetGeometryType() != geometry::Geometry::GeometryType::HalfEdgeTriangleMesh) { PrintShaderWarning("Rendering type is not geometry::TriangleMesh."); return false; } const geometry::TriangleMesh &mesh = (const geometry::TriangleMesh &)geometry; if (mesh.HasTriangles() == false) { PrintShaderWarning("Binding failed with empty triangle mesh."); return false; } if (mesh.HasTriangleNormals() == false || mesh.HasVertexNormals() == false) { PrintShaderWarning("Binding failed because mesh has no normals."); PrintShaderWarning("Call ComputeVertexNormals() before binding."); return false; } std::vector> tmp_points; std::vector> tmp_normals; std::vector> tmp_uvs; num_materials_ = (int)mesh.textures_.size(); array_offsets_.resize(num_materials_); draw_array_sizes_.resize(num_materials_); vertex_position_buffers_.resize(num_materials_); vertex_normal_buffers_.resize(num_materials_); vertex_uv_buffers_.resize(num_materials_); diffuse_texture_buffers_.resize(num_materials_); tmp_points.resize(num_materials_); tmp_normals.resize(num_materials_); tmp_uvs.resize(num_materials_); for (size_t i = 0; i < mesh.triangles_.size(); i++) { const auto &triangle = mesh.triangles_[i]; int mi = mesh.triangle_material_ids_[i]; for (size_t j = 0; j < 3; j++) { size_t idx = i * 3 + j; size_t vi = triangle(j); tmp_points[mi].push_back(mesh.vertices_[vi].cast()); tmp_uvs[mi].push_back(mesh.triangle_uvs_[idx].cast()); if (option.mesh_shade_option_ == RenderOption::MeshShadeOption::FlatShade) { tmp_normals[mi].push_back( mesh.triangle_normals_[i].cast()); } else { tmp_normals[mi].push_back( mesh.vertex_normals_[vi].cast()); } } } // Bind textures for (int mi = 0; mi < num_materials_; ++mi) { glGenTextures(1, &diffuse_texture_buffers_[mi]); glBindTexture(GL_TEXTURE_2D, diffuse_texture_buffers_[mi]); GLenum format, type; auto it = GLHelper::texture_format_map_.find( mesh.textures_[mi].num_of_channels_); if (it == GLHelper::texture_format_map_.end()) { utility::LogWarning("Unknown texture format, abort!"); return false; } format = it->second; it = GLHelper::texture_type_map_.find( mesh.textures_[mi].bytes_per_channel_); if (it == GLHelper::texture_type_map_.end()) { utility::LogWarning("Unknown texture type, abort!"); return false; } type = it->second; glTexImage2D(GL_TEXTURE_2D, 0, format, mesh.textures_[mi].width_, mesh.textures_[mi].height_, 0, format, type, mesh.textures_[mi].data_.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } // Point seperations array_offsets_[0] = 0; draw_array_sizes_[0] = static_cast(tmp_points[0].size()); for (int mi = 1; mi < num_materials_; ++mi) { draw_array_sizes_[mi] = static_cast(tmp_points[mi].size()); array_offsets_[mi] = array_offsets_[mi - 1] + draw_array_sizes_[mi - 1]; } // Prepared chunk of points and uvs points.clear(); uvs.clear(); normals.clear(); for (int mi = 0; mi < num_materials_; ++mi) { points.insert(points.end(), tmp_points[mi].begin(), tmp_points[mi].end()); uvs.insert(uvs.end(), tmp_uvs[mi].begin(), tmp_uvs[mi].end()); normals.insert(normals.end(), tmp_normals[mi].begin(), tmp_normals[mi].end()); } draw_arrays_mode_ = GL_TRIANGLES; return true; } } // namespace glsl } // namespace visualization } // namespace open3d