-
Notifications
You must be signed in to change notification settings - Fork 0
/
raytracer.h
360 lines (320 loc) · 12.9 KB
/
raytracer.h
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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
/*
* File: raytracer.h
* Authors: Alexander Epp, Rain Epp
* Project: CMPUT274 Final Project
* Description: Contains the RayTracer class, which traces the path of rays cast
* by the Camera, and renders the results to a WindowFramework.
*/
#pragma once
#include "array.h"
#include "object.h"
#include "light.h"
#include "windowframework.h"
#include "util.h"
#include "vec.h"
#include "camera.h"
struct RefractionData;
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
class RayTracer
{
public:
/*
* Initialize ray-tracer by specifying maximum recursion depth
*/
RayTracer(int);
~RayTracer();
/*
* Add an object to the scene to be rendered (the object is added as
* a pointer instead of using variadic templates like the other methods
* since this allows allocating memory for each object statically).
* If the object pointer was successfully added, it is returned; otherwise
* nullptr is returned.
* RayTracer is not responsible for deleting these objects.
*/
template<typename T>
T* addObject(T* object);
/*
* Add a point light to the scene to be rendered. Arguments are the
* arguments to PointLight's c-tor.
*/
template<typename...Args>
bool addPointLight(Args&& ...args);
/*
* Add a directional light to the scene to be rendered. Arguments are the
* arguments to DirectionalLight's c-tor. Returns true iff the light was
* successfully added.
*/
template<typename...Args>
bool addDirectionalLight(Args&& ...args);
/*
* Set the ambient light of the scene to be rendered. Arguments are the
* arguments to AmbientLight's c-tor. Returns true iff the light was
* successfully added.
*/
template<typename...Args>
void setAmbientLight(Args&& ...args);
/*
* Render the scene to a given WindowFramework, with rays cast by Camera.
* Returns false if WindowFramework indicates the application should quit,
* and true otherwise.
*/
bool render(WindowFramework* fw, Camera* cam);
/*
* Set the maximum recursion depth of the ray-tracing algorithm.
*/
void setRecursionDepth(int);
private:
/*
* Returns a pointer (from m_objects) of the object that first intersects
* a ray. If there is no such intersection, nullptr is returned instead.
*/
Object* getFirstIntersection(Ray ray);
/*
* Cast a ray and recursively trace its path. Returns the colour "seen"
* with the ray.
*/
fvec3 castRay(Ray ray, float intensity, uint16_t recursionDepth);
/*
* Get colour contributed by lights given a point and normal
*/
fvec3 getLighting(fvec3 point, fvec3 normal);
/*
* Reflect a ray from a normal (double-sided)
*/
Ray reflectRay(Ray ray, fvec3 normal);
/*
* Refract a ray from a normal (double-sided)
*/
RefractionData refractRay(Ray ray, fvec3 normal, float refractiveIndex);
/*
* Tests if the given point is in the given light's shadow
*/
bool isShadowed(const PointLight& light, fvec3 pos);
bool isShadowed(const DirectionalLight& light, fvec3 pos);
fvec3 m_backgroundColour;
const float m_minIntensityThreshold; // Stop recursing once light intensity is below this threshold
uint16_t m_maxRecursionDepth;
const float m_minRayLength; // Rays must intersect at a point beyond this length from the ray origin
Array<Object*, NumObjects> m_objects;
Array<PointLight, NumPointLights> m_pointLights;
Array<DirectionalLight, NumDirectionalLights> m_directionalLights;
AmbientLight m_ambientLight;
};
struct RefractionData
{
RefractionData(Ray r, bool t)
: ray(r), tir(t) {}
Ray ray;
bool tir; // total internal reflection
};
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::RayTracer(int maxRecursionDepth)
: m_backgroundColour(0.f, 0.f, 0.f),
m_minIntensityThreshold(0.001f),
m_maxRecursionDepth(maxRecursionDepth),
m_minRayLength(0.001)
{
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::~RayTracer()
{
// RayTracer is not responsible for deleting objects (and lights are cleaned
// up automatically).
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
bool RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::isShadowed(const PointLight& light, fvec3 pos)
{
for (auto object : m_objects)
{
auto toLight = light.position - pos;
auto distance = object->intersect({ pos, normalize(toLight) }, nullptr, m_minRayLength);
if (distance > 0 && distance < (toLight.length() - m_minRayLength)) // Collision is between light and object
{
return true;
}
}
return false;
};
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
bool RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::isShadowed(const DirectionalLight& light, fvec3 pos)
{
for (auto object : m_objects)
{
auto distance = object->intersect({ pos, -light.direction }, nullptr, m_minRayLength);
if (distance > 0)
{
return true;
}
}
return false;
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
fvec3 RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::getLighting(fvec3 point, fvec3 normal)
{
fvec3 total(0.f, 0.f, 0.f);
for (auto& light : m_pointLights)
{
auto diff = light.position - point;
auto dir = normalize(diff);
// A shadow ray test would go here
//normal += fvec3((rand()%2-1)/100.f, (rand()%2-1)/100.f, (rand()%2-1)/100.f);
// Only accumulate light if light ray is on the 'outside' of the object
// (direction in which the surface normal points). Since a 2D object
// (such as a plane) can be viewed from both sides, *both* sides will be
// lit, but only by lights from *one* side.
if (!isShadowed(light, point))
total += light.colour * util::max(dot(normal, dir), 0.f) * util::min(light.intensity / diff.length2(), 1.f);
}
for (auto& light : m_directionalLights)
{
if (!isShadowed(light, point))
total += light.colour * util::max(dot(normal, -light.direction), 0.f);
}
total += m_ambientLight.colour;
return total;
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
Ray RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::reflectRay(Ray ray, fvec3 normal)
{
if (dot(ray.dir, normal) < 0)
return { ray.origin, -reflectNormalized(ray.dir, normal) };
else
return { ray.origin, -reflectNormalized(ray.dir, -normal) };
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
RefractionData RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::refractRay(Ray ray, fvec3 normal, float refractiveIndex)
{
// This algorithm is as described in "An Improved Illumination Model for
// Shaded Display" (published 1980) by Turner Whitted, last accessed
// December 2017 at https://pdfs.semanticscholar.org/78e7/620a01ecaecc62b00bf47f86c6822e3d9625.pdf
auto v_dot_n = dot(ray.dir, normal);
if (v_dot_n == 0) // Ray is parallel to surface, so there is no refraction
{
return { ray, false };
}
else if (v_dot_n > 0) // Ray is leaving object, so flip normal and index of refraction
{
v_dot_n = -v_dot_n;
normal = -normal;
refractiveIndex = 1.f / refractiveIndex;
}
auto v_prime = -ray.dir / v_dot_n; // V' = V/|V.N|
auto radicand = util::pow2(refractiveIndex)*v_prime.length2() - (v_prime + normal).length2(); // k_n^2 |V'|^2 - |V'+N|^2
if (radicand <= 0) // Total internal reflection
{
return { ray, true };
}
auto k_f = 1.f / sqrt(radicand); // (k_n^2 |V'|^2 - |V'+N|^2)^(-1/2)
auto refractedRay = Ray(ray.origin, normalize((normal + v_prime)*k_f - normal)); // k_f (N+V') - N
return { refractedRay, false };
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
bool RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::render(WindowFramework* fw, Camera* cam)
{
for (decltype(cam->nPixelsX()) pixelX = 0; pixelX < cam->nPixelsX(); ++pixelX)
{
for (decltype(cam->nPixelsY()) pixelY = 0; pixelY < cam->nPixelsY(); ++pixelY)
{
Ray ray = cam->getPixelRay(pixelX, pixelY);
fw->drawPixel(pixelX, pixelY, castRay(ray, 1.f, 1));
if (!fw->tick()) return false;
}
}
return true;
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
Object * RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::getFirstIntersection(Ray ray)
{
Object* closestObject = nullptr;
float closestDistance = -1.f;
for (auto object : m_objects)
{
if (object) // Only consider used objects
{
// Get first intersection with object beyond minRayLength
auto distance = object->intersect(ray, nullptr, m_minRayLength);
if (distance > 0) // An intersection was found
{
if (!closestObject || distance < closestDistance)
{
closestObject = object;
closestDistance = distance;
}
}
}
}
return closestObject;
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
fvec3 RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::castRay(Ray ray, float intensity, uint16_t recursionDepth)
{
// Stop if light intensity has decayed to insignificance, or after a fixed number of recursions.
if (intensity < m_minIntensityThreshold || recursionDepth > m_maxRecursionDepth)
//return m_ambientLight.colour;
return fvec3(0.f, 0.f, 0.f);
// Get the first object that intersects this ray
auto intersectingObject = getFirstIntersection(ray);
// Stop if no intersection took place
if (!intersectingObject)
//return m_ambientLight.colour;
return fvec3(0.f, 0.f, 0.f);
// Get detailed information of the intersection
IntersectionData id;
if (intersectingObject->intersect(ray, &id, m_minRayLength) < 0)
util::debugPrint("castRay() could not find an intersection for supposedly intersecting object.");
// Calculate reflected ray
auto reflectedRay = reflectRay({ id.intersection, ray.dir }, id.normal);
auto reflectionCoefficient = id.reflectionCoefficient;
// Calculate refracted ray
auto refractionData = refractRay({ id.intersection, ray.dir }, id.normal, id.refractiveIndex);
auto refractedRay = refractionData.ray;
auto transmissionCoefficient = id.transmissionCoefficient;
if (refractionData.tir) // Total internal reflection
{
reflectionCoefficient += transmissionCoefficient;
transmissionCoefficient = 0;
}
// Calculate diffuse lighting
auto diffuseCoefficient = 1.f - reflectionCoefficient - transmissionCoefficient;
auto diffuseLighting = getLighting(id.intersection, id.normal);
// Cast the reflected and refracted rays
return diffuseLighting*id.colour*diffuseCoefficient
+ castRay(reflectedRay, intensity*reflectionCoefficient, recursionDepth + 1)*reflectionCoefficient
+ castRay(refractedRay, intensity*transmissionCoefficient, recursionDepth + 1)*transmissionCoefficient;
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
template<typename T>
T * RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::addObject(T* object)
{
if (m_objects.add(object))
{
return object;
}
else
{
return nullptr;
}
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
template<typename ...Args>
bool RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::addPointLight(Args&& ...args)
{
return m_pointLights.add(util::forward<Args>(args)...);
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
template<typename ...Args>
bool RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::addDirectionalLight(Args&& ...args)
{
return m_directionalLights.add(util::forward<Args>(args)...);
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
template<typename ...Args>
void RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::setAmbientLight(Args&& ...args)
{
m_ambientLight = AmbientLight(util::forward<Args>(args)...);
}
template<uint16_t NumObjects, uint16_t NumPointLights, uint16_t NumDirectionalLights>
void RayTracer<NumObjects, NumPointLights, NumDirectionalLights>::setRecursionDepth(int rd)
{
m_maxRecursionDepth = rd;
}