Raycard-Entschlüsselung im Postkartenformat


"Er hat es wieder getan!" - das ist mir als erstes eingefallen, als ich auf die Rückseite des Pixar-Flyers [1] schaute, der vollständig mit Code gefüllt war. Eine Gruppe von Konstruktionen und Ausdrücken wurde in der unteren rechten Ecke von niemand anderem als Andrew Kensler signiert. Für diejenigen, die ihn nicht kennen, sage ich: Andrew ist ein Programmierer, der 2009 einen 1337-Byte -Raytracer in Visitenkartengröße erfunden hat.

Diesmal hatte Andrew etwas umfangreicheres, aber ein viel interessanteres visuelles Ergebnis. Seit ich meine Game Engine Black Books über Wolf3D und DOOM fertig geschrieben habe, hatte ich Zeit, die Innenseiten des kryptischen Codes zu lernen. Und fast sofort war ich buchstäblich fasziniert von den Techniken, die in ihm entdeckt wurden. Sie unterschieden sich sehr von Andrews früheren Arbeiten, die auf einem "Standard" -Rochen-Tracer basierten. Ich war daran interessiert, etwas über Strahlenmarsch, Merkmale konstruktiver volumetrischer Geometrie, Monte-Carlo-Rendering / Pfadverfolgung sowie viele andere Tricks zu lernen, mit denen er Code in ein so kleines Stück Papier drückte.



Quellcode




Die Vorderseite des Flyers ist eine Anzeige für die Personalabteilung von Pixar. Auf der Rückseite sind 2.037 Bytes C ++ - Code gedruckt, der verschleiert ist, um die kleinstmögliche Oberfläche zu belegen.

#include <stdlib.h> // card > pixar.ppm #include <stdio.h> #include <math.h> #define R return #define O operator typedef float F;typedef int I;struct V{F x,y,z;V(F v=0){x=y=z=v;}V(F a,F b,F c=0){x=a;y=b;z=c;}V O+(V r){RV(x+rx,y+ry,z+rz);}VO*(V r){RV(x*rx,y*r. y,z*rz);}FO%(V r){R x*r.x+y*r.y+z*rz;}VO!(){R*this*(1/sqrtf(*this%*this) );}};FL(F l,F r){R l<r?l:r;}FU(){R(F)rand()/RAND_MAX;}FB(V p,V l,V h){l=p +l*-1;h=h+p*-1;RL(L(L(lx,hx),L(ly,hy)),L(lz,hz));}FS(V p,I&m){F d=1\ e9;V f=p;fz=0;char l[]="5O5_5W9W5_9_COC_AOEOA_E_IOQ_I_QOUOY_Y_]OWW[WaOa_aW\ eWa_e_cWiO";for(I i=0;i<60;i+=4){V b=V(l[i]-79,l[i+1]-79)*.5,e=V(l[i+2]-79,l [i+3]-79)*.5+b*-1,o=f+(b+e*L(-L((b+f*-1)%e/(e%e),0),1))*-1;d=L(d,o%o);}d=sq\ rtf(d);V a[]={V(-11,6),V(11,6)};for(I i=2;i--;){V o=f+a[i]*-1;d=L(d,ox>0?f\ absf(sqrtf(o%o)-2):(o.y+=oy>0?-2:2,sqrtf(o%o)));}d=powf(powf(d,8)+powf(pz, 8),.125)-.5;m=1;F r=L(-L(B(p,V(-30,-.5,-30),V(30,18,30)),B(p,V(-25,17,-25),V (25,20,25))),B(V(fmodf(fabsf(px),8),py,pz),V(1.5,18.5,-25),V(6.5,20,25))) ;if(r<d)d=r,m=2;F s=19.9-py;if(s<d)d=s,m=3;R d;}IM(V o,V d,V&h,V&n){I m,s= 0;F t=0,c;for(;t<100;t+=c)if((c=S(h=o+d*t,m))<.01||++s>99)R n=!V(S(h+V(.01,0 ),s)-c,S(h+V(0,.01),s)-c,S(h+V(0,0,.01),s)-c),m;R 0;}VT(V o,V d){V h,n,r,t= 1,l(!V(.6,.6,1));for(I b=3;b--;){I m=M(o,d,h,n);if(!m)break;if(m==1){d=d+n*( n%d*-2);o=h+d*.1;t=t*.2;}if(m==2){F i=n%l,p=6.283185*U(),c=U(),s=sqrtf(1-c), g=nz<0?-1:1,u=-1/(g+nz),v=nx*ny*u;d=V(v,g+ny*ny*u,-ny)*(cosf(p)*s)+V( 1+g*nx*nx*u,g*v,-g*nx)*(sinf(p)*s)+n*sqrtf(c);o=h+d*.1;t=t*.2;if(i>0&&M(h +n*.1,l,h,n)==3)r=r+t*V(500,400,100)*i;}if(m==3){r=r+t*V(50,80,100);break;}} R r;}I main(){I w=960,h=540,s=16;V e(-22,5,25),g=!(V(-3,4,0)+e*-1),l=!V(gz, 0,-gx)*(1./w),u(gy*lz-gz*ly,gz*lx-gx*lz,gx*ly-gy*lx);printf("P\ 6 %d %d 255 ",w,h);for(I y=h;y--;)for(I x=w;x--;){V c;for(I p=s;p--;)c=c+T(e ,!(g+l*(xw/2+U())+u*(yh/2+U())));c=c*(1./s)+14./241;V o=c+1;c=V(cx/ox,c. y/oy,cz/oz)*255;printf("%c%c%c",(I)cx,(I)cy,(I)cz);}}// Andrew Kensler 

