对渲染管线的一点理解:opengl把渲染流程设计成一套“渲染管线”,把相同的操作抽象出来设计成“黑盒”,对开发者透明,把可以定制的操作抽象成API接口,提供给开发者,就像做填空题一样。于是就有了“顶点着色器”、“片元着色器”,开发者不用和复杂的GPU硬件接口打交道,就能实现酷炫的图像效果。
opengl渲染管线简化理解
回归主题
一、丢弃纹理实现纹理混合
1. 增加alpha通道
代码语言:javascript复制简单的方式实现纹理混合
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
2. 片元着色器中,加载纹理的4个通道,opengl默认不会处理alpha通道,“discard”关键字可以丢弃片元,不做处理的话,纹理的空白处会很“奇怪”,应该是图元渲染光栅化采样造成的。
错误示例-去掉discard
正确示例-增加discard
代码语言:javascript复制void main()
{
vec4 texColor = texture(texture1, TexCoords);
if (texColor.a < 0.1)
{
discard;
}
// FragColor = vec4(vec3(texture(texture1, TexCoords)), 1.0);
FragColor = texture(texture1, TexCoords);
}
3. 草纹理实现
代码语言:javascript复制纹理使用的理解:纹理必须要贴在一个几何表面上,草的纹理不能凭空绘制出来,也是附着在一个正方形上。正方形平移四次绘制,就可以生成4个草的纹理。注意:顶点着色器中position有位移操作,但是纹理不需要再单独进行位移操作 草纹理附着的四边形
float transparentVertices[] = {
// positions // texture Coords (swapped y coordinates because texture is flipped upside down)
0.0f, 0.5f, 0.0f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f, 0.0f, 1.0f,
1.0f, -0.5f, 0.0f, 1.0f, 1.0f,
0.0f, 0.5f, 0.0f, 0.0f, 0.0f,
1.0f, -0.5f, 0.0f, 1.0f, 1.0f,
1.0f, 0.5f, 0.0f, 1.0f, 0.0f
};
代码语言:javascript复制// 绘制草
glBindVertexArray(transparantVAO);
glBindTexture(GL_TEXTURE_2D, transparentTexture);
for (unsigned int i = 0; i<vegetation.size(); i ) {
model = glm::mat4(1.0f);
model = glm::translate(model, vegetation[i]);
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
白色条纹
代码语言:javascript复制// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
clamp去条纹
二、纹理混合的实现
纹理缓和的计算也不复杂,根据alpha通道值做叠加或减除融合,详细可参考opengl-混合
这里,我们重点看下混合中的问题及解决防范
1. 接口使用:渲染半透明纹理API调用上很简单,两行代码
代码语言:javascript复制glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2. 混合中的问题:如果运气不好会出现奇怪的叠加效果,前面窗户完全遮挡住了后面的窗户
出现不合理的遮挡
运气好一切OK
运气好
正常运行效果的代码,其实就做了微小的调整,按照窗户从后到前的顺序绘制窗户,上面“不正常效果”是随机绘制
代码语言:javascript复制// 窗户纹理位移坐标
vector<glm::vec3> vegetation
{
glm::vec3(0.5f, 0.0f, -0.6f),
glm::vec3(-1.5f, 0.0f, -0.48f),
glm::vec3(-0.3f, 0.0f, -0.23f),
glm::vec3(1.5f, 0.0f, 0.51f),
glm::vec3(0.0f, 0.0f, 0.7f)
};
3. 混合问题分析及解决
深度测试并不能智能的分析出哪些片元需要考虑混合,只会“死脑筋”的按照绘制顺序做深度测试,如果先绘制了前面的图形(即使前面的图元有透明的部分),后面绘制的图形进行深度测试会失败会被丢弃,根本没机会进入到后面的混合渲染流程。
所以,按照从后到前顺序(Z坐标由远及近)绘制是没有问题的,那么问题来了,总不能每次手动调整图形绘制的前后顺序吧?
比较简单的处理,通过排序来调整绘制顺序。
代码语言:javascript复制注意STL map默认是对key值做升序排序
vector<glm::vec3> vegetation
{
glm::vec3(-1.5f, 0.0f, -0.48f),
glm::vec3( 1.5f, 0.0f, 0.51f),
glm::vec3( 0.0f, 0.0f, 0.7f),
glm::vec3(-0.3f, 0.0f, -2.3f),
glm::vec3( 0.5f, 0.0f, -0.6f)
};
std::map<float, glm::vec3> sorted;
for (unsigned int i = 0; i < vegetation.size(); i ) {
float distance = glm::length(camera.Position - vegetation[i]);
sorted[distance] = vegetation[i];
}
...
省略其他代码
...
for (std::map<float, glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); it) {
model = glm::mat4(1.0f);
model = glm::translate(model, it->second);
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
实现效果正常