测试是什么
在说透明测试之前,我想啰嗦一下,对测试这个词的理解。webgl有很多测试,像深度测试,模版测试,裁剪测试等等。 每种测试都是一道关卡, 只有通过测试才能继续下一关,没有通过测试的就只能止步了,已经失去了显示在屏幕上的可能性。 专业一点的说法就是,不能通过测试的片元会被抛弃,不能继续流水线。
graph LR
顶点 -->2[片元]
2-->3[裁剪测试]
3-->4[模板和深度测试]
4-->5[...]
透明测试
所以透明测试的意思就是, 如果片元颜色的不透明度(alpha通道)低于预设的透明测试的值就会被抛弃。关键代码就这么点。
discard 关键字的意思就是抛弃这个片元,可以类比常规语言的return 之类的关键字。 不同的是,glsl中只要出现这个关键字,就会终止着色器代码,不管有多少层嵌套。
if ( diffuseColor.a < alphaTest) discard;
three的透明测试是用着色器代码实现的,因为webgl没有提供相关api,可能就是因为实现确实简单,有了可编程流水线,用两行代码就能实现。
threeJS的具体实现
打开threejs的项目搜索USE_ALPHATEST,就可以看到相关的代码了。本地的three.js版本为146.0。
在threejs中 ,alphaTest是材质上的一个属性。在webGLProgam.js里面,我们可以看到,插入定义
USE_ALPHATEST的条件就是 alphaTest为真。 alphaTest默认是0,所以默认就是不开启。
parameters.alphaTest ? '#define USE_ALPHATEST' : '',
启用和关闭透明测试的方法就是使用条件编译。如果定义了 USE_ALPHATEST,就会执行条件语句内的代码 。 three的glsl的代码,是按功能分块的,每个模块又分变量声明和运算逻辑 ,这是很符合glsl的函数编程的。 所以下面的两行代码,分别被条件编译包裹。
#ifdef USE_ALPHATEST
uniform float alphaTest;
#endif
#ifdef USE_ALPHATEST
if ( diffuseColor.a < alphaTest ) discard;
#endif
我的问题
按上面的设计 ,在alphaTest = 0的时候, 不透明度为0的变片元仍会显示(如果没有开启transparent),这很合理。 而我的问题就是, 现在有材质的alphaTest为 0 , 有透明贴图,没有开启透明,结果就有意料之外片元显示出来了。 因为只有小于透明测试的值才会被抛弃,0 它不小于0,所以测试通过了。 常规的解决办法就是,增加alphaTest,比如设置为 0.001。
但是,如果我就是要在不透明度为0的时候,才抛弃它呢? 那也很简单,把上面的代码改一改。 这里我直接默认一直开启透明测试,当然也可以改成不小于0时开启。 透明测试和return一样,要尽早执行,减少无用功。
true ? '#define USE_ALPHATEST' : '',
#ifdef USE_ALPHATEST
if ( diffuseColor.a <= alphaTest ) discard;
#endif
`;
修改之后的效果,前面是球体,换个立方体。
还有一件事,上面我直接使用了svg作为纹理贴图,一开始相当模糊,我才意识到纹理靠的是采样。 而这个svg果然没给默认宽高,那就是宽300了。于是,就给了个1024,果然清晰了许多。
后话
当你解决了你以为的问题之后,真正的问题就再也藏不住了。
当我自以为解决了问题之后,才发现模型仍然显示不正常,并不是因为原本的透明测试的逻辑,而是因为它根本没有透明度贴图。 且纹理贴图是完全不透明的,所以显得十分奇怪。
本文正在参加「金石计划」