mirror of https://github.com/opencv/opencv.git
Merge pull request #24459 from savuor:tri_rasterize
Triangle rasterization function #24459 #24065 reopened since the previous one was automatically closed after rebase Connected PR with ground truth data: [#1113@extra](https://github.com/opencv/opencv_extra/pull/1113) ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMakepull/25080/head
parent
f084a229b4
commit
fa745553bf
7 changed files with 2398 additions and 0 deletions
@ -0,0 +1,113 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
|
||||||
|
# Python 2/3 compatibility |
||||||
|
from __future__ import print_function |
||||||
|
|
||||||
|
import os, numpy |
||||||
|
import math |
||||||
|
import unittest |
||||||
|
import cv2 as cv |
||||||
|
|
||||||
|
from tests_common import NewOpenCVTests |
||||||
|
|
||||||
|
def lookAtMatrixCal(position, lookat, upVector): |
||||||
|
tmp = position - lookat |
||||||
|
norm = numpy.linalg.norm(tmp) |
||||||
|
w = tmp / norm |
||||||
|
tmp = numpy.cross(upVector, w) |
||||||
|
norm = numpy.linalg.norm(tmp) |
||||||
|
u = tmp / norm |
||||||
|
v = numpy.cross(w, u) |
||||||
|
res = numpy.array([ |
||||||
|
[u[0], u[1], u[2], 0], |
||||||
|
[v[0], v[1], v[2], 0], |
||||||
|
[w[0], w[1], w[2], 0], |
||||||
|
[0, 0, 0, 1.0] |
||||||
|
], dtype=numpy.float32) |
||||||
|
translate = numpy.array([ |
||||||
|
[1.0, 0, 0, -position[0]], |
||||||
|
[0, 1.0, 0, -position[1]], |
||||||
|
[0, 0, 1.0, -position[2]], |
||||||
|
[0, 0, 0, 1.0] |
||||||
|
], dtype=numpy.float32) |
||||||
|
res = numpy.matmul(res, translate) |
||||||
|
return res |
||||||
|
|
||||||
|
class raster_test(NewOpenCVTests): |
||||||
|
|
||||||
|
def prepareData(self): |
||||||
|
self.vertices = numpy.array([ |
||||||
|
[ 2.0, 0.0, -2.0], |
||||||
|
[ 0.0, -6.0, -2.0], |
||||||
|
[-2.0, 0.0, -2.0], |
||||||
|
[ 3.5, -1.0, -5.0], |
||||||
|
[ 2.5, -2.5, -5.0], |
||||||
|
[-1.0, 1.0, -5.0], |
||||||
|
[-6.5, -1.0, -3.0], |
||||||
|
[-2.5, -2.0, -3.0], |
||||||
|
[ 1.0, 1.0, -5.0], |
||||||
|
], dtype=numpy.float32) |
||||||
|
|
||||||
|
self.indices = numpy.array([ [0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=int) |
||||||
|
|
||||||
|
col1 = [ 185.0, 238.0, 217.0 ] |
||||||
|
col2 = [ 238.0, 217.0, 185.0 ] |
||||||
|
col3 = [ 238.0, 10.0, 150.0 ] |
||||||
|
|
||||||
|
self.colors = numpy.array([ |
||||||
|
col1, col2, col3, |
||||||
|
col2, col3, col1, |
||||||
|
col3, col1, col2, |
||||||
|
], dtype=numpy.float32) |
||||||
|
|
||||||
|
self.colors = self.colors / 255.0 |
||||||
|
|
||||||
|
self.zNear = 0.1 |
||||||
|
self.zFar = 50.0 |
||||||
|
self.fovy = 45.0 * math.pi / 180.0 |
||||||
|
|
||||||
|
position = numpy.array([0.0, 0.0, 5.0], dtype=numpy.float32) |
||||||
|
lookat = numpy.array([0.0, 0.0, 0.0], dtype=numpy.float32) |
||||||
|
upVector = numpy.array([0.0, 1.0, 0.0], dtype=numpy.float32) |
||||||
|
self.cameraPose = lookAtMatrixCal(position, lookat, upVector) |
||||||
|
|
||||||
|
self.depth_buf = numpy.ones((240, 320), dtype=numpy.float32) * self.zFar |
||||||
|
self.color_buf = numpy.zeros((240, 320, 3), dtype=numpy.float32) |
||||||
|
|
||||||
|
self.settings = cv.TriangleRasterizeSettings().setShadingType(cv.RASTERIZE_SHADING_SHADED) |
||||||
|
self.settings = self.settings.setCullingMode(cv.RASTERIZE_CULLING_NONE) |
||||||
|
|
||||||
|
def compareResults(self, needRgb, needDepth): |
||||||
|
if needDepth: |
||||||
|
depth = self.get_sample('rendering/depth_image_Clipping_320x240_CullNone.png', cv.IMREAD_ANYDEPTH).astype(numpy.float32) |
||||||
|
depthFactor = 1000.0 |
||||||
|
diff = depth/depthFactor - self.depth_buf |
||||||
|
norm = numpy.linalg.norm(diff) |
||||||
|
self.assertLessEqual(norm, 356.0) |
||||||
|
|
||||||
|
if needRgb: |
||||||
|
rgb = self.get_sample('rendering/example_image_Clipping_320x240_CullNone_Shaded.png', cv.IMREAD_ANYCOLOR) |
||||||
|
diff = rgb/255.0 - self.color_buf |
||||||
|
norm = numpy.linalg.norm(diff) |
||||||
|
self.assertLessEqual(norm, 11.62) |
||||||
|
|
||||||
|
def test_rasterizeBoth(self): |
||||||
|
self.prepareData() |
||||||
|
self.color_buf, self.depth_buf = cv.triangleRasterize(self.vertices, self.indices, self.colors, self.color_buf, self.depth_buf, |
||||||
|
self.cameraPose, self.fovy, self.zNear, self.zFar, self.settings) |
||||||
|
self.compareResults(needRgb=True, needDepth=True) |
||||||
|
|
||||||
|
def test_rasterizeDepth(self): |
||||||
|
self.prepareData() |
||||||
|
self.depth_buf = cv.triangleRasterizeDepth(self.vertices, self.indices, self.depth_buf, |
||||||
|
self.cameraPose, self.fovy, self.zNear, self.zFar, self.settings) |
||||||
|
self.compareResults(needRgb=False, needDepth=True) |
||||||
|
|
||||||
|
def test_rasterizeColor(self): |
||||||
|
self.prepareData() |
||||||
|
self.color_buf = cv.triangleRasterizeColor(self.vertices, self.indices, self.colors, self.color_buf, |
||||||
|
self.cameraPose, self.fovy, self.zNear, self.zFar, self.settings) |
||||||
|
self.compareResults(needRgb=True, needDepth=False) |
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
NewOpenCVTests.bootstrap() |
@ -0,0 +1,344 @@ |
|||||||
|
// This file is part of OpenCV project.
|
||||||
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||||
|
// of this distribution and at http://opencv.org/license.html
|
||||||
|
|
||||||
|
#include "perf_precomp.hpp" |
||||||
|
|
||||||
|
namespace opencv_test |
||||||
|
{ |
||||||
|
|
||||||
|
// that was easier than using CV_ENUM() macro
|
||||||
|
namespace |
||||||
|
{ |
||||||
|
using namespace cv; |
||||||
|
struct ShadingTypeEnum |
||||||
|
{ |
||||||
|
static const std::array<TriangleShadingType, 3> vals; |
||||||
|
static const std::array<std::string, 3> svals; |
||||||
|
|
||||||
|
ShadingTypeEnum(TriangleShadingType v = RASTERIZE_SHADING_WHITE) : val(v) {} |
||||||
|
operator TriangleShadingType() const { return val; } |
||||||
|
void PrintTo(std::ostream *os) const |
||||||
|
{ |
||||||
|
int v = int(val); |
||||||
|
if (v >= 0 && v < (int)vals.size()) |
||||||
|
{ |
||||||
|
*os << svals[v]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
*os << "UNKNOWN"; |
||||||
|
} |
||||||
|
} |
||||||
|
static ::testing::internal::ParamGenerator<ShadingTypeEnum> all() |
||||||
|
{ |
||||||
|
return ::testing::Values(ShadingTypeEnum(vals[0]), |
||||||
|
ShadingTypeEnum(vals[1]), |
||||||
|
ShadingTypeEnum(vals[2])); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
TriangleShadingType val; |
||||||
|
}; |
||||||
|
|
||||||
|
const std::array<TriangleShadingType, 3> ShadingTypeEnum::vals |
||||||
|
{ |
||||||
|
RASTERIZE_SHADING_WHITE, |
||||||
|
RASTERIZE_SHADING_FLAT, |
||||||
|
RASTERIZE_SHADING_SHADED |
||||||
|
}; |
||||||
|
const std::array<std::string, 3> ShadingTypeEnum::svals |
||||||
|
{ |
||||||
|
std::string("White"), |
||||||
|
std::string("Flat"), |
||||||
|
std::string("Shaded") |
||||||
|
}; |
||||||
|
|
||||||
|
static inline void PrintTo(const ShadingTypeEnum &t, std::ostream *os) { t.PrintTo(os); } |
||||||
|
|
||||||
|
|
||||||
|
using namespace cv; |
||||||
|
struct GlCompatibleModeEnum |
||||||
|
{ |
||||||
|
static const std::array<TriangleGlCompatibleMode, 2> vals; |
||||||
|
static const std::array<std::string, 2> svals; |
||||||
|
|
||||||
|
GlCompatibleModeEnum(TriangleGlCompatibleMode v = RASTERIZE_COMPAT_DISABLED) : val(v) {} |
||||||
|
operator TriangleGlCompatibleMode() const { return val; } |
||||||
|
void PrintTo(std::ostream *os) const |
||||||
|
{ |
||||||
|
int v = int(val); |
||||||
|
if (v >= 0 && v < (int)vals.size()) |
||||||
|
{ |
||||||
|
*os << svals[v]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
*os << "UNKNOWN"; |
||||||
|
} |
||||||
|
} |
||||||
|
static ::testing::internal::ParamGenerator<GlCompatibleModeEnum> all() |
||||||
|
{ |
||||||
|
return ::testing::Values(GlCompatibleModeEnum(vals[0]), |
||||||
|
GlCompatibleModeEnum(vals[1])); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
TriangleGlCompatibleMode val; |
||||||
|
}; |
||||||
|
|
||||||
|
const std::array<TriangleGlCompatibleMode, 2> GlCompatibleModeEnum::vals |
||||||
|
{ |
||||||
|
RASTERIZE_COMPAT_DISABLED, |
||||||
|
RASTERIZE_COMPAT_INVDEPTH, |
||||||
|
}; |
||||||
|
const std::array<std::string, 2> GlCompatibleModeEnum::svals |
||||||
|
{ |
||||||
|
std::string("Disabled"), |
||||||
|
std::string("InvertedDepth"), |
||||||
|
}; |
||||||
|
|
||||||
|
static inline void PrintTo(const GlCompatibleModeEnum &t, std::ostream *os) { t.PrintTo(os); } |
||||||
|
} |
||||||
|
|
||||||
|
enum class Outputs |
||||||
|
{ |
||||||
|
DepthOnly = 0, |
||||||
|
ColorOnly = 1, |
||||||
|
DepthColor = 2, |
||||||
|
}; |
||||||
|
|
||||||
|
// that was easier than using CV_ENUM() macro
|
||||||
|
namespace |
||||||
|
{ |
||||||
|
using namespace cv; |
||||||
|
struct OutputsEnum |
||||||
|
{ |
||||||
|
static const std::array<Outputs, 3> vals; |
||||||
|
static const std::array<std::string, 3> svals; |
||||||
|
|
||||||
|
OutputsEnum(Outputs v = Outputs::DepthColor) : val(v) {} |
||||||
|
operator Outputs() const { return val; } |
||||||
|
void PrintTo(std::ostream *os) const |
||||||
|
{ |
||||||
|
int v = int(val); |
||||||
|
if (v >= 0 && v < (int)vals.size()) |
||||||
|
{ |
||||||
|
*os << svals[v]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
*os << "UNKNOWN"; |
||||||
|
} |
||||||
|
} |
||||||
|
static ::testing::internal::ParamGenerator<OutputsEnum> all() |
||||||
|
{ |
||||||
|
return ::testing::Values(OutputsEnum(vals[0]), |
||||||
|
OutputsEnum(vals[1]), |
||||||
|
OutputsEnum(vals[2])); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
Outputs val; |
||||||
|
}; |
||||||
|
|
||||||
|
const std::array<Outputs, 3> OutputsEnum::vals |
||||||
|
{ |
||||||
|
Outputs::DepthOnly, |
||||||
|
Outputs::ColorOnly, |
||||||
|
Outputs::DepthColor |
||||||
|
}; |
||||||
|
const std::array<std::string, 3> OutputsEnum::svals |
||||||
|
{ |
||||||
|
std::string("DepthOnly"), |
||||||
|
std::string("ColorOnly"), |
||||||
|
std::string("DepthColor") |
||||||
|
}; |
||||||
|
|
||||||
|
static inline void PrintTo(const OutputsEnum &t, std::ostream *os) { t.PrintTo(os); } |
||||||
|
} |
||||||
|
|
||||||
|
static Matx44d lookAtMatrixCal(const Vec3d& position, const Vec3d& lookat, const Vec3d& upVector) |
||||||
|
{ |
||||||
|
Vec3d w = cv::normalize(position - lookat); |
||||||
|
Vec3d u = cv::normalize(upVector.cross(w)); |
||||||
|
|
||||||
|
Vec3d v = w.cross(u); |
||||||
|
|
||||||
|
Matx44d res(u[0], u[1], u[2], 0, |
||||||
|
v[0], v[1], v[2], 0, |
||||||
|
w[0], w[1], w[2], 0, |
||||||
|
0, 0, 0, 1.0); |
||||||
|
|
||||||
|
Matx44d translate(1.0, 0, 0, -position[0], |
||||||
|
0, 1.0, 0, -position[1], |
||||||
|
0, 0, 1.0, -position[2], |
||||||
|
0, 0, 0, 1.0); |
||||||
|
res = res * translate; |
||||||
|
|
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static void generateNormals(const std::vector<Vec3f>& points, const std::vector<std::vector<int>>& indices, |
||||||
|
std::vector<Vec3f>& normals) |
||||||
|
{ |
||||||
|
std::vector<std::vector<Vec3f>> preNormals(points.size(), std::vector<Vec3f>()); |
||||||
|
|
||||||
|
for (const auto& tri : indices) |
||||||
|
{ |
||||||
|
Vec3f p0 = points[tri[0]]; |
||||||
|
Vec3f p1 = points[tri[1]]; |
||||||
|
Vec3f p2 = points[tri[2]]; |
||||||
|
|
||||||
|
Vec3f cross = cv::normalize((p1 - p0).cross(p2 - p0)); |
||||||
|
for (int i = 0; i < 3; i++) |
||||||
|
{ |
||||||
|
preNormals[tri[i]].push_back(cross); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
normals.reserve(points.size()); |
||||||
|
for (const auto& pn : preNormals) |
||||||
|
{ |
||||||
|
Vec3f sum { }; |
||||||
|
for (const auto& n : pn) |
||||||
|
{ |
||||||
|
sum += n; |
||||||
|
} |
||||||
|
normals.push_back(cv::normalize(sum)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// load model once and keep it in static memory
|
||||||
|
static void getModelOnce(const std::string& objectPath, std::vector<Vec3f>& vertices, |
||||||
|
std::vector<Vec3i>& indices, std::vector<Vec3f>& colors) |
||||||
|
{ |
||||||
|
static bool load = false; |
||||||
|
static std::vector<Vec3f> vert, col; |
||||||
|
static std::vector<Vec3i> ind; |
||||||
|
|
||||||
|
if (!load) |
||||||
|
{ |
||||||
|
std::vector<vector<int>> indvec; |
||||||
|
// using per-vertex normals as colors
|
||||||
|
loadMesh(objectPath, vert, indvec); |
||||||
|
generateNormals(vert, indvec, col); |
||||||
|
|
||||||
|
for (const auto &vec : indvec) |
||||||
|
{ |
||||||
|
ind.push_back({vec[0], vec[1], vec[2]}); |
||||||
|
} |
||||||
|
|
||||||
|
for (auto &color : col) |
||||||
|
{ |
||||||
|
color = Vec3f(abs(color[0]), abs(color[1]), abs(color[2])); |
||||||
|
} |
||||||
|
|
||||||
|
load = true; |
||||||
|
} |
||||||
|
|
||||||
|
vertices = vert; |
||||||
|
colors = col; |
||||||
|
indices = ind; |
||||||
|
} |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
std::string printEnum(T v) |
||||||
|
{ |
||||||
|
std::ostringstream ss; |
||||||
|
v.PrintTo(&ss); |
||||||
|
return ss.str(); |
||||||
|
} |
||||||
|
|
||||||
|
// resolution, shading type, outputs needed
|
||||||
|
typedef perf::TestBaseWithParam<std::tuple<std::tuple<int, int>, ShadingTypeEnum, OutputsEnum, GlCompatibleModeEnum>> RenderingTest; |
||||||
|
|
||||||
|
PERF_TEST_P(RenderingTest, rasterizeTriangles, ::testing::Combine( |
||||||
|
::testing::Values(std::make_tuple(1920, 1080), std::make_tuple(1024, 768), std::make_tuple(640, 480)), |
||||||
|
ShadingTypeEnum::all(), |
||||||
|
OutputsEnum::all(), |
||||||
|
GlCompatibleModeEnum::all() |
||||||
|
)) |
||||||
|
{ |
||||||
|
auto t = GetParam(); |
||||||
|
auto wh = std::get<0>(t); |
||||||
|
int width = std::get<0>(wh); |
||||||
|
int height = std::get<1>(wh); |
||||||
|
auto shadingType = std::get<1>(t); |
||||||
|
auto outputs = std::get<2>(t); |
||||||
|
auto glCompatibleMode = std::get<3>(t); |
||||||
|
|
||||||
|
string objectPath = findDataFile("viz/dragon.ply"); |
||||||
|
|
||||||
|
Vec3f position = Vec3d( 1.9, 0.4, 1.3); |
||||||
|
Vec3f lookat = Vec3d( 0.0, 0.0, 0.0); |
||||||
|
Vec3f upVector = Vec3d( 0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
double fovy = 45.0; |
||||||
|
|
||||||
|
std::vector<Vec3f> vertices; |
||||||
|
std::vector<Vec3i> indices; |
||||||
|
std::vector<Vec3f> colors; |
||||||
|
|
||||||
|
getModelOnce(objectPath, vertices, indices, colors); |
||||||
|
if (shadingType != RASTERIZE_SHADING_WHITE) |
||||||
|
{ |
||||||
|
// let vertices be in BGR format to avoid later color conversions
|
||||||
|
// cvtColor does not work with 1d Mats
|
||||||
|
std::vector<Mat> xyz; |
||||||
|
cv::split(colors, xyz); |
||||||
|
cv::merge(std::vector<Mat>{xyz[2], xyz[1], xyz[0]}, colors); |
||||||
|
} |
||||||
|
|
||||||
|
double zNear = 0.1, zFar = 50.0; |
||||||
|
|
||||||
|
Matx44d cameraPose = lookAtMatrixCal(position, lookat, upVector); |
||||||
|
double fovYradians = fovy * (CV_PI / 180.0); |
||||||
|
TriangleRasterizeSettings settings; |
||||||
|
settings.setCullingMode(RASTERIZE_CULLING_CW) |
||||||
|
.setShadingType(shadingType) |
||||||
|
.setGlCompatibleMode(glCompatibleMode); |
||||||
|
|
||||||
|
Mat depth_buf, color_buf; |
||||||
|
while (next()) |
||||||
|
{ |
||||||
|
// Prefilled to measure pure rendering time w/o allocation and clear
|
||||||
|
float zMax = (glCompatibleMode == RASTERIZE_COMPAT_INVDEPTH) ? 1.f : (float)zFar; |
||||||
|
depth_buf = Mat(height, width, CV_32F, zMax); |
||||||
|
color_buf = Mat(height, width, CV_32FC3, Scalar::all(0)); |
||||||
|
|
||||||
|
startTimer(); |
||||||
|
if (outputs == Outputs::ColorOnly) |
||||||
|
{ |
||||||
|
cv::triangleRasterizeColor(vertices, indices, colors, color_buf, cameraPose, |
||||||
|
fovYradians, zNear, zFar, settings); |
||||||
|
} |
||||||
|
else if (outputs == Outputs::DepthOnly) |
||||||
|
{ |
||||||
|
cv::triangleRasterizeDepth(vertices, indices, depth_buf, cameraPose, |
||||||
|
fovYradians, zNear, zFar, settings); |
||||||
|
} |
||||||
|
else // Outputs::DepthColor
|
||||||
|
{ |
||||||
|
cv::triangleRasterize(vertices, indices, colors, color_buf, depth_buf, |
||||||
|
cameraPose, fovYradians, zNear, zFar, settings); |
||||||
|
} |
||||||
|
stopTimer(); |
||||||
|
} |
||||||
|
|
||||||
|
if (debugLevel > 0) |
||||||
|
{ |
||||||
|
depth_buf.convertTo(depth_buf, CV_16U, 1000.0); |
||||||
|
|
||||||
|
std::string shadingName = printEnum(shadingType); |
||||||
|
std::string suffix = cv::format("%dx%d_%s", width, height, shadingName.c_str()); |
||||||
|
|
||||||
|
imwrite("perf_color_image_" + suffix + ".png", color_buf * 255.f); |
||||||
|
imwrite("perf_depth_image_" + suffix + ".png", depth_buf); |
||||||
|
} |
||||||
|
|
||||||
|
SANITY_CHECK_NOTHING(); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
@ -0,0 +1,402 @@ |
|||||||
|
// This file is part of OpenCV project.
|
||||||
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||||
|
// of this distribution and at http://opencv.org/license.html
|
||||||
|
|
||||||
|
#include "precomp.hpp" |
||||||
|
|
||||||
|
namespace cv { |
||||||
|
|
||||||
|
TriangleRasterizeSettings::TriangleRasterizeSettings() |
||||||
|
{ |
||||||
|
shadingType = RASTERIZE_SHADING_SHADED; |
||||||
|
cullingMode = RASTERIZE_CULLING_CW; |
||||||
|
glCompatibleMode = RASTERIZE_COMPAT_DISABLED; |
||||||
|
} |
||||||
|
|
||||||
|
static void drawTriangle(Vec4f verts[3], Vec3f colors[3], Mat& depthBuf, Mat& colorBuf, |
||||||
|
TriangleRasterizeSettings settings) |
||||||
|
{ |
||||||
|
// this will be useful during refactoring
|
||||||
|
// if there's gonna be more supported data types
|
||||||
|
CV_DbgAssert(depthBuf.empty() || depthBuf.type() == CV_32FC1); |
||||||
|
CV_DbgAssert(colorBuf.empty() || colorBuf.type() == CV_32FC3); |
||||||
|
|
||||||
|
// any of buffers can be empty
|
||||||
|
int width = std::max(colorBuf.cols, depthBuf.cols); |
||||||
|
int height = std::max(colorBuf.rows, depthBuf.rows); |
||||||
|
|
||||||
|
Point minPt(width, height), maxPt(0, 0); |
||||||
|
for (int i = 0; i < 3; i++) |
||||||
|
{ |
||||||
|
// round down to cover the whole pixel
|
||||||
|
int x = (int)(verts[i][0]), y = (int)(verts[i][1]); |
||||||
|
minPt.x = std::min( x, minPt.x); |
||||||
|
minPt.y = std::min( y, minPt.y); |
||||||
|
maxPt.x = std::max(x + 1, maxPt.x); |
||||||
|
maxPt.y = std::max(y + 1, maxPt.y); |
||||||
|
} |
||||||
|
|
||||||
|
minPt.x = std::max(minPt.x, 0); maxPt.x = std::min(maxPt.x, width); |
||||||
|
minPt.y = std::max(minPt.y, 0); maxPt.y = std::min(maxPt.y, height); |
||||||
|
|
||||||
|
Point2f a(verts[0][0], verts[0][1]), b(verts[1][0], verts[1][1]), c(verts[2][0], verts[2][1]); |
||||||
|
Point2f bc = b - c, ac = a - c; |
||||||
|
float d = ac.x*bc.y - ac.y*bc.x; |
||||||
|
|
||||||
|
// culling and degenerated triangle removal
|
||||||
|
if ((settings.cullingMode == RASTERIZE_CULLING_CW && d <= 0) || |
||||||
|
(settings.cullingMode == RASTERIZE_CULLING_CCW && d >= 0) || |
||||||
|
(abs(d) < 1e-6)) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
float invd = 1.f / d; |
||||||
|
Vec3f zinv { verts[0][2], verts[1][2], verts[2][2] }; |
||||||
|
Vec3f w { verts[0][3], verts[1][3], verts[2][3] }; |
||||||
|
|
||||||
|
for (int y = minPt.y; y < maxPt.y; y++) |
||||||
|
{ |
||||||
|
for (int x = minPt.x; x < maxPt.x; x++) |
||||||
|
{ |
||||||
|
Point2f p(x + 0.5f, y + 0.5f), pc = p - c; |
||||||
|
// barycentric coordinates
|
||||||
|
Vec3f f; |
||||||
|
f[0] = ( pc.x * bc.y - pc.y * bc.x) * invd; |
||||||
|
f[1] = ( pc.y * ac.x - pc.x * ac.y) * invd; |
||||||
|
f[2] = 1.f - f[0] - f[1]; |
||||||
|
// if inside the triangle
|
||||||
|
if ((f[0] >= 0) && (f[1] >= 0) && (f[2] >= 0)) |
||||||
|
{ |
||||||
|
bool update = false; |
||||||
|
if (!depthBuf.empty()) |
||||||
|
{ |
||||||
|
float zCurrent = depthBuf.at<float>(height - 1 - y, x); |
||||||
|
float zNew = f[0] * zinv[0] + f[1] * zinv[1] + f[2] * zinv[2]; |
||||||
|
if (zNew < zCurrent) |
||||||
|
{ |
||||||
|
update = true; |
||||||
|
depthBuf.at<float>(height - 1 - y, x) = zNew; |
||||||
|
} |
||||||
|
} |
||||||
|
else // RASTERIZE_SHADING_WHITE
|
||||||
|
{ |
||||||
|
update = true; |
||||||
|
} |
||||||
|
|
||||||
|
if (!colorBuf.empty() && update) |
||||||
|
{ |
||||||
|
Vec3f color; |
||||||
|
if (settings.shadingType == RASTERIZE_SHADING_WHITE) |
||||||
|
{ |
||||||
|
color = { 1.f, 1.f, 1.f }; |
||||||
|
} |
||||||
|
else if (settings.shadingType == RASTERIZE_SHADING_FLAT) |
||||||
|
{ |
||||||
|
color = colors[0]; |
||||||
|
} |
||||||
|
else // TriangleShadingType::Shaded
|
||||||
|
{ |
||||||
|
float zInter = 1.0f / (f[0] * w[0] + f[1] * w[1] + f[2] * w[2]); |
||||||
|
color = { 0, 0, 0 }; |
||||||
|
for (int j = 0; j < 3; j++) |
||||||
|
{ |
||||||
|
color += (f[j] * w[j]) * colors[j]; |
||||||
|
} |
||||||
|
color *= zInter; |
||||||
|
} |
||||||
|
colorBuf.at<Vec3f>(height - 1 - y, x) = color; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// values outside of [zNear, zFar] have to be restored
|
||||||
|
// [0, 1] -> [zNear, zFar]
|
||||||
|
static void linearizeDepth(const Mat& inbuf, const Mat& validMask, Mat outbuf, double zFar, double zNear) |
||||||
|
{ |
||||||
|
CV_Assert(inbuf.type() == CV_32FC1); |
||||||
|
CV_Assert(validMask.type() == CV_8UC1); |
||||||
|
CV_Assert(outbuf.type() == CV_32FC1); |
||||||
|
CV_Assert(outbuf.size() == inbuf.size()); |
||||||
|
|
||||||
|
float scaleNear = (float)(1.0 / zNear); |
||||||
|
float scaleFar = (float)(1.0 / zFar); |
||||||
|
for (int y = 0; y < inbuf.rows; y++) |
||||||
|
{ |
||||||
|
const float* inp = inbuf.ptr<float>(y); |
||||||
|
const uchar * validPtr = validMask.ptr<uchar>(y); |
||||||
|
float * outp = outbuf.ptr<float>(y); |
||||||
|
for (int x = 0; x < inbuf.cols; x++) |
||||||
|
{ |
||||||
|
if (validPtr[x]) |
||||||
|
{ |
||||||
|
float d = inp[x]; |
||||||
|
// precision-optimized version of this:
|
||||||
|
//float z = - zFar * zNear / (d * (zFar - zNear) - zFar);
|
||||||
|
float z = 1.f / ((1.f - d) * scaleNear + d * scaleFar ); |
||||||
|
outp[x] = z; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// [zNear, zFar] -> [0, 1]
|
||||||
|
static void invertDepth(const Mat& inbuf, Mat& outbuf, Mat& validMask, double zNear, double zFar) |
||||||
|
{ |
||||||
|
CV_Assert(inbuf.type() == CV_32FC1); |
||||||
|
outbuf.create(inbuf.size(), CV_32FC1); |
||||||
|
validMask.create(inbuf.size(), CV_8UC1); |
||||||
|
|
||||||
|
float fNear = (float)zNear, fFar = (float)zFar; |
||||||
|
float zadd = (float)(zFar / (zFar - zNear)); |
||||||
|
float zmul = (float)(-zNear * zFar / (zFar - zNear)); |
||||||
|
for (int y = 0; y < inbuf.rows; y++) |
||||||
|
{ |
||||||
|
const float * inp = inbuf.ptr<float>(y); |
||||||
|
float * outp = outbuf.ptr<float>(y); |
||||||
|
uchar * validPtr = validMask.ptr<uchar>(y); |
||||||
|
for (int x = 0; x < inbuf.cols; x++) |
||||||
|
{ |
||||||
|
float z = inp[x]; |
||||||
|
uchar m = (z >= fNear) && (z <= fFar); |
||||||
|
z = std::max(std::min(z, fFar), fNear); |
||||||
|
// precision-optimized version of this:
|
||||||
|
// outp[x] = (z - zNear) / z * zFar / (zFar - zNear);
|
||||||
|
outp[x] = zadd + zmul / z; |
||||||
|
validPtr[x] = m; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static void triangleRasterizeInternal(InputArray _vertices, InputArray _indices, InputArray _colors, |
||||||
|
Mat& colorBuf, Mat& depthBuf, |
||||||
|
InputArray world2cam, double fovyRadians, double zNear, double zFar, |
||||||
|
const TriangleRasterizeSettings& settings) |
||||||
|
{ |
||||||
|
CV_Assert(world2cam.type() == CV_32FC1 || world2cam.type() == CV_64FC1); |
||||||
|
CV_Assert((world2cam.size() == Size {4, 3}) || (world2cam.size() == Size {4, 4})); |
||||||
|
|
||||||
|
CV_Assert((fovyRadians > 0) && (fovyRadians < CV_PI)); |
||||||
|
CV_Assert(zNear > 0); |
||||||
|
CV_Assert(zFar > zNear); |
||||||
|
|
||||||
|
Mat cpMat; |
||||||
|
world2cam.getMat().convertTo(cpMat, CV_64FC1); |
||||||
|
Matx44d camPoseMat = Matx44d::eye(); |
||||||
|
for (int i = 0; i < 3; i++) |
||||||
|
{ |
||||||
|
for (int j = 0; j < 4; j++) |
||||||
|
{ |
||||||
|
camPoseMat(i, j) = cpMat.at<double>(i, j); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(_indices.empty()) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
CV_CheckFalse(_vertices.empty(), "No vertices provided along with indices array"); |
||||||
|
|
||||||
|
Mat vertices, colors, triangles; |
||||||
|
int nVerts = 0, nColors = 0, nTriangles = 0; |
||||||
|
|
||||||
|
int vertexType = _vertices.type(); |
||||||
|
CV_Assert(vertexType == CV_32FC1 || vertexType == CV_32FC3); |
||||||
|
vertices = _vertices.getMat(); |
||||||
|
// transform 3xN matrix to Nx3, except 3x3
|
||||||
|
if ((_vertices.channels() == 1) && (_vertices.rows() == 3) && (_vertices.cols() != 3)) |
||||||
|
{ |
||||||
|
vertices = vertices.t(); |
||||||
|
} |
||||||
|
// This transposition is performed on 1xN matrix so it's almost free in terms of performance
|
||||||
|
vertices = vertices.reshape(3, 1).t(); |
||||||
|
nVerts = (int)vertices.total(); |
||||||
|
|
||||||
|
int indexType = _indices.type(); |
||||||
|
CV_Assert(indexType == CV_32SC1 || indexType == CV_32SC3); |
||||||
|
triangles = _indices.getMat(); |
||||||
|
// transform 3xN matrix to Nx3, except 3x3
|
||||||
|
if ((_indices.channels() == 1) && (_indices.rows() == 3) && (_indices.cols() != 3)) |
||||||
|
{ |
||||||
|
triangles = triangles.t(); |
||||||
|
} |
||||||
|
// This transposition is performed on 1xN matrix so it's almost free in terms of performance
|
||||||
|
triangles = triangles.reshape(3, 1).t(); |
||||||
|
nTriangles = (int)triangles.total(); |
||||||
|
|
||||||
|
if (!_colors.empty()) |
||||||
|
{ |
||||||
|
int colorType = _colors.type(); |
||||||
|
CV_Assert(colorType == CV_32FC1 || colorType == CV_32FC3); |
||||||
|
colors = _colors.getMat(); |
||||||
|
// transform 3xN matrix to Nx3, except 3x3
|
||||||
|
if ((_colors.channels() == 1) && (_colors.rows() == 3) && (_colors.cols() != 3)) |
||||||
|
{ |
||||||
|
colors = colors.t(); |
||||||
|
} |
||||||
|
colors = colors.reshape(3, 1).t(); |
||||||
|
nColors = (int)colors.total(); |
||||||
|
|
||||||
|
CV_Assert(nColors == nVerts); |
||||||
|
} |
||||||
|
|
||||||
|
// any of buffers can be empty
|
||||||
|
Size imgSize {std::max(colorBuf.cols, depthBuf.cols), std::max(colorBuf.rows, depthBuf.rows)}; |
||||||
|
|
||||||
|
// world-to-camera coord system
|
||||||
|
Matx44d lookAtMatrix = camPoseMat; |
||||||
|
|
||||||
|
double ys = 1.0 / std::tan(fovyRadians / 2); |
||||||
|
double xs = ys / (double)imgSize.width * (double)imgSize.height; |
||||||
|
double zz = (zNear + zFar) / (zNear - zFar); |
||||||
|
double zw = 2.0 * zFar * zNear / (zNear - zFar); |
||||||
|
|
||||||
|
// camera to NDC: [-1, 1]^3
|
||||||
|
Matx44d perspectMatrix (xs, 0, 0, 0, |
||||||
|
0, ys, 0, 0, |
||||||
|
0, 0, zz, zw, |
||||||
|
0, 0, -1, 0); |
||||||
|
|
||||||
|
Matx44f mvpMatrix = perspectMatrix * lookAtMatrix; |
||||||
|
|
||||||
|
// vertex transform stage
|
||||||
|
|
||||||
|
Mat screenVertices(vertices.size(), CV_32FC4); |
||||||
|
for (int i = 0; i < nVerts; i++) |
||||||
|
{ |
||||||
|
Vec3f vglobal = vertices.at<Vec3f>(i); |
||||||
|
|
||||||
|
Vec4f vndc = mvpMatrix * Vec4f(vglobal[0], vglobal[1], vglobal[2], 1.f); |
||||||
|
|
||||||
|
float invw = 1.f / vndc[3]; |
||||||
|
Vec4f vdiv = {vndc[0] * invw, vndc[1] * invw, vndc[2] * invw, invw}; |
||||||
|
|
||||||
|
// [-1, 1]^3 => [0, width] x [0, height] x [0, 1]
|
||||||
|
Vec4f vscreen = { |
||||||
|
(vdiv[0] + 1.f) * 0.5f * (float)imgSize.width, |
||||||
|
(vdiv[1] + 1.f) * 0.5f * (float)imgSize.height, |
||||||
|
(vdiv[2] + 1.f) * 0.5f, |
||||||
|
vdiv[3] |
||||||
|
}; |
||||||
|
|
||||||
|
screenVertices.at<Vec4f>(i) = vscreen; |
||||||
|
} |
||||||
|
|
||||||
|
// draw stage
|
||||||
|
|
||||||
|
for (int t = 0; t < nTriangles; t++) |
||||||
|
{ |
||||||
|
Vec3i tri = triangles.at<Vec3i>(t); |
||||||
|
|
||||||
|
Vec3f col[3]; |
||||||
|
Vec4f ver[3]; |
||||||
|
for (int i = 0; i < 3; i++) |
||||||
|
{ |
||||||
|
int idx = tri[i]; |
||||||
|
CV_DbgAssert(idx >= 0 && idx < nVerts); |
||||||
|
|
||||||
|
col[i] = colors.empty() ? Vec3f::all(0) : colors.at<Vec3f>(idx); |
||||||
|
ver[i] = screenVertices.at<Vec4f>(idx); |
||||||
|
} |
||||||
|
|
||||||
|
drawTriangle(ver, col, depthBuf, colorBuf, settings); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void triangleRasterizeDepth(InputArray _vertices, InputArray _indices, InputOutputArray _depthBuf, |
||||||
|
InputArray world2cam, double fovY, double zNear, double zFar, |
||||||
|
const TriangleRasterizeSettings& settings) |
||||||
|
{ |
||||||
|
CV_Assert(!_depthBuf.empty()); |
||||||
|
CV_Assert(_depthBuf.type() == CV_32FC1); |
||||||
|
|
||||||
|
Mat emptyColorBuf; |
||||||
|
// out-of-range values from user-provided depthBuf should not be altered, let's mark them
|
||||||
|
Mat_<uchar> validMask; |
||||||
|
Mat depthBuf; |
||||||
|
if (settings.glCompatibleMode == RASTERIZE_COMPAT_INVDEPTH) |
||||||
|
{ |
||||||
|
depthBuf = _depthBuf.getMat(); |
||||||
|
} |
||||||
|
else // RASTERIZE_COMPAT_DISABLED
|
||||||
|
{ |
||||||
|
invertDepth(_depthBuf.getMat(), depthBuf, validMask, zNear, zFar); |
||||||
|
} |
||||||
|
|
||||||
|
triangleRasterizeInternal(_vertices, _indices, noArray(), emptyColorBuf, depthBuf, world2cam, fovY, zNear, zFar, settings); |
||||||
|
|
||||||
|
if (settings.glCompatibleMode == RASTERIZE_COMPAT_DISABLED) |
||||||
|
{ |
||||||
|
linearizeDepth(depthBuf, validMask, _depthBuf.getMat(), zFar, zNear); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void triangleRasterizeColor(InputArray _vertices, InputArray _indices, InputArray _colors, InputOutputArray _colorBuf, |
||||||
|
InputArray world2cam, double fovY, double zNear, double zFar, |
||||||
|
const TriangleRasterizeSettings& settings) |
||||||
|
{ |
||||||
|
CV_Assert(!_colorBuf.empty()); |
||||||
|
CV_Assert(_colorBuf.type() == CV_32FC3); |
||||||
|
Mat colorBuf = _colorBuf.getMat(); |
||||||
|
|
||||||
|
Mat depthBuf; |
||||||
|
if (_colors.empty()) |
||||||
|
{ |
||||||
|
// full white shading does not require depth test
|
||||||
|
CV_Assert(settings.shadingType == RASTERIZE_SHADING_WHITE); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// internal depth buffer is not exposed outside
|
||||||
|
depthBuf.create(_colorBuf.size(), CV_32FC1); |
||||||
|
depthBuf.setTo(1.0); |
||||||
|
} |
||||||
|
|
||||||
|
triangleRasterizeInternal(_vertices, _indices, _colors, colorBuf, depthBuf, world2cam, fovY, zNear, zFar, settings); |
||||||
|
} |
||||||
|
|
||||||
|
void triangleRasterize(InputArray _vertices, InputArray _indices, InputArray _colors, |
||||||
|
InputOutputArray _colorBuffer, InputOutputArray _depthBuffer, |
||||||
|
InputArray world2cam, double fovyRadians, double zNear, double zFar, |
||||||
|
const TriangleRasterizeSettings& settings) |
||||||
|
{ |
||||||
|
if (_colors.empty()) |
||||||
|
{ |
||||||
|
CV_Assert(settings.shadingType == RASTERIZE_SHADING_WHITE); |
||||||
|
} |
||||||
|
|
||||||
|
CV_Assert(!_colorBuffer.empty()); |
||||||
|
CV_Assert(_colorBuffer.type() == CV_32FC3); |
||||||
|
CV_Assert(!_depthBuffer.empty()); |
||||||
|
CV_Assert(_depthBuffer.type() == CV_32FC1); |
||||||
|
|
||||||
|
CV_Assert(_depthBuffer.size() == _colorBuffer.size()); |
||||||
|
|
||||||
|
Mat colorBuf = _colorBuffer.getMat(); |
||||||
|
|
||||||
|
// out-of-range values from user-provided depthBuf should not be altered, let's mark them
|
||||||
|
Mat_<uchar> validMask; |
||||||
|
Mat depthBuf; |
||||||
|
if (settings.glCompatibleMode == RASTERIZE_COMPAT_INVDEPTH) |
||||||
|
{ |
||||||
|
depthBuf = _depthBuffer.getMat(); |
||||||
|
} |
||||||
|
else // RASTERIZE_COMPAT_DISABLED
|
||||||
|
{ |
||||||
|
invertDepth(_depthBuffer.getMat(), depthBuf, validMask, zNear, zFar); |
||||||
|
} |
||||||
|
|
||||||
|
triangleRasterizeInternal(_vertices, _indices, _colors, colorBuf, depthBuf, world2cam, fovyRadians, zNear, zFar, settings); |
||||||
|
|
||||||
|
if (settings.glCompatibleMode == RASTERIZE_COMPAT_DISABLED) |
||||||
|
{ |
||||||
|
linearizeDepth(depthBuf, validMask, _depthBuffer.getMat(), zFar, zNear); |
||||||
|
} |
||||||
|
} |
||||||
|
} // namespace cv
|
@ -0,0 +1,948 @@ |
|||||||
|
// This file is part of OpenCV project.
|
||||||
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||||
|
// of this distribution and at http://opencv.org/license.html
|
||||||
|
|
||||||
|
#include "test_precomp.hpp" |
||||||
|
|
||||||
|
namespace opencv_test { namespace { |
||||||
|
using namespace cv; |
||||||
|
|
||||||
|
// that was easier than using CV_ENUM() macro
|
||||||
|
namespace |
||||||
|
{ |
||||||
|
using namespace cv; |
||||||
|
struct CullingModeEnum |
||||||
|
{ |
||||||
|
static const std::array<TriangleCullingMode, 3> vals; |
||||||
|
static const std::array<std::string, 3> svals; |
||||||
|
|
||||||
|
CullingModeEnum(TriangleCullingMode v = RASTERIZE_CULLING_NONE) : val(v) {} |
||||||
|
operator TriangleCullingMode() const { return val; } |
||||||
|
void PrintTo(std::ostream *os) const |
||||||
|
{ |
||||||
|
int v = int(val); |
||||||
|
if (v >= 0 && v < (int)vals.size()) |
||||||
|
{ |
||||||
|
*os << svals[v]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
*os << "UNKNOWN"; |
||||||
|
} |
||||||
|
} |
||||||
|
static ::testing::internal::ParamGenerator<CullingModeEnum> all() |
||||||
|
{ |
||||||
|
return ::testing::Values(CullingModeEnum(vals[0]), |
||||||
|
CullingModeEnum(vals[1]), |
||||||
|
CullingModeEnum(vals[2])); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
TriangleCullingMode val; |
||||||
|
}; |
||||||
|
|
||||||
|
const std::array<TriangleCullingMode, 3> CullingModeEnum::vals |
||||||
|
{ |
||||||
|
RASTERIZE_CULLING_NONE, |
||||||
|
RASTERIZE_CULLING_CW, |
||||||
|
RASTERIZE_CULLING_CCW |
||||||
|
}; |
||||||
|
const std::array<std::string, 3> CullingModeEnum::svals |
||||||
|
{ |
||||||
|
std::string("None"), |
||||||
|
std::string("CW"), |
||||||
|
std::string("CCW") |
||||||
|
}; |
||||||
|
|
||||||
|
static inline void PrintTo(const CullingModeEnum &t, std::ostream *os) { t.PrintTo(os); } |
||||||
|
} |
||||||
|
|
||||||
|
// that was easier than using CV_ENUM() macro
|
||||||
|
namespace |
||||||
|
{ |
||||||
|
using namespace cv; |
||||||
|
struct ShadingTypeEnum |
||||||
|
{ |
||||||
|
static const std::array<TriangleShadingType, 3> vals; |
||||||
|
static const std::array<std::string, 3> svals; |
||||||
|
|
||||||
|
ShadingTypeEnum(TriangleShadingType v = RASTERIZE_SHADING_WHITE) : val(v) {} |
||||||
|
operator TriangleShadingType() const { return val; } |
||||||
|
void PrintTo(std::ostream *os) const |
||||||
|
{ |
||||||
|
int v = int(val); |
||||||
|
if (v >= 0 && v < (int)vals.size()) |
||||||
|
{ |
||||||
|
*os << svals[v]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
*os << "UNKNOWN"; |
||||||
|
} |
||||||
|
} |
||||||
|
static ::testing::internal::ParamGenerator<ShadingTypeEnum> all() |
||||||
|
{ |
||||||
|
return ::testing::Values(ShadingTypeEnum(vals[0]), |
||||||
|
ShadingTypeEnum(vals[1]), |
||||||
|
ShadingTypeEnum(vals[2])); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
TriangleShadingType val; |
||||||
|
}; |
||||||
|
|
||||||
|
const std::array<TriangleShadingType, 3> ShadingTypeEnum::vals |
||||||
|
{ |
||||||
|
RASTERIZE_SHADING_WHITE, |
||||||
|
RASTERIZE_SHADING_FLAT, |
||||||
|
RASTERIZE_SHADING_SHADED |
||||||
|
}; |
||||||
|
const std::array<std::string, 3> ShadingTypeEnum::svals |
||||||
|
{ |
||||||
|
std::string("White"), |
||||||
|
std::string("Flat"), |
||||||
|
std::string("Shaded") |
||||||
|
}; |
||||||
|
|
||||||
|
static inline void PrintTo(const ShadingTypeEnum &t, std::ostream *os) { t.PrintTo(os); } |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
enum class ModelType |
||||||
|
{ |
||||||
|
Empty = 0, |
||||||
|
File = 1, |
||||||
|
Clipping = 2, |
||||||
|
Color = 3, |
||||||
|
Centered = 4 |
||||||
|
}; |
||||||
|
|
||||||
|
// that was easier than using CV_ENUM() macro
|
||||||
|
namespace |
||||||
|
{ |
||||||
|
using namespace cv; |
||||||
|
struct ModelTypeEnum |
||||||
|
{ |
||||||
|
static const std::array<ModelType, 5> vals; |
||||||
|
static const std::array<std::string, 5> svals; |
||||||
|
|
||||||
|
ModelTypeEnum(ModelType v = ModelType::Empty) : val(v) {} |
||||||
|
operator ModelType() const { return val; } |
||||||
|
void PrintTo(std::ostream *os) const |
||||||
|
{ |
||||||
|
int v = int(val); |
||||||
|
if (v >= 0 && v < (int)vals.size()) |
||||||
|
{ |
||||||
|
*os << svals[v]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
*os << "UNKNOWN"; |
||||||
|
} |
||||||
|
} |
||||||
|
static ::testing::internal::ParamGenerator<ModelTypeEnum> all() |
||||||
|
{ |
||||||
|
return ::testing::Values(ModelTypeEnum(vals[0]), |
||||||
|
ModelTypeEnum(vals[1]), |
||||||
|
ModelTypeEnum(vals[2]), |
||||||
|
ModelTypeEnum(vals[3]), |
||||||
|
ModelTypeEnum(vals[4])); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
ModelType val; |
||||||
|
}; |
||||||
|
|
||||||
|
const std::array<ModelType, 5> ModelTypeEnum::vals |
||||||
|
{ |
||||||
|
ModelType::Empty, |
||||||
|
ModelType::File, |
||||||
|
ModelType::Clipping, |
||||||
|
ModelType::Color, |
||||||
|
ModelType::Centered |
||||||
|
}; |
||||||
|
const std::array<std::string, 5> ModelTypeEnum::svals |
||||||
|
{ |
||||||
|
std::string("Empty"), |
||||||
|
std::string("File"), |
||||||
|
std::string("Clipping"), |
||||||
|
std::string("Color"), |
||||||
|
std::string("Centered") |
||||||
|
}; |
||||||
|
|
||||||
|
static inline void PrintTo(const ModelTypeEnum &t, std::ostream *os) { t.PrintTo(os); } |
||||||
|
} |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
std::string printEnum(T v) |
||||||
|
{ |
||||||
|
std::ostringstream ss; |
||||||
|
v.PrintTo(&ss); |
||||||
|
return ss.str(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static Matx44d lookAtMatrixCal(const Vec3d& position, const Vec3d& lookat, const Vec3d& upVector) |
||||||
|
{ |
||||||
|
Vec3d w = cv::normalize(position - lookat); |
||||||
|
Vec3d u = cv::normalize(upVector.cross(w)); |
||||||
|
|
||||||
|
Vec3d v = w.cross(u); |
||||||
|
|
||||||
|
Matx44d res(u[0], u[1], u[2], 0, |
||||||
|
v[0], v[1], v[2], 0, |
||||||
|
w[0], w[1], w[2], 0, |
||||||
|
0, 0, 0, 1.0); |
||||||
|
|
||||||
|
Matx44d translate(1.0, 0, 0, -position[0], |
||||||
|
0, 1.0, 0, -position[1], |
||||||
|
0, 0, 1.0, -position[2], |
||||||
|
0, 0, 0, 1.0); |
||||||
|
res = res * translate; |
||||||
|
|
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static void generateNormals(const std::vector<Vec3f>& points, const std::vector<std::vector<int>>& indices, |
||||||
|
std::vector<Vec3f>& normals) |
||||||
|
{ |
||||||
|
std::vector<std::vector<Vec3f>> preNormals(points.size(), std::vector<Vec3f>()); |
||||||
|
|
||||||
|
for (const auto& tri : indices) |
||||||
|
{ |
||||||
|
Vec3f p0 = points[tri[0]]; |
||||||
|
Vec3f p1 = points[tri[1]]; |
||||||
|
Vec3f p2 = points[tri[2]]; |
||||||
|
|
||||||
|
Vec3f cross = cv::normalize((p1 - p0).cross(p2 - p0)); |
||||||
|
for (int i = 0; i < 3; i++) |
||||||
|
{ |
||||||
|
preNormals[tri[i]].push_back(cross); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
normals.reserve(points.size()); |
||||||
|
for (const auto& pn : preNormals) |
||||||
|
{ |
||||||
|
Vec3f sum { }; |
||||||
|
for (const auto& n : pn) |
||||||
|
{ |
||||||
|
sum += n; |
||||||
|
} |
||||||
|
normals.push_back(cv::normalize(sum)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// load model once and keep it in static memory
|
||||||
|
static void getModelOnce(const std::string& objectPath, std::vector<Vec3f>& vertices, |
||||||
|
std::vector<Vec3i>& indices, std::vector<Vec3f>& colors) |
||||||
|
{ |
||||||
|
static bool load = false; |
||||||
|
static std::vector<Vec3f> vert, col; |
||||||
|
static std::vector<Vec3i> ind; |
||||||
|
|
||||||
|
if (!load) |
||||||
|
{ |
||||||
|
std::vector<vector<int>> indvec; |
||||||
|
// using per-vertex normals as colors
|
||||||
|
loadMesh(objectPath, vert, indvec); |
||||||
|
generateNormals(vert, indvec, col); |
||||||
|
|
||||||
|
for (const auto &vec : indvec) |
||||||
|
{ |
||||||
|
ind.push_back({vec[0], vec[1], vec[2]}); |
||||||
|
} |
||||||
|
|
||||||
|
for (auto &color : col) |
||||||
|
{ |
||||||
|
color = Vec3f(abs(color[0]), abs(color[1]), abs(color[2])); |
||||||
|
} |
||||||
|
|
||||||
|
load = true; |
||||||
|
} |
||||||
|
|
||||||
|
vertices = vert; |
||||||
|
colors = col; |
||||||
|
indices = ind; |
||||||
|
} |
||||||
|
|
||||||
|
class ModelData |
||||||
|
{ |
||||||
|
public: |
||||||
|
ModelData(ModelType type = ModelType::Empty) |
||||||
|
{ |
||||||
|
switch (type) |
||||||
|
{ |
||||||
|
case ModelType::Empty: |
||||||
|
{ |
||||||
|
position = Vec3d(0.0, 0.0, 0.0); |
||||||
|
lookat = Vec3d(0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d(0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 45.0; |
||||||
|
|
||||||
|
vertices = std::vector<Vec3f>(4, {2.0f, 0, -2.0f}); |
||||||
|
colors = std::vector<Vec3f>(4, {0, 0, 1.0f}); |
||||||
|
indices = { }; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::File: |
||||||
|
{ |
||||||
|
string objectPath = findDataFile("viz/dragon.ply"); |
||||||
|
|
||||||
|
position = Vec3d( 1.9, 0.4, 1.3); |
||||||
|
lookat = Vec3d( 0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d( 0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 45.0; |
||||||
|
|
||||||
|
getModelOnce(objectPath, vertices, indices, colors); |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::Clipping: |
||||||
|
{ |
||||||
|
position = Vec3d(0.0, 0.0, 5.0); |
||||||
|
lookat = Vec3d(0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d(0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 45.0; |
||||||
|
|
||||||
|
vertices = |
||||||
|
{ |
||||||
|
{ 2.0, 0.0, -2.0}, { 0.0, -6.0, -2.0}, {-2.0, 0.0, -2.0}, |
||||||
|
{ 3.5, -1.0, -5.0}, { 2.5, -2.5, -5.0}, {-1.0, 1.0, -5.0}, |
||||||
|
{-6.5, -1.0, -3.0}, {-2.5, -2.0, -3.0}, { 1.0, 1.0, -5.0}, |
||||||
|
}; |
||||||
|
|
||||||
|
indices = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8} }; |
||||||
|
|
||||||
|
Vec3f col1(217.0, 238.0, 185.0); |
||||||
|
Vec3f col2(185.0, 217.0, 238.0); |
||||||
|
Vec3f col3(150.0, 10.0, 238.0); |
||||||
|
|
||||||
|
col1 *= (1.f / 255.f); |
||||||
|
col2 *= (1.f / 255.f); |
||||||
|
col3 *= (1.f / 255.f); |
||||||
|
|
||||||
|
colors = |
||||||
|
{ |
||||||
|
col1, col2, col3, |
||||||
|
col2, col3, col1, |
||||||
|
col3, col1, col2, |
||||||
|
}; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::Centered: |
||||||
|
{ |
||||||
|
position = Vec3d(0.0, 0.0, 5.0); |
||||||
|
lookat = Vec3d(0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d(0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 45.0; |
||||||
|
|
||||||
|
vertices = |
||||||
|
{ |
||||||
|
{ 2.0, 0.0, -2.0}, { 0.0, -2.0, -2.0}, {-2.0, 0.0, -2.0}, |
||||||
|
{ 3.5, -1.0, -5.0}, { 2.5, -1.5, -5.0}, {-1.0, 0.5, -5.0}, |
||||||
|
}; |
||||||
|
|
||||||
|
indices = { {0, 1, 2}, {3, 4, 5} }; |
||||||
|
|
||||||
|
Vec3f col1(217.0, 238.0, 185.0); |
||||||
|
Vec3f col2(185.0, 217.0, 238.0); |
||||||
|
|
||||||
|
col1 *= (1.f / 255.f); |
||||||
|
col2 *= (1.f / 255.f); |
||||||
|
|
||||||
|
colors = |
||||||
|
{ |
||||||
|
col1, col2, col1, |
||||||
|
col2, col1, col2, |
||||||
|
}; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::Color: |
||||||
|
{ |
||||||
|
position = Vec3d(0.0, 0.0, 5.0); |
||||||
|
lookat = Vec3d(0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d(0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 60.0; |
||||||
|
|
||||||
|
vertices = |
||||||
|
{ |
||||||
|
{ 2.0, 0.0, -2.0}, |
||||||
|
{ 0.0, 2.0, -3.0}, |
||||||
|
{-2.0, 0.0, -2.0}, |
||||||
|
{ 0.0, -2.0, 1.0}, |
||||||
|
}; |
||||||
|
|
||||||
|
indices = { {0, 1, 2}, {0, 2, 3} }; |
||||||
|
|
||||||
|
colors = |
||||||
|
{ |
||||||
|
{ 0.0f, 0.0f, 1.0f}, |
||||||
|
{ 0.0f, 1.0f, 0.0f}, |
||||||
|
{ 1.0f, 0.0f, 0.0f}, |
||||||
|
{ 0.0f, 1.0f, 0.0f}, |
||||||
|
}; |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
CV_Error(Error::StsBadArg, "Unknown model type"); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Vec3d position; |
||||||
|
Vec3d lookat; |
||||||
|
Vec3d upVector; |
||||||
|
|
||||||
|
double fovy; |
||||||
|
|
||||||
|
std::vector<Vec3f> vertices; |
||||||
|
std::vector<Vec3i> indices; |
||||||
|
std::vector<Vec3f> colors; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
void compareDepth(const cv::Mat& gt, const cv::Mat& mat, cv::Mat& diff, double zFar, double scale, |
||||||
|
double maskThreshold, double normInfThreshold, double normL2Threshold) |
||||||
|
{ |
||||||
|
ASSERT_EQ(CV_16UC1, gt.type()); |
||||||
|
ASSERT_EQ(CV_16UC1, mat.type()); |
||||||
|
ASSERT_EQ(gt.size(), mat.size()); |
||||||
|
|
||||||
|
Mat gtMask = gt < zFar*scale; |
||||||
|
Mat matMask = mat < zFar*scale; |
||||||
|
|
||||||
|
Mat diffMask = gtMask != matMask; |
||||||
|
int nzDepthDiff = cv::countNonZero(diffMask); |
||||||
|
EXPECT_LE(nzDepthDiff, maskThreshold); |
||||||
|
|
||||||
|
Mat jointMask = gtMask & matMask; |
||||||
|
int nzJointMask = cv::countNonZero(jointMask); |
||||||
|
double normInfDepth = cv::norm(gt, mat, cv::NORM_INF, jointMask); |
||||||
|
EXPECT_LE(normInfDepth, normInfThreshold); |
||||||
|
double normL2Depth = nzJointMask ? (cv::norm(gt, mat, cv::NORM_L2, jointMask) / nzJointMask) : 0; |
||||||
|
EXPECT_LE(normL2Depth, normL2Threshold); |
||||||
|
|
||||||
|
// add --test_debug to output differences
|
||||||
|
if (debugLevel > 0) |
||||||
|
{ |
||||||
|
std::cout << "nzDepthDiff: " << nzDepthDiff << " vs " << maskThreshold << std::endl; |
||||||
|
std::cout << "normInfDepth: " << normInfDepth << " vs " << normInfThreshold << std::endl; |
||||||
|
std::cout << "normL2Depth: " << normL2Depth << " vs " << normL2Threshold << std::endl; |
||||||
|
} |
||||||
|
|
||||||
|
diff = (gt - mat) + (1 << 15); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void compareRGB(const cv::Mat& gt, const cv::Mat& mat, cv::Mat& diff, double normInfThreshold, double normL2Threshold) |
||||||
|
{ |
||||||
|
ASSERT_EQ(CV_32FC3, gt.type()); |
||||||
|
ASSERT_EQ(CV_32FC3, mat.type()); |
||||||
|
ASSERT_EQ(gt.size(), mat.size()); |
||||||
|
|
||||||
|
double normInfRgb = cv::norm(gt, mat, cv::NORM_INF); |
||||||
|
EXPECT_LE(normInfRgb, normInfThreshold); |
||||||
|
double normL2Rgb = cv::norm(gt, mat, cv::NORM_L2) / gt.total(); |
||||||
|
EXPECT_LE(normL2Rgb, normL2Threshold); |
||||||
|
// add --test_debug to output differences
|
||||||
|
if (debugLevel > 0) |
||||||
|
{ |
||||||
|
std::cout << "normInfRgb: " << normInfRgb << " vs " << normInfThreshold << std::endl; |
||||||
|
std::cout << "normL2Rgb: " << normL2Rgb << " vs " << normL2Threshold << std::endl; |
||||||
|
} |
||||||
|
diff = (gt - mat) * 0.5 + 0.5; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
struct RenderTestThresholds |
||||||
|
{ |
||||||
|
RenderTestThresholds( |
||||||
|
double _rgbInfThreshold, |
||||||
|
double _rgbL2Threshold, |
||||||
|
double _depthMaskThreshold, |
||||||
|
double _depthInfThreshold, |
||||||
|
double _depthL2Threshold) : |
||||||
|
rgbInfThreshold(_rgbInfThreshold), |
||||||
|
rgbL2Threshold(_rgbL2Threshold), |
||||||
|
depthMaskThreshold(_depthMaskThreshold), |
||||||
|
depthInfThreshold(_depthInfThreshold), |
||||||
|
depthL2Threshold(_depthL2Threshold) |
||||||
|
{ } |
||||||
|
|
||||||
|
double rgbInfThreshold; |
||||||
|
double rgbL2Threshold; |
||||||
|
double depthMaskThreshold; |
||||||
|
double depthInfThreshold; |
||||||
|
double depthL2Threshold; |
||||||
|
}; |
||||||
|
|
||||||
|
// resolution, shading type, culling mode, model type, float type, index type
|
||||||
|
typedef std::tuple<std::tuple<int, int>, ShadingTypeEnum, CullingModeEnum, ModelTypeEnum, MatDepth, MatDepth> RenderTestParamType; |
||||||
|
|
||||||
|
class RenderingTest : public ::testing::TestWithParam<RenderTestParamType> |
||||||
|
{ |
||||||
|
protected: |
||||||
|
void SetUp() override |
||||||
|
{ |
||||||
|
params = GetParam(); |
||||||
|
auto wh = std::get<0>(params); |
||||||
|
width = std::get<0>(wh); |
||||||
|
height = std::get<1>(wh); |
||||||
|
shadingType = std::get<1>(params); |
||||||
|
cullingMode = std::get<2>(params); |
||||||
|
modelType = std::get<3>(params); |
||||||
|
modelData = ModelData(modelType); |
||||||
|
ftype = std::get<4>(params); |
||||||
|
itype = std::get<5>(params); |
||||||
|
|
||||||
|
zNear = 0.1, zFar = 50.0; |
||||||
|
depthScale = 1000.0; |
||||||
|
|
||||||
|
depth_buf = Mat(height, width, ftype, zFar); |
||||||
|
color_buf = Mat(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0)); |
||||||
|
|
||||||
|
cameraPose = lookAtMatrixCal(modelData.position, modelData.lookat, modelData.upVector); |
||||||
|
fovYradians = modelData.fovy * (CV_PI / 180.0); |
||||||
|
|
||||||
|
verts = Mat(modelData.vertices); |
||||||
|
verts.convertTo(verts, ftype); |
||||||
|
|
||||||
|
if (shadingType != RASTERIZE_SHADING_WHITE) |
||||||
|
{ |
||||||
|
colors = Mat(modelData.colors); |
||||||
|
colors.convertTo(colors, ftype); |
||||||
|
// let vertices be in BGR format to avoid later color conversions
|
||||||
|
// cvtColor does not work with 1d Mats
|
||||||
|
std::vector<Mat> xyz; |
||||||
|
cv::split(colors, xyz); |
||||||
|
cv::merge(std::vector<Mat>{xyz[2], xyz[1], xyz[0]}, colors); |
||||||
|
} |
||||||
|
|
||||||
|
indices = Mat(modelData.indices); |
||||||
|
if (itype != CV_32S) |
||||||
|
{ |
||||||
|
indices.convertTo(indices, itype); |
||||||
|
} |
||||||
|
|
||||||
|
settings = TriangleRasterizeSettings().setCullingMode(cullingMode).setShadingType(shadingType); |
||||||
|
|
||||||
|
triangleRasterize(verts, indices, colors, color_buf, depth_buf, |
||||||
|
cameraPose, fovYradians, zNear, zFar, settings); |
||||||
|
} |
||||||
|
|
||||||
|
public: |
||||||
|
RenderTestParamType params; |
||||||
|
int width, height; |
||||||
|
double zNear, zFar, depthScale; |
||||||
|
|
||||||
|
Mat depth_buf, color_buf; |
||||||
|
|
||||||
|
Mat verts, colors, indices; |
||||||
|
Matx44d cameraPose; |
||||||
|
double fovYradians; |
||||||
|
TriangleRasterizeSettings settings; |
||||||
|
|
||||||
|
ModelData modelData; |
||||||
|
ModelTypeEnum modelType; |
||||||
|
ShadingTypeEnum shadingType; |
||||||
|
CullingModeEnum cullingMode; |
||||||
|
int ftype, itype; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
// depth-only or RGB-only rendering should produce the same result as usual rendering
|
||||||
|
TEST_P(RenderingTest, noArrays) |
||||||
|
{ |
||||||
|
Mat depthOnly(height, width, ftype, zFar); |
||||||
|
Mat colorOnly(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0)); |
||||||
|
|
||||||
|
triangleRasterizeDepth(verts, indices, depthOnly, cameraPose, fovYradians, zNear, zFar, settings); |
||||||
|
triangleRasterizeColor(verts, indices, colors, colorOnly, cameraPose, fovYradians, zNear, zFar, settings); |
||||||
|
|
||||||
|
Mat rgbDiff, depthDiff; |
||||||
|
compareRGB(color_buf, colorOnly, rgbDiff, 0, 0); |
||||||
|
depth_buf.convertTo(depth_buf, CV_16U, depthScale); |
||||||
|
depthOnly.convertTo(depthOnly, CV_16U, depthScale); |
||||||
|
compareDepth(depth_buf, depthOnly, depthDiff, zFar, depthScale, 0, 0, 0); |
||||||
|
|
||||||
|
// add --test_debug to output resulting images
|
||||||
|
if (debugLevel > 0) |
||||||
|
{ |
||||||
|
std::string modelName = printEnum(modelType); |
||||||
|
std::string shadingName = printEnum(shadingType); |
||||||
|
std::string cullingName = printEnum(cullingMode); |
||||||
|
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingName.c_str()); |
||||||
|
|
||||||
|
std::string outColorPath = "noarray_color_image_" + suffix + "_" + shadingName + ".png"; |
||||||
|
std::string outDepthPath = "noarray_depth_image_" + suffix + "_" + shadingName + ".png"; |
||||||
|
|
||||||
|
imwrite(outColorPath, color_buf * 255.f); |
||||||
|
imwrite(outDepthPath, depth_buf); |
||||||
|
imwrite("diff_" + outColorPath, rgbDiff * 255.f); |
||||||
|
imwrite("diff_" + outDepthPath, depthDiff); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// passing the same parameters in float should give the same result
|
||||||
|
TEST_P(RenderingTest, floatParams) |
||||||
|
{ |
||||||
|
Mat depth_buf2(height, width, ftype, zFar); |
||||||
|
Mat color_buf2(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0)); |
||||||
|
|
||||||
|
// cameraPose can also be float, checking it
|
||||||
|
triangleRasterize(verts, indices, colors, color_buf2, depth_buf2, |
||||||
|
Matx44f(cameraPose), (float)fovYradians, (float)zNear, (float)zFar, settings); |
||||||
|
|
||||||
|
RenderTestThresholds thr(0, 0, 0, 0, 0); |
||||||
|
switch (modelType) |
||||||
|
{ |
||||||
|
case ModelType::Empty: break; |
||||||
|
case ModelType::Color: break; |
||||||
|
case ModelType::Clipping: |
||||||
|
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CW) |
||||||
|
{ |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
thr.depthL2Threshold = 0.00127; |
||||||
|
} |
||||||
|
else if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_NONE) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 3e-7; |
||||||
|
thr.rgbL2Threshold = 1.86e-10; |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
thr.depthL2Threshold = 0.000406; |
||||||
|
} |
||||||
|
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 2.39e-07; |
||||||
|
thr.rgbL2Threshold = 1.86e-10; |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
thr.depthL2Threshold = 0.0016; |
||||||
|
} |
||||||
|
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.934; |
||||||
|
thr.rgbL2Threshold = 0.000102; |
||||||
|
thr.depthMaskThreshold = 21; |
||||||
|
} |
||||||
|
else if (width == 640 && height == 480 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE) |
||||||
|
{ |
||||||
|
thr.rgbL2Threshold = 1; |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
thr.depthL2Threshold = 0.000248; |
||||||
|
} |
||||||
|
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.934; |
||||||
|
thr.rgbL2Threshold = 3.18e-5; |
||||||
|
thr.depthMaskThreshold = 114; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::File: |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CCW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.000157; |
||||||
|
thr.rgbL2Threshold = 6e-09; |
||||||
|
thr.depthL2Threshold = 0.000413; |
||||||
|
} |
||||||
|
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.000303; |
||||||
|
thr.rgbL2Threshold = 1.9e-09; |
||||||
|
thr.depthL2Threshold = 0.00012; |
||||||
|
} |
||||||
|
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE) |
||||||
|
{ |
||||||
|
thr.depthL2Threshold = 0.00012; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::Centered: |
||||||
|
if (shadingType == RASTERIZE_SHADING_SHADED && cullingMode != RASTERIZE_CULLING_CW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 3.58e-07; |
||||||
|
thr.rgbL2Threshold = 1.51e-10; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
Mat rgbDiff, depthDiff; |
||||||
|
compareRGB(color_buf, color_buf2, rgbDiff, thr.rgbInfThreshold, thr.rgbL2Threshold); |
||||||
|
depth_buf.convertTo(depth_buf, CV_16U, depthScale); |
||||||
|
depth_buf2.convertTo(depth_buf2, CV_16U, depthScale); |
||||||
|
compareDepth(depth_buf, depth_buf2, depthDiff, zFar, depthScale, thr.depthMaskThreshold, thr.depthInfThreshold, thr.depthL2Threshold); |
||||||
|
|
||||||
|
// add --test_debug to output resulting images
|
||||||
|
if (debugLevel > 0) |
||||||
|
{ |
||||||
|
std::string modelName = printEnum(modelType); |
||||||
|
std::string shadingName = printEnum(shadingType); |
||||||
|
std::string cullingName = printEnum(cullingMode); |
||||||
|
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingName.c_str()); |
||||||
|
|
||||||
|
std::string outColorPath = "float_color_image_" + suffix + "_" + shadingName + ".png"; |
||||||
|
std::string outDepthPath = "float_depth_image_" + suffix + "_" + shadingName + ".png"; |
||||||
|
|
||||||
|
imwrite(outColorPath, color_buf * 255.f); |
||||||
|
imwrite(outDepthPath, depth_buf); |
||||||
|
imwrite("diff_" + outColorPath, rgbDiff * 255.f); |
||||||
|
imwrite("diff_" + outDepthPath, depthDiff); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// some culling options produce the same pictures, let's join them
|
||||||
|
TriangleCullingMode findSameCulling(ModelType modelType, TriangleShadingType shadingType, TriangleCullingMode cullingMode, bool forRgb) |
||||||
|
{ |
||||||
|
TriangleCullingMode sameCullingMode = cullingMode; |
||||||
|
|
||||||
|
if ((modelType == ModelType::Centered && cullingMode == RASTERIZE_CULLING_CCW) || |
||||||
|
(modelType == ModelType::Color && cullingMode == RASTERIZE_CULLING_CW) || |
||||||
|
(modelType == ModelType::File && shadingType == RASTERIZE_SHADING_WHITE && forRgb) || |
||||||
|
(modelType == ModelType::File && cullingMode == RASTERIZE_CULLING_CW)) |
||||||
|
{ |
||||||
|
sameCullingMode = RASTERIZE_CULLING_NONE; |
||||||
|
} |
||||||
|
|
||||||
|
return sameCullingMode; |
||||||
|
} |
||||||
|
|
||||||
|
// compare rendering results to the ones produced by samples/opengl/opengl_testdata_generator app
|
||||||
|
TEST_P(RenderingTest, accuracy) |
||||||
|
{ |
||||||
|
depth_buf.convertTo(depth_buf, CV_16U, depthScale); |
||||||
|
|
||||||
|
if (modelType == ModelType::Empty || |
||||||
|
(modelType == ModelType::Centered && cullingMode == RASTERIZE_CULLING_CW) || |
||||||
|
(modelType == ModelType::Color && cullingMode == RASTERIZE_CULLING_CCW)) |
||||||
|
{ |
||||||
|
// empty image case
|
||||||
|
EXPECT_EQ(0, cv::norm(color_buf, NORM_INF)); |
||||||
|
|
||||||
|
Mat depthDiff; |
||||||
|
absdiff(depth_buf, Scalar(zFar * depthScale), depthDiff); |
||||||
|
EXPECT_EQ(0, cv::norm(depthDiff, cv::NORM_INF)); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
RenderTestThresholds thr(0, 0, 0, 0, 0); |
||||||
|
switch (modelType) |
||||||
|
{ |
||||||
|
case ModelType::Centered: |
||||||
|
if (shadingType == RASTERIZE_SHADING_SHADED) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.00218; |
||||||
|
thr.rgbL2Threshold = 2.85e-06; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::Clipping: |
||||||
|
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CW) |
||||||
|
{ |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
thr.depthL2Threshold = 0.0016; |
||||||
|
} |
||||||
|
else if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_NONE) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.934; |
||||||
|
thr.rgbL2Threshold = 8.03E-05; |
||||||
|
thr.depthMaskThreshold = 23; |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
thr.depthL2Threshold = 0.000544; |
||||||
|
} |
||||||
|
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.0022; |
||||||
|
thr.rgbL2Threshold = 2.54E-06; |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
thr.depthL2Threshold = 0.00175; |
||||||
|
} |
||||||
|
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.934; |
||||||
|
thr.rgbL2Threshold = 0.000102; |
||||||
|
thr.depthMaskThreshold = 21; |
||||||
|
} |
||||||
|
else if (width == 640 && height == 480 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 1; |
||||||
|
thr.rgbL2Threshold = 3.95E-05; |
||||||
|
thr.depthMaskThreshold = 49; |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
thr.depthL2Threshold = 0.000269; |
||||||
|
} |
||||||
|
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.934; |
||||||
|
thr.rgbL2Threshold = 3.27e-5; |
||||||
|
thr.depthMaskThreshold = 121; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::Color: |
||||||
|
thr.depthInfThreshold = 1; |
||||||
|
if (width == 320 && height == 240) |
||||||
|
{ |
||||||
|
thr.depthL2Threshold = 0.000989; |
||||||
|
} |
||||||
|
else if (width == 256 && height == 256) |
||||||
|
{ |
||||||
|
thr.depthL2Threshold = 0.000785; |
||||||
|
} |
||||||
|
if (shadingType == RASTERIZE_SHADING_SHADED) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.0022; |
||||||
|
thr.rgbL2Threshold = 3.13e-06; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::File: |
||||||
|
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CCW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.93; |
||||||
|
thr.rgbL2Threshold = 2.45E-05; |
||||||
|
thr.depthMaskThreshold = 2; |
||||||
|
thr.depthInfThreshold = 99; |
||||||
|
thr.depthL2Threshold = 0.00544; |
||||||
|
} |
||||||
|
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 0.973; |
||||||
|
thr.rgbL2Threshold = 4.46E-06; |
||||||
|
thr.depthMaskThreshold = 3; |
||||||
|
thr.depthInfThreshold = 258; |
||||||
|
thr.depthL2Threshold = 0.00142; |
||||||
|
} |
||||||
|
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE) |
||||||
|
{ |
||||||
|
thr.rgbInfThreshold = 1; |
||||||
|
thr.rgbL2Threshold = 6.13E-06; |
||||||
|
thr.depthMaskThreshold = 3; |
||||||
|
thr.depthInfThreshold = 258; |
||||||
|
thr.depthL2Threshold = 0.00142; |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
CullingModeEnum cullingModeRgb = findSameCulling(modelType, shadingType, cullingMode, true); |
||||||
|
CullingModeEnum cullingModeDepth = findSameCulling(modelType, shadingType, cullingMode, false); |
||||||
|
|
||||||
|
std::string modelName = printEnum(modelType); |
||||||
|
std::string shadingName = printEnum(shadingType); |
||||||
|
std::string cullingName = printEnum(cullingMode); |
||||||
|
std::string cullingRgbName = printEnum(cullingModeRgb); |
||||||
|
std::string cullingDepthName = printEnum(cullingModeDepth); |
||||||
|
|
||||||
|
std::string path = findDataDirectory("rendering"); |
||||||
|
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingName.c_str()); |
||||||
|
std::string suffixRgb = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingRgbName.c_str()); |
||||||
|
std::string suffixDepth = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingDepthName.c_str()); |
||||||
|
std::string gtPathColor = path + "/example_image_" + suffixRgb + "_" + shadingName + ".png"; |
||||||
|
std::string gtPathDepth = path + "/depth_image_" + suffixDepth + ".png"; |
||||||
|
|
||||||
|
Mat rgbDiff, depthDiff; |
||||||
|
Mat groundTruthColor = imread(gtPathColor); |
||||||
|
groundTruthColor.convertTo(groundTruthColor, CV_32F, (1.f / 255.f)); |
||||||
|
compareRGB(groundTruthColor, color_buf, rgbDiff, thr.rgbInfThreshold, thr.rgbL2Threshold); |
||||||
|
|
||||||
|
Mat groundTruthDepth = imread(gtPathDepth, cv::IMREAD_GRAYSCALE | cv::IMREAD_ANYDEPTH); |
||||||
|
compareDepth(groundTruthDepth, depth_buf, depthDiff, zFar, depthScale, thr.depthMaskThreshold, thr.depthInfThreshold, thr.depthL2Threshold); |
||||||
|
|
||||||
|
// add --test_debug to output resulting images
|
||||||
|
if (debugLevel > 0) |
||||||
|
{ |
||||||
|
std::string outColorPath = "color_image_" + suffix + "_" + shadingName + ".png"; |
||||||
|
std::string outDepthPath = "depth_image_" + suffix + "_" + shadingName + ".png"; |
||||||
|
imwrite(outColorPath, color_buf * 255.f); |
||||||
|
imwrite(outDepthPath, depth_buf); |
||||||
|
imwrite("diff_" + outColorPath, rgbDiff * 255.f); |
||||||
|
imwrite("diff_" + outDepthPath, depthDiff); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// drawing model as a whole or as two halves should give the same result
|
||||||
|
TEST_P(RenderingTest, keepDrawnData) |
||||||
|
{ |
||||||
|
if (modelType != ModelType::Empty) |
||||||
|
{ |
||||||
|
Mat depth_buf2(height, width, ftype, zFar); |
||||||
|
Mat color_buf2(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0)); |
||||||
|
|
||||||
|
Mat idx1, idx2; |
||||||
|
int nTriangles = (int)indices.total(); |
||||||
|
idx1 = indices.reshape(3, 1)(Range::all(), Range(0, nTriangles / 2)); |
||||||
|
idx2 = indices.reshape(3, 1)(Range::all(), Range(nTriangles / 2, nTriangles)); |
||||||
|
|
||||||
|
triangleRasterize(verts, idx1, colors, color_buf2, depth_buf2, cameraPose, fovYradians, zNear, zFar, settings); |
||||||
|
triangleRasterize(verts, idx2, colors, color_buf2, depth_buf2, cameraPose, fovYradians, zNear, zFar, settings); |
||||||
|
|
||||||
|
Mat rgbDiff, depthDiff; |
||||||
|
compareRGB(color_buf, color_buf2, rgbDiff, 0, 0); |
||||||
|
depth_buf.convertTo(depth_buf, CV_16U, depthScale); |
||||||
|
depth_buf2.convertTo(depth_buf2, CV_16U, depthScale); |
||||||
|
compareDepth(depth_buf, depth_buf2, depthDiff, zFar, depthScale, 0, 0, 0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
TEST_P(RenderingTest, glCompatibleDepth) |
||||||
|
{ |
||||||
|
Mat depth_buf2(height, width, ftype, 1.0); |
||||||
|
|
||||||
|
triangleRasterizeDepth(verts, indices, depth_buf2, cameraPose, fovYradians, zNear, zFar, |
||||||
|
settings.setGlCompatibleMode(RASTERIZE_COMPAT_INVDEPTH)); |
||||||
|
|
||||||
|
Mat convertedDepth(height, width, ftype); |
||||||
|
// map from [0, 1] to [zNear, zFar]
|
||||||
|
double scaleNear = (1.0 / zNear); |
||||||
|
double scaleFar = (1.0 / zFar); |
||||||
|
for (int y = 0; y < height; y++) |
||||||
|
{ |
||||||
|
for (int x = 0; x < width; x++) |
||||||
|
{ |
||||||
|
double z = (double)depth_buf2.at<float>(y, x); |
||||||
|
convertedDepth.at<float>(y, x) = (float)(1.0 / ((1.0 - z) * scaleNear + z * scaleFar )); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
double normL2Diff = cv::norm(depth_buf, convertedDepth, cv::NORM_L2) / (height * width); |
||||||
|
const double normL2Threshold = 1.e-9; |
||||||
|
EXPECT_LE(normL2Diff, normL2Threshold); |
||||||
|
// add --test_debug to output differences
|
||||||
|
if (debugLevel > 0) |
||||||
|
{ |
||||||
|
std::cout << "normL2Diff: " << normL2Diff << " vs " << normL2Threshold << std::endl; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_CASE_P(Rendering, RenderingTest, ::testing::Values( |
||||||
|
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(640, 480), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CW, ModelType::Color, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Color, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Color, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::Color, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CW, ModelType::Clipping, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Clipping, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CCW, ModelType::Clipping, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_CW, ModelType::Clipping, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(640, 480), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::Clipping, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(700, 700), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CCW, ModelType::Clipping, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_CCW, ModelType::File, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(700, 700), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_CW, ModelType::File, CV_32F, CV_32S }, |
||||||
|
RenderTestParamType { std::make_tuple(700, 700), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::File, CV_32F, CV_32S } |
||||||
|
)); |
||||||
|
|
||||||
|
} // namespace ::
|
||||||
|
} // namespace opencv_test
|
@ -0,0 +1,460 @@ |
|||||||
|
#include <iostream> |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
#define WIN32_LEAN_AND_MEAN 1 |
||||||
|
#define NOMINMAX 1 |
||||||
|
#include <windows.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
#if defined(__APPLE__) |
||||||
|
#include <OpenGL/gl.h> |
||||||
|
#include <OpenGL/glu.h> |
||||||
|
#else |
||||||
|
#include <GL/gl.h> |
||||||
|
#include <GL/glu.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
#include "opencv2/core.hpp" |
||||||
|
#include "opencv2/core/opengl.hpp" |
||||||
|
#include "opencv2/3d.hpp" |
||||||
|
#include "opencv2/imgproc.hpp" |
||||||
|
#include "opencv2/highgui.hpp" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
using namespace cv; |
||||||
|
using namespace cv::cuda; |
||||||
|
|
||||||
|
// model data should be identical to the code from tests
|
||||||
|
enum class ModelType |
||||||
|
{ |
||||||
|
Empty = 0, |
||||||
|
File = 1, |
||||||
|
Clipping = 2, |
||||||
|
Color = 3, |
||||||
|
Centered = 4 |
||||||
|
}; |
||||||
|
|
||||||
|
static void generateNormals(const std::vector<Vec3f>& points, const std::vector<std::vector<int>>& indices, |
||||||
|
std::vector<Vec3f>& normals) |
||||||
|
{ |
||||||
|
std::vector<std::vector<Vec3f>> preNormals(points.size(), std::vector<Vec3f>()); |
||||||
|
|
||||||
|
for (const auto& tri : indices) |
||||||
|
{ |
||||||
|
Vec3f p0 = points[tri[0]]; |
||||||
|
Vec3f p1 = points[tri[1]]; |
||||||
|
Vec3f p2 = points[tri[2]]; |
||||||
|
|
||||||
|
Vec3f cross = cv::normalize((p1 - p0).cross(p2 - p0)); |
||||||
|
for (int i = 0; i < 3; i++) |
||||||
|
{ |
||||||
|
preNormals[tri[i]].push_back(cross); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
normals.reserve(points.size()); |
||||||
|
for (const auto& pn : preNormals) |
||||||
|
{ |
||||||
|
Vec3f sum { }; |
||||||
|
for (const auto& n : pn) |
||||||
|
{ |
||||||
|
sum += n; |
||||||
|
} |
||||||
|
normals.push_back(cv::normalize(sum)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ModelData |
||||||
|
{ |
||||||
|
public: |
||||||
|
ModelData(ModelType type = ModelType::Empty, std::string objPath = { }) |
||||||
|
{ |
||||||
|
switch (type) |
||||||
|
{ |
||||||
|
case ModelType::Empty: |
||||||
|
{ |
||||||
|
position = Vec3d(0.0, 0.0, 0.0); |
||||||
|
lookat = Vec3d(0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d(0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 45.0; |
||||||
|
|
||||||
|
vertices = std::vector<Vec3f>(4, {2.0f, 0, -2.0f}); |
||||||
|
colors = std::vector<Vec3f>(4, {0, 0, 1.0f}); |
||||||
|
indices = { }; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::File: |
||||||
|
{ |
||||||
|
position = Vec3d( 1.9, 0.4, 1.3); |
||||||
|
lookat = Vec3d( 0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d( 0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 45.0; |
||||||
|
|
||||||
|
objectPath = objPath; |
||||||
|
std::vector<vector<int>> indvec; |
||||||
|
loadMesh(objectPath, vertices, indvec); |
||||||
|
// using per-vertex normals as colors
|
||||||
|
generateNormals(vertices, indvec, colors); |
||||||
|
if (vertices.size() != colors.size()) |
||||||
|
{ |
||||||
|
std::runtime_error("Model should contain normals for each vertex"); |
||||||
|
} |
||||||
|
for (const auto &vec : indvec) |
||||||
|
{ |
||||||
|
indices.push_back({vec[0], vec[1], vec[2]}); |
||||||
|
} |
||||||
|
|
||||||
|
for (auto &color : colors) |
||||||
|
{ |
||||||
|
color = Vec3f(abs(color[0]), abs(color[1]), abs(color[2])); |
||||||
|
} |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::Clipping: |
||||||
|
{ |
||||||
|
position = Vec3d(0.0, 0.0, 5.0); |
||||||
|
lookat = Vec3d(0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d(0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 45.0; |
||||||
|
|
||||||
|
vertices = |
||||||
|
{ |
||||||
|
{ 2.0, 0.0, -2.0}, { 0.0, -6.0, -2.0}, {-2.0, 0.0, -2.0}, |
||||||
|
{ 3.5, -1.0, -5.0}, { 2.5, -2.5, -5.0}, {-1.0, 1.0, -5.0}, |
||||||
|
{-6.5, -1.0, -3.0}, {-2.5, -2.0, -3.0}, { 1.0, 1.0, -5.0}, |
||||||
|
}; |
||||||
|
|
||||||
|
indices = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8} }; |
||||||
|
|
||||||
|
Vec3f col1(217.0, 238.0, 185.0); |
||||||
|
Vec3f col2(185.0, 217.0, 238.0); |
||||||
|
Vec3f col3(150.0, 10.0, 238.0); |
||||||
|
|
||||||
|
col1 *= (1.f / 255.f); |
||||||
|
col2 *= (1.f / 255.f); |
||||||
|
col3 *= (1.f / 255.f); |
||||||
|
|
||||||
|
colors = |
||||||
|
{ |
||||||
|
col1, col2, col3, |
||||||
|
col2, col3, col1, |
||||||
|
col3, col1, col2, |
||||||
|
}; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::Centered: |
||||||
|
{ |
||||||
|
position = Vec3d(0.0, 0.0, 5.0); |
||||||
|
lookat = Vec3d(0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d(0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 45.0; |
||||||
|
|
||||||
|
vertices = |
||||||
|
{ |
||||||
|
{ 2.0, 0.0, -2.0}, { 0.0, -2.0, -2.0}, {-2.0, 0.0, -2.0}, |
||||||
|
{ 3.5, -1.0, -5.0}, { 2.5, -1.5, -5.0}, {-1.0, 0.5, -5.0}, |
||||||
|
}; |
||||||
|
|
||||||
|
indices = { {0, 1, 2}, {3, 4, 5} }; |
||||||
|
|
||||||
|
Vec3f col1(217.0, 238.0, 185.0); |
||||||
|
Vec3f col2(185.0, 217.0, 238.0); |
||||||
|
|
||||||
|
col1 *= (1.f / 255.f); |
||||||
|
col2 *= (1.f / 255.f); |
||||||
|
|
||||||
|
colors = |
||||||
|
{ |
||||||
|
col1, col2, col1, |
||||||
|
col2, col1, col2, |
||||||
|
}; |
||||||
|
} |
||||||
|
break; |
||||||
|
case ModelType::Color: |
||||||
|
{ |
||||||
|
position = Vec3d(0.0, 0.0, 5.0); |
||||||
|
lookat = Vec3d(0.0, 0.0, 0.0); |
||||||
|
upVector = Vec3d(0.0, 1.0, 0.0); |
||||||
|
|
||||||
|
fovy = 60.0; |
||||||
|
|
||||||
|
vertices = |
||||||
|
{ |
||||||
|
{ 2.0, 0.0, -2.0}, |
||||||
|
{ 0.0, 2.0, -3.0}, |
||||||
|
{-2.0, 0.0, -2.0}, |
||||||
|
{ 0.0, -2.0, 1.0}, |
||||||
|
}; |
||||||
|
|
||||||
|
indices = { {0, 1, 2}, {0, 2, 3} }; |
||||||
|
|
||||||
|
colors = |
||||||
|
{ |
||||||
|
{ 0.0f, 0.0f, 1.0f}, |
||||||
|
{ 0.0f, 1.0f, 0.0f}, |
||||||
|
{ 1.0f, 0.0f, 0.0f}, |
||||||
|
{ 0.0f, 1.0f, 0.0f}, |
||||||
|
}; |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
CV_Error(Error::StsBadArg, "Unknown model type"); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Vec3d position; |
||||||
|
Vec3d lookat; |
||||||
|
Vec3d upVector; |
||||||
|
|
||||||
|
double fovy; |
||||||
|
|
||||||
|
std::vector<Vec3f> vertices; |
||||||
|
std::vector<Vec3i> indices; |
||||||
|
std::vector<Vec3f> colors; |
||||||
|
|
||||||
|
string objectPath; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
struct DrawData |
||||||
|
{ |
||||||
|
ogl::Arrays arr; |
||||||
|
ogl::Buffer indices; |
||||||
|
}; |
||||||
|
|
||||||
|
void draw(void* userdata); |
||||||
|
|
||||||
|
void draw(void* userdata) |
||||||
|
{ |
||||||
|
DrawData* data = static_cast<DrawData*>(userdata); |
||||||
|
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
||||||
|
ogl::render(data->arr, data->indices, ogl::TRIANGLES); |
||||||
|
} |
||||||
|
|
||||||
|
static void generateImage(cv::Size imgSz, TriangleShadingType shadingType, TriangleCullingMode cullingMode, |
||||||
|
ModelType modelType, std::string modelPath, cv::Mat& colorImage, cv::Mat& depthImage) |
||||||
|
{ |
||||||
|
namedWindow("OpenGL", WINDOW_OPENGL); |
||||||
|
resizeWindow("OpenGL", imgSz.width, imgSz.height); |
||||||
|
|
||||||
|
ModelData modelData(modelType, modelPath); |
||||||
|
|
||||||
|
DrawData data; |
||||||
|
|
||||||
|
std::vector<Vec3f> vertices; |
||||||
|
std::vector<Vec4f> colors4f; |
||||||
|
std::vector<int> idxLinear; |
||||||
|
|
||||||
|
if (shadingType == RASTERIZE_SHADING_FLAT) |
||||||
|
{ |
||||||
|
// rearrange vertices and colors for flat shading
|
||||||
|
int ctr = 0; |
||||||
|
for (const auto& idx : modelData.indices) |
||||||
|
{ |
||||||
|
for (int i = 0; i < 3; i++) |
||||||
|
{ |
||||||
|
vertices.push_back(modelData.vertices[idx[i]]); |
||||||
|
idxLinear.push_back(ctr++); |
||||||
|
} |
||||||
|
|
||||||
|
Vec3f ci = modelData.colors[idx[0]]; |
||||||
|
for (int i = 0; i < 3; i++) |
||||||
|
{ |
||||||
|
colors4f.emplace_back(ci[0], ci[1], ci[2], 1.f); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
vertices = modelData.vertices; |
||||||
|
for (const auto& c : modelData.colors) |
||||||
|
{ |
||||||
|
Vec3f ci = (shadingType == RASTERIZE_SHADING_SHADED) ? c: cv::Vec3f::all(1.f); |
||||||
|
colors4f.emplace_back(ci[0], ci[1], ci[2], 1.0); |
||||||
|
} |
||||||
|
|
||||||
|
for (const auto& idx : modelData.indices) |
||||||
|
{ |
||||||
|
for (int i = 0; i < 3; i++) |
||||||
|
{ |
||||||
|
idxLinear.push_back(idx[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
data.arr.setVertexArray(vertices); |
||||||
|
data.arr.setColorArray(colors4f); |
||||||
|
data.indices.copyFrom(idxLinear); |
||||||
|
|
||||||
|
double zNear = 0.1, zFar = 50; |
||||||
|
glMatrixMode(GL_PROJECTION); |
||||||
|
glLoadIdentity(); |
||||||
|
gluPerspective(modelData.fovy, (double)imgSz.width / imgSz.height, zNear, zFar); |
||||||
|
|
||||||
|
glMatrixMode(GL_MODELVIEW); |
||||||
|
glLoadIdentity(); |
||||||
|
|
||||||
|
//gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
|
||||||
|
gluLookAt(modelData.position[0], modelData.position[1], modelData.position[2], |
||||||
|
modelData.lookat [0], modelData.lookat [1], modelData.lookat [2], |
||||||
|
modelData.upVector[0], modelData.upVector[1], modelData.upVector[2]); |
||||||
|
|
||||||
|
if (cullingMode == RASTERIZE_CULLING_NONE) |
||||||
|
{ |
||||||
|
glDisable(GL_CULL_FACE); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
glEnable(GL_CULL_FACE); |
||||||
|
glCullFace(GL_FRONT); |
||||||
|
if (cullingMode == RASTERIZE_CULLING_CW) |
||||||
|
{ |
||||||
|
glFrontFace(GL_CW); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
glFrontFace(GL_CCW); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST); |
||||||
|
|
||||||
|
cv::setOpenGlDrawCallback("OpenGL", draw, &data); |
||||||
|
|
||||||
|
const int framesToSkip = 10; |
||||||
|
for (int f = 0; f < framesToSkip; f++) |
||||||
|
{ |
||||||
|
updateWindow("OpenGL"); |
||||||
|
|
||||||
|
colorImage = cv::Mat(imgSz.height, imgSz.width, CV_8UC3); |
||||||
|
glReadPixels(0, 0, imgSz.width, imgSz.height, GL_RGB, GL_UNSIGNED_BYTE, colorImage.data); |
||||||
|
cv::cvtColor(colorImage, colorImage, cv::COLOR_RGB2BGR); |
||||||
|
cv::flip(colorImage, colorImage, 0); |
||||||
|
|
||||||
|
depthImage = cv::Mat(imgSz.height, imgSz.width, CV_32F); |
||||||
|
glReadPixels(0, 0, imgSz.width, imgSz.height, GL_DEPTH_COMPONENT, GL_FLOAT, depthImage.data); |
||||||
|
// map from [0, 1] to [zNear, zFar]
|
||||||
|
for (auto it = depthImage.begin<float>(); it != depthImage.end<float>(); ++it) |
||||||
|
{ |
||||||
|
*it = (float)(zNear * zFar / (double(*it) * (zNear - zFar) + zFar)); |
||||||
|
} |
||||||
|
cv::flip(depthImage, depthImage, 0); |
||||||
|
depthImage.convertTo(depthImage, CV_16U, 1000.0); |
||||||
|
|
||||||
|
char key = (char)waitKey(40); |
||||||
|
if (key == 27) |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
cv::setOpenGlDrawCallback("OpenGL", 0, 0); |
||||||
|
cv::destroyAllWindows(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) |
||||||
|
{ |
||||||
|
cv::CommandLineParser parser(argc, argv, |
||||||
|
"{ help h usage ? | | show this message }" |
||||||
|
"{ outPath | | output path for generated images }" |
||||||
|
"{ modelPath | | path to 3d model to render }" |
||||||
|
); |
||||||
|
parser.about("This app is used to generate test data for triangleRasterize() function"); |
||||||
|
|
||||||
|
if (parser.has("help")) |
||||||
|
{ |
||||||
|
parser.printMessage(); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
std::string modelPath = parser.get<std::string>("modelPath"); |
||||||
|
if (modelPath.empty()) |
||||||
|
{ |
||||||
|
std::cout << "No model path given" << std::endl; |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
std::string outPath = parser.get<std::string>("outPath"); |
||||||
|
if (outPath.empty()) |
||||||
|
{ |
||||||
|
std::cout << "No output path given" << std::endl; |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
std::array<cv::Size, 4> resolutions = { cv::Size {700, 700}, cv::Size {640, 480}, cv::Size(256, 256), cv::Size(320, 240) }; |
||||||
|
for (const auto& res : resolutions) |
||||||
|
{ |
||||||
|
for (const auto shadingType : { |
||||||
|
RASTERIZE_SHADING_WHITE, |
||||||
|
RASTERIZE_SHADING_FLAT, |
||||||
|
RASTERIZE_SHADING_SHADED |
||||||
|
}) |
||||||
|
{ |
||||||
|
std::string shadingName; |
||||||
|
switch (shadingType) |
||||||
|
{ |
||||||
|
case RASTERIZE_SHADING_WHITE: shadingName = "White"; break; |
||||||
|
case RASTERIZE_SHADING_FLAT: shadingName = "Flat"; break; |
||||||
|
case RASTERIZE_SHADING_SHADED: shadingName = "Shaded"; break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
for (const auto cullingMode : { |
||||||
|
RASTERIZE_CULLING_NONE, |
||||||
|
RASTERIZE_CULLING_CW, |
||||||
|
RASTERIZE_CULLING_CCW |
||||||
|
}) |
||||||
|
{ |
||||||
|
std::string cullingName; |
||||||
|
switch (cullingMode) |
||||||
|
{ |
||||||
|
case RASTERIZE_CULLING_NONE: cullingName = "None"; break; |
||||||
|
case RASTERIZE_CULLING_CW: cullingName = "CW"; break; |
||||||
|
case RASTERIZE_CULLING_CCW: cullingName = "CCW"; break; |
||||||
|
default: break; |
||||||
|
} |
||||||
|
|
||||||
|
for (const auto modelType : { |
||||||
|
ModelType::File, |
||||||
|
ModelType::Clipping, |
||||||
|
ModelType::Color, |
||||||
|
ModelType::Centered, |
||||||
|
}) |
||||||
|
{ |
||||||
|
std::string modelName; |
||||||
|
switch (modelType) |
||||||
|
{ |
||||||
|
case ModelType::File: modelName = "File"; break; |
||||||
|
case ModelType::Clipping: modelName = "Clipping"; break; |
||||||
|
case ModelType::Color: modelName = "Color"; break; |
||||||
|
case ModelType::Centered: modelName = "Centered"; break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), res.width, res.height, cullingName.c_str()); |
||||||
|
|
||||||
|
std::cout << suffix + "_" + shadingName << "..." << std::endl; |
||||||
|
|
||||||
|
cv::Mat colorImage, depthImage; |
||||||
|
generateImage(res, shadingType, cullingMode, modelType, modelPath, colorImage, depthImage); |
||||||
|
|
||||||
|
std::string gtPathColor = outPath + "/example_image_" + suffix + "_" + shadingName + ".png"; |
||||||
|
std::string gtPathDepth = outPath + "/depth_image_" + suffix + ".png"; |
||||||
|
|
||||||
|
cv::imwrite(gtPathColor, colorImage); |
||||||
|
cv::imwrite(gtPathDepth, depthImage); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
Loading…
Reference in new issue