用C++畫光(二):折射和色散

相關文章目錄

用C++畫光(一):基礎和反射

用C++畫光(二):折射和色散

用C++畫光(三):四叉樹

用C++畫光(四):更好的色散


這次嘗試了光的折射和簡易版的色散。

應該算是 @Milo Yip 大大的 用 C 語言畫光(五):折射 的課後作業。

繼續用上一篇用C++畫光(一)提到的光線幾何求交的方法進行光線跟蹤。

利用斯涅爾定律(即入射角與反射角的sin比例關係),可以得出根據入射角度、折射率、法線計算出射角度的公式:

bar{o} = eta bar{i} - (eta(bar{i}·bar{n})+sqrt{1-eta^{2}(1-(bar{i}·bar{n})^2)})bar{n}

其中, eta 為介質折射率, bar{i} 為入射方向, bar{n} 為法線方向。當計算從介質外向介質內的折射時,折射率需要求倒數。

推導過程這裡不再複述。詳見C 語言畫光(五):折射。

計算折射的代碼:

Color Refract(Entity* ent, Point inter, Vector d, Vector normal, int depth)n{ntif (depth > MAX_DEPTH || ent->GetRefractivity() == 0.f ) return{ 0.f, 0.f,0.f };ntfloat idotn = d * normal;ntfloat ri = ent->GetRefractIndex();ntfloat k, a;ntif (idotn > 0.f)t//從內向外折射nt{nttk = 1.f - ri*ri*(1.f - idotn*idotn);nttif (k < 0.f) return{ 0.f, 0.f, 0.f }; //全反射ntta = ri * idotn - sqrtf(k);nt}ntelse //從外向內折射nt{nttri = 1.f / ri;nttk = 1.f - ri*ri*(1.f - idotn*idotn);ntta = ri * idotn + sqrtf(k);nt}ntVector refract = d*ri - normal*a;ntreturn GetColor(inter + refract * BIAS, refract, color, depth) * ent->GetRefractivity();n}n

並對主迭代函數GetColor做相應的修改,加上折射量

Color GetColor(Point p, Vector d, int color, int depth = 0)t//獲取p點從d方向收到的emissiven{nt......//計算碰撞的ent,交點interntVector normal = ent->GetShape()->GetNormal(inter);ntColor reflect = Reflect(ent, inter, d, normal, depth + 1);ntColor refract = Refract(ent, inter, d, normal, depth + 1);ntreturn ent->GetEmissive() + reflect + refract;n}n

即可得到折射的結果

(這個基本是復現了一下C 語言畫光(五):折射中的例子。這裡體會到了 @Milo Yip大大把光源放在畫布外的良苦用心。自己嘗試了把光源放進畫布里,我的狗眼差點沒保住。)

色散

色散現象,是由於不同頻率的光有不同的折射率導致的。每考慮一種不同的頻率的光(一種不同顏色),就要增加一種折射的追蹤,這邊沒有想到太好的對較多顏色進行區分的方法,僅僅進行了簡化,考慮了對r,g,b三種顏色賦予不同折射率,來模擬色散現象。

此時,需要對摺射率定義和遞歸顏色計算函數進行一些修改。

Entity折射率定義成長度為3的數組,分別代表r,g,b三種顏色的折射率。

class Entityn{ntShape* m_shape;ntColor m_emissive;ntfloat m_reflectivity;tt//不考慮漫反射ntfloat m_refractivity;tt//一般的,要求reflectivity + refractivity <= 1ntfloat m_refract_index[3];t//認為每種顏色可以有不同的折射率n}n

在第一次折射時,則需要將光線分成三個方向追蹤,三個方向分別計算折射光的r,g,b值。在下圖中,左側是實際光線的簡化折射模擬,右側則是光線追蹤分三個方向計算示意圖。事實上,僅僅在第一次折射計算時,需要進行1裂3操作,後續則按照原來的方式僅追蹤一個方向即可。因此計算量不會增加「太多」。

遞歸顏色計算函數的相應修改:

Color GetColor(Point p, Vector d, int color_index, int depth = 0)t//獲取p點從d方向收到的emissiven{nt......ntColor refract = { 0.f, 0.f, 0.f };ntif (color_index >= 3)nt{nttrefract.r = Refract(ent, inter, d, normal, 0, depth + 1).r;nttrefract.g = Refract(ent, inter, d, normal, 1, depth + 1).g;nttrefract.b = Refract(ent, inter, d, normal, 2, depth + 1).b;nt}ntelsenttrefract = Refract(ent, inter, d, normal, color_index, depth + 1);ntreturn ent->GetEmissive() + reflect + refract;n}n

結果示例:

由於僅對三種顏色賦予不同折射率,因此存在一定的光束分離現象

聚光燈

為了取得與示意圖類似的三稜鏡色散效果,這裡設計了一種新的Entity:聚光燈,讓光線僅往較小的角度發射。代碼非常簡單,僅需要在求交時根據角度做一次預過濾

class SpotLight :public Entityn{ntVector m_dir;tt//聚光燈主方向ntfloat m_cosa;tt//聚光燈角度範圍的cosnt...ntbool Intersect(Point p, Vector d, Point &inter)nt{nttif (d*(-m_dir) < m_cosa)t//預過濾角度方向在照射範圍外的光線ntttreturn false;nttelsentttreturn m_shape->Intersect(p, d, inter);nt}n};n

聚光燈效果圖:

另外,為了提高Debug的效率,可以在圖上預畫出形狀顏色,再畫出光線軌跡,會更清楚一些,畫形狀顏色由於不需要對每個點進行光線跟蹤,僅需要判斷是否在Shape內部,因此基本不花額外的時間。Debug結果例如:

最後貼一下三稜鏡的折射效果:

代碼詳見lilypuye/cppLight2d

後續可能會增加更多Entity並考慮四叉樹管理

總結:我這波大概算是顏值驅動的編程。結果知乎壓我圖。不開心。


推薦閱讀:

Demosplash演示聚會將在卡內基梅隆大學舉辦
在《硬影像》與羅登導演聊渲染技術
Image Based Lighting
用Atlas實現多張貼圖混合的地形及注意事項
為了忘卻的紀念:析OpenGL史上第二偉大的擴展,DSA

TAG:编程 | CC | 计算机图形学 |