Arbeitet er überhaupt?




Mit dem Code gibt es eine Anweisung für den Start. Die Idee ist, die Standardausgabe in eine Datei umzuleiten. In der Erweiterung können wir annehmen, dass das Ausgabeformat ein Textbildformat namens NetPBM [2] ist .

  $ clang -o card2 -O3 raytracer.cpp
 $ time ./card> pixar.ppm

 echte 2m58.524s
 Benutzer 2m57.567s
 sys 0m0.415s 

Nach zwei Minuten und achtundfünfzig Sekunden [3] wird das folgende Bild erzeugt. Es ist erstaunlich, wie wenig Code dafür benötigt wird.


Sie können viel aus dem obigen Bild extrahieren. Grit ist ein offensichtliches Zeichen für einen "Pfad-Tracer". Diese Art von Renderer unterscheidet sich von Raytracing dadurch, dass die Strahlen nicht auf die Lichtquellen zurückgeführt werden. Bei diesem Verfahren werden Tausende von Strahlen pro Pixel von den Quellen emittiert und vom Programm überwacht, in der Hoffnung, dass sie die Lichtquelle finden. Dies ist eine interessante Technik, die viel besser als Raytracing das Rendern von Umgebungsokklusion, weichen Schatten, Ätzmitteln und Radiosität handhaben kann.

Wir werden den Code in Teile zerlegen




