原文在此:
Explanation of impossible blend
http://pcsx2.net/developer-blog/268-explanation-impossible-blend.htmlhttp://pcsx2.net/developer-blog/268-explanation-impossible-blend.html译者:我们很多人都用过PCSX2来玩PS2游戏,但可能很多人未必知道,PS2的模拟其实比很多其他硬件都要艰难。CPU方面由于EE/VU0/VU1自然不用多说,而GS方面,功能强大但和现代硬件不兼容的情况很多,偏偏很多游戏开发者(尤其是日系PS2游戏开发者)都喜欢摆弄一些稀奇古怪的效果,对模拟器开发人员来说就成为了一个大难题。老版本的PCSX2很多效果都不能在硬件模式下正确模拟,但新版解决了不少相关的问题。链接里就是PCSX2对PS2颜色混合模拟的官方解释。
---
颜色混合的目标是把两个颜色混合起来得到新的颜色。在现代的GPU上,混合公式一般是这么写的:
系数1 * 颜色1 +/- 系数2 * 颜色2
颜色1/颜色2 可能是源(fragment shader的计算结果)或者目标的颜色(帧缓冲里的保存数值)两者之一。
系数1/系数2可能是源或者目标的alpha值,1 - alpha,或者是一个常量。GPU会把系数clamp到0和1之间。
然而在PS2上,混合公式是这么写的:
(颜色1 - 颜色2) * 系数 + 颜色3
颜色1/2/3可能是源或者目标颜色,或者是0。
系数是源或者目标的alpha,或者是常量。
这给模拟器编写带来了两个问题:
1. PS2的系数值是被clamp到0和2之间的。还好这只在少数情境下出现。
(译注:最有名的例子之一,是FFX的重力魔法。当我了解了这个特效是如何实现之后,我深深崇拜想出这么用颜色混合的家伙。
这是个很普通的alpha blend,但由于alpha > 1,因此结果就从
SrcColor * SrcAlpha + DstColor * (1 - SrcAlpha)
变成了背景反色
SrcColor * SrcAlpha - DstColor * (SrcAlpha - 1)
错误的渲染结果是普通的alpha混合
正确的渲染结果,注意背景反色!
)
2. 如果颜色1/3是同一个源,那么公式就被转化为
颜色1 * (1 + 系数) - 颜色2 * 系数
看上去似乎能套在现代GPU的公式,然而1 + 系数意味着系数会大于1。在现代GPU下这个系数会被限制到1,无法正确模拟。(译注:这里的例子有很多,不过很多例子一般的玩家不一定能察觉。因为结果就是颜色比PS2要暗,但不至于很大的差别。)
PCSX2最近的更新把第二种情况给修正了。它使用的是在把混合公式直接塞到fragment shader里的方法。不过会有一个变数:fragment shader的执行比较慢(毕竟它相当于是在小CPU上执行的代码),所以现代GPU里都是并行乱序执行的。如果你一个draw call里塞入两个三角形,在计算的时候,第二个三角形的fragment shader可能会在第一个之前运算。然而颜色混合是按序执行的,如果乱序的话,渲染结果很可能就乱套了。不过,只要一次draw call渲染的物体本身没有自我覆盖,那么每个像素点只会被算一次fragment shader,那就不需要考虑执行顺序的问题了。
这就意味着,我们需要把一个n个三角面的draw call拆开成n个draw call。这么做的话性能损失就很大了,但在某些情形下这么干还是合理的。
另外一个要解决的问题就是如何访问目标像素的值。GPU是有纹理缓存(texture cache)的。纹理缓存是只读的,这样才不会有缓存一致性的问题。目标值可以被写入,但是下次读取的时候读取出来的值是错误的,这是因为缓存一致性被破坏。在进行fragment shader里混合的时候,纹理是只读的,但下一次draw call就被改变了。必须要有一个办法去invalidate 纹理缓存,而这在OpenGL 4.5里终于有函数去实现这个了。这样我们就能在fragment shader里进行颜色混合,而不会被GPU内核所限制住。