// 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 #include #include #include #include #include #include #include #include #include #include #include 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 &t0, const std::array &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 t0 { proj(a0), proj(b0), proj(c0) }; std::array 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 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; 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 projectedTriangleToViewport2D(const Basis2DOnPlane &basis, const std::array &proj) { return { to2D(basis, proj[0]), to2D(basis, proj[1]), to2D(basis, proj[2]) }; } static inline std::array 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 &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 &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 &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 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 &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 cands; cands.reserve(scene.size()); for (const Triangle &tri : scene) { if (tri.id == excludeId) continue; if (tri.mat == MaterialType::Curtain) continue; std::array 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 &scene, Image &out) const { RenderConfig cfg; cfg.maxDepth = 3; cfg.minAreaPixels = 3.0; // Curtain A:viewportRegion 就是它本身的 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 &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 &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 buildCornellScene() { std::vector 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; }