// main.cpp // 一个“以面片为处理单位”的步进式递归渲染器(非逐像素光线追踪),输出 PPM。 // 思路要点: // - 相机 viewport 被拆成 2 个三角形 Curtain;渲染时遍历场景三角形,把三角形“投影变形”到当前 viewport 三角形平面上 // - 采用“由远到近”排序的 painter 覆盖关系(O(n log n)) // - Diffuse:只贴纹理/颜色(不递归) // - Metal:先贴自身,再把相机原点关于该金属三角形平面对称,令该金属三角形成为下一层 viewport,递归渲染反射并合成到金属区域 // // 编译:g++ -O2 -std=c++17 main.cpp -o render // 运行:./render // 输出:out.ppm #include #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 : (x > 1 ? 1 : x); } struct Vec2 { double x = 0, y = 0; Vec2() = default; Vec2(double xx, double yy) : x(xx), y(yy) { } Vec2 operator+(const Vec2 &r) const { return { x + r.x, y + r.y }; } Vec2 operator-(const Vec2 &r) const { return { x - r.x, y - r.y }; } Vec2 operator*(double s) const { return { x * s, y * s }; } }; static inline double cross2(const Vec2 &a, const Vec2 &b) { return a.x * b.y - a.y * b.x; } static inline double dot2(const Vec2 &a, const Vec2 &b) { return a.x * b.x + a.y * b.y; } 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 &r) const { return { x + r.x, y + r.y, z + r.z }; } Vec3 operator-(const Vec3 &r) const { return { x - r.x, y - r.y, z - r.z }; } Vec3 operator*(double s) const { return { x * s, y * s, z * s }; } Vec3 operator/(double s) const { return { x / s, y / s, z / s }; } Vec3 &operator+=(const Vec3 &r) { x += r.x; y += r.y; z += r.z; return *this; } }; static inline double dot3(const Vec3 &a, const Vec3 &b) { return a.x * b.x + a.y * b.y + a.z * b.z; } static inline Vec3 cross3(const Vec3 &a, const Vec3 &b) { return { 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 length3(const Vec3 &v) { return std::sqrt(dot3(v, v)); } static inline Vec3 normalize3(const Vec3 &v) { double len = length3(v); if (len < kEps) return { 0, 0, 0 }; return v / len; } static inline Vec3 hadamard(const Vec3 &a, const Vec3 &b) { return { a.x * b.x, a.y * b.y, a.z * b.z }; } struct ScreenTri { Vec2 p0, p1, p2; }; static inline double triArea2D(const Vec2 &a, const Vec2 &b, const Vec2 &c) { return 0.5 * std::abs(cross2(b - a, c - a)); } static inline double triArea2D(const ScreenTri &t) { return triArea2D(t.p0, t.p1, t.p2); } static inline std::array barycentric2D(const Vec2 &p, const Vec2 &a, const Vec2 &b, const Vec2 &c) { // 返回 (w0, w1, w2),满足 p = w0*a + w1*b + w2*c,且 w0+w1+w2=1(在非退化情况下) Vec2 v0 = b - a; Vec2 v1 = c - a; Vec2 v2 = p - a; double den = cross2(v0, v1); if (std::abs(den) < kEps) return { -1, -1, -1 }; double w1 = cross2(v2, v1) / den; double w2 = cross2(v0, v2) / den; double w0 = 1.0 - w1 - w2; return { w0, w1, w2 }; } static inline bool pointInTri2D(const Vec2 &p, const Vec2 &a, const Vec2 &b, const Vec2 &c, double eps = 1e-8) { auto w = barycentric2D(p, a, b, c); return w[0] >= -eps && w[1] >= -eps && w[2] >= -eps; } static inline int orient(const Vec2 &a, const Vec2 &b, const Vec2 &c) { double v = cross2(b - a, c - a); if (v > 1e-12) return 1; if (v < -1e-12) return -1; return 0; } static inline bool onSegment(const Vec2 &a, const Vec2 &b, const Vec2 &p) { return std::min(a.x, b.x) - 1e-12 <= p.x && p.x <= std::max(a.x, b.x) + 1e-12 && std::min(a.y, b.y) - 1e-12 <= p.y && p.y <= std::max(a.y, b.y) + 1e-12 && std::abs(cross2(b - a, p - a)) < 1e-10; } static inline bool segIntersects(const Vec2 &a, const Vec2 &b, const Vec2 &c, const Vec2 &d) { int o1 = orient(a, b, c); int o2 = orient(a, b, d); int o3 = orient(c, d, a); int o4 = orient(c, d, b); if (o1 != o2 && o3 != o4) return true; if (o1 == 0 && onSegment(a, b, c)) return true; if (o2 == 0 && onSegment(a, b, d)) return true; if (o3 == 0 && onSegment(c, d, a)) return true; if (o4 == 0 && onSegment(c, d, b)) return true; return false; } static inline bool triOverlap2D(const ScreenTri &A, const ScreenTri &B) { // 顶点包含测试 if (pointInTri2D(A.p0, B.p0, B.p1, B.p2) || pointInTri2D(A.p1, B.p0, B.p1, B.p2) || pointInTri2D(A.p2, B.p0, B.p1, B.p2)) return true; if (pointInTri2D(B.p0, A.p0, A.p1, A.p2) || pointInTri2D(B.p1, A.p0, A.p1, A.p2) || pointInTri2D(B.p2, A.p0, A.p1, A.p2)) return true; // 边相交测试 std::array a = { A.p0, A.p1, A.p2 }; std::array b = { B.p0, B.p1, B.p2 }; for (int i = 0; i < 3; i++) { Vec2 a0 = a[i], a1 = a[(i + 1) % 3]; for (int j = 0; j < 3; j++) { Vec2 b0 = b[j], b1 = b[(j + 1) % 3]; if (segIntersects(a0, a1, b0, b1)) return true; } } return false; } enum class MaterialType { Curtain, Diffuse, Metal }; struct Material { MaterialType type = MaterialType::Diffuse; Vec3 baseColor { 1, 1, 1 }; double reflectivity = 0.85; // 仅 Metal 使用 bool checker = false; double checkerScale = 8.0; }; struct Triangle { int id = -1; Vec3 v0, v1, v2; Vec2 uv0 { 0, 0 }, uv1 { 1, 0 }, uv2 { 0, 1 }; Material mat; Vec3 normal() const { return normalize3(cross3(v1 - v0, v2 - v0)); } Vec3 centroid() const { return (v0 + v1 + v2) / 3.0; } }; struct Image { int w = 0, h = 0; int ox = 0, oy = 0; // 全局坐标偏移(用于子 buffer) bool masked = false; // true:未写入像素默认“无效” std::vector pix; std::vector mask; // masked=true 时使用 Image() = default; Image(int ww, int hh, int offx = 0, int offy = 0, bool useMask = false) : w(ww), h(hh), ox(offx), oy(offy), masked(useMask), pix((size_t)ww * (size_t)hh, { 0, 0, 0 }) { if (masked) mask.assign((size_t)ww * (size_t)hh, 0); } bool inBoundsGlobal(int x, int y) const { return x >= ox && x < ox + w && y >= oy && y < oy + h; } size_t idxGlobal(int x, int y) const { return (size_t)(y - oy) * (size_t)w + (size_t)(x - ox); } Vec3 get(int x, int y) const { if (!inBoundsGlobal(x, y)) return { 0, 0, 0 }; size_t i = idxGlobal(x, y); if (masked && mask[i] == 0) return { 0, 0, 0 }; return pix[i]; } bool has(int x, int y) const { if (!inBoundsGlobal(x, y)) return false; if (!masked) return true; return mask[idxGlobal(x, y)] != 0; } void set(int x, int y, const Vec3 &c) { if (!inBoundsGlobal(x, y)) return; size_t i = idxGlobal(x, y); pix[i] = c; if (masked) mask[i] = 1; } void writePPM(const std::string &path) const { // 仅支持 ox=oy=0 且 masked=false 的整图输出 std::ofstream out(path, std::ios::binary); 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]; // gamma 2.2 auto toByte = [](double v) -> uint8_t { v = clamp01(v); v = std::pow(v, 1.0 / 2.2); int b = (int)std::lround(v * 255.0); if (b < 0) b = 0; if (b > 255) b = 255; return (uint8_t)b; }; uint8_t r = toByte(c.x); uint8_t g = toByte(c.y); uint8_t b = toByte(c.z); out.write((const char *)&r, 1); out.write((const char *)&g, 1); out.write((const char *)&b, 1); } } } }; struct ViewportFrame { Vec3 p0; // viewport v0 Vec3 n; // plane normal (unit) Vec3 axisX; // in-plane basis Vec3 axisY; // in-plane basis Vec2 v0_2 { 0, 0 }; Vec2 v1_2 { 0, 0 }; Vec2 v2_2 { 0, 0 }; }; static inline ViewportFrame makeViewportFrame(const Triangle &viewport) { ViewportFrame f; f.p0 = viewport.v0; Vec3 e01 = viewport.v1 - viewport.v0; Vec3 e02 = viewport.v2 - viewport.v0; f.n = normalize3(cross3(e01, e02)); f.axisX = normalize3(e01); f.axisY = normalize3(cross3(f.n, f.axisX)); f.v0_2 = { 0, 0 }; f.v1_2 = { dot3(e01, f.axisX), dot3(e01, f.axisY) }; f.v2_2 = { dot3(e02, f.axisX), dot3(e02, f.axisY) }; return f; } static inline Vec2 toViewport2D(const ViewportFrame &f, const Vec3 &p) { Vec3 d = p - f.p0; return { dot3(d, f.axisX), dot3(d, f.axisY) }; } static inline Vec3 reflectPointAcrossPlane(const Vec3 &p, const Vec3 &planePoint, const Vec3 &planeNormalUnit) { // 反射点:p' = p - 2*dot(n, (p-planePoint))*n double d = dot3(planeNormalUnit, p - planePoint); return p - planeNormalUnit * (2.0 * d); } struct ProjectedTri { ScreenTri screen; // 投影到最终输出(全局像素坐标)的三角形 std::array invW; // “投影深度倒数”等价量(用于透视矫正插值) }; static inline std::optional projectTriangleOntoViewport( const Triangle &tri, const Vec3 &origin, const Triangle &viewportWorld, const ScreenTri &viewportScreen, const ViewportFrame &vf) { // 把 tri 的三个顶点沿着 origin->vertex 的直线投影到 viewportWorld 的平面上, // 再通过 viewportWorld 的重心坐标映射到 viewportScreen(像素三角形)上。 Vec3 n = vf.n; Vec3 planeP = vf.p0; auto projectPoint = [&](const Vec3 &P, double &outT, Vec3 &outProj) -> bool { Vec3 dir = P - origin; double denom = dot3(n, dir); if (std::abs(denom) < 1e-12) return false; double t = dot3(n, planeP - origin) / denom; // 关键:t>1 表示平面在 P 的“更远处”,即 P 位于 origin 与平面之间(不应被该 viewport 看见) // t<=0 表示交点在 origin 反方向 if (!(t > 1e-9 && t <= 1.0 + 1e-7)) return false; outT = t; outProj = origin + dir * t; return true; }; std::array P = { tri.v0, tri.v1, tri.v2 }; std::array Q; std::array t; for (int i = 0; i < 3; i++) { if (!projectPoint(P[i], t[i], Q[i])) return std::nullopt; } // 投影点 Q 都在 viewport 平面上:计算它们相对 viewportWorld 的重心坐标 auto screenFromQ = [&](const Vec3 &q) -> std::optional { Vec2 q2 = toViewport2D(vf, q); auto w = barycentric2D(q2, vf.v0_2, vf.v1_2, vf.v2_2); if (w[0] < -1e-6 || w[1] < -1e-6 || w[2] < -1e-6) { // 允许在 viewport 外(仍可能与 viewport 相交),所以这里不做强制 inside // 只要不是退化即可 } if (!std::isfinite(w[0]) || !std::isfinite(w[1]) || !std::isfinite(w[2])) return std::nullopt; Vec2 s = viewportScreen.p0 * w[0] + viewportScreen.p1 * w[1] + viewportScreen.p2 * w[2]; return s; }; std::array S; for (int i = 0; i < 3; i++) { auto s = screenFromQ(Q[i]); if (!s) return std::nullopt; S[i] = *s; } ProjectedTri out; out.screen = { S[0], S[1], S[2] }; // 透视矫正:在“固定 origin + 固定 viewport 平面”下,t 等价于 1/depth(类比 plane z=1 时 t=1/z) out.invW = { t[0], t[1], t[2] }; return out; } static inline Vec3 sampleMaterial(const Triangle &tri, const Vec2 &uv) { // 简易纹理:checker 或纯色 Vec3 c = tri.mat.baseColor; if (tri.mat.checker) { double u = uv.x * tri.mat.checkerScale; double v = uv.y * tri.mat.checkerScale; int iu = (int)std::floor(u); int iv = (int)std::floor(v); bool odd = ((iu + iv) & 1) != 0; double k = odd ? 0.55 : 1.0; c = c * k; } return c; } static inline Vec3 shadeSimple(const Triangle &tri, const Vec3 &base) { // 简易定向光 + 环境光,避免因法线翻转导致全黑:使用 abs(dot) Vec3 n = tri.normal(); Vec3 lightDir = normalize3(Vec3 { -0.3, 0.9, -0.2 }); double ndl = std::abs(dot3(n, lightDir)); double ambient = 0.18; double diff = ambient + (1.0 - ambient) * ndl; return base * diff; } struct RasterMask { int w = 0, h = 0; int ox = 0, oy = 0; std::vector m; RasterMask() = default; RasterMask(int ww, int hh, int offx, int offy) : w(ww), h(hh), ox(offx), oy(offy), m((size_t)ww * (size_t)hh, 0) { } bool inBoundsGlobal(int x, int y) const { return x >= ox && x < ox + w && y >= oy && y < oy + h; } size_t idxGlobal(int x, int y) const { return (size_t)(y - oy) * (size_t)w + (size_t)(x - ox); } void set(int x, int y) { if (!inBoundsGlobal(x, y)) return; m[idxGlobal(x, y)] = 1; } bool get(int x, int y) const { if (!inBoundsGlobal(x, y)) return false; return m[idxGlobal(x, y)] != 0; } }; static inline void rasterizeProjectedTriangle( Image &target, const Triangle &tri, const ProjectedTri &proj, const ScreenTri &viewportScreen, RasterMask *outMaskOrNull) { // 在目标 target 上对 proj.screen 做三角形光栅化,并且裁剪在 viewportScreen 内(防止越界 spill) ScreenTri st = proj.screen; double minx = std::floor(std::min({ st.p0.x, st.p1.x, st.p2.x })); double maxx = std::ceil(std::max({ st.p0.x, st.p1.x, st.p2.x })); double miny = std::floor(std::min({ st.p0.y, st.p1.y, st.p2.y })); double maxy = std::ceil(std::max({ st.p0.y, st.p1.y, st.p2.y })); int ix0 = (int)minx; int ix1 = (int)maxx; int iy0 = (int)miny; int iy1 = (int)maxy; for (int y = iy0; y <= iy1; y++) { for (int x = ix0; x <= ix1; x++) { Vec2 p { (double)x + 0.5, (double)y + 0.5 }; if (!pointInTri2D(p, st.p0, st.p1, st.p2)) continue; if (!pointInTri2D(p, viewportScreen.p0, viewportScreen.p1, viewportScreen.p2)) continue; if (!target.inBoundsGlobal(x, y)) continue; auto w = barycentric2D(p, st.p0, st.p1, st.p2); if (w[0] < -1e-8 || w[1] < -1e-8 || w[2] < -1e-8) continue; // 透视矫正插值 UV double iw0 = proj.invW[0], iw1 = proj.invW[1], iw2 = proj.invW[2]; double denom = w[0] * iw0 + w[1] * iw1 + w[2] * iw2; if (std::abs(denom) < 1e-12) continue; Vec2 uv = (tri.uv0 * (w[0] * iw0) + tri.uv1 * (w[1] * iw1) + tri.uv2 * (w[2] * iw2)) * (1.0 / denom); Vec3 base = sampleMaterial(tri, uv); Vec3 col = shadeSimple(tri, base); target.set(x, y, col); if (outMaskOrNull) outMaskOrNull->set(x, y); } } } // ---------(可选)三角形相交:这里给一个基础实现(不一定用于主流程)--------- struct RayHit { double t = 0; Vec3 p; Vec3 n; }; static inline std::optional intersectRayTriangle(const Vec3 &ro, const Vec3 &rd, const Triangle &tri) { // Möller–Trumbore(用于几何工具,主算法不按逐像素 ray casting) Vec3 e1 = tri.v1 - tri.v0; Vec3 e2 = tri.v2 - tri.v0; Vec3 pvec = cross3(rd, e2); double det = dot3(e1, pvec); if (std::abs(det) < 1e-12) return std::nullopt; double invDet = 1.0 / det; Vec3 tvec = ro - tri.v0; double u = dot3(tvec, pvec) * invDet; if (u < 0 || u > 1) return std::nullopt; Vec3 qvec = cross3(tvec, e1); double v = dot3(rd, qvec) * invDet; if (v < 0 || u + v > 1) return std::nullopt; double t = dot3(e2, qvec) * invDet; if (t < 0) return std::nullopt; RayHit hit; hit.t = t; hit.p = ro + rd * t; hit.n = tri.normal(); return hit; } static inline bool triangleTriangleIntersectRough(const Triangle &a, const Triangle &b) { // 粗略:边与对方三角形的射线相交 + 顶点包含(足够作为“相关运算”示意) auto testEdge = [&](const Vec3 &p0, const Vec3 &p1, const Triangle &tri) -> bool { Vec3 d = p1 - p0; double len = length3(d); if (len < 1e-12) return false; Vec3 rd = d / len; auto hit = intersectRayTriangle(p0, rd, tri); if (!hit) return false; return hit->t >= 0 && hit->t <= len; }; std::array av = { a.v0, a.v1, a.v2 }; std::array bv = { b.v0, b.v1, b.v2 }; for (int i = 0; i < 3; i++) { if (testEdge(av[i], av[(i + 1) % 3], b)) return true; if (testEdge(bv[i], bv[(i + 1) % 3], a)) return true; } return false; } // --------- 核心递归渲染:renderTriangleWithTriangle --------- struct RenderSettings { int maxDepth = 4; double minViewportAreaPx = 4.0; // 1~5 像素级阈值,这里取 4 }; struct Candidate { const Triangle *tri = nullptr; ProjectedTri proj; double dist = 0; }; static void renderTriangleWithTriangle( const std::vector &scene, const Vec3 &cameraOrigin, const Triangle &curtainTriWorld, // 保留形参以贴合你的描述;本实现不强依赖它 const Triangle ¤tViewportWorld, // 当前 viewport 面片(世界空间三角形) const ScreenTri &viewportInImage, // 当前 viewport 面片在最终输出图上的三角形区域(像素三点) Image &outTarget, const RenderSettings &settings, int depth) { (void)curtainTriWorld; if (depth > settings.maxDepth) return; if (triArea2D(viewportInImage) < settings.minViewportAreaPx) return; ViewportFrame vf = makeViewportFrame(currentViewportWorld); std::vector candidates; candidates.reserve(scene.size()); for (const Triangle &tri : scene) { if (tri.id == currentViewportWorld.id && tri.id != -1) continue; // viewport 自身不参与渲染,避免递归自反馈 auto projOpt = projectTriangleOntoViewport(tri, cameraOrigin, currentViewportWorld, viewportInImage, vf); if (!projOpt) continue; // 投影三角形必须与 viewport 区域有重叠,否则不处理 if (!triOverlap2D(projOpt->screen, viewportInImage)) continue; Candidate c; c.tri = &tri; c.proj = *projOpt; c.dist = length3(tri.centroid() - cameraOrigin); candidates.push_back(c); } // 覆盖关系:由远到近 painter(O(n log n)) std::sort(candidates.begin(), candidates.end(), [](const Candidate &a, const Candidate &b) { return a.dist > b.dist; }); for (const Candidate &c : candidates) { const Triangle &tri = *c.tri; if (tri.mat.type == MaterialType::Diffuse) { rasterizeProjectedTriangle(outTarget, tri, c.proj, viewportInImage, nullptr); continue; } if (tri.mat.type == MaterialType::Metal) { // 1) 先贴金属自身底色(并记录金属可见区域 mask) ScreenTri metalScreen = c.proj.screen; int bbMinX = (int)std::floor(std::min({ metalScreen.p0.x, metalScreen.p1.x, metalScreen.p2.x })); int bbMaxX = (int)std::ceil(std::max({ metalScreen.p0.x, metalScreen.p1.x, metalScreen.p2.x })); int bbMinY = (int)std::floor(std::min({ metalScreen.p0.y, metalScreen.p1.y, metalScreen.p2.y })); int bbMaxY = (int)std::ceil(std::max({ metalScreen.p0.y, metalScreen.p1.y, metalScreen.p2.y })); int bbW = std::max(1, bbMaxX - bbMinX + 1); int bbH = std::max(1, bbMaxY - bbMinY + 1); RasterMask metalMask(bbW, bbH, bbMinX, bbMinY); rasterizeProjectedTriangle(outTarget, tri, c.proj, viewportInImage, &metalMask); // 2) 计算“关于当前金属面片”的相机原点对称点,作为下一层递归的 origin Vec3 n = tri.normal(); Vec3 originRef = reflectPointAcrossPlane(cameraOrigin, tri.v0, n); // 3) 递归渲染反射:让当前金属面片 tri 自身成为下一层 viewport // 注意:反射结果渲染到一个子 buffer(避免直接 blend 导致 painter 合成错误) Image reflBuf(bbW, bbH, bbMinX, bbMinY, true /*masked*/); // 递归终止也会受 metalScreen 面积限制(通过 viewportInImage 面积判断) renderTriangleWithTriangle( scene, originRef, curtainTriWorld, tri, // 下一层 viewport = 当前金属三角形 metalScreen, // 下一层 viewport 在最终图片中的区域 = 当前金属区域(投影三角形) reflBuf, settings, depth + 1); // 4) 合成:只在 metalMask 覆盖到的像素上,把 reflBuf 以 reflectivity 混合到 outTarget double a = clamp01(tri.mat.reflectivity); for (int y = bbMinY; y <= bbMaxY; y++) { for (int x = bbMinX; x <= bbMaxX; x++) { if (!metalMask.get(x, y)) continue; // 只在“金属可见区域”合成 if (!reflBuf.has(x, y)) continue; // 反射缓冲没写入则不改变底色 Vec3 base = outTarget.get(x, y); Vec3 refl = reflBuf.get(x, y); Vec3 out = base * (1.0 - a) + refl * a; outTarget.set(x, y, out); } } continue; } // Curtain 类型一般不在 scene 里;若出现,忽略 } } // --------- Scene:Cornell Box + 两个金属盒子(蓝/黄)--------- static int gNextTriId = 1; static inline Triangle makeTri(const Vec3 &a, const Vec3 &b, const Vec3 &c, const Vec2 &uva, const Vec2 &uvb, const Vec2 &uvc, const Material &m) { Triangle t; t.id = gNextTriId++; t.v0 = a; t.v1 = b; t.v2 = c; t.uv0 = uva; t.uv1 = uvb; t.uv2 = uvc; t.mat = m; return t; } static inline void addQuad(std::vector &out, const Vec3 &a, const Vec3 &b, const Vec3 &c, const Vec3 &d, const Material &m) { // (a,b,c) + (a,c,d) out.push_back(makeTri(a, b, c, { 0, 0 }, { 1, 0 }, { 1, 1 }, m)); out.push_back(makeTri(a, c, d, { 0, 0 }, { 1, 1 }, { 0, 1 }, m)); } static inline Vec3 rotateY(const Vec3 &p, double rad) { double cs = std::cos(rad), sn = std::sin(rad); return { p.x * cs + p.z * sn, p.y, -p.x * sn + p.z * cs }; } static inline void addBox(std::vector &out, const Vec3 ¢er, const Vec3 &size, // (sx, sy, sz) double rotYRad, const Material &m) { Vec3 h = size * 0.5; // 8 corners in local space std::array v = { Vec3 { -h.x, -h.y, -h.z }, // 0 Vec3 { h.x, -h.y, -h.z }, // 1 Vec3 { h.x, h.y, -h.z }, // 2 Vec3 { -h.x, h.y, -h.z }, // 3 Vec3 { -h.x, -h.y, h.z }, // 4 Vec3 { h.x, -h.y, h.z }, // 5 Vec3 { h.x, h.y, h.z }, // 6 Vec3 { -h.x, h.y, h.z } // 7 }; for (auto &p : v) p = rotateY(p, rotYRad) + center; // faces: each as quad (a,b,c,d) with consistent uv // Front (-z): 0,1,2,3 addQuad(out, v[0], v[1], v[2], v[3], m); // Back (+z): 5,4,7,6 (swap order to vary orientation; shading使用abs不敏感) addQuad(out, v[5], v[4], v[7], v[6], m); // Left (-x): 4,0,3,7 addQuad(out, v[4], v[0], v[3], v[7], m); // Right (+x): 1,5,6,2 addQuad(out, v[1], v[5], v[6], v[2], m); // Bottom (-y): 4,5,1,0 addQuad(out, v[4], v[5], v[1], v[0], m); // Top (+y): 3,2,6,7 addQuad(out, v[3], v[2], v[6], v[7], m); } static inline std::vector buildCornellScene() { std::vector tris; tris.reserve(128); // Cornell box 尺寸(前方开口,便于相机观察) // x in [-1,1], y in [0,2], z in [0,2] Vec3 L { -1, 0, 0 }, R { 1, 0, 0 }; (void)L; (void)R; Material floorM; floorM.type = MaterialType::Diffuse; floorM.baseColor = { 0.9, 0.9, 0.9 }; floorM.checker = true; floorM.checkerScale = 6; Material leftM; leftM.type = MaterialType::Diffuse; leftM.baseColor = { 0.9, 0.2, 0.2 }; Material rightM; rightM.type = MaterialType::Diffuse; rightM.baseColor = { 0.2, 0.9, 0.2 }; Material backM; backM.type = MaterialType::Diffuse; backM.baseColor = { 0.95, 0.95, 0.95 }; // Floor y=0 addQuad(tris, Vec3 { -1, 0, 0 }, Vec3 { 1, 0, 0 }, Vec3 { 1, 0, 2 }, Vec3 { -1, 0, 2 }, floorM); // Left wall x=-1 addQuad(tris, Vec3 { -1, 0, 0 }, Vec3 { -1, 0, 2 }, Vec3 { -1, 2, 2 }, Vec3 { -1, 2, 0 }, leftM); // Right wall x=1 addQuad(tris, Vec3 { 1, 0, 0 }, Vec3 { 1, 2, 0 }, Vec3 { 1, 2, 2 }, Vec3 { 1, 0, 2 }, rightM); // Back wall z=2 addQuad(tris, Vec3 { -1, 0, 2 }, Vec3 { 1, 0, 2 }, Vec3 { 1, 2, 2 }, Vec3 { -1, 2, 2 }, backM); // 两个金属盒子(蓝/黄),放在盒子内部 Material blueMetal; blueMetal.type = MaterialType::Metal; blueMetal.baseColor = { 0.15, 0.25, 0.95 }; blueMetal.reflectivity = 0.85; blueMetal.checker = false; Material yellowMetal; yellowMetal.type = MaterialType::Metal; yellowMetal.baseColor = { 0.95, 0.85, 0.15 }; yellowMetal.reflectivity = 0.80; yellowMetal.checker = false; addBox(tris, Vec3 { -0.45, 0.50, 1.05 }, Vec3 { 0.55, 1.00, 0.55 }, 0.30, blueMetal); addBox(tris, Vec3 { 0.45, 0.35, 1.45 }, Vec3 { 0.65, 0.70, 0.65 }, -0.45, /*yellowMetal*/rightM); return tris; } // --------- Camera:viewport 两三角形 Curtain,调用两次渲染并输出 PPM --------- struct Camera { Vec3 origin { 0, 1, -3 }; Vec3 lookAt { 0, 1, 1 }; Vec3 up { 0, 1, 0 }; int width = 800; int height = 600; double fovYDeg = 50.0; double focalDist = 1.0; Triangle curtainA; // world Triangle curtainB; // world ScreenTri screenA; // image region ScreenTri screenB; // image region void buildCurtain() { Vec3 forward = normalize3(lookAt - origin); Vec3 right = normalize3(cross3(forward, up)); Vec3 up2 = normalize3(cross3(right, forward)); double aspect = (double)width / (double)height; double fovYRad = fovYDeg * M_PI / 180.0; double halfH = std::tan(fovYRad * 0.5) * focalDist; double halfW = halfH * aspect; Vec3 center = origin + forward * focalDist; Vec3 tl = center + up2 * halfH - right * halfW; Vec3 tr = center + up2 * halfH + right * halfW; Vec3 bl = center - up2 * halfH - right * halfW; Vec3 br = center - up2 * halfH + right * halfW; Material curtainM; curtainM.type = MaterialType::Curtain; curtainM.baseColor = { 0, 0, 0 }; curtainA.id = -1001; curtainA.v0 = tl; curtainA.v1 = tr; curtainA.v2 = bl; curtainA.mat = curtainM; curtainB.id = -1002; curtainB.v0 = br; curtainB.v1 = bl; curtainB.v2 = tr; curtainB.mat = curtainM; // 像素三角形:覆盖整个图像矩形(对角线划分) // 注意:图像坐标原点在左上,y 向下 screenA = { Vec2 { 0, 0 }, Vec2 { (double)(width - 1), 0 }, Vec2 { 0, (double)(height - 1) } }; screenB = { Vec2 { (double)(width - 1), (double)(height - 1) }, Vec2 { 0, (double)(height - 1) }, Vec2 { (double)(width - 1), 0 } }; } void render(const std::vector &scene, const std::string &outPath) { buildCurtain(); Image img(width, height, 0, 0, false); // 背景(默认黑) for (auto &p : img.pix) p = Vec3 { 0.0, 0.0, 0.0 }; RenderSettings settings; settings.maxDepth = 4; settings.minViewportAreaPx = 4.0; // CurtainA renderTriangleWithTriangle( scene, origin, curtainA, curtainA, screenA, img, settings, 0); // CurtainB renderTriangleWithTriangle( scene, origin, curtainB, curtainB, screenB, img, settings, 0); img.writePPM(outPath); } }; int main() { std::vector scene = buildCornellScene(); Camera cam; cam.width = 900; cam.height = 700; cam.origin = Vec3 { 0.0, 1.0, -3.2 }; cam.lookAt = Vec3 { 0.0, 1.0, 1.0 }; cam.fovYDeg = 52.0; cam.focalDist = 1.0; cam.render(scene, "out.ppm"); std::cerr << "Wrote out.ppm\n"; return 0; }