├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── data ├── bunny │ ├── front_color.png │ ├── front_mask.png │ ├── front_vis_depth.png │ ├── front_vis_face_id.png │ └── front_vis_normal.png └── minimum_example │ ├── color.png │ ├── mask.png │ ├── vis_depth.png │ ├── vis_face_id.png │ └── vis_normal.png ├── examples.cc ├── include └── currender │ ├── rasterizer.h │ ├── raytracer.h │ └── renderer.h ├── minimum_example.cc ├── rebuild.bat ├── reconfigure.bat └── src ├── pixel_shader.h ├── rasterizer.cc ├── raytracer.cc ├── util_private.cc └── util_private.h /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | /win_build/ 3 | /bin/ 4 | /lib/ 5 | 6 | # Prerequisites 7 | *.d 8 | 9 | # Compiled Object files 10 | *.slo 11 | *.lo 12 | *.o 13 | *.obj 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Compiled Dynamic libraries 20 | *.so 21 | *.dylib 22 | *.dll 23 | 24 | # Fortran module files 25 | *.mod 26 | *.smod 27 | 28 | # Compiled Static libraries 29 | *.lai 30 | *.la 31 | *.a 32 | *.lib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/nanort"] 2 | path = third_party/nanort 3 | url = https://github.com/lighttransport/nanort.git 4 | [submodule "third_party/ugu"] 5 | path = third_party/ugu 6 | url = https://github.com/unclearness/ugu.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(PROJECT_NAME currender) 4 | project(${PROJECT_NAME} LANGUAGES CXX VERSION 0.0.1 DESCRIPTION "A CPU renderer for computer vision") 5 | 6 | set(CMAKE_VERBOSE_MAKEFILE TRUE) 7 | 8 | # .lib 9 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib) 10 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib) 11 | 12 | # .dll and .exe 13 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) 14 | 15 | if (WIN32) 16 | # option for Visual Studio 17 | # -EHsc (enable proper Exxeption Handling) needs to avoid C4530 18 | # -Wall is too noisy so that set -W4. 19 | # https://docs.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level?view=vs-2017 20 | # "However, for a new project, it may be best to use /W4 in all compilations; 21 | # this will ensure the fewest possible hard-to-find code defects." 22 | set(CMAKE_CXX_FLAGS "-std=c++14 -W4 -EHsc") 23 | 24 | else() 25 | # g++ option for *nix 26 | set(CMAKE_CXX_FLAGS "-std=c++14 -Wall -O2") 27 | 28 | endif() 29 | 30 | get_directory_property(hasParent PARENT_DIRECTORY) 31 | if(hasParent) 32 | message(STATUS "Has a parent scope.") 33 | else() 34 | message(STATUS "Doesn't have a parent scope.") 35 | endif() 36 | 37 | function(set_with_parent ARG_NAME ARG_PATH ARG_TEXT) 38 | set(${ARG_NAME} ${ARG_PATH} CACHE PATH ${ARG_TEXT} FORCE) 39 | if (hasParent) 40 | set(${ARG_NAME} ${ARG_PATH} PARENT_SCOPE) 41 | endif() 42 | endfunction(set_with_parent) 43 | 44 | set(Currender_LIBS) 45 | set(Currender_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR};${CMAKE_CURRENT_SOURCE_DIR}/include;${CMAKE_CURRENT_SOURCE_DIR}/src") 46 | 47 | # third_party directries 48 | if(NOT DEFINED NANORT_INSTALL_DIR) 49 | set_with_parent(NANORT_INSTALL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/nanort" "NanoRT installed directory") 50 | endif() 51 | message("NANORT_INSTALL_DIR: ${NANORT_INSTALL_DIR}") 52 | 53 | if(NOT DEFINED UGU_INSTALL_DIR) 54 | set_with_parent(UGU_INSTALL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/ugu" "UGU installed directory") 55 | endif() 56 | message("UGU_INSTALL_DIR: ${UGU_INSTALL_DIR}") 57 | 58 | add_subdirectory(${UGU_INSTALL_DIR}) 59 | 60 | # inherit definitions from ugu 61 | # todo: find better way... 62 | if(UGU_USE_STB) 63 | add_definitions(-DUGU_USE_STB) 64 | endif() 65 | if(UGU_USE_OPENCV) 66 | add_definitions(-DUGU_USE_OPENCV) 67 | # need to find again here otherwise linking fails. 68 | # ${OpenCV_LIBS} contains only names (no directory) of all modules 69 | # while full path of world module is linked in VS project... 70 | find_package(OpenCV REQUIRED) 71 | add_definitions(-DUGU_USE_OPENCV) 72 | set(Currender_LIBS ${Currender_LIBS} ${OpenCV_LIBS}) 73 | endif() 74 | if(UGU_USE_TINYOBJLOADER) 75 | add_definitions(-DUGU_USE_TINYOBJLOADER) 76 | endif() 77 | if(UGU_USE_LODEPNG) 78 | add_definitions(-DUGU_USE_LODEPNG) 79 | endif() 80 | if(UGU_USE_TINYCOLORMAP) 81 | add_definitions(-DUGU_USE_TINYCOLORMAP) 82 | endif() 83 | if(UGU_USE_OPENMP) 84 | add_definitions(-DUGU_USE_OPENMP) 85 | endif() 86 | 87 | # switch for optional libraries 88 | option(CURRENDER_USE_OPENMP "Use OpenMP to enable parallelization" ON) 89 | message("CURRENDER_USE_OPENMP: ${CURRENDER_USE_OPENMP}") 90 | if(CURRENDER_USE_OPENMP) 91 | add_definitions(-DCURRENDER_USE_OPENMP) 92 | endif() 93 | 94 | option(CURRENDER_USE_NANORT "Use NanoRT for Raytracer" ON) 95 | message("CURRENDER_USE_NANORT: ${CURRENDER_USE_NANORT}") 96 | if(CURRENDER_USE_NANORT) 97 | add_definitions(-DCURRENDER_USE_NANORT) 98 | set(Currender_INCLUDE_DIRS ${Currender_INCLUDE_DIRS} ${NANORT_INSTALL_DIR}) 99 | endif() 100 | 101 | # For OpenMP 102 | if(CURRENDER_USE_OPENMP) 103 | find_package(OpenMP REQUIRED) 104 | if(OpenMP_FOUND) 105 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") 106 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 107 | endif() 108 | endif() 109 | 110 | set(Currender_LIB ${PROJECT_NAME}) 111 | add_library(${Currender_LIB} 112 | STATIC 113 | include/currender/renderer.h 114 | include/currender/raytracer.h 115 | include/currender/rasterizer.h 116 | 117 | src/raytracer.cc 118 | src/rasterizer.cc 119 | src/pixel_shader.h 120 | src/util_private.h 121 | src/util_private.cc 122 | ) 123 | 124 | set(Currender_LIBS ${Currender_LIBS} ${Currender_LIB} ${Ugu_LIBS}) 125 | set(Currender_INCLUDE_DIRS ${Currender_INCLUDE_DIRS} ${Ugu_INCLUDE_DIRS}) 126 | 127 | set_with_parent(Currender_LIBS "${Currender_LIBS}" "Currender_LIBS") 128 | set_with_parent(Currender_INCLUDE_DIRS "${Currender_INCLUDE_DIRS}" "Currender_INCLUDE_DIRS") 129 | 130 | message("Currender_LIBS: ${Currender_LIBS}") 131 | message("Currender_INCLUDE_DIRS: ${Currender_INCLUDE_DIRS}") 132 | 133 | target_include_directories(${Currender_LIB} PUBLIC ${Currender_INCLUDE_DIRS}) 134 | set_target_properties(${Currender_LIB} PROPERTIES VERSION ${PROJECT_VERSION}) 135 | 136 | if (NOT hasParent) 137 | set(MINIMUM_EXAMPLE_EXE currender_minimum_example) 138 | add_executable(${MINIMUM_EXAMPLE_EXE} 139 | minimum_example.cc) 140 | target_include_directories(${MINIMUM_EXAMPLE_EXE} PRIVATE ${Currender_INCLUDE_DIRS}) 141 | target_link_libraries(${MINIMUM_EXAMPLE_EXE} 142 | ${Currender_LIBS} 143 | ) 144 | 145 | set(EXAMPLES_EXE currender_examples) 146 | add_executable(${EXAMPLES_EXE} 147 | examples.cc) 148 | target_include_directories(${EXAMPLES_EXE} PRIVATE ${Currender_INCLUDE_DIRS}) 149 | target_link_libraries(${EXAMPLES_EXE} 150 | ${Currender_LIBS} 151 | ) 152 | endif() 153 | 154 | if (WIN32) 155 | set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT ${EXAMPLES_EXE}) 156 | # suppress C2338 for eigen 157 | add_definitions(-D_ENABLE_EXTENDED_ALIGNED_STORAGE) 158 | endif() 159 | 160 | # make test data directory 161 | if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data) 162 | file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data) 163 | endif() 164 | if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data/bunny) 165 | file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/bunny) 166 | endif() 167 | if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data/minimum_example) 168 | file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/minimum_example) 169 | endif() 170 | 171 | # test data preparation 172 | if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data/bunny/bunny.obj) 173 | 174 | # download test data 175 | if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data/bunny.zip) 176 | file(DOWNLOAD http://www.kunzhou.net/tex-models/bunny.zip ${CMAKE_CURRENT_SOURCE_DIR}/data/bunny.zip) 177 | endif() 178 | 179 | # unzip test data 180 | if (NOT hasParent) 181 | add_custom_target( Currender_UNZip ALL) 182 | add_custom_command(TARGET Currender_UNZip PRE_BUILD 183 | COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/data/bunny.zip 184 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/bunny 185 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/data/bunny.zip 186 | COMMENT "Unpacking bunny.zip" 187 | VERBATIM) 188 | endif() 189 | 190 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, unclearness 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Currender: A CPU renderer for computer vision 2 | **Currender** is a CPU raytracing/rasterization based rendering library written in C++. 3 | With 3D triangular mesh and camera parameters, you can easily render color, depth, normal, mask and face id images. 4 | 5 | |color|depth| 6 | |---|---| 7 | |![](data/bunny/front_color.png)|![](data/bunny/front_vis_depth.png)| 8 | 9 | 10 | |normal|mask|face id| 11 | |---|---|---| 12 | |![](data/bunny/front_vis_normal.png)|![](data/bunny/front_mask.png)|![](data/bunny/front_vis_face_id.png)| 13 | 14 | Currender is primarily designed for people who are involved in computer vision. 15 | Pros and cons against popular OpenGL based rendering are listed below. 16 | ## Pros 17 | - **Simple API, set mesh, set camera and render.** 18 | - You do not waste time in complex OpenGL settings. 19 | - **Less dependency.** 20 | - Only you need is Eigen for minimal Rasterizer configration. 21 | - **OpenCV compatible** 22 | - Support cv::Mat_ for internal Image class (optional) 23 | - **Standard coordinate system in computer vision community** 24 | - Identical to OpenCV (right-handed, z:forward, y:down, x:right). You are not irritated by coordinate conversion for OpenGL. 25 | - **Intrinsic parameters (principal point and focal length in pixel-scale) with pinhole camera model** 26 | - Popular camera projection representation in computer vision. You are not annoyed with converting the intrinsics to perspective projection matrix for OpenGL. 27 | - **Rendering depth, normal, mask and face id image is enabled as default.** 28 | - Computer vision algorithms often process them besides color image. 29 | - **Fast for lower resolution.** 30 | - Enough speed with less than VGA (640 * 480). Such small image size is commonly used in computer vison algorithms. 31 | - **Rendered images are directly stored in RAM.** 32 | - Easy to pass them to other CPU based programs. 33 | - **Easily port to any platform.** 34 | - No hardware or OS specific code is included. 35 | 36 | ## Cons 37 | - Slow for higher resolution due to the nature of CPU processing. 38 | - Showing images on window is not supported. You should use external libraries for visualization. 39 | - Not desgined to render beautiful and realistic color images. Only simple diffuse shading is implemented. 40 | 41 | # Renderer 42 | You can choose **Raytracer** or **Rasterizer** as rendering algorithm. 43 | 44 | - **Raytracer** 45 | - Currently Raytracer is faster for rendering but it needs additional BVH construction time when you change mesh. Raytracer depends on NanoRT. 46 | 47 | - **Rasterizer** 48 | - Rasterizer is slower but more portable. The only third party library you need is Eigen. 49 | 50 | # Usage 51 | This is the main function of `minimum_example.cc` to show simple usage of API. 52 | ```C++ 53 | int main() { 54 | // make an inclined cube mesh with vertex color 55 | auto mesh = MakeExampleCube(); 56 | 57 | // initialize renderer enabling vertex color rendering and lambertian shading 58 | currender::RendererOption option; 59 | option.diffuse_color = currender::DiffuseColor::kVertex; 60 | option.diffuse_shading = currender::DiffuseShading::kLambertian; 61 | 62 | // select Rasterizer or Raytracer 63 | #ifdef USE_RASTERIZER 64 | std::unique_ptr renderer = 65 | std::make_unique(option); 66 | #else 67 | std::unique_ptr renderer = 68 | std::make_unique(option); 69 | #endif 70 | 71 | // set mesh 72 | renderer->set_mesh(mesh); 73 | 74 | // prepare mesh for rendering (e.g. make BVH) 75 | renderer->PrepareMesh(); 76 | 77 | // make PinholeCamera (perspective camera) at origin. 78 | // its image size is 160 * 120 and its y (vertical) FoV is 50 deg. 79 | int width = 160; 80 | int height = 120; 81 | float fov_y_deg = 50.0f; 82 | Eigen ::Vector2f principal_point, focal_length; 83 | CalcIntrinsics(width, height, fov_y_deg, &principal_point, &focal_length); 84 | auto camera = std::make_shared( 85 | width, height, Eigen::Affine3d::Identity(), principal_point, 86 | focal_length); 87 | 88 | // set camera 89 | renderer->set_camera(camera); 90 | 91 | // render images 92 | currender::Image3b color; 93 | currender::Image1f depth; 94 | currender::Image3f normal; 95 | currender::Image1b mask; 96 | currender::Image1i face_id; 97 | renderer->Render(&color, &depth, &normal, &mask, &face_id); 98 | 99 | // save images 100 | SaveImages(color, depth, normal, mask, face_id); 101 | 102 | return 0; 103 | } 104 | ``` 105 | 106 | `examples.cc` shows a varietiy of usage (Bunny image on the top of this document was rendered by `examples.cc`). 107 | 108 | # Use case 109 | Expected use cases are the following but not limited to 110 | - Embedded in computer vision algortihm with rendering. 111 | - Especially in the case that OpenGL is used for visualization, so you hesitate to use OpenGL for algorithm with rendering simultaneously. 112 | - Debugging of computer vision algortihm. 113 | - Data augumentation for machine learning. 114 | 115 | # Dependencies 116 | ## Mandatory 117 | - Eigen 118 | https://github.com/eigenteam/eigen-git-mirror 119 | - Math 120 | ## Optional 121 | - NanoRT 122 | https://github.com/lighttransport/nanort 123 | - Ray intersection acceralated by BVH for Raytracer 124 | - OpenCV 125 | - cv::Mat_ as Image class. Image I/O 126 | - stb 127 | https://github.com/nothings/stb 128 | - Image I/O 129 | - LodePNG 130 | https://github.com/lvandeve/lodepng 131 | - .png I/O particularly for 16bit writing that is not supported by stb 132 | - tinyobjloader 133 | https://github.com/syoyo/tinyobjloader 134 | - Load .obj 135 | - tinycolormap 136 | https://github.com/yuki-koyama/tinycolormap 137 | - Colorization of depth, face id, etc. 138 | - OpenMP 139 | (if supported by your compiler) 140 | - Multi-thread accelaration 141 | 142 | 143 | # Build 144 | - `git submodule update --init --recursive` 145 | - To pull dependencies registered as git submodule. 146 | - Use CMake with `CMakeLists.txt`. 147 | - `reconfigure.bat` and `rebuild.bat` are command line CMake utilities for Windows 10 and Visual Studio 2017. 148 | 149 | # Platforms 150 | Tested on 151 | - Windows 10 with Visual Studio 2017. 152 | - Ubuntu 18.04 LTS with gcc 153 | 154 | Porting to the other platforms (Android, Mac and iOS) is under planning. 155 | Minor modifitation of code and CMakeLists.txt would be required. 156 | 157 | # To do 158 | - Porting to other platforms. 159 | - Real-time rendering visualization sample with external library (maybe OpenGL). 160 | - Support point cloud rendering. 161 | - Replace NanoRT with own ray intersection. 162 | - Introduce ambient and specular. 163 | 164 | # Data 165 | Borrowed .obj from [Zhou, Kun, et al. "TextureMontage." ACM Transactions on Graphics (TOG) 24.3 (2005): 1148-1155.](http://www.kunzhou.net/tex-models.htm) for testing purposes. 166 | -------------------------------------------------------------------------------- /data/bunny/front_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/bunny/front_color.png -------------------------------------------------------------------------------- /data/bunny/front_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/bunny/front_mask.png -------------------------------------------------------------------------------- /data/bunny/front_vis_depth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/bunny/front_vis_depth.png -------------------------------------------------------------------------------- /data/bunny/front_vis_face_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/bunny/front_vis_face_id.png -------------------------------------------------------------------------------- /data/bunny/front_vis_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/bunny/front_vis_normal.png -------------------------------------------------------------------------------- /data/minimum_example/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/minimum_example/color.png -------------------------------------------------------------------------------- /data/minimum_example/mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/minimum_example/mask.png -------------------------------------------------------------------------------- /data/minimum_example/vis_depth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/minimum_example/vis_depth.png -------------------------------------------------------------------------------- /data/minimum_example/vis_face_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/minimum_example/vis_face_id.png -------------------------------------------------------------------------------- /data/minimum_example/vis_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unclearness/currender/e3397be6b9435e02f301b5685e09c1a208108e66/data/minimum_example/vis_normal.png -------------------------------------------------------------------------------- /examples.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "currender/rasterizer.h" 12 | #include "currender/raytracer.h" 13 | #include "ugu/util.h" 14 | 15 | // #define USE_RASTERIZER 16 | 17 | using currender::Camera; 18 | using currender::Depth2Gray; 19 | using currender::Depth2Mesh; 20 | using currender::Depth2PointCloud; 21 | using currender::FaceId2RandomColor; 22 | using currender::Image1b; 23 | using currender::Image1f; 24 | using currender::Image1i; 25 | using currender::Image1w; 26 | using currender::Image3b; 27 | using currender::Image3f; 28 | using currender::imwrite; 29 | using currender::Mesh; 30 | using currender::MeshStats; 31 | using currender::Normal2Color; 32 | using currender::PinholeCamera; 33 | using currender::Renderer; 34 | using currender::RendererOption; 35 | using currender::WriteFaceIdAsText; 36 | 37 | namespace { 38 | void Test(const std::string& out_dir, std::shared_ptr mesh, 39 | std::shared_ptr camera, const Renderer& renderer) { 40 | // images 41 | Image3b color; 42 | Image1f depth; 43 | Image1w depthw; 44 | Image3f normal; 45 | Image1b mask; 46 | Image1i face_id; 47 | Image1b vis_depth; 48 | Image3b vis_normal; 49 | Image3b vis_face_id; 50 | Mesh view_mesh, view_point_cloud; 51 | const float kMaxConnectZDiff = 100.0f; 52 | 53 | // for pose output by tum format 54 | std::vector poses; 55 | 56 | MeshStats stats = mesh->stats(); 57 | Eigen::Vector3f center = stats.center; 58 | Eigen::Vector3f eye; 59 | Eigen::Matrix4f c2w_mat; 60 | 61 | // translation offset is the largest edge length of bounding box * 1.5 62 | Eigen::Vector3f diff = stats.bb_max - stats.bb_min; 63 | float offset = std::max(diff[0], std::max(diff[1], diff[2])) * 1.5f; 64 | 65 | std::vector pose_list; 66 | std::vector name_list; 67 | 68 | // from front 69 | eye = center; 70 | eye[2] -= offset; 71 | currender::c2w(eye, center, Eigen::Vector3f(0, -1, 0), &c2w_mat); 72 | pose_list.push_back(Eigen::Affine3d(c2w_mat.cast())); 73 | name_list.push_back("front"); 74 | 75 | // from back 76 | eye = center; 77 | eye[2] += offset; 78 | currender::c2w(eye, center, Eigen::Vector3f(0, -1, 0), &c2w_mat); 79 | pose_list.push_back(Eigen::Affine3d(c2w_mat.cast())); 80 | name_list.push_back("back"); 81 | 82 | // from right 83 | eye = center; 84 | eye[0] += offset; 85 | currender::c2w(eye, center, Eigen::Vector3f(0, -1, 0), &c2w_mat); 86 | pose_list.push_back(Eigen::Affine3d(c2w_mat.cast())); 87 | name_list.push_back("right"); 88 | 89 | // from left 90 | eye = center; 91 | eye[0] -= offset; 92 | currender::c2w(eye, center, Eigen::Vector3f(0, -1, 0), &c2w_mat); 93 | pose_list.push_back(Eigen::Affine3d(c2w_mat.cast())); 94 | name_list.push_back("left"); 95 | 96 | // from top 97 | eye = center; 98 | eye[1] -= offset; 99 | currender::c2w(eye, center, Eigen::Vector3f(0, 0, 1), &c2w_mat); 100 | pose_list.push_back(Eigen::Affine3d(c2w_mat.cast())); 101 | name_list.push_back("top"); 102 | 103 | // from bottom 104 | eye = center; 105 | eye[1] += offset; 106 | currender::c2w(eye, center, Eigen::Vector3f(0, 0, -1), &c2w_mat); 107 | pose_list.push_back(Eigen::Affine3d(c2w_mat.cast())); 108 | name_list.push_back("bottom"); 109 | 110 | for (size_t i = 0; i < pose_list.size(); i++) { 111 | Eigen::Affine3d& c2w = pose_list[i]; 112 | std::string& prefix = name_list[i]; 113 | 114 | camera->set_c2w(c2w); 115 | renderer.Render(&color, &depth, &normal, &mask, &face_id); 116 | imwrite(out_dir + prefix + "_color.png", color); 117 | imwrite(out_dir + prefix + "_mask.png", mask); 118 | currender::ConvertTo(depth, &depthw); 119 | imwrite(out_dir + prefix + "_depth.png", depthw); 120 | Depth2Gray(depth, &vis_depth); 121 | imwrite(out_dir + prefix + "_vis_depth.png", vis_depth); 122 | Normal2Color(normal, &vis_normal); 123 | imwrite(out_dir + prefix + "_vis_normal.png", vis_normal); 124 | FaceId2RandomColor(face_id, &vis_face_id); 125 | imwrite(out_dir + prefix + "_vis_face_id.png", vis_face_id); 126 | WriteFaceIdAsText(face_id, out_dir + prefix + "_face_id.txt"); 127 | Depth2Mesh(depth, color, *camera, &view_mesh, kMaxConnectZDiff); 128 | Depth2PointCloud(depth, color, *camera, &view_point_cloud); 129 | view_point_cloud.WritePly(out_dir + prefix + "_mesh.ply"); 130 | view_mesh.WriteObj(out_dir, prefix + "_mesh"); 131 | poses.push_back(camera->c2w()); 132 | } 133 | 134 | currender::WriteTumFormat(poses, out_dir + "tumpose.txt"); 135 | } 136 | 137 | void AlignMesh(std::shared_ptr mesh) { 138 | // move center as origin 139 | MeshStats stats = mesh->stats(); 140 | mesh->Translate(-stats.center); 141 | 142 | // rotate 180 deg. around x_axis to align z:forward, y:down, x:right, 143 | Eigen::Matrix3f R = 144 | Eigen::AngleAxisf(currender::radians(180.0f), Eigen::Vector3f::UnitX()) 145 | .toRotationMatrix(); 146 | mesh->Rotate(R); 147 | 148 | // recover original translation 149 | mesh->Translate(stats.center); 150 | } 151 | } // namespace 152 | 153 | int main(int argc, char* argv[]) { 154 | (void)argc; 155 | (void)argv; 156 | 157 | // CMake downloads and unzip this data automatically 158 | // Please do manually if it got wrong 159 | // http://www.kunzhou.net/tex-models/bunny.zip 160 | /* 161 | * @article{Texturemontage05, 162 | * author = "Kun Zhou and Xi Wang and Yiying Tong and Mathieu Desbrun and 163 | * Baining Guo and Heung-Yeung Shum", title = "Texturemontage: Seamless 164 | * Texturing of Arbitrary Surfaces From Multiple Images", journal = "ACM 165 | * Transactions on Graphics", volume = "24", number = "3", year="2005", pages 166 | * = "1148-1155" 167 | */ 168 | std::string data_dir = "../data/bunny/"; 169 | std::string obj_path = data_dir + "bunny.obj"; 170 | 171 | std::ifstream ifs(obj_path); 172 | if (!ifs.is_open()) { 173 | printf("Please put %s\n", obj_path.c_str()); 174 | return -1; 175 | } 176 | 177 | // load mesh 178 | std::shared_ptr mesh = std::make_shared(); 179 | mesh->LoadObj(obj_path, data_dir); 180 | 181 | // original mesh with z:backward, y:up, x:right, like OpenGL 182 | // align z:forward, y:down, x:right 183 | AlignMesh(mesh); 184 | 185 | // initialize renderer with diffuse texture color and lambertian shading 186 | RendererOption option; 187 | option.diffuse_color = currender::DiffuseColor::kTexture; 188 | option.diffuse_shading = currender::DiffuseShading::kLambertian; 189 | #ifdef USE_RASTERIZER 190 | std::unique_ptr renderer = 191 | std::make_unique(option); 192 | #else 193 | std::unique_ptr renderer = 194 | std::make_unique(option); 195 | #endif 196 | 197 | // set mesh 198 | renderer->set_mesh(mesh); 199 | 200 | // prepare mesh for rendering (e.g. make BVH) 201 | renderer->PrepareMesh(); 202 | 203 | // Make PinholeCamera 204 | // borrow KinectV1 intrinsics of Freiburg 1 RGB 205 | // https://vision.in.tum.de/data/datasets/rgbd-dataset/file_formats 206 | float r = 0.5f; // scale to smaller size from VGA 207 | int width = static_cast(640 * r); 208 | int height = static_cast(480 * r); 209 | Eigen::Vector2f principal_point(318.6f * r, 255.3f * r); 210 | Eigen::Vector2f focal_length(517.3f * r, 516.5f * r); 211 | std::shared_ptr camera = std::make_shared( 212 | width, height, Eigen::Affine3d::Identity(), principal_point, 213 | focal_length); 214 | 215 | // set camera 216 | renderer->set_camera(camera); 217 | 218 | // test 219 | Test(data_dir, mesh, camera, *renderer); 220 | 221 | return 0; 222 | } 223 | -------------------------------------------------------------------------------- /include/currender/rasterizer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "currender/renderer.h" 11 | 12 | namespace currender { 13 | 14 | class Rasterizer : public Renderer { 15 | class Impl; 16 | std::unique_ptr pimpl_; 17 | 18 | public: 19 | Rasterizer(); 20 | ~Rasterizer() override; 21 | 22 | // Set option 23 | explicit Rasterizer(const RendererOption& option); 24 | void set_option(const RendererOption& option) override; 25 | 26 | // Set mesh 27 | void set_mesh(std::shared_ptr mesh) override; 28 | 29 | // Should call after set_mesh() and before Render() 30 | // Don't modify mesh outside after calling PrepareMesh() 31 | bool PrepareMesh() override; 32 | 33 | // Set camera 34 | void set_camera(std::shared_ptr camera) override; 35 | 36 | // Rendering all images 37 | // If you don't need some of them, pass nullptr 38 | bool Render(Image3b* color, Image1f* depth, Image3f* normal, Image1b* mask, 39 | Image1i* face_id) const override; 40 | 41 | // Rendering a image 42 | bool RenderColor(Image3b* color) const override; 43 | bool RenderDepth(Image1f* depth) const override; 44 | bool RenderNormal(Image3f* normal) const override; 45 | bool RenderMask(Image1b* mask) const override; 46 | bool RenderFaceId(Image1i* face_id) const override; 47 | 48 | // These Image1w* depth interfaces are prepared for widely used 16 bit 49 | // (unsigned short) and mm-scale depth image format 50 | bool RenderW(Image3b* color, Image1w* depth, Image3f* normal, Image1b* mask, 51 | Image1i* face_id) const override; 52 | bool RenderDepthW(Image1w* depth) const override; 53 | }; 54 | 55 | } // namespace currender 56 | -------------------------------------------------------------------------------- /include/currender/raytracer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #pragma once 7 | 8 | #ifdef CURRENDER_USE_NANORT 9 | 10 | #include 11 | 12 | #include "currender/renderer.h" 13 | 14 | namespace currender { 15 | 16 | class Raytracer : public Renderer { 17 | class Impl; 18 | std::unique_ptr pimpl_; 19 | 20 | public: 21 | Raytracer(); 22 | ~Raytracer() override; 23 | 24 | // Set option 25 | explicit Raytracer(const RendererOption& option); 26 | void set_option(const RendererOption& option) override; 27 | 28 | // Set mesh 29 | void set_mesh(std::shared_ptr mesh) override; 30 | 31 | // Should call after set_mesh() and before Render() 32 | // Don't modify mesh outside after calling PrepareMesh() 33 | bool PrepareMesh() override; 34 | 35 | // Set camera 36 | void set_camera(std::shared_ptr camera) override; 37 | 38 | // Rendering all images 39 | // If you don't need some of them, pass nullptr 40 | bool Render(Image3b* color, Image1f* depth, Image3f* normal, Image1b* mask, 41 | Image1i* face_id) const override; 42 | 43 | // Rendering a image 44 | bool RenderColor(Image3b* color) const override; 45 | bool RenderDepth(Image1f* depth) const override; 46 | bool RenderNormal(Image3f* normal) const override; 47 | bool RenderMask(Image1b* mask) const override; 48 | bool RenderFaceId(Image1i* face_id) const override; 49 | 50 | // These Image1w* depth interfaces are prepared for widely used 16 bit 51 | // (unsigned short) and mm-scale depth image format 52 | bool RenderW(Image3b* color, Image1w* depth, Image3f* normal, Image1b* mask, 53 | Image1i* face_id) const override; 54 | bool RenderDepthW(Image1w* depth) const override; 55 | }; 56 | 57 | } // namespace currender 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /include/currender/renderer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #include "ugu/camera.h" 12 | #include "ugu/mesh.h" 13 | 14 | namespace currender { 15 | 16 | using namespace ugu; 17 | 18 | // Diffuse color 19 | enum class DiffuseColor { 20 | kNone = 0, // Default white color 21 | kTexture = 1, // From diffuse uv texture 22 | kVertex = 2 // From vertex color 23 | }; 24 | 25 | // Normal used for shading 26 | // Also returned as output normal 27 | enum class ShadingNormal { 28 | kFace = 0, // Face normal 29 | kVertex = 1 // Vertex normal. Maybe average of face normals 30 | }; 31 | 32 | // Diffuse shading 33 | // Light ray same to viewing ray is used for shading 34 | enum class DiffuseShading { 35 | kNone = 0, // No shading 36 | kLambertian = 1, // Lambertian reflectance model 37 | kOrenNayar = 38 | 2 // Simplified Oren-Nayar reflectatnce model described in wikipedia 39 | // https://en.wikipedia.org/wiki/Oren%E2%80%93Nayar_reflectance_model 40 | }; 41 | 42 | // Interpolation method in texture uv space 43 | // Meaningful only if DiffuseColor::kTexture is specified otherwise ignored 44 | enum class ColorInterpolation { 45 | kNn = 0, // Nearest Neigbor 46 | kBilinear = 1 // Bilinear interpolation 47 | }; 48 | 49 | struct RendererOption { 50 | DiffuseColor diffuse_color{DiffuseColor::kNone}; 51 | ColorInterpolation interp{ColorInterpolation::kBilinear}; 52 | ShadingNormal shading_normal{ShadingNormal::kVertex}; 53 | DiffuseShading diffuse_shading{DiffuseShading::kNone}; 54 | 55 | float depth_scale{1.0f}; // Multiplied to output depth 56 | bool backface_culling{true}; // Back-face culling flag 57 | float oren_nayar_sigma{0.3f}; // Oren-Nayar's sigma 58 | 59 | RendererOption() {} 60 | ~RendererOption() {} 61 | void CopyTo(RendererOption* dst) const { 62 | dst->diffuse_color = diffuse_color; 63 | dst->depth_scale = depth_scale; 64 | dst->interp = interp; 65 | dst->shading_normal = shading_normal; 66 | dst->diffuse_shading = diffuse_shading; 67 | dst->backface_culling = backface_culling; 68 | } 69 | }; 70 | 71 | // interface (pure abstract base class with no state or defined methods) for 72 | // renderer 73 | class Renderer { 74 | public: 75 | virtual ~Renderer() {} 76 | 77 | // Set option 78 | virtual void set_option(const RendererOption& option) = 0; 79 | 80 | // Set mesh 81 | virtual void set_mesh(std::shared_ptr mesh) = 0; 82 | 83 | // Should call after set_mesh() and before Render() 84 | // Don't modify mesh outside after calling PrepareMesh() 85 | virtual bool PrepareMesh() = 0; 86 | 87 | // Set camera 88 | virtual void set_camera(std::shared_ptr camera) = 0; 89 | 90 | // Rendering all images 91 | // If you don't need some of them, pass nullptr 92 | virtual bool Render(Image3b* color, Image1f* depth, Image3f* normal, 93 | Image1b* mask, Image1i* face_id) const = 0; 94 | 95 | // Rendering a image 96 | virtual bool RenderColor(Image3b* color) const = 0; 97 | virtual bool RenderDepth(Image1f* depth) const = 0; 98 | virtual bool RenderNormal(Image3f* normal) const = 0; 99 | virtual bool RenderMask(Image1b* mask) const = 0; 100 | virtual bool RenderFaceId(Image1i* face_id) const = 0; 101 | 102 | // These Image1w* depth interfaces are prepared for widely used 16 bit 103 | // (unsigned short) and mm-scale depth image format 104 | virtual bool RenderW(Image3b* color, Image1w* depth, Image3f* normal, 105 | Image1b* mask, Image1i* face_id) const = 0; 106 | virtual bool RenderDepthW(Image1w* depth) const = 0; 107 | }; 108 | 109 | } // namespace currender 110 | -------------------------------------------------------------------------------- /minimum_example.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #include "currender/rasterizer.h" 7 | #include "currender/raytracer.h" 8 | 9 | // #define USE_RASTERIZER 10 | 11 | namespace { 12 | 13 | std::shared_ptr MakeExampleCube() { 14 | // cube mesh parameters 15 | float length = 200; // cube length 16 | Eigen::Vector3f center{0, 0, 600}; // cube center position 17 | 18 | // mesh rotation matrix from eular angle. rotate cube -30 deg. around y axis 19 | // and then rotate further 30 deg. around x axis 20 | Eigen::Matrix3f R = 21 | (Eigen::AngleAxisf(currender::radians(-30.0f), Eigen::Vector3f::UnitY()) * 22 | Eigen::AngleAxisf(currender::radians(30.0f), Eigen::Vector3f::UnitX()) * 23 | Eigen::AngleAxisf(currender::radians(0.0f), Eigen::Vector3f::UnitZ())) 24 | .toRotationMatrix(); 25 | // make cube mesh with the above paramters and default vertex color 26 | return currender::MakeCube(length, R, center); 27 | } 28 | 29 | void SaveImages(const currender::Image3b& color, 30 | const currender::Image1f& depth, 31 | const currender::Image3f& normal, 32 | const currender::Image1b& mask, 33 | const currender::Image1i& face_id) { 34 | // dir to save images 35 | std::string save_dir = "../data/minimum_example/"; 36 | 37 | // save color 38 | currender::imwrite(save_dir + "color.png", color); 39 | 40 | // save mask 41 | currender::imwrite(save_dir + "mask.png", mask); 42 | 43 | // convert depth to gray and save 44 | currender::Image1b vis_depth; 45 | currender::Depth2Gray(depth, &vis_depth); 46 | currender::imwrite(save_dir + "vis_depth.png", vis_depth); 47 | 48 | // convert normal to color and save 49 | currender::Image3b vis_normal; 50 | currender::Normal2Color(normal, &vis_normal); 51 | currender::imwrite(save_dir + "vis_normal.png", vis_normal); 52 | 53 | // convert face id to color and save 54 | currender::Image3b vis_face_id; 55 | currender::FaceId2RandomColor(face_id, &vis_face_id); 56 | currender::imwrite(save_dir + "vis_face_id.png", vis_face_id); 57 | 58 | printf("images are saved in %s\n", save_dir.c_str()); 59 | } 60 | 61 | void CalcIntrinsics(int width, int height, float fov_y_deg, 62 | Eigen ::Vector2f* principal_point, 63 | Eigen::Vector2f* focal_length) { 64 | (*principal_point)[0] = width * 0.5f - 0.5f; 65 | (*principal_point)[1] = height * 0.5f - 0.5f; 66 | 67 | (*focal_length)[1] = 68 | height * 0.5f / 69 | static_cast(std::tan(currender::radians(fov_y_deg) * 0.5)); 70 | (*focal_length)[0] = (*focal_length)[1]; 71 | } 72 | 73 | } // namespace 74 | 75 | int main() { 76 | // make an inclined cube mesh with vertex color 77 | auto mesh = MakeExampleCube(); 78 | 79 | // initialize renderer enabling vertex color rendering and lambertian shading 80 | currender::RendererOption option; 81 | option.diffuse_color = currender::DiffuseColor::kVertex; 82 | option.diffuse_shading = currender::DiffuseShading::kLambertian; 83 | 84 | // select Rasterizer or Raytracer 85 | #ifdef USE_RASTERIZER 86 | std::unique_ptr renderer = 87 | std::make_unique(option); 88 | #else 89 | std::unique_ptr renderer = 90 | std::make_unique(option); 91 | #endif 92 | 93 | // set mesh 94 | renderer->set_mesh(mesh); 95 | 96 | // prepare mesh for rendering (e.g. make BVH) 97 | renderer->PrepareMesh(); 98 | 99 | // make PinholeCamera (perspective camera) at origin. 100 | // its image size is 160 * 120 and its y (vertical) FoV is 50 deg. 101 | int width = 160; 102 | int height = 120; 103 | float fov_y_deg = 50.0f; 104 | Eigen ::Vector2f principal_point, focal_length; 105 | CalcIntrinsics(width, height, fov_y_deg, &principal_point, &focal_length); 106 | auto camera = std::make_shared( 107 | width, height, Eigen::Affine3d::Identity(), principal_point, 108 | focal_length); 109 | 110 | // set camera 111 | renderer->set_camera(camera); 112 | 113 | // render images 114 | currender::Image3b color; 115 | currender::Image1f depth; 116 | currender::Image3f normal; 117 | currender::Image1b mask; 118 | currender::Image1i face_id; 119 | renderer->Render(&color, &depth, &normal, &mask, &face_id); 120 | 121 | // save images 122 | SaveImages(color, depth, normal, mask, face_id); 123 | 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /rebuild.bat: -------------------------------------------------------------------------------- 1 | cmake --build win_build -------------------------------------------------------------------------------- /reconfigure.bat: -------------------------------------------------------------------------------- 1 | rd /s /q win_build 2 | md win_build 3 | cd win_build 4 | cmake -G "Visual Studio 15 2017 Win64" .. 5 | pause 6 | -------------------------------------------------------------------------------- /src/pixel_shader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "currender/renderer.h" 13 | 14 | #include "ugu/common.h" 15 | #include "ugu/image.h" 16 | #include "ugu/mesh.h" 17 | 18 | namespace currender { 19 | 20 | struct OrenNayarParam { 21 | public: 22 | float sigma{0.0f}; 23 | float A{0.0f}; 24 | float B{0.0f}; 25 | OrenNayarParam(); 26 | explicit OrenNayarParam(float sigma); 27 | ~OrenNayarParam(); 28 | }; 29 | 30 | struct PixelShaderInput { 31 | public: 32 | PixelShaderInput() = delete; 33 | Image3b* color{nullptr}; 34 | int x; 35 | int y; 36 | float u; 37 | float v; 38 | uint32_t face_index; 39 | const Eigen::Vector3f* ray_w{nullptr}; 40 | const Eigen::Vector3f* light_dir{nullptr}; 41 | const Eigen::Vector3f* shading_normal{nullptr}; 42 | const OrenNayarParam* oren_nayar_param{nullptr}; 43 | std::shared_ptr mesh{nullptr}; 44 | 45 | PixelShaderInput(Image3b* color, int x, int y, float u, float v, 46 | uint32_t face_index, const Eigen::Vector3f* ray_w, 47 | const Eigen::Vector3f* light_dir, 48 | const Eigen::Vector3f* shading_normal, 49 | const OrenNayarParam* oren_nayar_param, 50 | std::shared_ptr mesh); 51 | ~PixelShaderInput(); 52 | }; 53 | 54 | class DiffuseColorizer { 55 | public: 56 | virtual ~DiffuseColorizer() {} 57 | virtual void Process(const PixelShaderInput& input) const = 0; 58 | }; 59 | 60 | class DiffuseDefaultColorizer : public DiffuseColorizer { 61 | public: 62 | DiffuseDefaultColorizer(); 63 | ~DiffuseDefaultColorizer(); 64 | void Process(const PixelShaderInput& input) const override; 65 | }; 66 | 67 | class DiffuseVertexColorColorizer : public DiffuseColorizer { 68 | public: 69 | DiffuseVertexColorColorizer(); 70 | ~DiffuseVertexColorColorizer(); 71 | void Process(const PixelShaderInput& input) const override; 72 | }; 73 | 74 | class DiffuseTextureNnColorizer : public DiffuseColorizer { 75 | public: 76 | DiffuseTextureNnColorizer(); 77 | ~DiffuseTextureNnColorizer(); 78 | void Process(const PixelShaderInput& input) const override; 79 | }; 80 | 81 | class DiffuseTextureBilinearColorizer : public DiffuseColorizer { 82 | public: 83 | DiffuseTextureBilinearColorizer(); 84 | ~DiffuseTextureBilinearColorizer(); 85 | void Process(const PixelShaderInput& input) const override; 86 | }; 87 | 88 | class DiffuseShader { 89 | public: 90 | virtual ~DiffuseShader() {} 91 | virtual void Process(const PixelShaderInput& input) const = 0; 92 | }; 93 | 94 | class DiffuseDefaultShader : public DiffuseShader { 95 | public: 96 | DiffuseDefaultShader(); 97 | ~DiffuseDefaultShader(); 98 | void Process(const PixelShaderInput& input) const override; 99 | }; 100 | 101 | class DiffuseLambertianShader : public DiffuseShader { 102 | public: 103 | DiffuseLambertianShader(); 104 | ~DiffuseLambertianShader(); 105 | void Process(const PixelShaderInput& input) const override; 106 | }; 107 | 108 | class DiffuseOrenNayarShader : public DiffuseShader { 109 | public: 110 | DiffuseOrenNayarShader(); 111 | ~DiffuseOrenNayarShader(); 112 | void Process(const PixelShaderInput& input) const override; 113 | }; 114 | 115 | class PixelShader { 116 | std::unique_ptr diffuse_colorizer_{nullptr}; 117 | std::unique_ptr diffuse_shader_{nullptr}; 118 | PixelShader(std::unique_ptr&& diffuse_colorizer, 119 | std::unique_ptr&& diffuse_shader); 120 | 121 | public: 122 | PixelShader(const PixelShader&) = delete; 123 | PixelShader& operator=(const PixelShader&) = delete; 124 | PixelShader(PixelShader&&) = delete; 125 | PixelShader& operator=(PixelShader&&) = delete; 126 | friend class PixelShaderFactory; 127 | PixelShader(); 128 | ~PixelShader(); 129 | void Process(const PixelShaderInput& input) const; 130 | }; 131 | 132 | class PixelShaderFactory { 133 | PixelShaderFactory(); 134 | ~PixelShaderFactory(); 135 | 136 | public: 137 | static std::unique_ptr Create(DiffuseColor diffuse_color, 138 | ColorInterpolation interp, 139 | DiffuseShading diffuse_shading); 140 | }; 141 | 142 | inline OrenNayarParam::OrenNayarParam() {} 143 | inline OrenNayarParam::OrenNayarParam(float sigma) : sigma(sigma) { 144 | assert(0 <= sigma); 145 | float sigma_2 = sigma * sigma; 146 | A = 1.0f - (0.5f * sigma_2 / (sigma_2 + 0.33f)); 147 | B = 0.45f * sigma_2 / (sigma_2 + 0.09f); 148 | } 149 | inline OrenNayarParam::~OrenNayarParam() {} 150 | 151 | inline PixelShaderInput::~PixelShaderInput() {} 152 | inline PixelShaderInput::PixelShaderInput( 153 | Image3b* color, int x, int y, float u, float v, uint32_t face_index, 154 | const Eigen::Vector3f* ray_w, const Eigen::Vector3f* light_dir, 155 | const Eigen::Vector3f* shading_normal, 156 | const OrenNayarParam* oren_nayar_param, std::shared_ptr mesh) 157 | : color(color), 158 | x(x), 159 | y(y), 160 | u(u), 161 | v(v), 162 | face_index(face_index), 163 | ray_w(ray_w), 164 | light_dir(light_dir), 165 | shading_normal(shading_normal), 166 | oren_nayar_param(oren_nayar_param), 167 | mesh(mesh) {} 168 | 169 | inline PixelShaderFactory::PixelShaderFactory() {} 170 | 171 | inline PixelShaderFactory::~PixelShaderFactory() {} 172 | 173 | inline std::unique_ptr PixelShaderFactory::Create( 174 | DiffuseColor diffuse_color, ColorInterpolation interp, 175 | DiffuseShading diffuse_shading) { 176 | std::unique_ptr colorizer; 177 | std::unique_ptr shader; 178 | 179 | if (diffuse_color == DiffuseColor::kVertex) { 180 | colorizer.reset(new DiffuseVertexColorColorizer); 181 | } else if (diffuse_color == DiffuseColor::kTexture) { 182 | if (interp == ColorInterpolation::kNn) { 183 | colorizer.reset(new DiffuseTextureNnColorizer); 184 | } else if (interp == ColorInterpolation::kBilinear) { 185 | colorizer.reset(new DiffuseTextureBilinearColorizer); 186 | } 187 | } else if (diffuse_color == DiffuseColor::kNone) { 188 | colorizer.reset(new DiffuseDefaultColorizer); 189 | } 190 | assert(colorizer); 191 | 192 | if (diffuse_shading == DiffuseShading::kNone) { 193 | shader.reset(new DiffuseDefaultShader); 194 | } else if (diffuse_shading == DiffuseShading::kLambertian) { 195 | shader.reset(new DiffuseLambertianShader); 196 | } else if (diffuse_shading == DiffuseShading::kOrenNayar) { 197 | shader.reset(new DiffuseOrenNayarShader); 198 | } 199 | assert(shader); 200 | 201 | return std::unique_ptr( 202 | new PixelShader(std::move(colorizer), std::move(shader))); 203 | } 204 | 205 | inline PixelShader::PixelShader() {} 206 | inline PixelShader::~PixelShader() {} 207 | 208 | inline PixelShader::PixelShader( 209 | std::unique_ptr&& diffuse_colorizer, 210 | std::unique_ptr&& diffuse_shader) { 211 | diffuse_colorizer_ = std::move(diffuse_colorizer); 212 | diffuse_shader_ = std::move(diffuse_shader); 213 | } 214 | 215 | inline void PixelShader::Process(const PixelShaderInput& input) const { 216 | diffuse_colorizer_->Process(input); 217 | diffuse_shader_->Process(input); 218 | } 219 | 220 | inline DiffuseDefaultColorizer::DiffuseDefaultColorizer() {} 221 | inline DiffuseDefaultColorizer::~DiffuseDefaultColorizer() {} 222 | inline void DiffuseDefaultColorizer::Process( 223 | const PixelShaderInput& input) const { 224 | Image3b* color = input.color; 225 | int x = input.x; 226 | int y = input.y; 227 | 228 | std::memset(&color->data[color->cols * 3 * y + x * 3], 255, 229 | sizeof(unsigned char) * 3); 230 | } 231 | 232 | inline DiffuseVertexColorColorizer::DiffuseVertexColorColorizer() {} 233 | inline DiffuseVertexColorColorizer::~DiffuseVertexColorColorizer() {} 234 | inline void DiffuseVertexColorColorizer::Process( 235 | const PixelShaderInput& input) const { 236 | Image3b* color = input.color; 237 | int x = input.x; 238 | int y = input.y; 239 | float u = input.u; 240 | float v = input.v; 241 | uint32_t face_index = input.face_index; 242 | std::shared_ptr mesh = input.mesh; 243 | 244 | const auto& vertex_colors = mesh->vertex_colors(); 245 | const auto& faces = mesh->vertex_indices(); 246 | Eigen::Vector3f interp_color; 247 | // barycentric interpolation of vertex color 248 | interp_color = (1.0f - u - v) * vertex_colors[faces[face_index][0]] + 249 | u * vertex_colors[faces[face_index][1]] + 250 | v * vertex_colors[faces[face_index][2]]; 251 | 252 | Vec3b& c = color->at(y, x); 253 | for (int k = 0; k < 3; k++) { 254 | c[k] = static_cast(interp_color[k]); 255 | } 256 | } 257 | 258 | inline DiffuseTextureNnColorizer::DiffuseTextureNnColorizer() {} 259 | inline DiffuseTextureNnColorizer::~DiffuseTextureNnColorizer() {} 260 | inline void DiffuseTextureNnColorizer::Process( 261 | const PixelShaderInput& input) const { 262 | Image3b* color = input.color; 263 | int x = input.x; 264 | int y = input.y; 265 | float u = input.u; 266 | float v = input.v; 267 | uint32_t face_index = input.face_index; 268 | std::shared_ptr mesh = input.mesh; 269 | 270 | const auto& uv = mesh->uv(); 271 | const auto& uv_indices = mesh->uv_indices(); 272 | int material_index = mesh->material_ids()[face_index]; 273 | const auto& diffuse_texture = mesh->materials()[material_index].diffuse_tex; 274 | 275 | Eigen::Vector3f interp_color; 276 | // barycentric interpolation of uv 277 | Eigen::Vector2f interp_uv = (1.0f - u - v) * uv[uv_indices[face_index][0]] + 278 | u * uv[uv_indices[face_index][1]] + 279 | v * uv[uv_indices[face_index][2]]; 280 | float f_tex_pos[2]; 281 | f_tex_pos[0] = interp_uv[0] * (diffuse_texture.cols - 1); 282 | f_tex_pos[1] = (1.0f - interp_uv[1]) * (diffuse_texture.rows - 1); 283 | 284 | int tex_pos[2] = {0, 0}; 285 | // get nearest integer index by round 286 | tex_pos[0] = static_cast(std::round(f_tex_pos[0])); 287 | tex_pos[1] = static_cast(std::round(f_tex_pos[1])); 288 | 289 | const Vec3b& dt = diffuse_texture.at(tex_pos[1], tex_pos[0]); 290 | for (int k = 0; k < 3; k++) { 291 | interp_color[k] = dt[k]; 292 | } 293 | 294 | Vec3b& c = color->at(y, x); 295 | for (int k = 0; k < 3; k++) { 296 | c[k] = static_cast(interp_color[k]); 297 | } 298 | } 299 | 300 | inline DiffuseTextureBilinearColorizer::DiffuseTextureBilinearColorizer() {} 301 | inline DiffuseTextureBilinearColorizer::~DiffuseTextureBilinearColorizer() {} 302 | inline void DiffuseTextureBilinearColorizer::Process( 303 | const PixelShaderInput& input) const { 304 | Image3b* color = input.color; 305 | int x = input.x; 306 | int y = input.y; 307 | float u = input.u; 308 | float v = input.v; 309 | uint32_t face_index = input.face_index; 310 | std::shared_ptr mesh = input.mesh; 311 | 312 | const auto& uv = mesh->uv(); 313 | const auto& uv_indices = mesh->uv_indices(); 314 | int material_index = mesh->material_ids()[face_index]; 315 | const auto& diffuse_texture = mesh->materials()[material_index].diffuse_tex; 316 | 317 | Eigen::Vector3f interp_color; 318 | 319 | // barycentric interpolation of uv 320 | Eigen::Vector2f interp_uv = (1.0f - u - v) * uv[uv_indices[face_index][0]] + 321 | u * uv[uv_indices[face_index][1]] + 322 | v * uv[uv_indices[face_index][2]]; 323 | float f_tex_pos[2]; 324 | f_tex_pos[0] = interp_uv[0] * (diffuse_texture.cols - 1); 325 | f_tex_pos[1] = (1.0f - interp_uv[1]) * (diffuse_texture.rows - 1); 326 | 327 | int tex_pos_min[2] = {0, 0}; 328 | int tex_pos_max[2] = {0, 0}; 329 | tex_pos_min[0] = static_cast(std::floor(f_tex_pos[0])); 330 | tex_pos_min[1] = static_cast(std::floor(f_tex_pos[1])); 331 | tex_pos_max[0] = tex_pos_min[0] + 1; 332 | tex_pos_max[1] = tex_pos_min[1] + 1; 333 | 334 | float local_u = f_tex_pos[0] - tex_pos_min[0]; 335 | float local_v = f_tex_pos[1] - tex_pos_min[1]; 336 | 337 | const Vec3b& dt_minmin = 338 | diffuse_texture.at(tex_pos_min[1], tex_pos_min[0]); 339 | const Vec3b& dt_maxmin = 340 | diffuse_texture.at(tex_pos_min[1], tex_pos_max[0]); 341 | const Vec3b& dt_minmax = 342 | diffuse_texture.at(tex_pos_max[1], tex_pos_min[0]); 343 | const Vec3b& dt_maxmax = 344 | diffuse_texture.at(tex_pos_max[1], tex_pos_max[0]); 345 | for (int k = 0; k < 3; k++) { 346 | // bilinear interpolation of pixel color 347 | interp_color[k] = (1.0f - local_u) * (1.0f - local_v) * dt_minmin[k] + 348 | local_u * (1.0f - local_v) * dt_maxmin[k] + 349 | (1.0f - local_u) * local_v * dt_minmax[k] + 350 | local_u * local_v * dt_maxmax[k]; 351 | 352 | assert(0.0f <= interp_color[k] && interp_color[k] <= 255.0f); 353 | } 354 | 355 | Vec3b& c = color->at(y, x); 356 | for (int k = 0; k < 3; k++) { 357 | c[k] = static_cast(interp_color[k]); 358 | } 359 | } 360 | 361 | inline DiffuseDefaultShader::DiffuseDefaultShader() {} 362 | inline DiffuseDefaultShader::~DiffuseDefaultShader() {} 363 | inline void DiffuseDefaultShader::Process(const PixelShaderInput& input) const { 364 | // do nothing. 365 | (void)input; 366 | } 367 | 368 | inline DiffuseLambertianShader::DiffuseLambertianShader() {} 369 | inline DiffuseLambertianShader::~DiffuseLambertianShader() {} 370 | inline void DiffuseLambertianShader::Process( 371 | const PixelShaderInput& input) const { 372 | Image3b* color = input.color; 373 | int x = input.x; 374 | int y = input.y; 375 | 376 | // dot product of normal and inverse light direction 377 | float coeff = -input.light_dir->dot(*input.shading_normal); 378 | 379 | // if negative (may happen at back-face or occluding boundary), bound to 0 380 | if (coeff < 0.0f) { 381 | coeff = 0.0f; 382 | } 383 | 384 | Vec3b& c = color->at(y, x); 385 | for (int k = 0; k < 3; k++) { 386 | c[k] = static_cast(coeff * c[k]); 387 | } 388 | } 389 | 390 | inline DiffuseOrenNayarShader::DiffuseOrenNayarShader() {} 391 | inline DiffuseOrenNayarShader::~DiffuseOrenNayarShader() {} 392 | inline void DiffuseOrenNayarShader::Process( 393 | const PixelShaderInput& input) const { 394 | // angle against normal 395 | float dot_light = -input.light_dir->dot(*input.shading_normal); 396 | float theta_i = std::acos(dot_light); 397 | float dot_ray = -input.ray_w->dot(*input.shading_normal); 398 | float theta_r = std::acos(dot_ray); 399 | 400 | // angle against binormal (perpendicular to normal) 401 | Eigen::Vector3f binormal_light = 402 | -*input.shading_normal * dot_light - *input.light_dir; 403 | Eigen::Vector3f binormal_ray = 404 | -*input.shading_normal * dot_light - *input.ray_w; 405 | float phi_diff_cos = std::max(0.0f, binormal_light.dot(binormal_ray)); 406 | 407 | float alpha = std::max(theta_i, theta_r); 408 | float beta = std::min(theta_i, theta_r); 409 | 410 | float A = input.oren_nayar_param->A; 411 | float B = input.oren_nayar_param->B; 412 | float coeff = std::max(0.0f, dot_light) * 413 | (A + (B * phi_diff_cos * std::sin(alpha) * std::tan(beta))); 414 | 415 | Image3b* color = input.color; 416 | int x = input.x; 417 | int y = input.y; 418 | Vec3b& c = color->at(y, x); 419 | for (int k = 0; k < 3; k++) { 420 | c[k] = static_cast(coeff * c[k]); 421 | } 422 | } 423 | 424 | } // namespace currender 425 | -------------------------------------------------------------------------------- /src/rasterizer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #include "currender/rasterizer.h" 7 | 8 | #include 9 | 10 | #include "src/pixel_shader.h" 11 | #include "src/util_private.h" 12 | 13 | #include "ugu/timer.h" 14 | 15 | namespace { 16 | 17 | template 18 | void Argsort(const std::vector& data, std::vector* indices) { 19 | indices->resize(data.size()); 20 | std::iota(indices->begin(), indices->end(), 0); 21 | std::sort(indices->begin(), indices->end(), 22 | [&data](size_t i1, size_t i2) { return data[i1] < data[i2]; }); 23 | } 24 | 25 | inline float EdgeFunction(const Eigen::Vector3f& a, const Eigen::Vector3f& b, 26 | const Eigen::Vector3f& c) { 27 | return (c[0] - a[0]) * (b[1] - a[1]) - (c[1] - a[1]) * (b[0] - a[0]); 28 | } 29 | 30 | } // namespace 31 | 32 | namespace currender { 33 | 34 | // Rasterizer::Impl implementation 35 | class Rasterizer::Impl { 36 | bool mesh_initialized_{false}; 37 | std::shared_ptr camera_{nullptr}; 38 | std::shared_ptr mesh_{nullptr}; 39 | RendererOption option_; 40 | 41 | public: 42 | Impl(); 43 | ~Impl(); 44 | 45 | explicit Impl(const RendererOption& option); 46 | void set_option(const RendererOption& option); 47 | 48 | void set_mesh(std::shared_ptr mesh); 49 | 50 | bool PrepareMesh(); 51 | 52 | void set_camera(std::shared_ptr camera); 53 | 54 | bool Render(Image3b* color, Image1f* depth, Image3f* normal, Image1b* mask, 55 | Image1i* face_id) const; 56 | 57 | bool RenderColor(Image3b* color) const; 58 | bool RenderDepth(Image1f* depth) const; 59 | bool RenderNormal(Image3f* normal) const; 60 | bool RenderMask(Image1b* mask) const; 61 | bool RenderFaceId(Image1i* face_id) const; 62 | 63 | bool RenderW(Image3b* color, Image1w* depth, Image3f* normal, Image1b* mask, 64 | Image1i* face_id) const; 65 | bool RenderDepthW(Image1w* depth) const; 66 | }; 67 | 68 | Rasterizer::Impl::Impl() {} 69 | Rasterizer::Impl::~Impl() {} 70 | 71 | Rasterizer::Impl::Impl(const RendererOption& option) { set_option(option); } 72 | 73 | void Rasterizer::Impl::set_option(const RendererOption& option) { 74 | option.CopyTo(&option_); 75 | } 76 | 77 | void Rasterizer::Impl::set_mesh(std::shared_ptr mesh) { 78 | mesh_initialized_ = false; 79 | mesh_ = mesh; 80 | 81 | if (mesh_->face_normals().empty()) { 82 | LOGW("face normal is empty. culling and shading may not work\n"); 83 | } 84 | 85 | if (mesh_->normals().empty()) { 86 | LOGW("vertex normal is empty. shading may not work\n"); 87 | } 88 | } 89 | 90 | bool Rasterizer::Impl::PrepareMesh() { 91 | if (mesh_ == nullptr) { 92 | LOGE("mesh has not been set\n"); 93 | return false; 94 | } 95 | 96 | mesh_initialized_ = true; 97 | 98 | return true; 99 | } 100 | 101 | void Rasterizer::Impl::set_camera(std::shared_ptr camera) { 102 | camera_ = camera; 103 | } 104 | 105 | bool Rasterizer::Impl::Render(Image3b* color, Image1f* depth, Image3f* normal, 106 | Image1b* mask, Image1i* face_id) const { 107 | if (!ValidateAndInitBeforeRender(mesh_initialized_, camera_, mesh_, option_, 108 | color, depth, normal, mask, face_id)) { 109 | return false; 110 | } 111 | 112 | // make pixel shader 113 | std::unique_ptr pixel_shader = PixelShaderFactory::Create( 114 | option_.diffuse_color, option_.interp, option_.diffuse_shading); 115 | 116 | OrenNayarParam oren_nayar_param(option_.oren_nayar_sigma); 117 | 118 | const Eigen::Matrix3f w2c_R = camera_->w2c().rotation().cast(); 119 | const Eigen::Vector3f w2c_t = camera_->w2c().translation().cast(); 120 | 121 | Timer<> timer; 122 | timer.Start(); 123 | 124 | // project face to 2d (fully parallel) 125 | std::vector camera_vertices(mesh_->vertices().size()); 126 | std::vector camera_normals(mesh_->vertices().size()); 127 | std::vector camera_depth_list(mesh_->vertices().size()); 128 | std::vector image_vertices(mesh_->vertices().size()); 129 | 130 | // get projected vertex positions 131 | for (int i = 0; i < static_cast(mesh_->vertices().size()); i++) { 132 | camera_vertices[i] = w2c_R * mesh_->vertices()[i] + w2c_t; 133 | camera_normals[i] = w2c_R * mesh_->normals()[i]; 134 | camera_depth_list[i] = camera_vertices[i].z(); 135 | camera_->Project(camera_vertices[i], &image_vertices[i]); 136 | } 137 | 138 | Image1f depth_internal; 139 | Image1f* depth_{depth}; 140 | if (depth_ == nullptr) { 141 | depth_ = &depth_internal; 142 | } 143 | Init(depth_, camera_->width(), camera_->height(), 0.0f); 144 | 145 | Image1i face_id_internal; 146 | Image1i* face_id_{face_id}; 147 | if (face_id_ == nullptr) { 148 | face_id_ = &face_id_internal; 149 | } 150 | Init(face_id_, camera_->width(), camera_->height(), -1); 151 | 152 | // 255: backface, 0:frontface 153 | Image1b backface_image; 154 | Init(&backface_image, camera_->width(), camera_->height(), 155 | static_cast(0)); 156 | 157 | // 0:(1 - u - v), 1:u, 2:v 158 | Image3f weight_image; 159 | Init(&weight_image, camera_->width(), camera_->height(), 0.0f); 160 | 161 | // make face id image by z-buffer method 162 | for (int i = 0; i < static_cast(mesh_->vertex_indices().size()); i++) { 163 | const Eigen::Vector3i& face = mesh_->vertex_indices()[i]; 164 | const Eigen::Vector3f& v0_i = image_vertices[face[0]]; 165 | const Eigen::Vector3f& v1_i = image_vertices[face[1]]; 166 | const Eigen::Vector3f& v2_i = image_vertices[face[2]]; 167 | 168 | // skip if a vertex is back of the camera 169 | // todo: add near and far plane 170 | if (v0_i.z() < 0.0f || v1_i.z() < 0.0f || v2_i.z() < 0.0f) { 171 | continue; 172 | } 173 | 174 | float xmin = std::min({v0_i.x(), v1_i.x(), v2_i.x()}); 175 | float ymin = std::min({v0_i.y(), v1_i.y(), v2_i.y()}); 176 | float xmax = std::max({v0_i.x(), v1_i.x(), v2_i.x()}); 177 | float ymax = std::max({v0_i.y(), v1_i.y(), v2_i.y()}); 178 | 179 | // the triangle is out of screen 180 | if (xmin > camera_->width() - 1 || xmax < 0 || 181 | ymin > camera_->height() - 1 || ymax < 0) { 182 | continue; 183 | } 184 | 185 | uint32_t x0 = std::max(int32_t(0), (int32_t)(std::ceil(xmin))); 186 | uint32_t x1 = std::min(camera_->width() - 1, (int32_t)(std::floor(xmax))); 187 | uint32_t y0 = std::max(int32_t(0), (int32_t)(std::ceil(ymin))); 188 | uint32_t y1 = std::min(camera_->height() - 1, (int32_t)(std::floor(ymax))); 189 | 190 | float area = EdgeFunction(v0_i, v1_i, v2_i); 191 | if (std::abs(area) < std::numeric_limits::min()) { 192 | continue; 193 | } 194 | for (uint32_t y = y0; y <= y1; ++y) { 195 | for (uint32_t x = x0; x <= x1; ++x) { 196 | Eigen::Vector3f ray_w; 197 | camera_->ray_w(static_cast(x), static_cast(y), &ray_w); 198 | // even if back-face culling is enabled, dont' skip back-face 199 | // need to update z-buffer to handle front-face occluded by back-face 200 | bool backface = mesh_->face_normals()[i].dot(ray_w) > 0; 201 | Eigen::Vector3f pixel_sample(static_cast(x), 202 | static_cast(y), 0.0f); 203 | float w0 = EdgeFunction(v1_i, v2_i, pixel_sample); 204 | float w1 = EdgeFunction(v2_i, v0_i, pixel_sample); 205 | float w2 = EdgeFunction(v0_i, v1_i, pixel_sample); 206 | if ((!backface && (w0 >= 0 && w1 >= 0 && w2 >= 0)) || 207 | (backface && (w0 <= 0 && w1 <= 0 && w2 <= 0))) { 208 | w0 /= area; 209 | w1 /= area; 210 | w2 /= area; 211 | #if 0 212 | // original 213 | pixel_sample.z() = w0 * v0_i.z() + w1 * v1_i.z() + w2 * v2_i.z(); 214 | #else 215 | /** Perspective-Correct Interpolation **/ 216 | w0 /= v0_i.z(); 217 | w1 /= v1_i.z(); 218 | w2 /= v2_i.z(); 219 | 220 | pixel_sample.z() = 1.0f / (w0 + w1 + w2); 221 | 222 | w0 = w0 * pixel_sample.z(); 223 | w1 = w1 * pixel_sample.z(); 224 | w2 = w2 * pixel_sample.z(); 225 | /** Perspective-Correct Interpolation **/ 226 | #endif 227 | 228 | float& d = depth->at(y, x); 229 | if (d < std::numeric_limits::min() || pixel_sample.z() < d) { 230 | d = pixel_sample.z(); 231 | face_id->at(y, x) = i; 232 | Vec3f& weight = weight_image.at(y, x); 233 | weight[0] = w0; 234 | weight[1] = w1; 235 | weight[2] = w2; 236 | backface_image.at(y, x) = backface ? 255 : 0; 237 | } 238 | } 239 | } 240 | } 241 | } 242 | 243 | // make images by referring to face id image 244 | for (int y = 0; y < backface_image.rows; y++) { 245 | for (int x = 0; x < backface_image.cols; x++) { 246 | const unsigned char& bf = backface_image.at(y, x); 247 | int& fid = face_id_->at(y, x); 248 | if (option_.backface_culling && bf == 255) { 249 | depth_->at(y, x) = 0.0f; 250 | fid = -1; 251 | continue; 252 | } 253 | 254 | if (fid > 0) { 255 | Eigen::Vector3f ray_w; 256 | camera_->ray_w(x, y, &ray_w); 257 | 258 | Vec3f& weight = weight_image.at(y, x); 259 | float w0 = weight[0]; 260 | float w1 = weight[1]; 261 | float w2 = weight[2]; 262 | 263 | // fill mask 264 | if (mask != nullptr) { 265 | mask->at(y, x) = 255; 266 | } 267 | 268 | // calculate shading normal 269 | Eigen::Vector3f shading_normal_w{0.0f, 0.0f, 0.0f}; 270 | if (option_.shading_normal == ShadingNormal::kFace) { 271 | shading_normal_w = mesh_->face_normals()[fid]; 272 | } else if (option_.shading_normal == ShadingNormal::kVertex) { 273 | // barycentric interpolation of normal 274 | const auto& normals = mesh_->normals(); 275 | const auto& normal_indices = mesh_->normal_indices(); 276 | shading_normal_w = w0 * normals[normal_indices[fid][0]] + 277 | w1 * normals[normal_indices[fid][1]] + 278 | w2 * normals[normal_indices[fid][2]]; 279 | } 280 | 281 | // set shading normal 282 | if (normal != nullptr) { 283 | Eigen::Vector3f shading_normal_c = 284 | w2c_R * shading_normal_w; // rotate to camera coordinate 285 | Vec3f& n = normal->at(y, x); 286 | for (int k = 0; k < 3; k++) { 287 | n[k] = shading_normal_c[k]; 288 | } 289 | } 290 | 291 | // delegate color calculation to pixel_shader 292 | if (color != nullptr) { 293 | Eigen::Vector3f light_dir = ray_w; // emit light same as ray 294 | PixelShaderInput pixel_shader_input(color, x, y, w1, w2, fid, &ray_w, 295 | &light_dir, &shading_normal_w, 296 | &oren_nayar_param, mesh_); 297 | pixel_shader->Process(pixel_shader_input); 298 | } 299 | } 300 | } 301 | } 302 | 303 | timer.End(); 304 | LOGI(" Rendering main loop time: %.1f msecs\n", timer.elapsed_msec()); 305 | 306 | return true; 307 | } 308 | 309 | bool Rasterizer::Impl::RenderColor(Image3b* color) const { 310 | return Render(color, nullptr, nullptr, nullptr, nullptr); 311 | } 312 | 313 | bool Rasterizer::Impl::RenderDepth(Image1f* depth) const { 314 | return Render(nullptr, depth, nullptr, nullptr, nullptr); 315 | } 316 | 317 | bool Rasterizer::Impl::RenderNormal(Image3f* normal) const { 318 | return Render(nullptr, nullptr, normal, nullptr, nullptr); 319 | } 320 | 321 | bool Rasterizer::Impl::RenderMask(Image1b* mask) const { 322 | return Render(nullptr, nullptr, nullptr, mask, nullptr); 323 | } 324 | 325 | bool Rasterizer::Impl::RenderFaceId(Image1i* face_id) const { 326 | return Render(nullptr, nullptr, nullptr, nullptr, face_id); 327 | } 328 | 329 | bool Rasterizer::Impl::RenderW(Image3b* color, Image1w* depth, Image3f* normal, 330 | Image1b* mask, Image1i* face_id) const { 331 | if (depth == nullptr) { 332 | LOGE("depth is nullptr"); 333 | return false; 334 | } 335 | 336 | Image1f f_depth; 337 | bool org_ret = Render(color, &f_depth, normal, mask, face_id); 338 | 339 | if (org_ret) { 340 | ConvertTo(f_depth, depth); 341 | } 342 | 343 | return org_ret; 344 | } 345 | 346 | bool Rasterizer::Impl::RenderDepthW(Image1w* depth) const { 347 | return RenderW(nullptr, depth, nullptr, nullptr, nullptr); 348 | } 349 | 350 | // Renderer implementation 351 | Rasterizer::Rasterizer() : pimpl_(std::unique_ptr(new Impl)) {} 352 | 353 | Rasterizer::~Rasterizer() {} 354 | 355 | Rasterizer::Rasterizer(const RendererOption& option) 356 | : pimpl_(std::unique_ptr(new Impl(option))) {} 357 | 358 | void Rasterizer::set_option(const RendererOption& option) { 359 | pimpl_->set_option(option); 360 | } 361 | 362 | void Rasterizer::set_mesh(std::shared_ptr mesh) { 363 | pimpl_->set_mesh(mesh); 364 | } 365 | 366 | bool Rasterizer::PrepareMesh() { return pimpl_->PrepareMesh(); } 367 | 368 | void Rasterizer::set_camera(std::shared_ptr camera) { 369 | pimpl_->set_camera(camera); 370 | } 371 | 372 | bool Rasterizer::Render(Image3b* color, Image1f* depth, Image3f* normal, 373 | Image1b* mask, Image1i* face_id) const { 374 | return pimpl_->Render(color, depth, normal, mask, face_id); 375 | } 376 | 377 | bool Rasterizer::RenderColor(Image3b* color) const { 378 | return pimpl_->RenderColor(color); 379 | } 380 | 381 | bool Rasterizer::RenderDepth(Image1f* depth) const { 382 | return pimpl_->RenderDepth(depth); 383 | } 384 | 385 | bool Rasterizer::RenderNormal(Image3f* normal) const { 386 | return pimpl_->RenderNormal(normal); 387 | } 388 | 389 | bool Rasterizer::RenderMask(Image1b* mask) const { 390 | return pimpl_->RenderMask(mask); 391 | } 392 | 393 | bool Rasterizer::RenderFaceId(Image1i* face_id) const { 394 | return pimpl_->RenderFaceId(face_id); 395 | } 396 | 397 | bool Rasterizer::RenderW(Image3b* color, Image1w* depth, Image3f* normal, 398 | Image1b* mask, Image1i* face_id) const { 399 | return pimpl_->RenderW(color, depth, normal, mask, face_id); 400 | } 401 | 402 | bool Rasterizer::RenderDepthW(Image1w* depth) const { 403 | return pimpl_->RenderDepthW(depth); 404 | } 405 | 406 | } // namespace currender 407 | -------------------------------------------------------------------------------- /src/raytracer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #ifdef CURRENDER_USE_NANORT 7 | 8 | #include "currender/raytracer.h" 9 | 10 | #include 11 | 12 | #include "nanort.h" 13 | 14 | #include "src/pixel_shader.h" 15 | #include "src/util_private.h" 16 | 17 | #include "ugu/timer.h" 18 | 19 | namespace { 20 | inline void PrepareRay(nanort::Ray* ray, 21 | const Eigen::Vector3f& camera_pos_w, 22 | const Eigen::Vector3f& ray_w) { 23 | const float kFar = 1.0e+30f; 24 | ray->min_t = 0.0001f; 25 | ray->max_t = kFar; 26 | 27 | // camera position in world coordinate 28 | ray->org[0] = camera_pos_w[0]; 29 | ray->org[1] = camera_pos_w[1]; 30 | ray->org[2] = camera_pos_w[2]; 31 | 32 | // ray in world coordinate 33 | ray->dir[0] = ray_w[0]; 34 | ray->dir[1] = ray_w[1]; 35 | ray->dir[2] = ray_w[2]; 36 | } 37 | } // namespace 38 | 39 | namespace currender { 40 | 41 | // Raytracer::Impl implementation 42 | class Raytracer::Impl { 43 | bool mesh_initialized_{false}; 44 | std::shared_ptr camera_{nullptr}; 45 | std::shared_ptr mesh_{nullptr}; 46 | RendererOption option_; 47 | 48 | std::vector flatten_vertices_; 49 | std::vector flatten_faces_; 50 | 51 | nanort::BVHBuildOptions build_options_; 52 | std::unique_ptr> triangle_mesh_; 53 | std::unique_ptr> triangle_pred_; 54 | nanort::BVHAccel accel_; 55 | nanort::BVHBuildStatistics stats_; 56 | float bmin_[3], bmax_[3]; 57 | 58 | public: 59 | Impl(); 60 | ~Impl(); 61 | 62 | explicit Impl(const RendererOption& option); 63 | void set_option(const RendererOption& option); 64 | 65 | void set_mesh(std::shared_ptr mesh); 66 | 67 | bool PrepareMesh(); 68 | 69 | void set_camera(std::shared_ptr camera); 70 | 71 | bool Render(Image3b* color, Image1f* depth, Image3f* normal, Image1b* mask, 72 | Image1i* face_id) const; 73 | 74 | bool RenderColor(Image3b* color) const; 75 | bool RenderDepth(Image1f* depth) const; 76 | bool RenderNormal(Image3f* normal) const; 77 | bool RenderMask(Image1b* mask) const; 78 | bool RenderFaceId(Image1i* face_id) const; 79 | 80 | bool RenderW(Image3b* color, Image1w* depth, Image3f* normal, Image1b* mask, 81 | Image1i* face_id) const; 82 | bool RenderDepthW(Image1w* depth) const; 83 | }; 84 | 85 | Raytracer::Impl::Impl() {} 86 | Raytracer::Impl::~Impl() {} 87 | 88 | Raytracer::Impl::Impl(const RendererOption& option) { set_option(option); } 89 | 90 | void Raytracer::Impl::set_option(const RendererOption& option) { 91 | option.CopyTo(&option_); 92 | } 93 | 94 | void Raytracer::Impl::set_mesh(std::shared_ptr mesh) { 95 | mesh_initialized_ = false; 96 | mesh_ = mesh; 97 | 98 | if (mesh_->face_normals().empty()) { 99 | LOGW("face normal is empty. culling and shading may not work\n"); 100 | } 101 | 102 | if (mesh_->normals().empty()) { 103 | LOGW("vertex normal is empty. shading may not work\n"); 104 | } 105 | 106 | flatten_vertices_.clear(); 107 | flatten_faces_.clear(); 108 | 109 | const std::vector& vertices = mesh_->vertices(); 110 | flatten_vertices_.resize(vertices.size() * 3); 111 | for (size_t i = 0; i < vertices.size(); i++) { 112 | flatten_vertices_[i * 3 + 0] = vertices[i][0]; 113 | flatten_vertices_[i * 3 + 1] = vertices[i][1]; 114 | flatten_vertices_[i * 3 + 2] = vertices[i][2]; 115 | } 116 | 117 | const std::vector& vertex_indices = mesh_->vertex_indices(); 118 | flatten_faces_.resize(vertex_indices.size() * 3); 119 | for (size_t i = 0; i < vertex_indices.size(); i++) { 120 | flatten_faces_[i * 3 + 0] = vertex_indices[i][0]; 121 | flatten_faces_[i * 3 + 1] = vertex_indices[i][1]; 122 | flatten_faces_[i * 3 + 2] = vertex_indices[i][2]; 123 | } 124 | } 125 | 126 | bool Raytracer::Impl::PrepareMesh() { 127 | if (mesh_ == nullptr) { 128 | LOGE("mesh has not been set\n"); 129 | return false; 130 | } 131 | 132 | if (flatten_vertices_.empty() || flatten_faces_.empty()) { 133 | LOGE("mesh is empty\n"); 134 | return false; 135 | } 136 | 137 | bool ret = false; 138 | build_options_.cache_bbox = false; 139 | 140 | LOGI(" BVH build option:\n"); 141 | LOGI(" # of leaf primitives: %d\n", build_options_.min_leaf_primitives); 142 | LOGI(" SAH binsize : %d\n", build_options_.bin_size); 143 | 144 | Timer<> timer; 145 | timer.Start(); 146 | 147 | triangle_mesh_.reset(new nanort::TriangleMesh( 148 | &flatten_vertices_[0], &flatten_faces_[0], sizeof(float) * 3)); 149 | 150 | triangle_pred_.reset(new nanort::TriangleSAHPred( 151 | &flatten_vertices_[0], &flatten_faces_[0], sizeof(float) * 3)); 152 | 153 | LOGI("num_triangles = %llu\n", 154 | static_cast(mesh_->vertex_indices().size())); 155 | // LOGI("faces = %p\n", mesh_->vertex_indices().size()); 156 | 157 | ret = accel_.Build(static_cast(mesh_->vertex_indices().size()), 158 | *triangle_mesh_, *triangle_pred_, build_options_); 159 | 160 | if (!ret) { 161 | LOGE("BVH building failed\n"); 162 | return false; 163 | } 164 | 165 | timer.End(); 166 | LOGI(" BVH build time: %.1f msecs\n", timer.elapsed_msec()); 167 | 168 | stats_ = accel_.GetStatistics(); 169 | 170 | LOGI(" BVH statistics:\n"); 171 | LOGI(" # of leaf nodes: %d\n", stats_.num_leaf_nodes); 172 | LOGI(" # of branch nodes: %d\n", stats_.num_branch_nodes); 173 | LOGI(" Max tree depth : %d\n", stats_.max_tree_depth); 174 | 175 | accel_.BoundingBox(bmin_, bmax_); 176 | LOGI(" Bmin : %f, %f, %f\n", bmin_[0], bmin_[1], bmin_[2]); 177 | LOGI(" Bmax : %f, %f, %f\n", bmax_[0], bmax_[1], bmax_[2]); 178 | 179 | mesh_initialized_ = true; 180 | 181 | return true; 182 | } 183 | 184 | void Raytracer::Impl::set_camera(std::shared_ptr camera) { 185 | camera_ = camera; 186 | } 187 | 188 | bool Raytracer::Impl::Render(Image3b* color, Image1f* depth, Image3f* normal, 189 | Image1b* mask, Image1i* face_id) const { 190 | if (!ValidateAndInitBeforeRender(mesh_initialized_, camera_, mesh_, option_, 191 | color, depth, normal, mask, face_id)) { 192 | return false; 193 | } 194 | 195 | // make pixel shader 196 | std::unique_ptr pixel_shader = PixelShaderFactory::Create( 197 | option_.diffuse_color, option_.interp, option_.diffuse_shading); 198 | 199 | OrenNayarParam oren_nayar_param(option_.oren_nayar_sigma); 200 | 201 | const Eigen::Matrix3f w2c_R = camera_->w2c().rotation().cast(); 202 | const Eigen::Vector3f w2c_t = camera_->w2c().translation().cast(); 203 | 204 | Timer<> timer; 205 | timer.Start(); 206 | #if defined(_OPENMP) && defined(CURRENDER_USE_OPENMP) 207 | #pragma omp parallel for schedule(dynamic, 1) 208 | #endif 209 | for (int y = 0; y < camera_->height(); y++) { 210 | for (int x = 0; x < camera_->width(); x++) { 211 | // ray from camera position in world coordinate 212 | Eigen::Vector3f ray_w, org_ray_w; 213 | camera_->ray_w(x, y, &ray_w); 214 | camera_->org_ray_w(x, y, &org_ray_w); 215 | nanort::Ray ray; 216 | PrepareRay(&ray, org_ray_w, ray_w); 217 | 218 | // shoot ray 219 | nanort::TriangleIntersector<> triangle_intersector( 220 | &flatten_vertices_[0], &flatten_faces_[0], sizeof(float) * 3); 221 | nanort::TriangleIntersection<> isect; 222 | bool hit = accel_.Traverse(ray, triangle_intersector, &isect); 223 | 224 | if (!hit) { 225 | continue; 226 | } 227 | 228 | unsigned int fid = isect.prim_id; 229 | float u = isect.u; 230 | float v = isect.v; 231 | 232 | // back-face culling 233 | if (option_.backface_culling) { 234 | // back-face if face normal has same direction to ray 235 | if (mesh_->face_normals()[fid].dot(ray_w) > 0) { 236 | continue; 237 | } 238 | } 239 | 240 | // fill face id 241 | if (face_id != nullptr) { 242 | face_id->at(y, x) = fid; 243 | } 244 | 245 | // fill mask 246 | if (mask != nullptr) { 247 | mask->at(y, x) = 255; 248 | } 249 | 250 | // convert hit position to camera coordinate to get depth value 251 | if (depth != nullptr) { 252 | Eigen::Vector3f hit_pos_w = org_ray_w + ray_w * isect.t; 253 | Eigen::Vector3f hit_pos_c = w2c_R * hit_pos_w + w2c_t; 254 | assert(0.0f <= hit_pos_c[2]); // depth should be positive 255 | depth->at(y, x) = hit_pos_c[2] * option_.depth_scale; 256 | } 257 | 258 | // calculate shading normal 259 | Eigen::Vector3f shading_normal_w = Eigen::Vector3f::Zero(); 260 | if (option_.shading_normal == ShadingNormal::kFace) { 261 | shading_normal_w = mesh_->face_normals()[fid]; 262 | } else if (option_.shading_normal == ShadingNormal::kVertex) { 263 | // barycentric interpolation of normal 264 | const auto& normals = mesh_->normals(); 265 | const auto& normal_indices = mesh_->normal_indices(); 266 | shading_normal_w = (1.0f - u - v) * normals[normal_indices[fid][0]] + 267 | u * normals[normal_indices[fid][1]] + 268 | v * normals[normal_indices[fid][2]]; 269 | } 270 | 271 | // set shading normal 272 | if (normal != nullptr) { 273 | Eigen::Vector3f shading_normal_c = 274 | w2c_R * shading_normal_w; // rotate to camera coordinate 275 | Vec3f& n = normal->at(y, x); 276 | for (int k = 0; k < 3; k++) { 277 | n[k] = shading_normal_c[k]; 278 | } 279 | } 280 | 281 | // delegate color calculation to pixel_shader 282 | if (color != nullptr) { 283 | Eigen::Vector3f light_dir = ray_w; // emit light same as ray 284 | PixelShaderInput pixel_shader_input(color, x, y, u, v, fid, &ray_w, 285 | &light_dir, &shading_normal_w, 286 | &oren_nayar_param, mesh_); 287 | pixel_shader->Process(pixel_shader_input); 288 | } 289 | } 290 | } 291 | timer.End(); 292 | LOGI(" Rendering main loop time: %.1f msecs\n", timer.elapsed_msec()); 293 | 294 | return true; 295 | } 296 | 297 | bool Raytracer::Impl::RenderColor(Image3b* color) const { 298 | return Render(color, nullptr, nullptr, nullptr, nullptr); 299 | } 300 | 301 | bool Raytracer::Impl::RenderDepth(Image1f* depth) const { 302 | return Render(nullptr, depth, nullptr, nullptr, nullptr); 303 | } 304 | 305 | bool Raytracer::Impl::RenderNormal(Image3f* normal) const { 306 | return Render(nullptr, nullptr, normal, nullptr, nullptr); 307 | } 308 | 309 | bool Raytracer::Impl::RenderMask(Image1b* mask) const { 310 | return Render(nullptr, nullptr, nullptr, mask, nullptr); 311 | } 312 | 313 | bool Raytracer::Impl::RenderFaceId(Image1i* face_id) const { 314 | return Render(nullptr, nullptr, nullptr, nullptr, face_id); 315 | } 316 | 317 | bool Raytracer::Impl::RenderW(Image3b* color, Image1w* depth, Image3f* normal, 318 | Image1b* mask, Image1i* face_id) const { 319 | if (depth == nullptr) { 320 | LOGE("depth is nullptr"); 321 | return false; 322 | } 323 | 324 | Image1f f_depth; 325 | bool org_ret = Render(color, &f_depth, normal, mask, face_id); 326 | 327 | if (org_ret) { 328 | ConvertTo(f_depth, depth); 329 | } 330 | 331 | return org_ret; 332 | } 333 | 334 | bool Raytracer::Impl::RenderDepthW(Image1w* depth) const { 335 | return RenderW(nullptr, depth, nullptr, nullptr, nullptr); 336 | } 337 | 338 | // Renderer implementation 339 | Raytracer::Raytracer() : pimpl_(std::unique_ptr(new Impl)) {} 340 | 341 | Raytracer::~Raytracer() {} 342 | 343 | Raytracer::Raytracer(const RendererOption& option) 344 | : pimpl_(std::unique_ptr(new Impl(option))) {} 345 | 346 | void Raytracer::set_option(const RendererOption& option) { 347 | pimpl_->set_option(option); 348 | } 349 | 350 | void Raytracer::set_mesh(std::shared_ptr mesh) { 351 | pimpl_->set_mesh(mesh); 352 | } 353 | 354 | bool Raytracer::PrepareMesh() { return pimpl_->PrepareMesh(); } 355 | 356 | void Raytracer::set_camera(std::shared_ptr camera) { 357 | pimpl_->set_camera(camera); 358 | } 359 | 360 | bool Raytracer::Render(Image3b* color, Image1f* depth, Image3f* normal, 361 | Image1b* mask, Image1i* face_id) const { 362 | return pimpl_->Render(color, depth, normal, mask, face_id); 363 | } 364 | 365 | bool Raytracer::RenderColor(Image3b* color) const { 366 | return pimpl_->RenderColor(color); 367 | } 368 | 369 | bool Raytracer::RenderDepth(Image1f* depth) const { 370 | return pimpl_->RenderDepth(depth); 371 | } 372 | 373 | bool Raytracer::RenderNormal(Image3f* normal) const { 374 | return pimpl_->RenderNormal(normal); 375 | } 376 | 377 | bool Raytracer::RenderMask(Image1b* mask) const { 378 | return pimpl_->RenderMask(mask); 379 | } 380 | 381 | bool Raytracer::RenderFaceId(Image1i* face_id) const { 382 | return pimpl_->RenderFaceId(face_id); 383 | } 384 | 385 | bool Raytracer::RenderW(Image3b* color, Image1w* depth, Image3f* normal, 386 | Image1b* mask, Image1i* face_id) const { 387 | return pimpl_->RenderW(color, depth, normal, mask, face_id); 388 | } 389 | 390 | bool Raytracer::RenderDepthW(Image1w* depth) const { 391 | return pimpl_->RenderDepthW(depth); 392 | } 393 | 394 | } // namespace currender 395 | 396 | #endif 397 | -------------------------------------------------------------------------------- /src/util_private.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #include "src/util_private.h" 7 | 8 | #include 9 | 10 | namespace currender { 11 | 12 | bool ValidateAndInitBeforeRender(bool mesh_initialized, 13 | std::shared_ptr camera, 14 | std::shared_ptr mesh, 15 | const RendererOption& option, Image3b* color, 16 | Image1f* depth, Image3f* normal, Image1b* mask, 17 | Image1i* face_id) { 18 | if (camera == nullptr) { 19 | LOGE("camera has not been set\n"); 20 | return false; 21 | } 22 | if (!mesh_initialized) { 23 | LOGE("mesh has not been initialized\n"); 24 | return false; 25 | } 26 | if (option.backface_culling && mesh->face_normals().empty()) { 27 | LOGE("specified back-face culling but face normal is empty.\n"); 28 | return false; 29 | } 30 | if (option.diffuse_color == DiffuseColor::kTexture && 31 | mesh->materials().empty()) { 32 | LOGE("specified texture as diffuse color but texture is empty.\n"); 33 | return false; 34 | } 35 | if (option.diffuse_color == DiffuseColor::kTexture) { 36 | for (int i = 0; i < static_cast(mesh->materials().size()); i++) { 37 | if (mesh->materials()[i].diffuse_tex.empty()) { 38 | LOGE("specified texture as diffuse color but %d th texture is empty.\n", 39 | i); 40 | return false; 41 | } 42 | } 43 | } 44 | if (option.diffuse_color == DiffuseColor::kVertex && 45 | mesh->vertex_colors().empty()) { 46 | LOGE( 47 | "specified vertex color as diffuse color but vertex color is empty.\n"); 48 | return false; 49 | } 50 | if (option.shading_normal == ShadingNormal::kFace && 51 | mesh->face_normals().empty()) { 52 | LOGE("specified face normal as shading normal but face normal is empty.\n"); 53 | return false; 54 | } 55 | if (option.shading_normal == ShadingNormal::kVertex && 56 | mesh->normals().empty()) { 57 | LOGE( 58 | "specified vertex normal as shading normal but vertex normal is " 59 | "empty.\n"); 60 | return false; 61 | } 62 | if (color == nullptr && depth == nullptr && normal == nullptr && 63 | mask == nullptr && face_id == nullptr) { 64 | LOGE("all arguments are nullptr. nothing to do\n"); 65 | return false; 66 | } 67 | 68 | int width = camera->width(); 69 | int height = camera->height(); 70 | 71 | if (color != nullptr) { 72 | Init(color, width, height, unsigned char(0)); 73 | } 74 | if (depth != nullptr) { 75 | Init(depth, width, height, 0.0f); 76 | } 77 | if (normal != nullptr) { 78 | Init(normal, width, height, 0.0f); 79 | } 80 | if (mask != nullptr) { 81 | Init(mask, width, height, unsigned char(0)); 82 | } 83 | if (face_id != nullptr) { 84 | // initialize with -1 (no hit) 85 | Init(face_id, width, height, -1); 86 | } 87 | 88 | return true; 89 | } 90 | 91 | } // namespace currender 92 | -------------------------------------------------------------------------------- /src/util_private.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019, unclearness 3 | * All rights reserved. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #include "currender/renderer.h" 12 | 13 | namespace currender { 14 | 15 | bool ValidateAndInitBeforeRender(bool mesh_initialized, 16 | std::shared_ptr camera, 17 | std::shared_ptr mesh, 18 | const RendererOption& option, Image3b* color, 19 | Image1f* depth, Image3f* normal, Image1b* mask, 20 | Image1i* face_id); 21 | 22 | } // namespace currender 23 | --------------------------------------------------------------------------------