OpenGL ES for Android(混合)

简介

在之前使用物体时没考虑过透明的情况,而混合就是除了物体透明度的一种方式。在处理物体透明时一般有两种方式,一种是直接丢弃掉透明度达到一定程度的部分;另外一种就是混合。例如一扇窗户如果是全透明的我们看到的物体就是窗口后的物体;如果它是半透明的有色玻璃时,看到的就是玻璃和窗口后物体的叠加。之前我们使用纹理的颜色都是纹理的rgb颜色,没有用到最后一个alpha(也就是透明度),alpha的范围是0到1,1表示不透明,0表示全透明。

丢弃片段

有些物体的部分,要么是全透明,要么是不透明,不存在其他的情况下,我们选择丢弃掉透明的部分来显示。例如下面这个小草的图片:

首先我们先用它来显示不做任何处理的情况,我们改变位置显示几个纹理,一些关键代码:

       private float[][] translate = new float[][]{
            {-1.5f, 0.0f, -0.48f},
            {1.5f, 0.0f, 0.51f},
            {0.0f, 0.0f, 0.7f},
            {-0.3f, 0.0f, -2.3f},
            {0.5f, 0.0f, -0.6f}};
    ……
    for (float[] vegetation : translate) {
         Matrix.setIdentityM(modelMatrix, 0);
         Matrix.translateM(modelMatrix, 0, vegetation[0], vegetation[1], vegetation[2]);
         //Matrix.rotateM(modelMatrix, 0, angleInDegrees, 0.0f, 1.0f, 0.0f);
 
         Matrix.multiplyMM(mMVPMatrix, 0, viewMatrix, 0, modelMatrix, 0);
         Matrix.multiplyMM(mMVPMatrix, 0, projectionMatrix, 0, mMVPMatrix, 0);
         GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
         GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
    }
    ……

因为我们没做任何的处理,在透明度为0的位置就显示了黑色:

这样显然是不能接受的。想要丢弃一部分片段,可以使用着色器内建的方法discard,这个方法的解释是:可以在片段着色器中使用它来放弃对当前片段的操作;此关键字导致片段被丢弃,并且不会对任何缓冲区进行更新。通常和条件判断一起使用,我们来修改下着色器代码,当alpha小于设置的值时丢弃片段:

  ……
  void main() {
      vec4 texColor = texture2D(texture, TextCoord);
      if (texColor.a < 0.1){
         discard;
      }
      gl_FragColor = texColor;
  }

这时的显示效果如下:

混合

处理多个半透明时,使用丢弃就无法处理了,这时必须使用混合了。启用混合和启用其他类似,代码是:

  GLES20.glEnable(GLES20.GL_BLEND);

随后是设置混合的方式,使用方法glBlendFunc( int sfactor, int dfactor )或者glBlendFuncSeparate( int srcRGB, int dstRGB, int srcAlpha, int dstAlpha )。两个方法的区别,glBlendFunc直接设置rgba的混合方式,而glBlendFuncSeparate可以分别设置rgb和alpha的混合方式。

对于具体的参数解析,可以参考文档https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/03%20Blending/。这里我们使用

  GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, 
  GLES20.GL_ONE_MINUS_SRC_ALPHA)

使用源颜色向量的alpha作为源因子,使用1−alpha作为目标因子。我们使用下图的窗户代替小草展示效果:

绘制完成后的效果如下:

https://pic3.zhimg.com/80/v2-387ca5d6520fd99e2a37d53ef24e460a_720w.jpg

其中有些怪异的地方,最前面窗户的透明部分遮蔽了右侧背后的窗户。这是深度测试的原因,当写入深度缓冲时,深度缓冲不会检查片段是否是透明的,所以透明的部分会和其它值一样写入到深度缓冲中。结果就是窗户的整个四边形不论透明度都会进行深度测试。即使透明的部分应该显示背后的窗户,深度测试仍然丢弃了它们。要想让混合在多个物体上工作,我们需要最先绘制最远的物体,最后绘制最近的物体。因此我们需要计算各个物体与观察点的距离排序。这里我们使用TreeMap来排序,关键代码:

     Map<Float, float[]> map = new TreeMap<>(new Comparator<Float>() {
            @Override
            public int compare(Float o1, Float o2) {
                switch (o1.compareTo(o2)){
                    case 1:
                        return -1;
                    case -1:
                        return 1;
                    default:
                        return 0;
                }
            }
        });
        for (float[] vegetation : translate) {
            float result = (float) Math.sqrt((vegetation[0] - mViewPos[0]) * (vegetation[0] - mViewPos[0]) +
                    (vegetation[1] - mViewPos[1]) * (vegetation[1] - mViewPos[1]) +
                    (vegetation[2] - mViewPos[2]) * (vegetation[2] - mViewPos[2]));
            map.put(result, vegetation);
        }
 
        for (Map.Entry<Float, float[]> entry : map.entrySet()) {
            float[] vegetation = entry.getValue();
            ……绘制
        }

修改之后的显示效果:

现在还没考虑到变换缩放等情况,而且对于非平面的物体需要更多的处理,不过已经是比较不错的混合实现效果了。

如有不足敬请谅解,路过的看官帮忙点赞关注下。

猜你喜欢

转载自blog.csdn.net/ajsliu1233/article/details/106361449