├── README.md ├── basic ├── basic.c ├── basic_a64.png ├── basic_b64.png ├── basic_c64.png ├── basic_final.png ├── basic_varyingn.png ├── basic_varyingsampling.png ├── beerlambert.c ├── beerlambert.png ├── beerlambert_color.c ├── beerlambert_color.png ├── beerlambert_montage.png ├── box.png ├── box.tex ├── capsule.png ├── capsule.tex ├── csg.c ├── csg.png ├── csg_final.png ├── csg_intersect.png ├── csg_ops.png ├── csg_scene.png ├── csg_subtract1.png ├── csg_subtract2.png ├── csg_union.png ├── fresnel.c ├── fresnel_box.png ├── fresnel_concavelens.png ├── fresnel_convexlens.png ├── fresnel_montage.png ├── fresnel_semicircular.png ├── heart.c ├── heart.png ├── linesegmentpointdistance.png ├── linesegmentpointdistance.tex ├── m.c ├── m.png ├── m2.c ├── m2.png ├── makefile ├── reflection.c ├── reflection_box.png ├── reflection_boxgradient.png ├── reflection_boxscene.png ├── reflection_concavemirror.png ├── refraction.c ├── refraction_box.png ├── refraction_concavelens.png ├── refraction_convexlens.png ├── refraction_montage.png ├── refraction_semicircular.png ├── shapes.c ├── shapes.png ├── shapes_box.png ├── shapes_capsule.png ├── shapes_circle.png ├── shapes_plane.png ├── shapes_roundedbox.png ├── shapes_roundedtriangle.png ├── shapes_semicircle.png ├── shapes_triangle.png ├── snellslaw.png ├── snellslaw.tex ├── svpng.inc ├── vector_fresnel.png ├── vector_fresnel.tex ├── vector_reflect.png ├── vector_reflect.tex ├── vector_refract.png └── vector_refract.tex /README.md: -------------------------------------------------------------------------------- 1 | # light2d 2 | This project illustrates light rendering in 2D with C. 3 | 4 | All samples output PNGs with [svpng](https://github.com/miloyip/svpng). 5 | 6 | License: public domain. 7 | 8 | # Basic 9 | 10 | Use Monte Carol integration and ray marching of signed distance field (SDF) to render a emissive circle. 11 | 12 | Source code: [basic.c](basic.c) 13 | 14 | Uniform sampling (64 samples per pixel): 15 | 16 | ![ ](basic_a64.png) 17 | 18 | Uniform sampling with different number of samples per pixel: 19 | 20 | ![ ](basic_varyingn.png) 21 | 22 | Stratified sampling (64 samples per pixel): 23 | 24 | ![ ](basic_b64.png) 25 | 26 | Jittered sampling (64 samples per pixel): 27 | 28 | ![ ](basic_c64.png) 29 | 30 | Various sampling method side-by-side comparison (64 samples per pixel):: 31 | 32 | ![ ](basic_varyingsampling.png) 33 | 34 | # Constructive Solid Geometry 35 | 36 | Source code: [csg.c](csg.c) 37 | 38 | Use union operation for creating multiple shapes: 39 | 40 | ![ ](csg_scene.png) 41 | 42 | Various CSG operations on two circles: 43 | 44 | ![ ](csg_ops.png) 45 | 46 | # Shapes 47 | 48 | Source code: [shapes.c](shapes.c) 49 | 50 | Examples of various shapes defined by SDF: 51 | 52 | ![ ](shapes.png) 53 | 54 | ![ ](m.png) 55 | 56 | # Reflection 57 | 58 | Source code: [reflection.c](reflection.c) 59 | 60 | ![ ](vector_reflect.png) 61 | 62 | Test scene with two boxes: 63 | 64 | ![ ](reflection_boxscene.png) 65 | 66 | Visualization of SDF gradient, which is approximated by central difference: 67 | 68 | ![ ](reflection_boxgradient.png) 69 | 70 | Reflection via recursive tracing: 71 | 72 | ![ ](reflection_box.png) 73 | 74 | Concave mirror scene generates caustics effect: 75 | 76 | ![ ](reflection_concavemirror.png) 77 | 78 | # Refraction 79 | 80 | Source code: [refraction.c](refraction.c) 81 | 82 | Applying Snell's law to compute refraction direction. Total internal reflection is also handled. 83 | 84 | ![ ](vector_refract.png) 85 | 86 | Test scenes: 87 | 88 | ![ ](refraction_box.png) 89 | 90 | ![ ](refraction_concavelens.png) 91 | 92 | ![ ](refraction_convexlens.png) 93 | 94 | ![ ](refraction_semicircular.png) 95 | 96 | ![ ](m2.png) 97 | 98 | # Fresnel Reflectance 99 | 100 | Source code: [fresnel.c](fresnel.c) 101 | 102 | Applying Fresnel equation to compute reflectance of dielectric medium. 103 | 104 | ![ ](vector_fresnel.png) 105 | 106 | Without Fresnel: 107 | 108 | ![ ](refraction_montage.png) 109 | 110 | With Fresnel term: 111 | 112 | ![ ](fresnel_montage.png) 113 | 114 | # Beer-Lambert 115 | 116 | Source code: [beerlambert.c](beerlambert.c) [beerlambert_color.c](beerlambert_color.c) 117 | 118 | Applying Beer-Lambert law to simulate absorption of light in medimum. 119 | 120 | ![ ](beerlambert_montage.png) 121 | 122 | ![ ](beerlambert_color.png) 123 | 124 | ![ ](heart.png) 125 | -------------------------------------------------------------------------------- /basic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/basic -------------------------------------------------------------------------------- /basic.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fminf(), sinf(), cosf() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 512 7 | #define H 512 8 | #define N 64 9 | #define MAX_STEP 10 10 | #define MAX_DISTANCE 2.0f 11 | #define EPSILON 1e-6f 12 | 13 | unsigned char img[W * H * 3]; 14 | 15 | float circleSDF(float x, float y, float cx, float cy, float r) { 16 | float ux = x - cx, uy = y - cy; 17 | return sqrtf(ux * ux + uy * uy) - r; 18 | } 19 | 20 | float trace(float ox, float oy, float dx, float dy) { 21 | float t = 0.0f; 22 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 23 | float sd = circleSDF(ox + dx * t, oy + dy * t, 0.5f, 0.5f, 0.1f); 24 | if (sd < EPSILON) 25 | return 2.0f; 26 | t += sd; 27 | } 28 | return 0.0f; 29 | } 30 | 31 | float sample(float x, float y) { 32 | float sum = 0.0f; 33 | for (int i = 0; i < N; i++) { 34 | // float a = TWO_PI * rand() / RAND_MAX; 35 | // float a = TWO_PI * i / N; 36 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 37 | sum += trace(x, y, cosf(a), sinf(a)); 38 | } 39 | return sum / N; 40 | } 41 | 42 | int main() { 43 | unsigned char* p = img; 44 | for (int y = 0; y < H; y++) 45 | for (int x = 0; x < W; x++, p += 3) 46 | p[0] = p[1] = p[2] = (int)(fminf(sample((float)x / W, (float)y / H) * 255.0f, 255.0f)); 47 | svpng(fopen("basic.png", "wb"), W, H, img, 0); 48 | } 49 | -------------------------------------------------------------------------------- /basic_a64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/basic_a64.png -------------------------------------------------------------------------------- /basic_b64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/basic_b64.png -------------------------------------------------------------------------------- /basic_c64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/basic_c64.png -------------------------------------------------------------------------------- /basic_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/basic_final.png -------------------------------------------------------------------------------- /basic_varyingn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/basic_varyingn.png -------------------------------------------------------------------------------- /basic_varyingsampling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/basic_varyingsampling.png -------------------------------------------------------------------------------- /beerlambert.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fabsf(), fminf(), fmaxf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 512 7 | #define H 512 8 | #define N 256 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 5.0f 11 | #define EPSILON 1e-6f 12 | #define BIAS 1e-4f 13 | #define MAX_DEPTH 3 14 | 15 | typedef struct { float sd, emissive, reflectivity, eta, absorption; } Result; 16 | 17 | unsigned char img[W * H * 3]; 18 | 19 | float circleSDF(float x, float y, float cx, float cy, float r) { 20 | float ux = x - cx, uy = y - cy; 21 | return sqrtf(ux * ux + uy * uy) - r; 22 | } 23 | 24 | float boxSDF(float x, float y, float cx, float cy, float theta, float sx, float sy) { 25 | float costheta = cosf(theta), sintheta = sinf(theta); 26 | float dx = fabs((x - cx) * costheta + (y - cy) * sintheta) - sx; 27 | float dy = fabs((y - cy) * costheta - (x - cx) * sintheta) - sy; 28 | float ax = fmaxf(dx, 0.0f), ay = fmaxf(dy, 0.0f); 29 | return fminf(fmaxf(dx, dy), 0.0f) + sqrtf(ax * ax + ay * ay); 30 | } 31 | 32 | Result unionOp(Result a, Result b) { 33 | return a.sd < b.sd ? a : b; 34 | } 35 | 36 | Result scene(float x, float y) { 37 | Result a = { circleSDF(x, y, -0.2f, -0.2f, 0.1f), 10.0f, 0.0f, 0.0f, 0.0f }; 38 | Result b = { boxSDF(x, y, 0.5f, 0.5f, 0.0f, 0.3, 0.2f), 0.0f, 0.2f, 1.5f, 4.0f }; 39 | return unionOp(a, b); 40 | } 41 | 42 | void gradient(float x, float y, float* nx, float* ny) { 43 | *nx = (scene(x + EPSILON, y).sd - scene(x - EPSILON, y).sd) * (0.5f / EPSILON); 44 | *ny = (scene(x, y + EPSILON).sd - scene(x, y - EPSILON).sd) * (0.5f / EPSILON); 45 | } 46 | 47 | void reflect(float ix, float iy, float nx, float ny, float* rx, float* ry) { 48 | float idotn2 = (ix * nx + iy * ny) * 2.0f; 49 | *rx = ix - idotn2 * nx; 50 | *ry = iy - idotn2 * ny; 51 | } 52 | 53 | int refract(float ix, float iy, float nx, float ny, float eta, float* rx, float* ry) { 54 | float idotn = ix * nx + iy * ny; 55 | float k = 1.0f - eta * eta * (1.0f - idotn * idotn); 56 | if (k < 0.0f) 57 | return 0; // Total internal reflection 58 | float a = eta * idotn + sqrtf(k); 59 | *rx = eta * ix - a * nx; 60 | *ry = eta * iy - a * ny; 61 | return 1; 62 | } 63 | 64 | float fresnel(float cosi, float cost, float etai, float etat) { 65 | float rs = (etat * cosi - etai * cost) / (etat * cosi + etai * cost); 66 | float rp = (etai * cosi - etat * cost) / (etai * cosi + etat * cost); 67 | return (rs * rs + rp * rp) * 0.5f; 68 | } 69 | 70 | float beerLambert(float a, float d) { 71 | return expf(-a * d); 72 | } 73 | 74 | float trace(float ox, float oy, float dx, float dy, int depth) { 75 | float t = 1e-3f; 76 | float sign = scene(ox, oy).sd > 0.0f ? 1.0f : -1.0f; 77 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 78 | float x = ox + dx * t, y = oy + dy * t; 79 | Result r = scene(x, y); 80 | if (r.sd * sign < EPSILON) { 81 | float sum = r.emissive; 82 | if (depth < MAX_DEPTH && (r.reflectivity > 0.0f || r.eta > 0.0f)) { 83 | float nx, ny, rx, ry, refl = r.reflectivity; 84 | gradient(x, y, &nx, &ny); 85 | float s = 1.0f / (nx * nx + ny * ny); 86 | nx *= sign * s; 87 | ny *= sign * s; 88 | if (r.eta > 0.0f) { 89 | if (refract(dx, dy, nx, ny, sign < 0.0f ? r.eta : 1.0f / r.eta, &rx, &ry)) { 90 | float cosi = -(dx * nx + dy * ny); 91 | float cost = -(rx * nx + ry * ny); 92 | refl = sign < 0.0f ? fresnel(cosi, cost, r.eta, 1.0f) : fresnel(cosi, cost, 1.0f, r.eta); 93 | sum += (1.0f - refl) * trace(x - nx * BIAS, y - ny * BIAS, rx, ry, depth + 1); 94 | } 95 | else 96 | refl = 1.0f; // Total internal reflection 97 | } 98 | if (refl > 0.0f) { 99 | reflect(dx, dy, nx, ny, &rx, &ry); 100 | sum += refl * trace(x + nx * BIAS, y + ny * BIAS, rx, ry, depth + 1); 101 | } 102 | } 103 | return sum * beerLambert(r.absorption, t); 104 | } 105 | t += r.sd * sign; 106 | } 107 | return 0.0f; 108 | } 109 | 110 | float sample(float x, float y) { 111 | float sum = 0.0f; 112 | for (int i = 0; i < N; i++) { 113 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 114 | sum += trace(x, y, cosf(a), sinf(a), 0); 115 | } 116 | return sum / N; 117 | } 118 | 119 | int main() { 120 | unsigned char* p = img; 121 | for (int y = 0; y < H; y++) 122 | for (int x = 0; x < W; x++, p += 3) 123 | p[0] = p[1] = p[2] = (int)(fminf(sample((float)x / W, (float)y / H) * 255.0f, 255.0f)); 124 | svpng(fopen("beerlambert.png", "wb"), W, H, img, 0); 125 | } 126 | -------------------------------------------------------------------------------- /beerlambert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/beerlambert.png -------------------------------------------------------------------------------- /beerlambert_color.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fabsf(), fminf(), fmaxf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 512 7 | #define H 512 8 | #define N 256 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 5.0f 11 | #define EPSILON 1e-6f 12 | #define BIAS 1e-4f 13 | #define MAX_DEPTH 5 14 | #define BLACK { 0.0f, 0.0f, 0.0f } 15 | 16 | typedef struct { float r, g, b; } Color; 17 | typedef struct { float sd, reflectivity, eta; Color emissive, absorption; } Result; 18 | 19 | unsigned char img[W * H * 3]; 20 | 21 | Color colorAdd(Color a, Color b) { 22 | Color c = { a.r + b.r, a.g + b.g, a.b + b.b }; 23 | return c; 24 | } 25 | 26 | Color colorMultiply(Color a, Color b) { 27 | Color c = { a.r * b.r, a.g * b.g, a.b * b.b }; 28 | return c; 29 | } 30 | 31 | Color colorScale(Color a, float s) { 32 | Color c = { a.r * s, a.g * s, a.b * s }; 33 | return c; 34 | } 35 | 36 | float circleSDF(float x, float y, float cx, float cy, float r) { 37 | float ux = x - cx, uy = y - cy; 38 | return sqrtf(ux * ux + uy * uy) - r; 39 | } 40 | 41 | float planeSDF(float x, float y, float px, float py, float nx, float ny) { 42 | return (x - px) * nx + (y - py) * ny; 43 | } 44 | 45 | float ngonSDF(float x, float y, float cx, float cy, float r, float n) { 46 | float ux = x - cx, uy = y - cy, a = TWO_PI / n; 47 | float t = fmodf(atan2f(uy, ux) + TWO_PI, a), s = sqrtf(ux * ux + uy * uy); 48 | return planeSDF(s * cosf(t), s * sinf(t), r, 0.0f, cosf(a * 0.5f), sinf(a * 0.5f)); 49 | } 50 | 51 | Result unionOp(Result a, Result b) { 52 | return a.sd < b.sd ? a : b; 53 | } 54 | 55 | Result scene(float x, float y) { 56 | Result a = { circleSDF(x, y, 0.5f, -0.2f, 0.1f), 0.0f, 0.0f, { 10.0f, 10.0f, 10.0f }, BLACK }; 57 | Result b = { ngonSDF(x, y, 0.5f, 0.5f, 0.25f, 5.0f), 0.0f, 1.5f, BLACK, { 4.0f, 4.0f, 1.0f} }; 58 | return unionOp(a, b); 59 | } 60 | 61 | void gradient(float x, float y, float* nx, float* ny) { 62 | *nx = (scene(x + EPSILON, y).sd - scene(x - EPSILON, y).sd) * (0.5f / EPSILON); 63 | *ny = (scene(x, y + EPSILON).sd - scene(x, y - EPSILON).sd) * (0.5f / EPSILON); 64 | } 65 | 66 | void reflect(float ix, float iy, float nx, float ny, float* rx, float* ry) { 67 | float idotn2 = (ix * nx + iy * ny) * 2.0f; 68 | *rx = ix - idotn2 * nx; 69 | *ry = iy - idotn2 * ny; 70 | } 71 | 72 | int refract(float ix, float iy, float nx, float ny, float eta, float* rx, float* ry) { 73 | float idotn = ix * nx + iy * ny; 74 | float k = 1.0f - eta * eta * (1.0f - idotn * idotn); 75 | if (k < 0.0f) 76 | return 0; // Total internal reflection 77 | float a = eta * idotn + sqrtf(k); 78 | *rx = eta * ix - a * nx; 79 | *ry = eta * iy - a * ny; 80 | return 1; 81 | } 82 | 83 | float fresnel(float cosi, float cost, float etai, float etat) { 84 | float rs = (etat * cosi - etai * cost) / (etat * cosi + etai * cost); 85 | float rp = (etai * cosi - etat * cost) / (etai * cosi + etat * cost); 86 | return (rs * rs + rp * rp) * 0.5f; 87 | } 88 | 89 | Color beerLambert(Color a, float d) { 90 | Color c = { expf(-a.r * d), expf(-a.g * d), expf(-a.b * d) }; 91 | return c; 92 | } 93 | 94 | Color trace(float ox, float oy, float dx, float dy, int depth) { 95 | float t = 1e-3f; 96 | float sign = scene(ox, oy).sd > 0.0f ? 1.0f : -1.0f; 97 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 98 | float x = ox + dx * t, y = oy + dy * t; 99 | Result r = scene(x, y); 100 | if (r.sd * sign < EPSILON) { 101 | Color sum = r.emissive; 102 | if (depth < MAX_DEPTH && r.eta > 0.0f) { 103 | float nx, ny, rx, ry, refl = r.reflectivity; 104 | gradient(x, y, &nx, &ny); 105 | float s = 1.0f / (nx * nx + ny * ny); 106 | nx *= sign * s; 107 | ny *= sign * s; 108 | if (r.eta > 0.0f) { 109 | if (refract(dx, dy, nx, ny, sign < 0.0f ? r.eta : 1.0f / r.eta, &rx, &ry)) { 110 | float cosi = -(dx * nx + dy * ny); 111 | float cost = -(rx * nx + ry * ny); 112 | refl = sign < 0.0f ? fresnel(cosi, cost, r.eta, 1.0f) : fresnel(cosi, cost, 1.0f, r.eta); 113 | refl = fmaxf(fminf(refl, 1.0f), 0.0f); 114 | sum = colorAdd(sum, colorScale(trace(x - nx * BIAS, y - ny * BIAS, rx, ry, depth + 1), 1.0f - refl)); 115 | } 116 | else 117 | refl = 1.0f; // Total internal reflection 118 | } 119 | if (refl > 0.0f) { 120 | reflect(dx, dy, nx, ny, &rx, &ry); 121 | sum = colorAdd(sum, colorScale(trace(x + nx * BIAS, y + ny * BIAS, rx, ry, depth + 1), refl)); 122 | } 123 | } 124 | return colorMultiply(sum, beerLambert(r.absorption, t)); 125 | } 126 | t += r.sd * sign; 127 | } 128 | Color black = BLACK; 129 | return black; 130 | } 131 | 132 | Color sample(float x, float y) { 133 | Color sum = BLACK; 134 | for (int i = 0; i < N; i++) { 135 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 136 | sum = colorAdd(sum, trace(x, y, cosf(a), sinf(a), 0)); 137 | } 138 | return colorScale(sum, 1.0f / N); 139 | } 140 | 141 | int main() { 142 | unsigned char* p = img; 143 | for (int y = 0; y < H; y++) 144 | for (int x = 0; x < W; x++, p += 3) { 145 | Color c = sample((float)x / W, (float)y / H); 146 | p[0] = (int)(fminf(c.r * 255.0f, 255.0f)); 147 | p[1] = (int)(fminf(c.g * 255.0f, 255.0f)); 148 | p[2] = (int)(fminf(c.b * 255.0f, 255.0f)); 149 | } 150 | svpng(fopen("beerlambert_color.png", "wb"), W, H, img, 0); 151 | } 152 | -------------------------------------------------------------------------------- /beerlambert_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/beerlambert_color.png -------------------------------------------------------------------------------- /beerlambert_montage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/beerlambert_montage.png -------------------------------------------------------------------------------- /box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/box.png -------------------------------------------------------------------------------- /box.tex: -------------------------------------------------------------------------------- 1 | \documentclass[tikz]{standalone} 2 | \usetikzlibrary{arrows,angles,quotes,calc} 3 | \begin{document} 4 | \begin{tikzpicture}[>=triangle 45,line width=1pt,scale=1.0,font=\fontsize{10pt}{0}] 5 | \def\W{3}; 6 | \def\H{2}; 7 | \begin{scope}[rotate=-20] 8 | \draw (-\W, -\H) -- (\W, -\H) -- (\W, \H) -- (-\W, \H) -- (-\W, -\H); 9 | \filldraw (0, 0) node[anchor=south east] {$\mathbf{c}$} circle (2pt); 10 | \draw [->] (0,0) -- node[anchor=south west] {$\mathbf{s}$} (\W, -\H); 11 | \draw [dashed] (0, 0) -- (\W, 0); 12 | \draw (0,0) -- (right:1) arc (0:20:1) node [anchor=north west] {$\theta$}; 13 | \end{scope} 14 | \draw [dashed] (0, 0) -- (4, 0); 15 | \end{tikzpicture} 16 | \end{document} 17 | -------------------------------------------------------------------------------- /capsule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/capsule.png -------------------------------------------------------------------------------- /capsule.tex: -------------------------------------------------------------------------------- 1 | \documentclass[tikz]{standalone} 2 | \usetikzlibrary{arrows,angles,quotes,calc} 3 | \begin{document} 4 | \begin{tikzpicture}[>=triangle 45,line width=1pt,scale=1,font=\fontsize{10pt}{0}] 5 | \coordinate (C) at (0, 0); 6 | \def\R{1.5}; 7 | \def\RY{0.75}; 8 | \def\H{2.5}; 9 | 10 | \begin{scope}[xshift=-6cm, rotate=-30] 11 | \draw (0,0) + (\R, 0) 12 | % arc[start angle=180, end angle=360, x radius=\R, y radius=\RY] 13 | arc[start angle=360, end angle=180, radius=\R]; 14 | \draw (0,0) + (\R, \H) 15 | % arc[start angle=180, end angle=360, x radius=\R, y radius=\RY] 16 | arc[start angle=0, end angle=180, radius=\R]; 17 | 18 | \draw (0,0) +(\R, 0) -- +(\R, \H); 19 | \draw (0,0) +(-\R, 0) -- +(-\R, \H); 20 | 21 | \filldraw (0,0) node[anchor=south east] {$\mathbf{a}$} circle (2pt); 22 | \filldraw (0,0) + (0, \H) node[anchor=south east] {$\mathbf{b}$} circle (2pt); 23 | 24 | \draw [->] (0,0) -- node[anchor=east] {$\mathbf{u}$} +(0, \H); 25 | 26 | \draw [stealth-stealth] (0,0) -- node[anchor=south] {$r$} (\R, 0); 27 | \draw [stealth-stealth] (0,0) ++(0, \H) -- node[anchor=south] {$r$} ++(45:\R); 28 | 29 | \end{scope} 30 | 31 | \end{tikzpicture} 32 | \end{document} 33 | -------------------------------------------------------------------------------- /csg.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fminf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 512 7 | #define H 512 8 | #define N 64 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 2.0f 11 | #define EPSILON 1e-6f 12 | 13 | typedef struct { float sd, emissive; } Result; 14 | 15 | unsigned char img[W * H * 3]; 16 | 17 | float circleSDF(float x, float y, float cx, float cy, float r) { 18 | float ux = x - cx, uy = y - cy; 19 | return sqrtf(ux * ux + uy * uy) - r; 20 | } 21 | 22 | Result unionOp(Result a, Result b) { 23 | return a.sd < b.sd ? a : b; 24 | } 25 | 26 | Result intersectOp(Result a, Result b) { 27 | Result r = a.sd > b.sd ? b : a; 28 | r.sd = a.sd > b.sd ? a.sd : b.sd; 29 | return r; 30 | } 31 | 32 | Result subtractOp(Result a, Result b) { 33 | Result r = a; 34 | r.sd = (a.sd > -b.sd) ? a.sd : -b.sd; 35 | return r; 36 | } 37 | 38 | Result complementOp(Result a) { 39 | a.sd = -a.sd; 40 | return a; 41 | } 42 | 43 | Result scene(float x, float y) { 44 | #if 0 45 | Result r1 = { circleSDF(x, y, 0.3f, 0.3f, 0.10f), 2.0f }; 46 | Result r2 = { circleSDF(x, y, 0.3f, 0.7f, 0.05f), 0.8f }; 47 | Result r3 = { circleSDF(x, y, 0.7f, 0.5f, 0.10f), 0.0f }; 48 | return unionOp(unionOp(r1, r2), r3); 49 | #else 50 | Result a = { circleSDF(x, y, 0.4f, 0.5f, 0.20f), 1.0f }; 51 | Result b = { circleSDF(x, y, 0.6f, 0.5f, 0.20f), 0.8f }; 52 | return unionOp(a, b); 53 | // return intersectOp(a, b); 54 | // return subtractOp(a, b); 55 | // return subtractOp(b, a); 56 | #endif 57 | } 58 | 59 | float trace(float ox, float oy, float dx, float dy) { 60 | float t = 0.001f; 61 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 62 | Result r = scene(ox + dx * t, oy + dy * t); 63 | if (r.sd < EPSILON) 64 | return r.emissive; 65 | t += r.sd; 66 | } 67 | return 0.0f; 68 | } 69 | 70 | float sample(float x, float y) { 71 | float sum = 0.0f; 72 | for (int i = 0; i < N; i++) { 73 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 74 | sum += trace(x, y, cosf(a), sinf(a)); 75 | } 76 | return sum / N; 77 | } 78 | 79 | int main() { 80 | unsigned char* p = img; 81 | for (int y = 0; y < H; y++) 82 | for (int x = 0; x < W; x++, p += 3) 83 | p[0] = p[1] = p[2] = (int)(fminf(sample((float)x / W, (float)y / H) * 255.0f, 255.0f)); 84 | svpng(fopen("csg.png", "wb"), W, H, img, 0); 85 | } 86 | -------------------------------------------------------------------------------- /csg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/csg.png -------------------------------------------------------------------------------- /csg_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/csg_final.png -------------------------------------------------------------------------------- /csg_intersect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/csg_intersect.png -------------------------------------------------------------------------------- /csg_ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/csg_ops.png -------------------------------------------------------------------------------- /csg_scene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/csg_scene.png -------------------------------------------------------------------------------- /csg_subtract1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/csg_subtract1.png -------------------------------------------------------------------------------- /csg_subtract2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/csg_subtract2.png -------------------------------------------------------------------------------- /csg_union.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/csg_union.png -------------------------------------------------------------------------------- /fresnel.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fabsf(), fminf(), fmaxf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 512 7 | #define H 512 8 | #define N 256 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 5.0f 11 | #define EPSILON 1e-6f 12 | #define BIAS 1e-4f 13 | #define MAX_DEPTH 3 14 | 15 | typedef struct { float sd, emissive, reflectivity, eta; } Result; 16 | 17 | unsigned char img[W * H * 3]; 18 | 19 | float circleSDF(float x, float y, float cx, float cy, float r) { 20 | float ux = x - cx, uy = y - cy; 21 | return sqrtf(ux * ux + uy * uy) - r; 22 | } 23 | 24 | float boxSDF(float x, float y, float cx, float cy, float theta, float sx, float sy) { 25 | float costheta = cosf(theta), sintheta = sinf(theta); 26 | float dx = fabs((x - cx) * costheta + (y - cy) * sintheta) - sx; 27 | float dy = fabs((y - cy) * costheta - (x - cx) * sintheta) - sy; 28 | float ax = fmaxf(dx, 0.0f), ay = fmaxf(dy, 0.0f); 29 | return fminf(fmaxf(dx, dy), 0.0f) + sqrtf(ax * ax + ay * ay); 30 | } 31 | 32 | float planeSDF(float x, float y, float px, float py, float nx, float ny) { 33 | return (x - px) * nx + (y - py) * ny; 34 | } 35 | 36 | Result unionOp(Result a, Result b) { 37 | return a.sd < b.sd ? a : b; 38 | } 39 | 40 | Result intersectOp(Result a, Result b) { 41 | return a.sd > b.sd ? a : b; 42 | } 43 | 44 | Result subtractOp(Result a, Result b) { 45 | Result r = a; 46 | r.sd = (a.sd > -b.sd) ? a.sd : -b.sd; 47 | return r; 48 | } 49 | 50 | Result scene(float x, float y) { 51 | Result a = { circleSDF(x, y, -0.2f, -0.2f, 0.1f), 10.0f, 0.0f, 0.0f }; 52 | Result b = { boxSDF(x, y, 0.5f, 0.5f, 0.0f, 0.3, 0.2f), 0.0f, 0.2f, 1.5f }; 53 | Result c = { circleSDF(x, y, 0.5f, -0.5f, 0.05f), 20.0f, 0.0f, 0.0f }; 54 | Result d = { circleSDF(x, y, 0.5f, 0.2f, 0.35f), 0.0f, 0.2f, 1.5f }; 55 | Result e = { circleSDF(x, y, 0.5f, 0.8f, 0.35f), 0.0f, 0.2f, 1.5f }; 56 | Result f = { boxSDF(x, y, 0.5f, 0.5f, 0.0f, 0.2, 0.1f), 0.0f, 0.2f, 1.5f }; 57 | Result g = { circleSDF(x, y, 0.5f, 0.12f, 0.35f), 0.0f, 0.2f, 1.5f }; 58 | Result h = { circleSDF(x, y, 0.5f, 0.87f, 0.35f), 0.0f, 0.2f, 1.5f }; 59 | Result i = { circleSDF(x, y, 0.5f, 0.5f, 0.2f), 0.0f, 0.2f, 1.5f }; 60 | Result j = { planeSDF(x, y, 0.5f, 0.5f, 0.0f, -1.0f), 0.0f, 0.2f, 1.5f }; 61 | return unionOp(a, b); 62 | // return unionOp(c, intersectOp(d, e)); 63 | // return unionOp(c, subtractOp(f, unionOp(g, h))); 64 | // return unionOp(c, intersectOp(i, j)); 65 | } 66 | 67 | void gradient(float x, float y, float* nx, float* ny) { 68 | *nx = (scene(x + EPSILON, y).sd - scene(x - EPSILON, y).sd) * (0.5f / EPSILON); 69 | *ny = (scene(x, y + EPSILON).sd - scene(x, y - EPSILON).sd) * (0.5f / EPSILON); 70 | } 71 | 72 | void reflect(float ix, float iy, float nx, float ny, float* rx, float* ry) { 73 | float idotn2 = (ix * nx + iy * ny) * 2.0f; 74 | *rx = ix - idotn2 * nx; 75 | *ry = iy - idotn2 * ny; 76 | } 77 | 78 | int refract(float ix, float iy, float nx, float ny, float eta, float* rx, float* ry) { 79 | float idotn = ix * nx + iy * ny; 80 | float k = 1.0f - eta * eta * (1.0f - idotn * idotn); 81 | if (k < 0.0f) 82 | return 0; // Total internal reflection 83 | float a = eta * idotn + sqrtf(k); 84 | *rx = eta * ix - a * nx; 85 | *ry = eta * iy - a * ny; 86 | return 1; 87 | } 88 | 89 | float fresnel(float cosi, float cost, float etai, float etat) { 90 | float rs = (etat * cosi - etai * cost) / (etat * cosi + etai * cost); 91 | float rp = (etai * cosi - etat * cost) / (etai * cosi + etat * cost); 92 | return (rs * rs + rp * rp) * 0.5f; 93 | } 94 | 95 | float schlick(float cosi, float cost, float etai, float etat) { 96 | float r0 = (etai - etat) / (etai + etat); 97 | r0 *= r0; 98 | float a = 1.0f - (etai < etat ? cosi : cost); 99 | float aa = a * a; 100 | return r0 + (1.0f - r0) * aa * aa * a; 101 | } 102 | 103 | float trace(float ox, float oy, float dx, float dy, int depth) { 104 | float t = 1e-3f; 105 | float sign = scene(ox, oy).sd > 0.0f ? 1.0f : -1.0f; 106 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 107 | float x = ox + dx * t, y = oy + dy * t; 108 | Result r = scene(x, y); 109 | if (r.sd * sign < EPSILON) { 110 | float sum = r.emissive; 111 | if (depth < MAX_DEPTH && (r.reflectivity > 0.0f || r.eta > 0.0f)) { 112 | float nx, ny, rx, ry, refl = r.reflectivity; 113 | gradient(x, y, &nx, &ny); 114 | float s = 1.0f / (nx * nx + ny * ny); 115 | nx *= sign * s; 116 | ny *= sign * s; 117 | if (r.eta > 0.0f) { 118 | if (refract(dx, dy, nx, ny, sign < 0.0f ? r.eta : 1.0f / r.eta, &rx, &ry)) { 119 | float cosi = -(dx * nx + dy * ny); 120 | float cost = -(rx * nx + ry * ny); 121 | refl = sign < 0.0f ? fresnel(cosi, cost, r.eta, 1.0f) : fresnel(cosi, cost, 1.0f, r.eta); 122 | // refl = sign < 0.0f ? schlick(cosi, cost, r.eta, 1.0f) : schlick(cosi, cost, 1.0f, r.eta); 123 | sum += (1.0f - refl) * trace(x - nx * BIAS, y - ny * BIAS, rx, ry, depth + 1); 124 | } 125 | else 126 | refl = 1.0f; // Total internal reflection 127 | } 128 | if (refl > 0.0f) { 129 | reflect(dx, dy, nx, ny, &rx, &ry); 130 | sum += refl * trace(x + nx * BIAS, y + ny * BIAS, rx, ry, depth + 1); 131 | } 132 | } 133 | return sum; 134 | } 135 | t += r.sd * sign; 136 | } 137 | return 0.0f; 138 | } 139 | 140 | float sample(float x, float y) { 141 | float sum = 0.0f; 142 | for (int i = 0; i < N; i++) { 143 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 144 | sum += trace(x, y, cosf(a), sinf(a), 0); 145 | } 146 | return sum / N; 147 | } 148 | 149 | #if 0 150 | int main() { 151 | float nx = -1.0f, ny = 0.0f, eta1 = 1.0f, eta2 = 1.5f; 152 | // Air to denser medium 153 | for (int i = 0; i <= 90; i++) { 154 | float t = i * TWO_PI / 360.0f, ix = cosf(t), iy = sinf(t), rx, ry; 155 | refract(ix, iy, nx, ny, eta1 / eta2, &rx, &ry); 156 | float cosi = -(ix * nx + iy * ny); 157 | float cost = -(rx * nx + ry * ny); 158 | printf("%d,%f,%f\n", i, 159 | fresnel(cosi, cost, eta1, eta2), 160 | schlick(cosi, cost, eta1, eta2)); 161 | } 162 | // Denser medium to air 163 | for (int i = 0; i <= 90; i++) { 164 | float t = i * TWO_PI / 360.0f, ix = cosf(t), iy = sinf(t), rx, ry; 165 | int tir = !refract(ix, iy, nx, ny, eta2 / eta1, &rx, &ry); 166 | float cosi = -(ix * nx + iy * ny); 167 | float cost = -(rx * nx + ry * ny); 168 | printf("%d,%f,%f\n", i, 169 | tir ? 1.0f : fresnel(cosi, cost, eta2, eta1), 170 | tir ? 1.0f : schlick(cosi, cost, eta2, eta1)); 171 | } 172 | } 173 | #else 174 | int main() { 175 | unsigned char* p = img; 176 | for (int y = 0; y < H; y++) 177 | for (int x = 0; x < W; x++, p += 3) 178 | p[0] = p[1] = p[2] = (int)(fminf(sample((float)x / W, (float)y / H) * 255.0f, 255.0f)); 179 | svpng(fopen("fresnel.png", "wb"), W, H, img, 0); 180 | } 181 | #endif -------------------------------------------------------------------------------- /fresnel_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/fresnel_box.png -------------------------------------------------------------------------------- /fresnel_concavelens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/fresnel_concavelens.png -------------------------------------------------------------------------------- /fresnel_convexlens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/fresnel_convexlens.png -------------------------------------------------------------------------------- /fresnel_montage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/fresnel_montage.png -------------------------------------------------------------------------------- /fresnel_semicircular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/fresnel_semicircular.png -------------------------------------------------------------------------------- /heart.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fabsf(), fminf(), fmaxf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 1024 7 | #define H 1024 8 | #define N 256 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 5.0f 11 | #define EPSILON 1e-6f 12 | #define BIAS 1e-4f 13 | #define MAX_DEPTH 3 14 | #define BLACK { 0.0f, 0.0f, 0.0f } 15 | 16 | typedef struct { float r, g, b; } Color; 17 | typedef struct { float sd, reflectivity, eta; Color emissive, absorption; } Result; 18 | 19 | unsigned char img[W * H * 3]; 20 | 21 | Color colorAdd(Color a, Color b) { 22 | Color c = { a.r + b.r, a.g + b.g, a.b + b.b }; 23 | return c; 24 | } 25 | 26 | Color colorMultiply(Color a, Color b) { 27 | Color c = { a.r * b.r, a.g * b.g, a.b * b.b }; 28 | return c; 29 | } 30 | 31 | Color colorScale(Color a, float s) { 32 | Color c = { a.r * s, a.g * s, a.b * s }; 33 | return c; 34 | } 35 | 36 | float circleSDF(float x, float y, float cx, float cy, float r) { 37 | float ux = x - cx, uy = y - cy; 38 | return sqrtf(ux * ux + uy * uy) - r; 39 | } 40 | 41 | float planeSDF(float x, float y, float px, float py, float nx, float ny) { 42 | return (x - px) * nx + (y - py) * ny; 43 | } 44 | 45 | float ngonSDF(float x, float y, float cx, float cy, float r, float n) { 46 | float ux = x - cx, uy = y - cy, a = TWO_PI / n; 47 | float t = fmodf(atan2f(uy, ux) + TWO_PI, a), s = sqrtf(ux * ux + uy * uy); 48 | return planeSDF(s * cosf(t), s * sinf(t), r, 0.0f, cosf(a * 0.5f), sinf(a * 0.5f)); 49 | } 50 | 51 | Result unionOp(Result a, Result b) { 52 | return a.sd < b.sd ? a : b; 53 | } 54 | 55 | Result intersectOp(Result a, Result b) { 56 | return a.sd > b.sd ? a : b; 57 | } 58 | 59 | Result scene(float x, float y) { 60 | float u = x - 0.5f, v = y - 0.5f, t = fmodf(atan2f(v, u) + TWO_PI, TWO_PI / 16), s = sqrtf(u * u + v * v); 61 | x = fabsf(x - 0.5f) + 0.5f; 62 | Color m = { 0.0f, 3.0f, 3.0f }; 63 | Result a = { ngonSDF(x, y, 0.7f, 0.35f, 0.2f, 16), 0.0f, 1.77f, BLACK, m }; 64 | Result b = { ngonSDF(x, y, 0.35f, 0.35f, 0.55f, 32), 0.0f, 1.77f, BLACK, m }; 65 | Result c = { planeSDF(x, y, 0.5f, 0.35f, 0.0f, -1.0f), 0.0f, 1.77f, BLACK, m }; 66 | // y = fabsf(y - 0.5f) + 0.5f; 67 | // Result d = { circleSDF(x, y, 1.05f, 1.05f, 0.05f), 0.0f, 0.0f, { 5.0f, 5.0f, 5.0f }, BLACK }; 68 | // Result d = { -circleSDF(x, y, 0.5f, 0.5f, 3.0f), 0.0f, 0.0f, { 0.5f, 0.5f, 0.5f }, BLACK }; 69 | Result d = { circleSDF(s * cosf(t), s * sinf(t), 0.6f * cosf(TWO_PI / 32), 0.5f * sinf(TWO_PI / 32), 0.05f), 0.0f, 0.0f, { 2.0f, 2.0f, 2.0f }, BLACK }; 70 | return unionOp(unionOp(a, intersectOp(b, c)), d); 71 | } 72 | 73 | void gradient(float x, float y, float* nx, float* ny) { 74 | *nx = (scene(x + EPSILON, y).sd - scene(x - EPSILON, y).sd) * (0.5f / EPSILON); 75 | *ny = (scene(x, y + EPSILON).sd - scene(x, y - EPSILON).sd) * (0.5f / EPSILON); 76 | } 77 | 78 | void reflect(float ix, float iy, float nx, float ny, float* rx, float* ry) { 79 | float idotn2 = (ix * nx + iy * ny) * 2.0f; 80 | *rx = ix - idotn2 * nx; 81 | *ry = iy - idotn2 * ny; 82 | } 83 | 84 | int refract(float ix, float iy, float nx, float ny, float eta, float* rx, float* ry) { 85 | float idotn = ix * nx + iy * ny; 86 | float k = 1.0f - eta * eta * (1.0f - idotn * idotn); 87 | if (k < 0.0f) 88 | return 0; // Total internal reflection 89 | float a = eta * idotn + sqrtf(k); 90 | *rx = eta * ix - a * nx; 91 | *ry = eta * iy - a * ny; 92 | return 1; 93 | } 94 | 95 | float fresnel(float cosi, float cost, float etai, float etat) { 96 | float rs = (etat * cosi - etai * cost) / (etat * cosi + etai * cost); 97 | float rp = (etai * cosi - etat * cost) / (etai * cosi + etat * cost); 98 | return (rs * rs + rp * rp) * 0.5f; 99 | } 100 | 101 | Color beerLambert(Color a, float d) { 102 | Color c = { expf(-a.r * d), expf(-a.g * d), expf(-a.b * d) }; 103 | return c; 104 | } 105 | 106 | Color trace(float ox, float oy, float dx, float dy, int depth) { 107 | float t = 1e-3f; 108 | float sign = scene(ox, oy).sd > 0.0f ? 1.0f : -1.0f; 109 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 110 | float x = ox + dx * t, y = oy + dy * t; 111 | Result r = scene(x, y); 112 | if (r.sd * sign < EPSILON) { 113 | Color sum = r.emissive; 114 | if (depth < MAX_DEPTH && r.eta > 0.0f) { 115 | float nx, ny, rx, ry, refl = r.reflectivity; 116 | gradient(x, y, &nx, &ny); 117 | float s = 1.0f / (nx * nx + ny * ny); 118 | nx *= sign * s; 119 | ny *= sign * s; 120 | if (r.eta > 0.0f) { 121 | if (refract(dx, dy, nx, ny, sign < 0.0f ? r.eta : 1.0f / r.eta, &rx, &ry)) { 122 | float cosi = -(dx * nx + dy * ny); 123 | float cost = -(rx * nx + ry * ny); 124 | refl = sign < 0.0f ? fresnel(cosi, cost, r.eta, 1.0f) : fresnel(cosi, cost, 1.0f, r.eta); 125 | refl = fmaxf(fminf(refl, 1.0f), 0.0f); 126 | sum = colorAdd(sum, colorScale(trace(x - nx * BIAS, y - ny * BIAS, rx, ry, depth + 1), 1.0f - refl)); 127 | } 128 | else 129 | refl = 1.0f; // Total internal reflection 130 | } 131 | if (refl > 0.0f) { 132 | reflect(dx, dy, nx, ny, &rx, &ry); 133 | sum = colorAdd(sum, colorScale(trace(x + nx * BIAS, y + ny * BIAS, rx, ry, depth + 1), refl)); 134 | } 135 | } 136 | Color c = colorMultiply(sum, beerLambert(r.absorption, t)); 137 | return c; 138 | } 139 | t += r.sd * sign; 140 | } 141 | Color black = BLACK; 142 | return black; 143 | } 144 | 145 | Color sample(float x, float y) { 146 | Color sum = BLACK; 147 | for (int i = 0; i < N; i++) { 148 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 149 | sum = colorAdd(sum, trace(x, y, cosf(a), sinf(a), 0)); 150 | } 151 | return colorScale(sum, 1.0f / N); 152 | } 153 | 154 | int main() { 155 | unsigned char* p = img; 156 | for (int y = 0; y < H; y++) 157 | for (int x = 0; x < W; x++, p += 3) { 158 | Color c = sample((float)x / W, (float)y / H); 159 | p[0] = (int)(fminf(c.r * 255.0f, 255.0f)); 160 | p[1] = (int)(fminf(c.g * 255.0f, 255.0f)); 161 | p[2] = (int)(fminf(c.b * 255.0f, 255.0f)); 162 | } 163 | svpng(fopen("heart.png", "wb"), W, H, img, 0); 164 | } 165 | -------------------------------------------------------------------------------- /heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/heart.png -------------------------------------------------------------------------------- /linesegmentpointdistance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/linesegmentpointdistance.png -------------------------------------------------------------------------------- /linesegmentpointdistance.tex: -------------------------------------------------------------------------------- 1 | \documentclass[tikz]{standalone} 2 | \usetikzlibrary{arrows,angles,quotes,calc} 3 | \begin{document} 4 | \begin{tikzpicture}[>=triangle 45,line width=1pt,scale=1.0,font=\fontsize{10pt}{0}] 5 | 6 | \begin{scope} 7 | \coordinate (A) at (0,0); 8 | \coordinate (B) at (4,2); 9 | \coordinate (C) at (2,3); 10 | \node[fill,inner sep=2pt,circle,label=135:$\mathbf{a}$] at (A) {}; 11 | \node[fill,inner sep=2pt,circle,label=135:$\mathbf{b}$] at (B) {}; 12 | \node[fill,inner sep=2pt,circle,label=$\mathbf{x}$] at (C) {}; 13 | \draw[->] (A) -- (B) node[midway, anchor=south east] {$\mathbf{u}$}; 14 | \draw[->,dashed] (A) -- (C) node[midway, anchor=south east] {$\mathbf{v}$}; 15 | \draw[dashed] ($(A)!-0.2!(B)$) -- ($(A)!1.2!(B)$); 16 | \draw[dashed, blue](C) -- ($(A)!0.72!(B)$) node[midway, anchor=west] {$d$} node[fill,inner sep=2pt,circle,label=-90:$\mathbf{x}'$] {}; 17 | \end{scope} 18 | 19 | \begin{scope}[xshift=7cm] 20 | \coordinate (A) at (0,0); 21 | \coordinate (B) at (4,2); 22 | \coordinate (C) at (4.5,4); 23 | \node[fill,inner sep=2pt,circle,label=135:$\mathbf{a}$] at (A) {}; 24 | \node[fill,inner sep=2pt,circle,label=135:$\mathbf{b}$] at (B) {}; 25 | \node[fill,inner sep=2pt,circle,label=$\mathbf{x}$] at (C) {}; 26 | \draw[->] (A) -- (B) node[midway, anchor=north west] {$\mathbf{u}$}; 27 | \draw[->,dashed] (A) -- (C) node[midway, anchor=south east] {$\mathbf{v}$}; 28 | \draw[dashed] ($(A)!-0.2!(B)$) -- ($(A)!1.3!(B)$); 29 | \draw[dashed](C) -- ($(A)!1.3!(B)$); 30 | \draw[dashed, blue](C) -- (B) node[midway, anchor=west] {$d$} node[fill,inner sep=2pt,circle,label=-90:$\mathbf{x}'$] {}; 31 | \end{scope} 32 | 33 | \end{tikzpicture} 34 | \end{document} 35 | -------------------------------------------------------------------------------- /m.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fminf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 1024 7 | #define H 1024 8 | #define N 256 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 2.0f 11 | #define EPSILON 1e-6f 12 | 13 | typedef struct { float sd, emissive; } Result; 14 | 15 | unsigned char img[W * H * 3]; 16 | 17 | float capsuleSDF(float x, float y, float ax, float ay, float bx, float by, float r) { 18 | float vx = x - ax, vy = y - ay, ux = bx - ax, uy = by - ay; 19 | float t = fmaxf(fminf((vx * ux + vy * uy) / (ux * ux + uy * uy), 1.0f), 0.0f); 20 | float dx = vx - ux * t, dy = vy - uy * t; 21 | return sqrtf(dx * dx + dy * dy) - r; 22 | } 23 | 24 | Result scene(float x, float y) { 25 | x = fabsf(x - 0.5f) + 0.5f; 26 | Result a = { capsuleSDF(x, y, 0.75f, 0.25f, 0.75f, 0.75f, 0.05f), 1.0f }; 27 | Result b = { capsuleSDF(x, y, 0.75f, 0.25f, 0.50f, 0.75f, 0.05f), 1.0f }; 28 | return a.sd < b.sd ? a : b; 29 | } 30 | 31 | float trace(float ox, float oy, float dx, float dy) { 32 | float t = 0.0f; 33 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 34 | Result r = scene(ox + dx * t, oy + dy * t); 35 | if (r.sd < EPSILON) 36 | return r.emissive; 37 | t += r.sd; 38 | } 39 | return 0.0f; 40 | } 41 | 42 | float sample(float x, float y) { 43 | float sum = 0.0f; 44 | for (int i = 0; i < N; i++) { 45 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 46 | sum += trace(x, y, cosf(a), sinf(a)); 47 | } 48 | return sum / N; 49 | } 50 | 51 | int main() { 52 | unsigned char* p = img; 53 | for (int y = 0; y < H; y++) 54 | for (int x = 0; x < W; x++, p += 3) 55 | p[0] = p[1] = p[2] = (int)(fminf(sample((float)x / W, (float)y / H) * 255.0f, 255.0f)); 56 | svpng(fopen("m.png", "wb"), W, H, img, 0); 57 | } 58 | -------------------------------------------------------------------------------- /m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/m.png -------------------------------------------------------------------------------- /m2.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fabsf(), fminf(), fmaxf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 1024 7 | #define H 1024 8 | #define N 256 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 5.0f 11 | #define EPSILON 1e-6f 12 | #define BIAS 1e-4f 13 | #define MAX_DEPTH 5 14 | 15 | typedef struct { float sd, emissive, reflectivity, eta; } Result; 16 | 17 | unsigned char img[W * H * 3]; 18 | 19 | float circleSDF(float x, float y, float cx, float cy, float r) { 20 | float ux = x - cx, uy = y - cy; 21 | return sqrtf(ux * ux + uy * uy) - r; 22 | } 23 | 24 | float capsuleSDF(float x, float y, float ax, float ay, float bx, float by, float r) { 25 | float vx = x - ax, vy = y - ay, ux = bx - ax, uy = by - ay; 26 | float t = fmaxf(fminf((vx * ux + vy * uy) / (ux * ux + uy * uy), 1.0f), 0.0f); 27 | float dx = vx - ux * t, dy = vy - uy * t; 28 | return sqrtf(dx * dx + dy * dy) - r; 29 | } 30 | 31 | Result unionOp(Result a, Result b) { 32 | return a.sd < b.sd ? a : b; 33 | } 34 | 35 | Result scene(float x, float y) { 36 | x = fabsf(x - 0.5f) + 0.5f; 37 | Result a = { capsuleSDF(x, y, 0.75f, 0.25f, 0.75f, 0.75f, 0.05f), 0.0f, 0.2f, 1.5f }; 38 | Result b = { capsuleSDF(x, y, 0.75f, 0.25f, 0.50f, 0.75f, 0.05f), 0.0f, 0.2f, 1.5f }; 39 | y = fabsf(y - 0.5f) + 0.5f; 40 | Result c = { circleSDF(x, y, 1.05f, 1.05f, 0.05f), 5.0f, 0.0f, 0.0f }; 41 | return unionOp(a, unionOp(b, c)); 42 | } 43 | 44 | void gradient(float x, float y, float* nx, float* ny) { 45 | *nx = (scene(x + EPSILON, y).sd - scene(x - EPSILON, y).sd) * (0.5f / EPSILON); 46 | *ny = (scene(x, y + EPSILON).sd - scene(x, y - EPSILON).sd) * (0.5f / EPSILON); 47 | } 48 | 49 | void reflect(float ix, float iy, float nx, float ny, float* rx, float* ry) { 50 | float idotn2 = (ix * nx + iy * ny) * 2.0f; 51 | *rx = ix - idotn2 * nx; 52 | *ry = iy - idotn2 * ny; 53 | } 54 | 55 | int refract(float ix, float iy, float nx, float ny, float eta, float* rx, float* ry) { 56 | float idotn = ix * nx + iy * ny; 57 | float k = 1.0f - eta * eta * (1.0f - idotn * idotn); 58 | if (k < 0.0f) 59 | return 0; // Total internal reflection 60 | float a = eta * idotn + sqrtf(k); 61 | *rx = eta * ix - a * nx; 62 | *ry = eta * iy - a * ny; 63 | return 1; 64 | } 65 | 66 | float trace(float ox, float oy, float dx, float dy, int depth) { 67 | float t = 1e-3f; 68 | float sign = scene(ox, oy).sd > 0.0f ? 1.0f : -1.0f; 69 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 70 | float x = ox + dx * t, y = oy + dy * t; 71 | Result r = scene(x, y); 72 | if (r.sd * sign < EPSILON) { 73 | float sum = r.emissive; 74 | if (depth < MAX_DEPTH && (r.reflectivity > 0.0f || r.eta > 0.0f)) { 75 | float nx, ny, rx, ry, refl = r.reflectivity; 76 | gradient(x, y, &nx, &ny); 77 | nx *= sign; 78 | ny *= sign; 79 | if (r.eta > 0.0f) { 80 | if (refract(dx, dy, nx, ny, sign < 0.0f ? r.eta : 1.0f / r.eta, &rx, &ry)) 81 | sum += (1.0f - refl) * trace(x - nx * BIAS, y - ny * BIAS, rx, ry, depth + 1); 82 | else 83 | refl = 1.0f; // Total internal reflection 84 | } 85 | if (refl > 0.0f) { 86 | reflect(dx, dy, nx, ny, &rx, &ry); 87 | sum += refl * trace(x + nx * BIAS, y + ny * BIAS, rx, ry, depth + 1); 88 | } 89 | } 90 | return sum; 91 | } 92 | t += r.sd * sign; 93 | } 94 | return 0.0f; 95 | } 96 | 97 | float sample(float x, float y) { 98 | float sum = 0.0f; 99 | for (int i = 0; i < N; i++) { 100 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 101 | sum += trace(x, y, cosf(a), sinf(a), 0); 102 | } 103 | return sum / N; 104 | } 105 | 106 | int main() { 107 | unsigned char* p = img; 108 | for (int y = 0; y < H; y++) 109 | for (int x = 0; x < W; x++, p += 3) 110 | p[0] = p[1] = p[2] = (int)(fminf(sample((float)x / W, (float)y / H) * 255.0f, 255.0f)); 111 | svpng(fopen("m2.png", "wb"), W, H, img, 0); 112 | } 113 | -------------------------------------------------------------------------------- /m2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/m2.png -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | TARGETS=basic csg shapes reflection refraction fresnel beerlambert beerlambert_color heart 2 | OUTPUTS=$(addsuffix .png, $(TARGETS)) 3 | TEXFILES=$(basename $(wildcard *.tex)) 4 | DIAGRAMS=$(addsuffix .png, $(TEXFILES)) 5 | 6 | all: $(TARGETS) 7 | test: $(TARGETS) $(OUTPUTS) 8 | diagram: $(DIAGRAMS) 9 | 10 | %: %.c 11 | gcc -Wall -O3 -o $@ $< 12 | 13 | %.png: % 14 | time ./$< 15 | 16 | %.png: %.tex 17 | xelatex $< 18 | convert -density 150 $(basename $<).pdf $(basename $<).png 19 | rm $(basename $<).aux $(basename $<).log $(basename $<).pdf 20 | 21 | clean: 22 | rm $(TARGETS) *.png 23 | -------------------------------------------------------------------------------- /reflection.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fabsf(), fminf(), fmaxf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 512 7 | #define H 512 8 | #define N 64 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 5.0f 11 | #define EPSILON 1e-6f 12 | #define BIAS 1e-4f 13 | #define MAX_DEPTH 3 14 | 15 | typedef struct { float sd, emissive, reflectivity; } Result; 16 | 17 | unsigned char img[W * H * 3]; 18 | 19 | float circleSDF(float x, float y, float cx, float cy, float r) { 20 | float ux = x - cx, uy = y - cy; 21 | return sqrtf(ux * ux + uy * uy) - r; 22 | } 23 | 24 | float boxSDF(float x, float y, float cx, float cy, float theta, float sx, float sy) { 25 | float costheta = cosf(theta), sintheta = sinf(theta); 26 | float dx = fabs((x - cx) * costheta + (y - cy) * sintheta) - sx; 27 | float dy = fabs((y - cy) * costheta - (x - cx) * sintheta) - sy; 28 | float ax = fmaxf(dx, 0.0f), ay = fmaxf(dy, 0.0f); 29 | return fminf(fmaxf(dx, dy), 0.0f) + sqrtf(ax * ax + ay * ay); 30 | } 31 | 32 | float planeSDF(float x, float y, float px, float py, float nx, float ny) { 33 | return (x - px) * nx + (y - py) * ny; 34 | } 35 | 36 | Result unionOp(Result a, Result b) { 37 | return a.sd < b.sd ? a : b; 38 | } 39 | 40 | Result intersectOp(Result a, Result b) { 41 | return a.sd > b.sd ? a : b; 42 | } 43 | 44 | Result subtractOp(Result a, Result b) { 45 | Result r = a; 46 | r.sd = (a.sd > -b.sd) ? a.sd : -b.sd; 47 | return r; 48 | } 49 | 50 | Result scene(float x, float y) { 51 | Result a = { circleSDF(x, y, 0.4f, 0.2f, 0.1f), 2.0f, 0.0f }; 52 | Result b = { boxSDF(x, y, 0.5f, 0.8f, TWO_PI / 16.0f, 0.1f, 0.1f), 0.0f, 0.9f }; 53 | Result c = { boxSDF(x, y, 0.8f, 0.5f, TWO_PI / 16.0f, 0.1f, 0.1f), 0.0f, 0.9f }; 54 | Result d = { planeSDF(x, y, 0.0f, 0.5f, 0.0f, -1.0f), 0.0f, 0.9f }; 55 | Result e = { circleSDF(x, y, 0.5f, 0.5f, 0.4f), 0.0f, 0.9f }; 56 | // return unionOp(unionOp(a, b), c); 57 | return unionOp(a, subtractOp(d, e)); 58 | } 59 | 60 | void gradient(float x, float y, float* nx, float* ny) { 61 | *nx = (scene(x + EPSILON, y).sd - scene(x - EPSILON, y).sd) * (0.5f / EPSILON); 62 | *ny = (scene(x, y + EPSILON).sd - scene(x, y - EPSILON).sd) * (0.5f / EPSILON); 63 | } 64 | 65 | void reflect(float ix, float iy, float nx, float ny, float* rx, float* ry) { 66 | float idotn2 = (ix * nx + iy * ny) * 2.0f; 67 | *rx = ix - idotn2 * nx; 68 | *ry = iy - idotn2 * ny; 69 | } 70 | 71 | float trace(float ox, float oy, float dx, float dy, int depth) { 72 | float t = 0.0f; 73 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 74 | float x = ox + dx * t, y = oy + dy * t; 75 | Result r = scene(x, y); 76 | if (r.sd < EPSILON) { 77 | float sum = r.emissive; 78 | if (depth < MAX_DEPTH && r.reflectivity > 0.0f) { 79 | float nx, ny, rx, ry; 80 | gradient(x, y, &nx, &ny); 81 | reflect(dx, dy, nx, ny, &rx, &ry); 82 | sum += r.reflectivity * trace(x + nx * BIAS, y + ny * BIAS, rx, ry, depth + 1); 83 | } 84 | return sum; 85 | } 86 | t += r.sd; 87 | } 88 | return 0.0f; 89 | } 90 | 91 | float sample(float x, float y) { 92 | float sum = 0.0f; 93 | for (int i = 0; i < N; i++) { 94 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 95 | sum += trace(x, y, cosf(a), sinf(a), 0); 96 | } 97 | return sum / N; 98 | } 99 | 100 | int main() { 101 | unsigned char* p = img; 102 | for (int y = 0; y < H; y++) 103 | for (int x = 0; x < W; x++, p += 3) { 104 | // float nx, ny; 105 | // gradient((float)x / W, (float)y / H, &nx, &ny); 106 | // p[0] = (int)((fmaxf(fminf(nx, 1.0f), -1.0f) * 0.5f + 0.5f) * 255.0f); 107 | // p[1] = (int)((fmaxf(fminf(ny, 1.0f), -1.0f) * 0.5f + 0.5f) * 255.0f); 108 | // p[2] = 0; 109 | p[0] = p[1] = p[2] = (int)(fminf(sample((float)x / W, (float)y / H) * 255.0f, 255.0f)); 110 | } 111 | svpng(fopen("reflection.png", "wb"), W, H, img, 0); 112 | } 113 | -------------------------------------------------------------------------------- /reflection_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/reflection_box.png -------------------------------------------------------------------------------- /reflection_boxgradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/reflection_boxgradient.png -------------------------------------------------------------------------------- /reflection_boxscene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/reflection_boxscene.png -------------------------------------------------------------------------------- /reflection_concavemirror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/reflection_concavemirror.png -------------------------------------------------------------------------------- /refraction.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fabsf(), fminf(), fmaxf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 512 7 | #define H 512 8 | #define N 256 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 5.0f 11 | #define EPSILON 1e-6f 12 | #define BIAS 1e-4f 13 | #define MAX_DEPTH 3 14 | 15 | typedef struct { float sd, emissive, reflectivity, eta; } Result; 16 | 17 | unsigned char img[W * H * 3]; 18 | 19 | float circleSDF(float x, float y, float cx, float cy, float r) { 20 | float ux = x - cx, uy = y - cy; 21 | return sqrtf(ux * ux + uy * uy) - r; 22 | } 23 | 24 | float boxSDF(float x, float y, float cx, float cy, float theta, float sx, float sy) { 25 | float costheta = cosf(theta), sintheta = sinf(theta); 26 | float dx = fabs((x - cx) * costheta + (y - cy) * sintheta) - sx; 27 | float dy = fabs((y - cy) * costheta - (x - cx) * sintheta) - sy; 28 | float ax = fmaxf(dx, 0.0f), ay = fmaxf(dy, 0.0f); 29 | return fminf(fmaxf(dx, dy), 0.0f) + sqrtf(ax * ax + ay * ay); 30 | } 31 | 32 | float planeSDF(float x, float y, float px, float py, float nx, float ny) { 33 | return (x - px) * nx + (y - py) * ny; 34 | } 35 | 36 | Result unionOp(Result a, Result b) { 37 | return a.sd < b.sd ? a : b; 38 | } 39 | 40 | Result intersectOp(Result a, Result b) { 41 | return a.sd > b.sd ? a : b; 42 | } 43 | 44 | Result subtractOp(Result a, Result b) { 45 | Result r = a; 46 | r.sd = (a.sd > -b.sd) ? a.sd : -b.sd; 47 | return r; 48 | } 49 | 50 | Result scene(float x, float y) { 51 | Result a = { circleSDF(x, y, -0.2f, -0.2f, 0.1f), 10.0f, 0.0f, 0.0f }; 52 | Result b = { boxSDF(x, y, 0.5f, 0.5f, 0.0f, 0.3, 0.2f), 0.0f, 0.2f, 1.5f }; 53 | Result c = { circleSDF(x, y, 0.5f, -0.5f, 0.05f), 20.0f, 0.0f, 0.0f }; 54 | Result d = { circleSDF(x, y, 0.5f, 0.2f, 0.35f), 0.0f, 0.2f, 1.5f }; 55 | Result e = { circleSDF(x, y, 0.5f, 0.8f, 0.35f), 0.0f, 0.2f, 1.5f }; 56 | Result f = { boxSDF(x, y, 0.5f, 0.5f, 0.0f, 0.2, 0.1f), 0.0f, 0.2f, 1.5f }; 57 | Result g = { circleSDF(x, y, 0.5f, 0.12f, 0.35f), 0.0f, 0.2f, 1.5f }; 58 | Result h = { circleSDF(x, y, 0.5f, 0.87f, 0.35f), 0.0f, 0.2f, 1.5f }; 59 | Result i = { circleSDF(x, y, 0.5f, 0.5f, 0.2f), 0.0f, 0.2f, 1.5f }; 60 | Result j = { planeSDF(x, y, 0.5f, 0.5f, 0.0f, -1.0f), 0.0f, 0.2f, 1.5f }; 61 | // return unionOp(a, b); 62 | // return unionOp(c, intersectOp(d, e)); 63 | // return unionOp(c, subtractOp(f, unionOp(g, h))); 64 | return unionOp(c, intersectOp(i, j)); 65 | } 66 | 67 | void gradient(float x, float y, float* nx, float* ny) { 68 | *nx = (scene(x + EPSILON, y).sd - scene(x - EPSILON, y).sd) * (0.5f / EPSILON); 69 | *ny = (scene(x, y + EPSILON).sd - scene(x, y - EPSILON).sd) * (0.5f / EPSILON); 70 | } 71 | 72 | void reflect(float ix, float iy, float nx, float ny, float* rx, float* ry) { 73 | float idotn2 = (ix * nx + iy * ny) * 2.0f; 74 | *rx = ix - idotn2 * nx; 75 | *ry = iy - idotn2 * ny; 76 | } 77 | 78 | int refract(float ix, float iy, float nx, float ny, float eta, float* rx, float* ry) { 79 | float idotn = ix * nx + iy * ny; 80 | float k = 1.0f - eta * eta * (1.0f - idotn * idotn); 81 | if (k < 0.0f) 82 | return 0; // Total internal reflection 83 | float a = eta * idotn + sqrtf(k); 84 | *rx = eta * ix - a * nx; 85 | *ry = eta * iy - a * ny; 86 | return 1; 87 | } 88 | 89 | float trace(float ox, float oy, float dx, float dy, int depth) { 90 | float t = 1e-3f; 91 | float sign = scene(ox, oy).sd > 0.0f ? 1.0f : -1.0f; 92 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 93 | float x = ox + dx * t, y = oy + dy * t; 94 | Result r = scene(x, y); 95 | if (r.sd * sign < EPSILON) { 96 | float sum = r.emissive; 97 | if (depth < MAX_DEPTH && (r.reflectivity > 0.0f || r.eta > 0.0f)) { 98 | float nx, ny, rx, ry, refl = r.reflectivity; 99 | gradient(x, y, &nx, &ny); 100 | nx *= sign; 101 | ny *= sign; 102 | if (r.eta > 0.0f) { 103 | if (refract(dx, dy, nx, ny, sign < 0.0f ? r.eta : 1.0f / r.eta, &rx, &ry)) 104 | sum += (1.0f - refl) * trace(x - nx * BIAS, y - ny * BIAS, rx, ry, depth + 1); 105 | else 106 | refl = 1.0f; // Total internal reflection 107 | } 108 | if (refl > 0.0f) { 109 | reflect(dx, dy, nx, ny, &rx, &ry); 110 | sum += refl * trace(x + nx * BIAS, y + ny * BIAS, rx, ry, depth + 1); 111 | } 112 | } 113 | return sum; 114 | } 115 | t += r.sd * sign; 116 | } 117 | return 0.0f; 118 | } 119 | 120 | float sample(float x, float y) { 121 | float sum = 0.0f; 122 | for (int i = 0; i < N; i++) { 123 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 124 | sum += trace(x, y, cosf(a), sinf(a), 0); 125 | } 126 | return sum / N; 127 | } 128 | 129 | int main() { 130 | float a = TWO_PI * 0.73f; 131 | printf("%f", trace(0.6f, 0.6f, cosf(a), sinf(a), 0)); 132 | unsigned char* p = img; 133 | for (int y = 0; y < H; y++) 134 | for (int x = 0; x < W; x++, p += 3) 135 | p[0] = p[1] = p[2] = (int)(fminf(sample((float)x / W, (float)y / H) * 255.0f, 255.0f)); 136 | svpng(fopen("refraction.png", "wb"), W, H, img, 0); 137 | } 138 | -------------------------------------------------------------------------------- /refraction_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/refraction_box.png -------------------------------------------------------------------------------- /refraction_concavelens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/refraction_concavelens.png -------------------------------------------------------------------------------- /refraction_convexlens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/refraction_convexlens.png -------------------------------------------------------------------------------- /refraction_montage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/refraction_montage.png -------------------------------------------------------------------------------- /refraction_semicircular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/refraction_semicircular.png -------------------------------------------------------------------------------- /shapes.c: -------------------------------------------------------------------------------- 1 | #include "svpng.inc" 2 | #include // fminf(), sinf(), cosf(), sqrt() 3 | #include // rand(), RAND_MAX 4 | 5 | #define TWO_PI 6.28318530718f 6 | #define W 512 7 | #define H 512 8 | #define N 64 9 | #define MAX_STEP 64 10 | #define MAX_DISTANCE 2.0f 11 | #define EPSILON 1e-6f 12 | 13 | typedef struct { float sd, emissive; } Result; 14 | 15 | unsigned char img[W * H * 3]; 16 | 17 | float circleSDF(float x, float y, float cx, float cy, float r) { 18 | float ux = x - cx, uy = y - cy; 19 | return sqrtf(ux * ux + uy * uy) - r; 20 | } 21 | 22 | float planeSDF(float x, float y, float px, float py, float nx, float ny) { 23 | return (x - px) * nx + (y - py) * ny; 24 | } 25 | 26 | float segmentSDF(float x, float y, float ax, float ay, float bx, float by) { 27 | float vx = x - ax, vy = y - ay, ux = bx - ax, uy = by - ay; 28 | float t = fmaxf(fminf((vx * ux + vy * uy) / (ux * ux + uy * uy), 1.0f), 0.0f); 29 | float dx = vx - ux * t, dy = vy - uy * t; 30 | return sqrtf(dx * dx + dy * dy); 31 | } 32 | 33 | float capsuleSDF(float x, float y, float ax, float ay, float bx, float by, float r) { 34 | return segmentSDF(x, y, ax, ay, bx, by) - r; 35 | } 36 | 37 | float boxSDF(float x, float y, float cx, float cy, float theta, float sx, float sy) { 38 | float costheta = cosf(theta), sintheta = sinf(theta); 39 | float dx = fabs((x - cx) * costheta + (y - cy) * sintheta) - sx; 40 | float dy = fabs((y - cy) * costheta - (x - cx) * sintheta) - sy; 41 | float ax = fmaxf(dx, 0.0f), ay = fmaxf(dy, 0.0f); 42 | return fminf(fmaxf(dx, dy), 0.0f) + sqrtf(ax * ax + ay * ay); 43 | } 44 | 45 | float triangleSDF(float x, float y, float ax, float ay, float bx, float by, float cx, float cy) { 46 | float d = fminf(fminf( 47 | segmentSDF(x, y, ax, ay, bx, by), 48 | segmentSDF(x, y, bx, by, cx, cy)), 49 | segmentSDF(x, y, cx, cy, ax, ay)); 50 | return (bx - ax) * (y - ay) > (by - ay) * (x - ax) && 51 | (cx - bx) * (y - by) > (cy - by) * (x - bx) && 52 | (ax - cx) * (y - cy) > (ay - cy) * (x - cx) ? -d : d; 53 | } 54 | 55 | Result intersectOp(Result a, Result b) { 56 | return a.sd > b.sd ? a : b; 57 | } 58 | 59 | Result scene(float x, float y) { 60 | Result a = { circleSDF(x, y, 0.5f, 0.5f, 0.2f), 1.0f }; 61 | Result b = { planeSDF(x, y, 0.0f, 0.5f, 0.0f, 1.0f), 0.8f }; 62 | Result c = { capsuleSDF(x, y, 0.4f, 0.4f, 0.6f, 0.6f, 0.1f), 1.0f }; 63 | Result d = { boxSDF(x, y, 0.5f, 0.5f, TWO_PI / 16.0f, 0.3f, 0.1f), 1.0f }; 64 | Result e = { boxSDF(x, y, 0.5f, 0.5f, TWO_PI / 16.0f, 0.3f, 0.1f) - 0.1f, 1.0f }; 65 | Result f = { triangleSDF(x, y, 0.5f, 0.2f, 0.8f, 0.8f, 0.3f, 0.6f), 1.0f }; 66 | Result g = { triangleSDF(x, y, 0.5f, 0.2f, 0.8f, 0.8f, 0.3f, 0.6f) - 0.1f, 1.0f }; 67 | // return a; 68 | // return b; 69 | return intersectOp(a, b); 70 | // return c; 71 | // return d; 72 | // return e; 73 | // return f; 74 | // return g; 75 | } 76 | 77 | float trace(float ox, float oy, float dx, float dy) { 78 | float t = 0.0f; 79 | for (int i = 0; i < MAX_STEP && t < MAX_DISTANCE; i++) { 80 | Result r = scene(ox + dx * t, oy + dy * t); 81 | if (r.sd < EPSILON) 82 | return r.emissive; 83 | t += r.sd; 84 | } 85 | return 0.0f; 86 | } 87 | 88 | float sample(float x, float y) { 89 | float sum = 0.0f; 90 | for (int i = 0; i < N; i++) { 91 | float a = TWO_PI * (i + (float)rand() / RAND_MAX) / N; 92 | sum += trace(x, y, cosf(a), sinf(a)); 93 | } 94 | return sum / N; 95 | } 96 | 97 | int main() { 98 | unsigned char* p = img; 99 | for (int y = 0; y < H; y++) 100 | for (int x = 0; x < W; x++, p += 3) 101 | p[0] = p[1] = p[2] = (int)(fminf(sample((float)x / W, (float)y / H) * 255.0f, 255.0f)); 102 | svpng(fopen("shapes.png", "wb"), W, H, img, 0); 103 | } 104 | -------------------------------------------------------------------------------- /shapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/shapes.png -------------------------------------------------------------------------------- /shapes_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/shapes_box.png -------------------------------------------------------------------------------- /shapes_capsule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/shapes_capsule.png -------------------------------------------------------------------------------- /shapes_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/shapes_circle.png -------------------------------------------------------------------------------- /shapes_plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/shapes_plane.png -------------------------------------------------------------------------------- /shapes_roundedbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/shapes_roundedbox.png -------------------------------------------------------------------------------- /shapes_roundedtriangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/shapes_roundedtriangle.png -------------------------------------------------------------------------------- /shapes_semicircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/shapes_semicircle.png -------------------------------------------------------------------------------- /shapes_triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/shapes_triangle.png -------------------------------------------------------------------------------- /snellslaw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/snellslaw.png -------------------------------------------------------------------------------- /snellslaw.tex: -------------------------------------------------------------------------------- 1 | \documentclass[tikz]{standalone} 2 | \usetikzlibrary{arrows,angles,quotes,calc,decorations.pathreplacing} 3 | \begin{document} 4 | \begin{tikzpicture}[>=triangle 45,line width=1.6pt,scale=1.5,font=\fontsize{15pt}{0}] 5 | \coordinate (O) at (0,0); 6 | \coordinate (V) at (-2.5,2); 7 | \coordinate (R) at (2,-2.5); 8 | \coordinate (N) at (0, 3); 9 | \fill[blue!10] (-4,0) rectangle (4,4); 10 | \fill[blue!40] (-4,0) rectangle (4,-4); 11 | \draw[line width=0.5pt] (-4, 0) -- (4, 0); 12 | \draw[->,style=dashed] (O) -- (N) node[anchor=south] {$\hat{\mathbf{n}}$}; 13 | \draw[style=dashed] (O) -- (0, -3); 14 | \draw[->] (V) -- (O); 15 | \draw[->] (O) -- (R); 16 | \draw (0,0.5) arc (90:140:0.5); 17 | \draw (0,-0.5) arc (270:310:0.5); 18 | \node[] at (115:0.8) {$\theta_{1}$}; 19 | \node[] at (290:0.8) {$\theta_{2}$}; 20 | \node[anchor=south] at (-3.5, 0) {$\eta_1$}; 21 | \node[anchor=north] at (-3.5, 0) {$\eta_2$}; 22 | \end{tikzpicture} 23 | \end{document} 24 | -------------------------------------------------------------------------------- /svpng.inc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2017 Milo Yip. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of pngout nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | /*! \file 31 | \brief svpng() is a minimalistic C function for saving RGB/RGBA image into uncompressed PNG. 32 | \author Milo Yip 33 | \version 0.1.1 34 | \copyright MIT license 35 | \sa http://github.com/miloyip/svpng 36 | */ 37 | 38 | #ifndef SVPNG_INC_ 39 | #define SVPNG_INC_ 40 | 41 | /*! \def SVPNG_LINKAGE 42 | \brief User customizable linkage for svpng() function. 43 | By default this macro is empty. 44 | User may define this macro as static for static linkage, 45 | and/or inline in C99/C++, etc. 46 | */ 47 | #ifndef SVPNG_LINKAGE 48 | #define SVPNG_LINKAGE 49 | #endif 50 | 51 | /*! \def SVPNG_OUTPUT 52 | \brief User customizable output stream. 53 | By default, it uses C file descriptor and fputc() to output bytes. 54 | In C++, for example, user may use std::ostream or std::vector instead. 55 | */ 56 | #ifndef SVPNG_OUTPUT 57 | #include 58 | #define SVPNG_OUTPUT FILE* fp 59 | #endif 60 | 61 | /*! \def SVPNG_PUT 62 | \brief Write a byte 63 | */ 64 | #ifndef SVPNG_PUT 65 | #define SVPNG_PUT(u) fputc(u, fp) 66 | #endif 67 | 68 | 69 | /*! 70 | \brief Save a RGB/RGBA image in PNG format. 71 | \param SVPNG_OUTPUT Output stream (by default using file descriptor). 72 | \param w Width of the image. (<16383) 73 | \param h Height of the image. 74 | \param img Image pixel data in 24-bit RGB or 32-bit RGBA format. 75 | \param alpha Whether the image contains alpha channel. 76 | */ 77 | SVPNG_LINKAGE void svpng(SVPNG_OUTPUT, unsigned w, unsigned h, const unsigned char* img, int alpha) { 78 | static const unsigned t[] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 79 | /* CRC32 Table */ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; 80 | unsigned a = 1, b = 0, c, p = w * (alpha ? 4 : 3) + 1, x, y, i; /* ADLER-a, ADLER-b, CRC, pitch */ 81 | #define SVPNG_U8A(ua, l) for (i = 0; i < l; i++) SVPNG_PUT((ua)[i]); 82 | #define SVPNG_U32(u) do { SVPNG_PUT((u) >> 24); SVPNG_PUT(((u) >> 16) & 255); SVPNG_PUT(((u) >> 8) & 255); SVPNG_PUT((u) & 255); } while(0) 83 | #define SVPNG_U8C(u) do { SVPNG_PUT(u); c ^= (u); c = (c >> 4) ^ t[c & 15]; c = (c >> 4) ^ t[c & 15]; } while(0) 84 | #define SVPNG_U8AC(ua, l) for (i = 0; i < l; i++) SVPNG_U8C((ua)[i]) 85 | #define SVPNG_U16LC(u) do { SVPNG_U8C((u) & 255); SVPNG_U8C(((u) >> 8) & 255); } while(0) 86 | #define SVPNG_U32C(u) do { SVPNG_U8C((u) >> 24); SVPNG_U8C(((u) >> 16) & 255); SVPNG_U8C(((u) >> 8) & 255); SVPNG_U8C((u) & 255); } while(0) 87 | #define SVPNG_U8ADLER(u) do { SVPNG_U8C(u); a = (a + (u)) % 65521; b = (b + a) % 65521; } while(0) 88 | #define SVPNG_BEGIN(s, l) do { SVPNG_U32(l); c = ~0U; SVPNG_U8AC(s, 4); } while(0) 89 | #define SVPNG_END() SVPNG_U32(~c) 90 | SVPNG_U8A("\x89PNG\r\n\32\n", 8); /* Magic */ 91 | SVPNG_BEGIN("IHDR", 13); /* IHDR chunk { */ 92 | SVPNG_U32C(w); SVPNG_U32C(h); /* Width & Height (8 bytes) */ 93 | SVPNG_U8C(8); SVPNG_U8C(alpha ? 6 : 2); /* Depth=8, Color=True color with/without alpha (2 bytes) */ 94 | SVPNG_U8AC("\0\0\0", 3); /* Compression=Deflate, Filter=No, Interlace=No (3 bytes) */ 95 | SVPNG_END(); /* } */ 96 | SVPNG_BEGIN("IDAT", 2 + h * (5 + p) + 4); /* IDAT chunk { */ 97 | SVPNG_U8AC("\x78\1", 2); /* Deflate block begin (2 bytes) */ 98 | for (y = 0; y < h; y++) { /* Each horizontal line makes a block for simplicity */ 99 | SVPNG_U8C(y == h - 1); /* 1 for the last block, 0 for others (1 byte) */ 100 | SVPNG_U16LC(p); SVPNG_U16LC(~p); /* Size of block in little endian and its 1's complement (4 bytes) */ 101 | SVPNG_U8ADLER(0); /* No filter prefix (1 byte) */ 102 | for (x = 0; x < p - 1; x++, img++) 103 | SVPNG_U8ADLER(*img); /* Image pixel data */ 104 | } 105 | SVPNG_U32C((b << 16) | a); /* Deflate block end with adler (4 bytes) */ 106 | SVPNG_END(); /* } */ 107 | SVPNG_BEGIN("IEND", 0); SVPNG_END(); /* IEND chunk {} */ 108 | } 109 | 110 | #endif /* SVPNG_INC_ */ 111 | -------------------------------------------------------------------------------- /vector_fresnel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/vector_fresnel.png -------------------------------------------------------------------------------- /vector_fresnel.tex: -------------------------------------------------------------------------------- 1 | \documentclass[tikz]{standalone} 2 | \usetikzlibrary{arrows,angles,quotes,calc,decorations.pathreplacing} 3 | \begin{document} 4 | \begin{tikzpicture}[>=triangle 45,line width=1.6pt,scale=1.5,font=\fontsize{15pt}{0}] 5 | \coordinate (O) at (0,0); 6 | \coordinate (V) at (-2.5,2); 7 | \coordinate (R) at (2.5,2); 8 | \coordinate (T) at (2,-2.5); 9 | \coordinate (N) at (0, 3); 10 | \fill[blue!10] (-4,0) rectangle (4,4); 11 | \fill[blue!40] (-4,0) rectangle (4,-4); 12 | \draw[line width=0.5pt] (-4, 0) -- (4, 0); 13 | \draw[->, style=dashed] (0, -3) -- (N) node[anchor=south] {$\hat{\mathbf{n}}$}; 14 | \draw[->] (V) -- (O); 15 | \draw[->] (O) -- (R); 16 | \draw[->] (O) -- (T); 17 | \draw (0,0.5) arc (90:140:0.5); 18 | \draw (0,-0.5) arc (270:310:0.5); 19 | \node[] at (115:0.8) {$\theta_{i}$}; 20 | \node[] at (290:0.8) {$\theta_{t}$}; 21 | \node[anchor=south] at (-3.5, 0) {$\eta_1$}; 22 | \node[anchor=north] at (-3.5, 0) {$\eta_2$}; 23 | \node[anchor=south west] at (R) {$R$}; 24 | \node[anchor=north] at (T) {$1 - R$}; 25 | \end{tikzpicture} 26 | \end{document} 27 | -------------------------------------------------------------------------------- /vector_reflect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/vector_reflect.png -------------------------------------------------------------------------------- /vector_reflect.tex: -------------------------------------------------------------------------------- 1 | \documentclass[tikz]{standalone} 2 | \usetikzlibrary{arrows,angles,quotes,calc,decorations.pathreplacing} 3 | \begin{document} 4 | \begin{tikzpicture}[>=triangle 45,line width=1.6pt,scale=1.5,font=\fontsize{20pt}{0}, 5 | interface/.style={ 6 | postaction={draw,decorate,decoration={border,angle=-45, 7 | amplitude=0.3cm,segment length=2mm}}}] 8 | \coordinate (O) at (0,0); 9 | \coordinate (V) at (-3,2); 10 | \coordinate (R) at (3,2); 11 | \coordinate (N) at (0, 1.5); 12 | \draw[blue,line width=.5pt,interface](-4,0)--(4,0); 13 | \draw[->] (O) -- (N) node[anchor=south] {$\hat{\mathbf{n}}$}; 14 | \draw[->] (V) -- (O) node[midway, anchor=north east] {$\mathbf{i}$}; 15 | \draw[->] (O) -- (R) node[midway, anchor=north west] {$\mathbf{r}$}; 16 | \draw[->, blue, style=dashed] (V) -- (-3, 0) node[midway, anchor=east] {$(\mathbf{i} \cdot \hat{\mathbf{n}})\hat{\mathbf{n}}$}; 17 | \draw[->, blue, style=dashed] (-3, 0) -- (-3, -2) node[midway, anchor=east] {$(\mathbf{i} \cdot \hat{\mathbf{n}})\hat{\mathbf{n}}$}; 18 | \draw[->, style=dashed] (-3, -2) -- (O) node[midway, anchor=north west] {$\mathbf{r}$}; 19 | \end{tikzpicture} 20 | \end{document} 21 | -------------------------------------------------------------------------------- /vector_refract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miloyip/light2d/b70036a528c8b0e37464371e1cc1dacc86392a72/vector_refract.png -------------------------------------------------------------------------------- /vector_refract.tex: -------------------------------------------------------------------------------- 1 | \documentclass[tikz]{standalone} 2 | \usetikzlibrary{arrows,angles,quotes,calc,decorations.pathreplacing} 3 | \begin{document} 4 | \begin{tikzpicture}[>=triangle 45,line width=1.6pt,scale=1.5,font=\fontsize{15pt}{0}] 5 | \coordinate (O) at (0,0); 6 | \coordinate (V) at (-2.5,2); 7 | \coordinate (R) at (2,-2.5); 8 | \coordinate (N) at (0, 3); 9 | \coordinate (M) at (3, 0); 10 | \fill[blue!10] (-4,0) rectangle (4,4); 11 | \fill[blue!40] (-4,0) rectangle (4,-4); 12 | \draw[line width=0.5pt] (-4, 0) -- (4, 0); 13 | \draw[->, style=dashed] (O) -- (N) node[anchor=south] {$\hat{\mathbf{n}}$}; 14 | \draw[->, style=dashed] (O) -- (M) node[anchor=south] {$\hat{\mathbf{m}}$}; 15 | \draw[->] (V) -- (O) node[midway, anchor=south west] {$\hat{\mathbf{i}}$}; 16 | \draw[->] (O) -- (R) node[midway, anchor=south west] {$\hat{\mathbf{t}}$}; 17 | \draw[->, blue, style=dashed] (V) -- (-2.5, 0) node[midway, anchor=east] {$(\hat{\mathbf{i}} \cdot \hat{\mathbf{n}})\hat{\mathbf{n}}$}; 18 | \draw[->, violet, style=dashed] (-2.5, 0) -- (O) node[midway, anchor=north] {$\sin\theta_1\hat{\mathbf{m}}$}; 19 | \draw[->, blue, style=dashed] (O) -- (0, -2.5) node[midway, anchor=east] {$-\cos\theta_2\hat{\mathbf{n}}$}; 20 | \draw[->, violet, style=dashed] (0, -2.5) -- (R) node[midway, anchor=north] {$\sin\theta_2\hat{\mathbf{m}}$}; 21 | \draw (0,0.5) arc (90:140:0.5); 22 | \draw (0,-0.5) arc (270:310:0.5); 23 | \node[] at (115:0.8) {$\theta_{1}$}; 24 | \node[] at (290:0.8) {$\theta_{2}$}; 25 | \node[anchor=south] at (-3.5, 0) {$\eta_1$}; 26 | \node[anchor=north] at (-3.5, 0) {$\eta_2$}; 27 | \end{tikzpicture} 28 | \end{document} 29 | --------------------------------------------------------------------------------