本文從下面鏈接翻譯過來:
Android Lesson Three: Moving to Per-Fragment Lighting
歡迎來到Android的第三個教程! 在本課中,我們將學習第二課中學到的所有內容,並學習如何在片元着色器上應用相同的光照技術。 即使給簡單的立方體添加漫反射光照,我們也能看到差異。
假設和先決條件
本系列的每節課都以前面的課程爲基礎。 本課程是第二課的延伸,因此請務必在繼續學習之前先閱前面的課程。 以下是本系列的前幾課:
什麼是片元着色器進行光照計算?
隨着着色器的使用,逐像素光照計算在遊戲中是一種相對較新的現象。 許多著名的舊遊戲,例如原始版本的Half Life ,都是在着色器之前開發出來的,主要是靜態光照特性,可以有一些技巧來動態計算光照,比如在每個頂點計算光照或者通過動態光照貼圖技術(lightmap)。
光照貼圖可以提供非常好的效果,有時甚至可以提供比着色器更好的效果,因爲可以預先完成耗時的光照計算,但缺點是它們佔用大量內存並且使用它們進行動態光照僅限於簡單的計算。
使用着色器,現在可以將許多這些計算放到GPU,這樣可以實現更多的實時效果。
從頂點着色器移動到片元着色器
在本課中,我們將針對頂點着色器計算光照的解決方案和片元着色器計算光照的解決方案使用相同的代碼。
移動設備的GPU越來越快,但性能仍然令人擔憂。對於諸如地形的通過代碼來實現的光照計算,把光照計算放置在頂點着色器可能足更好。 確保您在質量和速度之間取得適當的平衡。
在某些情況下可以看到兩種類型的光照計算之間的顯着差異。 看看以下屏幕截圖:
頂點光照;正方形四個頂點爲中心 |
片元光照;正方形四個頂點爲中心 |
在左圖的每頂點光照中正方體的 正面看起來像是平面陰影,不能 表明附近有光源。這是因爲正面 的四個頂點和光源距離差不多相 等,並且四個點的低光強度被簡 單的插入兩個三角形構成的正面。 相對比,每片元光光照很好的 顯示了光照特性 |
頂點光照;正方形角落 |
片元光照;正方形角落 |
左圖顯示了一個Gouraud陰影 立方體。當光源移動到立方體正 面角落時,可以看到類似三角形 的效果。這是因爲正面實際上是 由兩個三角形組成,並且在每個 三角形不同方向插值,我們能看 到構成立方體的基礎幾何圖形。 片元光照的版本顯示上沒有此類插 值的問題並且它在邊緣附近顯示 了一個漂亮的圓形高光 |
頂點着色器光照概述
我們來看看第二課的着色器; 在該課程中可以找到關於着色器的更詳細說明。
頂點着色器
uniform mat4 u_MVPMatrix; // A constant representing the combined model/view/projection matrix.
uniform mat4 u_MVMatrix; // A constant representing the combined model/view matrix.
uniform vec3 u_LightPos; // The position of the light in eye space.
attribute vec4 a_Position; // Per-vertex position information we will pass in.
attribute vec4 a_Color; // Per-vertex color information we will pass in.
attribute vec3 a_Normal; // Per-vertex normal information we will pass in.
varying vec4 v_Color; // This will be passed into the fragment shader.
// The entry point for our vertex shader.
void main()
{
// Transform the vertex into eye space.
vec3 modelViewVertex = vec3(u_MVMatrix * a_Position);
// Transform the normal's orientation into eye space.
vec3 modelViewNormal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));
// Will be used for attenuation.
float distance = length(u_LightPos - modelViewVertex);
// Get a lighting direction vector from the light to the vertex.
vec3 lightVector = normalize(u_LightPos - modelViewVertex);
// Calculate the dot product of the light vector and vertex normal. If the normal and light vector are
// pointing in the same direction then it will get max illumination.
float diffuse = max(dot(modelViewNormal, lightVector), 0.1);
// Attenuate the light based on distance.
diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance)));
// Multiply the color by the illumination level. It will be interpolated across the triangle.
v_Color = a_Color * diffuse;
// gl_Position is a special variable used to store the final position.
// Multiply the vertex by the matrix to get the final point in normalized screen coordinates.
gl_Position = u_MVPMatrix * a_Position;
}
片元着色器
precision mediump float; // Set the default precision to medium. We don't need as high of a
// precision in the fragment shader.
varying vec4 v_Color; // This is the color from the vertex shader interpolated across the
// triangle per fragment.
// The entry point for our fragment shader.
void main()
{
gl_FragColor = v_Color; // Pass the color directly through the pipeline.
}
如您所見,大部分工作都在我們的頂點着色器中完成。 移動到片元進行光照計算意味着我們的片元着色器將有更多工作要做。
實現片元着色器光照計算
以下是移動到片元着色器計算光滑光照後代碼的樣子
頂點着色器
uniform mat4 u_MVPMatrix; // A constant representing the combined model/view/projection matrix.
uniform mat4 u_MVMatrix; // A constant representing the combined model/view matrix.
attribute vec4 a_Position; // Per-vertex position information we will pass in.
attribute vec4 a_Color; // Per-vertex color information we will pass in.
attribute vec3 a_Normal; // Per-vertex normal information we will pass in.
varying vec3 v_Position; // This will be passed into the fragment shader.
varying vec4 v_Color; // This will be passed into the fragment shader.
varying vec3 v_Normal; // This will be passed into the fragment shader.
// The entry point for our vertex shader.
void main()
{
// Transform the vertex into eye space.
v_Position = vec3(u_MVMatrix * a_Position);
// Pass through the color.
v_Color = a_Color;
// Transform the normal's orientation into eye space.
v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));
// gl_Position is a special variable used to store the final position.
// Multiply the vertex by the matrix to get the final point in normalized screen coordinates.
gl_Position = u_MVPMatrix * a_Position;
}
頂點着色器比以前簡單。我們添加了兩個線性插值變量來傳遞給片元着色器:頂點位置和頂點法線。 在片元着色器中計算光照時,將使用這兩者。
片元着色器
precision mediump float; // Set the default precision to medium. We don't need as high of a
// precision in the fragment shader.
uniform vec3 u_LightPos; // The position of the light in eye space.
varying vec3 v_Position; // Interpolated position for this fragment.
varying vec4 v_Color; // This is the color from the vertex shader interpolated across the
// triangle per fragment.
varying vec3 v_Normal; // Interpolated normal for this fragment.
// The entry point for our fragment shader.
void main()
{
// Will be used for attenuation.
float distance = length(u_LightPos - v_Position);
// Get a lighting direction vector from the light to the vertex.
vec3 lightVector = normalize(u_LightPos - v_Position);
// Calculate the dot product of the light vector and vertex normal. If the normal and light vector are
// pointing in the same direction then it will get max illumination.
float diffuse = max(dot(v_Normal, lightVector), 0.1);
// Add attenuation.
diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance)));
// Multiply the color by the diffuse illumination level to get final output color.
gl_FragColor = v_Color * diffuse;
}
使用片元進行光照計算,我們的片元着色器還有很多工作要做。我們基本上將朗伯計算和衰減移動到每像素級別,這使我們更加逼真,無需添加更多頂點。
進一步練習
我們可以在頂點着色器中計算距離,然後賦值給varying變量通過線性插值傳入片元着色器嗎?
可以從GitHub上的項目站點下載本課程的完整源代碼。