Getting Started¶
The following examples shows all aspects of the high-level scene API. The code we show here is very similar to
examples/scene_primitives/main.cc; however, we excluded writing to a PNG file here.
Note
The scene API is considered to be (mostly) stable.
Warning
Scene<T> will very likely become Scene<T, BVH, Builder> at some point in time to choose between different BVH and Builder representations.
Setup¶
Before we can start using blazert we need to include a few headers from the C++ standard library and more importantly the blazert header itself:
1 2 3 4 5 6 | #include <iostream>
#include <memory>
#include <vector>
// blazert includes
#include <blazert/blazert.h>
|
blazert/blazert.h includes all headers necessary to use blazert. If you only need parts or want a more fine-grained
include, you can include single headers as well.
Furthermore, to easily change which floating point type we want to use, we define a type alias.
1 2 3 | // This alias is defined in order to setup the simulation for
// float or double, depending on what you want to do.
using ft = double;
|
We also define the dimensions for a rendering case which is later used for ray generation. We also define a rotation
matrix rot which we will later use to rotate the cylinders around the y-axis.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int main(int argc, char **argv) {
// define width and height of the rendering
int width = 1080;
int height = 720;
// Define a rotation matrix around y-axis
blazert::Mat3r<ft> rot{
{0, 0, 1},
{0, 1, 0},
{-1, 0, 0}};
...
}
|
Creating Primitives¶
To do any ray tracing we need to add some primitives to the scene. blazert comes with some pre-defined primitives:
sphere
cylinder
planes
Besides these simple primitives blazert can also trace against triangle meshes which is probably one of the more common application scenarios, at least for rendering.
For this example, we want to create two cylinders and one sphere.
First, we need to allocate memory on the heap with the pre-defined data types blazert::Vec3rList<T> and
blazert::Matr3rList<T> for the blaze data types. This is necessary, because otherwise reallocation of memory might
corrupt the pointers/references deep in blazert.
1 2 3 4 5 6 | // the properties of the cylinders need to be saved on the heap
auto centers = std::make_unique<blazert::Vec3rList<ft>>();
auto semi_axes_a = std::make_unique<std::vector<ft>>();
auto semi_axes_b = std::make_unique<std::vector<ft>>();
auto heights = std::make_unique<std::vector<ft>>();
auto rotations = std::make_unique<blazert::Mat3rList<ft>>();
|
Note
centers and rotations cannot be regular std::vector types because for blaze provides a custom allocator for aligned data types.
Next, we need to fill these lists with the information about our objects by adding fields to these lists:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // cylinder 1
centers->emplace_back(blazert::Vec3r<ft>{-3, 3, 0});
semi_axes_a->emplace_back(1);
semi_axes_b->emplace_back(1);
heights->emplace_back(1);
rotations->push_back(rot);
// cylinder 2
centers->emplace_back(blazert::Vec3r<ft>{1, 4, 0});
semi_axes_a->emplace_back(0.5);
semi_axes_b->emplace_back(0.5);
heights->emplace_back(2);
rotations->push_back(rot);
|
Therefore, each std::vector holds $N$ entries if $N$ primitives are have been added to the scene.
The same can be done with other primitives as well. They follow the same scheme. Only the needed parameters to describe the primitives differ.
1 2 3 4 5 6 7 8 | // We do the same for the spheres
auto sph_centers = std::make_unique<blazert::Vec3rList<ft>>();
auto radii = std::make_unique<std::vector<ft>>();
// sphere 1
sph_centers->emplace_back(blazert::Vec3r<ft>{1, 1, 0});
radii->emplace_back(0.5);
sph_centers->emplace_back(blazert::Vec3r<ft>{-2, 10, 0});
radii->emplace_back(1.5);
|
Before we can start path tracing we need to create a Scene object and add the corresponding primitives via the
add_<primitive> functions. More information on the API can be found in the API reference.
1 2 3 4 | // Create the scene, add the cylinders and spheres
blazert::Scene<ft> scene;
scene.add_cylinders(*centers, *semi_axes_a, *semi_axes_b, *heights, *rotations);
scene.add_spheres(*sph_centers, *radii);
|
We are almost set up for path tracing now.
Path Tracing¶
Before we can do any ray tracing we need to commit the scene. This means, we get the scene ready for ray tracing by building the BVH acceleration structure:
1 2 3 4 | // commits the scene -> build two (trivial) BVHs:
// - one for cylinders
// - one for spheres
scene.commit();
|
Note
Each geometry type has it’s own BVH.
Now, we are ready to do the ray tracing. In this case we define a ray origin (0, 5, 20) and the direction of each ray
is determined by the grid of dimension \(\mathrm{width} \times \mathrm{height}\).
The actual ray tracing is done by intersect1 which traverses the BVHs of each geometry type and finds the closest intersection
between the ray and any geometry present in the scene.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // iterate over the pixels which define the direction of the rays which are launched
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// create a ray
const blazert::Ray<ft> ray{
{0.0, 5.0, 20.0},
{static_cast<ft>((x / ft(width)) - 0.5),
static_cast<ft>((y / ft(height)) - 0.5), ft(-1.)}
};
blazert::RayHit<ft> rayhit;
const bool hit = intersect1(scene, ray, rayhit);
if (hit) {
// Do something ...
}
}
}
|
You are now ready to dive into the blazert framework and use it however you like to.
Complete Code¶
The complete code is shown here:
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 | #include <iostream>
#include <memory>
#include <vector>
#include <blazert/blazert.h>
#include <blazert/datatypes.h>
// This alias is defined in order to setup the simulation for
// float or double, depending on what you want to do.
using ft = double;
int main(int argc, char **argv) {
// define width and height of the rendering
int width = 1080;
int height = 720;
// the properties of the cylinders need to be saved on the heap
auto centers = std::make_unique<blazert::Vec3rList<ft>>();
auto semi_axes_a = std::make_unique<std::vector<ft>>();
auto semi_axes_b = std::make_unique<std::vector<ft>>();
auto heights = std::make_unique<std::vector<ft>>();
auto rotations = std::make_unique<blazert::Mat3rList<ft>>();
// Define a rotation matrix around y-axis
blazert::Mat3r<ft> rot{
{0, 0, 1},
{0, 1, 0},
{-1, 0, 0}};
// Each cylinder adds an element to the std::vectors containing the
// corresponding parameters
// cylinder 1
centers->emplace_back(blazert::Vec3r<ft>{-3, 3, 0});
semi_axes_a->emplace_back(1);
semi_axes_b->emplace_back(1);
heights->emplace_back(1);
rotations->push_back(rot);
// cylinder 2
centers->emplace_back(blazert::Vec3r<ft>{1, 4, 0});
semi_axes_a->emplace_back(0.5);
semi_axes_b->emplace_back(0.5);
heights->emplace_back(2);
rotations->push_back(rot);
// We do the same for the spheres
auto sph_centers = std::make_unique<blazert::Vec3rList<ft>>();
auto radii = std::make_unique<std::vector<ft>>();
// sphere 1
sph_centers->emplace_back(blazert::Vec3r<ft>{1, 1, 0});
radii->emplace_back(0.5);
sph_centers->emplace_back(blazert::Vec3r<ft>{-2, 10, 0});
radii->emplace_back(1.5);
// Create the scene, add the cylinders and spheres
blazert::Scene<ft> scene;
scene.add_cylinders(*centers, *semi_axes_a, *semi_axes_b, *heights, *rotations);
scene.add_spheres(*sph_centers, *radii);
// commits the scene -> build two (trivial) BVHs:
// - one for cylinders
// - one for spheres
scene.commit();
// iterate over the pixels which define the direction of the rays which are launched
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// create a ray
const blazert::Ray<ft> ray{
{0.0, 5.0, 20.0},
{static_cast<ft>((x / ft(width)) - 0.5),
static_cast<ft>((y / ft(height)) - 0.5), ft(-1.)}
};
blazert::RayHit<ft> rayhit;
const bool hit = intersect1(scene, ray, rayhit);
if (hit) {
// Do something ...
}
}
}
return 0;
}
|