├── .gitignore ├── 3d-viewing-pinhole-camera ├── boat.obj ├── geometry.h └── pinhole.cpp ├── README.md ├── bezier-curve-rendering-utah-teapot ├── geometry.h ├── teapot.cpp └── teapotdata.h ├── build-raytracer-core ├── area-light-impl.cc ├── area-light-impl.ilk ├── area-light.cc ├── intro-lighting-kraemer-remapping-quad-tri.gif ├── intro-lighting-quadtree.gif ├── math.h ├── mc-integration.cc ├── pointlight.cc ├── quad.cc ├── quad.ilk ├── random_dir.cc ├── source.mel ├── spotlight.cc ├── test.cc └── triangle-sampling.cc ├── cam-nav-controls └── 3d-nav-controls.cc ├── colors └── mcbeth.cpp ├── computing-pixel-coordinates-of-3d-point ├── geometry.h ├── perspproj.cpp └── xtree.obj ├── digital-images ├── readwrite.cpp └── xmas.ppm ├── geometry └── geometry.cpp ├── global-illumination-path-tracing ├── geometry.h ├── indirectdiffuse.cpp └── objects.zip ├── interpolation ├── .gitignore └── interpolation.cpp ├── introduction-acceleration-structure ├── acceleration.cpp └── teapotdata.h ├── introduction-polygon-mesh ├── cow.geo ├── geometry.h └── raytracepolymesh.cpp ├── introduction-rendering ├── raytracer ├── raytracer.cpp └── untitled.ppm ├── introduction-to-lighting ├── .gitignore ├── area-light-impl.cc ├── math.h ├── pointlight.cc └── spotlight.cc ├── introduction-to-ray-tracing └── raytracer.cpp ├── introduction-to-shading ├── geometry.zip └── shading.cpp ├── introduction-to-texturing ├── cube1.h ├── pixar-texture3.pbm ├── sphere.h └── texturing.c ├── matrix-inverse └── MatrixInverse.cpp ├── minimal-ray-tracer-rendering-simple-shapes ├── geometry.h ├── raybox.cpp └── simpleshapes.cpp ├── monte-carlo-methods-in-practice ├── mcintegration.cpp └── mcsim.cpp ├── obj-file-format ├── objimporter.cc └── zombie.obj ├── perlin-noise-part-2 └── perlinnoise.cpp ├── perspective-and-orthographic-projection-matrix ├── geometry.h ├── glorthoprojmatrix.cpp ├── glprojmatrix.cpp ├── projmatrix.cpp ├── teapot.obj └── vertexdata.h ├── phong-shader-BRDF ├── geometry.h └── phong.cpp ├── polygon-mesh ├── cow.geo ├── geometry.h ├── loadgeometry.cpp ├── raster3d.cpp └── test.geo ├── procedural-patterns-noise-part-1 └── noise.cpp ├── rasterization-practical-implementation ├── cow.h ├── cow.obj ├── geometry.h └── raster3d.cpp ├── ray-tracing-generating-camera-rays ├── camerarays.cpp └── geometry.h ├── ray-tracing-overview └── whitted.cpp ├── ray-tracing-rendering-a-triangle ├── geometry.h └── raytri.cpp ├── simple-image-manipulations ├── image.cpp └── testimages.zip ├── simulating-sky └── skycolor.cpp ├── transforming-objects-using-matrices ├── geometry.h ├── raytracetransform.cpp └── teapot.geo ├── volume-rendering-for-developers ├── cachefiles.zip ├── raymarch-chap2.cpp ├── raymarch-chap3.cpp ├── raymarch-chap4.cpp ├── raymarch-chap5.cpp └── raymarch-chap6.cpp └── windowing ├── sample.pbm └── window.cc /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.ppm 3 | *.pbm 4 | *.pdb 5 | fourier-transform/ 6 | -------------------------------------------------------------------------------- /3d-viewing-pinhole-camera/boat.obj: -------------------------------------------------------------------------------- 1 | v -2.570266 0.780534 -0.000024 2 | v -0.892643 0.022582 0.018577 3 | v 1.687845 -0.017131 0.022032 4 | v 3.465899 0.025667 0.018577 5 | v -2.570266 0.789692 -0.001202 6 | v -0.892643 0.251213 0.935730 7 | v 1.687845 0.251213 1.109724 8 | v 3.503150 0.252933 0.935730 9 | v -2.570266 1.055815 -0.001347 10 | v -0.892643 1.055815 1.048658 11 | v 1.687845 1.055815 1.243651 12 | v 3.634242 1.052729 1.048658 13 | v -2.570266 1.055815 0.000000 14 | v -0.892643 1.055815 0.000000 15 | v 1.687845 1.055815 0.000000 16 | v 3.634242 1.052729 0.000000 17 | v -2.570266 1.055815 0.001347 18 | v -0.892643 1.055815 -1.048658 19 | v 1.687845 1.055815 -1.243651 20 | v 3.634242 1.052729 -1.048658 21 | v -2.570266 0.789692 0.001202 22 | v -0.892643 0.251213 -0.935730 23 | v 1.687845 0.251213 -1.109724 24 | v 3.503150 0.252933 -0.935730 25 | v 3.503150 0.252933 0.000000 26 | v -2.570266 0.789692 0.000000 27 | v 1.109142 1.217887 0.000000 28 | v 1.145029 6.617012 0.000000 29 | v 4.087809 1.238261 0.000000 30 | v -2.569333 1.177139 -0.081683 31 | v 0.983535 6.494768 -0.081683 32 | v -0.721124 1.136391 -0.081683 33 | v 0.929704 6.454020 0.000000 34 | v -0.792899 1.279010 0.000000 35 | v 0.911760 1.299384 0.000000 36 | f 5 1 6 37 | f 1 2 6 38 | f 2 3 6 39 | f 6 3 7 40 | f 4 8 3 41 | f 3 8 7 42 | f 6 10 5 43 | f 5 10 9 44 | f 6 7 10 45 | f 10 7 11 46 | f 8 12 7 47 | f 7 12 11 48 | f 10 14 9 49 | f 9 14 13 50 | f 11 15 10 51 | f 10 15 14 52 | f 11 12 15 53 | f 15 12 16 54 | f 18 17 14 55 | f 13 14 17 56 | f 14 15 18 57 | f 18 15 19 58 | f 16 20 15 59 | f 15 20 19 60 | f 17 18 21 61 | f 21 18 22 62 | f 19 23 18 63 | f 18 23 22 64 | f 19 20 23 65 | f 23 20 24 66 | f 21 22 1 67 | f 22 2 1 68 | f 23 3 22 69 | f 22 3 2 70 | f 23 24 3 71 | f 3 24 4 72 | f 4 24 25 73 | f 4 25 8 74 | f 25 24 16 75 | f 16 24 20 76 | f 25 16 8 77 | f 8 16 12 78 | f 1 26 21 79 | f 1 5 26 80 | f 21 26 17 81 | f 17 26 13 82 | f 26 5 13 83 | f 13 5 9 84 | f 27 28 29 85 | f 30 31 32 86 | f 33 35 34 -------------------------------------------------------------------------------- /3d-viewing-pinhole-camera/pinhole.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // This program implements a physical pinhole camera model similar to the model 3 | // used in popular 3D packages such as Maya. 4 | //[/header] 5 | //[compile] 6 | // Download the pinhole.cpp and geometry.h files to the same folder. 7 | // Open a shell/terminal, and run the following command where the files are saved: 8 | // 9 | // c++ pinhole.cpp -o pinhole -std=c++11 10 | // 11 | // Run with: ./pinhole. Open the file ./pinhole.svg in any Internet browser to see 12 | // the result. 13 | //[/compile] 14 | //[ignore] 15 | // Copyright (C) 2012 www.scratchapixel.com 16 | // 17 | // This program is free software: you can redistribute it and/or modify 18 | // it under the terms of the GNU General Public License as published by 19 | // the Free Software Foundation, either version 3 of the License, or 20 | // (at your option) any later version. 21 | // 22 | // This program is distributed in the hope that it will be useful, 23 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 | // GNU General Public License for more details. 26 | // 27 | // You should have received a copy of the GNU General Public License 28 | // along with this program. If not, see . 29 | //[/ignore] 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include "geometry.h" 36 | 37 | #include 38 | #define M_PI 3.1415926 39 | //[comment] 40 | // List of vertices making up the object 41 | //[/comment] 42 | const Vec3f verts[146] = { 43 | { -2.5703, 0.78053, -2.4e-05}, { -0.89264, 0.022582, 0.018577}, 44 | { 1.6878, -0.017131, 0.022032}, { 3.4659, 0.025667, 0.018577}, 45 | { -2.5703, 0.78969, -0.001202}, { -0.89264, 0.25121, 0.93573}, 46 | { 1.6878, 0.25121, 1.1097}, { 3.5031, 0.25293, 0.93573}, 47 | { -2.5703, 1.0558, -0.001347}, { -0.89264, 1.0558, 1.0487}, 48 | { 1.6878, 1.0558, 1.2437}, { 3.6342, 1.0527, 1.0487}, 49 | { -2.5703, 1.0558, 0}, { -0.89264, 1.0558, 0}, 50 | { 1.6878, 1.0558, 0}, { 3.6342, 1.0527, 0}, 51 | { -2.5703, 1.0558, 0.001347}, { -0.89264, 1.0558, -1.0487}, 52 | { 1.6878, 1.0558, -1.2437}, { 3.6342, 1.0527, -1.0487}, 53 | { -2.5703, 0.78969, 0.001202}, { -0.89264, 0.25121, -0.93573}, 54 | { 1.6878, 0.25121, -1.1097}, { 3.5031, 0.25293, -0.93573}, 55 | { 3.5031, 0.25293, 0}, { -2.5703, 0.78969, 0}, 56 | { 1.1091, 1.2179, 0}, { 1.145, 6.617, 0}, 57 | { 4.0878, 1.2383, 0}, { -2.5693, 1.1771, -0.081683}, 58 | { 0.98353, 6.4948, -0.081683}, { -0.72112, 1.1364, -0.081683}, 59 | { 0.9297, 6.454, 0}, { -0.7929, 1.279, 0}, 60 | { 0.91176, 1.2994, 0} 61 | }; 62 | 63 | const uint32_t numTris = 51; 64 | 65 | //[comment] 66 | // Triangle index array. A triangle has 3 vertices. Each successive group of 3 67 | // integers in this array represent the positions of the vertices in the vertex 68 | // array making up one triangle of that object. For example, the first 3 integers 69 | // from this array, 8/7/9 represent the positions of the vertices making up the 70 | // the first triangle. You can access these vertices with the following code: 71 | // 72 | // verts[8]; /* first vertex */ 73 | // 74 | // verts[7]; /* second vertex */ 75 | // 76 | // verts[9]; /* third vertex */ 77 | // 78 | // 6/5/5 are the positions of the vertices in the vertex array making up the second 79 | // triangle, and so on. 80 | // To find the indices of the n-th triangle, use the following code: 81 | // 82 | // tris[n * 3]; /* index of the first vertex in the verts array */ 83 | // 84 | // tris[n * 3 + 1]; /* index of the second vertexin the verts array */ 85 | // 86 | // tris[n * 3 + 2]; /* index of the third vertex in the verts array */ 87 | //[/comment] 88 | const uint32_t tris[numTris * 3] = { 89 | 4, 0, 5, 0, 1, 5, 1, 2, 5, 5, 2, 6, 3, 7, 2, 90 | 2, 7, 6, 5, 9, 4, 4, 9, 8, 5, 6, 9, 9, 6, 10, 91 | 7, 11, 6, 6, 11, 10, 9, 13, 8, 8, 13, 12, 10, 14, 9, 92 | 9, 14, 13, 10, 11, 14, 14, 11, 15, 17, 16, 13, 12, 13, 16, 93 | 13, 14, 17, 17, 14, 18, 15, 19, 14, 14, 19, 18, 16, 17, 20, 94 | 20, 17, 21, 18, 22, 17, 17, 22, 21, 18, 19, 22, 22, 19, 23, 95 | 20, 21, 0, 21, 1, 0, 22, 2, 21, 21, 2, 1, 22, 23, 2, 96 | 2, 23, 3, 3, 23, 24, 3, 24, 7, 24, 23, 15, 15, 23, 19, 97 | 24, 15, 7, 7, 15, 11, 0, 25, 20, 0, 4, 25, 20, 25, 16, 98 | 16, 25, 12, 25, 4, 12, 12, 4, 8, 26, 27, 28, 29, 30, 31, 99 | 32, 34, 33 100 | }; 101 | 102 | //[comment] 103 | // Compute the 2D pixel coordinates of a point defined in world space. This function 104 | // requires the point original world coordinates of course, the world-to-camera 105 | // matrix (which you can get from computing the inverse of the camera-to-world matrix, 106 | // the matrix transforming the camera), the canvas dimension and the image width and 107 | // height in pixels. 108 | // 109 | // The canvas coordinates (bottom, left, top, right) are also passed to the function 110 | // so we can test the point raster coordinates against the canvas coordinates. If the 111 | // point in raster space lies within the canvas boundaries, the function returns true, 112 | // false otherwise. 113 | //[/comment] 114 | bool computePixelCoordinates( 115 | const Vec3f &pWorld, 116 | const Matrix44f &worldToCamera, 117 | const float &b, 118 | const float &l, 119 | const float &t, 120 | const float &r, 121 | const float &near, 122 | const uint32_t &imageWidth, 123 | const uint32_t &imageHeight, 124 | Vec2i &pRaster) 125 | { 126 | Vec3f pCamera; 127 | worldToCamera.multVecMatrix(pWorld, pCamera); 128 | Vec2f pScreen; 129 | pScreen.x = pCamera.x / -pCamera.z * near; 130 | pScreen.y = pCamera.y / -pCamera.z * near; 131 | 132 | Vec2f pNDC; 133 | pNDC.x = (pScreen.x + r) / (2 * r); 134 | pNDC.y = (pScreen.y + t) / (2 * t); 135 | pRaster.x = (int)(pNDC.x * imageWidth); 136 | pRaster.y = (int)((1 - pNDC.y) * imageHeight); 137 | 138 | bool visible = true; 139 | if (pScreen.x < l || pScreen.x > r || pScreen.y < b || pScreen.y > t) 140 | visible = false; 141 | 142 | return visible; 143 | } 144 | //[comment] 145 | // Settings of the physical camera model: 146 | // 147 | // - focal length in millimetre 148 | // 149 | // - film dimensions (width and height in inches) 150 | // 151 | // Other settings: 152 | // 153 | // - clipping planes (the canvas is positionned at the near clipping plane) 154 | // 155 | // - image dimensions in pixels 156 | // 157 | // - fit film mode (overscan or fit) 158 | //[/comment] 159 | float focalLength = 35; // in mm 160 | // 35mm Full Aperture in inches 161 | float filmApertureWidth = 0.825; 162 | float filmApertureHeight = 0.446; 163 | static const float inchToMm = 25.4; 164 | float nearClippingPlane = 0.1; 165 | float farClipingPlane = 1000; 166 | // image resolution in pixels 167 | uint32_t imageWidth = 512; 168 | uint32_t imageHeight = 512; 169 | 170 | enum FitResolutionGate { kFill = 0, kOverscan }; 171 | FitResolutionGate fitFilm = kOverscan; 172 | 173 | int main(int argc, char **argv) 174 | { 175 | //[comment] 176 | // First compute the canvas coordinates. The canvas is positionned by choice, 177 | // at the near clipping plane. By changing the near clipping plane value, you 178 | // will change the position of the canvas, but this won't change the output of the 179 | // program. 180 | //[/comment] 181 | float filmAspectRatio = filmApertureWidth / filmApertureHeight; 182 | float deviceAspectRatio = imageWidth / (float)imageHeight; 183 | 184 | float top = ((filmApertureHeight * inchToMm / 2) / focalLength) * nearClippingPlane; 185 | float right = ((filmApertureWidth * inchToMm / 2) / focalLength) * nearClippingPlane; 186 | 187 | float xscale = 1; 188 | float yscale = 1; 189 | 190 | switch (fitFilm) { 191 | default: 192 | case kFill: 193 | if (filmAspectRatio > deviceAspectRatio) { 194 | xscale = deviceAspectRatio / filmAspectRatio; 195 | } 196 | else { 197 | yscale = filmAspectRatio / deviceAspectRatio; 198 | } 199 | break; 200 | case kOverscan: 201 | if (filmAspectRatio > deviceAspectRatio) { 202 | yscale = filmAspectRatio / deviceAspectRatio; 203 | } 204 | else { 205 | xscale = deviceAspectRatio / filmAspectRatio; 206 | } 207 | break; 208 | } 209 | 210 | right *= xscale; 211 | top *= yscale; 212 | 213 | float bottom = -top; 214 | float left = -right; 215 | 216 | printf("Screen window coordinates: %f %f %f %f\n", bottom, left, top, right); 217 | printf("Film Aspect Ratio: %f\nDevice Aspect Ratio: %f\n", filmAspectRatio, deviceAspectRatio); 218 | printf("Angle of view: %f (deg)\n", 2 * atan((filmApertureWidth * inchToMm / 2) / focalLength) * 180 / M_PI); 219 | 220 | //[comment] 221 | // Project the triangles vertices onto the plane. If at least one of the vertices lies outside 222 | // the canvas boundaries (if the function computePixelCoordinates returns false), the triangle 223 | // is drawn in red and black otherwise. The result is store in a SVG file. 224 | //[/comment] 225 | std::ofstream ofs; 226 | ofs.open("./pinhole.svg"); 227 | ofs << "" << std::endl; 228 | Matrix44f cameraToWorld(-0.95424, 0, 0.299041, 0, 0.0861242, 0.95763, 0.274823, 0, -0.28637, 0.288002, -0.913809, 0, -3.734612, 7.610426, -14.152769, 1); 229 | Matrix44f worldToCamera = cameraToWorld.inverse(); 230 | float canvasWidth = 2, canvasHeight = 2; 231 | for (uint32_t i = 0; i < numTris; ++i) { 232 | const Vec3f &v0World = verts[tris[i * 3]]; 233 | const Vec3f &v1World = verts[tris[i * 3 + 1]]; 234 | const Vec3f &v2World = verts[tris[i * 3 + 2]]; 235 | Vec2i v0Raster, v1Raster, v2Raster; 236 | 237 | bool visible = true; 238 | visible &= computePixelCoordinates(v0World, worldToCamera, bottom, left, top, right, nearClippingPlane, imageWidth, imageHeight, v0Raster); 239 | visible &= computePixelCoordinates(v1World, worldToCamera, bottom, left, top, right, nearClippingPlane, imageWidth, imageHeight, v1Raster); 240 | visible &= computePixelCoordinates(v2World, worldToCamera, bottom, left, top, right, nearClippingPlane, imageWidth, imageHeight, v2Raster); 241 | 242 | int val = visible ? 0 : 255; 243 | ofs << "\n"; 244 | ofs << "\n"; 245 | ofs << "\n"; 246 | } 247 | ofs << "\n"; 248 | ofs.close(); 249 | 250 | return 0; 251 | } 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Official Scratchapixel website's code repo. 2 | 3 | See [www.scratchapixel.com](www.scratchapixel.com) 4 | -------------------------------------------------------------------------------- /build-raytracer-core/area-light-impl.ilk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/build-raytracer-core/area-light-impl.ilk -------------------------------------------------------------------------------- /build-raytracer-core/intro-lighting-kraemer-remapping-quad-tri.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/build-raytracer-core/intro-lighting-kraemer-remapping-quad-tri.gif -------------------------------------------------------------------------------- /build-raytracer-core/intro-lighting-quadtree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/build-raytracer-core/intro-lighting-quadtree.gif -------------------------------------------------------------------------------- /build-raytracer-core/math.h: -------------------------------------------------------------------------------- 1 | #ifndef _MATH_H_ 2 | #define _MATH_H_ 3 | 4 | template 5 | class Vec2 { 6 | public: 7 | using type = T; 8 | Vec2() noexcept : x(0), y(0) {} 9 | Vec2(T xx) : x(xx), y(xx) {} 10 | Vec2(T xx, T yy) : x(xx), y(yy) {} 11 | T x, y; 12 | }; 13 | 14 | template 15 | class Vec3 { 16 | public: 17 | using type = T; 18 | Vec3() noexcept : x(0), y(0), z(0) {} 19 | Vec3(T xx) : x(xx), y(xx), z(xx) {} 20 | Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} 21 | constexpr unsigned int Dimensions() const noexcept { 22 | return 3; 23 | } 24 | constexpr T& operator[](int i) noexcept { 25 | return (&x)[i]; 26 | } 27 | constexpr const T& operator[](int i) const noexcept { 28 | return (&x)[i]; 29 | } 30 | // Return a reference to the instance it modifies 31 | const Vec3& Normalize() noexcept { 32 | T len = std::sqrt(x * x + y * y + z * z); 33 | if (len != T(0)) [[likely]] { 34 | x /= len; 35 | y /= len; 36 | z /= len; 37 | } 38 | return *this; 39 | } 40 | constexpr Vec3 Cross(const Vec3& v) const noexcept { 41 | return {y * v.z - z * v.y, 42 | z * v.x - x * v.z, 43 | x * v.y - y * v.x}; 44 | } 45 | constexpr T Dot(const Vec3& v) const noexcept { 46 | return x * v.x + y * v.y + z * v.z; 47 | } 48 | constexpr Vec3 operator-(const Vec3& v) const noexcept { 49 | return {x - v.x, y - v.y, z - v.z}; 50 | } 51 | constexpr Vec3 operator+(const Vec3& v) const noexcept { 52 | return {x + v.x, y + v.y, z + v.z}; 53 | } 54 | Vec3& operator+=(const Vec3& v) noexcept { 55 | x += v.x; 56 | y += v.y; 57 | z += v.z; 58 | return *this; 59 | } 60 | template 61 | Vec3& operator/=(const S a) noexcept { 62 | x /= a; 63 | y /= a; 64 | z /= a; 65 | return *this; 66 | } 67 | constexpr Vec3 operator*(T a) const noexcept { 68 | return {x * a, y * a, z * a}; 69 | } 70 | constexpr Vec3 operator/(T a) const noexcept { 71 | return {x / a, y / a, z / a}; 72 | } 73 | friend constexpr Vec3 operator*(T a, const Vec3& v) noexcept{ 74 | return {a * v.x, a * v.y, a * v.z}; 75 | } 76 | // @todo not safe 77 | friend constexpr Vec3 operator/(T a, const Vec3& v) noexcept{ 78 | return {a / v.x, a / v.y, a / v.z}; 79 | } 80 | constexpr T Max() const noexcept { 81 | return std::max(std::max(x, y), z); 82 | } 83 | constexpr T Length() const noexcept { 84 | return std::sqrt(x * x + y * y + z * z); 85 | } 86 | constexpr bool operator==(const Vec3& v) const noexcept { 87 | return x == v.x && y == v.y && z == v.z; 88 | } 89 | friend std::ostream& operator<<(std::ostream& os, const Vec3& v) { 90 | return os << v.x << " " << v.y << " " << v.z; 91 | } 92 | T x, y, z; 93 | }; 94 | 95 | template 96 | class Box { 97 | public: 98 | V min_, max_; 99 | void MakeEmpty() noexcept { 100 | min_ = std::numeric_limits::max(); 101 | max_ = std::numeric_limits::lowest(); 102 | } 103 | void ExtendBy(const V& point) noexcept { 104 | for (unsigned int i = 0; i < min_.Dimensions(); ++i) { 105 | if (point[i] < min_[i]) min_[i] = point[i]; 106 | if (point[i] > max_[i]) max_[i] = point[i]; 107 | } 108 | } 109 | void ExtendBy(const Box& box) noexcept { 110 | for (unsigned int i = 0; i < min_.Dimensions(); ++i) { 111 | if (box.min_[i] < min_[i]) min_[i] = box.min_[i]; 112 | if (box.max_[i] > max_[i]) max_[i] = box.max_[i]; 113 | } 114 | } 115 | }; 116 | 117 | using Box3f = Box>; 118 | 119 | template 120 | class Matrix44 { 121 | public: 122 | constexpr Matrix44() noexcept { 123 | x[0][0] = 1; 124 | x[0][1] = 0; 125 | x[0][2] = 0; 126 | x[0][3] = 0; 127 | x[1][0] = 0; 128 | x[1][1] = 1; 129 | x[1][2] = 0; 130 | x[1][3] = 0; 131 | x[2][0] = 0; 132 | x[2][1] = 0; 133 | x[2][2] = 1; 134 | x[2][3] = 0; 135 | x[3][0] = 0; 136 | x[3][1] = 0; 137 | x[3][2] = 0; 138 | x[3][3] = 1; 139 | } 140 | constexpr Matrix44( 141 | T a, T b, T c, T d, 142 | T e, T f, T g, T h, 143 | T i, T j, T k, T l, 144 | T m, T n, T o, T p) noexcept { 145 | x[0][0] = a; x[0][1] = b; x[0][2] = c; x[0][3] = d; 146 | x[1][0] = e; x[1][1] = f; x[1][2] = g; x[1][3] = h; 147 | x[2][0] = i; x[2][1] = j; x[2][2] = k; x[2][3] = l; 148 | x[3][0] = m; x[3][1] = n; x[3][2] = o; x[3][3] = p; 149 | } 150 | constexpr Matrix44(const T* m) { 151 | std::memcpy(&x[0], m, sizeof(T) * 16); 152 | } 153 | constexpr bool operator==(const Matrix44& rhs) const noexcept { 154 | return x[0][0] == rhs.x[0][0] && x[0][1] == rhs.x[0][1] && 155 | x[0][2] == rhs.x[0][2] && x[0][3] == rhs.x[0][3] && 156 | x[1][0] == rhs.x[1][0] && x[1][1] == rhs.x[1][1] && 157 | x[1][2] == rhs.x[1][2] && x[1][3] == rhs.x[1][3] && 158 | x[2][0] == rhs.x[2][0] && x[2][1] == rhs.x[2][1] && 159 | x[2][2] == rhs.x[2][2] && x[2][3] == rhs.x[2][3] && 160 | x[3][0] == rhs.x[3][0] && x[3][1] == rhs.x[3][1] && 161 | x[3][2] == rhs.x[3][2] && x[3][3] == rhs.x[3][3]; 162 | } 163 | template 164 | void MultVecMatrix(const Vec3& src, Vec3& dst) const noexcept { 165 | S a, b, c, w; 166 | 167 | a = src.x * x[0][0] + src.y * x[1][0] + src.z * x[2][0] + x[3][0]; 168 | b = src.x * x[0][1] + src.y * x[1][1] + src.z * x[2][1] + x[3][1]; 169 | c = src.x * x[0][2] + src.y * x[1][2] + src.z * x[2][2] + x[3][2]; 170 | w = src.x * x[0][3] + src.y * x[1][3] + src.z * x[2][3] + x[3][3]; 171 | 172 | dst.x = a / w; 173 | dst.y = b / w; 174 | dst.z = c / w; 175 | } 176 | template 177 | void MultDirMatrix(const Vec3& src, Vec3& dst) const noexcept { 178 | S a, b, c; 179 | 180 | a = src.x * x[0][0] + src.y * x[1][0] + src.z * x[2][0]; 181 | b = src.x * x[0][1] + src.y * x[1][1] + src.z * x[2][1]; 182 | c = src.x * x[0][2] + src.y * x[1][2] + src.z * x[2][2]; 183 | 184 | dst.x = a; 185 | dst.y = b; 186 | dst.z = c; 187 | } 188 | T* operator[] (int i) noexcept{ 189 | return x[i]; 190 | } 191 | const T* operator[] (int i) const noexcept { 192 | return x[i]; 193 | } 194 | public: 195 | T x[4][4]; 196 | }; 197 | 198 | #endif -------------------------------------------------------------------------------- /build-raytracer-core/mc-integration.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | template 7 | class Vec3 { 8 | public: 9 | Vec3() : x(0), y(0), z(0) {} 10 | Vec3(T xx) : x(xx), y(xx), z(xx) {} 11 | Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} 12 | T Dot(const Vec3& v) const noexcept { 13 | return x * v.x + y * v.y + z * v.z; 14 | } 15 | Vec3 operator*(const Vec3& v) const noexcept { 16 | return {x * v.x, y * v.y, z * v.z}; 17 | } 18 | Vec3 operator*(T r) const noexcept { 19 | return {x * r, y * r, z * r}; 20 | } 21 | template 22 | Vec3& operator/=(S r) noexcept { 23 | x /= r; 24 | y /= r; 25 | z /= r; 26 | return *this; 27 | } 28 | template 29 | Vec3 operator/(S r) const noexcept { 30 | return {x / r, y / r, z / r}; 31 | } 32 | Vec3 operator-(const Vec3& v) const noexcept { 33 | return {x - v.x, y - v.y, z - v.z}; 34 | } 35 | T Length() const noexcept { 36 | return std::sqrt(x * x + y * y + z * z); 37 | } 38 | Vec3& Normalize() noexcept { 39 | T len = Length(); 40 | if (len != 0) [[likely]] 41 | x /= len, y /= len, z /= len; 42 | return *this; 43 | } 44 | Vec3& operator+=(const Vec3& v) noexcept { 45 | x += v.x, y += v.y, z += v.z; 46 | return *this; 47 | } 48 | friend std::ostream& operator<<(std::ostream& os, const Vec3& v) { 49 | return os << v.x << " " << v.y << " " << v.z; 50 | } 51 | T x, y, z; 52 | }; 53 | 54 | template 55 | class Vec2 { 56 | public: 57 | Vec2() : x(0), y(0) {} 58 | T x, y; 59 | }; 60 | 61 | Vec3 Le(Vec3 x) { 62 | return (x.x < -0.4) ? 1 : 0.001; 63 | } 64 | 65 | int num_samples = 16; 66 | 67 | void example1() { 68 | Vec3 sum = 0; 69 | std::random_device rd; 70 | std::mt19937 gen(rd()); 71 | std::uniform_real_distribution dist(-0.5,0.5); 72 | Vec3 x(0,0,5); 73 | Vec3 Nf(0,0,-1); 74 | Vec3 Nl(0,0,1); 75 | for (int n = 0; n < num_samples; ++n) { 76 | Vec3 sample = {dist(gen), dist(gen), 0}; 77 | Vec3 d = sample - x; 78 | double r = d.Length(); 79 | d.Normalize(); 80 | //std::cerr << "n: " << n << ", sample: " << sample << ", Le(sample): " << Le(sample) << ", Nf.Dot(d): " << Nf.Dot(d) << std::endl; 81 | std::cerr << "emit -o \"part2\" -pos " << sample << ";\n"; 82 | sum += Le(sample) * Nf.Dot(d); 83 | } 84 | sum /= num_samples; 85 | std::cerr << "Example 1: " << sum << std::endl; 86 | } 87 | 88 | void example2() { 89 | Vec3 sum = 0; 90 | std::random_device rd; 91 | std::mt19937 gen(rd()); 92 | std::uniform_real_distribution dist(0, 1); 93 | Vec3 x(0,0,5); 94 | Vec3 Nf(0,0,-1); 95 | Vec3 Nl(0,0,1); 96 | for (int n = 0; n < num_samples; ++n) { 97 | double rand = dist(gen); 98 | Vec3 sample; 99 | double pdf; 100 | if (dist(gen) <= 0.8) { 101 | std::uniform_real_distribution dist_sample(-0.5, -0.4); 102 | sample = {dist_sample(gen), (2 * dist(gen) - 1) * 0.5, 0}; 103 | pdf = 0.8 / 0.1; // probability / segment length 104 | } 105 | else { 106 | std::uniform_real_distribution dist_sample(-0.4, 0.5); 107 | sample = {dist_sample(gen), (2 * dist(gen) - 1) * 0.5, 0}; 108 | pdf = 0.2 / 0.9; // probability / segment length 109 | } 110 | Vec3 d = sample - x; 111 | double r = d.Length(); 112 | d.Normalize(); 113 | //std::cerr << "n: " << n << ", sample: " << sample << ", Le(sample): " << Le(sample) << ", Nf.Dot(d): " << Nf.Dot(d) << std::endl; 114 | //std::cerr << "emit -o \"part2\" -pos " << sample << ";\n"; 115 | sum += Le(sample) * Nf.Dot(d) / pdf; 116 | } 117 | sum /= num_samples; 118 | std::cerr << "Example 2: " << sum << std::endl; 119 | } 120 | 121 | int main() { 122 | example1(); 123 | example2(); 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /build-raytracer-core/pointlight.cc: -------------------------------------------------------------------------------- 1 | // (c) www.scratchapixel.com - 2024. 2 | // Distributed under the terms of the CC BY-NC-ND 4.0 License. 3 | // https://creativecommons.org/licenses/by-nc-nd/4.0/ 4 | // clang++ -Wall -Wextra -std=c++23 -o light.exe light.cc -O3 5 | 6 | #define _USE_MATH_DEFINES 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | template 17 | class Vec3 { 18 | public: 19 | Vec3() : x(0), y(0), z(0) {} 20 | Vec3(T xx) : x(xx), y(xx), z(xx) {} 21 | Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} 22 | Vec3 operator+(const Vec3& v) const noexcept { 23 | return {x + v.x, y + v.y, z + v.z}; 24 | } 25 | Vec3 operator-(const Vec3& v) const noexcept { 26 | return {x - v.x, y - v.y, z - v.z}; 27 | } 28 | Vec3 operator*(const T& real) const noexcept { 29 | return {x * real, y * real, z * real}; 30 | } 31 | friend Vec3 operator*(const T& real, const Vec3& v) noexcept { 32 | return {real * v.x, real * v.y, real * v.z}; 33 | } 34 | Vec3 operator/(const T& real) const noexcept { 35 | return {x / real, y / real, z / real}; 36 | } 37 | Vec3 operator-() const noexcept { 38 | return {-x, -y, -z}; 39 | } 40 | T Length() const { 41 | return std::sqrtf(x * x + y * y + z * z); 42 | } 43 | Vec3& Normalize() noexcept { 44 | T len = Length(); 45 | if (len != 0) [[likely]] { 46 | x /= len; 47 | y /= len; 48 | z /= len; 49 | } 50 | return *this; 51 | } 52 | T Dot(const Vec3& v) const noexcept { 53 | return x * v.x + y * v.y + z * v.z; 54 | } 55 | Vec3 Cross(const Vec3& v) const noexcept { 56 | return {y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x}; 57 | } 58 | friend std::ostream& operator<<(std::ostream& os, const Vec3& v) { 59 | return os << v.x << " " << v.y << " " << v.z; 60 | } 61 | T x, y, z; 62 | }; 63 | 64 | struct Hit { 65 | float u; 66 | float v; 67 | int id0{-1}; 68 | int id1{-1}; 69 | float t{std::numeric_limits::max()}; 70 | operator bool() const { return id0 != -1; } 71 | }; 72 | 73 | struct DifferentialGeometry : Hit { 74 | Vec3 P; 75 | Vec3 Ng; 76 | Vec3 Ns; 77 | }; 78 | 79 | struct Ray { 80 | Vec3 orig; 81 | Vec3 dir; 82 | float near{0.1}; 83 | float far{std::numeric_limits::max()}; 84 | }; 85 | 86 | class TriangleMesh { 87 | public: 88 | struct Triangle { 89 | uint32_t v0, v1, v2; 90 | }; 91 | 92 | void PostIntersect(const Ray& ray, DifferentialGeometry& dg) { 93 | const Triangle& tri = triangles_[dg.id1]; 94 | Vec3 p0 = position_[tri.v0]; 95 | Vec3 p1 = position_[tri.v1]; 96 | Vec3 p2 = position_[tri.v2]; 97 | 98 | float u = dg.u, v = dg.v, w = 1.f - u - v, t = dg.t; 99 | 100 | const Vec3 dPdu = p1 - p0, dPdv = p2 - p0; 101 | dg.P = ray.orig + t * ray.dir; 102 | dg.Ng = dPdv.Cross(dPdu).Normalize(); 103 | 104 | if (normals_.size()) { 105 | const Vec3 n0 = normals_[tri.v0], n1 = normals_[tri.v1], n2 = normals_[tri.v2]; 106 | Vec3 Ns = w * n0 + u * n1 + v * n2; 107 | float len2 = Ns.Dot(Ns); 108 | Ns = len2 > 0 ? Ns / std::sqrt(len2) : dg.Ng; 109 | if (Ns.Dot(dg.Ng) < 0) Ns = -Ns; 110 | dg.Ns = Ns; 111 | } 112 | else 113 | dg.Ns = dg.Ng; 114 | } 115 | 116 | std::vector> position_; 117 | std::vector> normals_; 118 | std::vector triangles_; 119 | }; 120 | 121 | class Sphere : public TriangleMesh { 122 | public: 123 | Sphere() : center_(Vec3(0,-2,-22)), radius_(2) { 124 | Triangulate(); 125 | } 126 | private: 127 | Vec3 SphericalToCartesian(float theta, float phi) { 128 | return Vec3( 129 | std::sin(theta) * std::cos(phi), 130 | std::cos(theta), 131 | std::sin(theta) * std::sin(phi)); 132 | } 133 | void Triangulate() { 134 | for (uint32_t theta = 0; theta <= num_theta_; ++theta) { 135 | for (uint32_t phi = 0; phi < num_phi_; ++phi) { 136 | Vec3 p = SphericalToCartesian(theta * M_PI / num_theta_, phi * 2 * M_PI / num_phi_); 137 | normals_.push_back(p); 138 | position_.push_back(center_ + p * radius_); 139 | } 140 | if (theta == 0) continue; 141 | for (uint32_t phi = 1; phi <= num_phi_; ++phi) { 142 | uint32_t p00 = (theta - 1) * num_phi_ + phi - 1; 143 | uint32_t p01 = (theta - 1) * num_phi_ + phi % num_phi_; 144 | uint32_t p10 = theta * num_phi_ + phi - 1; 145 | uint32_t p11 = theta * num_phi_ + phi % num_phi_; 146 | if (theta > 1) triangles_.push_back({p10, p01, p00}); 147 | if (theta < num_theta_) triangles_.push_back({p11, p01, p10}); 148 | } 149 | } 150 | } 151 | public: 152 | uint32_t num_theta_{16}; 153 | uint32_t num_phi_{16}; 154 | Vec3 center_; 155 | float radius_; 156 | }; 157 | 158 | class Light { 159 | public: 160 | Vec3 Sample(const DifferentialGeometry& dg, Vec3& wi, float& pdf, float &t_max) const { 161 | Vec3 d = pos_ - dg.P; 162 | float distance = d.Length(); 163 | wi = d / distance; 164 | pdf = distance * distance; 165 | t_max = distance; 166 | return color_; 167 | } 168 | Vec3 pos_{0,8,-22}; 169 | Vec3 color_{1,1,1}; 170 | }; 171 | 172 | /** 173 | * Extracts the sign bit of a float, returning -0.0 for negative and 0.0 for 174 | * positive or zero. Uses SIMD operations for efficiency. _mm_set_ss sets 175 | * float x in a 128-bit vector, while _mm_set1_epi32(0x80000000) creates a 176 | * mask to isolate the sign bit. _mm_and_ps applies the mask, and _mm_cvtss_f32 177 | * converts the result back to a float. 178 | */ 179 | __forceinline float signmsk(const float x) { 180 | return _mm_cvtss_f32(_mm_and_ps(_mm_set_ss(x),_mm_castsi128_ps(_mm_set1_epi32(0x80000000)))); 181 | } 182 | 183 | 184 | /** 185 | * xorf performs a bitwise XOR on float x and y, returning a float. 186 | * - If x and y are both positive or both negative: No sign change in x. 187 | * - If x and y have different signs: The sign of x is "inverted". 188 | * This operation can flip the sign of x or leave it unchanged, 189 | * depending on y's sign. 190 | */ 191 | __forceinline float xorf(const float x, const float y) { 192 | return _mm_cvtss_f32(_mm_xor_ps(_mm_set_ss(x),_mm_set_ss(y))); 193 | } 194 | 195 | void Intersect(const Ray& ray, 196 | Hit& hit, 197 | int obj_id, 198 | int tri_id, 199 | const TriangleMesh::Triangle& tri, 200 | const Vec3* verts) { 201 | const Vec3 p0 = verts[tri.v0]; 202 | const Vec3 p1 = verts[tri.v1]; 203 | const Vec3 p2 = verts[tri.v2]; 204 | const Vec3 e1 = p0 - p1; 205 | const Vec3 e2 = p2 - p0; 206 | const Vec3 Ng = e1.Cross(e2); 207 | 208 | const Vec3 C = p0 - ray.orig; 209 | const Vec3 R = ray.dir.Cross(C); 210 | const float det = Ng.Dot(ray.dir); 211 | const float abs_det = std::abs(det); 212 | const float sng_det = signmsk(det); 213 | if (det == 0) [[unlikely]] return; 214 | 215 | const float U = xorf(R.Dot(e2), sng_det); 216 | if (U < 0) [[likely]] return; 217 | 218 | const float V = xorf(R.Dot(e1), sng_det); 219 | if (V < 0) [[likely]] return; 220 | 221 | const float W = abs_det - U - V; 222 | if (W < 0) [[likely]] return; 223 | 224 | const float T = xorf(Ng.Dot(C), sng_det); 225 | if (T < abs_det * ray.near || abs_det * hit.t < T) [[unlikely]] return; 226 | 227 | hit.u = U / abs_det; 228 | hit.v = V / abs_det; 229 | hit.t = T / abs_det; 230 | hit.id0 = obj_id; 231 | hit.id1 = tri_id; 232 | } 233 | 234 | bool Occluded(const Ray& ray, 235 | const TriangleMesh::Triangle& tri, 236 | const Vec3* verts) { 237 | const Vec3 p0 = verts[tri.v0]; 238 | const Vec3 p1 = verts[tri.v1]; 239 | const Vec3 p2 = verts[tri.v2]; 240 | const Vec3 e1 = p0 - p1; 241 | const Vec3 e2 = p2 - p0; 242 | const Vec3 Ng = e1.Cross(e2); 243 | 244 | const Vec3 C = p0 - ray.orig; 245 | const Vec3 R = ray.dir.Cross(C); 246 | const float det = Ng.Dot(ray.dir); 247 | const float abs_det = abs(det); 248 | const float sgn_det = signmsk(det); 249 | if (det == 0.f) [[unlikely]] return false; 250 | 251 | const float U = xorf(R.Dot(e2),sgn_det); 252 | if (U < 0.f) [[likely]] return false; 253 | 254 | const float V = xorf(R.Dot(e1), sgn_det); 255 | if (V < 0.f) [[likely]] return false; 256 | 257 | const float W = abs_det - U - V; 258 | if (W < 0.f) [[likely]] return false; 259 | 260 | const float T = xorf(Ng.Dot(C), sgn_det); 261 | if (T < abs_det * ray.near || abs_det * ray.far < T) [[unlikely]] return false; 262 | 263 | return true; 264 | } 265 | 266 | bool Occluded(const Ray& ray, const std::vector>& prims) { 267 | for (int i = 0; i < (int)prims.size(); ++i) { 268 | const std::vector>& pos = prims[i]->position_; 269 | const std::vector& tris = prims[i]->triangles_; 270 | for (size_t j = 0; j < tris.size(); ++j) { 271 | if (Occluded(ray, tris[j], pos.data())) 272 | return true; 273 | } 274 | } 275 | return false; 276 | } 277 | 278 | constexpr uint32_t width = 960; 279 | constexpr uint32_t height = 540; 280 | constexpr float angle_of_view = 60.f; 281 | 282 | int main() { 283 | std::vector> prims; 284 | prims.push_back(std::make_unique()); 285 | Light light; 286 | 287 | std::vector> verts = { 288 | {-5,-4,-17}, {5,-4,-17}, {5,-4,-27}, {-5,-4,-27}, 289 | {5,-4,-27}, {5,6,-27}, {-5,6,-27}, {-5,-4,-27}}; 290 | std::vector> nors = {{0,1,0},{0,0,1}}; 291 | 292 | for (uint32_t i = 0; i < 2; ++i) { 293 | TriangleMesh* mesh = new TriangleMesh; 294 | for (uint32_t j = 0; j < 4; ++j) { 295 | mesh->position_.push_back(verts[i * 4 + j]); 296 | mesh->normals_.push_back(nors[i]); 297 | } 298 | mesh->triangles_.push_back({2,1,0}); 299 | mesh->triangles_.push_back({3,2,0}); 300 | 301 | prims.push_back(std::unique_ptr(mesh)); 302 | } 303 | 304 | float scale = std::tan(angle_of_view * 0.5 * M_PI / 180.f); 305 | float aspect_ratio = width / static_cast(height); 306 | std::unique_ptr buf = std::make_unique(width * height * 3); 307 | uint8_t* pbuf = buf.get(); 308 | std::memset(pbuf, 0x0, width * height * 3); 309 | 310 | for (uint32_t y = 0; y < height; ++y) { 311 | for (uint32_t x = 0; x < width; ++x, pbuf += 3) { 312 | float px = (2.f * (x + 0.5) / static_cast(width) - 1.f) * scale; 313 | float py = (1.f - 2.0f * (y + 0.5) / static_cast(height)) * scale / aspect_ratio; 314 | Vec3 dir(px, py, -1); 315 | dir.Normalize(); 316 | Ray ray = {Vec3(0), dir}; 317 | DifferentialGeometry dg; 318 | for (int i = 0; i < (int)prims.size(); ++i) { 319 | const std::vector>& pos = prims[i]->position_; 320 | const std::vector& tris = prims[i]->triangles_; 321 | for (size_t j = 0; j < tris.size(); ++j) { 322 | Intersect(ray, dg, i, j, tris[j], pos.data()); 323 | } 324 | } 325 | if (dg) { 326 | prims[dg.id0]->PostIntersect(ray, dg); 327 | Vec3 wi; 328 | float t_max, pdf; 329 | Vec3 light_L = light.Sample(dg, wi, pdf, t_max); 330 | bool in_shadow = Occluded({dg.P, wi, 0.01f, t_max - 0.01f}, prims); 331 | if (in_shadow) 332 | continue; 333 | Vec3 L = std::pow(2.f, 7) * light_L * std::max(0.f, dg.Ns.Dot(wi)) / (M_PI * pdf); 334 | pbuf[0] = static_cast(std::min(1.f, L.x) * 255); 335 | pbuf[1] = static_cast(std::min(1.f, L.y) * 255); 336 | pbuf[2] = static_cast(std::min(1.f, L.z) * 255); 337 | } 338 | } 339 | } 340 | 341 | std::ofstream ofs("./test.ppm", std::ios::binary); 342 | ofs << "P6\n" << width << " " << height << "\n255\n"; 343 | ofs.write(reinterpret_cast(buf.get()), width * height * 3); 344 | ofs.close(); 345 | 346 | return 0; 347 | }; -------------------------------------------------------------------------------- /build-raytracer-core/quad.cc: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | constexpr uint32_t width = 512, height = 512; 9 | uint32_t radius = 128; 10 | int32_t center[2] = {width/2, height/2}; 11 | uint32_t max_depth = 4; 12 | uint8_t quad_buf[width * height]; 13 | uint32_t total_num_illum_pixels = 0; 14 | 15 | bool ContainsWhitePixelsOnly(const uint8_t* buf, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { 16 | for (uint32_t j = y; j < y + h; ++j) { 17 | for (uint32_t i = x; i < x + w; ++i) 18 | if (buf[j * width + i] == 0) 19 | return false; 20 | } 21 | return true; 22 | } 23 | 24 | void DrawQuads(uint32_t x, uint32_t y, uint32_t w, uint32_t h) { 25 | for (uint32_t i = x; i < x + w; ++i) { 26 | quad_buf[y * width + i] = 128; 27 | quad_buf[std::min(height - 1, y + h) * width + i] = 128; 28 | } 29 | for (uint32_t j = y + 1; j < y + h; ++j) { 30 | quad_buf[j * width + x] = 128; 31 | quad_buf[j * width + std::min(width - 1, x + w)] = 128; 32 | } 33 | } 34 | 35 | void BuildQuadTree(const uint8_t* buf, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t depth) { 36 | std::cerr << "Depth " << depth << " " << x << " " << y << " " << w << " " << h << std::endl; 37 | if (depth == 0) { 38 | DrawQuads(x, y, w, h); 39 | return; 40 | } 41 | if (ContainsWhitePixelsOnly(buf, x, y, w, h)) { 42 | DrawQuads(x, y, w, h); 43 | total_num_illum_pixels += w * h; 44 | return; 45 | } 46 | uint32_t mid_x = w / 2, mid_y = h / 2; 47 | //DrawQuads(x, y, w, h); 48 | //if (depth == 4) return; 49 | BuildQuadTree(buf, x, y, mid_x, mid_y, depth - 1); 50 | BuildQuadTree(buf, x, y + mid_y, mid_x, h - mid_y, depth - 1); 51 | BuildQuadTree(buf, x + mid_x, y, w - mid_x, mid_y, depth - 1); 52 | BuildQuadTree(buf, x + mid_x, y + mid_y, w - mid_x, h - mid_y, depth - 1); 53 | } 54 | 55 | int main() { 56 | std::unique_ptr buf = std::make_unique(width * height); 57 | std::memset(buf.get(), 0xFF, width * height); 58 | std::memset(quad_buf, 0x0, width * height); 59 | for (int32_t j = 0; j < height; ++j) { 60 | for (int32_t i = 0; i < width; ++i) { 61 | if ((i - center[0]) * (i - center[0]) + (j - center[1]) * (j - center[1]) <= radius * radius) 62 | buf[j * width + i] = 0; 63 | } 64 | } 65 | std::cerr << "All good...\n"; 66 | BuildQuadTree(buf.get(), 0, 0, width, height, max_depth); 67 | std::cerr << "Writing quad\n"; 68 | std::ofstream ofs("./quad.ppm", std::ios::binary); 69 | ofs << "P5\n" << width << " " << height << "\n255\n"; 70 | ofs.write((char*)quad_buf, width * height); 71 | ofs.close(); 72 | std::cerr << "Result: " << ((100.f * total_num_illum_pixels) / (width * height)) << std::endl; 73 | std::cerr << 100 * (1 - M_PI * radius * radius / (float)(width * height)) << std::endl; 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /build-raytracer-core/quad.ilk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/build-raytracer-core/quad.ilk -------------------------------------------------------------------------------- /build-raytracer-core/random_dir.cc: -------------------------------------------------------------------------------- 1 | 2 | #define _USE_MATH_DEFINES 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | int main() { 10 | std::random_device rd; // Obtain a random number from hardware 11 | std::mt19937 gen(rd()); // Seed the generator 12 | std::uniform_real_distribution<> distr(0.0, 1.0); // Define the range 13 | for (size_t i = 0; i < 16; ++i) { 14 | float theta = M_PI * 0.5 * distr(gen); 15 | float phi = 2 * M_PI * distr(gen); 16 | std::cerr << "curve -d 1 -p 0 0 0 -p " << 17 | std::cos(phi) * sin(theta) << " " << 18 | std::sin(theta) * std::sin(phi) << " " << 19 | cos(theta) << " -k 0 -k 1;" << std::endl; 20 | } 21 | return 0; 22 | }; 23 | -------------------------------------------------------------------------------- /build-raytracer-core/triangle-sampling.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | template 6 | class Vec2 { 7 | public: 8 | Vec2() : x(T(0)), y(T(0)) {} 9 | Vec2(T xx) : x(xx), y(xx) {} 10 | Vec2(T xx, T yy) : x(xx), y(yy) {} 11 | Vec2 operator+(const Vec2 v) const noexcept { 12 | return {x + v.x, y + v.y}; 13 | } 14 | friend Vec2 operator*(T a, const Vec2& v) { 15 | return {a * v.x, a * v.y}; 16 | } 17 | friend std::ostream& operator<<(std::ostream& os, const Vec2& v) { 18 | return os << v.x << " " << v.y; 19 | } 20 | T x, y; 21 | }; 22 | 23 | using Vec2f = Vec2; 24 | 25 | constexpr uint32_t nsamples = 512; 26 | 27 | Vec2f NaiveMethod(const Vec2f& v1, const Vec2f& v2, float u, float v) { 28 | return u * v1 + (1.f - u) * v * v2; 29 | } 30 | 31 | Vec2f BarycentricCoordinatesMethodBad(const Vec2f& v1, const Vec2f& v2, float u, float v, float w) { 32 | float sum = u + v + w; // we need to normalize the barycentric coordinates 33 | return /* (u / sum) * v0 + */ (v / sum) * v1 + (w / sum) * v2; 34 | } 35 | 36 | Vec2f BarycentricCoordinatesMethodGood(const Vec2f& v1, const Vec2f& v2, float r1, float r2) { 37 | float q = std::sqrt(r1); 38 | // v0 = (0,0) so skip calculation 39 | return /* (1 - q) * v0 + */ q * (1 - r2) * v1 + q * r2 * v2; 40 | } 41 | 42 | Vec2f KraemerMethod(const Vec2f& v1, const Vec2f& v2, float r1, float r2) { 43 | #if 1 44 | if (r1 > r2) std::swap(r1, r2); 45 | float u = r1, v = r2 - r1, w = 1 - r2; 46 | #else 47 | float q = std::abs(r1 - r2); 48 | float u = q, v = 0.5f * (r1 + r2 - q), w = 1 - 0.5 * (q + r1 + r2); 49 | #endif 50 | return /* u * v0 + */ v * v1 + w * v2; 51 | } 52 | 53 | template 54 | void SampleTriangle(SamplingMethod sample) { 55 | std::random_device rd; 56 | std::mt19937 eng(rd()); 57 | std::uniform_real_distribution distr(0.f, 1.f); 58 | for (size_t i = 0; i < nsamples; ++i) { 59 | // We assume v0 = (0,0) 60 | Vec2f x = sample({2,0}, {1,2}, distr(eng), distr(eng)); 61 | std::cout << "emit -object \"particleShape1\" -pos " << x << " 0;\n"; 62 | } 63 | } 64 | 65 | int main() { 66 | SampleTriangle(BarycentricCoordinatesMethodGood); 67 | }; -------------------------------------------------------------------------------- /computing-pixel-coordinates-of-3d-point/xtree.obj: -------------------------------------------------------------------------------- 1 | # http://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point 2 | 3 | v 0.000000 39.033714 0.000000 4 | v 0.762116 36.843475 0.000000 5 | v 3.000000 36.604431 0.000000 6 | v 1.000000 35.604431 0.000000 7 | v 2.016184 33.382484 0.000000 8 | v 0.000000 34.541019 0.000000 9 | v -2.016184 33.382484 0.000000 10 | v -1.000000 35.604431 0.000000 11 | v -3.000000 36.604431 0.000000 12 | v -0.762116 36.843475 0.000000 13 | v -0.040181 34.310349 -0.000000 14 | v 3.277792 30.463991 0.000000 15 | v -0.040181 30.463991 -0.000000 16 | v -0.028749 30.463991 0.000000 17 | v 3.277792 30.463991 0.000000 18 | v 1.272227 29.197426 0.000000 19 | v 1.272227 29.197426 0.000000 20 | v -0.028703 29.197426 0.000000 21 | v 1.272227 29.197426 0.000000 22 | v 5.277792 25.397711 0.000000 23 | v -0.028650 25.397711 0.000000 24 | v 1.272227 29.197426 0.000000 25 | v 5.277792 25.397711 0.000000 26 | v 3.332222 24.098793 0.000000 27 | v -0.028683 24.098793 0.000000 28 | v 7.195669 20.299080 0.000000 29 | v -0.028610 20.299080 0.000000 30 | v 5.277791 19.064854 0.000000 31 | v -0.028663 18.984453 0.000000 32 | v 9.277791 15.265141 0.000000 33 | v -0.028571 15.184738 0.000000 34 | v 9.277792 15.265141 0.000000 35 | v 7.377179 13.999161 0.000000 36 | v -0.028625 13.900555 0.000000 37 | v 9.277791 15.265141 0.000000 38 | v 12.277791 8.932283 0.000000 39 | v -0.028771 8.974176 0.000000 40 | v 12.277791 8.932283 0.000000 41 | v 10.277796 7.665712 0.000000 42 | v -0.028592 7.655210 0.000000 43 | v 15.277796 2.599426 0.000000 44 | v -0.028775 2.607727 0.000000 45 | v 15.277796 2.599426 0.000000 46 | v 13.277795 1.332857 0.000000 47 | v -0.028727 1.261691 0.000000 48 | v 18.277796 -3.733428 0.000000 49 | v 18.277796 -3.733428 0.000000 50 | v 2.272227 -1.200286 0.000000 51 | v -0.028727 -1.309806 0.000000 52 | v 4.272227 -5.000000 0.000000 53 | v 4.272227 -5.000000 0.000000 54 | v -0.028727 -5.000000 0.000000 55 | v -3.358153 30.463991 -0.000000 56 | v -3.358153 30.463991 -0.000000 57 | v -1.352588 29.197426 -0.000000 58 | v -1.352588 29.197426 -0.000000 59 | v -1.352588 29.197426 -0.000000 60 | v -5.358153 25.397711 -0.000000 61 | v -1.352588 29.197426 -0.000000 62 | v -5.358153 25.397711 -0.000000 63 | v -3.412583 24.098793 -0.000000 64 | v -7.276031 20.299080 -0.000000 65 | v -5.358152 19.064854 -0.000000 66 | v -9.358152 15.265141 -0.000000 67 | v -9.358153 15.265141 -0.000000 68 | v -7.457540 13.999161 -0.000000 69 | v -9.358152 15.265141 -0.000000 70 | v -12.358152 8.932283 -0.000000 71 | v -12.358152 8.932283 -0.000000 72 | v -10.358157 7.665712 -0.000000 73 | v -15.358157 2.599426 -0.000000 74 | v -15.358157 2.599426 -0.000000 75 | v -13.358156 1.332857 -0.000000 76 | v -18.358156 -3.733428 -0.000000 77 | v -18.358156 -3.733428 -0.000000 78 | v -2.352588 -1.200286 -0.000000 79 | v -4.352589 -5.000000 -0.000000 80 | v -4.352589 -5.000000 -0.000000 81 | v -0.000000 34.310349 0.040181 82 | v 0.000000 30.463991 -3.277792 83 | v -0.000000 30.463991 0.040181 84 | v -0.000000 30.463991 0.028749 85 | v 0.000000 30.463991 -3.277792 86 | v 0.000000 29.197426 -1.272227 87 | v 0.000000 29.197426 -1.272227 88 | v -0.000000 29.197426 0.028703 89 | v 0.000000 29.197426 -1.272227 90 | v 0.000000 25.397711 -5.277792 91 | v -0.000000 25.397711 0.028650 92 | v 0.000000 29.197426 -1.272227 93 | v 0.000000 25.397711 -5.277792 94 | v 0.000000 24.098793 -3.332222 95 | v -0.000000 24.098793 0.028683 96 | v 0.000000 20.299080 -7.195669 97 | v -0.000000 20.299080 0.028610 98 | v 0.000000 19.064854 -5.277791 99 | v -0.000000 18.984453 0.028663 100 | v 0.000000 15.265141 -9.277791 101 | v -0.000000 15.184738 0.028571 102 | v 0.000000 15.265141 -9.277792 103 | v 0.000000 13.999161 -7.377179 104 | v -0.000000 13.900555 0.028625 105 | v 0.000000 15.265141 -9.277791 106 | v 0.000000 8.932283 -12.277791 107 | v -0.000000 8.974176 0.028771 108 | v 0.000000 8.932283 -12.277791 109 | v 0.000000 7.665712 -10.277796 110 | v -0.000000 7.655210 0.028592 111 | v 0.000000 2.599426 -15.277796 112 | v -0.000000 2.607727 0.028775 113 | v 0.000000 2.599426 -15.277796 114 | v 0.000000 1.332857 -13.277795 115 | v -0.000000 1.261691 0.028727 116 | v 0.000000 -3.733428 -18.277796 117 | v 0.000000 -3.733428 -18.277796 118 | v 0.000000 -1.200286 -2.272227 119 | v -0.000000 -1.309806 0.028727 120 | v 0.000000 -5.000000 -4.272227 121 | v 0.000000 -5.000000 -4.272227 122 | v -0.000000 -5.000000 0.028727 123 | v -0.000000 30.463991 3.358153 124 | v -0.000000 30.463991 3.358153 125 | v -0.000000 29.197426 1.352588 126 | v -0.000000 29.197426 1.352588 127 | v -0.000000 29.197426 1.352588 128 | v -0.000000 25.397711 5.358153 129 | v -0.000000 29.197426 1.352588 130 | v -0.000000 25.397711 5.358153 131 | v -0.000000 24.098793 3.412583 132 | v -0.000000 20.299080 7.276031 133 | v -0.000000 19.064854 5.358152 134 | v -0.000000 15.265141 9.358152 135 | v -0.000000 15.265141 9.358153 136 | v -0.000000 13.999161 7.457540 137 | v -0.000000 15.265141 9.358152 138 | v -0.000000 8.932283 12.358152 139 | v -0.000000 8.932283 12.358152 140 | v -0.000000 7.665712 10.358157 141 | v -0.000000 2.599426 15.358157 142 | v -0.000000 2.599426 15.358157 143 | v -0.000000 1.332857 13.358156 144 | v -0.000000 -3.733428 18.358156 145 | v -0.000000 -3.733428 18.358156 146 | v -0.000000 -1.200286 2.352588 147 | v -0.000000 -5.000000 4.352589 148 | v -0.000000 -5.000000 4.352589 149 | f 9 8 10 150 | f 7 6 8 151 | f 5 4 6 152 | f 3 2 4 153 | f 1 10 2 154 | f 6 4 8 155 | f 8 4 10 156 | f 10 4 2 157 | f 11 13 12 158 | f 14 16 15 159 | f 16 14 17 160 | f 14 18 17 161 | f 19 21 20 162 | f 18 21 22 163 | f 21 24 23 164 | f 21 25 24 165 | f 24 27 26 166 | f 25 27 24 167 | f 27 28 26 168 | f 27 29 28 169 | f 28 31 30 170 | f 29 31 28 171 | f 31 33 32 172 | f 31 34 33 173 | f 28 31 35 174 | f 33 37 36 175 | f 34 37 33 176 | f 37 39 38 177 | f 37 40 39 178 | f 39 42 41 179 | f 40 42 39 180 | f 42 44 43 181 | f 42 45 44 182 | f 45 46 44 183 | f 45 48 47 184 | f 45 49 48 185 | f 49 50 48 186 | f 49 52 51 187 | f 11 53 13 188 | f 14 54 55 189 | f 56 18 55 190 | f 14 55 18 191 | f 57 58 21 192 | f 18 59 21 193 | f 21 60 61 194 | f 21 61 25 195 | f 61 62 27 196 | f 25 61 27 197 | f 27 62 63 198 | f 27 63 29 199 | f 63 64 31 200 | f 29 63 31 201 | f 31 65 66 202 | f 31 66 34 203 | f 63 67 31 204 | f 66 68 37 205 | f 34 66 37 206 | f 37 69 70 207 | f 37 70 40 208 | f 70 71 42 209 | f 40 70 42 210 | f 42 72 73 211 | f 42 73 45 212 | f 45 73 74 213 | f 45 75 76 214 | f 45 76 49 215 | f 49 76 77 216 | f 49 78 52 217 | f 79 81 80 218 | f 82 84 83 219 | f 84 82 85 220 | f 82 86 85 221 | f 87 89 88 222 | f 86 89 90 223 | f 89 92 91 224 | f 89 93 92 225 | f 92 95 94 226 | f 93 95 92 227 | f 95 96 94 228 | f 95 97 96 229 | f 96 99 98 230 | f 97 99 96 231 | f 99 101 100 232 | f 99 102 101 233 | f 96 99 103 234 | f 101 105 104 235 | f 102 105 101 236 | f 105 107 106 237 | f 105 108 107 238 | f 107 110 109 239 | f 108 110 107 240 | f 110 112 111 241 | f 110 113 112 242 | f 113 114 112 243 | f 113 116 115 244 | f 113 117 116 245 | f 117 118 116 246 | f 117 120 119 247 | f 79 121 81 248 | f 82 122 123 249 | f 124 86 123 250 | f 82 123 86 251 | f 125 126 89 252 | f 86 127 89 253 | f 89 128 129 254 | f 89 129 93 255 | f 129 130 95 256 | f 93 129 95 257 | f 95 130 131 258 | f 95 131 97 259 | f 131 132 99 260 | f 97 131 99 261 | f 99 133 134 262 | f 99 134 102 263 | f 131 135 99 264 | f 134 136 105 265 | f 102 134 105 266 | f 105 137 138 267 | f 105 138 108 268 | f 138 139 110 269 | f 108 138 110 270 | f 110 140 141 271 | f 110 141 113 272 | f 113 141 142 273 | f 113 143 144 274 | f 113 144 117 275 | f 117 144 145 276 | f 117 146 120 -------------------------------------------------------------------------------- /digital-images/readwrite.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // Digital Images 3 | //[/header] 4 | //[compile] 5 | // Download the readwrite.cpp and xmas.ppm file to a folder. 6 | // Open a shell/terminal, and run the following command where the files is saved: 7 | // 8 | // clang++ -o readwrite readwrite.cpp -std=c++11 -O3 9 | // 10 | // You can use c++ if you don't use clang++ 11 | // 12 | // Run with: ./readwrite. Open the resulting image (ppm) in Photoshop or any program 13 | // reading PPM files. 14 | //[/compile] 15 | //[ignore] 16 | // Copyright (C) 2016 www.scratchapixel.com 17 | // 18 | // This program is free software: you can redistribute it and/or modify 19 | // it under the terms of the GNU General Public License as published by 20 | // the Free Software Foundation, either version 3 of the License, or 21 | // (at your option) any later version. 22 | // 23 | // This program is distributed in the hope that it will be useful, 24 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 | // GNU General Public License for more details. 27 | // 28 | // You should have received a copy of the GNU General Public License 29 | // along with this program. If not, see . 30 | //[/ignore] 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | // [comment] 40 | // The main Image class 41 | // [/comment] 42 | class Image 43 | { 44 | public: 45 | 46 | struct Rgb 47 | { 48 | Rgb() : r(0), g(0), b(0) {} 49 | Rgb(float rr) : r(rr), g(rr), b(rr) {} 50 | Rgb(float rr, float gg, float bb) : r(rr), g(gg), b(bb) {} 51 | float r, g, b; 52 | }; 53 | 54 | Image() : w(0), h(0), pixels(nullptr) {} 55 | Image(const unsigned int &_w, const unsigned int &_h) : 56 | w(_w), h(_h), pixels(nullptr) 57 | { 58 | pixels = new Rgb[w * h]; 59 | for (int i = 0; i < w * h; ++i) pixels[i] = 0; 60 | } 61 | ~Image() { if (pixels != nullptr) delete [] pixels; } 62 | unsigned int w, h; 63 | Rgb *pixels; 64 | }; 65 | 66 | // [comment] 67 | // Save an image to PPM image file 68 | // [/comment] 69 | void savePPM(const Image &img, const char *filename) 70 | { 71 | if (img.w == 0 || img.h == 0) { fprintf(stderr, "Can't save an empty image\n"); return; } 72 | std::ofstream ofs; 73 | try { 74 | ofs.open(filename, std::ios::binary); // need to spec. binary mode for Windows users 75 | if (ofs.fail()) throw("Can't open output file"); 76 | ofs << "P6\n" << img.w << " " << img.h << "\n255\n"; 77 | unsigned char r, g, b; 78 | // loop over each pixel in the image, clamp and convert to byte format 79 | for (int i = 0; i < img.w * img.h; ++i) { 80 | r = static_cast(std::min(1.f, img.pixels[i].r) * 255); 81 | g = static_cast(std::min(1.f, img.pixels[i].g) * 255); 82 | b = static_cast(std::min(1.f, img.pixels[i].b) * 255); 83 | ofs << r << g << b; 84 | } 85 | ofs.close(); 86 | } 87 | catch (const char *err) { 88 | fprintf(stderr, "%s\n", err); 89 | ofs.close(); 90 | } 91 | } 92 | 93 | // [comment] 94 | // Read a PPM image file 95 | // [/comment] 96 | Image readPPM(const char *filename) 97 | { 98 | std::ifstream ifs; 99 | ifs.open(filename, std::ios::binary); // need to spec. binary mode for Windows users 100 | Image img; 101 | try { 102 | if (ifs.fail()) { throw("Can't open input file"); } 103 | std::string header; 104 | int w, h, b; 105 | ifs >> header; 106 | if (strcmp(header.c_str(), "P6") != 0) throw("Can't read input file"); 107 | ifs >> w >> h >> b; 108 | img.w = w; img.h = h; 109 | img.pixels = new Image::Rgb[w * h]; // this is throw an exception if bad_alloc 110 | ifs.ignore(256, '\n'); // skip empty lines in necessary until we get to the binary data 111 | unsigned char pix[3]; 112 | // read each pixel one by one and convert bytes to floats 113 | for (int i = 0; i < w * h; ++i) { 114 | ifs.read(reinterpret_cast(pix), 3); 115 | img.pixels[i].r = pix[0] / 255.f; 116 | img.pixels[i].g = pix[1] / 255.f; 117 | img.pixels[i].b = pix[2] / 255.f; 118 | } 119 | ifs.close(); 120 | } 121 | catch (const char *err) { 122 | fprintf(stderr, "%s\n", err); 123 | ifs.close(); 124 | } 125 | 126 | return img; 127 | } 128 | 129 | // [comment] 130 | // Read/Write an image stored in the PPM format 131 | // [/comment] 132 | int main(int argc, char **argv) 133 | { 134 | Image I = readPPM("./xmas.ppm"); 135 | savePPM(I, "./out.ppm"); 136 | 137 | return 0; 138 | } -------------------------------------------------------------------------------- /digital-images/xmas.ppm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/digital-images/xmas.ppm -------------------------------------------------------------------------------- /global-illumination-path-tracing/objects.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/global-illumination-path-tracing/objects.zip -------------------------------------------------------------------------------- /interpolation/.gitignore: -------------------------------------------------------------------------------- 1 | *exe 2 | interpolation 3 | *ppm 4 | *png 5 | *gif 6 | *tx 7 | -------------------------------------------------------------------------------- /interpolation/interpolation.cpp: -------------------------------------------------------------------------------- 1 | // (c) www.scratchapixel.com - 2024. 2 | // Distributed under the terms of the CC BY-NC-ND 4.0 License. 3 | // https://creativecommons.org/licenses/by-nc-nd/4.0/ 4 | // Contributors: 5 | // - Scratchpixel 6 | // - Kristopolous / Chris Mckenzie 7 | // clang++ -std=c++23 -O3 -o interpolation.exe interpolation.cpp 8 | 9 | #define _CRT_SECURE_NO_WARNINGS 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | template 19 | class Color3 { 20 | public: 21 | Color3() : r(0), g(0), b(0) {} 22 | Color3(T rr) : r(rr), g(rr), b(rr) {} 23 | Color3(T rr, T gg, T bb) : r(rr), g(gg), b(bb) {} 24 | Color3 operator * (const T& v) const { 25 | return Color3(r * v, g * v, b * v); 26 | } 27 | Color3 operator + (const Color3& c) const { 28 | return Color3(r + c.r, g + c.g, b + c.b); 29 | } 30 | friend Color3 operator * (const float& f, const Color3& c) { 31 | return Color3(c.r * f, c.g * f, c.b * f); 32 | } 33 | friend std::ostream& operator << (std::ostream& os, const Color3& c) { 34 | os << c.r << " " << c.g << " " << c.b; 35 | return os; 36 | } 37 | float r, g, b; 38 | }; 39 | 40 | using Color3f = Color3; 41 | 42 | void saveToPPM(const char* fn, const Color3f* c, const int& width, const int& height) { 43 | std::ofstream ofs; 44 | // flags are necessary if your compile on Windows 45 | ofs.open(fn, std::ios::out | std::ios::binary); 46 | if (ofs.fail()) { 47 | fprintf(stderr, "ERROR: can't save image to file %s\n", fn); 48 | } 49 | else { 50 | ofs << "P6\n" << width << " " << height << "\n255\n"; 51 | const Color3f* pc = c; 52 | for (int j = 0; j < height; ++j) { 53 | for (int i = 0; i < width; ++i) { 54 | char r = static_cast(std::min(255.f, 255 * pc->r + 0.5f)); 55 | char g = static_cast(std::min(255.f, 255 * pc->g + 0.5f)); 56 | char b = static_cast(std::min(255.f, 255 * pc->b + 0.5f)); 57 | ofs << r << g << b; 58 | pc++; 59 | } 60 | } 61 | } 62 | ofs.close(); 63 | } 64 | 65 | template 66 | T bilinear( 67 | const float& tx, 68 | const float& ty, 69 | const T& c00, 70 | const T& c10, 71 | const T& c01, 72 | const T& c11) { 73 | #if 1 74 | T a = c00 * (1.f - tx) + c10 * tx; 75 | T b = c01 * (1.f - tx) + c11 * tx; 76 | return a * (1.f - ty) + b * ty; 77 | #else 78 | return (1 - tx) * (1 - ty) * c00 + 79 | tx * (1 - ty) * c10 + 80 | (1.f - tx) * ty * c01 + 81 | tx * ty * c11; 82 | #endif 83 | } 84 | 85 | std::random_device rd; // Obtain a random number from hardware 86 | std::mt19937 gen(rd()); // Seed the generator 87 | std::uniform_real_distribution<> distr(0.0, 1.0); // Define the range 88 | 89 | void TestBilinearInterpolation() { 90 | 91 | // testing bilinear interpolation 92 | int imageWidth = 512; 93 | int gridSizeX = 9, gridSizeY = 9; 94 | Color3f* grid2d = new Color3f[(gridSizeX + 1) * (gridSizeY + 1)]; // lattices 95 | // fill grid with random colors 96 | Color3f c[4] = { Color3f(1,0,0), Color3f(0,1,0), Color3f(0,0,1), Color3f(1,1,0) }; 97 | for (int j = 0, k = 0; j <= gridSizeY; ++j) { 98 | for (int i = 0; i <= gridSizeX; ++i, ++k) { 99 | grid2d[j * (gridSizeX + 1) + i] = Color3f(distr(gen), distr(gen), distr(gen)); 100 | printf("%d %d %f\n", i, j, grid2d[j * (gridSizeX + 1) + i].r); 101 | } 102 | } 103 | // now compute our final image using bilinear interpolation 104 | Color3f* imageData = new Color3f[imageWidth * imageWidth], * pixel = imageData; 105 | for (int j = 0; j < imageWidth; ++j) { 106 | for (int i = 0; i < imageWidth; ++i) { 107 | // convert i,j to grid coordinates 108 | float gx = i / float(imageWidth) * gridSizeX; // be careful to interpolate boundaries 109 | float gy = j / float(imageWidth) * gridSizeY; // be careful to interpolate boundaries 110 | int gxi = int(gx); 111 | int gyi = int(gy); 112 | const Color3f& c00 = grid2d[gyi * (gridSizeX + 1) + gxi]; 113 | const Color3f& c10 = grid2d[gyi * (gridSizeX + 1) + (gxi + 1)]; 114 | const Color3f& c01 = grid2d[(gyi + 1) * (gridSizeX + 1) + gxi]; 115 | const Color3f& c11 = grid2d[(gyi + 1) * (gridSizeX + 1) + (gxi + 1)]; 116 | *(pixel++) = bilinear(gx - gxi, gy - gyi, c00, c10, c01, c11); 117 | } 118 | } 119 | saveToPPM("./bilinear.ppm", imageData, imageWidth, imageWidth); 120 | // uncomnent this code if you want to see what the input colors look like 121 | pixel = imageData; 122 | int cellsize = imageWidth / (gridSizeX); 123 | fprintf(stderr, "%d\n", cellsize); 124 | for (int j = 0; j < imageWidth; ++j) { 125 | for (int i = 0; i < imageWidth; ++i) { 126 | float gx = (i + cellsize / 2) / float(imageWidth); 127 | float gy = (j + cellsize / 2) / float(imageWidth); 128 | int gxi = static_cast(gx * gridSizeX); 129 | int gyi = static_cast(gy * gridSizeY); 130 | *pixel = grid2d[gyi * (gridSizeX + 1) + gxi]; 131 | int mx = (i + cellsize / 2) % cellsize; 132 | int my = (j + cellsize / 2) % cellsize; 133 | int ma = cellsize / 2 + 2, mb = cellsize / 2 - 2; 134 | if (mx < ma && mx > mb && my < ma && my > mb) 135 | *pixel = Color3f(0, 0, 0); 136 | pixel++; 137 | } 138 | } 139 | saveToPPM("./inputbilinear1.ppm", imageData, imageWidth, imageWidth); 140 | delete[] imageData; 141 | } 142 | 143 | #define IX(size, i, j, k) (i * size * size + j * size + k) 144 | 145 | 146 | /** 147 | * Trilinear interpolation example. We take a cube of size "gridsize" and then 148 | * "upscale" it to scale * gridsize. To evaluate the result of a random 149 | * point within the grid, we pick the 8 point's neighbor cells and trilinearly 150 | * interpolate the results. Each of the results get written to a sequentially 151 | * named ppm file. They can be viewed, in an animation that cycles through the 152 | * slices on the command line using a tool like "mpv" like so: 153 | * 154 | * mpv --speed=10 --image-display-duration=0.1 trilinear-slice-*.ppm 155 | * 156 | * *or* if you have imagemagick you can make it into an animation like so: 157 | * 158 | * magick trilinear-slice-*.ppm demo.gif 159 | * 160 | */ 161 | void TestTrilinearInterpolation() { 162 | // 163 | // Note that the size is gridSize ^ 3 and correspondingly, since we "upscale" 164 | // in each dimension, you will have (gridSize * scale) ^ 3 for the output 165 | // image. In this particular example, the initial 3D grid is 8x8x8 and the 166 | // upres grid is 128x128x128. 167 | // 168 | uint32_t grid_size = 8; // number of cells along any of x, y and z-axis 169 | uint32_t scale = 16; 170 | 171 | uint32_t src_num_verts = grid_size + 1; 172 | uint32_t target_num_verts = grid_size * scale + 1; 173 | uint32_t src_array_size = src_num_verts * src_num_verts * src_num_verts; 174 | uint32_t target_array_size = target_num_verts * target_num_verts * target_num_verts; 175 | 176 | std::unique_ptr scr_grid3d = std::make_unique(src_array_size); 177 | std::unique_ptr target_grid3d = std::make_unique(target_array_size); 178 | std::memset(target_grid3d.get(), 0x0, sizeof(Color3f) * target_array_size); 179 | 180 | for (uint32_t k = 0; k < src_num_verts; ++k) { 181 | for (uint32_t j = 0; j < src_num_verts; ++j) { 182 | for (uint32_t i = 0; i < src_num_verts; ++i) { 183 | scr_grid3d[IX(src_num_verts, i, j, k)] = Color3f(distr(gen), distr(gen), distr(gen)); 184 | } 185 | } 186 | } 187 | 188 | // interpolate grid data, we assume the grid is a unit cube 189 | float gx, gy, gz; 190 | uint32_t gxi0, gyi0, gzi0, gxi1, gyi1, gzi1; 191 | float tx, ty, tz; 192 | 193 | for (uint32_t z = 0; z < target_num_verts; ++z) { 194 | gz = float(z) / scale; 195 | gzi0 = uint32_t(gz); tz = gz - gzi0; 196 | gzi1 = std::min(grid_size, gzi0 + 1); 197 | 198 | for (uint32_t y = 0; y < target_num_verts; ++y) { 199 | gy = float(y) / scale; 200 | gyi0 = uint32_t(gy); ty = gy - gyi0; 201 | gyi1 = std::min(grid_size, gyi0 + 1); 202 | 203 | for (uint32_t x = 0; x < target_num_verts; ++x) { 204 | gx = float(x) / scale; 205 | gxi0 = uint32_t(gx); tx = gx - gxi0; 206 | gxi1 = std::min(grid_size, gxi0 + 1); 207 | 208 | const Color3f& c000 = scr_grid3d[IX(src_num_verts, gxi0, gyi0, gzi0)]; 209 | const Color3f& c001 = scr_grid3d[IX(src_num_verts, gxi0, gyi0, gzi1)]; 210 | const Color3f& c010 = scr_grid3d[IX(src_num_verts, gxi0, gyi1, gzi0)]; 211 | const Color3f& c011 = scr_grid3d[IX(src_num_verts, gxi0, gyi1, gzi1)]; 212 | 213 | const Color3f& c100 = scr_grid3d[IX(src_num_verts, gxi1, gyi0, gzi0)]; 214 | const Color3f& c101 = scr_grid3d[IX(src_num_verts, gxi1, gyi0, gzi1)]; 215 | const Color3f& c110 = scr_grid3d[IX(src_num_verts, gxi1, gyi1, gzi0)]; 216 | const Color3f& c111 = scr_grid3d[IX(src_num_verts, gxi1, gyi1, gzi1)]; 217 | #if 1 218 | // interpolate vertex data in zy plane at x and x+1 219 | Color3f e = bilinear(tz, ty, c000, c001, c010, c011); 220 | Color3f f = bilinear(tz, ty, c100, c101, c110, c111); 221 | 222 | // interpolate along the x-axis 223 | Color3f g = e * (1 - tx) + f * tx; 224 | 225 | target_grid3d[IX(target_num_verts, x, y, z)] = g; 226 | #else 227 | Color3f g = 228 | (1 - tx) * (1 - ty) * (1 - tz) * c000 + 229 | tx * (1 - ty) * (1 - tz) * c100 + 230 | (1 - tx) * ty * (1 - tz) * c010 + 231 | tx * ty * (1 - tz) * c110 + 232 | (1 - tx) * (1 - ty) * tz * c001 + 233 | tx * (1 - ty) * tz * c101 + 234 | (1 - tx) * ty * tz * c011 + 235 | tx * ty * tz * c111; 236 | #endif 237 | } 238 | } 239 | } 240 | char file_name[100] = { 0 }; 241 | for (uint32_t k = 0; k < grid_size * scale; ++k) { 242 | sprintf(file_name, "trilinear-slice-%04d.ppm", k); 243 | const Color3f* slice_data = target_grid3d.get() + k * target_num_verts * target_num_verts; 244 | saveToPPM(file_name, slice_data, target_num_verts, target_num_verts); 245 | } 246 | } 247 | 248 | int main(int argc, char** argv) 249 | { 250 | //TestBilinearInterpolation(); 251 | TestTrilinearInterpolation(); 252 | 253 | return 0; 254 | } 255 | -------------------------------------------------------------------------------- /introduction-rendering/raytracer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/introduction-rendering/raytracer -------------------------------------------------------------------------------- /introduction-rendering/raytracer.cpp: -------------------------------------------------------------------------------- 1 | // [header] 2 | // A very basic raytracer example. 3 | // [/header] 4 | // [compile] 5 | // c++ -o raytracer -O3 -Wall raytracer.cpp 6 | // [/compile] 7 | // [ignore] 8 | // Copyright (C) 2012 www.scratchapixel.com 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // [/ignore] 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #if defined __linux__ || defined __APPLE__ 32 | // "Compiled for Linux 33 | #else 34 | // Windows doesn't define these values by default, Linux does 35 | #define M_PI 3.141592653589793 36 | #define INFINITY 1e8 37 | #endif 38 | 39 | template 40 | class Vec3 41 | { 42 | public: 43 | T x, y, z; 44 | Vec3() : x(T(0)), y(T(0)), z(T(0)) {} 45 | Vec3(T xx) : x(xx), y(xx), z(xx) {} 46 | Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} 47 | Vec3& normalize() 48 | { 49 | T nor2 = length2(); 50 | if (nor2 > 0) { 51 | T invNor = 1 / sqrt(nor2); 52 | x *= invNor, y *= invNor, z *= invNor; 53 | } 54 | return *this; 55 | } 56 | Vec3 operator * (const T &f) const { return Vec3(x * f, y * f, z * f); } 57 | Vec3 operator * (const Vec3 &v) const { return Vec3(x * v.x, y * v.y, z * v.z); } 58 | T dot(const Vec3 &v) const { return x * v.x + y * v.y + z * v.z; } 59 | Vec3 operator - (const Vec3 &v) const { return Vec3(x - v.x, y - v.y, z - v.z); } 60 | Vec3 operator + (const Vec3 &v) const { return Vec3(x + v.x, y + v.y, z + v.z); } 61 | Vec3& operator += (const Vec3 &v) { x += v.x, y += v.y, z += v.z; return *this; } 62 | Vec3& operator *= (const Vec3 &v) { x *= v.x, y *= v.y, z *= v.z; return *this; } 63 | Vec3 operator - () const { return Vec3(-x, -y, -z); } 64 | T length2() const { return x * x + y * y + z * z; } 65 | T length() const { return sqrt(length2()); } 66 | friend std::ostream & operator << (std::ostream &os, const Vec3 &v) 67 | { 68 | os << "[" << v.x << " " << v.y << " " << v.z << "]"; 69 | return os; 70 | } 71 | }; 72 | 73 | typedef Vec3 Vec3f; 74 | 75 | class Sphere 76 | { 77 | public: 78 | Vec3f center; /// position of the sphere 79 | float radius, radius2; /// sphere radius and radius^2 80 | Vec3f surfaceColor, emissionColor; /// surface color and emission (light) 81 | float transparency, reflection; /// surface transparency and reflectivity 82 | Sphere( 83 | const Vec3f &c, 84 | const float &r, 85 | const Vec3f &sc, 86 | const float &refl = 0, 87 | const float &transp = 0, 88 | const Vec3f &ec = 0) : 89 | center(c), radius(r), radius2(r * r), surfaceColor(sc), emissionColor(ec), 90 | transparency(transp), reflection(refl) 91 | { /* empty */ } 92 | //[comment] 93 | // Compute a ray-sphere intersection using the geometric solution 94 | //[/comment] 95 | bool intersect(const Vec3f &rayorig, const Vec3f &raydir, float &t0, float &t1) const 96 | { 97 | Vec3f l = center - rayorig; 98 | float tca = l.dot(raydir); 99 | if (tca < 0) return false; 100 | float d2 = l.dot(l) - tca * tca; 101 | if (d2 > radius2) return false; 102 | float thc = sqrt(radius2 - d2); 103 | t0 = tca - thc; 104 | t1 = tca + thc; 105 | 106 | return true; 107 | } 108 | }; 109 | 110 | //[comment] 111 | // This variable controls the maximum recursion depth 112 | //[/comment] 113 | #define MAX_RAY_DEPTH 5 114 | 115 | float mix(const float &a, const float &b, const float &mix) 116 | { 117 | return b * mix + a * (1 - mix); 118 | } 119 | 120 | //[comment] 121 | // This is the main trace function. It takes a ray as argument (defined by its origin 122 | // and direction). We test if this ray intersects any of the geometry in the scene. 123 | // If the ray intersects an object, we compute the intersection point, the normal 124 | // at the intersection point, and shade this point using this information. 125 | // Shading depends on the surface property (is it transparent, reflective, diffuse). 126 | // The function returns a color for the ray. If the ray intersects an object that 127 | // is the color of the object at the intersection point, otherwise it returns 128 | // the background color. 129 | //[/comment] 130 | Vec3f trace( 131 | const Vec3f &rayorig, 132 | const Vec3f &raydir, 133 | const std::vector &spheres, 134 | const int &depth) 135 | { 136 | //if (raydir.length() != 1) std::cerr << "Error " << raydir << std::endl; 137 | float tnear = INFINITY; 138 | const Sphere* sphere = NULL; 139 | // find intersection of this ray with the sphere in the scene 140 | for (unsigned i = 0; i < spheres.size(); ++i) { 141 | float t0 = INFINITY, t1 = INFINITY; 142 | if (spheres[i].intersect(rayorig, raydir, t0, t1)) { 143 | if (t0 < 0) t0 = t1; 144 | if (t0 < tnear) { 145 | tnear = t0; 146 | sphere = &spheres[i]; 147 | } 148 | } 149 | } 150 | // if there's no intersection return black or background color 151 | if (!sphere) return Vec3f(2); 152 | Vec3f surfaceColor = 0; // color of the ray/surfaceof the object intersected by the ray 153 | Vec3f phit = rayorig + raydir * tnear; // point of intersection 154 | Vec3f nhit = phit - sphere->center; // normal at the intersection point 155 | nhit.normalize(); // normalize normal direction 156 | // If the normal and the view direction are not opposite to each other 157 | // reverse the normal direction. That also means we are inside the sphere so set 158 | // the inside bool to true. Finally reverse the sign of IdotN which we want 159 | // positive. 160 | float bias = 1e-4; // add some bias to the point from which we will be tracing 161 | bool inside = false; 162 | if (raydir.dot(nhit) > 0) nhit = -nhit, inside = true; 163 | if ((sphere->transparency > 0 || sphere->reflection > 0) && depth < MAX_RAY_DEPTH) { 164 | float facingratio = -raydir.dot(nhit); 165 | // change the mix value to tweak the effect 166 | float fresneleffect = mix(pow(1 - facingratio, 3), 1, 0.1); 167 | // compute reflection direction (not need to normalize because all vectors 168 | // are already normalized) 169 | Vec3f refldir = raydir - nhit * 2 * raydir.dot(nhit); 170 | refldir.normalize(); 171 | Vec3f reflection = trace(phit + nhit * bias, refldir, spheres, depth + 1); 172 | Vec3f refraction = 0; 173 | // if the sphere is also transparent compute refraction ray (transmission) 174 | if (sphere->transparency) { 175 | float ior = 1.1, eta = (inside) ? ior : 1 / ior; // are we inside or outside the surface? 176 | float cosi = -nhit.dot(raydir); 177 | float k = 1 - eta * eta * (1 - cosi * cosi); 178 | Vec3f refrdir = raydir * eta + nhit * (eta * cosi - sqrt(k)); 179 | refrdir.normalize(); 180 | refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1); 181 | } 182 | // the result is a mix of reflection and refraction (if the sphere is transparent) 183 | surfaceColor = ( 184 | reflection * fresneleffect + 185 | refraction * (1 - fresneleffect) * sphere->transparency) * sphere->surfaceColor; 186 | } 187 | else { 188 | // it's a diffuse object, no need to raytrace any further 189 | for (unsigned i = 0; i < spheres.size(); ++i) { 190 | if (spheres[i].emissionColor.x > 0) { 191 | // this is a light 192 | Vec3f transmission = 1; 193 | Vec3f lightDirection = spheres[i].center - phit; 194 | lightDirection.normalize(); 195 | for (unsigned j = 0; j < spheres.size(); ++j) { 196 | if (i != j) { 197 | float t0, t1; 198 | if (spheres[j].intersect(phit + nhit * bias, lightDirection, t0, t1)) { 199 | transmission = 0; 200 | break; 201 | } 202 | } 203 | } 204 | surfaceColor += sphere->surfaceColor * transmission * 205 | std::max(float(0), nhit.dot(lightDirection)) * spheres[i].emissionColor; 206 | } 207 | } 208 | } 209 | 210 | return surfaceColor + sphere->emissionColor; 211 | } 212 | 213 | //[comment] 214 | // Main rendering function. We compute a camera ray for each pixel of the image 215 | // trace it and return a color. If the ray hits a sphere, we return the color of the 216 | // sphere at the intersection point, else we return the background color. 217 | //[/comment] 218 | void render(const std::vector &spheres) 219 | { 220 | unsigned width = 640, height = 480; 221 | Vec3f *image = new Vec3f[width * height], *pixel = image; 222 | float invWidth = 1 / float(width), invHeight = 1 / float(height); 223 | float fov = 30, aspectratio = width / float(height); 224 | float angle = tan(M_PI * 0.5 * fov / 180.); 225 | // Trace rays 226 | for (unsigned y = 0; y < height; ++y) { 227 | for (unsigned x = 0; x < width; ++x, ++pixel) { 228 | float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio; 229 | float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle; 230 | Vec3f raydir(xx, yy, -1); 231 | raydir.normalize(); 232 | *pixel = trace(Vec3f(0), raydir, spheres, 0); 233 | } 234 | } 235 | // Save result to a PPM image (keep these flags if you compile under Windows) 236 | std::ofstream ofs("./untitled.ppm", std::ios::out | std::ios::binary); 237 | ofs << "P6\n" << width << " " << height << "\n255\n"; 238 | for (unsigned i = 0; i < width * height; ++i) { 239 | ofs << (unsigned char)(std::min(float(1), image[i].x) * 255) << 240 | (unsigned char)(std::min(float(1), image[i].y) * 255) << 241 | (unsigned char)(std::min(float(1), image[i].z) * 255); 242 | } 243 | ofs.close(); 244 | delete [] image; 245 | } 246 | 247 | //[comment] 248 | // In the main function, we will create the scene which is composed of 5 spheres 249 | // and 1 light (which is also a sphere). Then, once the scene description is complete 250 | // we render that scene, by calling the render() function. 251 | //[/comment] 252 | int main(int argc, char **argv) 253 | { 254 | srand48(13); 255 | std::vector spheres; 256 | // position, radius, surface color, reflectivity, transparency, emission color 257 | spheres.push_back(Sphere(Vec3f( 0.0, -10004, -20), 10000, Vec3f(0.20, 0.20, 0.20), 0, 0.0)); 258 | spheres.push_back(Sphere(Vec3f( 0.0, 0, -20), 4, Vec3f(1.00, 0.32, 0.36), 1, 0.5)); 259 | spheres.push_back(Sphere(Vec3f( 5.0, -1, -15), 2, Vec3f(0.90, 0.76, 0.46), 1, 0.0)); 260 | spheres.push_back(Sphere(Vec3f( 5.0, 0, -25), 3, Vec3f(0.65, 0.77, 0.97), 1, 0.0)); 261 | spheres.push_back(Sphere(Vec3f(-5.5, 0, -15), 3, Vec3f(0.90, 0.90, 0.90), 1, 0.0)); 262 | // light 263 | spheres.push_back(Sphere(Vec3f( 0.0, 20, -30), 3, Vec3f(0.00, 0.00, 0.00), 0, 0.0, Vec3f(3))); 264 | render(spheres); 265 | 266 | return 0; 267 | } -------------------------------------------------------------------------------- /introduction-rendering/untitled.ppm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/introduction-rendering/untitled.ppm -------------------------------------------------------------------------------- /introduction-to-lighting/.gitignore: -------------------------------------------------------------------------------- 1 | test.cc 2 | *.mel 3 | -------------------------------------------------------------------------------- /introduction-to-lighting/math.h: -------------------------------------------------------------------------------- 1 | // (c) www.scratchapixel.com - 2024. 2 | // Distributed under the terms of the CC BY-NC-ND 4.0 License. 3 | // https://creativecommons.org/licenses/by-nc-nd/4.0/ 4 | 5 | #ifndef _MATH_H_ 6 | #define _MATH_H_ 7 | 8 | #include 9 | 10 | template 11 | class Vec2 { 12 | public: 13 | using type = T; 14 | Vec2() noexcept : x(0), y(0) {} 15 | Vec2(T xx) : x(xx), y(xx) {} 16 | Vec2(T xx, T yy) : x(xx), y(yy) {} 17 | T x, y; 18 | }; 19 | 20 | template 21 | class Vec3 { 22 | public: 23 | using type = T; 24 | Vec3() noexcept : x(0), y(0), z(0) {} 25 | Vec3(T xx) : x(xx), y(xx), z(xx) {} 26 | Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} 27 | constexpr unsigned int Dimensions() const noexcept { 28 | return 3; 29 | } 30 | constexpr T& operator[](int i) noexcept { 31 | return (&x)[i]; 32 | } 33 | constexpr const T& operator[](int i) const noexcept { 34 | return (&x)[i]; 35 | } 36 | // Return a reference to the instance it modifies 37 | const Vec3& Normalize() noexcept { 38 | T len = std::sqrt(x * x + y * y + z * z); 39 | if (len != T(0)) [[likely]] { 40 | x /= len; 41 | y /= len; 42 | z /= len; 43 | } 44 | return *this; 45 | } 46 | constexpr Vec3 Cross(const Vec3& v) const noexcept { 47 | return {y * v.z - z * v.y, 48 | z * v.x - x * v.z, 49 | x * v.y - y * v.x}; 50 | } 51 | constexpr T Dot(const Vec3& v) const noexcept { 52 | return x * v.x + y * v.y + z * v.z; 53 | } 54 | constexpr Vec3 operator-(const Vec3& v) const noexcept { 55 | return {x - v.x, y - v.y, z - v.z}; 56 | } 57 | constexpr Vec3 operator+(const Vec3& v) const noexcept { 58 | return {x + v.x, y + v.y, z + v.z}; 59 | } 60 | Vec3& operator+=(const Vec3& v) noexcept { 61 | x += v.x; 62 | y += v.y; 63 | z += v.z; 64 | return *this; 65 | } 66 | template 67 | Vec3& operator/=(const S a) noexcept { 68 | x /= a; 69 | y /= a; 70 | z /= a; 71 | return *this; 72 | } 73 | constexpr Vec3 operator*(T a) const noexcept { 74 | return {x * a, y * a, z * a}; 75 | } 76 | constexpr Vec3 operator/(T a) const noexcept { 77 | return {x / a, y / a, z / a}; 78 | } 79 | friend constexpr Vec3 operator*(T a, const Vec3& v) noexcept{ 80 | return {a * v.x, a * v.y, a * v.z}; 81 | } 82 | // @todo not safe 83 | friend constexpr Vec3 operator/(T a, const Vec3& v) noexcept{ 84 | return {a / v.x, a / v.y, a / v.z}; 85 | } 86 | constexpr T Max() const noexcept { 87 | return std::max(std::max(x, y), z); 88 | } 89 | constexpr T Length() const noexcept { 90 | return std::sqrt(x * x + y * y + z * z); 91 | } 92 | constexpr bool operator==(const Vec3& v) const noexcept { 93 | return x == v.x && y == v.y && z == v.z; 94 | } 95 | friend std::ostream& operator<<(std::ostream& os, const Vec3& v) { 96 | return os << v.x << " " << v.y << " " << v.z; 97 | } 98 | T x, y, z; 99 | }; 100 | 101 | template 102 | class Box { 103 | public: 104 | V min_, max_; 105 | void MakeEmpty() noexcept { 106 | min_ = std::numeric_limits::max(); 107 | max_ = std::numeric_limits::lowest(); 108 | } 109 | void ExtendBy(const V& point) noexcept { 110 | for (unsigned int i = 0; i < min_.Dimensions(); ++i) { 111 | if (point[i] < min_[i]) min_[i] = point[i]; 112 | if (point[i] > max_[i]) max_[i] = point[i]; 113 | } 114 | } 115 | void ExtendBy(const Box& box) noexcept { 116 | for (unsigned int i = 0; i < min_.Dimensions(); ++i) { 117 | if (box.min_[i] < min_[i]) min_[i] = box.min_[i]; 118 | if (box.max_[i] > max_[i]) max_[i] = box.max_[i]; 119 | } 120 | } 121 | }; 122 | 123 | using Box3f = Box>; 124 | 125 | template 126 | class Matrix44 { 127 | public: 128 | static const Matrix44 kIdentity; 129 | constexpr Matrix44() noexcept { 130 | x[0][0] = 1; 131 | x[0][1] = 0; 132 | x[0][2] = 0; 133 | x[0][3] = 0; 134 | x[1][0] = 0; 135 | x[1][1] = 1; 136 | x[1][2] = 0; 137 | x[1][3] = 0; 138 | x[2][0] = 0; 139 | x[2][1] = 0; 140 | x[2][2] = 1; 141 | x[2][3] = 0; 142 | x[3][0] = 0; 143 | x[3][1] = 0; 144 | x[3][2] = 0; 145 | x[3][3] = 1; 146 | } 147 | constexpr Matrix44( 148 | T a, T b, T c, T d, 149 | T e, T f, T g, T h, 150 | T i, T j, T k, T l, 151 | T m, T n, T o, T p) noexcept { 152 | x[0][0] = a; x[0][1] = b; x[0][2] = c; x[0][3] = d; 153 | x[1][0] = e; x[1][1] = f; x[1][2] = g; x[1][3] = h; 154 | x[2][0] = i; x[2][1] = j; x[2][2] = k; x[2][3] = l; 155 | x[3][0] = m; x[3][1] = n; x[3][2] = o; x[3][3] = p; 156 | } 157 | constexpr Matrix44(const T* m) { 158 | std::memcpy(&x[0], m, sizeof(T) * 16); 159 | } 160 | constexpr Matrix44& operator=(const Matrix44& v) noexcept { 161 | x[0][0] = v.x[0][0]; 162 | x[0][1] = v.x[0][1]; 163 | x[0][2] = v.x[0][2]; 164 | x[0][3] = v.x[0][3]; 165 | x[1][0] = v.x[1][0]; 166 | x[1][1] = v.x[1][1]; 167 | x[1][2] = v.x[1][2]; 168 | x[1][3] = v.x[1][3]; 169 | x[2][0] = v.x[2][0]; 170 | x[2][1] = v.x[2][1]; 171 | x[2][2] = v.x[2][2]; 172 | x[2][3] = v.x[2][3]; 173 | x[3][0] = v.x[3][0]; 174 | x[3][1] = v.x[3][1]; 175 | x[3][2] = v.x[3][2]; 176 | x[3][3] = v.x[3][3]; 177 | return *this; 178 | } 179 | constexpr bool operator==(const Matrix44& rhs) const noexcept { 180 | return x[0][0] == rhs.x[0][0] && x[0][1] == rhs.x[0][1] && 181 | x[0][2] == rhs.x[0][2] && x[0][3] == rhs.x[0][3] && 182 | x[1][0] == rhs.x[1][0] && x[1][1] == rhs.x[1][1] && 183 | x[1][2] == rhs.x[1][2] && x[1][3] == rhs.x[1][3] && 184 | x[2][0] == rhs.x[2][0] && x[2][1] == rhs.x[2][1] && 185 | x[2][2] == rhs.x[2][2] && x[2][3] == rhs.x[2][3] && 186 | x[3][0] == rhs.x[3][0] && x[3][1] == rhs.x[3][1] && 187 | x[3][2] == rhs.x[3][2] && x[3][3] == rhs.x[3][3]; 188 | } 189 | template 190 | void MultVecMatrix(const Vec3& src, Vec3& dst) const noexcept { 191 | S a, b, c, w; 192 | 193 | a = src.x * x[0][0] + src.y * x[1][0] + src.z * x[2][0] + x[3][0]; 194 | b = src.x * x[0][1] + src.y * x[1][1] + src.z * x[2][1] + x[3][1]; 195 | c = src.x * x[0][2] + src.y * x[1][2] + src.z * x[2][2] + x[3][2]; 196 | w = src.x * x[0][3] + src.y * x[1][3] + src.z * x[2][3] + x[3][3]; 197 | 198 | dst.x = a / w; 199 | dst.y = b / w; 200 | dst.z = c / w; 201 | } 202 | template 203 | void MultDirMatrix(const Vec3& src, Vec3& dst) const noexcept { 204 | S a, b, c; 205 | 206 | a = src.x * x[0][0] + src.y * x[1][0] + src.z * x[2][0]; 207 | b = src.x * x[0][1] + src.y * x[1][1] + src.z * x[2][1]; 208 | c = src.x * x[0][2] + src.y * x[1][2] + src.z * x[2][2]; 209 | 210 | dst.x = a; 211 | dst.y = b; 212 | dst.z = c; 213 | } 214 | T* operator[] (int i) noexcept{ 215 | return x[i]; 216 | } 217 | const T* operator[] (int i) const noexcept { 218 | return x[i]; 219 | } 220 | constexpr Matrix44 Transposed () const noexcept { 221 | return Matrix44 ( 222 | x[0][0], 223 | x[1][0], 224 | x[2][0], 225 | x[3][0], 226 | x[0][1], 227 | x[1][1], 228 | x[2][1], 229 | x[3][1], 230 | x[0][2], 231 | x[1][2], 232 | x[2][2], 233 | x[3][2], 234 | x[0][3], 235 | x[1][3], 236 | x[2][3], 237 | x[3][3]); 238 | } 239 | constexpr const Matrix44& Invert() noexcept { 240 | *this = Inverse(); 241 | return *this; 242 | } 243 | constexpr Matrix44 Inverse() const noexcept { 244 | if (x[0][3] != 0 || x[1][3] != 0 || x[2][3] != 0 || x[3][3] != 1) 245 | abort(); 246 | // return gjInverse(); 247 | 248 | Matrix44 s ( 249 | x[1][1] * x[2][2] - x[2][1] * x[1][2], 250 | x[2][1] * x[0][2] - x[0][1] * x[2][2], 251 | x[0][1] * x[1][2] - x[1][1] * x[0][2], 252 | 0, 253 | 254 | x[2][0] * x[1][2] - x[1][0] * x[2][2], 255 | x[0][0] * x[2][2] - x[2][0] * x[0][2], 256 | x[1][0] * x[0][2] - x[0][0] * x[1][2], 257 | 0, 258 | 259 | x[1][0] * x[2][1] - x[2][0] * x[1][1], 260 | x[2][0] * x[0][1] - x[0][0] * x[2][1], 261 | x[0][0] * x[1][1] - x[1][0] * x[0][1], 262 | 0, 263 | 264 | 0, 265 | 0, 266 | 0, 267 | 1); 268 | 269 | T r = x[0][0] * s.x[0][0] + x[0][1] * s.x[1][0] + x[0][2] * s.x[2][0]; 270 | 271 | if (std::abs(r) >= 1) { 272 | for (int i = 0; i < 3; ++i) { 273 | for (int j = 0; j < 3; ++j) { 274 | s.x[i][j] /= r; 275 | } 276 | } 277 | } 278 | else { 279 | T mr = std::abs (r) / std::numeric_limits::min (); 280 | 281 | for (int i = 0; i < 3; ++i) { 282 | for (int j = 0; j < 3; ++j) { 283 | if (mr > std::abs (s.x[i][j])) { 284 | s.x[i][j] /= r; 285 | } 286 | else { 287 | return Matrix44 (); 288 | } 289 | } 290 | } 291 | } 292 | 293 | s.x[3][0] = -x[3][0] * s.x[0][0] - x[3][1] * s.x[1][0] - x[3][2] * s.x[2][0]; 294 | s.x[3][1] = -x[3][0] * s.x[0][1] - x[3][1] * s.x[1][1] - x[3][2] * s.x[2][1]; 295 | s.x[3][2] = -x[3][0] * s.x[0][2] - x[3][1] * s.x[1][2] - x[3][2] * s.x[2][2]; 296 | 297 | return s; 298 | } 299 | friend std::ostream& operator<< (std::ostream& s, const Matrix44& m) { 300 | std::ios_base::fmtflags oldFlags = s.flags(); 301 | int width; 302 | 303 | if (s.flags() & std::ios_base::fixed) { 304 | s.setf(std::ios_base::showpoint); 305 | width = static_cast (s.precision ()) + 5; 306 | } 307 | else { 308 | s.setf(std::ios_base::scientific); 309 | s.setf(std::ios_base::showpoint); 310 | width = static_cast (s.precision ()) + 8; 311 | } 312 | 313 | s << "(" << std::setw(width) << m[0][0] << " " << std::setw(width) 314 | << m[0][1] << " " << std::setw(width) << m[0][2] << " " 315 | << std::setw(width) << m[0][3] << "\n" 316 | << 317 | 318 | " " << std::setw(width) << m[1][0] << " " << std::setw(width) 319 | << m[1][1] << " " << std::setw(width) << m[1][2] << " " 320 | << std::setw(width) << m[1][3] << "\n" 321 | << 322 | 323 | " " << std::setw(width) << m[2][0] << " " << std::setw(width) 324 | << m[2][1] << " " << std::setw(width) << m[2][2] << " " 325 | << std::setw(width) << m[2][3] << "\n" 326 | << 327 | 328 | " " << std::setw(width) << m[3][0] << " " << std::setw(width) 329 | << m[3][1] << " " << std::setw(width) << m[3][2] << " " 330 | << std::setw(width) << m[3][3] << ")\n"; 331 | 332 | s.flags(oldFlags); 333 | return s; 334 | } 335 | public: 336 | T x[4][4]; 337 | }; 338 | 339 | template 340 | void ExtractScaling(const Matrix44& mat, Vec3& scale) { 341 | Vec3 row[3]; 342 | 343 | row[0] = Vec3(mat[0][0], mat[0][1], mat[0][2]); 344 | row[1] = Vec3(mat[1][0], mat[1][1], mat[1][2]); 345 | row[2] = Vec3(mat[2][0], mat[2][1], mat[2][2]); 346 | 347 | scale.x = row[0].Length(); 348 | scale.y = row[1].Length(); 349 | scale.z = row[2].Length(); 350 | } 351 | 352 | template 353 | const Matrix44 Matrix44::kIdentity = Matrix44(); 354 | 355 | #endif -------------------------------------------------------------------------------- /introduction-to-lighting/pointlight.cc: -------------------------------------------------------------------------------- 1 | // (c) www.scratchapixel.com - 2024. 2 | // Distributed under the terms of the CC BY-NC-ND 4.0 License. 3 | // https://creativecommons.org/licenses/by-nc-nd/4.0/ 4 | // clang++ -Wall -Wextra -std=c++23 -o light.exe light.cc -O3 5 | 6 | #define _USE_MATH_DEFINES 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | template 17 | class Vec3 { 18 | public: 19 | Vec3() : x(0), y(0), z(0) {} 20 | Vec3(T xx) : x(xx), y(xx), z(xx) {} 21 | Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} 22 | Vec3 operator+(const Vec3& v) const noexcept { 23 | return {x + v.x, y + v.y, z + v.z}; 24 | } 25 | Vec3 operator-(const Vec3& v) const noexcept { 26 | return {x - v.x, y - v.y, z - v.z}; 27 | } 28 | Vec3 operator*(const T& real) const noexcept { 29 | return {x * real, y * real, z * real}; 30 | } 31 | friend Vec3 operator*(const T& real, const Vec3& v) noexcept { 32 | return {real * v.x, real * v.y, real * v.z}; 33 | } 34 | Vec3 operator/(const T& real) const noexcept { 35 | return {x / real, y / real, z / real}; 36 | } 37 | Vec3 operator-() const noexcept { 38 | return {-x, -y, -z}; 39 | } 40 | T Length() const { 41 | return std::sqrtf(x * x + y * y + z * z); 42 | } 43 | Vec3& Normalize() noexcept { 44 | T len = Length(); 45 | if (len != 0) [[likely]] { 46 | x /= len; 47 | y /= len; 48 | z /= len; 49 | } 50 | return *this; 51 | } 52 | T Dot(const Vec3& v) const noexcept { 53 | return x * v.x + y * v.y + z * v.z; 54 | } 55 | Vec3 Cross(const Vec3& v) const noexcept { 56 | return {y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x}; 57 | } 58 | friend std::ostream& operator<<(std::ostream& os, const Vec3& v) { 59 | return os << v.x << " " << v.y << " " << v.z; 60 | } 61 | T x, y, z; 62 | }; 63 | 64 | struct Hit { 65 | float u; 66 | float v; 67 | int id0{-1}; 68 | int id1{-1}; 69 | float t{std::numeric_limits::max()}; 70 | operator bool() const { return id0 != -1; } 71 | }; 72 | 73 | struct DifferentialGeometry : Hit { 74 | Vec3 P; 75 | Vec3 Ng; 76 | Vec3 Ns; 77 | }; 78 | 79 | struct Ray { 80 | Vec3 orig; 81 | Vec3 dir; 82 | float near{0.1}; 83 | float far{std::numeric_limits::max()}; 84 | }; 85 | 86 | class TriangleMesh { 87 | public: 88 | struct Triangle { 89 | uint32_t v0, v1, v2; 90 | }; 91 | 92 | void PostIntersect(const Ray& ray, DifferentialGeometry& dg) { 93 | const Triangle& tri = triangles_[dg.id1]; 94 | Vec3 p0 = position_[tri.v0]; 95 | Vec3 p1 = position_[tri.v1]; 96 | Vec3 p2 = position_[tri.v2]; 97 | 98 | float u = dg.u, v = dg.v, w = 1.f - u - v, t = dg.t; 99 | 100 | const Vec3 dPdu = p1 - p0, dPdv = p2 - p0; 101 | dg.P = ray.orig + t * ray.dir; 102 | dg.Ng = dPdv.Cross(dPdu).Normalize(); 103 | 104 | if (normals_.size()) { 105 | const Vec3 n0 = normals_[tri.v0], n1 = normals_[tri.v1], n2 = normals_[tri.v2]; 106 | Vec3 Ns = w * n0 + u * n1 + v * n2; 107 | float len2 = Ns.Dot(Ns); 108 | Ns = len2 > 0 ? Ns / std::sqrt(len2) : dg.Ng; 109 | if (Ns.Dot(dg.Ng) < 0) Ns = -Ns; 110 | dg.Ns = Ns; 111 | } 112 | else 113 | dg.Ns = dg.Ng; 114 | } 115 | 116 | std::vector> position_; 117 | std::vector> normals_; 118 | std::vector triangles_; 119 | }; 120 | 121 | class Sphere : public TriangleMesh { 122 | public: 123 | Sphere() : center_(Vec3(0,-2,-22)), radius_(2) { 124 | Triangulate(); 125 | } 126 | private: 127 | Vec3 SphericalToCartesian(float theta, float phi) { 128 | return Vec3( 129 | std::sin(theta) * std::cos(phi), 130 | std::cos(theta), 131 | std::sin(theta) * std::sin(phi)); 132 | } 133 | void Triangulate() { 134 | for (uint32_t theta = 0; theta <= num_theta_; ++theta) { 135 | for (uint32_t phi = 0; phi < num_phi_; ++phi) { 136 | Vec3 p = SphericalToCartesian(theta * M_PI / num_theta_, phi * 2 * M_PI / num_phi_); 137 | normals_.push_back(p); 138 | position_.push_back(center_ + p * radius_); 139 | } 140 | if (theta == 0) continue; 141 | for (uint32_t phi = 1; phi <= num_phi_; ++phi) { 142 | uint32_t p00 = (theta - 1) * num_phi_ + phi - 1; 143 | uint32_t p01 = (theta - 1) * num_phi_ + phi % num_phi_; 144 | uint32_t p10 = theta * num_phi_ + phi - 1; 145 | uint32_t p11 = theta * num_phi_ + phi % num_phi_; 146 | if (theta > 1) triangles_.push_back({p10, p01, p00}); 147 | if (theta < num_theta_) triangles_.push_back({p11, p01, p10}); 148 | } 149 | } 150 | } 151 | public: 152 | uint32_t num_theta_{16}; 153 | uint32_t num_phi_{16}; 154 | Vec3 center_; 155 | float radius_; 156 | }; 157 | 158 | class Light { 159 | public: 160 | Vec3 Sample(const DifferentialGeometry& dg, Vec3& wi, float& pdf, float &t_max) const { 161 | Vec3 d = pos_ - dg.P; 162 | float distance = d.Length(); 163 | wi = d / distance; 164 | pdf = distance * distance; 165 | t_max = distance; 166 | return color_; 167 | } 168 | Vec3 pos_{0,8,-22}; 169 | Vec3 color_{1,1,1}; 170 | }; 171 | 172 | /** 173 | * Extracts the sign bit of a float, returning -0.0 for negative and 0.0 for 174 | * positive or zero. Uses SIMD operations for efficiency. _mm_set_ss sets 175 | * float x in a 128-bit vector, while _mm_set1_epi32(0x80000000) creates a 176 | * mask to isolate the sign bit. _mm_and_ps applies the mask, and _mm_cvtss_f32 177 | * converts the result back to a float. 178 | */ 179 | __forceinline float signmsk(const float x) { 180 | return _mm_cvtss_f32(_mm_and_ps(_mm_set_ss(x),_mm_castsi128_ps(_mm_set1_epi32(0x80000000)))); 181 | } 182 | 183 | 184 | /** 185 | * xorf performs a bitwise XOR on float x and y, returning a float. 186 | * - If x and y are both positive or both negative: No sign change in x. 187 | * - If x and y have different signs: The sign of x is "inverted". 188 | * This operation can flip the sign of x or leave it unchanged, 189 | * depending on y's sign. 190 | */ 191 | __forceinline float xorf(const float x, const float y) { 192 | return _mm_cvtss_f32(_mm_xor_ps(_mm_set_ss(x),_mm_set_ss(y))); 193 | } 194 | 195 | void Intersect(const Ray& ray, 196 | Hit& hit, 197 | int obj_id, 198 | int tri_id, 199 | const TriangleMesh::Triangle& tri, 200 | const Vec3* verts) { 201 | const Vec3 p0 = verts[tri.v0]; 202 | const Vec3 p1 = verts[tri.v1]; 203 | const Vec3 p2 = verts[tri.v2]; 204 | const Vec3 e1 = p0 - p1; 205 | const Vec3 e2 = p2 - p0; 206 | const Vec3 Ng = e1.Cross(e2); 207 | 208 | const Vec3 C = p0 - ray.orig; 209 | const Vec3 R = ray.dir.Cross(C); 210 | const float det = Ng.Dot(ray.dir); 211 | const float abs_det = std::abs(det); 212 | const float sng_det = signmsk(det); 213 | if (det == 0) [[unlikely]] return; 214 | 215 | const float U = xorf(R.Dot(e2), sng_det); 216 | if (U < 0) [[likely]] return; 217 | 218 | const float V = xorf(R.Dot(e1), sng_det); 219 | if (V < 0) [[likely]] return; 220 | 221 | const float W = abs_det - U - V; 222 | if (W < 0) [[likely]] return; 223 | 224 | const float T = xorf(Ng.Dot(C), sng_det); 225 | if (T < abs_det * ray.near || abs_det * hit.t < T) [[unlikely]] return; 226 | 227 | hit.u = U / abs_det; 228 | hit.v = V / abs_det; 229 | hit.t = T / abs_det; 230 | hit.id0 = obj_id; 231 | hit.id1 = tri_id; 232 | } 233 | 234 | bool Occluded(const Ray& ray, 235 | const TriangleMesh::Triangle& tri, 236 | const Vec3* verts) { 237 | const Vec3 p0 = verts[tri.v0]; 238 | const Vec3 p1 = verts[tri.v1]; 239 | const Vec3 p2 = verts[tri.v2]; 240 | const Vec3 e1 = p0 - p1; 241 | const Vec3 e2 = p2 - p0; 242 | const Vec3 Ng = e1.Cross(e2); 243 | 244 | const Vec3 C = p0 - ray.orig; 245 | const Vec3 R = ray.dir.Cross(C); 246 | const float det = Ng.Dot(ray.dir); 247 | const float abs_det = abs(det); 248 | const float sgn_det = signmsk(det); 249 | if (det == 0.f) [[unlikely]] return false; 250 | 251 | const float U = xorf(R.Dot(e2),sgn_det); 252 | if (U < 0.f) [[likely]] return false; 253 | 254 | const float V = xorf(R.Dot(e1), sgn_det); 255 | if (V < 0.f) [[likely]] return false; 256 | 257 | const float W = abs_det - U - V; 258 | if (W < 0.f) [[likely]] return false; 259 | 260 | const float T = xorf(Ng.Dot(C), sgn_det); 261 | if (T < abs_det * ray.near || abs_det * ray.far < T) [[unlikely]] return false; 262 | 263 | return true; 264 | } 265 | 266 | bool Occluded(const Ray& ray, const std::vector>& prims) { 267 | for (int i = 0; i < (int)prims.size(); ++i) { 268 | const std::vector>& pos = prims[i]->position_; 269 | const std::vector& tris = prims[i]->triangles_; 270 | for (size_t j = 0; j < tris.size(); ++j) { 271 | if (Occluded(ray, tris[j], pos.data())) 272 | return true; 273 | } 274 | } 275 | return false; 276 | } 277 | 278 | constexpr uint32_t width = 960; 279 | constexpr uint32_t height = 540; 280 | constexpr float angle_of_view = 60.f; 281 | 282 | int main() { 283 | std::vector> prims; 284 | prims.push_back(std::make_unique()); 285 | Light light; 286 | 287 | std::vector> verts = { 288 | {-5,-4,-17}, {5,-4,-17}, {5,-4,-27}, {-5,-4,-27}, 289 | {5,-4,-27}, {5,6,-27}, {-5,6,-27}, {-5,-4,-27}}; 290 | std::vector> nors = {{0,1,0},{0,0,1}}; 291 | 292 | for (uint32_t i = 0; i < 2; ++i) { 293 | TriangleMesh* mesh = new TriangleMesh; 294 | for (uint32_t j = 0; j < 4; ++j) { 295 | mesh->position_.push_back(verts[i * 4 + j]); 296 | mesh->normals_.push_back(nors[i]); 297 | } 298 | mesh->triangles_.push_back({2,1,0}); 299 | mesh->triangles_.push_back({3,2,0}); 300 | 301 | prims.push_back(std::unique_ptr(mesh)); 302 | } 303 | 304 | float scale = std::tan(angle_of_view * 0.5 * M_PI / 180.f); 305 | float aspect_ratio = width / static_cast(height); 306 | std::unique_ptr buf = std::make_unique(width * height * 3); 307 | uint8_t* pbuf = buf.get(); 308 | std::memset(pbuf, 0x0, width * height * 3); 309 | 310 | for (uint32_t y = 0; y < height; ++y) { 311 | for (uint32_t x = 0; x < width; ++x, pbuf += 3) { 312 | float px = (2.f * (x + 0.5) / static_cast(width) - 1.f) * scale; 313 | float py = (1.f - 2.0f * (y + 0.5) / static_cast(height)) * scale / aspect_ratio; 314 | Vec3 dir(px, py, -1); 315 | dir.Normalize(); 316 | Ray ray = {Vec3(0), dir}; 317 | DifferentialGeometry dg; 318 | for (int i = 0; i < (int)prims.size(); ++i) { 319 | const std::vector>& pos = prims[i]->position_; 320 | const std::vector& tris = prims[i]->triangles_; 321 | for (size_t j = 0; j < tris.size(); ++j) { 322 | Intersect(ray, dg, i, j, tris[j], pos.data()); 323 | } 324 | } 325 | if (dg) { 326 | prims[dg.id0]->PostIntersect(ray, dg); 327 | Vec3 wi; 328 | float t_max, pdf; 329 | Vec3 light_L = light.Sample(dg, wi, pdf, t_max); 330 | bool in_shadow = Occluded({dg.P, wi, 0.01f, t_max - 0.01f}, prims); 331 | if (in_shadow) 332 | continue; 333 | Vec3 L = std::pow(2.f, 7) * light_L * std::max(0.f, dg.Ns.Dot(wi)) / (M_PI * pdf); 334 | pbuf[0] = static_cast(std::min(1.f, L.x) * 255); 335 | pbuf[1] = static_cast(std::min(1.f, L.y) * 255); 336 | pbuf[2] = static_cast(std::min(1.f, L.z) * 255); 337 | } 338 | } 339 | } 340 | 341 | std::ofstream ofs("./test.ppm", std::ios::binary); 342 | ofs << "P6\n" << width << " " << height << "\n255\n"; 343 | ofs.write(reinterpret_cast(buf.get()), width * height * 3); 344 | ofs.close(); 345 | 346 | return 0; 347 | }; -------------------------------------------------------------------------------- /introduction-to-ray-tracing/raytracer.cpp: -------------------------------------------------------------------------------- 1 | // [header] 2 | // A very basic raytracer example. 3 | // [/header] 4 | // [compile] 5 | // c++ -o raytracer -O3 -Wall raytracer.cpp 6 | // [/compile] 7 | // [ignore] 8 | // Copyright (C) 2012 www.scratchapixel.com 9 | // 10 | // This program is free software: you can redistribute it and/or modify 11 | // it under the terms of the GNU General Public License as published by 12 | // the Free Software Foundation, either version 3 of the License, or 13 | // (at your option) any later version. 14 | // 15 | // This program is distributed in the hope that it will be useful, 16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | // GNU General Public License for more details. 19 | // 20 | // You should have received a copy of the GNU General Public License 21 | // along with this program. If not, see . 22 | // [/ignore] 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #if defined __linux__ || defined __APPLE__ 32 | // "Compiled for Linux 33 | #else 34 | // Windows doesn't define these values by default, Linux does 35 | #define M_PI 3.141592653589793 36 | #define INFINITY 1e8 37 | #endif 38 | 39 | template 40 | class Vec3 41 | { 42 | public: 43 | T x, y, z; 44 | Vec3() : x(T(0)), y(T(0)), z(T(0)) {} 45 | Vec3(T xx) : x(xx), y(xx), z(xx) {} 46 | Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} 47 | Vec3& normalize() 48 | { 49 | T nor2 = length2(); 50 | if (nor2 > 0) { 51 | T invNor = 1 / sqrt(nor2); 52 | x *= invNor, y *= invNor, z *= invNor; 53 | } 54 | return *this; 55 | } 56 | Vec3 operator * (const T &f) const { return Vec3(x * f, y * f, z * f); } 57 | Vec3 operator * (const Vec3 &v) const { return Vec3(x * v.x, y * v.y, z * v.z); } 58 | T dot(const Vec3 &v) const { return x * v.x + y * v.y + z * v.z; } 59 | Vec3 operator - (const Vec3 &v) const { return Vec3(x - v.x, y - v.y, z - v.z); } 60 | Vec3 operator + (const Vec3 &v) const { return Vec3(x + v.x, y + v.y, z + v.z); } 61 | Vec3& operator += (const Vec3 &v) { x += v.x, y += v.y, z += v.z; return *this; } 62 | Vec3& operator *= (const Vec3 &v) { x *= v.x, y *= v.y, z *= v.z; return *this; } 63 | Vec3 operator - () const { return Vec3(-x, -y, -z); } 64 | T length2() const { return x * x + y * y + z * z; } 65 | T length() const { return sqrt(length2()); } 66 | friend std::ostream & operator << (std::ostream &os, const Vec3 &v) 67 | { 68 | os << "[" << v.x << " " << v.y << " " << v.z << "]"; 69 | return os; 70 | } 71 | }; 72 | 73 | typedef Vec3 Vec3f; 74 | 75 | class Sphere 76 | { 77 | public: 78 | Vec3f center; /// position of the sphere 79 | float radius, radius2; /// sphere radius and radius^2 80 | Vec3f surfaceColor, emissionColor; /// surface color and emission (light) 81 | float transparency, reflection; /// surface transparency and reflectivity 82 | Sphere( 83 | const Vec3f &c, 84 | const float &r, 85 | const Vec3f &sc, 86 | const float &refl = 0, 87 | const float &transp = 0, 88 | const Vec3f &ec = 0) : 89 | center(c), radius(r), radius2(r * r), surfaceColor(sc), emissionColor(ec), 90 | transparency(transp), reflection(refl) 91 | { /* empty */ } 92 | //[comment] 93 | // Compute a ray-sphere intersection using the geometric solution 94 | //[/comment] 95 | bool intersect(const Vec3f &rayorig, const Vec3f &raydir, float &t0, float &t1) const 96 | { 97 | Vec3f l = center - rayorig; 98 | float tca = l.dot(raydir); 99 | if (tca < 0) return false; 100 | float d2 = l.dot(l) - tca * tca; 101 | if (d2 > radius2) return false; 102 | float thc = sqrt(radius2 - d2); 103 | t0 = tca - thc; 104 | t1 = tca + thc; 105 | 106 | return true; 107 | } 108 | }; 109 | 110 | //[comment] 111 | // This variable controls the maximum recursion depth 112 | //[/comment] 113 | #define MAX_RAY_DEPTH 5 114 | 115 | float mix(const float &a, const float &b, const float &mix) 116 | { 117 | return b * mix + a * (1 - mix); 118 | } 119 | 120 | //[comment] 121 | // This is the main trace function. It takes a ray as argument (defined by its origin 122 | // and direction). We test if this ray intersects any of the geometry in the scene. 123 | // If the ray intersects an object, we compute the intersection point, the normal 124 | // at the intersection point, and shade this point using this information. 125 | // Shading depends on the surface property (is it transparent, reflective, diffuse). 126 | // The function returns a color for the ray. If the ray intersects an object that 127 | // is the color of the object at the intersection point, otherwise it returns 128 | // the background color. 129 | //[/comment] 130 | Vec3f trace( 131 | const Vec3f &rayorig, 132 | const Vec3f &raydir, 133 | const std::vector &spheres, 134 | const int &depth) 135 | { 136 | //if (raydir.length() != 1) std::cerr << "Error " << raydir << std::endl; 137 | float tnear = INFINITY; 138 | const Sphere* sphere = NULL; 139 | // find intersection of this ray with the sphere in the scene 140 | for (unsigned i = 0; i < spheres.size(); ++i) { 141 | float t0 = INFINITY, t1 = INFINITY; 142 | if (spheres[i].intersect(rayorig, raydir, t0, t1)) { 143 | if (t0 < 0) t0 = t1; 144 | if (t0 < tnear) { 145 | tnear = t0; 146 | sphere = &spheres[i]; 147 | } 148 | } 149 | } 150 | // if there's no intersection return black or background color 151 | if (!sphere) return Vec3f(2); 152 | Vec3f surfaceColor = 0; // color of the ray/surfaceof the object intersected by the ray 153 | Vec3f phit = rayorig + raydir * tnear; // point of intersection 154 | Vec3f nhit = phit - sphere->center; // normal at the intersection point 155 | nhit.normalize(); // normalize normal direction 156 | // If the normal and the view direction are not opposite to each other 157 | // reverse the normal direction. That also means we are inside the sphere so set 158 | // the inside bool to true. Finally reverse the sign of IdotN which we want 159 | // positive. 160 | float bias = 1e-4; // add some bias to the point from which we will be tracing 161 | bool inside = false; 162 | if (raydir.dot(nhit) > 0) nhit = -nhit, inside = true; 163 | if ((sphere->transparency > 0 || sphere->reflection > 0) && depth < MAX_RAY_DEPTH) { 164 | float facingratio = -raydir.dot(nhit); 165 | // change the mix value to tweak the effect 166 | float fresneleffect = mix(pow(1 - facingratio, 3), 1, 0.1); 167 | // compute reflection direction (not need to normalize because all vectors 168 | // are already normalized) 169 | Vec3f refldir = raydir - nhit * 2 * raydir.dot(nhit); 170 | refldir.normalize(); 171 | Vec3f reflection = trace(phit + nhit * bias, refldir, spheres, depth + 1); 172 | Vec3f refraction = 0; 173 | // if the sphere is also transparent compute refraction ray (transmission) 174 | if (sphere->transparency) { 175 | float ior = 1.1, eta = (inside) ? ior : 1 / ior; // are we inside or outside the surface? 176 | float cosi = -nhit.dot(raydir); 177 | float k = 1 - eta * eta * (1 - cosi * cosi); 178 | Vec3f refrdir = raydir * eta + nhit * (eta * cosi - sqrt(k)); 179 | refrdir.normalize(); 180 | refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1); 181 | } 182 | // the result is a mix of reflection and refraction (if the sphere is transparent) 183 | surfaceColor = ( 184 | reflection * fresneleffect + 185 | refraction * (1 - fresneleffect) * sphere->transparency) * sphere->surfaceColor; 186 | } 187 | else { 188 | // it's a diffuse object, no need to raytrace any further 189 | for (unsigned i = 0; i < spheres.size(); ++i) { 190 | if (spheres[i].emissionColor.x > 0) { 191 | // this is a light 192 | Vec3f transmission = 1; 193 | Vec3f lightDirection = spheres[i].center - phit; 194 | lightDirection.normalize(); 195 | for (unsigned j = 0; j < spheres.size(); ++j) { 196 | if (i != j) { 197 | float t0, t1; 198 | if (spheres[j].intersect(phit + nhit * bias, lightDirection, t0, t1)) { 199 | transmission = 0; 200 | break; 201 | } 202 | } 203 | } 204 | surfaceColor += sphere->surfaceColor * transmission * 205 | std::max(float(0), nhit.dot(lightDirection)) * spheres[i].emissionColor; 206 | } 207 | } 208 | } 209 | 210 | return surfaceColor + sphere->emissionColor; 211 | } 212 | 213 | //[comment] 214 | // Main rendering function. We compute a camera ray for each pixel of the image 215 | // trace it and return a color. If the ray hits a sphere, we return the color of the 216 | // sphere at the intersection point, else we return the background color. 217 | //[/comment] 218 | void render(const std::vector &spheres) 219 | { 220 | unsigned width = 640, height = 480; 221 | Vec3f *image = new Vec3f[width * height], *pixel = image; 222 | float invWidth = 1 / float(width), invHeight = 1 / float(height); 223 | float fov = 30, aspectratio = width / float(height); 224 | float angle = tan(M_PI * 0.5 * fov / 180.); 225 | // Trace rays 226 | for (unsigned y = 0; y < height; ++y) { 227 | for (unsigned x = 0; x < width; ++x, ++pixel) { 228 | float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio; 229 | float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle; 230 | Vec3f raydir(xx, yy, -1); 231 | raydir.normalize(); 232 | *pixel = trace(Vec3f(0), raydir, spheres, 0); 233 | } 234 | } 235 | // Save result to a PPM image (keep these flags if you compile under Windows) 236 | std::ofstream ofs("./untitled.ppm", std::ios::out | std::ios::binary); 237 | ofs << "P6\n" << width << " " << height << "\n255\n"; 238 | for (unsigned i = 0; i < width * height; ++i) { 239 | ofs << (unsigned char)(std::min(float(1), image[i].x) * 255) << 240 | (unsigned char)(std::min(float(1), image[i].y) * 255) << 241 | (unsigned char)(std::min(float(1), image[i].z) * 255); 242 | } 243 | ofs.close(); 244 | delete [] image; 245 | } 246 | 247 | //[comment] 248 | // In the main function, we will create the scene which is composed of 5 spheres 249 | // and 1 light (which is also a sphere). Then, once the scene description is complete 250 | // we render that scene, by calling the render() function. 251 | //[/comment] 252 | int main(int argc, char **argv) 253 | { 254 | srand48(13); 255 | std::vector spheres; 256 | // position, radius, surface color, reflectivity, transparency, emission color 257 | spheres.push_back(Sphere(Vec3f( 0.0, -10004, -20), 10000, Vec3f(0.20, 0.20, 0.20), 0, 0.0)); 258 | spheres.push_back(Sphere(Vec3f( 0.0, 0, -20), 4, Vec3f(1.00, 0.32, 0.36), 1, 0.5)); 259 | spheres.push_back(Sphere(Vec3f( 5.0, -1, -15), 2, Vec3f(0.90, 0.76, 0.46), 1, 0.0)); 260 | spheres.push_back(Sphere(Vec3f( 5.0, 0, -25), 3, Vec3f(0.65, 0.77, 0.97), 1, 0.0)); 261 | spheres.push_back(Sphere(Vec3f(-5.5, 0, -15), 3, Vec3f(0.90, 0.90, 0.90), 1, 0.0)); 262 | // light 263 | spheres.push_back(Sphere(Vec3f( 0.0, 20, -30), 3, Vec3f(0.00, 0.00, 0.00), 0, 0.0, Vec3f(3))); 264 | render(spheres); 265 | 266 | return 0; 267 | } -------------------------------------------------------------------------------- /introduction-to-shading/geometry.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/introduction-to-shading/geometry.zip -------------------------------------------------------------------------------- /introduction-to-texturing/cube1.h: -------------------------------------------------------------------------------- 1 | int faceVertexCounts[] = {4, 4, 4, 4, 4, 4}; 2 | int faceVertexIndices[] = {0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4}; 3 | 4 | point3f points[] = {{-1.2272518, 0.89192164, 0.33913693}, {0.018165737, 0.17787457, 0.8174443}, {-0.5606737, 2.2260292, 0.5951388}, {0.6847437, 1.5119821, 1.0734462}, {-0.018165737, 2.2260292, -0.8174443}, {1.2272518, 1.5119821, -0.33913693}, {-0.6847437, 0.89192164, -1.0734462}, {0.5606737, 0.17787457, -0.5951388}}; 5 | 6 | texcoord2f st[] = {{0.375, 0}, {0.625, 0}, {0.375, 0.25}, {0.625, 0.25}, {0.375, 0.5}, {0.625, 0.5}, {0.375, 0.75}, {0.625, 0.75}, {0.375, 1}, {0.625, 1}, {0.875, 0}, {0.875, 0.25}, {0.125, 0}, {0.125, 0.25}}; 7 | 8 | int indices[] = {0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 9, 8, 1, 10, 11, 3, 12, 0, 2, 13}; -------------------------------------------------------------------------------- /introduction-to-texturing/pixar-texture3.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/introduction-to-texturing/pixar-texture3.pbm -------------------------------------------------------------------------------- /matrix-inverse/MatrixInverse.cpp: -------------------------------------------------------------------------------- 1 | // Open a Terminal (GitBash) and compile with: 2 | // clang++ -std=c++20 -o MatrixInverse MatrixInverse.cpp 3 | // 4 | // Copyright (C) 2023 www.scratchapixel.com 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | #include 20 | 21 | class Matrix4 22 | { 23 | public: 24 | Matrix4() 25 | { 26 | memset(&matrix[0][0], 0x0, sizeof(float) * 16); 27 | matrix[0][0] = matrix[1][1] = matrix[2][2] = matrix[3][3] = 1.f; 28 | } 29 | Matrix4(const Matrix4& m) 30 | { 31 | for (uint32_t j = 0; j < 4; ++j) { 32 | for (uint32_t i = 0; i < 4; ++i) { 33 | matrix[j][i] = m[j][i]; 34 | } 35 | } 36 | } 37 | Matrix4(float a, float b, float c, float d, 38 | float e, float f, float g, float h, 39 | float i, float j, float k, float l, 40 | float m, float n, float o, float p) 41 | { 42 | matrix[0][0] = a, matrix[0][1] = b, matrix[0][2] = c, matrix[0][3] = d, 43 | matrix[1][0] = e, matrix[1][1] = f, matrix[1][2] = g, matrix[1][3] = h, 44 | matrix[2][0] = i, matrix[2][1] = j, matrix[2][2] = k, matrix[2][3] = l, 45 | matrix[3][0] = m, matrix[3][1] = n, matrix[3][2] = o, matrix[3][3] = p; 46 | } 47 | float* operator[] (size_t i) { return &matrix[i][0]; }; 48 | const float* operator[] (size_t i) const { return &matrix[i][0]; }; 49 | Matrix4 Inverse() const; 50 | friend std::ostream& operator<<(std::ostream&, const Matrix4&); 51 | 52 | float matrix[4][4]; 53 | }; 54 | 55 | std::ostream& operator<<(std::ostream& os, const Matrix4& m) 56 | { 57 | os << "["; 58 | for (uint32_t i = 0; i < 4; ++i) { 59 | if (i) os << ", "; 60 | os << "["; 61 | for (uint32_t j = 0; j < 4; ++j) { 62 | if (j) os << ", "; 63 | os << m[i][j]; 64 | } 65 | os << "]"; 66 | } 67 | os << "]"; 68 | 69 | return os; 70 | } 71 | 72 | inline 73 | Matrix4 Matrix4::Inverse() const 74 | { 75 | Matrix4 s; 76 | Matrix4 t(*this); 77 | 78 | // Forward elimination 79 | for (uint32_t i = 0; i < 3; i++) { 80 | 81 | // Step 1: choose a pivot 82 | uint32_t pivot = i; 83 | 84 | float pivotsize = t[i][i]; 85 | 86 | if (pivotsize < 0) pivotsize = -pivotsize; 87 | 88 | for (uint32_t j = i + 1; j < 4; j++) { 89 | float tmp = t[j][i]; 90 | 91 | if (tmp < 0) tmp = -tmp; 92 | 93 | if (tmp > pivotsize) { 94 | pivot = j; 95 | pivotsize = tmp; 96 | } 97 | } 98 | 99 | if (pivotsize == 0) { return Matrix4(); } 100 | 101 | if (pivot != i) { 102 | for (uint32_t j = 0; j < 4; j++) { 103 | float tmp; 104 | 105 | tmp = t[i][j]; 106 | t[i][j] = t[pivot][j]; 107 | t[pivot][j] = tmp; 108 | 109 | tmp = s[i][j]; 110 | s[i][j] = s[pivot][j]; 111 | s[pivot][j] = tmp; 112 | } 113 | } 114 | 115 | // Step 2: eliminate all the numbers below the diagonal 116 | for (uint32_t j = i + 1; j < 4; j++) { 117 | float f = t[j][i] / t[i][i]; 118 | 119 | for (uint32_t k = 0; k < 4; k++) { 120 | t[j][k] -= f * t[i][k]; 121 | s[j][k] -= f * s[i][k]; 122 | } 123 | // Set the column value to exactly 0 in case 124 | // numeric roundoff left it a very tiny number 125 | t[j][i] = 0.f; 126 | } 127 | } 128 | 129 | // Step 3: set elements along the diagonal to 1.0 130 | for (uint32_t i = 0; i < 4; i++) { 131 | float divisor = t[i][i]; 132 | for (uint32_t j = 0; j < 4; j++) { 133 | t[i][j] = t[i][j] / divisor; 134 | s[i][j] = s[i][j] / divisor; 135 | } 136 | // set the diagonal to 1.0 exactly to avoid 137 | // possible round-off error 138 | t[i][i] = 1.f; 139 | } 140 | 141 | // Step 4: eliminate all the numbers above the diagonal 142 | for (uint32_t i = 0; i < 3; i++) { 143 | for (uint32_t j = i + 1; j < 4; j++) { 144 | float constant = t[i][j]; 145 | for (uint32_t k = 0; k < 4; k++) { 146 | t[i][k] -= t[j][k] * constant; 147 | s[i][k] -= s[j][k] * constant; 148 | } 149 | t[i][j] = 0.f; // in case of round-off error 150 | } 151 | } 152 | 153 | return s; 154 | } 155 | 156 | int main() 157 | { 158 | Matrix4 mat(0.832921, 0, 0.553392, 0, 0.291613, 0.849893, -0.438913, 0, -0.470323, 0.526956, 0.707894, 0, -2.574104, 3.650642, 4.868381, 1); 159 | std::cerr << mat << std::endl; 160 | Matrix4 inv = mat.Inverse(); 161 | 162 | // expected result 163 | // 0.832921 0.291613 -0.470323 0 0 0.849893 0.526956 0 0.553392 -0.438913 0.707894 0 -0.550095 -0.215218 -6.580685 1 164 | std::cerr << inv << std::endl; 165 | return 1; 166 | } -------------------------------------------------------------------------------- /minimal-ray-tracer-rendering-simple-shapes/raybox.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // A simple program to compute the intersection of rays with AA boxes 3 | //[/header] 4 | //[compile] 5 | // Download the raybox.cpp and geometry.h files to a folder. 6 | // Open a shell/terminal, and run the following command where the files is saved: 7 | // 8 | // c++ -o raybox raybox.cpp -O3 -std=c++11 9 | // 10 | // Run with: ./raybox. 11 | //[/compile] 12 | //[ignore] 13 | // Copyright (C) 2012 www.scratchapixel.com 14 | // 15 | // This program is free software: you can redistribute it and/or modify 16 | // it under the terms of the GNU General Public License as published by 17 | // the Free Software Foundation, either version 3 of the License, or 18 | // (at your option) any later version. 19 | // 20 | // This program is distributed in the hope that it will be useful, 21 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | // GNU General Public License for more details. 24 | // 25 | // You should have received a copy of the GNU General Public License 26 | // along with this program. If not, see . 27 | //[/ignore] 28 | 29 | #include "geometry.h" 30 | #include 31 | #include 32 | 33 | std::random_device rd; 34 | std::mt19937 gen(rd()); 35 | std::uniform_real_distribution<> dis(0, 1); 36 | 37 | class Ray 38 | { 39 | public: 40 | Ray(const Vec3f &orig, const Vec3f &dir) : orig(orig), dir(dir) 41 | { 42 | invdir = 1 / dir; 43 | sign[0] = (invdir.x < 0); 44 | sign[1] = (invdir.y < 0); 45 | sign[2] = (invdir.z < 0); 46 | } 47 | Vec3f orig, dir; // ray orig and dir 48 | Vec3f invdir; 49 | int sign[3]; 50 | }; 51 | 52 | class AABBox 53 | { 54 | public: 55 | AABBox(const Vec3f &b0, const Vec3f &b1) { bounds[0] = b0, bounds[1] = b1; } 56 | bool intersect(const Ray &r, float &t) const 57 | { 58 | float tmin, tmax, tymin, tymax, tzmin, tzmax; 59 | 60 | tmin = (bounds[r.sign[0]].x - r.orig.x) * r.invdir.x; 61 | tmax = (bounds[1-r.sign[0]].x - r.orig.x) * r.invdir.x; 62 | tymin = (bounds[r.sign[1]].y - r.orig.y) * r.invdir.y; 63 | tymax = (bounds[1-r.sign[1]].y - r.orig.y) * r.invdir.y; 64 | 65 | if ((tmin > tymax) || (tymin > tmax)) 66 | return false; 67 | 68 | if (tymin > tmin) 69 | tmin = tymin; 70 | if (tymax < tmax) 71 | tmax = tymax; 72 | 73 | tzmin = (bounds[r.sign[2]].z - r.orig.z) * r.invdir.z; 74 | tzmax = (bounds[1-r.sign[2]].z - r.orig.z) * r.invdir.z; 75 | 76 | if ((tmin > tzmax) || (tzmin > tmax)) 77 | return false; 78 | 79 | if (tzmin > tmin) 80 | tmin = tzmin; 81 | if (tzmax < tmax) 82 | tmax = tzmax; 83 | 84 | t = tmin; 85 | 86 | if (t < 0) { 87 | t = tmax; 88 | if (t < 0) return false; 89 | } 90 | 91 | return true; 92 | } 93 | Vec3f bounds[2]; 94 | }; 95 | 96 | int main(int argc, char **argv) 97 | { 98 | AABBox box(Vec3f(-1), Vec3f(1)); 99 | gen.seed(0); 100 | for (uint32_t i = 0; i < 16; ++i) { 101 | Vec3f randDir(2 * dis(gen) - 1, 2 * dis(gen) - 1, 2 * dis(gen) - 1); 102 | randDir.normalize(); 103 | Ray ray(Vec3f(0), randDir); 104 | float t; 105 | if (box.intersect(ray, t)) { 106 | Vec3f Phit = ray.orig + ray.dir * t; 107 | std::cerr << ray.orig << " " << Phit << std::endl; 108 | } 109 | } 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /monte-carlo-methods-in-practice/mcsim.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // Monte Carlo simulation of light transport 3 | //[/header] 4 | //[compile] 5 | // Download the mcsim.cpp file to a folder. 6 | // Open a shell/terminal, and run the following command where the files is saved: 7 | // 8 | // c++ -O3 -o mcsim mcsim.cpp -std=c++11 9 | // 10 | // Run with: ./mcsim. Open the file ./out.png in Photoshop or any program 11 | // reading PPM files. 12 | //[/compile] 13 | //[ignore] 14 | // Copyright (C) 2012 www.scratchapixel.com 15 | // 16 | // This program is free software: you can redistribute it and/or modify 17 | // it under the terms of the GNU General Public License as published by 18 | // the Free Software Foundation, either version 3 of the License, or 19 | // (at your option) any later version. 20 | // 21 | // This program is distributed in the hope that it will be useful, 22 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | // GNU General Public License for more details. 25 | // 26 | // You should have received a copy of the GNU General Public License 27 | // along with this program. If not, see . 28 | //[/ignore] 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | double getCosTheta(const double &g) // sampling the H-G scattering phase function 37 | { 38 | if (g == 0) return 2 * drand48() - 1; 39 | double mu = (1 - g * g) / (1 - g + 2 * g * drand48()); 40 | return (1 + g * g - mu * mu) / (2.0 * g); 41 | } 42 | 43 | // [comment] 44 | // Combpute the new photon direction (due to scattering event) 45 | // [/comment] 46 | void spin(double &mu_x, double &mu_y, double &mu_z, const double &g) 47 | { 48 | double costheta = getCosTheta(g); 49 | double phi = 2 * M_PI * drand48(); 50 | double sintheta = sqrt(1.0 - costheta * costheta); // sin(theta) 51 | double sinphi = sin(phi); 52 | double cosphi = cos(phi); 53 | if (mu_z == 1.0) { 54 | mu_x = sintheta * cosphi; 55 | mu_y = sintheta * sinphi; 56 | mu_z = costheta; 57 | } 58 | else if (mu_z == -1.0) { 59 | mu_x = sintheta * cosphi; 60 | mu_y = -sintheta * sinphi; 61 | mu_z = -costheta; 62 | } 63 | else { 64 | double denom = sqrt(1.0 - mu_z * mu_z); 65 | double muzcosphi = mu_z * cosphi; 66 | double ux = sintheta * (mu_x * muzcosphi - mu_y * sinphi) / denom + mu_x * costheta; 67 | double uy = sintheta * (mu_y * muzcosphi + mu_x * sinphi) / denom + mu_y * costheta; 68 | double uz = -denom * sintheta * cosphi + mu_z * costheta; 69 | mu_x = ux, mu_y = uy, mu_z = uz; 70 | } 71 | } 72 | 73 | // [comment] 74 | // Simulate the transport of light in a thin translucent slab 75 | // [/comment] 76 | void MCSimulation(double *&records, const uint32_t &size) 77 | { 78 | // [comment] 79 | // Total number of photon packets 80 | // [/comment] 81 | uint32_t nphotons = 100000; 82 | double scale = 1.0 / nphotons; 83 | double sigma_a = 1, sigma_s = 2, sigma_t = sigma_a + sigma_s; 84 | double d = 0.5, slabsize = 0.5, g = 0.75; 85 | static const short m = 10; 86 | double Rd = 0, Tt = 0; 87 | for (int n = 0; n < nphotons; ++n) { 88 | double w = 1; 89 | double x = 0, y = 0, z = 0, mux = 0, muy = 0, muz = 1; 90 | while (w != 0) { 91 | double s = -log(drand48()) / sigma_t; 92 | double distToBoundary = 0; 93 | if (muz > 0) distToBoundary = (d - z) / muz; 94 | else if (muz < 0) distToBoundary = -z / muz; 95 | // [comment] 96 | // Did the pack leave the slab? 97 | // [/comment] 98 | if (s > distToBoundary) { 99 | #ifdef ONED 100 | // compute diffuse reflectance and transmittance 101 | if (muz > 0) Tt += w; else Rd += w; 102 | #else 103 | int xi = (int)((x + slabsize / 2) / slabsize * size); 104 | int yi = (int)((y + slabsize / 2) / slabsize * size); 105 | if (muz > 0 && xi >= 0 && x < size && yi >= 0 && yi < size) { 106 | records[yi * size + xi] += w; 107 | } 108 | #endif 109 | break; 110 | } 111 | // [comment] 112 | // Move photon packet 113 | // [/comment] 114 | x += s * mux; 115 | y += s * muy; 116 | z += s * muz; 117 | // [comment] 118 | // The photon packet looses energy (absorption) 119 | // [/comment] 120 | double dw = sigma_a / sigma_t; 121 | w -= dw; w = std::max(0.0, w); 122 | if (w < 0.001) { // russian roulette test 123 | if (drand48() > 1.0 / m) break; 124 | else w *= m; 125 | } 126 | // [comment] 127 | // Scatter 128 | // [/comment] 129 | spin(mux, muy, muz, g); 130 | } 131 | } 132 | #ifdef ONED 133 | printf("Rd %f Tt %f\n", Rd * scale, Tt * scale); 134 | #endif 135 | } 136 | 137 | int main(int argc, char **argv) 138 | { 139 | double *records = NULL; 140 | const uint32_t size = 512; 141 | records = new double[size * size * 3]; 142 | memset(records, 0x0, sizeof(double) * size * size * 3); 143 | uint32_t npasses = 1; 144 | 145 | float *pixels = new float[size * size]; // image 146 | while (npasses < 64) { 147 | MCSimulation(records, size); 148 | for (int i = 0; i < size * size; ++i) pixels[i] = records[i] / npasses; 149 | //display(pixels); 150 | npasses++; 151 | printf("num passes: %d\n", npasses); 152 | } 153 | 154 | // save image to file 155 | std::ofstream ofs; 156 | ofs.open("./out.ppm", std::ios::out | std::ios::binary); 157 | ofs << "P6\n" << size << " " << size << "\n255\n"; 158 | for (uint32_t i = 0; i < size * size; ++i) { 159 | unsigned char val = (unsigned char)(255 * std::min(1.0f, pixels[i])); 160 | ofs << val << val << val; 161 | } 162 | 163 | ofs.close(); 164 | 165 | delete [] records; 166 | delete [] pixels; 167 | 168 | return 0; 169 | } -------------------------------------------------------------------------------- /obj-file-format/objimporter.cc: -------------------------------------------------------------------------------- 1 | // (c) www.scratchapixel.com - 2024. 2 | // Distributed under the terms of the CC BY-NC-ND 4.0 License. 3 | // https://creativecommons.org/licenses/by-nc-nd/4.0/ 4 | // clang++ -Wall -Wextra -std=c++23 -o objimporter.exe objimporter.cc -O3 5 | 6 | #define _USE_MATH_DEFINES 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | uint32_t image_width = 640; 22 | uint32_t image_height = 480; 23 | 24 | template 25 | class Vec3 { 26 | public: 27 | Vec3() : x(T(0)), y(T(0)), z(T(0)) {} 28 | Vec3(T x) : x(x), y(x), z(x) {} 29 | Vec3(T x, T y, T z) : x(x), y(y), z(z) {} 30 | Vec3& normalize() { 31 | T len = std::sqrt(x * x + y * y + z * z); 32 | x /= len, y /= len, z /= len; 33 | return *this; 34 | } 35 | constexpr T Dot(const Vec3& v) const { 36 | return x * v.x + y * v.y + z * v.z; 37 | } 38 | constexpr Vec3 Cross(const Vec3& v) const { 39 | return Vec3( 40 | y * v.z - z * v.y, 41 | z * v.x - x * v.z, 42 | x * v.y - y * v.x); 43 | } 44 | constexpr Vec3 operator-(const Vec3& v) const { 45 | return Vec3(x - v.x, y - v.y, z - v.z); 46 | } 47 | constexpr Vec3 operator*(const T& r) const { 48 | return Vec3(x * r, y * r, z * r); 49 | } 50 | constexpr Vec3 operator+(const Vec3& v) const { 51 | return Vec3(x + v.x, y + v.y, z + v.z); 52 | } 53 | friend std::ostream& operator<<(std::ostream& os, const Vec3& v) { 54 | return os << v.x << " " << v.y << " " << v.z; 55 | } 56 | T x, y, z; 57 | }; 58 | 59 | template 60 | class Vec2 { 61 | public: 62 | Vec2() : x(T(0)), y(T(0)) {} 63 | Vec2(T x ) : x(x), y(x) {} 64 | Vec2(T x, T y, T z) : x(x), y(y) {} 65 | T x, y; 66 | friend std::ostream& operator<<(std::ostream& os, const Vec2& v) { 67 | return os << v.x << " " << v.y; 68 | } 69 | }; 70 | 71 | using Vec2f = Vec2; 72 | using Vec3f = Vec3; 73 | 74 | struct FaceVertex { 75 | int vertex_index{-1}; 76 | int st_coord_index{-1}; 77 | int normal_index{-1}; 78 | friend std::ostream& operator<<(std::ostream& os, const FaceVertex& fv) { 79 | return os << fv.vertex_index 80 | << "/" 81 | << (fv.st_coord_index != -1 ? std::to_string(fv.st_coord_index) : "") 82 | << "/" 83 | << (fv.normal_index != -1 ? std::to_string(fv.normal_index) : ""); 84 | } 85 | }; 86 | 87 | void ParseFaceVertex(const std::string& tuple, FaceVertex& face_vertex) { 88 | std::istringstream stream(tuple); 89 | std::string part; 90 | 91 | std::getline(stream, part, '/'); 92 | assert(!part.empty()); 93 | face_vertex.vertex_index = std::stoi(part) - 1; 94 | 95 | if (std::getline(stream, part, '/') && !part.empty()) { 96 | face_vertex.st_coord_index = std::stoi(part) - 1; 97 | } 98 | 99 | if (std::getline(stream, part, '/') && !part.empty()) { 100 | face_vertex.normal_index = std::stoi(part) - 1; 101 | } 102 | } 103 | 104 | void ProcessFace(const std::vector& tuples, 105 | std::vector& face_vertices) { 106 | assert(tuples.size() == 3); 107 | for (const auto& tuple : tuples) { 108 | FaceVertex face_vertex; 109 | ParseFaceVertex(tuple, face_vertex); 110 | face_vertices.push_back(face_vertex); 111 | } 112 | } 113 | 114 | std::vector vertices, normals; 115 | std::vector tex_coordinates; 116 | 117 | struct FaceGroup { 118 | std::vector face_vertices; 119 | std::string name; 120 | }; 121 | 122 | /** 123 | * Avoid using std::vector for storing elements when we need to maintain 124 | * stable pointers or references to those elements across insertions or 125 | * deletions. This is because the underlying storage of a std::vector may 126 | * be reallocated as it grows, potentially invalidating existing pointers 127 | * and references. To ensure that pointers and references remain valid 128 | * despite container growth, use std::deque or std::list instead, as these 129 | * containers provide stable references even when new elements are added 130 | * or existing elements are removed. 131 | */ 132 | std::deque face_groups; 133 | 134 | void ParseObj(const char* file) { 135 | std::ifstream ifs(file); 136 | std::string line; 137 | face_groups.emplace_back(); 138 | FaceGroup* cur_face_group = &face_groups.back(); 139 | while (std::getline(ifs, line)) { 140 | std::istringstream stream(line); 141 | std::string type; 142 | stream >> type; 143 | if (type == "v") { 144 | Vec3f v; 145 | stream >> v.x >> v.y >> v.z; 146 | vertices.push_back(v); 147 | if (cur_face_group->face_vertices.size() != 0) [[unlikely]] { 148 | face_groups.emplace_back(); 149 | cur_face_group = &face_groups.back(); 150 | } 151 | } 152 | else if (type == "vt") { 153 | Vec2f st; 154 | stream >> st.x >> st.y; 155 | tex_coordinates.push_back(st); 156 | } 157 | else if (type == "vn") { 158 | Vec3f n; 159 | stream >> n.x >> n.y >> n.z; 160 | normals.push_back(n); 161 | } 162 | else if (type == "f") { 163 | std::vector face; 164 | std::string tuple; 165 | while (stream >> tuple) 166 | face.push_back(tuple); 167 | ProcessFace(face, cur_face_group->face_vertices); 168 | } 169 | else if (type == "g") { 170 | if (cur_face_group->face_vertices.size() != 0) { 171 | face_groups.emplace_back(); 172 | cur_face_group = &face_groups.back(); 173 | } 174 | stream >> cur_face_group->name; 175 | } 176 | } 177 | std::cerr << face_groups.size() << std::endl; 178 | for (const auto& group : face_groups) { 179 | std::cerr << group.name << " " << group.face_vertices.size() / 3 << std::endl; 180 | } 181 | ifs.close(); 182 | } 183 | 184 | template 185 | T DegreesToRadians(const T& degrees) { 186 | return M_PI * degrees / T(180); 187 | } 188 | 189 | float angle = 50.f; 190 | constexpr float super_far = 1.e6; 191 | 192 | struct Hit { 193 | bool hit{false}; 194 | float t{super_far}; 195 | float u, v; 196 | }; 197 | 198 | inline float xorf(const float x, const float y) { 199 | std::uint32_t ix, iy; 200 | 201 | std::memcpy(&ix, &x, sizeof(float)); 202 | std::memcpy(&iy, &y, sizeof(float)); 203 | 204 | std::uint32_t resultInt = ix ^ iy; 205 | 206 | float result; 207 | std::memcpy(&result, &resultInt, sizeof(float)); 208 | 209 | return result; 210 | } 211 | 212 | void intersect(const Vec3& ray_orig, 213 | const Vec3& ray_dir, 214 | const Vec3& p0, 215 | const Vec3& p1, 216 | const Vec3& p2, 217 | Hit& hit) { 218 | const float ray_near = 0.1; 219 | const Vec3 e1 = p0 - p1; 220 | const Vec3 e2 = p2 - p0; 221 | const Vec3 Ng = e1.Cross(e2); 222 | 223 | const Vec3 C = p0 - ray_orig; 224 | const Vec3 R = ray_dir.Cross(C); 225 | const float det = Ng.Dot(ray_dir); 226 | const float abs_det = std::abs(det); 227 | const float sign_det = std::copysign(0.f, det); 228 | if (det == 0) [[unlikely]] return; 229 | 230 | const float U = xorf(R.Dot(e2), sign_det); 231 | if (U < 0) [[likely]] return; 232 | 233 | const float V = xorf(R.Dot(e1), sign_det); 234 | if (V < 0) [[likely]] return; 235 | 236 | const float W = abs_det - U - V; 237 | if (W < 0) [[likely]] return; 238 | 239 | const float T = xorf(Ng.Dot(C), sign_det); 240 | if (T < abs_det * ray_near || abs_det * hit.t < T) [[unlikely]] return; 241 | 242 | const float rcp_abs_det = 1.f / abs_det; 243 | hit.u = U * rcp_abs_det; 244 | hit.v = V * rcp_abs_det; 245 | hit.t = T * rcp_abs_det; 246 | } 247 | 248 | void DoSomeWork() { 249 | auto start = std::chrono::high_resolution_clock::now(); 250 | const Vec3 ray_orig(0,0,12); 251 | float aspect_ratio = image_width / static_cast(image_height); 252 | float scale = std::tan(DegreesToRadians(0.5f * angle)); 253 | auto buf = std::make_unique(image_width * image_height); 254 | uint8_t* pbuf = buf.get(); 255 | std::memset(pbuf, 0x0, image_width * image_height); 256 | for (uint32_t j = 0; j < image_height; ++j) { 257 | float y = (1 - 2 * (j + 0.5f) / static_cast(image_height)) * scale * 1 / aspect_ratio; 258 | for (uint32_t i = 0; i < image_width; ++i, ++pbuf) { 259 | float x = (2 * (i + 0.5f) / static_cast(image_width) - 1) * scale; 260 | Vec3f ray_dir(x, y, -1); 261 | ray_dir.normalize(); 262 | float t = super_far; 263 | for (const auto& group : face_groups) { 264 | for (size_t n = 0; n < group.face_vertices.size() ; n += 3) { 265 | const Vec3f& v0 = vertices[group.face_vertices[n].vertex_index]; 266 | const Vec3f& v1 = vertices[group.face_vertices[n + 1].vertex_index]; 267 | const Vec3f& v2 = vertices[group.face_vertices[n + 2].vertex_index]; 268 | Hit hit; 269 | intersect(ray_orig, ray_dir, v0, v1, v2, hit); 270 | if (hit.t < t) { 271 | t = hit.t; 272 | const Vec3f& n0 = normals[group.face_vertices[n].normal_index]; 273 | const Vec3f& n1 = normals[group.face_vertices[n + 1].normal_index]; 274 | const Vec3f& n2 = normals[group.face_vertices[n + 2].normal_index]; 275 | Vec3f nor = n1 * hit.u + n2 * hit.v + n0 * (1 - (hit.u + hit.v)); 276 | nor.normalize(); 277 | *pbuf = static_cast(255 * std::max(0.f, nor.z)); 278 | } 279 | } 280 | } 281 | } 282 | fprintf(stderr, "\r%03u", static_cast(j / static_cast(image_height) * 100)); 283 | } 284 | auto stop = std::chrono::high_resolution_clock::now(); 285 | auto duration = std::chrono::duration_cast(stop - start); 286 | std::cout << "Render time: " << duration.count() / 1000.0 << " seconds." << std::endl; 287 | std::ofstream ofs("./result.ppm", std::ios::binary); 288 | ofs << "P6\n" << image_width << " " << image_height << "\n255\n"; 289 | for (uint32_t i = 0; i < image_width * image_height; ++i) 290 | ofs << buf[i] << buf[i] << buf[i]; 291 | ofs.close(); 292 | } 293 | 294 | int main() { 295 | ParseObj("./zombie.obj"); 296 | DoSomeWork(); 297 | return 0; 298 | } 299 | -------------------------------------------------------------------------------- /perspective-and-orthographic-projection-matrix/glorthoprojmatrix.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // A simple program to demonstrate how to build and use the OpenGL orthographic projection matrix 3 | //[/header] 4 | //[compile] 5 | // Download the glorthoprojmatrix.cpp, vertexdata.h and geometry.h files to the same folder. 6 | // Open a shell/terminal, and run the following command where the files are saved: 7 | // 8 | // c++ -o glorthoprojmatrix glorthoprojmatrix.cpp -std=c++11 -O3 9 | // 10 | // Run with: ./glorthoprojmatrix. Open the file ./out.png in Photoshop or any program 11 | // reading PPM files. 12 | //[/compile] 13 | //[ignore] 14 | // Copyright (C) 2012 www.scratchapixel.com 15 | // 16 | // This program is free software: you can redistribute it and/or modify 17 | // it under the terms of the GNU General Public License as published by 18 | // the Free Software Foundation, either version 3 of the License, or 19 | // (at your option) any later version. 20 | // 21 | // This program is distributed in the hope that it will be useful, 22 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | // GNU General Public License for more details. 25 | // 26 | // You should have received a copy of the GNU General Public License 27 | // along with this program. If not, see . 28 | //[/ignore] 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "geometry.h" 35 | #include "vertexdata.h" 36 | 37 | //[comment] 38 | // Set the OpenGL orthographic projection matrix 39 | //[/comment] 40 | void glOrtho( 41 | const float &b, const float &t, const float &l, const float &r, 42 | const float &n, const float &f, 43 | Matrix44f &M) 44 | { 45 | // set OpenGL perspective projection matrix 46 | M[0][0] = 2 / (r - l); 47 | M[0][1] = 0; 48 | M[0][2] = 0; 49 | M[0][3] = 0; 50 | 51 | M[1][0] = 0; 52 | M[1][1] = 2 / (t - b); 53 | M[1][2] = 0; 54 | M[1][3] = 0; 55 | 56 | M[2][0] = 0; 57 | M[2][1] = 0; 58 | M[2][2] = -2 / (f - n); 59 | M[2][3] = 0; 60 | 61 | M[3][0] = -(r + l) / (r - l); 62 | M[3][1] = -(t + b) / (t - b); 63 | M[3][2] = -(f + n) / (f - n); 64 | M[3][3] = 1; 65 | } 66 | 67 | //[comment] 68 | // Point-Matrix multiplication. The input point is assumed to have Cartesian 69 | // coordinates but because we will multiply this point by a 4x4 matrix we actually 70 | // assume it is a point with homogeneous coordinatess (x, y, z, w = 1). 71 | // The point-matrix results in another point with homogeneous coordinates (x', y', z', w'). 72 | // To get back to Cartesian coordinates we need to noramlized these coordinates: (x'/w', y'/w', z'/w'). 73 | //[/comment] 74 | void multPointMatrix(const Vec3f &in, Vec3f &out, const Matrix44f &M) 75 | { 76 | //out = in * Mproj; 77 | out.x = in.x * M[0][0] + in.y * M[1][0] + in.z * M[2][0] + /* in.z = 1 */ M[3][0]; 78 | out.y = in.x * M[0][1] + in.y * M[1][1] + in.z * M[2][1] + /* in.z = 1 */ M[3][1]; 79 | out.z = in.x * M[0][2] + in.y * M[1][2] + in.z * M[2][2] + /* in.z = 1 */ M[3][2]; 80 | float w = in.x * M[0][3] + in.y * M[1][3] + in.z * M[2][3] + /* in.z = 1 */ M[3][3]; 81 | 82 | // normalize if w is different than 1 (convert from homogeneous to Cartesian coordinates) 83 | if (w != 1) { 84 | out.x /= w; 85 | out.y /= w; 86 | out.z /= w; 87 | } 88 | } 89 | 90 | int main(int argc, char **argv) 91 | { 92 | uint32_t imageWidth = 512, imageHeight = 512; 93 | Matrix44f Mproj; 94 | Matrix44f worldToCamera = {0.95424, 0.20371, -0.218924, 0, 0, 0.732087, 0.681211, 0, 0.299041, -0.650039, 0.698587, 0, -0.553677, -3.920548, -62.68137, 1}; 95 | 96 | float near = 0.1; 97 | float far = 100; 98 | float imageAspectRatio = imageWidth / (float)imageHeight; // 1 if the image is square 99 | 100 | //[comment] 101 | // Compute the scene bounding box 102 | //[/comment] 103 | const float kInfinity = std::numeric_limits::max(); 104 | Vec3f minWorld(kInfinity), maxWorld(-kInfinity); 105 | for (uint32_t i = 0; i < numVertices; ++i) { 106 | if (vertices[i].x < minWorld.x) minWorld.x = vertices[i].x; 107 | if (vertices[i].y < minWorld.y) minWorld.y = vertices[i].y; 108 | if (vertices[i].z < minWorld.z) minWorld.z = vertices[i].z; 109 | if (vertices[i].x > maxWorld.x) maxWorld.x = vertices[i].x; 110 | if (vertices[i].y > maxWorld.y) maxWorld.y = vertices[i].y; 111 | if (vertices[i].z > maxWorld.z) maxWorld.z = vertices[i].z; 112 | } 113 | 114 | //[comment] 115 | // Transform min and max to camera space 116 | //[/comment] 117 | Vec3f minCamera, maxCamera; 118 | multPointMatrix(minWorld, minCamera, worldToCamera); 119 | multPointMatrix(maxWorld, maxCamera, worldToCamera); 120 | 121 | //[comment] 122 | // Find the coordinates with the maximum absolute value (in both x and y). 123 | // Use this values to set the l, r, b, and t coordinates of the screen window. 124 | // Don't forget to multiply l by the image aspect ration if != 1. 125 | //[/comment] 126 | float maxx = std::max(fabs(minCamera.x), fabs(maxCamera.x)); 127 | float maxy = std::max(fabs(minCamera.y), fabs(maxCamera.y)); 128 | float max = std::max(maxx, maxy); 129 | float r = max * imageAspectRatio, t = max; 130 | float l = -r, b = -t; 131 | 132 | //[comment] 133 | // Set the OpenGL orthographic matrix 134 | //[/comment] 135 | glOrtho(b, t, l, r, near, far, Mproj); 136 | unsigned char *buffer = new unsigned char[imageWidth * imageHeight]; 137 | memset(buffer, 0x0, imageWidth * imageHeight); 138 | 139 | //[comment] 140 | // Loop over all points 141 | //[/comment] 142 | for (uint32_t i = 0; i < numVertices; ++i) { 143 | Vec3f vertCamera, projectedVert; 144 | 145 | //[comment] 146 | // Transform to camera space 147 | //[/comment] 148 | multPointMatrix(vertices[i], vertCamera, worldToCamera); 149 | 150 | //[comment] 151 | // Project 152 | //[/comment] 153 | multPointMatrix(vertCamera, projectedVert, Mproj); 154 | if (projectedVert.x < -imageAspectRatio || projectedVert.x > imageAspectRatio || projectedVert.y < -1 || projectedVert.y > 1) continue; 155 | // convert to raster space and mark the position of the vertex in the image with a simple dot 156 | uint32_t x = std::min(imageWidth - 1, (uint32_t)((projectedVert.x + 1) * 0.5 * imageWidth)); 157 | uint32_t y = std::min(imageHeight - 1, (uint32_t)((1 - (projectedVert.y + 1) * 0.5) * imageHeight)); 158 | buffer[y * imageWidth + x] = 255; 159 | } 160 | // export to image 161 | std::ofstream ofs; 162 | ofs.open("./out.ppm"); 163 | ofs << "P5\n" << imageWidth << " " << imageHeight << "\n255\n"; 164 | ofs.write((char*)buffer, imageWidth * imageHeight); 165 | ofs.close(); 166 | delete [] buffer; 167 | 168 | return 0; 169 | } 170 | -------------------------------------------------------------------------------- /perspective-and-orthographic-projection-matrix/glprojmatrix.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // A simple program to demonstrate how to build and use the OpenGL perspective projection matrix 3 | //[/header] 4 | //[compile] 5 | // Download the glorthoprojmatrix.cpp, vertexdata.h and geometry.h files to the same folder. 6 | // Open a shell/terminal, and run the following command where the files are saved: 7 | // 8 | // c++ -o glprojmatrix glprojmatrix.cpp -std=c++11 -O3 9 | // 10 | // Run with: ./glprojmatrix. Open the file ./out.png in Photoshop or any program 11 | // reading PPM files. 12 | //[/compile] 13 | //[ignore] 14 | // Copyright (C) 2012 www.scratchapixel.com 15 | // 16 | // This program is free software: you can redistribute it and/or modify 17 | // it under the terms of the GNU General Public License as published by 18 | // the Free Software Foundation, either version 3 of the License, or 19 | // (at your option) any later version. 20 | // 21 | // This program is distributed in the hope that it will be useful, 22 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | // GNU General Public License for more details. 25 | // 26 | // You should have received a copy of the GNU General Public License 27 | // along with this program. If not, see . 28 | //[/ignore] 29 | 30 | #include 31 | #include 32 | #include 33 | #include "geometry.h" 34 | #include "vertexdata.h" 35 | 36 | //[comment] 37 | // Compute screen coordinates 38 | //[/comment] 39 | void gluPerspective( 40 | const float &angleOfView, 41 | const float &imageAspectRatio, 42 | const float &n, const float &f, 43 | float &b, float &t, float &l, float &r) 44 | { 45 | float scale = tan(angleOfView * 0.5 * M_PI / 180) * n; 46 | r = imageAspectRatio * scale, l = -r; 47 | t = scale, b = -t; 48 | } 49 | 50 | //[comment] 51 | // Set the OpenGL perspective projection matrix 52 | //[/comment] 53 | void glFrustum( 54 | const float &b, const float &t, const float &l, const float &r, 55 | const float &n, const float &f, 56 | Matrix44f &M) 57 | { 58 | // set OpenGL perspective projection matrix 59 | M[0][0] = 2 * n / (r - l); 60 | M[0][1] = 0; 61 | M[0][2] = 0; 62 | M[0][3] = 0; 63 | 64 | M[1][0] = 0; 65 | M[1][1] = 2 * n / (t - b); 66 | M[1][2] = 0; 67 | M[1][3] = 0; 68 | 69 | M[2][0] = (r + l) / (r - l); 70 | M[2][1] = (t + b) / (t - b); 71 | M[2][2] = -(f + n) / (f - n); 72 | M[2][3] = -1; 73 | 74 | M[3][0] = 0; 75 | M[3][1] = 0; 76 | M[3][2] = -2 * f * n / (f - n); 77 | M[3][3] = 0; 78 | } 79 | 80 | //[comment] 81 | // Point-Matrix multiplication. The input point is assumed to have Cartesian 82 | // coordinates but because we will multiply this point by a 4x4 matrix we actually 83 | // assume it is a point with homogeneous coordinatess (x, y, z, w = 1). 84 | // The point-matrix results in another point with homogeneous coordinates (x', y', z', w'). 85 | // To get back to Cartesian coordinates we need to noramlized these coordinates: (x'/w', y'/w', z'/w'). 86 | //[/comment] 87 | void multPointMatrix(const Vec3f &in, Vec3f &out, const Matrix44f &M) 88 | { 89 | //out = in * Mproj; 90 | out.x = in.x * M[0][0] + in.y * M[1][0] + in.z * M[2][0] + /* in.z = 1 */ M[3][0]; 91 | out.y = in.x * M[0][1] + in.y * M[1][1] + in.z * M[2][1] + /* in.z = 1 */ M[3][1]; 92 | out.z = in.x * M[0][2] + in.y * M[1][2] + in.z * M[2][2] + /* in.z = 1 */ M[3][2]; 93 | float w = in.x * M[0][3] + in.y * M[1][3] + in.z * M[2][3] + /* in.z = 1 */ M[3][3]; 94 | 95 | // normalize if w is different than 1 (convert from homogeneous to Cartesian coordinates) 96 | if (w != 1) { 97 | out.x /= w; 98 | out.y /= w; 99 | out.z /= w; 100 | } 101 | } 102 | 103 | int main(int argc, char **argv) 104 | { 105 | uint32_t imageWidth = 512, imageHeight = 512; 106 | Matrix44f Mproj; 107 | Matrix44f worldToCamera; 108 | worldToCamera[3][1] = -10; 109 | worldToCamera[3][2] = -20; 110 | float angleOfView = 90; 111 | float near = 0.1; 112 | float far = 100; 113 | float imageAspectRatio = imageWidth / (float)imageHeight; // 1 if the image is square 114 | float b, t, l, r; 115 | 116 | //[comment] 117 | // Set the screen coordinates 118 | //[/comment] 119 | gluPerspective(angleOfView, imageAspectRatio, near, far, b, t, l, r); 120 | 121 | //[comment] 122 | // Set the perspective projection matrix 123 | //[/comment] 124 | glFrustum(b, t, l, r, near, far, Mproj); 125 | 126 | unsigned char *buffer = new unsigned char[imageWidth * imageHeight]; 127 | memset(buffer, 0x0, imageWidth * imageHeight); 128 | 129 | //[comment] 130 | // Loop over all points 131 | //[/comment] 132 | for (uint32_t i = 0; i < numVertices; ++i) { 133 | Vec3f vertCamera, projectedVert; 134 | 135 | //[comment] 136 | // Transform to camera space 137 | //[/comment] 138 | multPointMatrix(vertices[i], vertCamera, worldToCamera); 139 | 140 | //[comment] 141 | // Project 142 | //[/comment] 143 | multPointMatrix(vertCamera, projectedVert, Mproj); 144 | if (projectedVert.x < -imageAspectRatio || projectedVert.x > imageAspectRatio || projectedVert.y < -1 || projectedVert.y > 1) continue; 145 | // convert to raster space and mark the position of the vertex in the image with a simple dot 146 | uint32_t x = std::min(imageWidth - 1, (uint32_t)((projectedVert.x + 1) * 0.5 * imageWidth)); 147 | uint32_t y = std::min(imageHeight - 1, (uint32_t)((1 - (projectedVert.y + 1) * 0.5) * imageHeight)); 148 | buffer[y * imageWidth + x] = 255; 149 | } 150 | // export to image 151 | std::ofstream ofs; 152 | ofs.open("./out.ppm"); 153 | ofs << "P5\n" << imageWidth << " " << imageHeight << "\n255\n"; 154 | ofs.write((char*)buffer, imageWidth * imageHeight); 155 | ofs.close(); 156 | delete [] buffer; 157 | 158 | return 0; 159 | } 160 | -------------------------------------------------------------------------------- /perspective-and-orthographic-projection-matrix/projmatrix.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // A simple program to demonstrate how to build and use a simple perspective projection matrix 3 | //[/header] 4 | //[compile] 5 | // Download the projmatrix.cpp, vertexdata.h and geometry.h files to the same folder. 6 | // Open a shell/terminal, and run the following command where the files are saved: 7 | // 8 | // c++ -o projmatrix projmatrix.cpp -std=c++11 -O3 9 | // 10 | // Run with: ./projmatrix. Open the file ./out.png in Photoshop or any program 11 | // reading PPM files. 12 | //[/compile] 13 | //[ignore] 14 | // Copyright (C) 2012 www.scratchapixel.com 15 | // 16 | // This program is free software: you can redistribute it and/or modify 17 | // it under the terms of the GNU General Public License as published by 18 | // the Free Software Foundation, either version 3 of the License, or 19 | // (at your option) any later version. 20 | // 21 | // This program is distributed in the hope that it will be useful, 22 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | // GNU General Public License for more details. 25 | // 26 | // You should have received a copy of the GNU General Public License 27 | // along with this program. If not, see . 28 | //[/ignore] 29 | 30 | #include 31 | #include 32 | #include 33 | #include "geometry.h" 34 | #include "vertexdata.h" 35 | 36 | //[comment] 37 | // Set the basic perspective projection matrix 38 | //[/comment] 39 | void setProjectionMatrix(const float &angleOfView, const float &near, const float &far, Matrix44f &M) 40 | { 41 | // do some work here 42 | float scale = 1 / tan(angleOfView * 0.5 * M_PI / 180); 43 | M[0][0] = scale; 44 | M[1][1] = scale; 45 | M[2][2] = -far / (far - near); 46 | M[3][2] = -far * near / (far - near); 47 | M[2][3] = -1; 48 | M[3][3] = 0; 49 | } 50 | 51 | //[comment] 52 | // Point-Matrix multiplication. The input point is assumed to have Cartesian 53 | // coordinates but because we will multiply this point by a 4x4 matrix we actually 54 | // assume it is a point with homogeneous coordinatess (x, y, z, w = 1). 55 | // The point-matrix results in another point with homogeneous coordinates (x', y', z', w'). 56 | // To get back to Cartesian coordinates we need to noramlized these coordinates: (x'/w', y'/w', z'/w'). 57 | //[/comment] 58 | void multPointMatrix(const Vec3f &in, Vec3f &out, const Matrix44f &M) 59 | { 60 | //out = in * Mproj; 61 | out.x = in.x * M[0][0] + in.y * M[1][0] + in.z * M[2][0] + /* in.z = 1 */ M[3][0]; 62 | out.y = in.x * M[0][1] + in.y * M[1][1] + in.z * M[2][1] + /* in.z = 1 */ M[3][1]; 63 | out.z = in.x * M[0][2] + in.y * M[1][2] + in.z * M[2][2] + /* in.z = 1 */ M[3][2]; 64 | float w = in.x * M[0][3] + in.y * M[1][3] + in.z * M[2][3] + /* in.z = 1 */ M[3][3]; 65 | 66 | // normalize if w is different than 1 (convert from homogeneous to Cartesian coordinates) 67 | if (w != 1) { 68 | out.x /= w; 69 | out.y /= w; 70 | out.z /= w; 71 | } 72 | } 73 | 74 | int main(int argc, char **argv) 75 | { 76 | uint32_t imageWidth = 512, imageHeight = 512; 77 | Matrix44f Mproj; 78 | Matrix44f worldToCamera; 79 | worldToCamera[3][1] = -10; 80 | worldToCamera[3][2] = -20; 81 | float angleOfView = 90; 82 | float near = 0.1; 83 | float far = 100; 84 | 85 | //[comment] 86 | // Set the basic perspective projection matrix 87 | //[/comment] 88 | setProjectionMatrix(angleOfView, near, far, Mproj); 89 | unsigned char *buffer = new unsigned char[imageWidth * imageHeight]; 90 | memset(buffer, 0x0, imageWidth * imageHeight); 91 | 92 | //[comment] 93 | // Loop over all points 94 | //[/comment] 95 | for (uint32_t i = 0; i < numVertices; ++i) { 96 | Vec3f vertCamera, projectedVert; 97 | 98 | //[comment] 99 | // Transform to camera space 100 | //[/comment] 101 | multPointMatrix(vertices[i], vertCamera, worldToCamera); 102 | 103 | //[comment] 104 | // Project 105 | //[/comment] 106 | multPointMatrix(vertCamera, projectedVert, Mproj); 107 | if (projectedVert.x < -1 || projectedVert.x > 1 || projectedVert.y < -1 || projectedVert.y > 1) continue; 108 | // convert to raster space and mark the position of the vertex in the image with a simple dot 109 | uint32_t x = std::min(imageWidth - 1, (uint32_t)((projectedVert.x + 1) * 0.5 * imageWidth)); 110 | uint32_t y = std::min(imageHeight - 1, (uint32_t)((1 - (projectedVert.y + 1) * 0.5) * imageHeight)); 111 | buffer[y * imageWidth + x] = 255; 112 | //std::cerr << "here sometmes" << std::endl; 113 | } 114 | // export to image 115 | std::ofstream ofs; 116 | ofs.open("./out.ppm"); 117 | ofs << "P5\n" << imageWidth << " " << imageHeight << "\n255\n"; 118 | ofs.write((char*)buffer, imageWidth * imageHeight); 119 | ofs.close(); 120 | delete [] buffer; 121 | 122 | return 0; 123 | } 124 | -------------------------------------------------------------------------------- /polygon-mesh/loadgeometry.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // A simple program to load geometry data from a file 3 | //[/header] 4 | //[compile] 5 | // Download the loadgeometry.cpp, geometry.h and test.geo files to a folder. 6 | // Open a shell/terminal, and run the following command where the files is saved: 7 | // 8 | // c++ -o loadgeometry loadgeometry.cpp -O3 -std=c++11 9 | // 10 | // Run with: ./loadgeometry. 11 | //[/compile] 12 | //[ignore] 13 | // Copyright (C) 2012 www.scratchapixel.com 14 | // 15 | // This program is free software: you can redistribute it and/or modify 16 | // it under the terms of the GNU General Public License as published by 17 | // the Free Software Foundation, either version 3 of the License, or 18 | // (at your option) any later version. 19 | // 20 | // This program is distributed in the hope that it will be useful, 21 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | // GNU General Public License for more details. 24 | // 25 | // You should have received a copy of the GNU General Public License 26 | // along with this program. If not, see . 27 | //[/ignore] 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "geometry.h" 34 | 35 | void createMesh( 36 | const uint32_t nfaces, 37 | std::unique_ptr &faceIndex, 38 | const uint32_t &vertsIndexArraySize, 39 | std::unique_ptr &vertsIndex, 40 | const uint32_t &versArraySize, 41 | std::unique_ptr &verts, 42 | std::unique_ptr &normals, 43 | std::unique_ptr &st) 44 | {} 45 | 46 | // [comment] 47 | // Structure of the geo file (ascii): 48 | // 49 | // * nfaces (integer) 50 | // 51 | // * face index array (array of integers) 52 | // 53 | // * vertex index array (array of integers) 54 | // 55 | // * vertex array (array of Vec3f, coordinates are seperated by a space) 56 | // 57 | // * normal araay (array of Vec3f, coordinates are seperated by a space) 58 | // 59 | // * texture coordinates array (array of Vec2f, coordinates are seperated by a space) 60 | // [/comment] 61 | void loadGeoFile(const char *file) 62 | { 63 | std::ifstream ifs; 64 | try { 65 | ifs.open(file); 66 | if (ifs.fail()) throw; 67 | std::stringstream ss; 68 | ss << ifs.rdbuf(); 69 | uint32_t numFaces; 70 | ss >> numFaces; 71 | std::cerr << "Mesh has " << numFaces << " faces " << std::endl; 72 | std::unique_ptr faceIndex(new uint32_t[numFaces]); 73 | uint32_t vertsIndexArraySize = 0; 74 | // reading face index array 75 | for (uint32_t i = 0; i < numFaces; ++i) { 76 | ss >> faceIndex[i]; 77 | vertsIndexArraySize += faceIndex[i]; 78 | std::cerr << faceIndex[i] << std::endl; 79 | } 80 | std::cerr << "Verts index array size " << vertsIndexArraySize << std::endl; 81 | std::unique_ptr vertsIndex(new uint32_t[vertsIndexArraySize]); 82 | uint32_t vertsArraySize = 0; 83 | // reading vertex index array 84 | for (uint32_t i = 0; i < vertsIndexArraySize; ++i) { 85 | ss >> vertsIndex[i]; 86 | if (vertsIndex[i] > vertsArraySize) vertsArraySize = vertsIndex[i]; 87 | std::cerr << vertsIndex[i] << std::endl; 88 | } 89 | vertsArraySize += 1; 90 | std::cerr << "Max verts index " << vertsArraySize << std::endl; 91 | // reading vertices 92 | std::unique_ptr verts(new Vec3f[vertsArraySize]); 93 | for (uint32_t i = 0; i < vertsArraySize; ++i) { 94 | ss >> verts[i].x >> verts[i].y >> verts[i].z; 95 | std::cerr << verts[i] << std::endl; 96 | } 97 | // reading normals 98 | std::cerr << "Reading normals\n"; 99 | std::unique_ptr normals(new Vec3f[vertsIndexArraySize]); 100 | for (uint32_t i = 0; i < vertsIndexArraySize; ++i) { 101 | ss >> normals[i].x >> normals[i].y >> normals[i].z; 102 | std::cerr << normals[i] << std::endl; 103 | } 104 | // reading st coordinates 105 | std::cerr << "Reading texture coordinates\n"; 106 | std::unique_ptr st(new Vec2f[vertsIndexArraySize]); 107 | for (uint32_t i = 0; i < vertsIndexArraySize; ++i) { 108 | ss >> st[i].x >> st[i].y; 109 | std::cerr << st[i] << std::endl; 110 | } 111 | 112 | createMesh(numFaces, faceIndex, vertsIndexArraySize, vertsIndex, vertsArraySize, verts, normals, st); 113 | } 114 | catch (...) { 115 | ifs.close(); 116 | } 117 | ifs.close(); 118 | } 119 | 120 | int main(int argc, char **argv) 121 | { 122 | loadGeoFile("./test.geo"); 123 | 124 | return 0; 125 | } -------------------------------------------------------------------------------- /polygon-mesh/test.geo: -------------------------------------------------------------------------------- 1 | 6 2 | 4 4 4 4 4 4 3 | 0 1 2 3 0 4 5 1 1 5 6 2 0 3 7 4 5 4 7 6 2 6 7 3 4 | -1 1 1 1 1 1 1 1 -1 -1 1 -1 -1 -1 1 1 -1 1 1 -1 -1 -1 -1 -1 5 | 0 0 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 0 0 1 0 0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 -1 0 0 -1 0 0 -1 0 0 -1 0 1 0 0 1 0 0 1 0 0 1 0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0 6 | 0.375 0.625 0.625 0.375 0.375 0.625 0.625 0.375 0.375 0.625 0.625 0.375 0.375 0.625 0.625 0.375 0.625 0.875 0.875 0.625 0.125 0.375 0.375 0.125 0.375 0.625 0.625 0.375 0.375 0.625 0.625 0.375 0.375 0.625 0.625 0.375 0.375 0.625 0.625 0.375 0.625 0.875 0.875 0.625 0.125 0.375 0.375 0.125 -------------------------------------------------------------------------------- /procedural-patterns-noise-part-1/noise.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // A simple program to demonstrate the concept of value noise 3 | //[/header] 4 | //[compile] 5 | // Download the noise.cpp file to a folder. 6 | // Open a shell/terminal, and run the following command where the file is saved: 7 | // 8 | // c++ -o noise noise.cpp -std=c++11 -O3 9 | // 10 | // Run with: ./noise. Open the file ./noise.ppm in Photoshop or any program 11 | // reading PPM files. 12 | //[/compile] 13 | //[ignore] 14 | // Copyright (C) 2012 www.scratchapixel.com 15 | // 16 | // This program is free software: you can redistribute it and/or modify 17 | // it under the terms of the GNU General Public License as published by 18 | // the Free Software Foundation, either version 3 of the License, or 19 | // (at your option) any later version. 20 | // 21 | // This program is distributed in the hope that it will be useful, 22 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | // GNU General Public License for more details. 25 | // 26 | // You should have received a copy of the GNU General Public License 27 | // along with this program. If not, see . 28 | //[/ignore] 29 | 30 | // if you use windows uncomment these two lines 31 | //#include "stdafx.h" 32 | //#define _USE_MATH_DEFINES 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | 42 | template 43 | class Vec2 44 | { 45 | public: 46 | Vec2() : x(T(0)), y(T(0)) {} 47 | Vec2(T xx, T yy) : x(xx), y(yy) {} 48 | Vec2 operator * (const T &r) const { return Vec2(x * r, y * r); } 49 | Vec2& operator *= (const T &r) { x *= r, y *= r; return *this; } 50 | T x, y; 51 | }; 52 | 53 | typedef Vec2 Vec2f; 54 | 55 | // [comment] 56 | // Linear interpolation 57 | // [/comment] 58 | template 59 | inline T lerp(const T &lo, const T &hi, const T &t) 60 | { 61 | return lo * (1 - t) + hi * t; 62 | } 63 | 64 | // [comment] 65 | // The smoothstep function 66 | // [/comment] 67 | inline 68 | float smoothstep(const float &t) 69 | { 70 | return t * t * (3 - 2 * t); 71 | } 72 | 73 | class ValueNoise 74 | { 75 | public: 76 | ValueNoise(unsigned seed = 2016) 77 | { 78 | std::mt19937 gen(seed); 79 | std::uniform_real_distribution distrFloat; 80 | auto randFloat = std::bind(distrFloat, gen); 81 | 82 | // create an array of random values and initialize permutation table 83 | for (unsigned k = 0; k < kMaxTableSize; ++k) { 84 | r[k] = randFloat(); 85 | permutationTable[k] = k; 86 | } 87 | 88 | // shuffle values of the permutation table 89 | std::uniform_int_distribution distrUInt; 90 | auto randUInt = std::bind(distrUInt, gen); 91 | for (unsigned k = 0; k < kMaxTableSize; ++k) { 92 | unsigned i = randUInt() & kMaxTableSizeMask; 93 | std::swap(permutationTable[k], permutationTable[i]); 94 | permutationTable[k + kMaxTableSize] = permutationTable[k]; 95 | } 96 | } 97 | 98 | float eval(Vec2f &p) const 99 | { 100 | int xi = std::floor(p.x); 101 | int yi = std::floor(p.y); 102 | 103 | float tx = p.x - xi; 104 | float ty = p.y - yi; 105 | 106 | int rx0 = xi & kMaxTableSizeMask; 107 | int rx1 = (rx0 + 1) & kMaxTableSizeMask; 108 | int ry0 = yi & kMaxTableSizeMask; 109 | int ry1 = (ry0 + 1) & kMaxTableSizeMask; 110 | 111 | // random values at the corners of the cell using permutation table 112 | const float & c00 = r[permutationTable[permutationTable[rx0] + ry0]]; 113 | const float & c10 = r[permutationTable[permutationTable[rx1] + ry0]]; 114 | const float & c01 = r[permutationTable[permutationTable[rx0] + ry1]]; 115 | const float & c11 = r[permutationTable[permutationTable[rx1] + ry1]]; 116 | 117 | // remapping of tx and ty using the Smoothstep function 118 | float sx = smoothstep(tx); 119 | float sy = smoothstep(ty); 120 | 121 | // linearly interpolate values along the x axis 122 | float nx0 = lerp(c00, c10, sx); 123 | float nx1 = lerp(c01, c11, sx); 124 | 125 | // linearly interpolate the nx0/nx1 along they y axis 126 | return lerp(nx0, nx1, sy); 127 | } 128 | static const unsigned kMaxTableSize = 256; 129 | static const unsigned kMaxTableSizeMask = kMaxTableSize - 1; 130 | float r[kMaxTableSize]; 131 | unsigned permutationTable[kMaxTableSize * 2]; 132 | }; 133 | 134 | int main(int argc, char **argv) 135 | { 136 | unsigned imageWidth = 512; 137 | unsigned imageHeight = 512; 138 | float *noiseMap = new float[imageWidth * imageHeight]{ 0 }; 139 | #if 0 140 | // [comment] 141 | // Generate white noise 142 | // [/comment] 143 | unsigned seed = 2016; 144 | std::mt19937 gen(seed); 145 | std::uniform_real_distribution distr; 146 | auto dice = std::bind(distr, gen); // std::function 147 | 148 | for (unsigned j = 0; j < imageHeight; ++j) { 149 | for (unsigned i = 0; i < imageWidth; ++i) { 150 | // generate a float in the range [0:1] 151 | noiseMap[j * imageWidth + i] = dice(); 152 | } 153 | } 154 | #elif 0 155 | // [comment] 156 | // Generate value noise 157 | // [/comment] 158 | ValueNoise noise; 159 | float frequency = 0.05f; 160 | for (unsigned j = 0; j < imageHeight; ++j) { 161 | for (unsigned i = 0; i < imageWidth; ++i) { 162 | // generate a float in the range [0:1] 163 | noiseMap[j * imageWidth + i] = noise.eval(Vec2f(i, j) * frequency); 164 | } 165 | } 166 | #elif 0 167 | // [comment] 168 | // Generate fractal pattern 169 | // [/comment] 170 | ValueNoise noise; 171 | float frequency = 0.02f; 172 | float frequencyMult = 1.8; 173 | float amplitudeMult = 0.35; 174 | unsigned numLayers = 5; 175 | float maxNoiseVal = 0; 176 | for (unsigned j = 0; j < imageHeight; ++j) { 177 | for (unsigned i = 0; i < imageWidth; ++i) { 178 | Vec2f pNoise = Vec2f(i, j) * frequency; 179 | float amplitude = 1; 180 | for (unsigned l = 0; l < numLayers; ++l) { 181 | noiseMap[j * imageWidth + i] += noise.eval(pNoise) * amplitude; 182 | pNoise *= frequencyMult; 183 | amplitude *= amplitudeMult; 184 | } 185 | if (noiseMap[j * imageWidth + i] > maxNoiseVal) maxNoiseVal = noiseMap[j * imageWidth + i]; 186 | } 187 | } 188 | for (unsigned i = 0; i < imageWidth * imageHeight; ++i) noiseMap[i] /= maxNoiseVal; 189 | #elif 0 190 | // [comment] 191 | // Generate turbulence pattern 192 | // [/comment] 193 | ValueNoise noise; 194 | float frequency = 0.02f; 195 | float frequencyMult = 1.8; 196 | float amplitudeMult = 0.35; 197 | unsigned numLayers = 5; 198 | float maxNoiseVal = 0; 199 | for (unsigned j = 0; j < imageHeight; ++j) { 200 | for (unsigned i = 0; i < imageWidth; ++i) { 201 | Vec2f pNoise = Vec2f(i, j) * frequency; 202 | float amplitude = 1; 203 | for (unsigned l = 0; l < numLayers; ++l) { 204 | noiseMap[j * imageWidth + i] += std::fabs(2 * noise.eval(pNoise) - 1) * amplitude; 205 | pNoise *= frequencyMult; 206 | amplitude *= amplitudeMult; 207 | } 208 | if (noiseMap[j * imageWidth + i] > maxNoiseVal) maxNoiseVal = noiseMap[j * imageWidth + i]; 209 | } 210 | } 211 | for (unsigned i = 0; i < imageWidth * imageHeight; ++i) noiseMap[i] /= maxNoiseVal; 212 | #elif 0 213 | // [comment] 214 | // Generate marble pattern 215 | // [/comment] 216 | ValueNoise noise; 217 | float frequency = 0.02f; 218 | float frequencyMult = 1.8; 219 | float amplitudeMult = 0.35; 220 | unsigned numLayers = 5; 221 | for (unsigned j = 0; j < imageHeight; ++j) { 222 | for (unsigned i = 0; i < imageWidth; ++i) { 223 | Vec2f pNoise = Vec2f(i, j) * frequency; 224 | float amplitude = 1; 225 | float noiseValue = 0; 226 | // compute some fractal noise 227 | for (unsigned l = 0; l < numLayers; ++l) { 228 | noiseValue += noise.eval(pNoise) * amplitude; 229 | pNoise *= frequencyMult; 230 | amplitude *= amplitudeMult; 231 | } 232 | // we "displace" the value i used in the sin() expression by noiseValue * 100 233 | noiseMap[j * imageWidth + i] = (sin((i + noiseValue * 100) * 2 * M_PI / 200.f) + 1) / 2.f; 234 | } 235 | } 236 | #else 1 237 | // [comment] 238 | // Generate wood pattern 239 | // [/comment] 240 | ValueNoise noise; 241 | float frequency = 0.01f; 242 | for (unsigned j = 0; j < imageHeight; ++j) { 243 | for (unsigned i = 0; i < imageWidth; ++i) { 244 | float g = noise.eval(Vec2f(i, j) * frequency) * 10; 245 | noiseMap[j * imageWidth + i] = g - (int)g; 246 | } 247 | } 248 | #endif 249 | 250 | // output noise map to PPM 251 | std::ofstream ofs; 252 | ofs.open("./noise.ppm", std::ios::out | std::ios::binary); 253 | ofs << "P6\n" << imageWidth << " " << imageHeight << "\n255\n"; 254 | for (unsigned k = 0; k < imageWidth * imageHeight; ++k) { 255 | unsigned char n = static_cast(noiseMap[k] * 255); 256 | ofs << n << n << n; 257 | } 258 | ofs.close(); 259 | 260 | delete[] noiseMap; 261 | 262 | return 0; 263 | } 264 | -------------------------------------------------------------------------------- /rasterization-practical-implementation/raster3d.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 www.scratchapixel.com 2 | // Distributed under the terms of the CC BY-NC-ND 4.0 License. 3 | // https://creativecommons.org/licenses/by-nc-nd/4.0/ 4 | // clang++ -o raster3d.exe raster3d.cpp -O3 5 | 6 | #define _USE_MATH_DEFINES 7 | 8 | #include "geometry.h" 9 | #include 10 | #include 11 | 12 | #include "cow.h" 13 | 14 | static const float inchToMm = 25.4; 15 | enum FitResolutionGate { kFill = 0, kOverscan }; 16 | 17 | void computeScreenCoordinates( 18 | const float &filmApertureWidth, 19 | const float &filmApertureHeight, 20 | const uint32_t &imageWidth, 21 | const uint32_t &imageHeight, 22 | const FitResolutionGate &fitFilm, 23 | const float &nearClippingPlane, 24 | const float &focalLength, 25 | float &top, float &bottom, float &left, float &right 26 | ) 27 | { 28 | float filmAspectRatio = filmApertureWidth / filmApertureHeight; 29 | float deviceAspectRatio = imageWidth / (float)imageHeight; 30 | 31 | top = ((filmApertureHeight * inchToMm / 2) / focalLength) * nearClippingPlane; 32 | right = ((filmApertureWidth * inchToMm / 2) / focalLength) * nearClippingPlane; 33 | 34 | // field of view (horizontal) 35 | float fov = 2 * 180 / M_PI * atan((filmApertureWidth * inchToMm / 2) / focalLength); 36 | std::cerr << "Field of view " << fov << std::endl; 37 | 38 | float xscale = 1; 39 | float yscale = 1; 40 | 41 | switch (fitFilm) { 42 | default: 43 | case kFill: 44 | if (filmAspectRatio > deviceAspectRatio) { 45 | xscale = deviceAspectRatio / filmAspectRatio; 46 | } 47 | else { 48 | yscale = filmAspectRatio / deviceAspectRatio; 49 | } 50 | break; 51 | case kOverscan: 52 | if (filmAspectRatio > deviceAspectRatio) { 53 | yscale = filmAspectRatio / deviceAspectRatio; 54 | } 55 | else { 56 | xscale = deviceAspectRatio / filmAspectRatio; 57 | } 58 | break; 59 | } 60 | 61 | right *= xscale; 62 | top *= yscale; 63 | 64 | bottom = -top; 65 | left = -right; 66 | } 67 | 68 | void convertToRaster( 69 | const Vec3f &vertexWorld, 70 | const Matrix44f &worldToCamera, 71 | const float &l, 72 | const float &r, 73 | const float &t, 74 | const float &b, 75 | const float &near, 76 | const uint32_t &imageWidth, 77 | const uint32_t &imageHeight, 78 | Vec3f &vertexRaster 79 | ) 80 | { 81 | Vec3f vertexCamera; 82 | 83 | worldToCamera.multVecMatrix(vertexWorld, vertexCamera); 84 | 85 | Vec2f vertexScreen; 86 | vertexScreen.x = near * vertexCamera.x / -vertexCamera.z; 87 | vertexScreen.y = near * vertexCamera.y / -vertexCamera.z; 88 | 89 | Vec2f vertexNDC; 90 | vertexNDC.x = 2 * vertexScreen.x / (r - l) - (r + l) / (r - l); 91 | vertexNDC.y = 2 * vertexScreen.y / (t - b) - (t + b) / (t - b); 92 | 93 | vertexRaster.x = (vertexNDC.x + 1) / 2 * imageWidth; 94 | vertexRaster.y = (1 - vertexNDC.y) / 2 * imageHeight; 95 | vertexRaster.z = -vertexCamera.z; 96 | } 97 | 98 | float min3(const float &a, const float &b, const float &c) 99 | { return std::min(a, std::min(b, c)); } 100 | 101 | float max3(const float &a, const float &b, const float &c) 102 | { return std::max(a, std::max(b, c)); } 103 | 104 | float edgeFunction(const Vec3f &a, const Vec3f &b, const Vec3f &c) 105 | { return (c[0] - a[0]) * (b[1] - a[1]) - (c[1] - a[1]) * (b[0] - a[0]); } 106 | 107 | const uint32_t imageWidth = 640; 108 | const uint32_t imageHeight = 480; 109 | const Matrix44f worldToCamera = {0.707107, -0.331295, 0.624695, 0, 0, 0.883452, 0.468521, 0, -0.707107, -0.331295, 0.624695, 0, -1.63871, -5.747777, -40.400412, 1}; 110 | 111 | const uint32_t ntris = 3156; 112 | const float nearClippingPlane = 1; 113 | const float farClippingPLane = 1000; 114 | float focalLength = 20; // in mm 115 | 116 | // 35mm Full Aperture in inches 117 | float filmApertureWidth = 0.980; 118 | float filmApertureHeight = 0.735; 119 | 120 | int main(int argc, char **argv) 121 | { 122 | Matrix44f cameraToWorld = worldToCamera.inverse(); 123 | 124 | float t, b, l, r; 125 | 126 | computeScreenCoordinates( 127 | filmApertureWidth, filmApertureHeight, 128 | imageWidth, imageHeight, 129 | kOverscan, 130 | nearClippingPlane, 131 | focalLength, 132 | t, b, l, r); 133 | 134 | Vec3 *frameBuffer = new Vec3[imageWidth * imageHeight]; 135 | 136 | for (uint32_t i = 0; i < imageWidth * imageHeight; ++i) { 137 | frameBuffer[i] = Vec3(255); 138 | } 139 | 140 | float *depthBuffer = new float[imageWidth * imageHeight]; 141 | 142 | for (uint32_t i = 0; i < imageWidth * imageHeight; ++i) { 143 | depthBuffer[i] = farClippingPLane; 144 | } 145 | 146 | auto t_start = std::chrono::high_resolution_clock::now(); 147 | 148 | for (uint32_t i = 0; i < ntris; ++i) { 149 | const Vec3f &v0 = vertices[nvertices[i * 3]]; 150 | const Vec3f &v1 = vertices[nvertices[i * 3 + 1]]; 151 | const Vec3f &v2 = vertices[nvertices[i * 3 + 2]]; 152 | 153 | Vec3f v0Raster, v1Raster, v2Raster; 154 | convertToRaster(v0, worldToCamera, l, r, t, b, nearClippingPlane, imageWidth, imageHeight, v0Raster); 155 | convertToRaster(v1, worldToCamera, l, r, t, b, nearClippingPlane, imageWidth, imageHeight, v1Raster); 156 | convertToRaster(v2, worldToCamera, l, r, t, b, nearClippingPlane, imageWidth, imageHeight, v2Raster); 157 | 158 | v0Raster.z = 1 / v0Raster.z, 159 | v1Raster.z = 1 / v1Raster.z, 160 | v2Raster.z = 1 / v2Raster.z; 161 | 162 | Vec2f st0 = st[stindices[i * 3]]; 163 | Vec2f st1 = st[stindices[i * 3 + 1]]; 164 | Vec2f st2 = st[stindices[i * 3 + 2]]; 165 | 166 | st0 *= v0Raster.z, st1 *= v1Raster.z, st2 *= v2Raster.z; 167 | 168 | float xmin = min3(v0Raster.x, v1Raster.x, v2Raster.x); 169 | float ymin = min3(v0Raster.y, v1Raster.y, v2Raster.y); 170 | float xmax = max3(v0Raster.x, v1Raster.x, v2Raster.x); 171 | float ymax = max3(v0Raster.y, v1Raster.y, v2Raster.y); 172 | 173 | if (xmin > imageWidth - 1 || xmax < 0 || ymin > imageHeight - 1 || ymax < 0) continue; 174 | 175 | uint32_t x0 = std::max(int32_t(0), (int32_t)(std::floor(xmin))); 176 | uint32_t x1 = std::min(int32_t(imageWidth) - 1, (int32_t)(std::floor(xmax))); 177 | uint32_t y0 = std::max(int32_t(0), (int32_t)(std::floor(ymin))); 178 | uint32_t y1 = std::min(int32_t(imageHeight) - 1, (int32_t)(std::floor(ymax))); 179 | 180 | float area = edgeFunction(v0Raster, v1Raster, v2Raster); 181 | 182 | for (uint32_t y = y0; y <= y1; ++y) { 183 | for (uint32_t x = x0; x <= x1; ++x) { 184 | Vec3f pixelSample(x + 0.5, y + 0.5, 0); 185 | float w0 = edgeFunction(v1Raster, v2Raster, pixelSample); 186 | float w1 = edgeFunction(v2Raster, v0Raster, pixelSample); 187 | float w2 = edgeFunction(v0Raster, v1Raster, pixelSample); 188 | if (w0 >= 0 && w1 >= 0 && w2 >= 0) { 189 | w0 /= area; 190 | w1 /= area; 191 | w2 /= area; 192 | float oneOverZ = v0Raster.z * w0 + v1Raster.z * w1 + v2Raster.z * w2; 193 | float z = 1 / oneOverZ; 194 | 195 | if (z < depthBuffer[y * imageWidth + x]) { 196 | depthBuffer[y * imageWidth + x] = z; 197 | 198 | Vec2f st = st0 * w0 + st1 * w1 + st2 * w2; 199 | 200 | st *= z; 201 | 202 | Vec3f v0Cam, v1Cam, v2Cam; 203 | worldToCamera.multVecMatrix(v0, v0Cam); 204 | worldToCamera.multVecMatrix(v1, v1Cam); 205 | worldToCamera.multVecMatrix(v2, v2Cam); 206 | 207 | float px = (v0Cam.x/-v0Cam.z) * w0 + (v1Cam.x/-v1Cam.z) * w1 + (v2Cam.x/-v2Cam.z) * w2; 208 | float py = (v0Cam.y/-v0Cam.z) * w0 + (v1Cam.y/-v1Cam.z) * w1 + (v2Cam.y/-v2Cam.z) * w2; 209 | 210 | Vec3f pt(px * z, py * z, -z); // pt is in camera space 211 | 212 | Vec3f n = (v1Cam - v0Cam).crossProduct(v2Cam - v0Cam); 213 | n.normalize(); 214 | Vec3f viewDirection = -pt; 215 | viewDirection.normalize(); 216 | 217 | float nDotView = std::max(0.f, n.dotProduct(viewDirection)); 218 | 219 | const int M = 10; 220 | float checker = (fmod(st.x * M, 1.0) > 0.5) ^ (fmod(st.y * M, 1.0) < 0.5); 221 | float c = 0.3 * (1 - checker) + 0.7 * checker; 222 | nDotView *= c; 223 | frameBuffer[y * imageWidth + x].x = nDotView * 255; 224 | frameBuffer[y * imageWidth + x].y = nDotView * 255; 225 | frameBuffer[y * imageWidth + x].z = nDotView * 255; 226 | } 227 | } 228 | } 229 | } 230 | } 231 | 232 | auto t_end = std::chrono::high_resolution_clock::now(); 233 | auto passedTime = std::chrono::duration(t_end - t_start).count(); 234 | std::cerr << "Wall passed time: " << passedTime << "ms" << std::endl; 235 | 236 | std::ofstream ofs; 237 | ofs.open("./output.ppm", std::ios::binary); 238 | ofs << "P6\n" << imageWidth << " " << imageHeight << "\n255\n"; 239 | ofs.write((char*)frameBuffer, imageWidth * imageWidth * 3); 240 | ofs.close(); 241 | 242 | delete [] frameBuffer; 243 | delete [] depthBuffer; 244 | 245 | return 0; 246 | } -------------------------------------------------------------------------------- /ray-tracing-generating-camera-rays/camerarays.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // A simple program to demonstrate how to implement Whitted-style ray-tracing 3 | //[/header] 4 | //[compile] 5 | // Download the camerarays.cpp and geometry.h files to a folder. 6 | // Open a shell/terminal, and run the following command where the files is saved: 7 | // 8 | // c++ -o camerarays camerarays.cpp -O3 -std=c++11 -DMAYA_STYLE 9 | // 10 | // Run with: ./camerarays. Open the file ./out.png in Photoshop or any program 11 | // reading PPM files. 12 | //[/compile] 13 | //[ignore] 14 | // Copyright (C) 2012 www.scratchapixel.com 15 | // 16 | // This program is free software: you can redistribute it and/or modify 17 | // it under the terms of the GNU General Public License as published by 18 | // the Free Software Foundation, either version 3 of the License, or 19 | // (at your option) any later version. 20 | // 21 | // This program is distributed in the hope that it will be useful, 22 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | // GNU General Public License for more details. 25 | // 26 | // You should have received a copy of the GNU General Public License 27 | // along with this program. If not, see . 28 | //[/ignore] 29 | 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "geometry.h" 43 | 44 | const float kInfinity = std::numeric_limits::max(); 45 | 46 | inline 47 | float clamp(const float &lo, const float &hi, const float &v) 48 | { return std::max(lo, std::min(hi, v)); } 49 | 50 | inline 51 | float deg2rad(const float °) 52 | { return deg * M_PI / 180; } 53 | 54 | struct Options 55 | { 56 | uint32_t width; 57 | uint32_t height; 58 | float fov; 59 | }; 60 | 61 | class Light 62 | { 63 | public: 64 | Light() {} 65 | }; 66 | 67 | class Object 68 | { 69 | public: 70 | Object() {} 71 | virtual ~Object() {} 72 | }; 73 | 74 | // [comment] 75 | // This function doesn't do much at the moment. It simply takes the ray direction 76 | // and turn it into a color. Ray direction coordinates are un the range [-1,1]. 77 | // To normalized them, we just add 1 and divide the result by 2. 78 | // [/comment] 79 | Vec3f castRay( 80 | const Vec3f &orig, const Vec3f &dir, 81 | const std::vector> &objects, 82 | const std::vector> &lights, 83 | const Options &options, 84 | uint32_t depth) 85 | { 86 | Vec3f hitColor = (dir + Vec3f(1)) * 0.5; 87 | return hitColor; 88 | } 89 | 90 | // [comment] 91 | // The main render function. This where we iterate over all pixels in the image, generate 92 | // primary rays and cast these rays into the scene. The content of the framebuffer is 93 | // saved to a file. 94 | // [/comment] 95 | void render( 96 | const Options &options, 97 | const std::vector> &objects, 98 | const std::vector> &lights) 99 | { 100 | Matrix44f cameraToWorld; 101 | Vec3f *framebuffer = new Vec3f[options.width * options.height]; 102 | Vec3f *pix = framebuffer; 103 | float scale = tan(deg2rad(options.fov * 0.5)); 104 | float imageAspectRatio = options.width / (float)options.height; 105 | // [comment] 106 | // Don't forget to transform the ray origin (which is also the camera origin 107 | // by transforming the point with coordinates (0,0,0) to world-space using the 108 | // camera-to-world matrix. 109 | // [/comment] 110 | Vec3f orig; 111 | cameraToWorld.multVecMatrix(Vec3f(0), orig); 112 | for (uint32_t j = 0; j < options.height; ++j) { 113 | for (uint32_t i = 0; i < options.width; ++i) { 114 | // [comment] 115 | // Generate primary ray direction. Compute the x and y position 116 | // of the ray in screen space. This gives a point on the image plane 117 | // at z=1. From there, we simply compute the direction by normalized 118 | // the resulting vec3f variable. This is similar to taking the vector 119 | // between the point on the image plane and the camera origin, which 120 | // in camera space is (0,0,0): 121 | // 122 | // ray.dir = normalize(Vec3f(x,y,-1) - Vec3f(0)); 123 | // [/comment] 124 | #ifdef MAYA_STYLE 125 | float x = (2 * (i + 0.5) / (float)options.width - 1) * scale; 126 | float y = (1 - 2 * (j + 0.5) / (float)options.height) * scale * 1 / imageAspectRatio; 127 | #elif 128 | 129 | float x = (2 * (i + 0.5) / (float)options.width - 1) * imageAspectRatio * scale; 130 | float y = (1 - 2 * (j + 0.5) / (float)options.height) * scale; 131 | #endif 132 | // [comment] 133 | // Don't forget to transform the ray direction using the camera-to-world matrix. 134 | // [/comment] 135 | Vec3f dir; 136 | cameraToWorld.multDirMatrix(Vec3f(x, y, -1), dir); 137 | dir.normalize(); 138 | *(pix++) = castRay(orig, dir, objects, lights, options, 0); 139 | } 140 | } 141 | 142 | // Save result to a PPM image (keep these flags if you compile under Windows) 143 | std::ofstream ofs("./out.ppm", std::ios::out | std::ios::binary); 144 | ofs << "P6\n" << options.width << " " << options.height << "\n255\n"; 145 | for (uint32_t i = 0; i < options.height * options.width; ++i) { 146 | char r = (char)(255 * clamp(0, 1, framebuffer[i].x)); 147 | char g = (char)(255 * clamp(0, 1, framebuffer[i].y)); 148 | char b = (char)(255 * clamp(0, 1, framebuffer[i].z)); 149 | ofs << r << g << b; 150 | } 151 | 152 | ofs.close(); 153 | 154 | delete [] framebuffer; 155 | } 156 | 157 | // [comment] 158 | // In the main function of the program, we create the scene (create objects and lights) 159 | // as well as set the options for the render (image widht and height, maximum recursion 160 | // depth, field-of-view, etc.). We then call the render function(). 161 | // [/comment] 162 | int main(int argc, char **argv) 163 | { 164 | // creating the scene (adding objects and lights) 165 | std::vector> objects; 166 | std::vector> lights; 167 | 168 | // setting up options 169 | Options options; 170 | options.width = 640; 171 | options.height = 480; 172 | options.fov = 90; 173 | 174 | // finally, render 175 | render(options, objects, lights); 176 | 177 | return 0; 178 | } 179 | -------------------------------------------------------------------------------- /ray-tracing-rendering-a-triangle/raytri.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2009-2024 www.scratchapixel.com 2 | // Distributed under the terms of the CC BY-NC-ND 4.0 License. 3 | // https://creativecommons.org/licenses/by-nc-nd/4.0/ 4 | // clang++ -o raytri.exe raytri.cpp -O3 -std=c++23 (optional: -DMOLLER_TRUMBORE) 5 | 6 | // needed on Windows 7 | #define _USE_MATH_DEFINES 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "geometry.h" 22 | 23 | constexpr float kEpsilon = 1e-8; 24 | 25 | inline 26 | float deg2rad(const float °) 27 | { return deg * M_PI / 180; } 28 | 29 | inline 30 | float clamp(const float &lo, const float &hi, const float &v) 31 | { return std::max(lo, std::min(hi, v)); } 32 | 33 | bool rayTriangleIntersect( 34 | const Vec3f &orig, const Vec3f &dir, 35 | const Vec3f &v0, const Vec3f &v1, const Vec3f &v2, 36 | float &t, float &u, float &v) 37 | { 38 | #ifdef MOLLER_TRUMBORE 39 | Vec3f e0 = v0 - v2; 40 | Vec3f e1 = v1 - v2; 41 | Vec3f pvec = dir.crossProduct(e1); 42 | float det = e0.dotProduct(pvec); 43 | #ifdef CULLING 44 | // if the determinant is negative the triangle is backfacing 45 | // if the determinant is close to 0, the ray misses the triangle 46 | if (det < kEpsilon) return false; 47 | #else 48 | // ray and triangle are parallel if det is close to 0 49 | if (fabs(det) < kEpsilon) return false; 50 | #endif 51 | float invDet = 1 / det; 52 | 53 | Vec3f tvec = orig - v2; 54 | u = tvec.dotProduct(pvec) * invDet; 55 | if (u < 0 || u > 1) return false; 56 | 57 | Vec3f qvec = tvec.crossProduct(e0); 58 | v = dir.dotProduct(qvec) * invDet; 59 | if (v < 0 || u + v > 1) return false; 60 | 61 | t = e1.dotProduct(qvec) * invDet; 62 | 63 | return true; 64 | #else 65 | // compute plane's normal 66 | Vec3f e0 = v2 - v1; 67 | Vec3f e1 = v0 - v2; 68 | // no need to normalize 69 | Vec3f N = e0.crossProduct(e1); // N 70 | float denom = N.dotProduct(N); 71 | 72 | // Step 1: finding P 73 | 74 | // check if ray and plane are parallel ? 75 | float NdotRayDirection = N.dotProduct(dir); 76 | 77 | if (fabs(NdotRayDirection) < kEpsilon) // almost 0 78 | return false; // they are parallel so they don't intersect ! 79 | 80 | // compute d parameter using equation 2 81 | float d = -N.dotProduct(v0); 82 | 83 | // compute t (equation 3) 84 | t = -(N.dotProduct(orig) + d) / NdotRayDirection; 85 | 86 | // check if the triangle is in behind the ray 87 | if (t < 0) return false; // the triangle is behind 88 | 89 | // compute the intersection point using equation 1 90 | Vec3f P = orig + t * dir; 91 | 92 | // Step 2: inside-outside test 93 | Vec3f C; // vector perpendicular to triangle's plane 94 | 95 | // Calculate u (for triangle BCP) 96 | Vec3f v1p = P - v1; 97 | C = e0.crossProduct(v1p); 98 | if ((u = N.dotProduct(C)) < 0) return false; // P is on the right side 99 | 100 | // Calculate v (for triangle CAP) 101 | Vec3f v2p = P - v2; 102 | C = e1.crossProduct(v2p); 103 | if ((v = N.dotProduct(C)) < 0) return false; // P is on the right side 104 | 105 | Vec3f e2 = v1 - v0; 106 | Vec3f v0p = P - v0; 107 | C = e2.crossProduct(v0p); 108 | if (N.dotProduct(C) < 0) return false; // P is on the right side 109 | 110 | u /= denom; 111 | v /= denom; 112 | 113 | return true; // this ray hits the triangle 114 | #endif 115 | } 116 | 117 | int main(int argc, char **argv) 118 | { 119 | Vec3f v0(-2, -1, -5); 120 | Vec3f v1( 1, -1, -5); 121 | Vec3f v2( 0, 1, -5); 122 | 123 | const uint32_t width = 640; 124 | const uint32_t height = 480; 125 | // v0 is yellow, v1 is cyan and v2 is magenta 126 | Vec3f cols[3] = {{1.f, 1.f, 0.f}, {0, 1.f, 1.f}, {1.f, 0.f, 1.f}}; 127 | Vec3f *framebuffer = new Vec3f[width * height]; 128 | Vec3f *pix = framebuffer; 129 | float fov = 51.52; 130 | float scale = tan(deg2rad(fov * 0.5)); 131 | float imageAspectRatio = width / (float)height; 132 | Vec3f orig(0); 133 | for (uint32_t j = 0; j < height; ++j) { 134 | for (uint32_t i = 0; i < width; ++i) { 135 | // compute primary ray 136 | float x = (2 * (i + 0.5) / (float)width - 1) * imageAspectRatio * scale; 137 | float y = (1 - 2 * (j + 0.5) / (float)height) * scale; 138 | Vec3f dir(x, y, -1); 139 | dir.normalize(); 140 | float t, u, v; 141 | if (rayTriangleIntersect(orig, dir, v0, v1, v2, t, u, v)) { 142 | *pix = u * cols[0] + v * cols[1] + (1 - u - v) * cols[2]; 143 | // uncomment this line if you want to visualize the row barycentric coordinates 144 | *pix = Vec3f(u, v, 1 - u - v); 145 | } 146 | pix++; 147 | } 148 | } 149 | 150 | // Save result to a PPM image (keep these flags if you compile under Windows) 151 | std::ofstream ofs("./out.ppm", std::ios::out | std::ios::binary); 152 | ofs << "P6\n" << width << " " << height << "\n255\n"; 153 | for (uint32_t i = 0; i < height * width; ++i) { 154 | char r = (char)(255 * clamp(0, 1, framebuffer[i].x)); 155 | char g = (char)(255 * clamp(0, 1, framebuffer[i].y)); 156 | char b = (char)(255 * clamp(0, 1, framebuffer[i].z)); 157 | ofs << r << g << b; 158 | } 159 | 160 | ofs.close(); 161 | 162 | delete [] framebuffer; 163 | 164 | return 0; 165 | } 166 | -------------------------------------------------------------------------------- /simple-image-manipulations/image.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // Simple image manipulations 3 | //[/header] 4 | //[compile] 5 | // Download the image.cpp and test images (zip file) to a folder. 6 | // Open a shell/terminal, and run the following command where the files is saved: 7 | // 8 | // clang++ -o image image.cpp -std=c++11 -O3 9 | // 10 | // You can use c++ if you don't use clang++ 11 | // 12 | // Run with: ./image. Open the resulting image (ppm) in Photoshop or any program 13 | // reading PPM files. 14 | //[/compile] 15 | //[ignore] 16 | // Copyright (C) 2016 www.scratchapixel.com 17 | // 18 | // This program is free software: you can redistribute it and/or modify 19 | // it under the terms of the GNU General Public License as published by 20 | // the Free Software Foundation, either version 3 of the License, or 21 | // (at your option) any later version. 22 | // 23 | // This program is distributed in the hope that it will be useful, 24 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 | // GNU General Public License for more details. 27 | // 28 | // You should have received a copy of the GNU General Public License 29 | // along with this program. If not, see . 30 | //[/ignore] 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | // [comment] 43 | // The main Image class 44 | // [/comment] 45 | class Image 46 | { 47 | public: 48 | 49 | struct Rgb 50 | { 51 | Rgb() : r(0), g(0), b(0) {} 52 | Rgb(float c) : r(c), g(c), b(c) {} 53 | Rgb(float _r, float _g, float _b) : r(_r), g(_g), b(_b) {} 54 | bool operator != (const Rgb &c) const { return c.r != r && c.g != g && c.b != b; } 55 | Rgb& operator *= (const Rgb &rgb) { r *= rgb.r, g *= rgb.g, b *= rgb.b; return *this; } 56 | Rgb& operator += (const Rgb &rgb) { r += rgb.r, g += rgb.g, b += rgb.b; return *this; } 57 | friend float& operator += (float &f, const Rgb rgb) 58 | { f += (rgb.r + rgb.g + rgb.b) / 3.f; return f; } 59 | float r, g, b; 60 | }; 61 | 62 | Image() : w(0), h(0), pixels(nullptr) 63 | { /* empty image */ } 64 | 65 | Image(const unsigned int &_w, const unsigned int &_h, const Rgb &c = kBlack) : 66 | w(_w), h(_h), pixels(nullptr) 67 | { 68 | pixels = new Rgb[w * h]; 69 | for (int i = 0; i < w * h; ++i) pixels[i] = c; 70 | } 71 | Image(const Image &img) : w(img.w), h(img.h), pixels(nullptr) 72 | { 73 | pixels = new Rgb[w * h]; 74 | memcpy(pixels, img.pixels, sizeof(Rgb) * w * h); 75 | } 76 | // move constructor 77 | Image(Image &&img) : w(0), h(0), pixels(nullptr) 78 | { 79 | w = img.w; 80 | h = img.h; 81 | pixels = img.pixels; 82 | img.pixels = nullptr; 83 | img.w = img.h = 0; 84 | } 85 | // move assignment operator 86 | Image& operator = (Image &&img) 87 | { 88 | if (this != &img) { 89 | if (pixels != nullptr) delete [] pixels; 90 | w = img.w, h = img.h; 91 | pixels = img.pixels; 92 | img.pixels = nullptr; 93 | img.w = img.h = 0; 94 | } 95 | return *this; 96 | } 97 | Rgb& operator () (const unsigned &x, const unsigned int &y) const 98 | { 99 | assert(x < w && y < h); 100 | return pixels[y * w + x]; 101 | } 102 | Image& operator *= (const Rgb &rgb) 103 | { 104 | for (int i = 0; i < w * h; ++i) pixels[i] *= rgb; 105 | return *this; 106 | } 107 | Image& operator += (const Image &img) 108 | { 109 | for (int i = 0; i < w * h; ++i) pixels[i] += img[i]; 110 | return *this; 111 | } 112 | Image& operator /= (const float &div) 113 | { 114 | float invDiv = 1 / div; 115 | for (int i = 0; i < w * h; ++i) pixels[i] *= invDiv; 116 | return *this; 117 | } 118 | friend Image operator * (const Rgb &rgb, const Image &img) 119 | { 120 | Image tmp(img); 121 | tmp *= rgb; 122 | return tmp; 123 | } 124 | Image operator * (const Image &img) 125 | { 126 | Image tmp(*this); 127 | // multiply pixels together 128 | for (int i = 0; i < w * h; ++i) tmp[i] *= img[i]; 129 | return tmp; 130 | } 131 | static Image circshift(const Image &img, const std::pair &shift) 132 | { 133 | Image tmp(img.w, img.h); 134 | int w = img.w, h = img.h; 135 | for (int j = 0; j < h; ++j) { 136 | int jmod = (j + shift.second) % h; 137 | for (int i = 0; i < w; ++i) { 138 | int imod = (i + shift.first) % w; 139 | tmp[jmod * w + imod] = img[j * w + i]; 140 | } 141 | } 142 | return tmp; 143 | } 144 | const Rgb& operator [] (const unsigned int &i) const { return pixels[i]; } 145 | Rgb& operator [] (const unsigned int &i) { return pixels[i]; } 146 | ~Image() { if (pixels != nullptr) delete [] pixels; } 147 | unsigned int w, h; 148 | Rgb *pixels; 149 | static const Rgb kBlack, kWhite, kRed, kGreen, kBlue; 150 | }; 151 | 152 | const Image::Rgb Image::kBlack = Image::Rgb(0); 153 | const Image::Rgb Image::kWhite = Image::Rgb(1); 154 | const Image::Rgb Image::kRed = Image::Rgb(1,0,0); 155 | const Image::Rgb Image::kGreen = Image::Rgb(0,1,0); 156 | const Image::Rgb Image::kBlue = Image::Rgb(0,0,1); 157 | 158 | // [comment] 159 | // Save an image to PPM image file 160 | // [/comment] 161 | void savePPM(const Image &img, const char *filename) 162 | { 163 | if (img.w == 0 || img.h == 0) { fprintf(stderr, "Can't save an empty image\n"); return; } 164 | std::ofstream ofs; 165 | try { 166 | ofs.open(filename, std::ios::binary); // need to spec. binary mode for Windows users 167 | if (ofs.fail()) throw("Can't open output file"); 168 | ofs << "P6\n" << img.w << " " << img.h << "\n255\n"; 169 | unsigned char r, g, b; 170 | // loop over each pixel in the image, clamp and convert to byte format 171 | for (int i = 0; i < img.w * img.h; ++i) { 172 | r = static_cast(std::min(1.f, img.pixels[i].r) * 255); 173 | g = static_cast(std::min(1.f, img.pixels[i].g) * 255); 174 | b = static_cast(std::min(1.f, img.pixels[i].b) * 255); 175 | ofs << r << g << b; 176 | } 177 | ofs.close(); 178 | } 179 | catch (const char *err) { 180 | fprintf(stderr, "%s\n", err); 181 | ofs.close(); 182 | } 183 | } 184 | 185 | // [comment] 186 | // Read a PPM image file 187 | // [/comment] 188 | Image readPPM(const char *filename) 189 | { 190 | std::ifstream ifs; 191 | ifs.open(filename, std::ios::binary); // need to spec. binary mode for Windows users 192 | Image img; 193 | try { 194 | if (ifs.fail()) { throw("Can't open input file"); } 195 | std::string header; 196 | int w, h, b; 197 | ifs >> header; 198 | if (strcmp(header.c_str(), "P6") != 0) throw("Can't read input file"); 199 | ifs >> w >> h >> b; 200 | img.w = w; img.h = h; 201 | img.pixels = new Image::Rgb[w * h]; // this is throw an exception if bad_alloc 202 | ifs.ignore(256, '\n'); // skip empty lines in necessary until we get to the binary data 203 | unsigned char pix[3]; 204 | // read each pixel one by one and convert bytes to floats 205 | for (int i = 0; i < w * h; ++i) { 206 | ifs.read(reinterpret_cast(pix), 3); 207 | img.pixels[i].r = pix[0] / 255.f; 208 | img.pixels[i].g = pix[1] / 255.f; 209 | img.pixels[i].b = pix[2] / 255.f; 210 | 211 | // [comment] 212 | // This is just to make the bokeh effect more visible, book high value pixels brightness 213 | // [/comment] 214 | if (img.pixels[i].r > 0.7) img.pixels[i].r *= 3; 215 | if (img.pixels[i].g > 0.7) img.pixels[i].g *= 3; 216 | if (img.pixels[i].b > 0.7) img.pixels[i].b *= 3; 217 | } 218 | ifs.close(); 219 | } 220 | catch (const char *err) { 221 | fprintf(stderr, "%s\n", err); 222 | ifs.close(); 223 | } 224 | 225 | return img; 226 | } 227 | 228 | // [comment] 229 | // Simulate the bokeh effect (downalod the test images or create your own) 230 | // [/comment] 231 | int main(int argc, char **argv) 232 | { 233 | try { 234 | Image I = readPPM("./xmas.ppm"); 235 | Image J = readPPM("./heart.ppm"); 236 | int w = J.w, h = J.h; 237 | Image K(w, h); 238 | float total = 0; 239 | for (int j = 0; j < h; ++j) { 240 | for (int i = 0; i < w; ++i) { 241 | if (J(i, j) != Image::kBlack) { 242 | K += J(i, j) * Image::circshift(I, std::pair(i, j)); 243 | total += J(i, j); 244 | } 245 | } 246 | } 247 | K /= total; 248 | savePPM(K, "./out.ppm"); 249 | } 250 | catch (const std::exception &e) { // catch general exception (bad_alloc mainly?) 251 | fprintf(stderr, "Error: %s\n", e.what()); 252 | } 253 | 254 | return 0; 255 | } -------------------------------------------------------------------------------- /simple-image-manipulations/testimages.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/simple-image-manipulations/testimages.zip -------------------------------------------------------------------------------- /volume-rendering-for-developers/cachefiles.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/volume-rendering-for-developers/cachefiles.zip -------------------------------------------------------------------------------- /volume-rendering-for-developers/raymarch-chap2.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // Rendering volumetric object using ray-marching. A basic implementation (chapter 1 & 2) 3 | // 4 | // https://www.scratchapixel.com/lessons/advanced-rendering/volume-rendering-for-developers/ray-marching-algorithm 5 | //[/header] 6 | //[compile] 7 | // Download the raymarch-chap2.cpp file to a folder. 8 | // Open a shell/terminal, and run the following command where the file is saved: 9 | // 10 | // clang++ -O3 raymarch-chap2.cpp -o render -std=c++17 (optional: -DBACKWARD_RAYMARCHING) 11 | // 12 | // You can use c++ if you don't use clang++ 13 | // 14 | // Run with: ./render. Open the resulting image (ppm) in Photoshop or any program 15 | // reading PPM files. 16 | //[/compile] 17 | //[ignore] 18 | // Copyright (C) 2022 www.scratchapixel.com 19 | // 20 | // This program is free software: you can redistribute it and/or modify 21 | // it under the terms of the GNU General Public License as published by 22 | // the Free Software Foundation, either version 3 of the License, or 23 | // (at your option) any later version. 24 | // 25 | // This program is distributed in the hope that it will be useful, 26 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | // GNU General Public License for more details. 29 | // 30 | // You should have received a copy of the GNU General Public License 31 | // along with this program. If not, see . 32 | //[/ignore] 33 | 34 | #define _USE_MATH_DEFINES 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | struct vec3 45 | { 46 | float x{ 0 }, y{ 0 }, z{ 0 }; 47 | vec3& nor() 48 | { 49 | float len = x * x + y * y + z * z; 50 | if (len != 0) len = sqrtf(len); 51 | x /= len, y /= len, z /= len; 52 | return *this; 53 | } 54 | float length() const 55 | { 56 | return sqrtf(x * x + y * y + z * z); 57 | } 58 | float operator * (const vec3& v) const 59 | { 60 | return x * v.x + y * v.y + z * v.z; 61 | } 62 | vec3 operator - (const vec3& v) const 63 | { 64 | return vec3{x - v.x, y - v.y, z - v.z}; 65 | } 66 | vec3 operator + (const vec3& v) const 67 | { 68 | return vec3{x + v.x, y + v.y, z + v.z}; 69 | } 70 | vec3& operator += (const vec3& v) 71 | { 72 | x += v.x, y += v.y, z += v.z; 73 | return *this; 74 | } 75 | vec3& operator *= (const float& r) 76 | { 77 | x *= r, y *= r, z *= r; 78 | return *this; 79 | } 80 | friend vec3 operator * (const float& r, const vec3& v) 81 | { 82 | return vec3{v.x * r, v.y * r, v.z * r}; 83 | } 84 | friend std::ostream& operator << (std::ostream& os, const vec3& v) 85 | { 86 | os << v.x << " " << v.y << " " << v.z; 87 | return os; 88 | } 89 | vec3 operator * (const float &r) const 90 | { 91 | return vec3{ x * r, y * r, z * r }; 92 | } 93 | }; 94 | 95 | constexpr vec3 background_color{ 0.572f, 0.772f, 0.921f }; 96 | constexpr float floatMax = std::numeric_limits::max(); 97 | 98 | struct IsectData 99 | { 100 | float t0{ floatMax }, t1{ floatMax }; 101 | vec3 pHit; 102 | vec3 nHit; 103 | bool inside{ false }; 104 | }; 105 | 106 | struct Object 107 | { 108 | public: 109 | vec3 color; 110 | int type{ 0 }; 111 | virtual bool intersect(const vec3&, const vec3&, IsectData&) const = 0; 112 | virtual ~Object() {} 113 | Object() {} 114 | }; 115 | 116 | bool solveQuadratic(float a, float b, float c, float& r0, float& r1) 117 | { 118 | float d = b * b - 4 * a * c; 119 | if (d < 0) return false; 120 | else if (d == 0) r0 = r1 = -0.5f * b / a; 121 | else { 122 | float q = (b > 0) ? -0.5f * (b + sqrtf(d)) : -0.5f * (b - sqrtf(d)); 123 | r0 = q / a; 124 | r1 = c / q; 125 | } 126 | 127 | if (r0 > r1) std::swap(r0, r1); 128 | 129 | return true; 130 | } 131 | 132 | struct Sphere : Object 133 | { 134 | public: 135 | Sphere() { color = vec3{ 1, 0, 0 }; type = 1; } 136 | bool intersect(const vec3& rayOrig, const vec3& rayDir, IsectData& isect) const override 137 | { 138 | vec3 rayOrigc = rayOrig - center; 139 | float a = rayDir * rayDir; 140 | float b = 2 * (rayDir * rayOrigc); 141 | float c = rayOrigc * rayOrigc - radius * radius; 142 | 143 | if (!solveQuadratic(a, b, c, isect.t0, isect.t1)) return false; 144 | 145 | if (isect.t0 < 0) { 146 | if (isect.t1 < 0) return false; 147 | else { 148 | isect.inside = true; 149 | isect.t0 = 0; 150 | } 151 | } 152 | 153 | return true; 154 | } 155 | 156 | float radius{ 1 }; 157 | vec3 center{ 0, 0, -4 }; 158 | }; 159 | 160 | std::default_random_engine generator; 161 | std::uniform_real_distribution distribution(0.0, 1.0); 162 | 163 | vec3 integrate(const vec3& ray_orig, const vec3& ray_dir, const std::vector> &objects) 164 | { 165 | const Object* hit_object = nullptr; 166 | IsectData isect; 167 | for (const auto& object : objects) { 168 | IsectData isect_object; 169 | if (object->intersect(ray_orig, ray_dir, isect_object)) { 170 | hit_object = object.get(); 171 | isect = isect_object; 172 | } 173 | } 174 | 175 | if (!hit_object) 176 | return background_color; 177 | 178 | float step_size = 0.2; 179 | float absorption = 0.1; 180 | float scattering = 0.1; 181 | float density = 1; 182 | int ns = std::ceil((isect.t1 - isect.t0) / step_size); 183 | step_size = (isect.t1 - isect.t0) / ns; 184 | 185 | vec3 light_dir{ 0, 1, 0 }; 186 | vec3 light_color{ 1.3, 0.3, 0.9 }; 187 | IsectData isect_vol; 188 | 189 | float transparency = 1; // initialize transmission to 1 (fully transparent) 190 | vec3 result{ 0 }; // initialize volumetric sphere color to 0 191 | 192 | #ifdef BACKWARD_RAYMARCHING 193 | // [comment] 194 | // The ray-marching loop (backward, march from t1 to t0) 195 | // [/comment] 196 | for (int n = 0; n < ns; ++n) { 197 | float t = isect.t1 - step_size * (n + 0.5); 198 | vec3 sample_pos = ray_orig + t * ray_dir; 199 | 200 | float sample_transparency = exp(-step_size * (scattering + absorption)); 201 | transparency *= sample_transparency; 202 | 203 | if (hit_object->intersect(sample_pos, light_dir, isect_vol) && isect_vol.inside) { 204 | float light_attenuation = exp(-density * isect_vol.t1 * (scattering + absorption)); 205 | result += light_color * light_attenuation * scattering * density * step_size; 206 | } 207 | else 208 | std::cerr << "oops\n"; 209 | 210 | result *= sample_transparency; 211 | } 212 | 213 | return background_color * transparency + result; 214 | #else 215 | // [comment] 216 | // The ray-marching loop (forward, march from t0 to t1) 217 | // [/comment] 218 | for (int n = 0; n < ns; ++n) { 219 | float t = isect.t0 + step_size * (n + 0.5); 220 | vec3 sample_pos = ray_orig + t * ray_dir; 221 | 222 | // compute sample transmission 223 | float sample_attenuation = exp(-step_size * (scattering + absorption)); 224 | transparency *= sample_attenuation; 225 | 226 | // In-scattering. Find distance light travels through volumetric sphere to the sample. 227 | // Then use Beer's law to attenuate the light contribution due to in-scattering. 228 | if (hit_object->intersect(sample_pos, light_dir, isect_vol) && isect_vol.inside) { 229 | float light_attenuation = exp(-density * isect_vol.t1 * (scattering + absorption)); 230 | result += transparency * light_color * light_attenuation * scattering * density * step_size; 231 | } 232 | else 233 | std::cerr << "oops\n"; 234 | } 235 | 236 | // combine background color and volumetric sphere color 237 | return background_color * transparency + result; 238 | #endif 239 | } 240 | 241 | int main() 242 | { 243 | unsigned int width = 640, height = 480; 244 | 245 | auto buffer = std::make_unique(width * height * 3); 246 | 247 | auto frameAspectRatio = width / float(height); 248 | float fov = 45; 249 | float focal = tan(M_PI / 180 * fov * 0.5); 250 | 251 | std::vector> geo; 252 | std::unique_ptr sph = std::make_unique(); 253 | sph->radius = 5; 254 | sph->center.x = 0; 255 | sph->center.y = 0; 256 | sph->center.z = -20; 257 | geo.push_back(std::move(sph)); 258 | 259 | vec3 rayOrig, rayDir; // ray origin & direction 260 | 261 | unsigned int offset = 0; 262 | for (unsigned int j = 0; j < height; ++j) { 263 | for (unsigned int i = 0; i < width; ++i) { 264 | rayDir.x = (2.f * (i + 0.5f) / width - 1) * focal; 265 | rayDir.y = (1 - 2.f * (j + 0.5f) / height) * focal * 1 / frameAspectRatio; // Maya style 266 | rayDir.z = -1.f; 267 | 268 | rayDir.nor(); 269 | 270 | vec3 c = integrate(rayOrig, rayDir, geo); 271 | 272 | buffer[offset++] = std::clamp(c.x, 0.f, 1.f) * 255; 273 | buffer[offset++] = std::clamp(c.y, 0.f, 1.f) * 255; 274 | buffer[offset++] = std::clamp(c.z, 0.f, 1.f) * 255; 275 | } 276 | } 277 | 278 | // writing file 279 | std::ofstream ofs; 280 | ofs.open("./image.ppm", std::ios::binary); 281 | ofs << "P6\n" << width << " " << height << "\n255\n"; 282 | ofs.write(reinterpret_cast(buffer.get()), width * height * 3); 283 | ofs.close(); 284 | 285 | return 0; 286 | } 287 | -------------------------------------------------------------------------------- /volume-rendering-for-developers/raymarch-chap3.cpp: -------------------------------------------------------------------------------- 1 | //[header] 2 | // Rendering volumetric object using ray-marching. A basic implementation (chapter 1 & 2) 3 | // 4 | // https://www.scratchapixel.com/lessons/advanced-rendering/volume-rendering-for-developers/ray-marching-algorithm 5 | //[/header] 6 | //[compile] 7 | // Download the raymarch-chap3.cpp file to a folder. 8 | // Open a shell/terminal, and run the following command where the file is saved: 9 | // 10 | // clang++ -O3 raymarch-chap3.cpp -o render -std=c++17 11 | // 12 | // You can use c++ if you don't use clang++ 13 | // 14 | // Run with: ./render. Open the resulting image (ppm) in Photoshop or any program 15 | // reading PPM files. 16 | //[/compile] 17 | //[ignore] 18 | // Copyright (C) 2022 www.scratchapixel.com 19 | // 20 | // This program is free software: you can redistribute it and/or modify 21 | // it under the terms of the GNU General Public License as published by 22 | // the Free Software Foundation, either version 3 of the License, or 23 | // (at your option) any later version. 24 | // 25 | // This program is distributed in the hope that it will be useful, 26 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | // GNU General Public License for more details. 29 | // 30 | // You should have received a copy of the GNU General Public License 31 | // along with this program. If not, see . 32 | //[/ignore] 33 | 34 | #define _USE_MATH_DEFINES 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | struct vec3 45 | { 46 | float x{ 0 }, y{ 0 }, z{ 0 }; 47 | vec3& nor() 48 | { 49 | float len = x * x + y * y + z * z; 50 | if (len != 0) len = sqrtf(len); 51 | x /= len, y /= len, z /= len; 52 | return *this; 53 | } 54 | float length() const 55 | { 56 | return sqrtf(x * x + y * y + z * z); 57 | } 58 | float operator * (const vec3& v) const 59 | { 60 | return x * v.x + y * v.y + z * v.z; 61 | } 62 | vec3 operator - (const vec3& v) const 63 | { 64 | return vec3{ x - v.x, y - v.y, z - v.z }; 65 | } 66 | vec3 operator + (const vec3& v) const 67 | { 68 | return vec3{ x + v.x, y + v.y, z + v.z }; 69 | } 70 | vec3& operator += (const vec3& v) 71 | { 72 | x += v.x, y += v.y, z += v.z; 73 | return *this; 74 | } 75 | vec3& operator *= (const float& r) 76 | { 77 | x *= r, y *= r, z *= r; 78 | return *this; 79 | } 80 | friend vec3 operator * (const float& r, const vec3& v) 81 | { 82 | return vec3{ v.x * r, v.y * r, v.z * r }; 83 | } 84 | friend std::ostream& operator << (std::ostream& os, const vec3& v) 85 | { 86 | os << v.x << " " << v.y << " " << v.z; 87 | return os; 88 | } 89 | vec3 operator * (const float& r) const 90 | { 91 | return vec3{ x * r, y * r, z * r }; 92 | } 93 | }; 94 | 95 | constexpr vec3 background_color{ 0.572f, 0.772f, 0.921f }; 96 | constexpr float floatMax = std::numeric_limits::max(); 97 | 98 | struct IsectData 99 | { 100 | float t0{ floatMax }, t1{ floatMax }; 101 | vec3 pHit; 102 | vec3 nHit; 103 | bool inside{ false }; 104 | }; 105 | 106 | struct Object 107 | { 108 | public: 109 | vec3 color; 110 | int type{ 0 }; 111 | virtual bool intersect(const vec3&, const vec3&, IsectData&) const = 0; 112 | virtual ~Object() {} 113 | Object() {} 114 | }; 115 | 116 | bool solveQuadratic(float a, float b, float c, float& r0, float& r1) 117 | { 118 | float d = b * b - 4 * a * c; 119 | if (d < 0) return false; 120 | else if (d == 0) r0 = r1 = -0.5f * b / a; 121 | else { 122 | float q = (b > 0) ? -0.5f * (b + sqrtf(d)) : -0.5f * (b - sqrtf(d)); 123 | r0 = q / a; 124 | r1 = c / q; 125 | } 126 | 127 | if (r0 > r1) std::swap(r0, r1); 128 | 129 | return true; 130 | } 131 | 132 | struct Sphere : Object 133 | { 134 | public: 135 | Sphere() { color = vec3{ 1, 0, 0 }; type = 1; } 136 | bool intersect(const vec3& rayOrig, const vec3& rayDir, IsectData& isect) const override 137 | { 138 | vec3 rayOrigc = rayOrig - center; 139 | float a = rayDir * rayDir; 140 | float b = 2 * (rayDir * rayOrigc); 141 | float c = rayOrigc * rayOrigc - radius * radius; 142 | 143 | if (!solveQuadratic(a, b, c, isect.t0, isect.t1)) return false; 144 | 145 | if (isect.t0 < 0) { 146 | if (isect.t1 < 0) return false; 147 | else { 148 | isect.inside = true; 149 | isect.t0 = 0; 150 | } 151 | } 152 | 153 | return true; 154 | } 155 | 156 | float radius{ 1 }; 157 | vec3 center{ 0, 0, -4 }; 158 | }; 159 | 160 | std::default_random_engine generator; 161 | std::uniform_real_distribution distribution(0.0, 1.0); 162 | 163 | // [comment] 164 | // The Henyey-Greenstein phase function 165 | // [/comment] 166 | float p(const float& g, const float& cos_theta) 167 | { 168 | float denom = 1 + g * g - 2 * g * cos_theta; 169 | return 1 / (4 * M_PI) * (1 - g * g) / (denom * sqrtf(denom)); 170 | } 171 | 172 | vec3 integrate(const vec3& ray_orig, const vec3& ray_dir, const std::vector>& objects) 173 | { 174 | const Object* hit_object = nullptr; 175 | IsectData isect; 176 | for (const auto& object : objects) { 177 | IsectData isect_object; 178 | if (object->intersect(ray_orig, ray_dir, isect_object)) { 179 | hit_object = object.get(); 180 | isect = isect_object; 181 | } 182 | } 183 | 184 | if (!hit_object) 185 | return background_color; 186 | 187 | float step_size = 0.1; 188 | float absorption = 0.5; 189 | float scattering = 0.5; 190 | float density = 0.25; 191 | float g = 0; // henyey-greenstein asymetry factor 192 | uint8_t d = 2; // russian roulette "probability" 193 | 194 | int ns = std::ceil((isect.t1 - isect.t0) / step_size); 195 | step_size = (isect.t1 - isect.t0) / ns; 196 | 197 | vec3 light_dir{ -1, 0, 0 }; 198 | vec3 light_color{ 13, 13, 13 }; 199 | IsectData isect_vol; 200 | 201 | float transparency = 1; // initialize transmission to 1 (fully transparent) 202 | vec3 result{ 0 }; // initialize volumetric sphere color to 0 203 | 204 | // [comment] 205 | // The ray-marching loop (forward, march from t0 to t1) 206 | // [/comment] 207 | for (int n = 0; n < ns; ++n) { 208 | 209 | // [comment] 210 | // Jiterring the sample position 211 | // [/comment] 212 | float t = isect.t0 + step_size * (n + distribution(generator)); 213 | vec3 sample_pos = ray_orig + t * ray_dir; 214 | 215 | // compute sample transmission 216 | float sample_attenuation = exp(-step_size * density * (scattering + absorption)); 217 | transparency *= sample_attenuation; 218 | 219 | // In-scattering. Find distance light travels through volumetric sphere to the sample. 220 | // Then use Beer's law to attenuate the light contribution due to in-scattering. 221 | if (hit_object->intersect(sample_pos, light_dir, isect_vol) && isect_vol.inside) { 222 | float light_attenuation = exp(-density * isect_vol.t1 * (scattering + absorption)); 223 | float cos_theta = (ray_dir * light_dir); 224 | result += light_color * light_attenuation * density * scattering * p(g, cos_theta) * transparency * step_size; 225 | } 226 | 227 | // [comment] 228 | // Russian roulette 229 | // [/comment] 230 | if (transparency < 1e-3) { 231 | if (distribution(generator) > 1.f / d) 232 | break; 233 | else 234 | transparency *= d; 235 | } 236 | } 237 | 238 | // combine background color and volumetric sphere color 239 | return background_color * transparency + result; 240 | } 241 | 242 | int main() 243 | { 244 | unsigned int width = 640, height = 480; 245 | 246 | auto buffer = std::make_unique(width * height * 3); 247 | 248 | auto frameAspectRatio = width / float(height); 249 | float fov = 45; 250 | float focal = tan(M_PI / 180 * fov * 0.5); 251 | 252 | std::vector> geo; 253 | std::unique_ptr sph = std::make_unique(); 254 | sph->radius = 5; 255 | sph->center.x = 0; 256 | sph->center.y = 0; 257 | sph->center.z = -20; 258 | geo.push_back(std::move(sph)); 259 | 260 | vec3 rayOrig, rayDir; // ray origin & direction 261 | 262 | unsigned int offset = 0; 263 | for (unsigned int j = 0; j < height; ++j) { 264 | for (unsigned int i = 0; i < width; ++i) { 265 | rayDir.x = (2.f * (i + 0.5f) / width - 1) * focal; 266 | rayDir.y = (1 - 2.f * (j + 0.5f) / height) * focal * 1 / frameAspectRatio; // Maya style 267 | rayDir.z = -1.f; 268 | 269 | rayDir.nor(); 270 | 271 | vec3 c = integrate(rayOrig, rayDir, geo); 272 | 273 | buffer[offset++] = std::clamp(c.x, 0.f, 1.f) * 255; 274 | buffer[offset++] = std::clamp(c.y, 0.f, 1.f) * 255; 275 | buffer[offset++] = std::clamp(c.z, 0.f, 1.f) * 255; 276 | } 277 | } 278 | 279 | // writing file 280 | std::ofstream ofs; 281 | ofs.open("./image.ppm", std::ios::binary); 282 | ofs << "P6\n" << width << " " << height << "\n255\n"; 283 | ofs.write(reinterpret_cast(buffer.get()), width * height * 3); 284 | ofs.close(); 285 | 286 | return 0; 287 | } -------------------------------------------------------------------------------- /windowing/sample.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchapixel/scratchapixel-code/47dd2e86cc02b1aad62ce2a24d0568f1e5b06cce/windowing/sample.pbm -------------------------------------------------------------------------------- /windowing/window.cc: -------------------------------------------------------------------------------- 1 | // (c) scratchapixel - 2024 2 | // Distributed under the terms of the CC BY-NC-ND 4.0 License. 3 | // https://creativecommons.org/licenses/by-nc-nd/4.0/ 4 | // clang++ -Wall -Wextra -std=c++23 -luser32 -lgdi32 -o 3d-nav-controls.exe 3d-nav-controls.cc 5 | 6 | #define UNICODE 7 | #include 8 | #include // GET_X_LPARAM 9 | #include 10 | #include 11 | #include 12 | 13 | HWND hwnd; 14 | 15 | const wchar_t* CLASSNAME = L"myapp_window"; 16 | uint32_t win_width = 640; 17 | uint32_t win_height = 480; 18 | HBITMAP hBitmap = nullptr; 19 | void* pBits = nullptr; 20 | bool is_drawing = false; 21 | HDC hdcOffscreen = nullptr; // Device context for the off-screen bitmap 22 | 23 | auto CreateBitmapFromRGB(char* pData, int width, int height) 24 | -> std::pair { 25 | BITMAPINFO bmi = {0}; 26 | bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); 27 | bmi.bmiHeader.biWidth = width; 28 | bmi.bmiHeader.biHeight = -height; // Negative indicates top-down bitmap 29 | bmi.bmiHeader.biPlanes = 1; 30 | bmi.bmiHeader.biBitCount = 24; // Assuming 24-bit RGB 31 | bmi.bmiHeader.biCompression = BI_RGB; 32 | 33 | HDC hdc = GetDC(nullptr); 34 | void* pBits; 35 | HBITMAP hbm = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0); 36 | if (hbm != nullptr) { 37 | std::memcpy(pBits, pData, width * height * 3); 38 | } 39 | ReleaseDC(nullptr, hdc); 40 | return {hbm, pBits}; 41 | } 42 | 43 | void InitializeOffScreenDC(HWND hwnd) { 44 | std::unique_ptr raw_data(new char[win_width * win_height * 3]); 45 | 46 | memset(raw_data.get(), 0x0, win_width * win_height * 3); 47 | std::ifstream ifs("./sample.pbm", std::ios::binary); 48 | std::string header; 49 | int width, height, bpp; 50 | ifs >> header; 51 | ifs >> width >> height >> bpp; 52 | ifs.ignore(); 53 | ifs.read(raw_data.get(), win_width * win_height * 3); 54 | for (uint32_t i = 0; i < win_width * win_height * 3; i += 3) { 55 | std::swap(raw_data[i], raw_data[i + 2]); 56 | } 57 | ifs.close(); 58 | 59 | auto bitmap_data = CreateBitmapFromRGB(raw_data.get(), win_width, win_height); 60 | hBitmap = bitmap_data.first; 61 | pBits = bitmap_data.second; 62 | 63 | HDC hdc = GetDC(hwnd); 64 | hdcOffscreen = CreateCompatibleDC(hdc); 65 | SelectObject(hdcOffscreen, hBitmap); 66 | ReleaseDC(hwnd, hdc); 67 | } 68 | 69 | void CleanupOffScreenDC() { 70 | if (hdcOffscreen) DeleteDC(hdcOffscreen); 71 | } 72 | 73 | void SetPixelColor(void* pBits, int width, int x, int y, uint8_t red, uint8_t green, uint8_t blue) { 74 | if (!pBits) return; 75 | 76 | int pixel_index = (y * width + x) * 3; 77 | 78 | uint8_t* pPixel = static_cast(pBits) + pixel_index; 79 | 80 | pPixel[0] = blue; 81 | pPixel[1] = green; 82 | pPixel[2] = red; 83 | } 84 | 85 | LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { 86 | switch(msg) { 87 | case WM_CLOSE: 88 | if (hBitmap != nullptr) { 89 | DeleteObject(hBitmap); 90 | hBitmap = nullptr; 91 | } 92 | CleanupOffScreenDC(); 93 | DestroyWindow(hWnd); 94 | break; 95 | case WM_DESTROY: 96 | PostQuitMessage(0); 97 | break; 98 | case WM_LBUTTONDOWN: 99 | is_drawing = true; 100 | break; 101 | case WM_LBUTTONUP: 102 | is_drawing = false; 103 | break; 104 | case WM_MOUSEMOVE: { 105 | int xpos = GET_X_LPARAM(lParam); 106 | int ypos = GET_Y_LPARAM(lParam); 107 | if (is_drawing) { 108 | SetPixelColor(pBits, win_width, xpos, ypos, 255, 0, 0); 109 | // Request the window to redraw 110 | InvalidateRect(hWnd, NULL, TRUE); 111 | } 112 | break; 113 | } 114 | /** 115 | * Prevent Windows from automatically erasing the background which 116 | * reduces/eliminates flickering due to to the background being erased 117 | * with the window's background brush before the drawing occurs. 118 | * Returning 1 meansd "erase completed". 119 | */ 120 | case WM_ERASEBKGND: 121 | return 1; // Indicate that background erase is handled 122 | case WM_PAINT: 123 | { 124 | PAINTSTRUCT ps; 125 | HDC hdc = BeginPaint(hWnd, &ps); 126 | // Performs the BitBlt operation directly from the off-screen DC (hdcOffscreen) 127 | // to the window's DC. 128 | BitBlt(hdc, 0, 0, win_width, win_height, hdcOffscreen, 0, 0, SRCCOPY); 129 | EndPaint(hWnd, &ps); 130 | } 131 | break; 132 | default: 133 | return DefWindowProc(hWnd, msg, wParam, lParam); 134 | } 135 | return 0; 136 | } 137 | 138 | void CreateAndRegisterWindow(HINSTANCE hInstance) { 139 | WNDCLASSEX wc = {}; 140 | wc.cbSize = sizeof(WNDCLASSEX); 141 | wc.lpfnWndProc = WndProc; 142 | wc.hInstance = hInstance; 143 | wc.lpszClassName = CLASSNAME; 144 | wc.hCursor = LoadCursor(nullptr, IDC_ARROW); // Set the default arrow cursor 145 | wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION); // Load the default application icon 146 | wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 147 | wc.lpszMenuName = nullptr; 148 | wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION); // Load the small icon for the application 149 | 150 | if (!RegisterClassEx(&wc)) { 151 | MessageBox(nullptr, L"Window Registration Failed", L"Error", 152 | MB_ICONEXCLAMATION | MB_OK); 153 | } 154 | 155 | hwnd = CreateWindowEx( 156 | WS_EX_CLIENTEDGE, 157 | CLASSNAME, 158 | L"Foo", 159 | WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, // non-resizable 160 | CW_USEDEFAULT, CW_USEDEFAULT, win_width, win_height, 161 | nullptr, nullptr, hInstance, nullptr); 162 | 163 | if (hwnd == nullptr) { 164 | MessageBox(nullptr, L"Window Creation Failed", L"Error", 165 | MB_ICONEXCLAMATION | MB_OK); 166 | } 167 | 168 | InitializeOffScreenDC(hwnd); 169 | 170 | ShowWindow(hwnd, SW_SHOWDEFAULT); // or use WS_VISIBLE but more control with this option 171 | UpdateWindow(hwnd); 172 | } 173 | 174 | void DoSomeWork() { 175 | /* not doing anything atm */ 176 | } 177 | 178 | int main(int argc, char** argv) { 179 | HINSTANCE hInstance = GetModuleHandle(NULL); 180 | CreateAndRegisterWindow(hInstance); 181 | MSG msg; 182 | while (1) { 183 | while(PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != 0) { 184 | TranslateMessage(&msg); 185 | DispatchMessage(&msg); 186 | if (msg.message == WM_QUIT) { 187 | break; 188 | } 189 | } 190 | if (msg.message == WM_QUIT) 191 | break; 192 | DoSomeWork(); 193 | } 194 | return 0; 195 | } 196 | --------------------------------------------------------------------------------