generated from moddyz/CXXTemplate
-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.cpp
411 lines (354 loc) · 18.4 KB
/
main.cpp
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
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
#include <cxxopts.hpp>
#include <gm/base/constants.h>
#include <gm/types/floatRange.h>
#include <gm/types/vec3f.h>
#include <gm/functions/clamp.h>
#include <gm/functions/dotProduct.h>
#include <gm/functions/lengthSquared.h>
#include <gm/functions/linearInterpolation.h>
#include <gm/functions/linearMap.h>
#include <gm/functions/normalize.h>
#include <gm/functions/randomNumber.h>
#include <raytrace/camera.h>
#include <raytrace/constantTexture.h>
#include <raytrace/dielectric.h>
#include <raytrace/hitRecord.h>
#include <raytrace/imageBuffer.h>
#include <raytrace/lambert.h>
#include <raytrace/metal.h>
#include <raytrace/ppmImageWriter.h>
#include <raytrace/randomPointInUnitDisk.h>
#include <raytrace/ray.h>
#include <raytrace/spatialBVH.h>
#include <raytrace/sphere.h>
#include <iostream>
/// \var c_normalizedRange
///
/// Normalized float range between 0 and 1.
constexpr gm::FloatRange c_normalizedRange( 0.0f, 1.0f );
/// \var Indentation
///
/// 4 spaces.
static const char* c_indent = " ";
/// Compute the ray color.
///
/// The ray is tested for intersection against a collection of scene objects.
/// The color is computed based on the surface outward normal of the nearest intersection.
///
/// In the case where there is no intersection, a background color is interpolated from a top-down gradient.
///
/// \param i_ray The ray.
/// \param i_numRayBounces The number of "bounces" a ray has left before termination.
/// \param i_rootObject The root object to perform hit tests against.
///
/// \return The computed ray color.
static gm::Vec3f ComputeRayColor( const raytrace::Ray& i_ray,
int i_numRayBounces,
const raytrace::SceneObjectPtr& i_rootObject,
bool i_printDebug )
{
if ( i_printDebug )
{
std::cout << c_indent << c_indent << i_ray << std::endl;
std::cout << c_indent << c_indent << "Num bounces: " << i_numRayBounces << std::endl;
}
if ( i_numRayBounces == 0 )
{
// No bounces left, terminate ray and do not produce any color (black).
return gm::Vec3f( 0, 0, 0 );
}
// Iterate over all scene objects and test for ray hit(s).
// We'd like to track the nearest hit and prune out farther objects.
raytrace::HitRecord record;
bool objectHit = false;
gm::FloatRange magnitudeRange( 0.001f, // Fix for "Shadow acne" by culling hits which are too near.
std::numeric_limits< float >::max() );
if ( i_rootObject->Hit( i_ray, magnitudeRange, record ) )
{
objectHit = true;
}
if ( objectHit )
{
if ( i_printDebug )
{
std::cout << c_indent << c_indent << "Hit" << std::endl
<< c_indent << c_indent << c_indent << "position: " << record.m_position << std::endl
<< c_indent << c_indent << c_indent << "normal: " << record.m_normal << std::endl;
}
raytrace::Ray scatteredRay;
gm::Vec3f attenuation;
if ( record.m_material->Scatter( i_ray, record, attenuation, scatteredRay ) )
{
// Material produced a new scattered ray.
// Continue ray color recursion.
// To resolve an aggregate color, we take the vector product.
gm::Vec3f descendentColor =
ComputeRayColor( scatteredRay, i_numRayBounces - 1, i_rootObject, i_printDebug );
if ( i_printDebug )
{
std::cout << c_indent << c_indent << "Attenuation: " << attenuation << std::endl;
}
return gm::Vec3f( attenuation[ 0 ] * descendentColor[ 0 ],
attenuation[ 1 ] * descendentColor[ 1 ],
attenuation[ 2 ] * descendentColor[ 2 ] );
}
else
{
if ( i_printDebug )
{
std::cout << c_indent << c_indent << "Absorbed!" << std::endl;
}
// Material has completely absorbed the ray, thus return no color.
return gm::Vec3f( 0, 0, 0 );
}
}
if ( i_printDebug )
{
std::cout << c_indent << c_indent << "Background colour!" << std::endl;
}
// Compute background color, by interpolating between two colors with the weight as the function of the ray
// direction.
float weight = 0.5f * i_ray.Direction().Y() + 1.0;
return gm::LinearInterpolation( gm::Vec3f( 1.0, 1.0, 1.0 ), gm::Vec3f( 0.5, 0.7, 1.0 ), weight );
}
/// Shade the specified pixel coordinate \p i_pixelCoord through colors sampled from casted rays.
///
/// \param i_pixelCoord The pixel coordinate to shade.
/// \param i_samplesPerPixel The number of rays casted to sample colors, per pixel.
/// \param i_rayBounceLimit The number of bounces a ray can perform before it is retired.
/// \param i_camera The camera model which rays are cast from.
/// \param i_rootObject The root object to perform hit tests against.
/// \param o_image The image buffer to write color values into.
/// \param i_printDebug Flag to enable debug printing of shading and ray information.
void ShadePixel( const gm::Vec2i& i_pixelCoord,
int i_samplesPerPixel,
int i_rayBounceLimit,
const raytrace::Camera& i_camera,
const raytrace::SceneObjectPtr& i_rootObject,
const gm::FloatRange& i_shutterRange,
raytrace::RGBImageBuffer& o_image,
bool i_printDebug = false )
{
if ( i_printDebug )
{
std::cout << "Pixel " << i_pixelCoord << std::endl;
}
// This could be constant over the entire image. But I don't want to pass in any more function parameters...
const float lensRadius = i_camera.Aperture() * 0.5f;
// Accumulate pixel color over multiple samples.
gm::Vec3f pixelColor;
for ( int sampleIndex = 0; sampleIndex < i_samplesPerPixel; ++sampleIndex )
{
// Compute normalised viewport coordinates (values between 0 and 1).
float u = ( float( i_pixelCoord.X() ) + gm::RandomNumber( c_normalizedRange ) ) / o_image.Extent().Max().X();
float v = ( float( i_pixelCoord.Y() ) + gm::RandomNumber( c_normalizedRange ) ) / o_image.Extent().Max().Y();
// Compute lens offset, produces the depth of field effect for those objects not exactly
// at the focal distance.
gm::Vec3f randomPointInLens = lensRadius * raytrace::RandomPointInUnitDisk();
gm::Vec3f lensOffset = randomPointInLens.X() * i_camera.Right() + randomPointInLens.Y() * i_camera.Up();
// Construct our ray.
gm::Vec3f rayDirection = i_camera.ViewportBottomLeft() // Starting from the viewport bottom left...
+ ( u * i_camera.ViewportHorizontal() ) // Horizontal offset.
+ ( v * i_camera.ViewportVertical() ) // Vertical offset.
- i_camera.Origin() // Get difference vector from camera origin.
- lensOffset; // Since the origin was offset, we must apply the inverse offset to
// the ray direction such that the ray position _at the focal plane_
// is the same as before!
raytrace::Ray ray( /* origin */ i_camera.Origin() + lensOffset,
/* direction */ gm::Normalize( rayDirection ),
/* time */ gm::RandomNumber( i_shutterRange ) );
// Accumulate color.
gm::Vec3f sampleColor = ComputeRayColor( ray, i_rayBounceLimit, i_rootObject, i_printDebug );
pixelColor += sampleColor;
if ( i_printDebug )
{
std::cout << c_indent << "Sample: " << sampleIndex << std::endl;
std::cout << c_indent << "Sample color: " << sampleColor << std::endl;
}
}
// Divide by number of samples to produce average color.
pixelColor /= ( float ) i_samplesPerPixel;
// Correct for gamma 2, by raising to 1/gamma.
pixelColor[ 0 ] = sqrt( pixelColor[ 0 ] );
pixelColor[ 1 ] = sqrt( pixelColor[ 1 ] );
pixelColor[ 2 ] = sqrt( pixelColor[ 2 ] );
// Clamp the value down to [0,1).
pixelColor = gm::Clamp( pixelColor, c_normalizedRange );
// Assign finalized colour.
o_image( i_pixelCoord.X(), i_pixelCoord.Y() ) = pixelColor;
}
/// Populate the scene by appending a variety of objects to \p o_sceneObjects.
///
/// \param i_shutterRange The time range where the shutter opens and closes.
/// \param o_sceneObjects Collection to populate with scene objects.
void PopulateSceneObjects( const gm::FloatRange& i_shutterRange, raytrace::SceneObjectPtrs& o_sceneObjects )
{
raytrace::MaterialSharedPtr groundMaterial = std::make_shared< raytrace::Lambert >(
std::make_shared< raytrace::ConstantTexture >( gm::Vec3f( 0.5, 0.5, 0.5 ) ) );
o_sceneObjects.push_back( std::make_shared< raytrace::Sphere >( gm::Vec3f( 0, -1000, 0 ), 1000, groundMaterial ) );
for ( int a = -11; a < 11; a++ )
{
for ( int b = -11; b < 11; b++ )
{
gm::Vec3f center( a + 0.9 * gm::RandomNumber( c_normalizedRange ),
0.2,
b + 0.9 * gm::RandomNumber( c_normalizedRange ) );
if ( gm::Length( center - gm::Vec3f( 4, 0.2, 0 ) ) > 0.9 )
{
float materialChoice = gm::RandomNumber( c_normalizedRange );
if ( materialChoice < 0.8 )
{
// Diffuse.
gm::Vec3f albedo( gm::RandomNumber( c_normalizedRange ),
gm::RandomNumber( c_normalizedRange ),
gm::RandomNumber( c_normalizedRange ) );
raytrace::MaterialSharedPtr sphereMaterial = std::make_shared< raytrace::Lambert >(
std::make_shared< raytrace::ConstantTexture >( albedo ) );
// Compute a random Y axis translation.
gm::Vec3f centerTranslation( 0, gm::RandomNumber( gm::FloatRange( 0, 0.5f ) ), 0 );
// Set multiple time samples for the sphere.
std::vector< std::pair< float, gm::Vec3f > > timeSamples = {
{i_shutterRange.Min(), center},
{i_shutterRange.Max(), center + centerTranslation}};
o_sceneObjects.push_back(
std::make_shared< raytrace::Sphere >( raytrace::Attribute< gm::Vec3f >( timeSamples ),
0.2,
sphereMaterial ) );
}
else if ( materialChoice < 0.95 )
{
// Metal.
gm::Vec3f albedo( gm::RandomNumber( gm::FloatRange( 0.5, 1.0 ) ),
gm::RandomNumber( gm::FloatRange( 0.5, 1.0 ) ),
gm::RandomNumber( gm::FloatRange( 0.5, 1.0 ) ) );
float fuzziness = gm::RandomNumber( gm::FloatRange( 0.0, 0.5 ) );
raytrace::MaterialSharedPtr sphereMaterial = std::make_shared< raytrace::Metal >(
/* albedo */ std::make_shared< raytrace::ConstantTexture >( albedo ),
/* fuzziness */ fuzziness );
o_sceneObjects.push_back( std::make_shared< raytrace::Sphere >( center, 0.2, sphereMaterial ) );
}
else
{
// Glass.
raytrace::MaterialSharedPtr sphereMaterial = std::make_shared< raytrace::Dielectric >( 1.5 );
o_sceneObjects.push_back( std::make_shared< raytrace::Sphere >( center, 0.2, sphereMaterial ) );
}
}
}
}
raytrace::MaterialSharedPtr material1 = std::make_shared< raytrace::Dielectric >( 1.5 );
o_sceneObjects.push_back( std::make_shared< raytrace::Sphere >( gm::Vec3f( 0, 1, 0 ), 1.0, material1 ) );
raytrace::MaterialSharedPtr material2 = std::make_shared< raytrace::Lambert >(
std::make_shared< raytrace::ConstantTexture >( gm::Vec3f( 0.4, 0.2, 0.1 ) ) );
o_sceneObjects.push_back( std::make_shared< raytrace::Sphere >( gm::Vec3f( -4, 1, 0 ), 1.0, material2 ) );
raytrace::MaterialSharedPtr material3 = std::make_shared< raytrace::Metal >(
/* albedo */ std::make_shared< raytrace::ConstantTexture >( gm::Vec3f( 0.7, 0.6, 0.5 ) ),
/* fuzziness */ 0.0f );
o_sceneObjects.push_back( std::make_shared< raytrace::Sphere >( gm::Vec3f( 4, 1, 0 ), 1.0, material3 ) );
}
int main( int i_argc, char** i_argv )
{
// ------------------------------------------------------------------------
// Parse command line arguments.
// ------------------------------------------------------------------------
cxxopts::Options options( "1_boundingVolumeHierarchies",
"Ray tracing program which uses a BVH for efficient intersection tests." );
options.add_options() // Command line options.
( "w,width", "Width of the image.", cxxopts::value< int >()->default_value( "384" ) ) // Width
( "h,height", "Height of the image.", cxxopts::value< int >()->default_value( "256" ) ) // Height;
( "o,output", "Output file", cxxopts::value< std::string >()->default_value( "out.ppm" ) ) // Output file.
( "s,samplesPerPixel",
"Number of samples per-pixel.",
cxxopts::value< int >()->default_value( "100" ) ) // Number of samples.
( "b,rayBounceLimit",
"Number of bounces possible for a ray until termination.",
cxxopts::value< int >()->default_value( "50" ) ) // Maximum number of light bounces before termination.
( "f,verticalFov",
"Vertical field of view of the camera, in degrees.",
cxxopts::value< float >()->default_value( "20" ) ) // Camera param.
( "a,aperture",
"Aperture of the camera (lens diameter).",
cxxopts::value< float >()->default_value( "0.2" ) ) // Camera param.
( "shutterOpen",
"The time when the shutter is open.",
cxxopts::value< float >()->default_value( "0.0" ) ) // Motion blur param.
( "shutterClose",
"The time when the shutter is closed.",
cxxopts::value< float >()->default_value( "1.0" ) ) // Motion blur param.
( "d,debug", "Turn on debug mode.", cxxopts::value< bool >()->default_value( "false" ) ) // Debug mode.
( "x,debugXCoord",
"The x-coordinate of the pixel in the image to print debug information for.",
cxxopts::value< int >()->default_value( "0" ) ) // Xcoord.
( "y,debugYCoord",
"The y-coordinate of the pixel in the image to print debug information for.",
cxxopts::value< int >()->default_value( "0" ) ); // Ycoord.
auto args = options.parse( i_argc, i_argv );
// Imaging options.
int imageWidth = args[ "width" ].as< int >();
int imageHeight = args[ "height" ].as< int >();
int samplesPerPixel = args[ "samplesPerPixel" ].as< int >();
int rayBounceLimit = args[ "rayBounceLimit" ].as< int >();
float verticalFov = args[ "verticalFov" ].as< float >();
float aperture = args[ "aperture" ].as< float >();
std::string filePath = args[ "output" ].as< std::string >();
// Timing options.
gm::FloatRange shutterRange( args[ "shutterOpen" ].as< float >(), args[ "shutterClose" ].as< float >() );
// Debug options.
bool debug = args[ "debug" ].as< bool >();
int debugXCoord = args[ "debugXCoord" ].as< int >();
int debugYCoord = imageHeight - args[ "debugYCoord" ].as< int >();
// ------------------------------------------------------------------------
// Allocate image buffer & camera.
// ------------------------------------------------------------------------
// Allocate the image to write into.
raytrace::RGBImageBuffer image( imageWidth, imageHeight );
// Camera model.
gm::Vec3f origin = gm::Vec3f( 13, 2, 3 );
gm::Vec3f lookAt = gm::Vec3f( 0, 0, 0 );
raytrace::Camera camera(
/* origin */ origin,
/* lookAt */ lookAt,
/* viewUp */ gm::Vec3f( 0, 1, 0 ),
/* verticalFov */ verticalFov,
/* aspectRatio */ ( float ) imageWidth / imageHeight,
/* aperture */ aperture,
/* focalDistance */ 10.0 );
// ------------------------------------------------------------------------
// Allocate scene objects, and perform transformations.
// ------------------------------------------------------------------------
// Populate an array of scene objects.
raytrace::SceneObjectPtrs sceneObjects;
PopulateSceneObjects( shutterRange, sceneObjects );
// Transform the scene objects into a BVH tree.
std::vector< float > times = {shutterRange.Min(), shutterRange.Max()};
raytrace::SceneObjectPtr rootObject = std::make_shared< raytrace::SpatialBVHNode >( sceneObjects, times );
// ------------------------------------------------------------------------
// Compute ray colors.
// ------------------------------------------------------------------------
for ( const gm::Vec2i& pixelCoord : image.Extent() )
{
ShadePixel( pixelCoord, samplesPerPixel, rayBounceLimit, camera, rootObject, shutterRange, image );
}
// ------------------------------------------------------------------------
// Print debug pixel
// ------------------------------------------------------------------------
if ( debug )
{
ShadePixel( gm::Vec2i( debugXCoord, debugYCoord ),
samplesPerPixel,
rayBounceLimit,
camera,
rootObject,
shutterRange,
image,
/* printDebug */ true );
}
// ------------------------------------------------------------------------
// Write out image.
// ------------------------------------------------------------------------
if ( !raytrace::WritePPMImage( image, filePath ) )
{
return -1;
}
return 0;
}