Durch die Übergabe der Eingabe an CLion wird der Code formatiert (siehe Ausgabe hier ) und in kleinere Teile / Aufgaben unterteilt.

  #include <stdlib.h> // card> pixar.ppm 
  #include <stdio.h> 
  #include <math.h> 

  #define R return 
  #define O Operator 
  typedef float F; typedef int I; 
  Struktur V {F x, y, z; V (F v = 0) {x = y = z = v;} V (F a, F b, F. 
  c = 0) {x = a; y = b; z = c;} V O + (V r) {RV (x + rx, y + ry, z + rz);} VO * (V r) {RV ( x * rx, y * r. 
  y, z * rz);} FO% (V r) {R x * r.x + y * r.y + z * rz;} VO! () {R * this * (1 / sqrtf (* this%) * this) 
  );}}; 
  FL (F l, F r) {R l <r? L: r;} FU () {R (F) rand () / RAND_MAX;} FB (V p, V l, V h) {l = p 
  + l * -1; h = h + p * -1; RL (L (L (L (lx, hx), L (ly, hy)), L (lz, hz));} 
  FS (V p, I & m) {F d = 1 \ 
  e9; V f = p; fz = 0; char l [] = "5O5_5W9W5_9_COC_AOEOA_E_IOQ_I_QOUOY_Y_] OWW [WaOa_aW \ 
  eWa_e_cWiO "; für (I i = 0; i <60; i + = 4) {V b = V (l [i] -79, l [i + 1] -79) *. 5, e = V (l [ i + 2] -79, l 
  [i + 3] -79) *. 5 + b * -1, o = f + (b + e * L (-L ((b + f * -1)% e / (e% e), 0), 1)) * - 1; d = L (d, o% o);} d = sq \ 
  rtf (d); Va [] = {V (-11,6), V (11,6)}; für (I i = 2; i -;) {V o = f + a [i] * -1; d = L (d, ox> 0? F \ 
  absf (sqrtf (o% o) -2) :( o.y + = oy> 0 & le; -2: 2, sqrtf (o% o));} d = powf (powf (d, 8) + powf (pz , 
  8), 125) - 5; m = 1; F r = L (-L (B (p, V (-30, - 5, -30), V (30, 18, 30)), B. (p, V (-25,17, -25), V. 
  (25, 20, 25))), B (V (fmodf (fabsf (px), 8), py, pz), V (1,5, 18,5, -25), V (6,5, 20, 25))) 
  ; wenn (r <d) d = r, m = 2; F s = 19,9-py; wenn (s <d) d = s, m = 3; R d;} 
  IM (V o, V d, V & h, V & n) {I m, s = 
  0; F t = 0, c; für (; t <100; t + = c) wenn ((c = S (h = o + d * t, m)) <. 01 || ++ s> 99) R. n =! V (S (h + V (0,01,0) 
  ), s) -c, S (h + V (0, 0,01), s) -c, S (h + V (0,0, 0,01), s) -c), m; R 0;} 
  VT (V o, V d) {V h, n, r, t = 
  1, l (! V (.6, .6,1)); für (I b = 3; b -;) {I m = M (o, d, h, n); wenn (! M) brechen ; wenn (m == 1) {d = d + n * ( 
  n% d * -2); o = h + d * .1; t = t * .2;} wenn (m == 2) {Fi = n% l, p = 6,283185 * U (), c = U (), s = sqrtf (1-c), 
  g = nz <0? -1: 1, u = -1 / (g + nz), v = nx * ny * u; d = V (v, g + ny * ny * u, -ny) * (cosf (p) * s) + V ( 
  1 + g * nx * nx * u, g * v, -g * nx) * (sinf (p) * s) + n * sqrtf (c); o = h + d * 0,1; t = t *. 2; wenn (i> 0 && M (h 
  + n * 0,1, l, h, n) == 3) r = r + t * V (500.400,100) * i;} wenn (m == 3) {r = r + t * V (50,80,100) ; break;}} 
  R r;} 
  I main () {I w = 960, h = 540, s = 16; V e (-22,5,25), g =! (V (-3,4,0) + e * -1), l =! V (gz, 
  0, -gx) * (1./w), u (gy * lz-gz * ly, gz * lx-gx * lz, gx * ly-gy * lx); printf ("P \ 
  6% d% d 255 ", w, h); für (I y = h; y -;) für (I x = w; x -;) {V c; für (I p = s; p- -;) c = c + T (e 
  ,! (g + 1 * (xw / 2 + U ()) + u * (yh / 2 + U ())); c = c * (1./s) + 14./241; V o = c + 1; c = V (cx / ox, c. 
  y / oy, cz / oz) * 255; printf ("% c% c% c", (I) cx, (I) cy, (I) cz);}} 
  // Andrew Kensler 

Jeder der Abschnitte wird im Rest des Artikels ausführlich beschrieben:
- gewöhnliche Tricks, - Vektorklasse, - Hilfscode, - Datenbank, - Ray Marching, - Sampling, - Hauptcode.

Allgemeine Tricks mit #define und typedef




Übliche Tricks sind die Verwendung von #define und typedef, um die Codemenge erheblich zu reduzieren. Hier bezeichnen wir F = float, I = int, R = return und O = operator. Reverse Engineering ist trivial.

Klasse v




