-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlights.cpp
More file actions
282 lines (217 loc) · 9.97 KB
/
lights.cpp
File metadata and controls
282 lines (217 loc) · 9.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#include "lights.h"
#include "scene.h"
#include "shapes.h"
#include "vector.h"
#include <cmath>
#include <iostream>
#include <stdexcept>
Vector3 PointLight::sample(Vector3 point, Vector3 normal, Scene& scene) const {
Vector3 light_vec = position - point;
float dist2 = light_vec.dot(light_vec);
// Avoid division by zero
if (dist2 < 1e-6f) return Vector3();
float dist = std::sqrt(dist2);
Vector3 L = light_vec / dist;
// Cosine term
float NdotL = normal.dot(L);
if (NdotL <= 0.0f) return Vector3();
// Shadow ray
Vector3 origin = point + L * 1e-4f;
Vector3 inv_L = 1 / L;
CollisionInfo shadow = scene.intersectRay(origin, L, inv_L);
if (shadow.hit && shadow.distance < dist) return Vector3();
// Inverse square falloff
Vector3 radiance = color * intensity * (NdotL / dist2);
if (!std::isfinite(radiance.x) || !std::isfinite(radiance.y) || !std::isfinite(radiance.z)) return Vector3();
return radiance;
}
Vector3 PointLight::sample(Vector3 point, Vector3 normal, Vector3 view, Scene& scene) const {
return Vector3(0, 0, 0); // On a mirror surface, a point light is never hit
}
Vector3 DirectionalLight::sample(Vector3 point, Vector3 normal, Scene& scene) const {
// Shadow ray check
Vector3 shadow_direction = direction * -1;
Vector3 inv_shadow = 1 / shadow_direction;
Vector3 origin = point + normal * 0.001;
CollisionInfo shadow = scene.intersectRay(origin, shadow_direction, inv_shadow);
if (shadow.hit) return Vector3();
double costheta = fmax(0.0, normal.dot(direction * -1));
Vector3 radiance = color * intensity * costheta;
if (!std::isfinite(radiance.x) || !std::isfinite(radiance.y) || !std::isfinite(radiance.z)) return Vector3();
return radiance;
}
Vector3 DirectionalLight::sample(Vector3 point, Vector3 normal, Vector3 view, Scene& scene) const {
// Shadow ray check
Vector3 shadow_direction = direction * -1;
Vector3 inv_shadow = 1 / shadow_direction;
point += normal * 0.001;
CollisionInfo shadow = scene.intersectRay(point, shadow_direction, inv_shadow);
if (shadow.hit) return Vector3();
Vector3 radiance = color * intensity;
if (!std::isfinite(radiance.x) || !std::isfinite(radiance.y) || !std::isfinite(radiance.z)) return Vector3();
return radiance;
}
Vector3 SphereLight::sample(Vector3 point, Vector3 normal, Scene& scene) const {
if (position != last_position || radius != last_radius) {
sphere = Sphere(position, radius);
last_position = position;
last_radius = radius;
surface_area = sphere.surfaceArea();
}
if (!rng) throw std::runtime_error("Tried to sample sphere light, but the RNG is not assigned yet");
Vector3 random_light_position = Vector3(0, 0, 0);
random_light_position.z = 2.0 * rng->random_float() - 1.0;
double theta = 2.0 * M_PI * rng->random_float();
double r = sqrt(1 - pow(random_light_position.z, 2));
random_light_position.x = r * cos(theta);
random_light_position.y = r * sin(theta);
random_light_position *= radius;
random_light_position += position;
Vector3 light_vec = random_light_position - point;
float dist2 = light_vec.dot(light_vec);
// Avoid division by zero
if (dist2 < 1e-6f) return Vector3();
float dist = std::sqrt(dist2);
Vector3 L = light_vec / dist;
// Cosine term
float NdotL = normal.dot(L);
if (NdotL <= 0.0f) return Vector3();
// Light cosine
Vector3 light_normal = (random_light_position - position).normalize();
float cos_light = light_normal.dot(-L);
if (cos_light <= 0.0f) return Vector3();
// Shadow ray
Vector3 origin = point + L * 1e-4f;
Vector3 inv_L = 1 / L;
CollisionInfo shadow = scene.intersectRay(origin, L, inv_L);
if (shadow.hit && shadow.distance < dist) return Vector3();
// Inverse square falloff
Vector3 radiance = color * intensity * NdotL * (cos_light / dist2);
radiance *= surface_area;
if (!std::isfinite(radiance.x) || !std::isfinite(radiance.y) || !std::isfinite(radiance.z)) return Vector3();
return radiance;
}
Vector3 SphereLight::sample(Vector3 point, Vector3 normal, Vector3 view, Scene& scene) const {
if (position != last_position || radius != last_radius) {
sphere = Sphere(position, radius);
last_position = position;
last_radius = radius;
surface_area = sphere.surfaceArea();
}
// Reflection
Vector3 reflected = view - normal * 2.0 * view.dot(normal);
Vector3 inv_reflected = 1 / reflected;
Vector3 origin = point + reflected * 1e-4f;
CollisionInfo sphere_collision = sphere.collide_ray(origin, reflected, inv_reflected);
if (!sphere_collision.hit) return Vector3();
CollisionInfo shadow = scene.intersectRay(origin, reflected, inv_reflected);
if (shadow.hit && shadow.distance < sphere_collision.distance) return Vector3();
double costheta = fmax(0, normal.dot(reflected));
// Inverse square falloff
Vector3 radiance = color * intensity * (costheta / pow(sphere_collision.distance, 2));
if (!std::isfinite(radiance.x) || !std::isfinite(radiance.y) || !std::isfinite(radiance.z)) return Vector3();
return radiance;
}
Vector3 BoxLight::sample(Vector3 point, Vector3 normal, Scene& scene) const {
if (last_halfsize != halfsize || last_position != position || last_rotation != rotation) {
box = Box(position, halfsize, rotation);
Vector3 zero = Vector3();
Vector3 right = Vector3(1, 0, 0);
box.collide_ray(zero, right, right);
last_halfsize = halfsize;
last_position = position;
last_rotation = rotation;
surface_area = box.surfaceArea();
}
if (!rng) throw std::runtime_error("Tried to sample box light, but the RNG is not assigned yet");
const float hx = halfsize.x;
const float hy = halfsize.y;
const float hz = halfsize.z;
const double face_area_posx = 4.0 * hy * hz; // +X
const double face_area_posy = 4.0 * hx * hz; // +Y
const double face_area_posz = 4.0 * hx * hy; // +Z
const double area_pair_x = face_area_posx * 2.0;
const double area_pair_y = face_area_posy * 2.0;
const double area_pair_z = face_area_posz * 2.0;
const double total_area = area_pair_x + area_pair_y + area_pair_z;
if (total_area <= 0.0) return Vector3();
double r = rng->random_float() * total_area;
Vector3 random_light_position;
Vector3 light_normal;
if (r < area_pair_x) {
bool positive = rng->random_float() < 0.5f;
float x = positive ? hx : -hx;
float y = (rng->random_float() * 2.0f - 1.0f) * hy;
float z = (rng->random_float() * 2.0f - 1.0f) * hz;
random_light_position = Vector3(x, y, z);
light_normal = Vector3(positive * 2 - 1, 0, 0);
} else if (r < area_pair_x + area_pair_y) {
bool positive = rng->random_float() < 0.5f;
float y = positive ? hy : -hy;
float x = (rng->random_float() * 2.0f - 1.0f) * hx;
float z = (rng->random_float() * 2.0f - 1.0f) * hz;
random_light_position = Vector3(x, y, z);
light_normal = Vector3(0, positive * 2 - 1, 0);
} else {
bool positive = rng->random_float() < 0.5f;
float z = positive ? hz : -hz;
float x = (rng->random_float() * 2.0f - 1.0f) * hx;
float y = (rng->random_float() * 2.0f - 1.0f) * hy;
random_light_position = Vector3(x, y, z);
light_normal = Vector3(0, 0, positive * 2 - 1);
}
Vector3* rotation_matrix = box.get_rotation_matrix();
random_light_position = Vector3(
rotation_matrix[0].dot(random_light_position),
rotation_matrix[1].dot(random_light_position),
rotation_matrix[2].dot(random_light_position)
);
light_normal = Vector3(
rotation_matrix[0].dot(light_normal),
rotation_matrix[1].dot(light_normal),
rotation_matrix[2].dot(light_normal)
);
random_light_position += position;
Vector3 light_vec = random_light_position - point;
float dist2 = light_vec.dot(light_vec);
if (dist2 < 1e-6f) return Vector3();
float dist = std::sqrt(dist2);
Vector3 L = light_vec / dist;
Vector3 inv_L = 1 / L;
float NdotL = normal.dot(L);
if (NdotL <= 0.0f) return Vector3();
float cos_light = light_normal.dot(-L);
if (cos_light <= 0.0f) return Vector3();
// Shadow ray (offset along normal to avoid self-intersection)
Vector3 origin = point + normal * 0.001;
CollisionInfo shadow = scene.intersectRay(origin, L, inv_L);
if (shadow.hit && shadow.distance < dist) return Vector3();
Vector3 radiance = color * intensity * NdotL * (cos_light / dist2);
radiance *= surface_area;
if (!std::isfinite(radiance.x) || !std::isfinite(radiance.y) || !std::isfinite(radiance.z)) return Vector3();
return radiance;
}
Vector3 BoxLight::sample(Vector3 point, Vector3 normal, Vector3 view, Scene& scene) const {
if (last_halfsize != halfsize || last_position != position || last_rotation != rotation) {
box = Box(position, halfsize, rotation);
Vector3 zero = Vector3();
Vector3 right = Vector3(1, 0, 0);
box.collide_ray(zero, right, right);
last_halfsize = halfsize;
last_position = position;
last_rotation = rotation;
surface_area = box.surfaceArea();
}
Vector3 reflected = view - normal * 2.0 * view.dot(normal);
Vector3 inv_reflected = 1 / reflected;
Vector3 origin = point + reflected * 1e-4f;
CollisionInfo box_collision = box.collide_ray(origin, reflected, inv_reflected);
if (!box_collision.hit) return Vector3();
CollisionInfo shadow = scene.intersectRay(origin, reflected, inv_reflected);
if (shadow.hit && shadow.distance < box_collision.distance) return Vector3();
double costheta = fmax(0, normal.dot(reflected));
// Inverse square falloff
Vector3 radiance = color * intensity * (costheta / pow(box_collision.distance, 2));
if (!std::isfinite(radiance.x) || !std::isfinite(radiance.y) || !std::isfinite(radiance.z)) return Vector3();
return radiance;
}