بحجم بطاقة بريدية فك تشفير التتبع


"لقد فعل ذلك مرة أخرى!" - هذا هو ما حدث لي أول شيء عندما نظرت إلى الجزء الخلفي من نشرة Pixar [1] ، المليئة بالكامل بالكود. تم توقيع مجموعة من الإنشاءات والعبارات في الزاوية اليمنى السفلى من قبل أندرو كينسلر. بالنسبة لأولئك الذين لا يعرفونه ، سأقول: أندرو هو مبرمج اخترع جهاز تتبع بحجم 1337 بايت بحجم بطاقة العمل في عام 2009.

هذه المرة ، توصل أندرو إلى شيء أكثر ضخامة ، ولكن بنتيجة مرئية أكثر إثارة للاهتمام. منذ أن أنهيت كتابتي Game Engine Black Books عن Wolf3D و DOOM ، كان لدي وقت لتعلم الدواوين الشفرة المشفرة. وعلى الفور تقريبًا ، كنت مفتونًا حقًا بالتقنيات المكتشفة فيه. كانت مختلفة تمامًا عن عمل أندرو السابق ، بناءً على جهاز تتبع شعاع "قياسي". لقد كنت مهتمًا بالتعرف على مسيرة الشعاع ، وخصائص الهندسة الحجرية البنّاءة ، وتقديم / مسار مونت كارلو ، بالإضافة إلى العديد من الحيل الأخرى التي استخدمها للضغط على الكود في قطعة صغيرة من الورق.



شفرة المصدر




الجزء الأمامي من المنشور هو إعلان لقسم التوظيف في بيكسار. على الجانب العكسي ، تتم طباعة 2037 بايت من رمز C ++ ، معتمًا بحيث تشغل أصغر سطح ممكن.

#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 

هل هو حتى العمل؟




مع رمز هناك تعليمات لإطلاقها. الفكرة هي إعادة توجيه الإخراج القياسي إلى ملف. بالامتداد ، يمكننا أن نفترض أن تنسيق الإخراج هو تنسيق صورة نصية يسمى NetPBM [2] .

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

 2m58.524s الحقيقي
 المستخدم 2m57.567s
 تميز الكلية 0m0.415s 

بعد دقيقتين وثمانية وخمسين ثانية [3] ، يتم إنشاء الصورة التالية. إنه لأمر مدهش كم هو مطلوب رمز لذلك.


يمكنك استخراج الكثير من الصورة أعلاه. حصى هو علامة واضحة على "مسار التتبع". هذا النوع من العارض يختلف عن التصوير الشعاعي لأنه لا يتم تتبع الأشعة مرة أخرى إلى مصادر الضوء. في هذه الطريقة ، تنبعث آلاف الأشعة لكل بكسل من المصادر ويقوم البرنامج بمراقبتها ، على أمل أن يعثروا على مصدر الضوء. هذه تقنية مثيرة للاهتمام ، أفضل بكثير من تتبع الشعاع ، والتي يمكنها التعامل مع التجسيد المحيط ، والظلال الناعمة ، والمواد الكاوية ، والإشعاع.

سنقوم بتقسيم الكود إلى أجزاء




