Dekripsi pelacak sinar seukuran kartu pos


β€œDia melakukannya lagi!” - inilah yang pertama kali terjadi pada saya ketika saya melihat bagian belakang selebaran Pixar [1] , yang benar-benar penuh dengan kode. Sekelompok konstruksi dan ekspresi ditandatangani di sudut kanan bawah oleh Andrew Kensler. Bagi mereka yang tidak mengenalnya, saya akan mengatakan: Andrew adalah seorang programmer yang menemukan pelacak sinar seukuran kartu nama 1337-byte pada tahun 2009.

Kali ini, Andrew muncul dengan sesuatu yang lebih banyak, tetapi dengan hasil visual yang jauh lebih menarik. Sejak saya selesai menulis Game Engine Black Books saya tentang Wolf3D dan DOOM , saya punya waktu untuk mempelajari bagian dalam kode samarnya. Dan segera, saya benar-benar terpesona oleh teknik yang ditemukan dalam dirinya. Mereka sangat berbeda dari pekerjaan Andrew sebelumnya, berdasarkan pelacak sinar "standar". Saya tertarik untuk belajar tentang ray marching, fitur geometri volumetrik yang konstruktif, render / jejak jalur Monte Carlo, serta banyak trik lain yang ia gunakan untuk memasukkan kode ke selembar kertas kecil.



Kode sumber




Bagian depan selebaran adalah iklan untuk departemen perekrutan Pixar. Di sisi sebaliknya dicetak 2.037 byte kode C ++, dikaburkan untuk menempati permukaan sekecil mungkin.

#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 

Apakah dia bekerja?




Dengan kode tersebut ada instruksi untuk peluncurannya. Idenya adalah untuk mengarahkan output standar ke file. Dengan ekstensi, kita dapat mengasumsikan bahwa format output adalah format gambar teks yang disebut NetPBM [2] .

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

 2m58.524s nyata
 pengguna 2m57.567s
 sys 0m0.415s 

Setelah dua menit lima puluh delapan detik [3] , gambar berikut dihasilkan. Sungguh menakjubkan betapa sedikit kode yang diperlukan untuk itu.


Anda dapat mengekstraksi banyak dari gambar di atas. Grit adalah tanda yang jelas dari "path tracer." Jenis renderer berbeda dari raytracing karena sinar tidak ditelusuri kembali ke sumber cahaya. Dalam metode ini, ribuan sinar per piksel dipancarkan dari sumber dan program memantaunya, berharap mereka akan menemukan sumber cahaya. Ini adalah teknik menarik yang, jauh lebih baik daripada ray tracing, dapat menangani render oklusi ambien, bayangan lembut, kaustik, dan radiositas.

Kami akan memecah kode menjadi beberapa bagian




Melewati input ke CLion memformat kode (lihat output di sini ) dan memecahnya menjadi bagian / tugas yang lebih kecil.

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

  #define R kembali 
  #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 * ini * (1 / sqrtf (*% ini * ini) 
  );}}; 
  FL (F l, F r) {R l <r? L: r;} FU () {R (F) rand () / RAND_MAX;} FB (Vp, Vl, V h) {l = p 
  + l * -1; h = h + p * -1; RL (L (L (lx, hx), L (ly, hy)), L (lz, hz));} 
  FS (Vp, I & m) {F d = 1 \ 
  e9; Vf = 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)}; untuk (I i = 2; i -;) {V o = f + a [i] * -1; d = L (d, sapi> 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)))) 
  ; jika (r <d) d = r, m = 2; F s = 19.9-py; jika (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; untuk (; t <100; t + = c) jika ((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)); untuk (I b = 3; b -;) {I m = M (o, d, h, n); jika (! M) putus ; if (m == 1) {d = d + n * ( 
  n% d * -2); o = h + d * .1; t = t * .2;} jika (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; jika (i> 0 && M (h 
  + n * .1, l, h, n) == 3) r = r + t * V (500.400.100) * i;} jika (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 -;) untuk (I x = w; x -;) {V c; untuk (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 

Setiap bagian dijelaskan secara rinci dalam sisa artikel:
β–  - trik biasa, β–  - Kelas vektor, β–  - kode tambahan, β–  - database, β–  - Ray berbaris, β–  - pengambilan sampel, β–  - kode utama.

Trik umum dengan #define dan typedef




Trik umum menggunakan #define dan typedef untuk secara signifikan mengurangi jumlah kode. Di sini kita menunjukkan F = float, I = int, R = return, dan O = operator. Reverse engineering itu sepele.

Kelas v




Berikutnya adalah kelas V, yang saya beri nama baru menjadi Vec (meskipun, seperti yang akan kita lihat di bawah, ini juga digunakan untuk menyimpan saluran RGB dalam format float).

 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) );} }; 

