2008年10月31日星期五

陰影映射

完成了紋理投影後,製作基本陰影映射就如吃生一樣容易。




// Pixel shader

varying vec3 normal, lightDir, halfVector;
varying vec2 colorCoord;
varying vec4 shadowCoord;
uniform sampler2D colorTex;
uniform sampler2DShadow shadowTex;

// Light intensity inside shadow
const float shadowIntensity = 0.5;

// Should be supplied as uniform
const float shadowMapPixelScale = 1.0 / float(2048);
const int pcfSize = 1; // The pcf filtering size, 0 -> 1x1 (no filtering), 1 -> 3x3 etc

void main(void)
{
vec4 diffuse = gl_FrontLightProduct[0].diffuse;
vec4 specular = gl_FrontLightProduct[0].specular;
vec4 ambient = gl_FrontLightProduct[0].ambient;
vec3 n = normalize(normal);
float NdotL = max(dot(n, lightDir), 0.0);

diffuse *= NdotL;
vec3 halfV = normalize(halfVector);
float NdotHV = max(dot(n, halfV), 0.0);
specular *= pow(NdotHV, gl_FrontMaterial.shininess);

// Get the shadow value, let the hardware perform perspective divide,
// depth comparison and 2x2 pcf if supported.
// float shadowValue = shadow2DProj(shadowTex, shadowCoord).r;

// Perform PCF filtering
float shadowValue = 0.0;
for(int i=-pcfSize; i<=pcfSize; ++i) for(int j=-pcfSize; j<=pcfSize; ++j)
{
vec4 offset = vec4(i * shadowMapPixelScale, j * shadowMapPixelScale, 0, 0);
shadowValue += shadow2DProj(shadowTex, shadowCoord + offset).r;
}
shadowValue /= (2 * pcfSize + 1) * (2 * pcfSize + 1);

float shadowSpecularFactor = shadowValue == 0 ? 0 : 1;
float shadowDiffuseFactor = min(1.0, shadowIntensity + shadowValue);

gl_FragData[0] = shadowSpecularFactor * specular +
(ambient + shadowDiffuseFactor * diffuse) * vec4(texture2D(colorTex, colorCoord).xyz, 1);
}


當然,還有許多的陰影技術可以嘗試;我比較臨感興趣的有:


相關文章

紋理投影

為了製作陰影映射 (Shadow mapping),先來一個紋理投影 (Projective texture)。




製作紋理投影的關鍵是紋理投影矩陣,是它把物體的世界座標轉換成投影空間的紋理座標;以下是 OpenGL fixed pipeline 的實作:


// The following code assums you have already applied the camera's view matrix
// to the model-view matrix stack
setupViewMatrix();

// Use another texture unit to avoid conflit with the color texture of the model
glActiveTexture(GL_TEXTURE1);

// You can choose between GL_MODULATE and GL_ADD
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);

// Matrix44 is just a simple matrix 4x4 class, but remember opengl use column major layout
// The bias matrix is to map from clip space [-1, 1] to texture space [0, 1]
Matrix44 biasMatrix = Matrix44(
0.5f, 0, 0, 0.5f,
0, 0.5f, 0, 0.5f,
0, 0, 0.5f, 0.5f,
0, 0, 0, 1.0f);

Matrix44 projectorProjection, projectorView;

// Setup projectorProjection and projectorView according to
// how you want the texture to be projected on the scene
// ...

Matrix44 textureMatrix = biasMatrix * projectorProjection * projectorView;

// Preform a transpose so that we get the rows of the matrix rather than columns
textureMatrix = textureMatrix.transpose();

// A post-multiply by the inverse of the CURRENT modelview matrix is applied
// by opengl automatically to the eye plane equations we provide.
// Therefor, it is important to enable these texture coordinate generation
// before appling any model-world matrix transform
glTexGenfv(GL_S, GL_EYE_PLANE, textureMatrix[0]); // Row 0
glTexGenfv(GL_T, GL_EYE_PLANE, textureMatrix[1]); // Row 1
glTexGenfv(GL_R, GL_EYE_PLANE, textureMatrix[2]); // Row 2
glTexGenfv(GL_Q, GL_EYE_PLANE, textureMatrix[3]); // Row 3

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

