Skip to content
This repository was archived by the owner on Dec 14, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 54 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,68 @@ These images were generated with 4 bounces and samples-per-pixel 16, 128, 512 an

## Build Instructions

### Windows

**Requirements:**
- A C++ compiler. Visual Studio is the simplest way to set this up on Windows. Follow the steps [here](https://devblogs.microsoft.com/cppblog/getting-started-with-visual-studio-for-c-and-cpp-development/).
- CMake. Download [here](https://cmake.org/download/). Get the Windows installer (file with .msi extension). Run the setup wizard and install. If there's an option to add something to your PATH, do it. This will allow you to run a `cmake` command from the command prompt.
- git. You can get it [here](https://git-scm.com/downloads). The download can be extremely slow sometimes, let me know if that is the case.
- A text editor. I use [vscode](https://code.visualstudio.com/). For a tutorial to use it with C++, see [this page](https://code.visualstudio.com/docs/cpp/config-msvc). Also install the CMake extension.

**Build:**

Open the "Developer Command Prompt" that was installed with Visual Studio. You will be greeted with something like this
```
C:\...some-path...>_
```
The path mentioned here is the "current directory". All commands entered will be relative to the current directory. You can change the current directory by typing `cd <new-path>` and Enter. Note that if you want to change the drive from C to D, do `d:` first, and then you'll be able to `cd` into any directory in the D drive.

Go to the path where you want to download this repository. I will assume that this is in `D:\code\`. Use git to download the repository
```
git clone https://github.com/mayant15/stamatics-pbr
```
You should now have a folder `D:\code\stamatics-pbr` with all code. Go into this folder with `cd stamatics-pbr`. We can now use CMake to compile the code.
```
mkdir build
cd build
cmake ..
cmake --build .
```

You should now have an executable in `build\bin` or `build\bin\Debug`.
- `mkdir build` creates a new folder called `build` inside the repository. This is going to store temporary data that CMake needs.- `cd build` to go into the `build` folder
- `cmake ..` starts cmake's _configuration step_. This step needs a configuration file with the settings for this project. This file, the `CMakeLists.txt`, has already been provided with the repository.
- `cmake --build .` tells cmake to compile the configured project. This will use our C++ compiler to generate the final executable.

### Ubuntu

**Requirements:**
- C++ compiler
- CMake (get this via `pip` and not `apt` if you're on Ubuntu)
- A C++ compiler. You can get this by running `sudo apt install build-essential`, which installs `gcc` and `g++`, among other things.
- CMake. Get this via `pip` if you have python installed, with `pip install cmake`. The version in `apt` is fairly old.
- git. You can get this by running `sudo apt install git`.
- A text editor. I use [vscode](https://code.visualstudio.com/). For a tutorial to use it with C++, see [this page](https://code.visualstudio.com/docs/cpp/config-linux). Also install the CMake extension.

**Build:**

Open a terminal. You will be greeted with something like this
```
user@device:path$ _
```
The path mentioned here is the "current directory". All commands entered will be relative to the current directory. You can change the current directory by typing `cd <new-path>` and Enter.

Go to the path where you want to download this repository. I will assume that this is in `~/code/`. Use git to download the repository
```
git clone https://github.com/mayant15/stamatics-pbr
```
You should now have a folder `~/code/stamatics-pbr` with all code. Go into this folder with `cd stamatics-pbr`. We can now use CMake to compile the code.
```
mkdir build
cd build
cmake ..
cmake --build .
```

You should now have an executable in `build/bin` or `build/bin/Debug`
You should now have an executable in `build/bin` or `build/bin/Debug`.
- `mkdir build` creates a new folder called `build` inside the repository. This is going to store temporary data that CMake needs.- `cd build` to go into the `build` folder
- `cmake ..` starts cmake's _configuration step_. This step needs a configuration file with the settings for this project. This file, the `CMakeLists.txt`, has already been provided with the repository.
- `cmake --build .` tells cmake to compile the configured project. This will use our C++ compiler to generate the final executable.
76 changes: 67 additions & 9 deletions src/brdfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace path
// NOTE: in is w.r.t. rays from the camera
struct BaseBRDF
{
virtual Ray sample(const Ray& in, const HitResult& hit)
virtual Ray sample(const Ray &in, const HitResult &hit)
{
// Samples a hemisphere uniformly by default
double u1 = dist(gen);
Expand All @@ -21,16 +21,15 @@ namespace path
double phi = 2 * PBR_PI * u2;

Vec w = hit.normal;
Vec u = normalize(cross(w, Vec { 0, 1, 0 }));
Vec u = normalize(cross(w, Vec{0, 1, 0}));
Vec v = normalize(cross(u, w));

return {
hit.point,
normalize(u * A * std::cos(phi) + v * A * std::sin(phi) + w * u1)
};
normalize(u * A * std::cos(phi) + v * A * std::sin(phi) + w * u1)};
}

virtual Colorf eval(const Ray& in, const HitResult& hit, const Ray& out)
virtual Colorf eval(const Ray &in, const HitResult &hit, const Ray &out)
{
// Returns hit.material.color by default
return hit.material->color;
Expand All @@ -50,7 +49,7 @@ namespace path
/** Lambertian Diffuse BRDF */
struct DiffuseBRDF : public BaseBRDF
{
virtual Colorf eval(const Ray& in, const HitResult& hit, const Ray& out) override
virtual Colorf eval(const Ray &in, const HitResult &hit, const Ray &out) override
{
// return normalize(out.direction);
auto diff = cosv(out.direction, hit.normal);
Expand All @@ -60,21 +59,80 @@ namespace path

struct SpecularBRDF : public BaseBRDF
{
virtual Ray sample(const Ray& in, const HitResult& hit) override
virtual Ray sample(const Ray &in, const HitResult &hit) override
{
Ray refl;
refl.direction = reflect(in.direction, hit.normal);
refl.origin = hit.point;
return refl;
}

virtual Colorf eval(const Ray& in, const HitResult& hit, const Ray& out) override
virtual Colorf eval(const Ray &in, const HitResult &hit, const Ray &out) override
{
return PBR_COLOR_WHITE;
}
};
}

/** Oren Nayer BRDF */
struct OrenNayarBRDF : public BaseBRDF
{
virtual Colorf eval(const Ray &in, const HitResult &hit, const Ray &out) override
{
// in ==> camera to point
// out ==> point to light

// Roughness calculations
double sigma = 0.75;
double A = 1.0 - (0.5 * ((sigma * sigma) / ((sigma * sigma) + 0.33)));
double B = 0.45 * ((sigma * sigma) / ((sigma * sigma) + 0.09));

// Angle Calculations
/*
double thetaI = pbr_angle(out.direction, hit.normal);
double thetaO = pbr_angle(in.direction, hit.normal);
double alpha = pbr_max(thetaI, thetaO);
double beta = pbr_min(thetaI, thetaO);
Vec cos_values;
cos_values.x = clamp(dot(hit.normal, out.direction));
cos_values.y = clamp(dot(hit.normal, in.direction));
Vec out_plane = normalize(out.direction - hit.normal * cos_values.x);
Vec in_plane = normalize(in.direction - hit.normal * cos_values.y);
float cos_val = clamp(dot(out_plane, in_plane));
auto diff = cosv(out.direction, hit.normal) * (A + (B * cos_val * sin(alpha) * tan(beta)));
*/

// Simplified Implementation
Vec N = hit.normal;
double L = clamp(dot(out.direction, N));
double V = clamp(dot(in.direction, N));
double D = sqrt(1 - (L * L)) * sqrt(1 - (V * V)) / pbr_max(L, V);
Vec out_plane = normalize(out.direction - N * L);
Vec in_plane = normalize(in.direction - N * V);
double C = clamp(dot(out_plane, in_plane));
auto diff = cosv(out.direction, hit.normal) * (A + (B * C * D));

return hit.material->color * diff * 2; // 2pi for monte carlo, 1/pi for Lambertian BRDF
}
};

struct PhongBRDF : public BaseBRDF
{
virtual Colorf eval(const Ray &in, const HitResult &hit, const Ray &out) override
{
const double kD = 0.95;
const double kS = 1 - kD;
const double shininess = 32;

auto diff = clamp(cosv(out.direction, hit.normal));
auto spec = std::pow(
clamp(dot(
normalize(reflect(out.direction * -1, hit.normal)),
normalize(in.direction * -1))),
shininess);
return hit.material->color * (kD * diff + kS * spec) * 2; // 2pi for monte carlo, 1/pi for Lambertian BRDF
}
};
}

#if 0

Expand Down
41 changes: 25 additions & 16 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,44 @@

#define PBR_MAX_RECURSION_DEPTH 4

#define PBR_SAMPLES_PER_PIXEL 16
#define PBR_SAMPLES_PER_PIXEL 1024

///////////////////////////////////////////////////////////////////////////////
// Scene and camera

#define PBR_ACTIVE_SCENE PBR_SCENE_RTWEEKEND
#define PBR_ACTIVE_SCENE PBR_SCENE_FINAL_ON

#define PBR_CAMERA_LOOKAT Vec { 0, 1, 0 }
#define PBR_CAMERA_POSITION Vec { 0, 2, 5 }
#define PBR_CAMERA_FOV_DEG 45
#define PBR_CAMERA_LOOKAT \
Vec { 0, 1, 0 }
#define PBR_CAMERA_POSITION \
Vec { 0, 2.5, 6 }
#define PBR_CAMERA_FOV_DEG 45

///////////////////////////////////////////////////////////////////////////////
// Preset colors

#define PBR_COLOR_SKYBLUE Colorf { 0.572, 0.886, 0.992 }
#define PBR_COLOR_BLACK Colorf { 0.0, 0.0, 0.0 }
#define PBR_COLOR_WHITE Colorf { 1.0, 1.0, 1.0 }
#define PBR_COLOR_GREY Colorf { 0.2, 0.2, 0.2 }
#define PBR_COLOR_RED Colorf { 1.0, 0.0, 0.0 }
#define PBR_COLOR_GREEN Colorf { 0.0, 1.0, 0.0 }
#define PBR_COLOR_BLUE Colorf { 0.0, 0.0, 1.0 }

#define PBR_BACKGROUND_COLOR PBR_COLOR_BLACK
#define PBR_COLOR_SKYBLUE \
Colorf { 0.572, 0.886, 0.992 }
#define PBR_COLOR_BLACK \
Colorf { 0.0, 0.0, 0.0 }
#define PBR_COLOR_WHITE \
Colorf { 1.0, 1.0, 1.0 }
#define PBR_COLOR_GREY \
Colorf { 0.2, 0.2, 0.2 }
#define PBR_COLOR_RED \
Colorf { 1.0, 0.0, 0.0 }
#define PBR_COLOR_GREEN \
Colorf { 0.0, 1.0, 0.0 }
#define PBR_COLOR_BLUE \
Colorf { 0.0, 0.0, 1.0 }

#define PBR_BACKGROUND_COLOR PBR_COLOR_SKYBLUE

///////////////////////////////////////////////////////////////////////////////
// Output

#define PBR_OUTPUT_IMAGE_COLUMNS 1280
#define PBR_OUTPUT_IMAGE_ROWS 720
#define PBR_OUTPUT_IMAGE_ROWS 720
#define PBR_OUTPUT_IMAGE_NAME "out.png"
#define PBR_USE_THREADS 1

Expand All @@ -44,4 +53,4 @@
#define PBR_ACTIVE_SAMPLER_CLASS UniformSampler
#define PBR_DISCRETE_SAMPLER_DIFFUSE_OFFSET 50
#define PBR_GRID_SAMPLER_SIZE 1
#define PBR_ACTIVE_BRDF_CLASS path::DiffuseBRDF
#define PBR_ACTIVE_BRDF_CLASS path::DiffuseBRDF
41 changes: 23 additions & 18 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,21 @@ std::vector<Point2D> sample_unit_circle()
std::mt19937 gen(rd());
std::uniform_real_distribution<> dist(0.0, 1.0);

std::vector<Point2D> result = { Point2D { 0.0, 0.0} }; // Always sample the origin
for (int i = 0; i < PBR_SAMPLES_PER_PIXEL - 1; ++i)
std::vector<Point2D> result = {Point2D{0.0, 0.0}}; // Always sample the origin
for (int i = 0; i < PBR_SAMPLES_PER_PIXEL - 1; ++i)
{
double u1 = dist(gen);
double u2 = dist(gen);

result.emplace_back(
std::sqrt(u1) * std::cos(2 * PBR_PI * u2),
std::sqrt(u1) * std::sin(2 * PBR_PI * u2)
);
std::sqrt(u1) * std::sin(2 * PBR_PI * u2));
}

return result;
}

Colori to_colori(const Colorf& color)
Colori to_colori(const Colorf &color)
{
// Gamma correction
double gamma = 1 / 2.2;
Expand All @@ -39,10 +38,7 @@ Colori to_colori(const Colorf& color)
double gz = std::pow(clamp(color.z), gamma);

// Convert a valid Colorf to Colori
return (255 << 24)
| ((int) std::floor(gz * 255) << 16)
| ((int) std::floor(gy * 255) << 8)
| (int) std::floor(gx * 255);
return (255 << 24) | ((int)std::floor(gz * 255) << 16) | ((int)std::floor(gy * 255) << 8) | (int)std::floor(gx * 255);
}

int main()
Expand All @@ -54,16 +50,16 @@ int main()
camera.position = PBR_CAMERA_POSITION;
camera.look_at = PBR_CAMERA_LOOKAT;
camera.fov = PBR_CAMERA_FOV_DEG;
camera.calculate_basis((double) PBR_OUTPUT_IMAGE_COLUMNS / PBR_OUTPUT_IMAGE_ROWS);
camera.calculate_basis((double)PBR_OUTPUT_IMAGE_COLUMNS / PBR_OUTPUT_IMAGE_ROWS);

PathIntegrator integrator;
integrator.set_scene(&PBR_ACTIVE_SCENE);

std::vector<Colori> image (PBR_OUTPUT_IMAGE_ROWS * PBR_OUTPUT_IMAGE_COLUMNS);
std::vector<Colori> image(PBR_OUTPUT_IMAGE_ROWS * PBR_OUTPUT_IMAGE_COLUMNS);

// Iterate over all rows
#if PBR_USE_THREADS
#pragma omp parallel for
#pragma omp parallel for
#endif
for (int row = 0; row < PBR_OUTPUT_IMAGE_ROWS; ++row)
{
Expand All @@ -74,17 +70,26 @@ int main()
{

// TODO: Implement stratified sampling here

// Done
auto spp = sample_unit_circle();

Colorf color;
for (const auto& s : spp)
int i = 0;
for (const auto &s : spp)
{
double c_x, c_y, d_x, d_y;
c_x = 0.5 * (i % 2) * 2 - 1;
c_y = 0.5 * (((i % 4) < 2) ? 1 : -1);
d_x = s.x / 2.0;
d_y = s.y / 2.0;
double x = ((col + c_x + d_x) / PBR_OUTPUT_IMAGE_COLUMNS) * 2 - 1;
double y = ((row + c_y + d_y) / PBR_OUTPUT_IMAGE_ROWS) * 2 - 1;
// Normalize (row + deviation, col + deviation) to (x, y) where x and y are between -1 and 1.
double x = ((col + s.x) / PBR_OUTPUT_IMAGE_COLUMNS) * 2 - 1;
double y = ((row + s.y) / PBR_OUTPUT_IMAGE_ROWS) * 2 - 1;
/* double x = ((col + s.x) / PBR_OUTPUT_IMAGE_COLUMNS) * 2 - 1;
double y = ((row + s.y) / PBR_OUTPUT_IMAGE_ROWS) * 2 - 1;*/
Ray ray = camera.get_ray(x, y);
color = color + integrator.trace_ray(ray, 0) / (PBR_SAMPLES_PER_PIXEL);
i++;
}

image[row * PBR_OUTPUT_IMAGE_COLUMNS + col] = to_colori(color);
Expand All @@ -93,12 +98,12 @@ int main()

// Write to file
stbi_flip_vertically_on_write(true);
stbi_write_png(PBR_OUTPUT_IMAGE_NAME, PBR_OUTPUT_IMAGE_COLUMNS, PBR_OUTPUT_IMAGE_ROWS, 4, image.data(), PBR_OUTPUT_IMAGE_COLUMNS * sizeof (Colori));
stbi_write_png(PBR_OUTPUT_IMAGE_NAME, PBR_OUTPUT_IMAGE_COLUMNS, PBR_OUTPUT_IMAGE_ROWS, 4, image.data(), PBR_OUTPUT_IMAGE_COLUMNS * sizeof(Colori));

// Completed successfully! :)
std::cout << "All ok!" << std::endl;
}
catch (const std::exception& e)
catch (const std::exception &e)
{
// Print out an error message if caught
std::cout << "ERROR: " << e.what() << std::endl;
Expand Down
Loading