Perhatikan bahwa tidak ada operator pengurangan (-), jadi alih-alih menulis "X = A - B", "X = A + B * -1" digunakan. Root kuadrat terbalik berguna nanti untuk menormalkan vektor.

Fungsi Utama




main () adalah satu-satunya karakter yang tidak dapat dikaburkan karena dipanggil oleh fungsi _start perpustakaan libc. Biasanya layak dimulai dengan itu, karena akan lebih mudah untuk bekerja dengan cara ini. Butuh beberapa saat untuk mencari tahu arti dari huruf-huruf pertama, tetapi masih berhasil membuat sesuatu yang bisa dibaca.

 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); } } 

Perhatikan bahwa liter float tidak mengandung huruf "f", dan bagian pecahan dibuang untuk menghemat ruang. Trik yang sama digunakan di bawah ini, di mana bagian integer dijatuhkan (float x = .5). Yang juga tidak biasa adalah konstruk "for" dengan ekspresi iterasi yang dimasukkan di dalam kondisi break.

Ini adalah fungsi utama yang cukup standar untuk pelacak sinar / lintasan. Vektor kamera diatur di sini dan sinar dipancarkan untuk setiap piksel. Perbedaan antara pelacak sinar dan pelacak jalur adalah bahwa beberapa sinar dipancarkan per piksel dalam TP, yang sedikit bergeser secara acak. Kemudian warna yang diperoleh untuk setiap sinar dalam pixel diakumulasikan dalam tiga kanal float R, B, G. Pada akhirnya, koreksi nada hasil metode Reinhardt dilakukan.

Bagian terpenting adalah sampleCount, yang secara teoritis dapat diatur ke 1 untuk mempercepat rendering dan iterasi. Berikut adalah rendering sampel dengan nilai sampel dari 1 hingga 2048.

Judul spoiler


1



2



4



8



16



32



64



128



256



512



1024



2048

Kode Pembantu




Sepotong kode sederhana adalah fungsi pembantu. Dalam kasus ini, kami memiliki fungsi sepele min (), generator nilai acak dalam interval [0,1] dan boxTest (), yang jauh lebih menarik, yang merupakan bagian dari sistem Constructive Solid Geometry (CSG) yang digunakan untuk memotong dunia. CSG dibahas di bagian selanjutnya.

 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)); } 

Fungsi geometri volumetrik konstruktif




Tidak ada simpul dalam kode. Semuanya dilakukan menggunakan fungsi CSG. Jika Anda tidak terbiasa dengan mereka, maka cukup katakan bahwa ini adalah fungsi yang menggambarkan apakah koordinat ada di dalam atau di luar objek. Jika fungsi mengembalikan jarak positif, maka titik ada di dalam objek. Jarak negatif menunjukkan bahwa titik berada di luar objek. Ada banyak fungsi untuk menggambarkan objek yang berbeda, tetapi demi penyederhanaan, mari kita ambil contoh bola dan dua titik, A dan B.

gambar

 // 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) 

