顯示包含「ssao」標籤的文章。顯示所有文章
顯示包含「ssao」標籤的文章。顯示所有文章

2009年3月6日星期五

Accumulative Screen Space Ambient Occlusion

This is my attempt to combine Real-Time Reprojection Cache and Screen Space Ambient Occlusion. Using such caching scheme, the spatio-temporal coherence nature of the SSAO algorithm can be exploited. You can download the demo with shader source here.


Add Video
The name "Accumulative SSAO" comes from the fact that the occlusion value is accumulated and averaged over a number of frames. The algorithm itself is quite independent of how the occlusion is calculated and here I will assume the reader is familiar with SSAO implementation such as those from Crysis and Startcraft II.

The pipeline

For every frame,

  1. The scene was rendered using deferred shading technique, producing the color, normal and depth buffers.
  2. A number of random vectors were generated in CPU (where in usual SSAO these vectors only generated once in the program).
  3. The normal and depth buffer are then utilized to calculate the occlusion value in the SSAO pass.
  4. Instead of writing the occlusion value to the final output, it would combine with the previous frame's accumulated occlusion value and then written to a second accumulation buffer.
  5. A blur pass can optionally apply to the most updated accumulated occlusion buffer.
  6. The color buffer was then combined with the occlusion value to product the final result, also the two accumulation buffers were switched with each other.
Re-projection

The re-projection happens in the SSAO pass when it tries to access the previous frame's occlusion value. Having the eye-space 3d position for each pixel, we can transform that into a texture coordinate by using a matrix (and a perspective division afterward), lets call it the delta matrix. This matrix is calculated on CPU as:
bias = translation(0.5, 0.5, 0.5) * scale(0.5, 0.5, 0.5)
deltaMatrix = bias * lastFrameProjection * lastFrameView * currentFrameView.inverse()

In simple words, for each current frame's pixel, we are trying to locate their corresponding pixel coordinate on the last frame. If there is no camera movements, the two coordinates should be the same.

Accumulative AO

With the re-projection working, the current frame's occlusion value can be combined with the previous one with the following accumulation formula:
currentAo = currentAo / 30.0 + lastFrameAo * 29.0 / 30.0;

In order to make something interesting for the above equation, the current occlusion value should not be the same as the previous one. Therefore, a new set of sampling position should be generated for each frame, this can be done by re-generating the random unit sphere samples or the dithering texture every frame. In a loosely sense, it is doing a Monte Carlo Integration over the time domain. To achieve better visual quality, more frames should be taken over the time.

As each frame's AO value will also depends on the last few frames, there will be some time delay for the AO to become up-to-date in a dynamic scene. However, by changing the numerator and denominator in the equation, the trade-off between quality and responsiveness can be adjusted.

Cache-miss consideration

Up to now the cache miss problem of the re-projection is not yet addressed. A cache miss will happen if somewhere in the scene that cannot be seen before becoming visible now, due to camera or object movement. Such a cache miss can be detected by comparing the current pixel's depth value with it's re-projected counterpart. If the two values differed by a certain threshold, a cache miss is detected. And to do this, the last frame's depth value is needed. Instead of using a separated texture to store the last frame's depth value, the depth can be encoded and stored together with the accumulative AO value into a 32-bit texture.
// Encode a float value into 3 bytes
// The input value should be in the range of [0, 1
// Reference: http://www.ozone3d.net/blogs/lab/?p=113
vec3 packFloatToVec3i(const float value)
{
 const vec3 bitSh = vec3(256.0 * 256.0, 256.0, 1.0);
 const vec3 bitMsk = vec3(0.0, 1.0/256.0, 1.0/256.0);
 vec3 res = fract(value * bitSh);
 res -= res.xxy * bitMsk;
 return res;
}
float unpackFloatFromVec3i(const vec3 value)
{
 const vec3 bitSh = vec3(1.0/(256.0*256.0), 1.0/256.0, 1.0);
 return dot(value, bitSh);
}

If there was a cached miss, the accumulative AO will be discarded and the instance AO value is used instead. Of course more samples can be taken in this frame to reduce the visual impact of the cache miss.

Discussion/improvements
  • Currently a new independent set of random samples were generated for the above video demo. Other random sample over time generation method may reduce the noise.
  • As some of the re-projection cache scheme suggested, a cache value should be cleared after a certain period of time to avoid in-stability and provide a better response to dynamic environment, and this is done here by the accumulation formula.
  • To reduce cache miss due to object movement, each object's last transformation matrix can also be incorporated into the algorithm.
  • The depth encoding scheme also make the blur pass much more efficient.
