GLSL 编程/GLUT/纹理层
本教程介绍了多纹理,即在着色器中使用多个纹理图像。
它扩展了纹理球教程中的着色器代码以处理多个纹理,并展示了一种组合它们的方法。如果您还没有阅读过这些教程,现在是一个很好的机会去阅读它们。
许多真实表面(例如左侧图像中所示的人类皮肤)由不同颜色、透明度、反射率等的几层组成。如果最上面一层是不透明的并且不透射任何光线,这对于渲染表面来说并不重要。但是,在许多情况下,最上面一层是(半)透明的,因此表面的准确渲染必须考虑多层。
事实上,Phong 反射模型中包含的镜面反射(参见镜面高光教程)通常对应于反射光的透明层:人类皮肤上的汗水、水果上的蜡、嵌入色素颗粒的透明塑料等等。另一方面,漫反射对应于最上面透明层下面的层。
照明这种分层表面不需要层的几何模型:它们可以用单个、无限薄的多边形网格表示。但是,照明计算必须为不同的层计算不同的反射,并且必须考虑光在层之间的传输(当光进入层时和当光离开层时)。这种方法的示例包括 Nvidia 的“Dawn”演示(参见“GPU Gems”一书的第 3 章,该书可在网上获得)和 Nvidia 的“Human Head”演示(参见“GPU Gems 3”一书的第 14 章,该书也可在网上获得)。
这些过程的完整描述超出了本教程的范围。只需说,层通常与纹理图像相关联,以指定它们的特性。在这里,我们只展示如何使用两种纹理和一种特定组合方法。这个例子实际上与层无关,因此说明了多纹理比表面层有更多应用。
由于人类活动,地球的未点亮一侧并不完全黑暗。相反,人造灯光标记了城市的位置和范围,如左图所示。因此,地球的漫反射照明不应该仅仅使太阳照射表面的纹理图像变暗,而应该实际将它与未点亮纹理图像混合。请注意,太阳照射的地球比未点亮一侧的人造灯光亮得多;但是,我们降低了这种对比度,以便展示夜间纹理。
着色器代码扩展了来自纹理球教程的代码以处理两个纹理图像,并使用漫反射教程中描述的计算来处理单个方向光源
根据该方程,漫反射照明水平levelOfLighting
为 max(0, N·L)。然后,我们根据levelOfLighting
混合白天纹理和夜间纹理的颜色。这可以通过将白天颜色乘以levelOfLighting
,将夜间颜色乘以1.0 - levelOfLighting
,然后将它们加起来以确定片段的颜色来实现。或者,可以使用内置的 GLSL 函数mix
(mix(a, b, w) = b*w + a*(1.0-w)
),它可能更有效。因此,片段着色器可以是(再次使用我们在longitudeLatitude
中计算纹理坐标的特定方法)
void main(void)
{
vec2 longitudeLatitude = vec2((atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
(asin(texCoords.z) / 3.1415926 + 0.5));
vec4 nighttimeColor = texture2D(mytexture, longitudeLatitude);
vec4 daytimeColor = texture2D(mytexture_sunlit, longitudeLatitude);
gl_FragColor = mix(nighttimeColor, daytimeColor, levelOfLighting);
}
请注意,这种混合与透明度教程中讨论的 alpha 混合非常相似,只是我们在片段着色器内执行混合,并使用levelOfLighting
而不是应该“覆盖”另一个纹理的纹理的 alpha 分量(即不透明度)。事实上,如果daytimeTexture
指定了一个 alpha 分量(参见透明纹理教程),我们可以使用这个 alpha 分量将mytexture_sunlit
混合到mytexture
上。这将对应于一个部分透明的层,它位于一个不透明的层之上,该不透明的层在最上面一层透明的地方可见。
varying float levelOfLighting;
varying vec4 texCoords;
uniform sampler2D mytexture;
uniform sampler2D mytexture_sunlit;
void main(void)
{
vec2 longitudeLatitude = vec2((atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
(asin(texCoords.z) / 3.1415926 + 0.5));
vec4 nighttimeColor = texture2D(mytexture, longitudeLatitude);
vec4 daytimeColor = texture2D(mytexture_sunlit, longitudeLatitude);
gl_FragColor = mix(nighttimeColor, daytimeColor, levelOfLighting);
}
attribute vec3 v_coord;
attribute vec3 v_normal;
uniform mat4 m, v, p;
uniform mat3 m_3x3_inv_transp;
uniform mat4 v_inv;
varying float levelOfLighting; // the level of diffuse
// lighting that is computed in the vertex shader
varying vec4 texCoords;
struct lightSource
{
vec4 position;
vec4 diffuse;
vec4 specular;
float constantAttenuation, linearAttenuation, quadraticAttenuation;
float spotCutoff, spotExponent;
vec3 spotDirection;
};
lightSource light0 = lightSource(
vec4(2.0, 1.0, 1.0, 0.0),
vec4(1.0, 1.0, 1.0, 1.0),
vec4(1.0, 1.0, 1.0, 1.0),
0.0, 1.0, 0.0,
180.0, 0.0,
vec3(0.0, 0.0, 0.0)
);
void main(void)
{
vec4 v_coord4 = vec4(v_coord, 1.0);
mat4 mvp = p*v*m;
vec3 normalDirection = normalize(m_3x3_inv_transp * v_normal);
vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - m * v_coord4));
vec3 lightDirection;
if (light0.position.w == 0.0) // directional light
{
lightDirection = normalize(vec3(light0.position));
}
else // point or spot light (or other kind of light)
{
vec3 vertexToLightSource = vec3(light0.position - m * v_coord4);
lightDirection = normalize(vertexToLightSource);
}
levelOfLighting = max(0.0, dot(normalDirection, lightDirection));
texCoords = v_coord4;
gl_Position = mvp * v_coord4;
}
恭喜!您已到达关于基本纹理的最后一个教程的结尾。我们已经了解了
- 表面层如何影响材料的外观(例如人类皮肤、打蜡水果、塑料等)。
- 在对代表地球的球体进行纹理化时,如何将未点亮一侧的人造灯光考虑在内。
- 如何在着色器中实现此技术。
- 这与将 alpha 纹理混合到第二个不透明纹理上的关系。
如果您还想了解更多
- 关于基本纹理,您应该阅读纹理球教程。
- 关于漫反射,您应该阅读漫反射教程。
- 关于 alpha 纹理,您应该阅读透明纹理教程。
- 关于高级皮肤渲染,您可以阅读 Randima Fernando(编辑)于 2004 年由 Addison-Wesley 出版、由 Curtis Beeson 和 Kevin Bjorke 撰写的“GPU Gems”一书中由 Curtis Beeson 和 Kevin Bjorke 撰写的第 3 章“Dawn”演示中的皮肤”和 Hubert Nguyen(编辑)于 2007 年由 Addison-Wesley 出版、由 Eugene d’Eon 和 David Luebke 撰写的“GPU Gems 3”一书中由 Eugene d’Eon 和 David Luebke 撰写的第 14 章“用于逼真实时皮肤渲染的高级技术”,这些书可在网上和网上获得。
返回OpenGL 编程 - 照明部分 | 返回GLSL 编程 - GLUT 部分 |