413 lines
14 KiB
C++
413 lines
14 KiB
C++
// 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 || validMask.type() == CV_8SC1 || validMask.type() == CV_BoolC1);
|
|
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);
|
|
|
|
float x_num = std::fma(mvpMatrix(0,0), vglobal[0],
|
|
std::fma(mvpMatrix(0,1), vglobal[1],
|
|
std::fma(mvpMatrix(0,2), vglobal[2], mvpMatrix(0,3))));
|
|
float y_num = std::fma(mvpMatrix(1,0), vglobal[0],
|
|
std::fma(mvpMatrix(1,1), vglobal[1],
|
|
std::fma(mvpMatrix(1,2), vglobal[2], mvpMatrix(1,3))));
|
|
float z_num = std::fma(mvpMatrix(2,0), vglobal[0],
|
|
std::fma(mvpMatrix(2,1), vglobal[1],
|
|
std::fma(mvpMatrix(2,2), vglobal[2], mvpMatrix(2,3))));
|
|
float w_num = std::fma(mvpMatrix(3,0), vglobal[0],
|
|
std::fma(mvpMatrix(3,1), vglobal[1],
|
|
std::fma(mvpMatrix(3,2), vglobal[2], mvpMatrix(3,3))));
|
|
|
|
float invw = 1.f / w_num;
|
|
|
|
// [-1, 1]^3 => [0, width] x [0, height] x [0, 1]
|
|
Vec4f vscreen = {
|
|
std::fma(x_num * invw, 0.5f * (float)imgSize.width, 0.5f * (float)imgSize.width),
|
|
std::fma(y_num * invw, 0.5f * (float)imgSize.height, 0.5f * (float)imgSize.height),
|
|
std::fma(z_num * invw, 0.5f, 0.5f),
|
|
invw
|
|
};
|
|
|
|
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
|