笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
CSDN视频网址:http://edu.csdn.net/lecturer/144
本章给读者介绍关于混合技术的实现,混合在游戏中经常使用,它在引擎中的实现主要是分为三种:透明,半透明,次序无关透明度,本篇博文主要是围绕它们进行。
在OpenGL中,物体透明技术通常被叫做混合(Blending)。一个物体的透明度,被定义为它的颜色的alpha值。alpha颜色值是一个颜色
向量的第四个元素。美术在制作的游戏图片颜色,主要是由rgba四位组成的,颜色的最后一位就是我们说的alpha通道,它主要是决定材质
的透明度的。
先说透明的材质处理,做过3D游戏的开发者都比较熟悉,在3D场景编辑器中经常需要在地面上刷一些草,这些草的图片制作是带有
alpha通道的,效果如下所示:
程序的处理方式就是把背景去掉,把草显示出来,所以,当向场景中添加像这样的纹理时,我们不希望看到一个方块图像,
而是只显示实际的纹理像素,剩下的部分可以被看穿。我们要忽略(丢弃)纹理透明部分的像素,不必将这些片段储存到颜色
缓冲中。接下来要做的事情就是加载带有Alpha通道的纹理图片,在这里我们使用了SOIL库, SOIL 是一个用于向OpenGL中
加载纹理的小型C语言库。下载地址: http://www.lonesock.net/soil.html,SOIL库提供的加载函数如下:
unsigned char * image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGBA);
不要忘记还要改变OpenGL生成的纹理:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
保证你在片段着色器中获取了纹理的所有4个颜色元素,而不仅仅是RGB元素:
void main()
{
// color = vec4(vec3(texture(texture1, TexCoords)), 1.0);
color = texture(texture1, TexCoords);
}
透明材质就加载完成了,接下来就是对草进行摆放了,代码段如下所示:
vector<glm::vec3> vegetation;
vegetation.push_back(glm::vec3(-1.5f, 0.0f, -0.48f));
vegetation.push_back(glm::vec3( 1.5f, 0.0f, 0.51f));
vegetation.push_back(glm::vec3( 0.0f, 0.0f, 0.7f));
vegetation.push_back(glm::vec3(-0.3f, 0.0f, -2.3f));
vegetation.push_back(glm::vec3( 0.5f, 0.0f, -0.6f));
一个单独的四边形被贴上草的纹理,这并不能完美的表现出真实的草,但是比起加载复杂的模型还是要高效很多,利用一些小技巧,
比如在同一个地方添加多个不同朝向的草,还是能获得比较好的效果的。
由于草纹理被添加到四边形物体上,我们需要再次创建另一个VAO,向里面填充VBO,以及设置合理的顶点属性指针。
在我们绘制完地面和两个立方体后,我们就来绘制草叶:
glBindVertexArray(vegetationVAO);
glBindTexture(GL_TEXTURE_2D, grassTexture);
for(GLuint i = 0; i < vegetation.size(); i++)
{
model = glm::mat4();
model = glm::translate(model, vegetation[i]);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 6);
}
glBindVertexArray(0);
运行程序得到的效果如下所示:
出现这种情况是因为OpenGL默认是不知道如何处理alpha值的,不知道何时忽略(丢弃)它们。我们不得不手动做这件事。
幸运的是这很简单,感谢着色器,GLSL为我们提供了discard命令,它保证了片段不会被进一步处理,这样就不会进入颜色缓冲。
有了这个命令我们就可以在片段着色器中检查一个片段是否有在一定的阈限下的alpha值,如果有,那么丢弃这个片段,就好像
它不存在一样:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D texture1;
void main()
{
vec4 texColor = texture(texture1, TexCoords);
if(texColor.a < 0.1)
discard;
color = texColor;
}
在这儿我们检查被采样纹理颜色包含着一个低于0.1这个阈限的alpha值,如果有,就丢弃这个片段。这个片段着色器能够保证我们只渲染哪些不是完全透明的片段。现在我们来看看效果:
下面把Shader脚本的顶点着色器给读者展示如下:
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoords;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0f);
TexCoords = texCoords;
}
片段着色器代码如下所示:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D texture1;
void main()
{
vec4 texColor = texture(texture1, TexCoords);
if(texColor.a < 0.1)
discard;
color = texColor;
}
其次介绍半透明处理,以上Shader完成了透明材质的渲染,其实这种方法在材质渲染中经常使用,可以把不需要的颜色放弃掉,这种方式不适合渲染半透明的图片,也没有用到Blend混合模式。为了渲染出不同的透明度级别,需要开启混合(Blending),开启混合功能函数如下:
glEnable(GL_BLEND);
开启混合后,我们还需要告诉OpenGL它该如何混合。
OpenGL以下面的方程进行混合:
- C¯source :源颜色向量。这是来自纹理的本来的颜色向量。