source/rig/RigSimulator.cpp (670 lines of code) (raw):
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <string>
#include <thread>
#include <vector>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <folly/Format.h>
#include "source/render/BoundingVolumeHierarchy.h"
#include "source/render/PerlinNoise.h"
#include "source/render/RaytracingPrimitives.h"
#include "source/util/Camera.h"
#include "source/util/CvUtil.h"
#include "source/util/MathUtil.h"
#include "source/util/SystemUtil.h"
using namespace fb360_dep;
using namespace fb360_dep::cv_util;
using namespace fb360_dep::math_util;
using namespace fb360_dep::render;
using namespace fb360_dep::system_util;
const std::string kUsageMessage = R"(
- Render an artificial scene as seen by the specified rig.
- Example:
./RigSimulator \
--mode=pinhole_ring \
--skybox_path=/path/to/skybox.png
)";
DEFINE_int32(
anti_alias_supersample,
1,
"1 = no supersampling, 2 or higher = anti-alias supersampling");
DEFINE_double(ceiling_depth, 0, "depth of ceiling texture (m)");
DEFINE_string(ceiling_path, "", "path to image to use for ceiling");
DEFINE_double(ceiling_position, 0, "how far up the ceiling is (m)");
DEFINE_double(ceiling_width, 0, "width of ceiling texture (m)");
DEFINE_string(
dest_cam_images,
"",
"path to directory to write camera images for multi-camera rigs");
DEFINE_string(dest_left, "", "path to left-eye image");
DEFINE_string(dest_mono, "", "path to mono image");
DEFINE_string(dest_mono_depth, "", "path to mono 1/depthmap (intensity = 1 / depth in meters)");
DEFINE_string(dest_right, "", "path to right-eye image");
DEFINE_string(dest_stereo, "", "path to right-eye image");
DEFINE_int32(eqr_height, 1540, "height of equirect output");
DEFINE_int32(eqr_width, 3080, "width of equirect output");
DEFINE_int32(ftheta_height, 400, "height of ftheta camera output");
DEFINE_double(
ftheta_image_circle_fov,
166.667,
"ftheta FOV, i.e. number of degrees spanned at the image circle");
DEFINE_int32(
ftheta_image_circle_radius,
250,
"image circle radius corresponding to specified ftheta FOV");
DEFINE_int32(ftheta_width, 300, "width of ftheta camera output");
DEFINE_double(
ground_plane_dist_m,
1.70,
"for 'ground_plane' scene, distance from camera to ground");
DEFINE_double(interpupillary_radius, 3.2, "half distance between eyes");
DEFINE_bool(
marble,
false,
"if true, adds a marble (perlin noise) texture to the objects in the scene");
DEFINE_double(marble_scale, 0.1, "scale applied to marble texture");
DEFINE_double(
max_icosahedron_dist,
250,
"maximum distance from origin that a randomly generated icosahedron can spawn");
DEFINE_double(max_icosahedron_radius, 50, "max radius of a randomly generated icosahedron");
DEFINE_double(
min_icosahedron_dist,
100,
"minimum distance from a center of camera to the closest point on a randomly generated icosahedron");
DEFINE_double(min_icosahedron_radius, 20, "min radius of a randomly generated icosahedron");
DEFINE_string(
mode,
"",
"mono_eqr,stereo_eqr,pinhole_ring,ftheta_ring,dodecahedron,icosahedron,rig_from_json (required)");
DEFINE_double(
noise_amplitude,
0.0,
"amount of noise to be added to pixels (to simulate real camera noise). pixel intensities are scaled in 0...255");
DEFINE_int32(num_cams_in_ring, 14, "number of cameras in simulated rings of cameras");
DEFINE_int32(num_random_icosahedrons, 250, "number of icosahedrons to generate");
DEFINE_double(
pinhole_aspect_ratio,
1.0,
"aspect ratio of pinhole lens = horizontal fov / vertical fov");
DEFINE_double(pinhole_fov_horizontal, 77.7, "horizontal FOV of pinhole lens (degrees)");
DEFINE_int32(pinhole_height, 512, "height of pinhole camera output");
DEFINE_int32(pinhole_width, 512, "width of pinhole camera output");
DEFINE_bool(red_triangle, false, "add a red triangle at (0,0)");
DEFINE_string(rig_in, "", "path to read json rig file if mode = rig_from_json");
DEFINE_string(rig_out, "", "path to write json description of multi-camera rig");
DEFINE_double(
rig_radius,
0.218,
"radius of the rig/sphere of cameras (m). distance from center to lens exit pupil.");
DEFINE_string(scene, "icosahedron", "scene to draw: 'icosahedron', 'cube', 'ground_plane'");
DEFINE_string(skybox_path, "res/skybox.jpg", "path to image to use as background/skybox");
DEFINE_double(top_cam_vertical_offset, 13.0, "distance from center plane to top camera");
namespace icosahedron_data {
const static float X = 0.525731112119133696;
const static float Z = 0.850650808352039932;
const static float icosahedronVertex[12][3] = {
{-X, 0.0, Z},
{X, 0.0, Z},
{-X, 0.0, -Z},
{X, 0.0, -Z},
{0.0, Z, X},
{0.0, Z, -X},
{0.0, -Z, X},
{0.0, -Z, -X},
{Z, X, 0.0},
{-Z, X, 0.0},
{Z, -X, 0.0},
{-Z, -X, 0.0}};
const static int icosahedronTriangle[20][3] = {
{1, 4, 0}, {4, 9, 0}, {4, 5, 9}, {8, 5, 4}, {1, 8, 4}, {1, 10, 8}, {10, 3, 8},
{8, 3, 5}, {3, 2, 5}, {3, 7, 2}, {3, 10, 7}, {10, 6, 7}, {6, 11, 7}, {6, 0, 11},
{6, 1, 0}, {10, 1, 6}, {11, 0, 9}, {2, 11, 9}, {5, 2, 9}, {11, 2, 7}};
}; // namespace icosahedron_data
void makeIcosahedron(
std::vector<Triangle>& triangles,
const cv::Vec3f& center,
const float& radius) {
using namespace icosahedron_data;
const cv::Vec3f color =
center[2] > 0 ? cv::Vec3f(0, 1, 0) : cv::Vec3f(randf0to1(), randf0to1(), randf0to1());
for (int i = 0; i < 20; ++i) {
const int v1i = icosahedronTriangle[i][0];
const int v2i = icosahedronTriangle[i][1];
const int v3i = icosahedronTriangle[i][2];
const cv::Vec3f v1(
icosahedronVertex[v1i][0], icosahedronVertex[v1i][1], icosahedronVertex[v1i][2]);
const cv::Vec3f v2(
icosahedronVertex[v2i][0], icosahedronVertex[v2i][1], icosahedronVertex[v2i][2]);
const cv::Vec3f v3(
icosahedronVertex[v3i][0], icosahedronVertex[v3i][1], icosahedronVertex[v3i][2]);
triangles.push_back(
Triangle(v1 * radius + center, v2 * radius + center, v3 * radius + center, color));
}
}
RayIntersectionResult raytraceBVH(const Ray& ray, const BoundingVolumeHierarchy& bvh) {
RayIntersectionResult closestIntersection(false, FLT_MAX, -1);
// if the ray misses the bvh, it misses everything
if (!rayIntersectSphereYesNo(ray, bvh.sphere)) {
return closestIntersection;
}
if (bvh.isLeaf) { // if its a leaf, do intersection with triangles in the leaf
for (int i = 0; i < int(bvh.leafTriangles.size()); ++i) {
RayIntersectionResult triResult = rayIntersectTriangle(ray, bvh.leafTriangles[i]);
if (triResult.hit && triResult.dist < closestIntersection.dist) {
closestIntersection = triResult;
}
}
} else { // if not a leaf, do intersection with child bvh's
for (int i = 0; i < int(bvh.children.size()); ++i) {
RayIntersectionResult childResult = raytraceBVH(ray, bvh.children[i]);
if (childResult.hit && childResult.dist < closestIntersection.dist) {
closestIntersection = childResult;
}
}
}
return closestIntersection;
}
// returns BGR-D (D=depth)
cv::Vec4f traceRayToGetColor(
const Ray& ray,
const std::vector<Triangle>& triangles,
const BoundingVolumeHierarchy& bvh,
const cv::Mat_<cv::Vec3b>& skybox) {
// intersect with geometry in bvh
RayIntersectionResult intersectionResult = raytraceBVH(ray, bvh);
// intersect with a textured rectangle above the rig
if (!FLAGS_ceiling_path.empty()) {
// solve r(depth).z = ceiling_position <=>
const float depth = (FLAGS_ceiling_position - ray.origin[2]) / ray.dir[2];
if (0 < depth && depth < intersectionResult.dist) {
cv::Vec3f p = ray.origin + depth * ray.dir;
float s = p[0] / FLAGS_ceiling_width + 0.5;
float t = p[1] / FLAGS_ceiling_depth + 0.5;
if (0 <= s && s < 1 && 0 <= t && t < 1) {
// ceiling is hit, return the color
static cv::Mat_<cv::Vec3b> ceiling =
cv_util::imreadExceptionOnFail(FLAGS_ceiling_path, cv::IMREAD_COLOR);
cv::Vec3f color = ceiling(t * ceiling.rows, s * ceiling.cols);
return cv::Vec4f(color[0] / 255, color[1] / 255, color[2] / 255, depth);
}
}
}
// if nothing else was hit, intersect with sky equirect
if (!intersectionResult.hit) {
const float phi = acos(math_util::clamp(
ray.dir[2], -1.0f, 1.0f)); // the min here is to avoid a nan.. other numerics can result in
// a value that is epsilon > 1.0 being passed here
const float theta = M_PI + atan2(ray.dir[1], ray.dir[0]);
const float sampleX = (theta / (2.0 * M_PI)) * skybox.cols;
const float sampleY = (phi / M_PI) * skybox.rows;
const cv::Vec3b skyColor =
skybox(std::min(int(sampleY), skybox.rows - 1), int(sampleX) % skybox.cols);
return cv::Vec4f(
skyColor[0] / 255.0f,
skyColor[1] / 255.0f,
skyColor[2] / 255.0f,
std::numeric_limits<float>::max());
}
assert(
intersectionResult.hitObjectIdx >=
0); // if this fails, we probably forgot to bind the triangle indices in Triangle::selfIdx
cv::Vec3f baseColor = triangles[intersectionResult.hitObjectIdx].color;
const cv::Vec3f& normal = triangles[intersectionResult.hitObjectIdx].normal;
const cv::Vec3f intersectionPoint = ray.origin + intersectionResult.dist * ray.dir;
if (FLAGS_marble) {
baseColor *= 0.7f +
0.3f *
std::fabs(perlin_noise::pnoise(
FLAGS_marble_scale * intersectionPoint[0],
FLAGS_marble_scale * intersectionPoint[1],
FLAGS_marble_scale * intersectionPoint[2]));
}
const static cv::Vec3f kLightPos(2.0f, 1.0f, 5.2f);
cv::Vec3f lightDir = kLightPos - intersectionPoint;
lightDir /= norm(lightDir);
const float lightCoef = .25f + .75f * std::max(0.0f, normal.dot(lightDir));
const cv::Vec3f shadedColor = baseColor * lightCoef;
return cv::Vec4f(shadedColor[0], shadedColor[1], shadedColor[2], intersectionResult.dist);
}
void makeIcosahedronScene(std::vector<Triangle>& triangles) {
for (int i = 0; i < FLAGS_num_random_icosahedrons; ++i) {
const float minAllowedCenterDist = FLAGS_min_icosahedron_dist + FLAGS_max_icosahedron_radius;
cv::Vec3f center;
do {
center = cv::Vec3f(
2.0f * (randf0to1() - 0.5) * FLAGS_max_icosahedron_dist,
2.0f * (randf0to1() - 0.5) * FLAGS_max_icosahedron_dist,
2.0f * (randf0to1() - 0.5) * FLAGS_max_icosahedron_dist);
} while (norm(center) < minAllowedCenterDist);
const float radiusRange = FLAGS_max_icosahedron_radius - FLAGS_min_icosahedron_radius;
const float icosahedronRadius = FLAGS_min_icosahedron_radius + randf0to1() * radiusRange;
makeIcosahedron(triangles, center, icosahedronRadius);
}
if (FLAGS_red_triangle) {
float kDepth = FLAGS_min_icosahedron_dist;
float kSide = 0.1f * kDepth;
triangles.push_back(Triangle(
cv::Vec3f(kDepth, 0, 0),
cv::Vec3f(kDepth, 0, kSide),
cv::Vec3f(kDepth, kSide, 0),
cv::Vec3f(0, 0, 1)));
}
}
void makeCubesScene(std::vector<Triangle>& triangles) {
static const std::vector<cv::Vec3f> kCubeVertices = {
{0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}, {1, 0, 0}, {1, 0, 1}, {1, 1, 0}, {1, 1, 1}};
static const std::vector<cv::Vec3i> kCubeTriangleIndices = {
{2, 0, 1},
{1, 3, 2},
{6, 2, 0},
{0, 4, 6},
{4, 0, 1},
{1, 5, 4},
{3, 1, 5},
{5, 7, 3},
{7, 3, 2},
{2, 6, 7},
{5, 4, 6},
{6, 7, 5}};
for (int triangle = 0; triangle < int(kCubeTriangleIndices.size()); ++triangle) {
cv::Vec3f vertices[3];
for (int vertex = 0; vertex < 3; ++vertex) {
vertices[vertex] = kCubeVertices[kCubeTriangleIndices[triangle][vertex]];
}
// Scales and offsets are chosen to place one cube straight ahead, and one
// smaller cube offset from centered.
static const std::vector<float> kScales = {2, 1};
static const std::vector<cv::Vec3f> kOffsets = {{0, 0, -25}, {5, 2, -20}};
static const cv::Vec3f kCenterShift(-0.5, -0.5, -0.5);
static const std::vector<std::vector<cv::Vec3f>> kColors = {
// Red Green Yellow Blue Magenta Cyan
{{0, 0, 1}, {0, 1, 0}, {0, 1, 1}, {1, 0, 0}, {1, 0, 1}, {1, 1, 0}},
// Teal Purple White Orange Salmon
{{0.5, 1, 0},
{1, 0, 0.5},
{1, 1, 1},
{0, 0.5, 1},
{0.5, 0.5, 1},
// Black
{0, 0, 0}}};
const int numCubes = int(kScales.size());
CHECK_EQ(numCubes, int(kOffsets.size()));
CHECK_EQ(numCubes, int(kColors.size()));
for (int cube = 0; cube < numCubes; ++cube) {
triangles.push_back(Triangle(
kScales[cube] * (vertices[0] + kCenterShift) + kOffsets[cube],
kScales[cube] * (vertices[1] + kCenterShift) + kOffsets[cube],
kScales[cube] * (vertices[2] + kCenterShift) + kOffsets[cube],
kColors[cube][triangle / 2]));
}
}
}
void makeGroundPlaneScene(std::vector<Triangle>& triangles) {
static const float kR = 100.0f; // 100 meters
const float z = -FLAGS_ground_plane_dist_m;
static const std::vector<cv::Vec3f> vertices = {
{-kR, -kR, z},
{+kR, -kR, z},
{+kR, +kR, z},
{-kR, +kR, z},
};
const cv::Vec3f red(0, 0, 1);
triangles.push_back(Triangle(vertices[0], vertices[1], vertices[2], red));
triangles.push_back(Triangle(vertices[3], vertices[0], vertices[2], red));
}
std::vector<Camera> ringOfClones(const Camera& camera, int count, double radius) {
std::vector<Camera> result(count, camera);
for (int i = 0; i < count; ++i) {
// the negative sign on theta here is so the camera array goes clockwise
const double theta = -2.0 * M_PI * double(i) / double(count);
Camera& clone = result[i];
clone.setRotation(Camera::Vector3(cos(theta), sin(theta), 0), Camera::Vector3::UnitZ());
clone.position = radius * clone.forward();
clone.id = std::to_string(i);
clone.group = "side camera";
}
return result;
}
std::vector<Camera> makeHorizontalRingOfPinholeCameras(
const int numCameras,
const float cameraArrayRadius,
const int pixelWidth,
const int pixelHeight,
const float fovHorizontalDegrees,
const float aspectRatioWoverH) {
const float tanHalfFov = std::tan(toRadians(fovHorizontalDegrees) / 2);
const Camera::Vector2 focal(
(pixelWidth / 2.0) / tanHalfFov, (pixelHeight / 2.0) / (tanHalfFov / aspectRatioWoverH));
const Camera generic(Camera::Type::RECTILINEAR, Camera::Vector2(pixelWidth, pixelHeight), focal);
return ringOfClones(generic, numCameras, cameraArrayRadius);
}
Camera makeGenericFTheta(
const int pixelWidth,
const int pixelHeight,
const int imageCircleRadius,
const float circleFov) {
return Camera(
Camera::Type::FTHETA,
Camera::Vector2(pixelWidth, pixelHeight),
2 * imageCircleRadius / toRadians(circleFov) * Camera::Vector2(1, 1));
}
void addTopCamera(
Camera::Rig& rig,
const int pixelWidth,
const int pixelHeight,
const int imageCircleRadius,
const float circleFov) {
Camera top = makeGenericFTheta(pixelWidth, pixelHeight, imageCircleRadius, circleFov);
top.position = Camera::Vector3(0, 0, FLAGS_top_cam_vertical_offset);
top.setRotation(Camera::Vector3(0, 0, 1), Camera::Vector3(1, 0, 0));
top.id = std::to_string(rig.size());
rig.push_back(top);
}
std::vector<Camera> makeHorizontalRingOfFThetaCameras(
const int numCameras,
const float cameraArrayRadius,
const int pixelWidth,
const int pixelHeight,
const int imageCircleRadius,
const float circleFov) {
Camera generic = makeGenericFTheta(pixelWidth, pixelHeight, imageCircleRadius, circleFov);
return ringOfClones(generic, numCameras, cameraArrayRadius);
}
// place an ftheta camera on a sphere of some specified radius and pointing
// forward in the direction of the sphere normal.
Camera makeFThetaCameraOnSphere(
const float /*sphereRadius*/,
const Camera::Vector3& normal,
const int pixelWidth,
const int pixelHeight,
const int imageCircleRadius,
const float circleFov,
const std::string& id) {
const Camera::Vector3 worldUp(0, 0, 1);
Camera camera = makeGenericFTheta(pixelWidth, pixelHeight, imageCircleRadius, circleFov);
camera.position = imageCircleRadius * normal;
Camera::Vector3 right = normal.cross(worldUp).normalized();
camera.setRotation(normal, normal.cross(-right));
camera.id = id;
return camera;
}
Camera::Vector3 icosaVert(const int index) {
const float(&v)[3] = icosahedron_data::icosahedronVertex[index];
return Eigen::Map<const Eigen::Vector3f>(v).cast<Camera::Real>();
}
std::vector<Camera> makeDodecahedronOfFThetaCameras(
const float cameraSphereRadius,
const int pixelWidth,
const int pixelHeight,
const int imageCircleRadius,
const float circleFov) {
std::vector<Camera> cameras;
for (int i = 0; i < int(ARRAY_SIZE(icosahedron_data::icosahedronVertex)); ++i) {
cameras.emplace_back(makeFThetaCameraOnSphere(
cameraSphereRadius,
icosaVert(i),
pixelWidth,
pixelHeight,
imageCircleRadius,
circleFov,
std::to_string(cameras.size())));
}
return cameras;
}
std::vector<Camera> makeIcosahedronOfFThetaCameras(
const float cameraSphereRadius,
const int pixelWidth,
const int pixelHeight,
const int imageCircleRadius,
const float circleFov) {
std::vector<Camera> cameras;
for (const auto& indexes : icosahedron_data::icosahedronTriangle) {
Camera::Vector3 midpoint =
(icosaVert(indexes[0]) + icosaVert(indexes[1]) + icosaVert(indexes[2])).normalized();
cameras.emplace_back(makeFThetaCameraOnSphere(
cameraSphereRadius,
midpoint,
pixelWidth,
pixelHeight,
imageCircleRadius,
circleFov,
std::to_string(cameras.size())));
}
return cameras;
}
// assumes a cv::Mat of Vec3f
void corruptImageWithNoise(cv::Mat_<cv::Vec3f>& image) {
const float a = FLAGS_noise_amplitude;
if (a == 0.0f) {
return;
}
for (int y = 0; y < image.rows; ++y) {
for (int x = 0; x < image.cols; ++x) {
const cv::Vec3f origColor = image(y, x);
image(y, x) = cv::Vec3f(
math_util::clamp<float>(origColor[0] + 2.0f * a * (randf0to1() - 0.5f), 0, 255.0f),
math_util::clamp<float>(origColor[1] + 2.0f * a * (randf0to1() - 0.5f), 0, 255.0f),
math_util::clamp<float>(origColor[2] + 2.0f * a * (randf0to1() - 0.5f), 0, 255.0f));
}
}
}
template <typename T>
cv::Mat_<T> downscale(const cv::Mat_<T>& src, int factor) {
CHECK_EQ(0, src.cols % factor) << src.cols << factor;
CHECK_EQ(0, src.rows % factor) << src.rows << factor;
cv::Mat_<T> dst;
resize(src, dst, cv::Size(src.cols / factor, src.rows / factor), 0, 0, cv::INTER_AREA);
return dst;
}
// return (rgb, 1/depth) as Mat
std::pair<cv::Mat_<cv::Vec3f>, cv::Mat_<float>> renderMonoEquirect(
const std::vector<Triangle>& triangles,
const BoundingVolumeHierarchy& bvh,
const int w,
const int h,
const cv::Mat_<cv::Vec3b>& skybox) {
const int aas = FLAGS_anti_alias_supersample;
cv::Mat_<cv::Vec3f> eqrImage(cv::Size(w * aas, h * aas));
cv::Mat_<float> eqrInvDepth(cv::Size(w * aas, h * aas));
for (int y = 0; y < eqrImage.rows; ++y) {
if (y % 100 == 0) {
LOG(INFO) << y;
}
for (int x = 0; x < eqrImage.cols; ++x) {
// theta increases counterclockwise, but x increases clockwise, hence the
// (1 - x/w) term in the equation for theta below.
const float theta = 2.0f * M_PI * (1.0f - (x + 0.5f) / float(eqrImage.cols));
const float phi = M_PI * (y + 0.5f) / float(eqrImage.rows);
const cv::Vec3f rayOrigin = cv::Vec3f(0.0, 0.0, 0.0);
const cv::Vec3f rayDir = cv::Vec3f(sin(phi) * cos(theta), sin(phi) * sin(theta), cos(phi));
const Ray ray(rayOrigin, rayDir);
const cv::Vec4f rgbd = traceRayToGetColor(ray, triangles, bvh, skybox);
eqrImage(y, x) = 255.0f * head3(rgbd);
eqrInvDepth(y, x) = math_util::clamp(1.0f / rgbd[3], 0.0f, 1.0f);
}
}
return std::make_pair(downscale(eqrImage, aas), downscale(eqrInvDepth, aas));
}
std::pair<cv::Mat_<cv::Vec3f>, cv::Mat_<cv::Vec3f>> renderStereoEquirect(
const std::vector<Triangle>& triangles,
const BoundingVolumeHierarchy& bvh,
const int w,
const int h,
const cv::Mat_<cv::Vec3b>& skybox) {
const int aas = FLAGS_anti_alias_supersample;
cv::Mat_<cv::Vec3f> eqrImageLeft(cv::Size(w * aas, h * aas));
cv::Mat_<cv::Vec3f> eqrImageRight(cv::Size(w * aas, h * aas));
for (int y = 0; y < eqrImageLeft.rows; ++y) {
if (y % 100 == 0) {
LOG(INFO) << y;
}
for (int x = 0; x < eqrImageLeft.cols; ++x) {
// theta increases counterclockwise, but x increases clockwise, hence the
// (1 - x/w) term in the equation for theta below.
const float theta = 2.0f * M_PI * (1.0f - (x + 0.5f) / float(eqrImageLeft.cols));
const float phi = M_PI * (y + 0.5f) / float(eqrImageLeft.rows);
const cv::Vec3f rayOriginLeft =
cv::Vec3f(cos(theta + M_PI / 2.0f), sin(theta + M_PI / 2.0f), 0.0f) *
FLAGS_interpupillary_radius;
const cv::Vec3f rayOriginRight =
cv::Vec3f(cos(theta - M_PI / 2.0f), sin(theta - M_PI / 2.0f), 0.0f) *
FLAGS_interpupillary_radius;
const cv::Vec3f rayDir = cv::Vec3f(sin(phi) * cos(theta), sin(phi) * sin(theta), cos(phi));
const cv::Vec3f rayColorLeft =
head3(traceRayToGetColor(Ray(rayOriginLeft, rayDir), triangles, bvh, skybox));
const cv::Vec3f rayColorRight =
head3(traceRayToGetColor(Ray(rayOriginRight, rayDir), triangles, bvh, skybox));
eqrImageLeft(y, x) = rayColorLeft * 255.0f;
eqrImageRight(y, x) = rayColorRight * 255.0f;
}
}
return std::make_pair(downscale(eqrImageLeft, aas), downscale(eqrImageRight, aas));
}
void renderCamera(
const Camera& cam,
const std::vector<Triangle>& triangles,
const BoundingVolumeHierarchy& bvh,
const cv::Mat_<cv::Vec3b>& skybox,
cv::Mat_<cv::Vec3f>& destImage,
cv::Mat_<float>& destDepthMap) {
const int aas = FLAGS_anti_alias_supersample;
cv::Mat_<cv::Vec3f> image(cv::Size(cam.resolution.x() * aas, cam.resolution.y() * aas));
cv::Mat_<float> depthMap(image.size());
for (int y = 0; y < image.rows; ++y) {
if (y % 100 == 0) {
LOG(INFO) << y;
}
for (int x = 0; x < image.cols; ++x) {
const Camera::Vector2 pixel((x + 0.5f) / aas, (y + 0.5f) / aas);
cv::Vec4f colorAndDepth;
if (cam.isOutsideImageCircle(pixel)) {
colorAndDepth = {0, 0, 0, FLT_MAX};
} else {
const Camera::Ray rig = cam.rig(pixel);
const Ray rigCv(
cv::Vec3f(rig.origin().x(), rig.origin().y(), rig.origin().z()),
cv::Vec3f(rig.direction().x(), rig.direction().y(), rig.direction().z()));
colorAndDepth = traceRayToGetColor(rigCv, triangles, bvh, skybox);
}
image(y, x) = 255.0f * head3(colorAndDepth);
depthMap(y, x) = colorAndDepth[3];
}
}
destImage = downscale(image, aas);
destDepthMap = downscale(depthMap, aas);
corruptImageWithNoise(destImage);
}
void renderCamerasThreaded(
const cv::Mat_<cv::Vec3b>& skybox,
const std::vector<Triangle>& triangles,
const BoundingVolumeHierarchy& bvh,
const std::vector<Camera>& cameras,
const std::string destDir) {
std::vector<std::thread> renderThreads;
std::vector<cv::Mat_<cv::Vec3f>> images(cameras.size(), cv::Mat_<cv::Vec3f>());
std::vector<cv::Mat_<float>> depthMaps(cameras.size(), cv::Mat_<float>());
for (int i = 0; i < int(cameras.size()); ++i) {
LOG(INFO) << folly::sformat("------ rendering camera {}", i);
renderThreads.emplace_back(
renderCamera,
std::ref(cameras[i]),
std::ref(triangles),
std::ref(bvh),
std::ref(skybox),
std::ref(images[i]),
std::ref(depthMaps[i]));
}
for (auto& renderThread : renderThreads) {
renderThread.join();
}
for (int i = 0; i < int(images.size()); ++i) {
imwriteExceptionOnFail(destDir + "/" + cameras[i].id + ".png", images[i]);
imwriteExceptionOnFail(destDir + "/" + cameras[i].id + "_depth.png", depthMaps[i]);
writeCvMat32FC1ToPFM(destDir + "/" + cameras[i].id + "_depth.pfm", depthMaps[i]);
}
}
int main(int argc, char** argv) {
system_util::initDep(argc, argv, kUsageMessage);
CHECK_NE(FLAGS_mode, "");
CHECK_NE(FLAGS_skybox_path, "");
// load skybox
cv::Mat_<cv::Vec3b> skybox = imreadExceptionOnFail(FLAGS_skybox_path, cv::IMREAD_COLOR);
// construct scene of triangles
std::vector<Triangle> triangles;
if (FLAGS_scene == "icosahedron") {
makeIcosahedronScene(triangles);
} else if (FLAGS_scene == "cube") {
makeCubesScene(triangles);
} else if (FLAGS_scene == "ground_plane") {
makeGroundPlaneScene(triangles);
} else {
CHECK(false) << "unexpected scene: " << FLAGS_scene;
}
// bind indices of triangles. we need these to know how to color them after
// finding the ray-bvh intersection.
for (int i = 0; i < int(triangles.size()); ++i) {
triangles[i].selfIdx = i;
}
// build bounding volume hierarchy
LOG(INFO) << "building BVH";
const static int kBVHStopNumTrianglesInLeaf = 20;
const static int kBVHSplitK = 5;
const static int kBVHMaxDepth = 50;
BoundingVolumeHierarchy bvh = BoundingVolumeHierarchy::makeBVH(
triangles,
kBVHStopNumTrianglesInLeaf,
kBVHSplitK,
0, // start at depth = 0
kBVHMaxDepth);
if (FLAGS_mode == "mono_eqr") {
CHECK_NE(FLAGS_dest_mono, "");
CHECK_NE(FLAGS_dest_mono_depth, "");
cv::Mat_<cv::Vec3f> monoEquirect;
cv::Mat_<float> monoEquirectInvDepth;
std::tie(monoEquirect, monoEquirectInvDepth) =
renderMonoEquirect(triangles, bvh, FLAGS_eqr_width, FLAGS_eqr_height, skybox);
imwriteExceptionOnFail(FLAGS_dest_mono, monoEquirect);
imwriteExceptionOnFail(FLAGS_dest_mono_depth, monoEquirectInvDepth * 255.0);
} else if (FLAGS_mode == "stereo_eqr") {
CHECK_NE(FLAGS_dest_left, "");
CHECK_NE(FLAGS_dest_right, "");
CHECK_NE(FLAGS_dest_stereo, "");
std::pair<cv::Mat_<cv::Vec3f>, cv::Mat_<cv::Vec3f>> eqrLeftRight =
renderStereoEquirect(triangles, bvh, FLAGS_eqr_width, FLAGS_eqr_height, skybox);
cv::Mat_<cv::Vec3f> stereoPair;
vconcat(eqrLeftRight.first, eqrLeftRight.second, stereoPair);
imwriteExceptionOnFail(FLAGS_dest_left, eqrLeftRight.first);
imwriteExceptionOnFail(FLAGS_dest_right, eqrLeftRight.second);
imwriteExceptionOnFail(FLAGS_dest_stereo, stereoPair);
} else {
std::vector<Camera> cameras;
if (FLAGS_mode == "pinhole_ring") {
cameras = makeHorizontalRingOfPinholeCameras(
FLAGS_num_cams_in_ring,
FLAGS_rig_radius,
FLAGS_pinhole_width,
FLAGS_pinhole_height,
FLAGS_pinhole_fov_horizontal,
FLAGS_pinhole_aspect_ratio);
} else if (FLAGS_mode == "ftheta_ring") {
cameras = makeHorizontalRingOfFThetaCameras(
FLAGS_num_cams_in_ring,
FLAGS_rig_radius,
FLAGS_ftheta_width,
FLAGS_ftheta_height,
FLAGS_ftheta_image_circle_radius,
FLAGS_ftheta_image_circle_fov);
// add top camera, too
addTopCamera(
cameras,
FLAGS_ftheta_width,
FLAGS_ftheta_height,
FLAGS_ftheta_image_circle_radius,
FLAGS_ftheta_image_circle_fov);
} else if (FLAGS_mode == "dodecahedron") {
cameras = makeDodecahedronOfFThetaCameras(
FLAGS_rig_radius,
FLAGS_ftheta_width,
FLAGS_ftheta_height,
FLAGS_ftheta_image_circle_radius,
FLAGS_ftheta_image_circle_fov);
} else if (FLAGS_mode == "icosahedron") {
cameras = makeIcosahedronOfFThetaCameras(
FLAGS_rig_radius,
FLAGS_ftheta_width,
FLAGS_ftheta_height,
FLAGS_ftheta_image_circle_radius,
FLAGS_ftheta_image_circle_fov);
} else if (FLAGS_mode == "rig_from_json") {
CHECK_NE(FLAGS_rig_in, "");
cameras = Camera::loadRig(FLAGS_rig_in);
} else {
CHECK(false) << "unexpected mode: " << FLAGS_mode;
}
if (!FLAGS_rig_out.empty()) {
const std::vector<std::string> comments = {};
const int doubleNumDigits = 10;
Camera::saveRig(FLAGS_rig_out, cameras, comments, doubleNumDigits);
}
if (!FLAGS_dest_cam_images.empty()) {
renderCamerasThreaded(skybox, triangles, bvh, cameras, FLAGS_dest_cam_images);
}
}
return EXIT_SUCCESS;
}