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 2Finding 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) 的,這可以怎樣解決哩?

3DS轉換矩陣

經過半天的勞力,終於成功載入轉換矩陣 (0x4160 trunk),全靠 lib3ds 的原碼。

// Trunk 0x4160 comes before 0x4120,
// during the loading of 0x4160 we got the information to
// change the clockwise/anti-clockwise triangle winding or not
// and apply this information during the loading of 0x4120
bool invertTriangleWinding = false;

// ...

// Loading the local coordinates chunk
// It's base from lib3ds: http://www.lib3ds.org
case 0x4160:
{
// We are using row major matrix
Mat44f matrix = Mat44f::cIdentity;
for(size_t i=0; i<4; ++i)
mStream->read(matrix.row[i], sizeof(float) * 3);

// Flip X coordinate of vertices if mesh matrix has negative determinant
if((invertWinding = (matrix.determinant() < 0)) == true) {
Mat44f inv = matrix.inverse();

matrix.m00 = -matrix.m00;
matrix.m01 = -matrix.m01;
matrix.m02 = -matrix.m02;
matrix.m03 = -matrix.m03;

matrix = (inv * matrix).transpose();

size_t vertexCount = getVertexCount();
Vec3f* vertex = getVertexPointer();

for(size_t i=0; i<vertexCount; ++i) {
// Transform tmp using matrix, where matrix[3] holds the translation
#ifdef FLIP_YZ_AXIS
Vec4f tmp(vertex[i].x, -vertex[i].z, vertex[i].y, 0);
tmp = (matrix * tmp) + matrix[3];
vertex[i] = Vec3f(tmp.x, tmp.z, -tmp.y);
#else
Vec4f tmp(vertex[i].x, vertex[i].y, vertex[i].z, 0);
tmp = (matrix * tmp) + matrix[3];
vertex[i] = Vec3f(tmp.x, tmp.y, tmp.z);
#endif
}
}
} break;

// ...

// Loading the face description (index values) trunk
case 0x4120:
{
uint16_t faceCount = 0;
uint16_t* indexArray = nullptr;

// ...

if(invertTriangleWinding) {
for(size_t i=0; i<faceCount*3; i+=3)
std::swap(indexArray[i], indexArray[i+2]);
}
} break;

2008年9月7日星期日

3DS 文件格式


於 Mesh Factory 下載模型

完成了 3DS 文件的加載器已有多個星期,到今天才有點時間張貼出來。
其實 3DS 已是一個很古老的格式,它的材質參考路徑僅支援 8.3 格式, 是 Dos 年代的產物。但基於這格式的廣泛流傳和結構簡單,因此儘管 Collada 是大勢所趨,我還是決意使用它作為引擎裡的第一個模型加載器。這樣我就可以快點進行其他方面的進修,待其他模組有了原型以後才加入其他加載器。

我的實作很基本,只支援頂點,索引,標準材質和貼圖;所有的法線得自行計算,雖然我已對 Smoothing Group 加以支援,但有些位置的效果還是顯得不平滑。相信最好的辦法都是直接從 DCC 工具中讀入法線向量,可惜 3DS 沒有這資訊。另外有好一些物體的位置錯了,似乎純粹讀入頂點位置並不足夠,還以為 3DS 格式用不著轉換矩陣。

最後我當然把加載器和之前設計好的資源管理和漸進裝載模組整合在一起,當中的細節留待日後再作詳述。

2008年9月3日星期三

谷歌瀏覽器 Chrome


作為全球資訊網的龍頭大哥谷歌 Google, 推出自家的瀏覽器是遲早的事. 這位新晉的瀏覽器名叫 Chrome, 還是 Beta 階段. 它的宣傳標題是 "一個方塊,無所不包", 正合谷歌一貫使用網上平台的理念; 而一個好的瀏覽器將會是這革命的催化劑.

試用後感覺良好, 介面簡單反應夠快. 而我最喜歡的就是把菜單/按鈕等等介面壓縮成不到80圖素的垂直空間裏, 就連標題棒都省去了.

至於內部構和設計理念, 谷歌用了漫畫形式展示出來. 從中得知谷歌為了避開記憶體洩漏以及安全性的問題, 挑選了一個 Tab, 一個進程 (Process) 的設計. 這有點兒走回頭路的感覺, 但不失為簡單快捷的方案; 何況 Chrome 創建新 Tab 的速度奇快, 沒有半點被創建進程的開銷所拖慢.

目前 Chrome 和 Firefox 相比下還缺少一眾好用的插件, 但相信新一輪瀏覽器之戰又開始了.