shader alpha混合blend

Alpha Blending,中文译作Alpha混合
Blending就是控制透明的。处于光栅化的最后阶段

公式

Blend SrcFactor DstFactor SrcFactor是源系数,DstFactor是目标系数
最终颜色 = (Shader计算出的点颜色值 * 源系数)+(点累积颜色 * 目标系数)

**Shader计算出的点颜色值:**这个是当前材质上点的颜色
**源系数:**这个是我们可以控制的(SrcFactor)
**点累积颜色:**这个是当前位置的点累计计算的颜色,在屏幕上每一个像素点,都有可能产生很多个颜色,
而最终的颜色是通过帧缓冲中同一个位置一层层颜色累计计算得出
**目标系数:**这个是我们可以控制的(DstFactor)

源颜色和目标颜色,是跟绘制的顺序有关。如果先绘制了一个红色的物体,再在其上绘制绿色的物体。则绿色是源颜色,红色是目标颜色。如果顺序反过来,则 红色就是源颜色,绿色才是目标颜色

一般关于(SrcFactor和DstFactor)的参数选择可以有下边这10种

one                          1
zero                         0
SrcColor                         源的RGB值,例如(0.5,0.4,1)
SrcAlpha                         源的A值, 例如0.6
DstColor                   混合目标的RGB值例如(0.50.4,1)
DstAlpha                         混合目标的A值例如0.6
OneMinusSrcColor          (1,1,1) - SrcColor
OneMinusSrcAlpha          1- SrcAlpha
OneMinusDstColor          (1,1,1) - DstColor
OneMinusDstAlpha          1- DstAlpha

最常用的混合方式当属这一种:Blend SrcAlpha OneMinusSrcAlpha
最终颜色 = 源颜色 * 源透明值 + 目标颜色*(1 - 源透明值)
解释:因为当前要显示的颜色的绘制顺序要高于之前累计的颜色,那么按照渲染顺序而言,应该以当前渲染颜色的alpha为准,如果它的alpha大,则后面就要小,如果它的alpha小,则后面的就要大,最后叠加

进一步解释
我们渲染很多显示节点在屏幕上,但我们的屏幕的大小是固定的,也就是说每个像素格子只能涂上一种颜色,在2d或者3d的空间里,有很多显示节点可能会有重叠的部分叠在一起,这个时候就会考虑到混合了,上面的公式中目标颜色指的是帧缓冲中的颜色附件的颜色,源颜色指的是当前要渲染节点的颜色,当然这个时候的源颜色还没有和透明度相乘,他还处于光栅化的最后阶段,还没有到片段着色器,这里会有一个疑问点,就是我们的节点颜色到底啥时候和透明度相乘?其实有两个地方,一个就是光栅化的最后阶段,我们对混合因子常用的设置是(SrcAlpha,OneMinusSrcAlpha),这个时候就会去和alpha相乘,主要过程就是去帧缓冲的颜色附件中根据当前的像素坐标取出目标颜色和目标因子相乘,然后再将当前节点光栅化后的源颜色和源因子相乘,最后将这两个结果相加,其实也不一定是相加,opegl提供了三种操作(加,减,逆向减),还有一个地方是就是在片段shader中国进行alpha的值进行相关颜色设置。
显示节点要和材质分开,我们图片的后缀名如果是png,则表示图片的纹理格式是rgba,加载到内存,每一个像素都有四个通道来存储,每个通道是8个字节,当然这不是绝对的,对于加载到内存的数据,我们都是可以设置的,其实一张图片的数据,不过就是一个二维数组就可以来存储,内存中的数据只有发到显存才会生成纹理,最后渲染出来,如何发往显存,我们自己可以设置,每一个节点还有透明度,其实这个透明度是和材质透明度分开的,只是最后计算的时候,每一个像素额外的还要乘以这个透明度而已,不过很多游戏引擎关于这个的计算都是放在shader中进行的
片段着色器以后将颜色数据写入帧缓冲的颜色附件中
AttachColor[width][heigh] = [(r,g,b,a),(r,g,b,a),(r,g,b,a),…]

感悟:

通过上面的学习,发现目标颜色尽然还有透明度alpha这个值,所以在GPU的显存中一定还有块内存是专门存放透明度alpha的数组,这个数组也是以屏幕的宽高作为下标的一个二维数组,只是这个数组和颜色附件这个数组里的值是高度匹配的,颜色附件里的值是rgb,就是最终呈现出来的颜色,但我们每一次进行写入到颜色附件的时候,都会去更新透明度alpha这个数组里的值,注意这里的更新是按照一定要求的,因为它会影响到混合的效果,究竟是把源颜色的alpha更新到alpha数组中,还是保留之前的,还是对当前alpha做一些其他的运算再将结果写入到alpha数组中,这个都是有可能的

Premultiplied Alpha