يؤدي تمرير الإدخال إلى CLion إلى تهيئة الرمز (انظر الإخراج هنا ) ويقسمه إلى أجزاء / مهام أصغر.

  #include <stdlib.h> // card> pixar.ppm 
  # تضمين <stdio.h> 
  # تتضمن <math.h> 

  # حدد عودة R 
  # تعريف عامل التشغيل O 
  typedef تعويم F ؛ typedef int I؛ 
  البنية 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 ( س * ص ، ص * ص. 
  y، z * rz)؛} FO٪ (V r) {R x * r.x + y * r.y + z * rz؛} VO! () {R * this * (1 / sqrtf (* 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 (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 "؛ لـ (I i = 0؛ i <60؛ i + = 4) {V b = V (l [i] -79، l [i + 1] -79) *. 5، e = V (l [ i + 2] -79 ، ل 
  [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)} ؛ من أجل (I i = 2 ؛ i - ؛) {V o = f + a [i] * -1 ؛ د = L (د ، ثور> 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 ؛ م = 1 ؛ F r = L (-L (B (p ، V (-30 ، -. 5 ، -30) ، V (30،18،30)) ، B (ص ، الخامس (-25.17 ، -25) ، الخامس 
  (25،20،25))) ، B (V (fmodf (fabsf (px) ، 8) ، py ، pz) ، V (1.5،18.5، -25)، V (6.5،20،25))) 
  ؛ إذا (r <d) d = r ، m = 2 ؛ F s = 19.9-py ؛ إذا (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 ؛ لـ (؛ 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)) ؛ من أجل (I b = 3 ؛ b - ؛) {I m = M (o ، d ، h ، n) ؛ إذا كسر (! M) ؛ إذا (م == 1) {د = د + ن * ( 
  n٪ d * -2) ؛ o = h + d * .1 ؛ t = t * .2؛} إذا (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 (ع) * s) + V ( 
  1 + g * nx * nx * u، g * v، -g * nx) * (sinf (p) * s) + n * sqrtf (c)؛ o = h + d * .1؛ t = t *. 2 ؛ إذا (i> 0 && M (h 
  + n * .1 ، l ، h ، n) == 3) r = r + t * V (500،400،100) * i؛} إذا (m == 3) {r = r + t * V (50،80،100) ؛ break ؛}} 
  ص ص ؛} 
  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، -xx) * (1./w) ، u (gy * lz-gz * ly ، gz * lx-gx * lz ، gx * ly-gy * lx) ؛ printf ("P \ 
  6٪ d٪ d 255 "، w ، h) ؛ من أجل (I y = h ؛ y - ؛) لـ (I x = w ؛ x - ؛) {V c ؛ من أجل (I p = s؛ p- - ؛) ج = ج + T (ه 
  ،! (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 / o، cz / oz) * 255؛ printf ("٪ c٪ c٪ c"، (I) cx، (I) cy، (I) cz)؛}} 
  // أندرو كينسلر 

يوصف كل قسم بالتفصيل في بقية المقال:
t - الحيل العادية ، - فئة المتجهات ، code - التعليمات البرمجية المساعدة ، database - قاعدة البيانات ، mar - مسيرة راي ، - أخذ العينات ، main - الكود الرئيسي.

الحيل المشتركة مع #define و typedef




تستخدم الحيل الشائعة #define و typedef لتقليل مقدار التعليمات البرمجية بشكل ملحوظ. هنا نشير إلى F = float و I = int و R = return و O = عامل التشغيل. الهندسة العكسية تافهة.

الدرجة الخامسة




بعد ذلك تأتي الفئة V ، التي قمت بإعادة تسميتها إلى Vec (على الرغم من ذلك ، كما سنرى أدناه ، يتم استخدامه أيضًا لتخزين قنوات RGB بتنسيق عائم).

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

لاحظ أنه لا يوجد عامل طرح (-) ، لذا بدلاً من كتابة "X = A - B" ، يتم استخدام "X = A + B * -1". يأتي الجذر التربيعي العكسي مفيدًا في وقت لاحق لتطبيع المتجهات.

الوظيفة الرئيسية




main () هو الحرف الوحيد الذي لا يمكن حجبه لأنه يتم استدعاؤه بواسطة الدالة _start لمكتبة libc. عادةً ما يستحق البدء به ، لأنه سيكون من الأسهل العمل بهذه الطريقة. استغرق الأمر مني بعض الوقت لمعرفة معاني الحروف الأولى ، ولكن لا يزال بإمكاني إنشاء شيء يمكن قراءته.

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

لاحظ أن القيم الحرفية العائمة لا تحتوي على الحرف "f" ، ويتم تجاهل الجزء الكسري لتوفير المساحة. يتم استخدام نفس الخدعة أدناه ، حيث يتم إسقاط الجزء الصحيح (تعويم x = 0.5). من غير المألوف أيضًا إنشاء "for" مع تعبير التكرار الذي يتم إدراجه داخل شرط الاستراحة.

هذه هي الوظيفة الرئيسية القياسية جدا لتتبع الأشعة / المسار. يتم تعيين متجهات الكاميرا هنا وتنبعث الأشعة لكل بكسل. الفرق بين جهاز تتبع الشعاع وجهاز تتبع المسار هو أن العديد من الأشعة تنبعث لكل بكسل في TP ، والتي تحولت بشكل عشوائي قليلاً. ثم يتم تجميع اللون الناتج لكل شعاع في البيكسل في ثلاث قنوات عائمة R و B و G. في النهاية ، يتم إجراء تصحيح الدرجة اللونية لنتائج طريقة راينهارت.

الجزء الأكثر أهمية هو sampleCount ، والذي يمكن من الناحية النظرية تعيينه على 1 لتسريع التقديم والتكرار. فيما يلي نماذج للنتائج مع قيم عينة من 1 إلى 2048.

المفسد العنوان


1



2



4



8



16



32



64



128



256



512



1024



2048

كود المساعد




آخر قطعة بسيطة من التعليمات البرمجية هي وظائف المساعد. في هذه الحالة ، لدينا دالة تافهة min () ، ومولد قيمة عشوائية في الفاصل الزمني [0،1] و boxTest () الأكثر إثارة للاهتمام ، وهو جزء من نظام Constructive Solid Geometry (CSG) المستخدم لقطع العالم. تمت مناقشة CSG في القسم التالي.

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

وظائف الهندسة الحجرية البناءة




لا توجد رؤوس في الكود. كل شيء يتم باستخدام وظائف CSG. إذا كنت غير معتادًا عليها ، فقل ببساطة أن هذه وظائف تصف ما إذا كان الإحداثي داخل الكائن أم خارجه. إذا أرجعت الدالة مسافة موجبة ، فستكون النقطة داخل الكائن. تشير المسافة السالبة إلى أن النقطة تقع خارج الكائن. هناك العديد من الوظائف لوصف الكائنات المختلفة ، ولكن من أجل التبسيط ، لنأخذ على سبيل المثال كرة ونقطتين ، A و B.

الصورة

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

ترجع الدالة testSphere () -1 للنقطة A (أي ، هي خارج) و 1 لـ B (أي ، هي داخل). تعتبر العلامات على المسافات مجرد خدعة ، حيث تتيح لك الحصول على قسمين من المعلومات بدلاً من واحدة في حالة قيمة واحدة. يمكن أيضًا كتابة نوع مماثل من الوظائف لوصف متوازي الاضلاع (هذا هو بالضبط ما يتم تنفيذه في الدالة 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) 

الآن دعونا نرى ما يحدث إذا قلبت علامة القيمة المرجعة.


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

الآن نحن لا نصف كائنًا صلبًا ، ولكننا أعلنا العالم كله صلبًا ونقطع مساحة فارغة فيه. يمكن استخدام الوظائف كطوب بناء ، والتي يمكن عند الجمع بينها وصف أشكال أكثر تعقيدًا. باستخدام عامل الإضافة المنطقية (دالة دقيقة) ، يمكننا قطع زوج من المستطيلات واحدًا فوق الآخر وستبدو النتيجة هكذا.


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

إذا كنت تفكر في الأمر ، فإنه يبدو وكأنه الغرفة التي ندرسها ، لأنه يتم التعبير عن الغرفة السفلية بهذه الطريقة بالضبط - بمساعدة اثنين من متوازي الأضلاع.

الآن ، بعد إتقان المعرفة القوية لـ CSG ، يمكننا العودة إلى الكود والنظر في وظيفة قاعدة البيانات ، والتي هي الأصعب في التعامل معها.

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

يمكنك أن ترى هنا وظيفة "الاستغناء" عن متوازي الاضلاع ، حيث يتم استخدام مستطيلين فقط لبناء الغرفة بأكملها (يعمل عقولنا بالباقي ، ويمثل الجدران). السلم الأفقي عبارة عن وظيفة CSG أكثر تعقيدًا باستخدام تقسيم الباقي. وأخيرًا ، تتكون أحرف كلمة PIXAR من 15 سطرًا مع زوج "أصل / دلتا" وحالتين خاصتين لمنحنيات في الحرفين P و R.

راي يسير




امتلاك قاعدة بيانات لوظائف CSG التي تصف العالم ، يكفي أن نتخطى جميع الأشعة المنبعثة في الوظيفة الرئيسية (). راي مسيرة يستخدم وظيفة المسافة. هذا يعني أن موضع أخذ العينات ينتقل للأمام مسافة إلى أقرب عقبة.

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

إن فكرة مسيرة الشعاع على أساس المسافة هي المضي قدمًا إلى أقرب كائن. في النهاية ، ستقترب الحزمة من السطح لدرجة أنه يمكن اعتبارها نقطة وقوع.


لاحظ أن مسيرة الشعاع لا تُرجع تقاطعًا حقيقيًا مع السطح ، ولكن تقريبًا. هذا هو السبب في توقف المسيرة في الكود عند d <0.01f.

وضع كل ذلك معا: أخذ العينات




التحقيق في مسار التتبع قد اكتمل تقريبًا. نحن نفتقد الجسر الذي يربط الوظيفة الرئيسية () بمسيرة الشعاع. هذا الجزء الأخير ، الذي قمت بتسميته "تتبع" ، هو "الدماغ" الذي ترتد فيه الأشعة أو تتوقف ، حسب ما تواجهه.

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

لقد جربت قليلاً مع هذه الوظيفة لتغيير الحد الأقصى لعدد انعكاسات الحزمة المسموح بها. القيمة "2" تعطي الحروف لون Vantablack المطلي بشكل جميل بشكل مدهش [4] .


1


2


3


4

تنظيفها تماما شفرة المصدر




لوضع كل شيء معًا ، قمت بإنشاء شفرة مصدر نظيفة تمامًا.

المراجع




[1] المصدر: منشور Twitter lexfrench في 8 أكتوبر 2018.

[2] المصدر: تنسيق صورة NetPBM

[3] المصدر: أداء التصور على أقوى جهاز MacBook Pro ، 2017

[4] المصدر: ويكيبيديا: فانتابلاك

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


All Articles