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上的项目站点下载本课程的完整源代码。