学了 离线渲染 相关的知识这么久了,自己也写过一些渲染器,但是我认为我对渲染器中的积分器的实现的理解还是不够透彻,于是打算写一个简单的 path tracer 程序来加深自己的印象
,于是就得到了(无第三方库
代码
1#include <cmath>
2#include <iostream>
3#include <random>
4#include <vector>
5constexpr float EPSILON = 1e-5f;
6constexpr float PI = 3.14159265358979323846f;
7struct Vec3 {
8 float x, y, z;
9 Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
10 Vec3() : x(0), y(0), z(0) {}
11 Vec3 operator+(const Vec3 &other) const {
12 return Vec3(x + other.x, y + other.y, z + other.z);
13 }
14 Vec3 operator-(const Vec3 &other) const {
15 return Vec3(x - other.x, y - other.y, z - other.z);
16 }
17 Vec3 operator*(float other) const {
18 return Vec3(x * other, y * other, z * other);
19 }
20 Vec3 operator*(Vec3 other) {
21 return Vec3(x * other.x, y * other.y, z * other.z);
22 }
23 Vec3 operator-() const { return Vec3(-x, -y, -z); }
24};
25Vec3 normalize(Vec3 v) { return v * (1.0f / sqrt(v.x * v.x + v.y * v.y + v.z * v.z)); }
26float dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
27float clamp(float x, float min, float max) {
28 return x < min ? min : (x > max ? max : x);
29}
30float linear_to_srgb(float x) {
31 x = clamp(x, 0.0f, 1.0f);
32 if (x <= 0.0031308f)
33 return 12.92f * x;
34 return 1.055f * std::pow(x, 1.0f / 2.4f) - 0.055f;
35}
36struct Ray {
37 Vec3 origin;
38 Vec3 direction;
39 Ray(Vec3 origin, Vec3 direction) : origin(origin), direction(direction) {}
40 Vec3 at(float t) const { return origin + direction * t; }
41};
42struct Sphere {
43 Sphere(float radius, Vec3 center, Vec3 color, bool light = false): radius(radius), center(center), color(color), is_light(light) {}
44 float radius;
45 Vec3 center;
46 Vec3 color;
47 bool is_light = false;
48};
49struct HitInfo {
50 bool is_hit;
51 float t;
52 Vec3 position;
53 Vec3 normal;
54 Sphere const* sphere = nullptr;
55};
56HitInfo intersect(const Ray &ray, const Sphere &sphere) {
57 Vec3 oc = ray.origin - sphere.center;
58 float b = 2.0f * dot(oc, ray.direction);
59 float c = dot(oc, oc) - sphere.radius * sphere.radius;
60 float discriminant = b * b - 4.0f * c;
61 if (discriminant < 0.0f)
62 return {false, 0.0f, Vec3(), Vec3(), &sphere};
63 float sqrt_d = sqrt(discriminant);
64 float t0 = (-b - sqrt_d) * 0.5f,t1 = (-b + sqrt_d) * 0.5f;
65 float t = t0;
66 if (t < EPSILON)
67 t = t1;
68 if (t < EPSILON)
69 return {false, 0.0f, Vec3(), Vec3(), &sphere};
70 return {true, t, ray.at(t), normalize(ray.at(t) - sphere.center), &sphere};
71}
72float random_float() {
73 thread_local std::mt19937 gen(std::minstd_rand{}());
74 thread_local std::uniform_real_distribution<float> dis(0.0f, 1.0f);
75 return dis(gen);
76}
77Vec3 sampling_hemisphere(const Vec3 &normal) {
78 float z = random_float();
79 float r = std::sqrt(1.0f - z * z);
80 float phi = 2.0f * PI * random_float();
81 Vec3 local(r * std::cos(phi), r * std::sin(phi), z);
82 Vec3 tangent;
83 if (std::abs(normal.x) > 0.9f)
84 tangent = normalize(Vec3(0.0f, 1.0f, 0.0f) - normal * normal.y);
85 else
86 tangent = normalize(Vec3(1.0f, 0.0f, 0.0f) - normal * normal.x);
87 Vec3 bitangent = Vec3(normal.y * tangent.z - normal.z * tangent.y,normal.z * tangent.x - normal.x * tangent.z,normal.x * tangent.y - normal.y * tangent.x);
88 return normalize(tangent * local.x + bitangent * local.y + normal * local.z);
89}
90Vec3 Lo(Ray ray, const std::vector<Sphere> &spheres, int depth) {
91 if (depth <= 0)
92 return Vec3();
93 float rr_prob = 0.9f;
94 if (random_float() > rr_prob)
95 return Vec3();
96 HitInfo nearest_hit;
97 nearest_hit.t = 1e10;
98 nearest_hit.is_hit = false;
99 for (auto &sphere : spheres) {
100 HitInfo hit = intersect(ray, sphere);
101 if (hit.is_hit && hit.t < nearest_hit.t)
102 nearest_hit = hit;
103 }
104 if (!nearest_hit.is_hit)
105 return Vec3();
106 if (nearest_hit.sphere->is_light) {
107 Vec3 Le = nearest_hit.sphere->color * 30.0f;
108 return Le;
109 }
110 Vec3 wo = -ray.direction,wi = sampling_hemisphere(nearest_hit.normal);
111 float pdf = 1.0f / (2.0f * PI);
112 Vec3 f_r = nearest_hit.sphere->color * (1.0f / PI);
113 float cos_theta = dot(wi, nearest_hit.normal);
114 Ray new_ray = Ray(nearest_hit.position + nearest_hit.normal * EPSILON, wi);
115 Vec3 Li = Lo(new_ray, spheres, depth - 1);
116 return f_r * Li * cos_theta * (1.0f / (rr_prob * pdf));
117}
118int main() {
119 int image_width = 256, image_height = 256,samples_per_pixel = 1024;
120 std::vector<Vec3> framebuffer(image_width * image_height);
121 Sphere left_wall = {1000.0f, Vec3(-1003.0f, 0.0f, -6.0f), Vec3(0.75f, 0.12f, 0.10f)};
122 Sphere right_wall = {1000.0f, Vec3(1003.0f, 0.0f, -6.0f),Vec3(0.12f, 0.45f, 0.12f)};
123 Sphere back_wall = {1000.0f, Vec3(0.0f, 0.0f, -1006.0f), Vec3(1.0f, 1.0f, 1.0f)};
124 Sphere floor = {1000.0f, Vec3(0.0f, -1002.6f, -6.0f), Vec3(1.0f, 1.0f, 1.0f)};
125 Sphere ceiling = {1000.0f, Vec3(0.0f, 1002.6f, -6.0f), Vec3(1.0f, 1.0f, 1.0f)};
126 Sphere light = {0.35f, Vec3(0.0f, 2.6f, -4.0f), Vec3(1.0f, 1.0f, 1.0f), true};
127 Sphere big_sphere = {1.3f, Vec3(-1.0f, -1.6f, -5.0f), Vec3(1.0f, 1.0f, 1.0f)};
128 Sphere small_sphere = {0.8f, Vec3(1.3f, -2.0f, -4.0f), Vec3(1.0f, 1.0f, 1.0f)};
129 std::vector<Sphere> spheres = {left_wall, right_wall, back_wall,floor,ceiling,light,big_sphere, small_sphere};
130 std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n";
131 for (int j = 0; j < image_height; j++) {
132 for (int i = 0; i < image_width; i++) {
133 Vec3 color;
134 for (int s = 0; s < samples_per_pixel; s++) {
135 float u = (((double(i) + random_float()) / image_width)-0.5f)*2;
136 float v = (((double(j) + random_float()) / image_height)-0.5f)*-2;
137 Vec3 ray_direction = normalize(Vec3(u, v, -1));
138 Ray ray = Ray(Vec3(), ray_direction);
139 color = color + Lo(ray, spheres, 10);
140 }
141 color = color * (1.0f / samples_per_pixel);
142 int ir = static_cast<int>(linear_to_srgb(color.x) * 255.0f);
143 int ig = static_cast<int>(linear_to_srgb(color.y) * 255.0f);
144 int ib = static_cast<int>(linear_to_srgb(color.z) * 255.0f);
145 std::cout << ir << ' ' << ig << ' ' << ib << '\n';
146 }
147 }
148}
原仓库:https://github.com/Yang-Junjie/mini_path_tracer