Fungsi testSphere () mengembalikan -1 untuk titik A (yaitu, di luar) dan 1 untuk B (yaitu, di dalam). Tanda pada jarak hanyalah tipuan, yang memungkinkan Anda untuk mendapatkan dua informasi, bukan satu dalam kasus nilai tunggal. Tipe fungsi yang serupa juga dapat ditulis untuk menggambarkan jajaran genjang (inilah yang dilakukan dalam fungsi BoxTest).


  // 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) 

Sekarang mari kita lihat apa yang terjadi jika Anda membalik tanda nilai pengembalian.


  // 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) 

Sekarang kita tidak menggambarkan objek yang solid, tetapi menyatakan seluruh dunia solid dan memotong ruang kosong di dalamnya. Fungsi dapat digunakan sebagai batu bata bangunan, yang bila digabungkan dapat menggambarkan bentuk yang lebih kompleks. Menggunakan operator tambahan logis (fungsi min) kita dapat memotong sepasang persegi panjang satu di atas yang lain dan hasilnya akan terlihat seperti ini.


  // 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) 

Jika Anda memikirkannya, sepertinya ruangan itu sedang kita pelajari, karena ruangan bawah dinyatakan persis seperti ini - dengan bantuan dua jajaran genjang.

Sekarang, setelah menguasai pengetahuan CSG yang kuat, kita dapat kembali ke kode dan mempertimbangkan fungsi basis data, yang merupakan yang paling sulit untuk ditangani.

 #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; } 

Anda dapat melihat di sini fungsi "memotong" jajaran genjang, di mana hanya dua persegi panjang yang digunakan untuk membangun seluruh ruangan (otak kita melakukan sisanya, itu melambangkan dinding). Tangga horisontal adalah fungsi CSG sedikit lebih kompleks menggunakan pembagian sisa. Dan akhirnya, huruf-huruf dari kata PIXAR terdiri dari 15 baris dengan pasangan "asal / delta" dan dua case khusus untuk kurva dalam huruf P dan R.

Ray berjalan




Memiliki basis data fungsi CSG yang menggambarkan dunia, cukup bagi kita untuk melewati semua sinar yang dipancarkan dalam fungsi main (). Ray marching menggunakan fungsi jarak. Ini berarti bahwa posisi pengambilan sampel bergeser maju jarak ke rintangan terdekat.

 // 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; } 

Gagasan ray marching berdasarkan jarak adalah untuk bergerak maju jarak ke objek terdekat. Pada akhirnya, balok akan mendekati permukaan sedemikian rupa sehingga dapat dianggap sebagai titik kejadian.


Perhatikan bahwa marching ray tidak mengembalikan persimpangan sejati dengan permukaan, tetapi perkiraan. Itulah sebabnya marching berhenti di kode ketika d <0,01f.

Menyatukan Semuanya: Pengambilan sampel




Investigasi jalur pelacak hampir selesai. Kami kehilangan jembatan yang menghubungkan fungsi utama () dengan ray marcher. Bagian terakhir ini, yang saya beri nama "Jejak", adalah "otak" tempat sinar memantul atau berhenti, tergantung pada apa yang mereka hadapi.

 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; } 

Saya bereksperimen sedikit dengan fungsi ini untuk mengubah jumlah maksimum pantulan sinar yang diizinkan. Nilai "2" memberi huruf-huruf warna Vantablack indah yang dipernis indah [4] .


1


2


3


4

Kode sumber dibersihkan sepenuhnya




Untuk menyatukan semuanya, saya membuat kode sumber yang benar-benar bersih.

Referensi




[1] Sumber: posting Twitter lexfrench pada 8 Oktober 2018.

[2] Sumber: Wikipedia: Format gambar NetPBM

[3] Sumber: Visualisasi dilakukan pada MacBook Pro paling kuat, 2017

[4] Sumber: Wikipedia: Vantablack

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


All Articles