Conclusion

The explained algorithm provides a new way to improve the quality and efficiency of traditional SSAO by using the result from a number of frames instead of one. It also opens up more parameters and sampling patterns to explore with.

2009年1月15日星期四

SSAO Demo

花了一點時間整理好我的 SSAO Demo,請按這裡下載。請各位多給意見 :)

操縱方法:
  • 鏡頭移動:W、A、S、D、Page up、Page down
  • 鏡頭方向:滑鼠左鍵
  • SSAO開關:F1,默認值:開
  • 銀幕減半開關:F2,默認值:開
  • 增大/減少模糊操作次數:F3/Shift+F3,默認值:2
  • 漫射材質開關:F4,默認值:關
  • 增大/減少閉塞半徑:Shift+F5/F5
後記:
  • 看過 R5 Demo 後才覺悟增大 blur pass 次數和 blur kernel size 的分別。 Orz
  • 把 depth encode 到三個 8-bit integer,那麼一塊 32-bit 的 RGBA render target 就可以運載 occlusion value 和 depth 到模糊操作中,從而大大減少 texture fetch 的數量。
相關文章

2008年12月31日星期三

新年前的 SSAO

相信很多朋友都趁年尾來一篇日誌,我也來熱鬧一番 ^.^
上一篇的 SSAO 日誌距今已有三個月;之後更改了演算法,本想多作改進和包裝成 demo 才放進來,但現在手頭上還正在建造一個給 OpenGl 的 Effect 架構,此刻只好放些 Screen shot 好了。

演算法和 星海爭霸2 的相近,但我對 Image processing 的學識尚淺,Bilateral (edge-preserving) blur 的部分還有問題。

最後祝大家新年快樂。


沒有SSAO, 900 fps


屏幕 1/4 SSAO with dither, 252 fps


屏幕 1/4 SSAO with dither + blur, 233 fps

2008年9月22日星期一

SSAO 新進展

Yeah! 利用了法線緩衝所提供的資訊後, SSAO (屏幕空間環境光遮蔽) 的效果迫真了許多。
開始感受到電腦繪圖算法的迷人之處,可惜再沒有人和我分享這份喜悅sad。




讓我嘗試簡單地解釋它的原理吧。
螢幕中的每一像素都會和它周圍的 N 個像素作比較,比較時有兩個因數需要考慮
  1. 兩像素於三圍空間中的位置;深度較淺的像素會遮蔽較深的像素,而遮蔽的程度就取決於距離。

  2. 兩像素的法線內積 (Dot product);面向面的像素會比面向同一方向的像素較接觸不到外來的光線。
至於怎樣對周圍的 N 個像素取樣,就是整個算法中最令人頭痛的問題。當然取樣越多效果越理想,但實際經驗告訴大家 N 只可以不大於 32 左右。隨著取樣的數量受限,而又希望有比較廣闊的取樣範圍 (位置較遙遠的像素都可互相影響),可用一些隨機取樣模式;不過暫時我只用了一個十字形的取樣模式,只要取樣範圍不太大是可以接受的。


uniform sampler2DRect texColor; // Color texture
uniform sampler2DRect texDepth; // Depth texture
uniform sampler2DRect texNormal;// Normal texture
uniform vec2 camerarange = vec2(1.0, 500);

varying vec2 texCoord;
const float aoCap = 1.0;
float aoMultiplier = 1000.0;

float pw = 1.0; // Use (1.0 / screensize.x) for GL_TEXTURE2D
float ph = 1.0;

float readDepth(in vec2 coord)
{
float nearZ = camerarange.x;
float farZ = camerarange.y;
float posZ = texture2DRect(texDepth, coord).x;

return (2.0 * nearZ) / (nearZ + farZ - posZ * (farZ - nearZ));
}

vec3 readNormal(in vec2 coord)
{
return normalize(2 * (texture2DRect(texNormal, coord).xyz - 1));
}

float compareDepths(in float depth1, in float depth2)
{
float depthDiff = depth1 - depth2;
const float aorange = 10.0; // Units in space the AO effect extends to (this gets divided by the camera far range)
float diff = clamp(1.0 - depthDiff * (camerarange.y - camerarange.x) / aorange, 0.0, 1.0);
return min(aoCap, max(0.0, depthDiff) * aoMultiplier) * diff;
}

float calAO(float depth, vec3 normal, float dw, float dh)
{
vec2 coord = vec2(texCoord.x + dw, texCoord.y + dh);
float angleFactor = 1 - dot(normal, readNormal(coord));

if(length(normal) == 0)
angleFactor = 0;

return angleFactor * compareDepths(depth, readDepth(coord));
}

