本篇講一下相交檢測的優化。有兩個措施。
線段相交檢測
之前的檢測都是檢測光線的終點是否在物體內。我們可以嘗試檢測光線的線段是否與物體相交。
比如說有一個非常薄的物體,光線差不多垂直於它的表面。如果用普通的方法的話,這個平面可能就會被光線跳過了。
我們將一個像素的厚度看做一維數軸上的一條線段,起點是其深度。同時將光線的起點、終點的深度值也用同樣的方法看做一條線段。此時我們去檢測這兩條線段是否有重合。有的話則證明相交。
用這種方法可以解決薄的物體被跳過的問題。
bool intersect(float raya, float rayb, float2 sspt) { //raya rayb是光線兩端的深度值,sspt是屏幕空間的坐標點。
float screenPCameraDepth = Linear01Depth(tex2Dlod(_CameraDepthTexture, float4(sspt / 2 + 0.5, 0, 0)));
float backZ = tex2Dlod(_BackfaceTex, float4(sspt / 2 + 0.5, 0, 0)).r;
if (raya > rayb) { //因為光線方向不定(可能朝向+z或者-z)需要排序
float t = raya;
raya = rayb;
rayb = t;
}
return raya < backZ && rayb > screenPCameraDepth;
}
為了優化效果,我們在計算光線的屏幕空間的坐標時,可以取光線兩個端點的中間點,投影到屏幕上作為采樣點。
二分搜索優化
大部分情況下,我們的每個像素的采樣次數不會很高。此時采樣的質量會比較差。我們可以引入二分搜索,當檢測到一個光線的相交時,我們在這段光線內部進行二分搜索,尋找精確的相交點。
這個圖不好截,我找了kode80博客里的一張圖進行說明。
值得注意的是,二分搜索只能對成功相交的光線進行優化,本身沒有相交的是無法進行優化的。
以下代碼中涉及到一些下篇的變量,但是大體意思很明了,即退回一段光線,然后在退回的區間中尋找相交。
if (intersect(screenPTrueDepth,prevDepth, screenPCurrent - dScreenPCurrent/2)){
#if 1 //二分搜索優化
float gapSize = PIXEL_STRIDE;
float2 screenPBegin = screenPCurrent - dScreenPCurrent; //回退
float oneOverZBegin = oneOverzCurrent - dOneOverZCurrent;
prevDepth = 1 / oneOverZBegin / -_ProjectionParams.z;
UNITY_LOOP
for (int j = 0; j < 10 && gapSize > 1.0; j++) {
gapSize /= 2;
dScreenPCurrent /= 2;
dOneOverZCurrent /= 2;
screenPCurrent = screenPBegin + dScreenPCurrent;
oneOverzCurrent = oneOverZBegin + dOneOverZCurrent;
screenPTrueDepth = 1 / oneOverzCurrent / -_ProjectionParams.z;
if (intersect(screenPTrueDepth, prevDepth, screenPCurrent)) { //命中了,起點不用動。(長度縮短一半即可)
}
else { //沒命中,將起點移動到中間。
prevDepth = screenPTrueDepth;
screenPBegin = screenPCurrent;
oneOverZBegin = oneOverzCurrent;
}
}
#endif
hitPixel = screenPCurrent / 2 + 0.5;
rayPercent = (float)i / STEP_COUNT;
return true;