Als nächstes kommt die Klasse V, die ich in Vec umbenannt habe (obwohl sie, wie wir weiter unten sehen werden, auch zum Speichern von RGB-Kanälen im Float-Format verwendet wird).

 struct Vec { float x, y, z; Vec(float v = 0) { x = y = z = v; } Vec(float a, float b, float c = 0) { x = a; y = b; z = c;} Vec operator+(Vec r) { return Vec(x + rx, y + ry, z + rz); } Vec operator*(Vec r) { return Vec(x * rx, y * ry, z * rz); } // dot product float operator%(Vec r) { return x * rx + y * ry + z * rz; } // inverse square root Vec operator!() {return *this * (1 / sqrtf(*this % *this) );} }; 

Beachten Sie, dass es keinen Subtraktionsoperator (-) gibt. Statt "X = A - B" zu schreiben, wird "X = A + B * -1" verwendet. Die inverse Quadratwurzel ist später nützlich, um die Vektoren zu normalisieren.

Hauptfunktion




main () ist das einzige Zeichen, das nicht verschleiert werden kann, da es von der _start-Funktion der libc-Bibliothek aufgerufen wird. Es lohnt sich normalerweise, damit zu beginnen, da es einfacher ist, auf diese Weise zu arbeiten. Es dauerte eine Weile, bis ich die Bedeutung der ersten Buchstaben herausgefunden hatte, aber es gelang mir trotzdem, etwas Lesbares zu schaffen.

 int main() { int w = 960, h = 540, samplesCount = 16; Vec position(-22, 5, 25); Vec goal = !(Vec(-3, 4, 0) + position * -1); Vec left = !Vec(goal.z, 0, -goal.x) * (1. / w); // Cross-product to get the up vector Vec up(goal.y * left.z - goal.z * left.y, goal.z * left.x - goal.x * left.z, goal.x * left.y - goal.y * left.x); printf("P6 %d %d 255 ", w, h); for (int y = h; y--;) for (int x = w; x--;) { Vec color; for (int p = samplesCount; p--;) color = color + Trace(position, !(goal + left * (x - w / 2 + randomVal())+ up * (y - h / 2 + randomVal()))); // Reinhard tone mapping color = color * (1. / samplesCount) + 14. / 241; Vec o = color + 1; color = Vec(color.x / ox, color.y / oy, color.z / oz) * 255; printf("%c%c%c", (int) color.x, (int) color.y, (int) color.z); } } 

Beachten Sie, dass Float-Literale nicht den Buchstaben „f“ enthalten und der Bruchteil aus Platzgründen verworfen wird. Der gleiche Trick wird unten verwendet, wo der ganzzahlige Teil gelöscht wird (float x = .5). Ungewöhnlich ist auch das Konstrukt "for" mit einem Iterationsausdruck, der in die Unterbrechungsbedingung eingefügt wird.

Dies ist eine ziemlich standardmäßige Hauptfunktion für einen Ray / Path-Tracer. Hier werden Kameravektoren eingestellt und für jedes Pixel Strahlen ausgesendet. Der Unterschied zwischen dem Ray Tracer und dem Path Tracer besteht darin, dass pro TP im TP mehrere Strahlen emittiert werden, die leicht zufällig verschoben sind. Dann wird die für jeden Strahl in einem Pixel erhaltene Farbe in drei Gleitkanälen R, B, G akkumuliert. Am Ende wird eine tonale Korrektur des Ergebnisses der Reinhardt-Methode durchgeführt.

Der wichtigste Teil ist sampleCount, das theoretisch auf 1 gesetzt werden kann, um das Rendern und die Iteration zu beschleunigen. Hier sind Beispiel-Renderings mit Beispielwerten von 1 bis 2048.

Spoiler Überschrift


1



2



4



8



16



32



64



128



256



512



1024



2048

Hilfecode




Ein weiterer einfacher Code sind Hilfsfunktionen. In diesem Fall haben wir eine triviale Funktion min (), einen Zufallswertgenerator im Intervall [0,1] und einen viel interessanteren boxTest (), der Teil des CSG-Systems (Constructive Solid Geometry) ist, mit dem die Welt ausgeschnitten wird. CSG wird im nächsten Abschnitt behandelt.

 float min(float l, float r) { return l < r ? l : r; } float randomVal() { return (float) rand() / RAND_MAX; } // Rectangle CSG equation. Returns minimum signed distance from // space carved by lowerLeft vertex and opposite rectangle // vertex upperRight. float BoxTest(Vec position, Vec lowerLeft, Vec upperRight) { lowerLeft = position + lowerLeft * -1; upperRight = upperRight + position * -1; return -min( min( min(lowerLeft.x, upperRight.x), min(lowerLeft.y, upperRight.y) ), min(lowerLeft.z, upperRight.z)); } 

Funktionen der konstruktiven Volumengeometrie




Der Code enthält keine Eckpunkte. Alles wird mit CSG-Funktionen erledigt. Wenn Sie mit ihnen nicht vertraut sind, sagen Sie einfach, dass dies Funktionen sind, die beschreiben, ob sich die Koordinate innerhalb oder außerhalb des Objekts befindet. Wenn die Funktion einen positiven Abstand zurückgibt, befindet sich der Punkt innerhalb des Objekts. Ein negativer Abstand zeigt an, dass sich der Punkt außerhalb des Objekts befindet. Es gibt viele Funktionen zum Beschreiben verschiedener Objekte. Zur Vereinfachung nehmen wir zum Beispiel eine Kugel und zwei Punkte, A und B.

Bild

 // Signed distance point(p) to sphere(c,r) float testSphere(Vec p, Vec c, float r) { Vec delta = c - p; float distance = sqrtf(delta%delta); return radius - distance; } Vec A {4, 6}; Vec B {3, 2}; Vec C {4, 2}; float r = 2.; testSphere(A, C, r); // == -1 (outside) testSphere(B, C, r); // == 1 (inside) 

Die Funktion testSphere () gibt -1 für Punkt A (dh außerhalb) und 1 für B (dh innen) zurück. Zeichen in Entfernungen sind nur ein Trick, mit dem Sie bei einem einzelnen Wert zwei Informationen anstelle von einer erhalten können. Ein ähnlicher Funktionstyp kann auch zur Beschreibung eines Parallelogramms geschrieben werden (genau dies wird in der Funktion BoxTest ausgeführt).


  // Signed distance point(p) to Box(c1,c2) float testRectangle(Vec p, Vec c1, Vec c2) { c1 = p + c1 * -1; c2 = c2 + position * -1; return min( min( min(c1.x, c2.x), min(c1.y, c2.y)), min(c1.z, c2.z)); } Vec A {3, 3}; Vec B {4, 6}; Vec C1 {2, 2}; Vec C2 {5, 4}; testRectangle(A, C1, C2); // 1.41 (inside) testRectangle(B, C1, C2); // -2.23 (outside) 

Nun wollen wir sehen, was passiert, wenn Sie das Vorzeichen des Rückgabewerts umdrehen.


  // Signed distance point(p) to carved box(c1,c2) float testCarveBox(Vec p, Vec c1, Vec c2) { c1 = p + c1 * -1; c2 = c2 + position * -1; return -min( min( min(c1.x, c2.x), min(c1.y, c2.y)), min(c1.z, c2.z)); } Vec A {3, 3}; Vec B {4, 6}; Vec C1 {2, 2}; Vec C2 {5, 4}; testCarveBox(A, C1, C2); // == -1.41 (outside) testCarveBox(B, C1, C2); // == 2.23 (inside) 

Jetzt beschreiben wir kein festes Objekt, sondern erklären die ganze Welt für fest und schneiden darin leeren Raum aus. Funktionen können als Bausteine ​​verwendet werden, die in Kombination komplexere Formen beschreiben können. Mit dem logischen Additionsoperator (min-Funktion) können wir ein Paar Rechtecke übereinander ausschneiden, und das Ergebnis sieht folgendermaßen aus.


  // Signed distance point to room float testRoom(Vec p) { Vec C1 {2, 4}; Vec C2 {5, 2}; // Lower room Vec C3 {3, 5}; Vec C4 {4, 4}; // Upper room // min() is the union of the two carved volumes. return min(testCarvedBox(p, C1, C2), testCarvedBox(p, C3, C4)); } Vec A {3, 3}; Vec B {4, 6}; testRoom(A, C1, C2); // == -1.41 (outside) testRoom(B, C1, C2); // == 1.00 (inside) 

Wenn Sie darüber nachdenken, sieht es aus wie der Raum, den wir studieren, weil der untere Raum genau so ausgedrückt wird - mit Hilfe von zwei geschnittenen Parallelogrammen.

Nachdem wir nun die leistungsstarken Kenntnisse von CSG beherrschen, können wir zum Code zurückkehren und die Datenbankfunktion betrachten, die am schwierigsten zu handhaben ist.

 #define HIT_NONE 0 #define HIT_LETTER 1 #define HIT_WALL 2 #define HIT_SUN 3 // Sample the world using Signed Distance Fields. float QueryDatabase(Vec position, int &hitType) { float distance = 1e9; Vec f = position; // Flattened position (z=0) fz = 0; char letters[15*4+1] = // 15 two points lines "5O5_" "5W9W" "5_9_" // P (without curve) "AOEO" "COC_" "A_E_" // I "IOQ_" "I_QO" // X "UOY_" "Y_]O" "WW[W" // A "aOa_" "aWeW" "a_e_" "cWiO"; // R (without curve) for (int i = 0; i < sizeof(letters); i += 4) { Vec begin = Vec(letters[i] - 79, letters[i + 1] - 79) * .5; Vec e = Vec(letters[i + 2] - 79, letters[i + 3] - 79) * .5 + begin * -1; Vec o = f + (begin + e * min(-min((begin + f * -1) % e / (e % e), 0), 1) ) * -1; distance = min(distance, o % o); // compare squared distance. } distance = sqrtf(distance); // Get real distance, not square distance. // Two curves (for P and R in PixaR) with hard-coded locations. Vec curves[] = {Vec(-11, 6), Vec(11, 6)}; for (int i = 2; i--;) { Vec o = f + curves[i] * -1; distance = min(distance, ox > 0 ? fabsf(sqrtf(o % o) - 2) : (oy += oy > 0 ? -2 : 2, sqrtf(o % o)) ); } distance = powf(powf(distance, 8) + powf(position.z, 8), .125) - .5; hitType = HIT_LETTER; float roomDist ; roomDist = min(// min(A,B) = Union with Constructive solid geometry //-min carves an empty space -min(// Lower room BoxTest(position, Vec(-30, -.5, -30), Vec(30, 18, 30)), // Upper room BoxTest(position, Vec(-25, 17, -25), Vec(25, 20, 25)) ), BoxTest( // Ceiling "planks" spaced 8 units apart. Vec(fmodf(fabsf(position.x), 8), position.y, position.z), Vec(1.5, 18.5, -25), Vec(6.5, 20, 25) ) ); if (roomDist < distance) distance = roomDist, hitType = HIT_WALL; float sun = 19.9 - position.y ; // Everything above 19.9 is light source. if (sun < distance)distance = sun, hitType = HIT_SUN; return distance; } 

Sie können hier die Funktion des "Ausschneidens" des Parallelogramms sehen, bei dem nur zwei Rechtecke verwendet werden, um den gesamten Raum zu konstruieren (unser Gehirn erledigt den Rest, es repräsentiert Wände). Die horizontale Leiter ist eine etwas komplexere CSG-Funktion unter Verwendung der Restteilung. Und schließlich bestehen die Buchstaben des Wortes PIXAR aus 15 Zeilen mit einem „Ursprung / Delta“ -Paar und zwei Sonderfällen für Kurven in den Buchstaben P und R.

Ray marschiert




Mit einer Datenbank von CSG-Funktionen, die die Welt beschreiben, reicht es aus, alle in der main () - Funktion emittierten Strahlen zu überspringen. Ray Marching verwendet die Distanzfunktion. Dies bedeutet, dass sich die Probenahmeposition um eine Strecke nach vorne zum nächsten Hindernis verschiebt.

 // Perform signed sphere marching // Returns hitType 0, 1, 2, or 3 and update hit position/normal int RayMarching(Vec origin, Vec direction, Vec &hitPos, Vec &hitNorm) { int hitType = HIT_NONE; int noHitCount = 0; float d; // distance from closest object in world. // Signed distance marching for (float total_d=0; total_d < 100; total_d += d) if ((d = QueryDatabase(hitPos = origin + direction * total_d, hitType)) < .01 || ++noHitCount > 99) return hitNorm = !Vec(QueryDatabase(hitPos + Vec(.01, 0), noHitCount) - d, QueryDatabase(hitPos + Vec(0, .01), noHitCount) - d, QueryDatabase(hitPos + Vec(0, 0, .01), noHitCount) - d) , hitType; // Weird return statement where a variable is also updated. return 0; } 

Die Idee des Strahlmarschierens basierend auf der Entfernung besteht darin, eine Entfernung vorwärts zum nächsten Objekt zu bewegen. Am Ende nähert sich der Strahl der Oberfläche so sehr, dass er als Einfallspunkt angesehen werden kann.


Beachten Sie, dass Ray Marching keinen echten Schnittpunkt mit der Oberfläche zurückgibt, sondern eine Annäherung. Deshalb stoppt das Marschieren im Code, wenn d <0,01f ist.

Alles zusammen: Sampling




Die Untersuchung des Pfadverfolgers ist fast abgeschlossen. Es fehlt eine Brücke, die die main () - Funktion mit dem Ray Marcher verbindet. Dieser letzte Teil, den ich in „Spur“ umbenannt habe, ist das „Gehirn“, in dem die Strahlen je nach Begegnung abprallen oder aufhören.

 Vec Trace(Vec origin, Vec direction) { Vec sampledPosition, normal, color, attenuation = 1; Vec lightDirection(!Vec(.6, .6, 1)); // Directional light for (int bounceCount = 3; bounceCount--;) { int hitType = RayMarching(origin, direction, sampledPosition, normal); if (hitType == HIT_NONE) break; // No hit. This is over, return color. if (hitType == HIT_LETTER) { // Specular bounce on a letter. No color acc. direction = direction + normal * ( normal % direction * -2); origin = sampledPosition + direction * 0.1; attenuation = attenuation * 0.2; // Attenuation via distance traveled. } if (hitType == HIT_WALL) { // Wall hit uses color yellow? float incidence = normal % lightDirection; float p = 6.283185 * randomVal(); float c = randomVal(); float s = sqrtf(1 - c); float g = normal.z < 0 ? -1 : 1; float u = -1 / (g + normal.z); float v = normal.x * normal.y * u; direction = Vec(v, g + normal.y * normal.y * u, -normal.y) * (cosf(p) * s) + Vec(1 + g * normal.x * normal.x * u, g * v, -g * normal.x) * (sinf(p) * s) + normal * sqrtf(c); origin = sampledPosition + direction * .1; attenuation = attenuation * 0.2; if (incidence > 0 && RayMarching(sampledPosition + normal * .1, lightDirection, sampledPosition, normal) == HIT_SUN) color = color + attenuation * Vec(500, 400, 100) * incidence; } if (hitType == HIT_SUN) { // color = color + attenuation * Vec(50, 80, 100); break; // Sun Color } } return color; } 

Ich habe ein wenig mit dieser Funktion experimentiert, um die maximal zulässige Anzahl von Strahlreflexionen zu ändern. Der Wert „2“ verleiht den Buchstaben eine überraschend schöne lackierte Vantablack-Farbe [4] .


1


2


3


4

Vollständig bereinigter Quellcode




Um alles zusammenzusetzen, habe ich einen völlig sauberen Quellcode erstellt .

Referenzen




[1] Quelle: lexfrench Twitter-Beitrag am 8. Oktober 2018.

[2] Quelle: Wikipedia: NetPBM-Bildformat

[3] Quelle: Visualisierung auf dem leistungsstärksten MacBook Pro, 2017

[4] Quelle: Wikipedia: Vantablack

Source: https://habr.com/ru/post/de434528/


All Articles