15.opengl高级-混合

2020-06-28 15:51:24 浏览数 (1)

对渲染管线的一点理解: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. 草纹理实现

纹理使用的理解:纹理必须要贴在一个几何表面上,草的纹理不能凭空绘制出来,也是附着在一个正方形上。正方形平移四次绘制,就可以生成4个草的纹理。注意:顶点着色器中position有位移操作,但是纹理不需要再单独进行位移操作 草纹理附着的四边形

代码语言:javascript复制
  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坐标由远及近)绘制是没有问题的,那么问题来了,总不能每次手动调整图形绘制的前后顺序吧?

比较简单的处理,通过排序来调整绘制顺序。

注意STL map默认是对key值做升序排序

代码语言:javascript复制
    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);
  }

实现效果正常

0 人点赞