aurora-rendering-engine/experiments/rt5.cpp

831 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// cornell_patchstep_fixed.cpp
// g++ -std=c++17 -O2 cornell_patchstep_fixed.cpp -o render && ./render
// 输出out.ppm
//
// 修复点(对应你的反馈):
// - 之前递归时“下一级 viewport 在最终图片中占哪块区域”的信息虽然隐含在 vm.screenUV/clip 中,
// 但逻辑上不够显式,容易在扩展/改动时导致下级按“全屏”比例写入。
// - 现在为 renderTriangleWithTriangle / renderViewport 显式增加参数:
// `viewportRegion` = 当前 viewport 在最终图片中的屏幕UV三角形(3个二维坐标)。
// - 所有“投影到屏幕”的计算都通过 viewportRegion 完成;所有写入也强制 clip 在 viewportRegion 内。
// - 向下递归时,会根据“当前三角形在当前 viewport 上的投影 triScreenUV”计算出下一级 viewportRegion 并传入。
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <limits>
#include <optional>
#include <string>
#include <utility>
#include <vector>
static constexpr double kEps = 1e-9;
static inline double clamp01(double x) {
return x < 0.0 ? 0.0 : (x > 1.0 ? 1.0 : x);
}
static inline int clampi(int x, int lo, int hi) {
return x < lo ? lo : (x > hi ? hi : x);
}
struct Vec2 {
double x = 0, y = 0;
Vec2() = default;
Vec2(double xx, double yy) : x(xx), y(yy) {
}
Vec2 operator+(const Vec2 &o) const {
return Vec2 { x + o.x, y + o.y };
}
Vec2 operator-(const Vec2 &o) const {
return Vec2 { x - o.x, y - o.y };
}
Vec2 operator*(double s) const {
return Vec2 { x * s, y * s };
}
};
static inline double dot(const Vec2 &a, const Vec2 &b) {
return a.x * b.x + a.y * b.y;
}
static inline double cross2(const Vec2 &a, const Vec2 &b) {
return a.x * b.y - a.y * b.x;
}
struct Vec3 {
double x = 0, y = 0, z = 0;
Vec3() = default;
Vec3(double xx, double yy, double zz) : x(xx), y(yy), z(zz) {
}
Vec3 operator+(const Vec3 &o) const {
return Vec3 { x + o.x, y + o.y, z + o.z };
}
Vec3 operator-(const Vec3 &o) const {
return Vec3 { x - o.x, y - o.y, z - o.z };
}
Vec3 operator*(double s) const {
return Vec3 { x * s, y * s, z * s };
}
Vec3 operator/(double s) const {
return Vec3 { x / s, y / s, z / s };
}
};
static inline Vec3 operator*(double s, const Vec3 &v) {
return v * s;
}
static inline double dot(const Vec3 &a, const Vec3 &b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static inline Vec3 cross(const Vec3 &a, const Vec3 &b) {
return Vec3 {
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x,
};
}
static inline double length(const Vec3 &v) {
return std::sqrt(dot(v, v));
}
static inline Vec3 normalize(const Vec3 &v) {
double len = length(v);
if (len < kEps)
return Vec3 { 0, 0, 0 };
return v / len;
}
struct Plane {
Vec3 n; // unit normal
double d; // n·x + d = 0
};
static inline Plane planeFromTriangle(const Vec3 &a, const Vec3 &b, const Vec3 &c) {
Vec3 n = normalize(cross(b - a, c - a));
double d = -dot(n, a);
return Plane { n, d };
}
static inline double planeSignedDistance(const Plane &p, const Vec3 &x) {
return dot(p.n, x) + p.d;
}
static inline Vec3 reflectPointAcrossPlane(const Vec3 &point, const Plane &p) {
double dist = planeSignedDistance(p, point);
return point - 2.0 * dist * p.n;
}
struct Basis2DOnPlane {
Vec3 origin;
Vec3 ux;
Vec3 uy;
Vec3 n;
};
static inline Basis2DOnPlane makePlaneBasis(const Vec3 &a, const Vec3 &b, const Vec3 &c) {
Vec3 n = normalize(cross(b - a, c - a));
Vec3 ux = normalize(b - a);
Vec3 uy = normalize(cross(n, ux));
return Basis2DOnPlane { a, ux, uy, n };
}
static inline Vec2 to2D(const Basis2DOnPlane &basis, const Vec3 &p) {
Vec3 v = p - basis.origin;
return Vec2 { dot(v, basis.ux), dot(v, basis.uy) };
}
static inline Vec3 barycentric2D(const Vec2 &p, const Vec2 &a, const Vec2 &b, const Vec2 &c) {
Vec2 v0 = b - a;
Vec2 v1 = c - a;
Vec2 v2 = p - a;
double den = cross2(v0, v1);
if (std::fabs(den) < kEps)
return Vec3 { -1, -1, -1 };
double v = cross2(v2, v1) / den;
double w = cross2(v0, v2) / den;
double u = 1.0 - v - w;
return Vec3 { u, v, w };
}
static inline bool pointInTri2D(const Vec2 &p, const Vec2 &a, const Vec2 &b, const Vec2 &c) {
Vec3 bc = barycentric2D(p, a, b, c);
return bc.x >= -1e-12 && bc.y >= -1e-12 && bc.z >= -1e-12;
}
static inline bool segIntersect2D(const Vec2 &a, const Vec2 &b, const Vec2 &c, const Vec2 &d) {
auto orient = [](const Vec2 &p, const Vec2 &q, const Vec2 &r) {
return cross2(q - p, r - p);
};
double o1 = orient(a, b, c);
double o2 = orient(a, b, d);
double o3 = orient(c, d, a);
double o4 = orient(c, d, b);
auto onSeg = [](const Vec2 &p, const Vec2 &q, const Vec2 &r) {
return std::min(p.x, r.x) - 1e-12 <= q.x && q.x <= std::max(p.x, r.x) + 1e-12 && std::min(p.y, r.y) - 1e-12 <= q.y && q.y <= std::max(p.y, r.y) + 1e-12;
};
if ((o1 * o2 < 0) && (o3 * o4 < 0))
return true;
if (std::fabs(o1) < 1e-12 && onSeg(a, c, b))
return true;
if (std::fabs(o2) < 1e-12 && onSeg(a, d, b))
return true;
if (std::fabs(o3) < 1e-12 && onSeg(c, a, d))
return true;
if (std::fabs(o4) < 1e-12 && onSeg(c, b, d))
return true;
return false;
}
static inline bool triOverlap2D(const std::array<Vec2, 3> &t0, const std::array<Vec2, 3> &t1) {
for (int i = 0; i < 3; i++) {
if (pointInTri2D(t0[i], t1[0], t1[1], t1[2]))
return true;
if (pointInTri2D(t1[i], t0[0], t0[1], t0[2]))
return true;
}
for (int i = 0; i < 3; i++) {
Vec2 a0 = t0[i], b0 = t0[(i + 1) % 3];
for (int j = 0; j < 3; j++) {
Vec2 a1 = t1[j], b1 = t1[(j + 1) % 3];
if (segIntersect2D(a0, b0, a1, b1))
return true;
}
}
return false;
}
// 3D 三角形相交满足你“Triangle类及相关运算”的要求渲染主流程不依赖它
static inline void triAABB(const Vec3 &a, const Vec3 &b, const Vec3 &c, Vec3 &mn, Vec3 &mx) {
mn = Vec3 { std::min({ a.x, b.x, c.x }), std::min({ a.y, b.y, c.y }), std::min({ a.z, b.z, c.z }) };
mx = Vec3 { std::max({ a.x, b.x, c.x }), std::max({ a.y, b.y, c.y }), std::max({ a.z, b.z, c.z }) };
}
static inline bool aabbOverlap(const Vec3 &mn0, const Vec3 &mx0, const Vec3 &mn1, const Vec3 &mx1) {
return (mn0.x <= mx1.x + 1e-12 && mx0.x + 1e-12 >= mn1.x) && (mn0.y <= mx1.y + 1e-12 && mx0.y + 1e-12 >= mn1.y) && (mn0.z <= mx1.z + 1e-12 && mx0.z + 1e-12 >= mn1.z);
}
static inline bool segmentTriangleIntersect(const Vec3 &p0, const Vec3 &p1,
const Vec3 &a, const Vec3 &b, const Vec3 &c,
double &tOut) {
Vec3 dir = p1 - p0;
Vec3 e1 = b - a;
Vec3 e2 = c - a;
Vec3 pvec = cross(dir, e2);
double det = dot(e1, pvec);
if (std::fabs(det) < 1e-12)
return false;
double invDet = 1.0 / det;
Vec3 tvec = p0 - a;
double u = dot(tvec, pvec) * invDet;
if (u < -1e-12 || u > 1.0 + 1e-12)
return false;
Vec3 qvec = cross(tvec, e1);
double v = dot(dir, qvec) * invDet;
if (v < -1e-12 || u + v > 1.0 + 1e-12)
return false;
double t = dot(e2, qvec) * invDet;
if (t < -1e-12 || t > 1.0 + 1e-12)
return false;
tOut = t;
return true;
}
static inline bool pointInTriangle3D(const Vec3 &p, const Vec3 &a, const Vec3 &b, const Vec3 &c) {
Vec3 n = cross(b - a, c - a);
Vec3 n0 = cross(b - a, p - a);
Vec3 n1 = cross(c - b, p - b);
Vec3 n2 = cross(a - c, p - c);
if (dot(n, n0) < -1e-12)
return false;
if (dot(n, n1) < -1e-12)
return false;
if (dot(n, n2) < -1e-12)
return false;
return true;
}
static inline bool triTriIntersect3D(const Vec3 &a0, const Vec3 &b0, const Vec3 &c0,
const Vec3 &a1, const Vec3 &b1, const Vec3 &c1) {
Vec3 mn0, mx0, mn1, mx1;
triAABB(a0, b0, c0, mn0, mx0);
triAABB(a1, b1, c1, mn1, mx1);
if (!aabbOverlap(mn0, mx0, mn1, mx1))
return false;
Plane p0 = planeFromTriangle(a0, b0, c0);
Plane p1 = planeFromTriangle(a1, b1, c1);
if (std::fabs(dot(p0.n, p1.n)) > 1.0 - 1e-7 && std::fabs(planeSignedDistance(p0, a1)) < 1e-7) {
Vec3 nn = p0.n;
int drop = 0;
double ax = std::fabs(nn.x), ay = std::fabs(nn.y), az = std::fabs(nn.z);
if (ay > ax && ay > az)
drop = 1;
else if (az > ax && az > ay)
drop = 2;
auto proj = [&](const Vec3 &v) -> Vec2 {
if (drop == 0)
return Vec2 { v.y, v.z };
if (drop == 1)
return Vec2 { v.x, v.z };
return Vec2 { v.x, v.y };
};
std::array<Vec2, 3> t0 { proj(a0), proj(b0), proj(c0) };
std::array<Vec2, 3> t1 { proj(a1), proj(b1), proj(c1) };
return triOverlap2D(t0, t1);
}
const Vec3 t0v[3] = { a0, b0, c0 };
const Vec3 t1v[3] = { a1, b1, c1 };
for (int i = 0; i < 3; i++) {
double t = 0;
if (segmentTriangleIntersect(t0v[i], t0v[(i + 1) % 3], a1, b1, c1, t))
return true;
if (segmentTriangleIntersect(t1v[i], t1v[(i + 1) % 3], a0, b0, c0, t))
return true;
}
if (pointInTriangle3D(a0, a1, b1, c1))
return true;
if (pointInTriangle3D(a1, a0, b0, c0))
return true;
return false;
}
struct Image {
int w = 0, h = 0;
std::vector<Vec3> pix;
Image(int ww, int hh) : w(ww), h(hh), pix((size_t)ww * (size_t)hh, Vec3 { 0, 0, 0 }) {
}
void blendOver(int x, int y, const Vec3 &src, double alpha) {
if (x < 0 || x >= w || y < 0 || y >= h)
return;
alpha = clamp01(alpha);
Vec3 dst = pix[(size_t)y * (size_t)w + (size_t)x];
Vec3 out = dst * (1.0 - alpha) + src * alpha;
pix[(size_t)y * (size_t)w + (size_t)x] = out;
}
void add(int x, int y, const Vec3 &src) {
if (x < 0 || x >= w || y < 0 || y >= h)
return;
Vec3 dst = pix[(size_t)y * (size_t)w + (size_t)x];
pix[(size_t)y * (size_t)w + (size_t)x] = Vec3 { dst.x + src.x, dst.y + src.y, dst.z + src.z };
}
void writePPM(const std::string &path) const {
std::ofstream out(path, std::ios::binary);
if (!out) {
std::cerr << "Failed to open output file: " << path << "\n";
return;
}
out << "P6\n"
<< w << " " << h << "\n255\n";
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
Vec3 c = pix[(size_t)y * (size_t)w + (size_t)x];
c.x = clamp01(c.x);
c.y = clamp01(c.y);
c.z = clamp01(c.z);
auto toByte = [](double v) -> unsigned char {
v = std::pow(clamp01(v), 1.0 / 2.2);
int iv = (int)std::lround(v * 255.0);
return (unsigned char)clampi(iv, 0, 255);
};
unsigned char rgb[3] = { toByte(c.x), toByte(c.y), toByte(c.z) };
out.write((char *)rgb, 3);
}
}
}
};
enum class MaterialType {
Curtain,
Diffuse,
Metal
};
struct Triangle {
int id = -1;
std::string name;
Vec3 p[3];
Vec2 uv[3];
MaterialType mat = MaterialType::Diffuse;
Vec3 baseColor { 1, 1, 1 };
Vec3 normal() const {
return normalize(cross(p[1] - p[0], p[2] - p[0]));
}
Vec3 centroid() const {
return (p[0] + p[1] + p[2]) / 3.0;
}
};
struct ProjectedVertex {
bool ok = false;
Vec3 onPlane;
double t = 0.0;
};
static inline ProjectedVertex projectPointToPlaneAlongOrigin(const Vec3 &origin, const Vec3 &v, const Plane &plane) {
Vec3 dir = v - origin;
double denom = dot(plane.n, dir);
if (std::fabs(denom) < 1e-12)
return ProjectedVertex { false, Vec3 {}, 0 };
double t = -(dot(plane.n, origin) + plane.d) / denom;
if (t <= 1e-9)
return ProjectedVertex { false, Vec3 {}, t };
Vec3 p = origin + dir * t;
return ProjectedVertex { true, p, t };
}
// 显式的“当前 viewport 在最终图片中的范围”
// 用屏幕UV三角形表达更贴合你描述的“两个三角形幕布”与递归中“面片->面片”的映射)
using ScreenTri = std::array<Vec2, 3>;
static inline bool pointInScreenTri(const ScreenTri &tri, const Vec2 &uv) {
return pointInTri2D(uv, tri[0], tri[1], tri[2]);
}
static inline Vec2 uvFromPixelCenter(int x, int y, int w, int h) {
double u = ((double)x + 0.5) / (double)w;
double v = 1.0 - (((double)y + 0.5) / (double)h);
return Vec2 { u, v };
}
static inline std::array<Vec2, 3> projectedTriangleToViewport2D(const Basis2DOnPlane &basis,
const std::array<Vec3, 3> &proj) {
return { to2D(basis, proj[0]), to2D(basis, proj[1]), to2D(basis, proj[2]) };
}
static inline std::array<Vec2, 3> viewportTriangleTo2D(const Basis2DOnPlane &basis, const Triangle &viewport) {
return {
to2D(basis, viewport.p[0]),
to2D(basis, viewport.p[1]),
to2D(basis, viewport.p[2]),
};
}
static inline bool projectTriangleToViewport(const Triangle &tri,
const Vec3 &origin,
const Triangle &viewport,
const Basis2DOnPlane &viewportBasis,
const ScreenTri &viewportRegion,
std::array<Vec3, 3> &projOnViewportPlane,
ScreenTri &triScreenUV) {
Plane plane = planeFromTriangle(viewport.p[0], viewport.p[1], viewport.p[2]);
ProjectedVertex pv[3];
for (int i = 0; i < 3; i++) {
pv[i] = projectPointToPlaneAlongOrigin(origin, tri.p[i], plane);
if (!pv[i].ok)
return false;
projOnViewportPlane[i] = pv[i].onPlane;
}
auto proj2D = projectedTriangleToViewport2D(viewportBasis, projOnViewportPlane);
auto vp2D = viewportTriangleTo2D(viewportBasis, viewport);
if (!triOverlap2D(proj2D, vp2D))
return false;
// 用 viewport 顶点 <-> viewportRegion 的三点对应建立“viewport平面 -> 屏幕UV”的仿射映射
Vec2 A2 = vp2D[0], B2 = vp2D[1], C2 = vp2D[2];
for (int i = 0; i < 3; i++) {
Vec3 bc = barycentric2D(proj2D[i], A2, B2, C2);
Vec2 uv = viewportRegion[0] * bc.x + viewportRegion[1] * bc.y + viewportRegion[2] * bc.z;
triScreenUV[i] = uv;
}
return true;
}
static inline double triAreaPixels(const ScreenTri &screenUV, int w, int h) {
Vec2 a = Vec2 { screenUV[0].x * w, screenUV[0].y * h };
Vec2 b = Vec2 { screenUV[1].x * w, screenUV[1].y * h };
Vec2 c = Vec2 { screenUV[2].x * w, screenUV[2].y * h };
double area2 = std::fabs(cross2(b - a, c - a));
return area2 * 0.5;
}
static inline Vec3 sampleProceduralTexture(const Triangle &tri, const Vec2 &uv) {
double u = uv.x, v = uv.y;
double s = 10.0;
int iu = (int)std::floor(u * s);
int iv = (int)std::floor(v * s);
bool checker = ((iu + iv) & 1) == 0;
Vec3 base = tri.baseColor;
if (tri.mat == MaterialType::Metal) {
double stripe = 0.6 + 0.4 * std::sin((u * 40.0 + v * 15.0) * 3.14159);
base = base * stripe;
} else {
double k = checker ? 1.0 : 0.85;
base = base * k;
}
return base;
}
static inline Vec3 shadeLambert(const Vec3 &color, const Vec3 &n) {
Vec3 lightDir = normalize(Vec3 { -0.35, 0.85, 0.25 });
double ndl = std::max(0.0, dot(n, lightDir));
double amb = 0.20;
double diff = 0.90 * ndl;
return color * (amb + diff);
}
static inline void rasterizeTriangleToImage(Image &img,
const ScreenTri &triScreenUV,
const Triangle &tri,
const ScreenTri &viewportRegion,
const Vec3 &throughput,
double alpha,
bool additive) {
// 强制只写入 viewportRegion 覆盖的屏幕区域
// triScreenUV 是“当前 tri 投影到当前 viewport 后,再映射到最终屏幕”的三角形
double minU = std::min({ triScreenUV[0].x, triScreenUV[1].x, triScreenUV[2].x });
double maxU = std::max({ triScreenUV[0].x, triScreenUV[1].x, triScreenUV[2].x });
double minV = std::min({ triScreenUV[0].y, triScreenUV[1].y, triScreenUV[2].y });
double maxV = std::max({ triScreenUV[0].y, triScreenUV[1].y, triScreenUV[2].y });
int x0 = clampi((int)std::floor(minU * img.w) - 1, 0, img.w - 1);
int x1 = clampi((int)std::ceil(maxU * img.w) + 1, 0, img.w - 1);
int y0 = clampi((int)std::floor((1.0 - maxV) * img.h) - 1, 0, img.h - 1);
int y1 = clampi((int)std::ceil((1.0 - minV) * img.h) + 1, 0, img.h - 1);
Vec2 a = triScreenUV[0], b = triScreenUV[1], c = triScreenUV[2];
Vec3 n = tri.normal();
for (int y = y0; y <= y1; y++) {
for (int x = x0; x <= x1; x++) {
Vec2 suv = uvFromPixelCenter(x, y, img.w, img.h);
// 关键:限制写入区域为当前 viewportRegion
if (!pointInScreenTri(viewportRegion, suv))
continue;
Vec3 bc = barycentric2D(suv, a, b, c);
if (bc.x < -1e-12 || bc.y < -1e-12 || bc.z < -1e-12)
continue;
Vec2 uv = tri.uv[0] * bc.x + tri.uv[1] * bc.y + tri.uv[2] * bc.z;
Vec3 tex = sampleProceduralTexture(tri, uv);
Vec3 lit = shadeLambert(tex, n);
Vec3 src = Vec3 { lit.x * throughput.x, lit.y * throughput.y, lit.z * throughput.z };
if (additive)
img.add(x, y, src * alpha);
else
img.blendOver(x, y, src, alpha);
}
}
}
struct RenderConfig {
int maxDepth = 3;
double minAreaPixels = 3.0;
};
static void renderViewport(const std::vector<Triangle> &scene,
const Vec3 &origin,
const Triangle &viewport,
const Basis2DOnPlane &viewportBasis,
const ScreenTri &viewportRegion,
Image &out,
const RenderConfig &cfg,
int depth,
const Vec3 &throughput,
int excludeId);
static inline double fresnelSchlick(double cosTheta, double f0) {
cosTheta = clamp01(cosTheta);
return f0 + (1.0 - f0) * std::pow(1.0 - cosTheta, 5.0);
}
static void renderTriangleWithTriangle(const std::vector<Triangle> &scene,
const Vec3 &origin,
const Triangle &viewport,
const Basis2DOnPlane &viewportBasis,
const ScreenTri &viewportRegion,
const Triangle &tri,
Image &out,
const RenderConfig &cfg,
int depth,
const Vec3 &throughput,
int excludeId) {
if (tri.id == excludeId)
return;
if (tri.mat == MaterialType::Curtain)
return;
std::array<Vec3, 3> projOnPlane {};
ScreenTri triScreenUV {};
if (!projectTriangleToViewport(tri, origin, viewport, viewportBasis, viewportRegion, projOnPlane, triScreenUV))
return;
double areaPix = triAreaPixels(triScreenUV, out.w, out.h);
if (areaPix < 0.25)
return;
if (tri.mat == MaterialType::Diffuse) {
// 漫反射:直接贴到最终屏幕,但写入范围必须受 viewportRegion 限制(已在 rasterize 内处理)
rasterizeTriangleToImage(out, triScreenUV, tri, viewportRegion, throughput, 1.0, false);
return;
}
if (tri.mat == MaterialType::Metal) {
// 金属:先贴自身底色,再递归把反射结果“写到它在最终屏幕上的 triScreenUV 区域里”
Vec3 n = tri.normal();
Vec3 viewDir = normalize(origin - tri.centroid());
double cosTheta = std::fabs(dot(n, viewDir));
double reflectK = 0.75;
double f0 = 0.90;
double F = fresnelSchlick(cosTheta, f0);
double refl = clamp01(reflectK * F);
rasterizeTriangleToImage(out, triScreenUV, tri, viewportRegion, throughput, 1.0, false);
if (depth >= cfg.maxDepth)
return;
if (areaPix < cfg.minAreaPixels)
return;
Plane triPlane = planeFromTriangle(tri.p[0], tri.p[1], tri.p[2]);
Vec3 reflectedOrigin = reflectPointAcrossPlane(origin, triPlane);
// 关键:下一级 viewport 就是 tri世界空间而它在最终图片中的区域就是 triScreenUV
const Triangle &childViewport = tri;
Basis2DOnPlane childBasis = makePlaneBasis(childViewport.p[0], childViewport.p[1], childViewport.p[2]);
ScreenTri childRegion = triScreenUV;
Vec3 throughputChild = Vec3 { throughput.x * refl, throughput.y * refl, throughput.z * refl };
renderViewport(scene, reflectedOrigin, childViewport, childBasis, childRegion, out, cfg, depth + 1, throughputChild, tri.id);
return;
}
}
static void renderViewport(const std::vector<Triangle> &scene,
const Vec3 &origin,
const Triangle &viewport,
const Basis2DOnPlane &viewportBasis,
const ScreenTri &viewportRegion,
Image &out,
const RenderConfig &cfg,
int depth,
const Vec3 &throughput,
int excludeId) {
struct Cand {
const Triangle *t;
double dist2;
};
std::vector<Cand> cands;
cands.reserve(scene.size());
for (const Triangle &tri : scene) {
if (tri.id == excludeId)
continue;
if (tri.mat == MaterialType::Curtain)
continue;
std::array<Vec3, 3> projOnPlane {};
ScreenTri triScreenUV {};
if (!projectTriangleToViewport(tri, origin, viewport, viewportBasis, viewportRegion, projOnPlane, triScreenUV))
continue;
Vec3 c = tri.centroid();
Vec3 d = c - origin;
double dist2 = dot(d, d);
cands.push_back(Cand { &tri, dist2 });
}
std::sort(cands.begin(), cands.end(), [](const Cand &a, const Cand &b) {
return a.dist2 > b.dist2;
});
for (const Cand &c : cands) {
renderTriangleWithTriangle(scene, origin, viewport, viewportBasis, viewportRegion, *c.t, out, cfg, depth, throughput, excludeId);
}
}
struct Camera {
Vec3 origin;
Triangle curtainA;
Triangle curtainB;
void render(const std::vector<Triangle> &scene, Image &out) const {
RenderConfig cfg;
cfg.maxDepth = 3;
cfg.minAreaPixels = 3.0;
// Curtain AviewportRegion 就是它本身的 uv三角形覆盖屏幕的一半
{
const Triangle &viewport = curtainA;
Basis2DOnPlane basis = makePlaneBasis(viewport.p[0], viewport.p[1], viewport.p[2]);
ScreenTri region = { viewport.uv[0], viewport.uv[1], viewport.uv[2] };
renderViewport(scene, origin, viewport, basis, region, out, cfg, 0, Vec3 { 1, 1, 1 }, -1);
}
// Curtain B
{
const Triangle &viewport = curtainB;
Basis2DOnPlane basis = makePlaneBasis(viewport.p[0], viewport.p[1], viewport.p[2]);
ScreenTri region = { viewport.uv[0], viewport.uv[1], viewport.uv[2] };
renderViewport(scene, origin, viewport, basis, region, out, cfg, 0, Vec3 { 1, 1, 1 }, -1);
}
}
};
static inline Triangle makeTri(int id, const std::string &name,
const Vec3 &a, const Vec3 &b, const Vec3 &c,
const Vec2 &ua, const Vec2 &ub, const Vec2 &uc,
MaterialType mat, const Vec3 &color) {
Triangle t;
t.id = id;
t.name = name;
t.p[0] = a;
t.p[1] = b;
t.p[2] = c;
t.uv[0] = ua;
t.uv[1] = ub;
t.uv[2] = uc;
t.mat = mat;
t.baseColor = color;
return t;
}
static inline void addQuad(std::vector<Triangle> &out, int &nextId, const std::string &name,
const Vec3 &a, const Vec3 &b, const Vec3 &c, const Vec3 &d,
const Vec2 &ua, const Vec2 &ub, const Vec2 &uc, const Vec2 &ud,
MaterialType mat, const Vec3 &color) {
out.push_back(makeTri(nextId++, name + "_t0", a, b, c, ua, ub, uc, mat, color));
out.push_back(makeTri(nextId++, name + "_t1", a, c, d, ua, uc, ud, mat, color));
}
static inline void addBox(std::vector<Triangle> &out, int &nextId, const std::string &name,
const Vec3 &mn, const Vec3 &mx,
MaterialType mat, const Vec3 &color) {
Vec3 p000 { mn.x, mn.y, mn.z };
Vec3 p001 { mn.x, mn.y, mx.z };
Vec3 p010 { mn.x, mx.y, mn.z };
Vec3 p011 { mn.x, mx.y, mx.z };
Vec3 p100 { mx.x, mn.y, mn.z };
Vec3 p101 { mx.x, mn.y, mx.z };
Vec3 p110 { mx.x, mx.y, mn.z };
Vec3 p111 { mx.x, mx.y, mx.z };
Vec2 uv00 { 0, 0 }, uv10 { 1, 0 }, uv11 { 1, 1 }, uv01 { 0, 1 };
addQuad(out, nextId, name + "_front", p001, p101, p111, p011, uv00, uv10, uv11, uv01, mat, color);
addQuad(out, nextId, name + "_back", p100, p000, p010, p110, uv00, uv10, uv11, uv01, mat, color);
addQuad(out, nextId, name + "_left", p000, p001, p011, p010, uv00, uv10, uv11, uv01, mat, color);
addQuad(out, nextId, name + "_right", p101, p100, p110, p111, uv00, uv10, uv11, uv01, mat, color);
addQuad(out, nextId, name + "_top", p010, p011, p111, p110, uv00, uv10, uv11, uv01, mat, color);
addQuad(out, nextId, name + "_bottom", p000, p100, p101, p001, uv00, uv10, uv11, uv01, mat, color);
}
static std::vector<Triangle> buildCornellScene() {
std::vector<Triangle> scene;
int nextId = 0;
Vec3 mn { -1, -1, 0 };
Vec3 mx { 1, 1, 2 };
Vec2 uv00 { 0, 0 }, uv10 { 1, 0 }, uv11 { 1, 1 }, uv01 { 0, 1 };
addQuad(scene, nextId, "wall_back",
Vec3 { mn.x, mn.y, mn.z }, Vec3 { mx.x, mn.y, mn.z }, Vec3 { mx.x, mx.y, mn.z }, Vec3 { mn.x, mx.y, mn.z },
uv00, uv10, uv11, uv01, MaterialType::Diffuse, Vec3 { 0.95, 0.95, 0.95 });
addQuad(scene, nextId, "wall_left",
Vec3 { mn.x, mn.y, mx.z }, Vec3 { mn.x, mn.y, mn.z }, Vec3 { mn.x, mx.y, mn.z }, Vec3 { mn.x, mx.y, mx.z },
uv00, uv10, uv11, uv01, MaterialType::Diffuse, Vec3 { 0.90, 0.25, 0.25 });
addQuad(scene, nextId, "wall_right",
Vec3 { mx.x, mn.y, mn.z }, Vec3 { mx.x, mn.y, mx.z }, Vec3 { mx.x, mx.y, mx.z }, Vec3 { mx.x, mx.y, mn.z },
uv00, uv10, uv11, uv01, MaterialType::Diffuse, Vec3 { 0.25, 0.90, 0.25 });
addQuad(scene, nextId, "floor",
Vec3 { mn.x, mn.y, mx.z }, Vec3 { mx.x, mn.y, mx.z }, Vec3 { mx.x, mn.y, mn.z }, Vec3 { mn.x, mn.y, mn.z },
uv00, uv10, uv11, uv01, MaterialType::Diffuse, Vec3 { 0.95, 0.95, 0.95 });
addQuad(scene, nextId, "ceiling",
Vec3 { mn.x, mx.y, mn.z }, Vec3 { mx.x, mx.y, mn.z }, Vec3 { mx.x, mx.y, mx.z }, Vec3 { mn.x, mx.y, mx.z },
uv00, uv10, uv11, uv01, MaterialType::Diffuse, Vec3 { 0.95, 0.95, 0.95 });
// 蓝金属盒
{
Vec3 bmn { -0.75, -1.0, 0.75 };
Vec3 bmx { -0.15, -0.40, 1.35 };
addBox(scene, nextId, "box_blue_metal", bmn, bmx, MaterialType::Metal, Vec3 { 0.20, 0.40, 0.95 });
}
// 黄金属盒
{
Vec3 ymn { 0.15, -1.0, 1.10 };
Vec3 ymx { 0.70, -0.20, 1.70 };
addBox(scene, nextId, "box_yellow_metal", ymn, ymx, MaterialType::Metal, Vec3 { 0.95, 0.85, 0.20 });
}
// 小漫反射柱(辅助反射观感)
{
Vec3 dmn { -0.10, -1.0, 1.55 };
Vec3 dmx { 0.10, -0.70, 1.75 };
addBox(scene, nextId, "diffuse_pillar", dmn, dmx, MaterialType::Diffuse, Vec3 { 0.85, 0.85, 0.90 });
}
return scene;
}
int main() {
const int W = 800;
const int H = 800;
Image img(W, H);
Camera cam;
cam.origin = Vec3 { 0.0, 0.0, 3.0 };
// Curtain两个三角形
Vec3 s0 { -1.1, -1.1, 2.5 };
Vec3 s1 { 1.1, -1.1, 2.5 };
Vec3 s2 { 1.1, 1.1, 2.5 };
Vec3 s3 { -1.1, 1.1, 2.5 };
cam.curtainA = makeTri(-100, "curtainA",
s0, s1, s2,
Vec2 { 0, 0 }, Vec2 { 1, 0 }, Vec2 { 1, 1 },
MaterialType::Curtain, Vec3 { 0, 0, 0 });
cam.curtainB = makeTri(-101, "curtainB",
s0, s2, s3,
Vec2 { 0, 0 }, Vec2 { 1, 1 }, Vec2 { 0, 1 },
MaterialType::Curtain, Vec3 { 0, 0, 0 });
auto scene = buildCornellScene();
// 只是证明 tri-tri 相交存在,不影响渲染
if (scene.size() >= 2) {
(void)triTriIntersect3D(scene[0].p[0], scene[0].p[1], scene[0].p[2],
scene[1].p[0], scene[1].p[1], scene[1].p[2]);
}
cam.render(scene, img);
img.writePPM("out.ppm");
std::cout << "Wrote out.ppm (" << W << "x" << H << ")\n";
return 0;
}