We stand with Ukraine!
This project contains the following files (right-click files you'd like to download):
raymarch-chap2.cppraymarch-chap3.cppraymarch-chap4.cppraymarch-chap5.cppcachefiles.zip
//[header] // Rendering volumetric object using ray-marching. A basic implementation (chapter 1 & 2) // // https://www.scratchapixel.com/lessons/advanced-rendering/volume-rendering-for-developers/ray-marching-algorithm //[/header] //[compile] // Download the raymarch-chap2.cpp file to a folder. // Open a shell/terminal, and run the following command where the files is saved: // // clang++ -O3 raymarch-chap2.cpp -o render -std=c++17 (optional: -DBACKWARD_RAYMARCHING) // // You can use c++ if you don't use clang++ // // Run with: ./render. Open the resulting image (ppm) in Photoshop or any program // reading PPM files. //[/compile] //[ignore] // Copyright (C) 2022 www.scratchapixel.com // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. //[/ignore] #define _USE_MATH_DEFINES #include <cmath> #include <iostream> #include <fstream> #include <memory> #include <algorithm> #include <vector> #include <random> struct vec3 { float x{ 0 }, y{ 0 }, z{ 0 }; vec3& nor() { float len = x * x + y * y + z * z; if (len != 0) len = sqrtf(len); x /= len, y /= len, z /= len; return *this; } float length() const { return sqrtf(x * x + y * y + z * z); } float operator * (const vec3& v) const { return x * v.x + y * v.y + z * v.z; } vec3 operator - (const vec3& v) const { return vec3{x - v.x, y - v.y, z - v.z}; } vec3 operator + (const vec3& v) const { return vec3{x + v.x, y + v.y, z + v.z}; } vec3& operator += (const vec3& v) { x += v.x, y += v.y, z += v.z; return *this; } vec3& operator *= (const float& r) { x *= r, y *= r, z *= r; return *this; } friend vec3 operator * (const float& r, const vec3& v) { return vec3{v.x * r, v.y * r, v.z * r}; } friend std::ostream& operator << (std::ostream& os, const vec3& v) { os << v.x << " " << v.y << " " << v.z; return os; } vec3 operator * (const float &r) const { return vec3{ x * r, y * r, z * r }; } }; constexpr vec3 background_color{ 0.572f, 0.772f, 0.921f }; constexpr float floatMax = std::numeric_limits<float>::max(); struct IsectData { float t0{ floatMax }, t1{ floatMax }; vec3 pHit; vec3 nHit; bool inside{ false }; }; struct Object { public: vec3 color; int type{ 0 }; virtual bool intersect(const vec3&, const vec3&, IsectData&) const = 0; virtual ~Object() {} Object() {} }; bool solveQuadratic(float a, float b, float c, float& r0, float& r1) { float d = b * b - 4 * a * c; if (d < 0) return false; else if (d == 0) r0 = r1 = -0.5f * b / a; else { float q = (b > 0) ? -0.5f * (b + sqrtf(d)) : -0.5f * (b - sqrtf(d)); r0 = q / a; r1 = c / q; } if (r0 > r1) std::swap(r0, r1); return true; } struct Sphere : Object { public: Sphere() { color = vec3{ 1, 0, 0 }; type = 1; } bool intersect(const vec3& rayOrig, const vec3& rayDir, IsectData& isect) const override { vec3 rayOrigc = rayOrig - center; float a = rayDir * rayDir; float b = 2 * (rayDir * rayOrigc); float c = rayOrigc * rayOrigc - radius * radius; if (!solveQuadratic(a, b, c, isect.t0, isect.t1)) return false; if (isect.t0 < 0) { if (isect.t1 < 0) return false; else { isect.inside = true; isect.t0 = 0; } } return true; } float radius{ 1 }; vec3 center{ 0, 0, -4 }; }; std::default_random_engine generator; std::uniform_real_distribution<float> distribution(0.0, 1.0); vec3 integrate(const vec3& ray_orig, const vec3& ray_dir, const std::vector<std::unique_ptr<Object>> &objects) { const Object* hit_object = nullptr; IsectData isect; for (const auto& object : objects) { IsectData isect_object; if (object->intersect(ray_orig, ray_dir, isect_object)) { hit_object = object.get(); isect = isect_object; } } if (!hit_object) return background_color; float step_size = 0.2; float absorption = 0.1; float scattering = 0.1; float density = 1; int ns = std::ceil((isect.t1 - isect.t0) / step_size); step_size = (isect.t1 - isect.t0) / ns; vec3 light_dir{ 0, 1, 0 }; vec3 light_color{ 1.3, 0.3, 0.9 }; IsectData isect_vol; float transparency = 1; // initialize transmission to 1 (fully transparent) vec3 result{ 0 }; // initialize volumetric sphere color to 0 #ifdef BACKWARD_RAYMARCHING // [comment] // The ray-marching loop (backward, march from t1 to t0) // [/comment] for (int n = 0; n < ns; ++n) { float t = isect.t1 - step_size * (n + 0.5); vec3 sample_pos = ray_orig + t * ray_dir; float sample_transparency = exp(-step_size * (scattering + absorption)); transparency *= sample_transparency; if (hit_object->intersect(sample_pos, light_dir, isect_vol) && isect_vol.inside) { float light_attenuation = exp(-density * isect_vol.t1 * (scattering + absorption)); result += light_color * light_attenuation * scattering * density * step_size; } else std::cerr << "oops\n"; result *= sample_transparency; } return background_color * transparency + result; #else // [comment] // The ray-marching loop (forward, march from t0 to t1) // [/comment] for (int n = 0; n < ns; ++n) { float t = isect.t0 + step_size * (n + 0.5); vec3 sample_pos = ray_orig + t * ray_dir; // compute sample transmission float sample_attenuation = exp(-step_size * (scattering + absorption)); transparency *= sample_attenuation; // In-scattering. Find distance light travels through volumetric sphere to the sample. // Then use Beer's law to attenuate the light contribution due to in-scattering. if (hit_object->intersect(sample_pos, light_dir, isect_vol) && isect_vol.inside) { float light_attenuation = exp(-density * isect_vol.t1 * (scattering + absorption)); result += transparency * light_color * light_attenuation * scattering * density * step_size; } else std::cerr << "oops\n"; } // combine background color and volumetric sphere color return background_color * transparency + result; #endif } int main() { unsigned int width = 640, height = 480; auto buffer = std::make_unique<unsigned char[]>(width * height * 3); auto frameAspectRatio = width / float(height); float fov = 45; float focal = tan(M_PI / 180 * fov * 0.5); std::vector<std::unique_ptr<Object>> geo; std::unique_ptr<Sphere> sph = std::make_unique<Sphere>(); sph->radius = 5; sph->center.x = 0; sph->center.y = 0; sph->center.z = -20; geo.push_back(std::move(sph)); vec3 rayOrig, rayDir; // ray origin & direction unsigned int offset = 0; for (unsigned int j = 0; j < height; ++j) { for (unsigned int i = 0; i < width; ++i) { rayDir.x = (2.f * (i + 0.5f) / width - 1) * focal; rayDir.y = (1 - 2.f * (j + 0.5f) / height) * focal * 1 / frameAspectRatio; // Maya style rayDir.z = -1.f; rayDir.nor(); vec3 c = integrate(rayOrig, rayDir, geo); buffer[offset++] = std::clamp(c.x, 0.f, 1.f) * 255; buffer[offset++] = std::clamp(c.y, 0.f, 1.f) * 255; buffer[offset++] = std::clamp(c.z, 0.f, 1.f) * 255; } } // writing file std::ofstream ofs; ofs.open("./image.ppm", std::ios::binary); ofs << "P6\n" << width << " " << height << "\n255\n"; ofs.write(reinterpret_cast<const char*>(buffer.get()), width * height * 3); ofs.close(); return 0; }