void main(void)
{
float depth = readDepth(texCoord);
float ao = 0.0;

vec3 normal = readNormal(texCoord);

for(int i=0; i<8; ++i) {
ao += calAO(depth, normal, pw, ph);
ao += calAO(depth, normal, pw, -ph);
ao += calAO(depth, normal, -pw, ph);
ao += calAO(depth, normal, -pw, -ph);

pw *= 1.4;
ph *= 1.4;
aoMultiplier /= 1.5;
}

ao *= 2.0;

gl_FragColor = vec4(1.0 - ao) * texture2DRect(texColor, texCoord);
}



相關文章

2008年9月14日星期日

《星海爭霸2》引擎技術解析



繼 CryEngine 2 的 Finding Next Gen 之後,一向不與學術界為伍的 Blizzard 也不甘示弱;於 Siggraph 08 發表了一篇論文,當中的內容頗為深入。
期望星海爭霸2可快點推出。

2008年9月13日星期六

初嚐 Shader 編程



完成基本的 Shader 類別後,一口氣連 Multiple Render Target (MRT) 和 Screen Space Ambient Occlusion (SSAO) 都攪定了。這叫 SSAO 的技術是近一年電腦遊戲繪圖領域的新寵兒;它的原理是利用深度緩衝 (Depth Buffer) 計算出當前考慮中的像素和它周圍的像素,於三圍空間中的相互關係,再加上法線緩衝的話,就可以知道這像素有沒有被其他像素所 "遮蔽"。

暫時我的實作只用上了深度緩衝,算不上真正的 SSAO,至多是一個邊緣強調器;但出來的效果也不錯,可凸顯出物件的層次感。現有的實作會繼續改進之餘,亦會留下來給低級別的顯示卡使用。

Vertex shader code:

// Screen space ambient occlusion
// Reference:
// http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=236698&fpart=1
// http://www.4gamer.net/games/047/G004713/20080223007/screenshot.html?num=002
// http://rgba.scenesp.org/iq/computer/articles/ssao/ssao.htm
// http://meshula.net/wordpress/?p=145

varying vec2 texCoord;

void main(void)
{
gl_Position = ftransform();
texCoord = gl_MultiTexCoord0.xy;
gl_FrontColor = gl_Color;
}


Pixel shader code:


uniform sampler2D texColor; // Color texture
uniform sampler2D texDepth; // Depth texture

uniform vec2 camerarange = vec2(1.0, 500);
uniform vec2 screensize;

varying vec2 texCoord;

float readDepth(in vec2 coord)
{
return (2.0 * camerarange.x) /
(camerarange.y + camerarange.x - texture2D(texDepth, coord).x * (camerarange.y - camerarange.x));
}

void main(void)
{
float depth = readDepth(texCoord);
float d;

float pw = 1.0 / screensize.x;
float ph = 1.0 / screensize.y;

float aoCap = 1.0;

float ao = 0.0;

float aoMultiplier = 1000.0;

float depthTolerance = 0.0001;

for(int i=0; i<4; ++i)
{
d = readDepth(vec2(texCoord.x + pw, texCoord.y + ph));
ao += min(aoCap, max(0.0, depth - d - depthTolerance) * aoMultiplier);

d = readDepth(vec2(texCoord.x - pw, texCoord.y + ph));
ao += min(aoCap, max(0.0, depth - d - depthTolerance) * aoMultiplier);

d=readDepth(vec2(texCoord.x + pw, texCoord.y - ph));
ao += min(aoCap, max(0.0, depth - d - depthTolerance) * aoMultiplier);

d = readDepth(vec2(texCoord.x - pw, texCoord.y - ph));
ao += min(aoCap, max(0.0, depth - d - depthTolerance) * aoMultiplier);

pw *= 2.0;
ph *= 2.0;
aoMultiplier /= 2.0;
}

ao /= 16.0;

gl_FragColor = vec4(1.0 - ao) * texture2D(texColor, texCoord);
}


最後還有一些未解決的問題,是關於 MRT 的;話說有些顯示卡並未支援非二乘方大小的材質緩衝,因此有必要使用 GL_TEXTURE_RECTANGLE_ARB,可惜用了這材質格式後 Pixel Shader 又神奇地把遮蔽量計錯了。看來 Shader 的除錯方法還要好好領會。

還有,材質緩衝是不支援 Fullscreen Anti-aliasing (FSAA) 的,這可以怎樣解決哩?