// Enable automatic texture coordinate generation
// Note that the R and Q component may not be used in simple projective texture
// but they are needed for shadow mapping
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glEnable(GL_TEXTURE_GEN_Q);

// Bind the projector's texture
glBindTexture(GL_TEXTURE_2D, textureHandle);

// You may move the clamp setting to where you initialize the texture
// rather than setting up every frame
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP);

// Set the active texture back to the model's color texture
glActiveTexture(GL_TEXTURE0);

// For each model:
// Apply any world transform for your model
// Draw the model
// End


當我試圖把上述的碼轉移到 glsl,我遇到了一個沒有多被紋理投影項目中詳述的問題,那就是如何在 glsl 裡得到物體的世界座標。這個問題沒有在 fixed pipeline 中出現是因為早於應用物體 - 世界矩陣 (Model-world matrix) 之前,那紋理投影矩陣已計算恰當。縱然 glsl (其實是整個 OpenGL) 沒有單獨的物體 - 世界矩陣可供查詢,我們可以把攝像機的視圖矩陣乘以 gl_ModelViewMatrix 求出物體 - 世界矩陣。


glActiveTexture(GL_TEXTURE1);

Matrix44 biasMatrix = Matrix44(
0.5f, 0, 0, 0.5f,
0, 0.5f, 0, 0.5f,
0, 0, 0.5f, 0.5f,
0, 0, 0, 1.0f);

// We need the camera's view matrix inverse in order to obtain the model-world
// transform in glsl
Matrix44 projectorProjection, projectorView, cameraView;

// Setup projectorProjection, projectorView and cameraView
// ...

Matrix44 textureMatrix =
biasMatrix * projectorProjection * projectorView * cameraView.inverse();

// Set up the texture matrix
glMatrixMode(GL_TEXTURE);
glLoadMatrixf(textureMatrix.getPtr());
glMatrixMode(GL_MODELVIEW);

glBindTexture(GL_TEXTURE_2D, textureHandle);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP);

glActiveTexture(GL_TEXTURE0);

// For each model:
// Apply any world transform for your model
// Draw the model
// End


// Vertex shader:
varying vec3 normal, lightDir, halfVector;
varying vec2 colorCoord;
varying vec4 projectiveCoord;

void main(void)
{
gl_Position = ftransform();
normal = gl_NormalMatrix * gl_Normal;
lightDir = normalize(gl_LightSource[0].position.xyz);
halfVector = normalize(gl_LightSource[0].halfVector.xyz);

colorCoord = gl_MultiTexCoord0.xy;

// gl_TextureMatrix[1] should contains the inverse of the view matrix,
// resulting a model matrix when combining with gl_ModelViewMatrix
projectiveCoord = gl_TextureMatrix[1] * gl_ModelViewMatrix * gl_Vertex;
}

// Pixel shader:
varying vec3 normal, lightDir, halfVector;
varying vec2 colorCoord;
varying vec4 projectiveCoord;
uniform sampler2D colorTex;
uniform sampler2D projectiveTex;

void main(void)
{
vec4 diffuse = gl_FrontLightProduct[0].diffuse;
vec4 specular = gl_FrontLightProduct[0].specular;
vec4 ambient = gl_FrontLightProduct[0].ambient;
vec3 n = normalize(normal);
float NdotL = max(dot(n, lightDir), 0.0);

diffuse *= NdotL;
vec3 halfV = normalize(halfVector);
float NdotHV = max(dot(n, halfV), 0.0);
specular *= pow(NdotHV, gl_FrontMaterial.shininess);

gl_FragData[0] = specular + (ambient + diffuse) * vec4(texture2D(colorTex, colorCoord).xyz, 1);

// Apply the projective texture
gl_FragData[0] += texture2DProj(projectiveTex, projectiveCoord);
}

2008年10月13日星期一

免費 Model 寶庫

今天無意中發現了一個博客非常慷慨地把大量高品質 (相對其他免費) 的 3D 模型分享給全世界。
站內有不同種類的模型,但還是汽車的居多;雖然下載的方法有點煩,畢竟是免費的,好應該說聲多謝。

http://i344.photobucket.com/albums/p338/free3dart/nsx_HP_small.jpg

http://i344.photobucket.com/albums/p338/free3dart/f18.jpg