第一点:我们将图片数据从内存发往显存,当然会调用gl.texImag2D这个函数对发来的图片数据进行出里生成GPU可以认识的纹理数据,姑且将这个存在显存中的纹理数据就看成是一个以图片宽高为下表的二维数组,里面存放着RGBA,注意这份纹理数据只是存放在显存中,它在等待被GPU绑定显示
第二点:GPU的FBO就是帧缓冲里面会有一个颜色附件,这个颜色附件是以屏幕宽高为下标的二维数组,里面存的是RGB,浏览器刷新屏幕就是从这里取得值,我们的游戏引擎和浏览器的刷新机制是两个不同的线程,浏览器刷新是从这个颜色附件中取颜色值,而我们的游戏引擎则是取更新这个颜色附件里的值,为了保持同步,当游戏引擎收到浏览器刷新消息的时候,去更新颜色附件里的值
第三点:alpha的混合操作是发生在光栅化后期的,那么就是一个如何使用一些因子做一些运算将源颜色和已经写到颜色附件的目标颜色融合在一起,发生混合一定是对于同样一个物理像素的,也就是物理坐标是一样的,我们在一帧渲染前控制混合就是通过修改GPU的状态,这个在上面已经说了,那么假如我们的源颜色已经预乘了,我们在给GPU发送这个源因子的时候应该是one,也就是下面这样
假如你是这么传GPU关于混合状态的话

SrcAlpha *源颜色+目标颜色*1-SrcAlpha)

那就应该改成

One *源颜色+目标颜色*1-SrcAlpha)

再设置一下纹理存储的状态,这个状态就是去告诉GPU是否使用预乘
如果使用的话,GPU那边会将纹理预先乘以一遍alpha,否则不处理

gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

看到这里,其实预乘一点都不影响混合是不是?是的没错就是一点都不影响,当然预乘还是有其功能的,接着往下看,第四点
第四点:虽然预乘不影响混合,但是预乘影响纹理的过滤,这个才是预乘的最重要的功能,我们存放在GPU中的纹理是有宽高的,当我们要显示的节点尺寸大于纹理的宽高,那么我们要取的纹理就要被放大,当我们要显示的节点尺寸小于纹理的宽高,那么我们要取的纹理数据就要被缩小,这里的放大缩小就是指纹理的过滤,有两种常用的方式一个是临近过滤,一个是线性过滤,而预乘影响的就是线性过滤,举个例子
最左边的是我们的存储在显存中的纹理数据,就只有两个像素
如果我们现在对显示节点进行缩小,缩小到就只有一个像素来显示,然后纹理过滤采用的是线性过滤,线性过滤就是插值,此时对于只有一个像素的颜色,就用左边的两个纹理颜色进行插值,第三个是没有使用预乘的结果,第四个是使用预乘的结果,很显然第四个最符合,这就是预乘的神奇功效!!!那为什么呢?举个例子,下面有三个图片,第一个图片有两个像素,后边两个图片分别只有一个像素,这两个图片分别是第一个图片在缩小成一个像素时不使用预乘和使用预乘的结果
我在第一点已经说了,我们存在显存中的纹理数据是死的,它不会变的,左边的纹理第二个像素是一个透明度为0.1的一块数据,和第一个透明度为1的大红色数据,他两个插值就会得到像第三个那样很绿的像素块,那个透明度为0.1的像素还没有发会作用,它必须要等到blend混合的时候才会发生作用,显然已经迟了,可以考虑提前预乘,就是让透明度发生作用,让那块透明度低的像素变的更低,进而在插值的时候,就更依靠最左边的红色像素块,最后得到第四个正常的显示了
左边红色纹理【255,0,0,1】
左边浅色纹理【0,255,0,0.1】
如果使用没有 Premultiplied Alpha 的颜色进行插值,那么结果就是:
((255,0,0,1)+(0,255,0,0.1))⋅0.5=(127,127,0,0.55)
如果使用 Premultiplied Alpha,也就是 (0, 255 * 0.1, 0, 0.1),和红色混合后:
((255,0,0,1)+(0,25,0,0.1))⋅0.5=(127,25,0,0.55)
在这里插入图片描述
注意看上面插值的计算方法,就是在显存中取两头的像素来算两个点中间对应的像素点颜色,最后alpha还加到一起了,如果我们不预乘的话,那这个alpha对于这种纹理插值是没有太多作用的,提前乘更能符合预期
总结
预乘不会影响混合,如果开启预乘的话,可以重新设置一下GPU的渲染状态,这里唯一的消耗可能就是多了一次GPU状态的切换
预乘是针对纹理过滤缩小导致出现一些奇怪的边缘
从这个预乘我们可以猜到,GPU对于纹理插值这块的处理,模拟如下
给定一个矩形,我们传给GPU的顶点着色器四个顶点,采用索引绘制
GPU拿到四个顶点的uv以后,开始插值
首先算出四个顶点的uv,然后算出四条边上的uv,这个时候容易出现一些奇怪的边缘,比如你一个顶点是【255,0,0,1】,另一个顶点是【0,255,0,0.1】,这两个顶点在插值的时候就需要预乘,虽然后面的顶点绿色值角度但是他的透明度太低,必须要预乘才可以真实的反应他的颜色,只有这样这两个点插值出来的结果才正确,你可以手动计算一下,不预乘和预乘,算出来的颜色差距蛮大的,这里也透露出来一个问题就是,GPU插值的时候每次都是从显存中的源纹理取得,而不是从颜色附件中取的

感悟:纹理在进行线性过滤的时候,纹理的每个像素的alpha会根据缩放比例而变化

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值