three.js的alphaTest

2,513 阅读3分钟

测试是什么

在说透明测试之前,我想啰嗦一下,对测试这个词的理解。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。

image.png 在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。

image.png

image.png 但是,如果我就是要在不透明度为0的时候,才抛弃它呢? 那也很简单,把上面的代码改一改。 这里我直接默认一直开启透明测试,当然也可以改成不小于0时开启。 透明测试和return一样,要尽早执行,减少无用功。

true ? '#define USE_ALPHATEST' : '',
#ifdef USE_ALPHATEST

	if ( diffuseColor.a <= alphaTest ) discard;

#endif
`;

修改之后的效果,前面是球体,换个立方体。

image.png

还有一件事,上面我直接使用了svg作为纹理贴图,一开始相当模糊,我才意识到纹理靠的是采样。 而这个svg果然没给默认宽高,那就是宽300了。于是,就给了个1024,果然清晰了许多。

后话

当你解决了你以为的问题之后,真正的问题就再也藏不住了。

当我自以为解决了问题之后,才发现模型仍然显示不正常,并不是因为原本的透明测试的逻辑,而是因为它根本没有透明度贴图。 且纹理贴图是完全不透明的,所以显得十分奇怪。

本文正在参加「金石计划」