threejs物理材质透明问题续

1,743 阅读8分钟

上篇文章挖掘了alpha通道变化的原因,但是没有提到很常见的一些问题。

使用

想要在three里面实现透明效果,一般来说,只要做两件事。

1 开启材质的transparent

2 将材质的opacity属性调至小于1

3 用贴图实现透明效果的话,图片本身要支持,就是说图片格式要有alpha通道,比如png。

4 也可也以使用transmission做出透明效果, 开启transmission之后,可以姑且认为粗糙度就是透明度,粗糙度越大能透射的光越少,透明度就越低。

问题

常见的一个问题就是, 当两个透明物体之间的深度差异非常小时,会出现透明穿透问题。 我实际上放了一个球和一个盒子,这个方向看上还是正常的,

image.png

但是换个方向就有问题了。

image.png

这也可也说是深度冲突,但不是常说的那个深度冲突。

three 对于透明物体,会排序,这样就会有一个顺序,不排序也会有一个顺序,渲染顺序是客观存在的,因为我们是一个个mesh去render的。

要想透过一个透明物体甲,看到另一个透明物体乙,那么 甲就必须在乙后面渲染,是不是就是我们熟悉的画家算法。

但是,移动相机,甲乙的前后关系就会变化,three是通过排序解决了这个问题, three会在渲染之前,对透明物体进行排序,按照到相机距离的远近。

所以,当两个物体的position一毛一样时,这个排序就等于没有了。

一般来说,解决深度冲突的最好的办法就是, 拉开差距,让两个物体的深度有足够可以被识别的差距。 但是我这里不行,因为模型就是有两层,包裹在一起,这两层的position 就是一样的,如果我要用拉开距离的办法,我就得在移动相机时去计算,把我想要位置去摆放出来,不太现实。 因为少有偏差,模型就会不好看。

image.png

image.png

解决问题

renderorder

上面已经说了,渲染顺序是客观存在的,默认是0, 这个属性是mesh的。 在three里面 renderorder 越大渲染顺序就越靠前。 也就是说,我们只需要把内层物体的order调大,或者外层的order调小,就可以实现想要的效果。 还好,项目模型不是我演示的这种,一半在里面,一半在外面,是完全包围的。

image.png

但是,对于我的项目来说,手动设置order,也不是很现实,因为模型是用户上传的,也许可以用包围盒检测,但是确实不太好。

depthWrite

深度写入。

我先直接说结论,把深度写入关闭即可,深度测试不要关了。这个实际上是混合透明物体和不透明物体所必须得步骤,并不能解决问题,但是opacity < 1的话,是需要关的。

image.png

再来说说这个操作是什么意思,我尽我所能说明白,我也有一点不解。

深度

我们总说深度,深度是啥,深度就是裁剪空间里的 z值,z值越小,距离屏幕越近。 换到相机世界坐标系就是,深度就是就离相机的距离。

对于不透明物体来说,离我们近的物体会遮挡住远的物体,这是我们期望的。 要实现这一点,通常有两个办法

画家算法和深度缓冲

就如果画画一样,我们要从远处画起,先画远的,再画近的,这样近的才会覆盖远景上面。(优秀的画家能直接描绘需要描绘的地方)。 我们绘制顶点也是如此,同一深度的点先绘制,然后再绘制深度小一些的,直到完成。 这就是画家算法

但是,实际上被覆盖的片元是不需要绘制的,这样一个个去绘制未免太耗费算力了。所以,我们有了 z-buffer 深度缓冲算法,

使用深度缓冲,只需要记录每个xy的最小的z即可, 同样的xy我们就只绘制最表面的那个。

这个手段也称隐藏面消除,被遮挡的三角面可不就是隐藏面吗, 既然被遮挡,那就看不见。 既然看不见,那就没有必要去画,这就是深度缓冲比画家算法更优的地方 。

在webgl里,要开启z-buffer,只需要gl.enable(gl.DEPTH_TEST), 开启深度测试。

开启深度测试之后,近的物体就能正确的遮挡远的物体,threejs是默认开启深度测试的。

透明物体不能直接用深度测试

使用z-buffer之后,大大减少需要绘制的顶点。

这本来很好,但是,对于透明物体来说,就不太合适了。 假如有一近一远,两个透明面, 那么远的那个面会被消除,其实就是不渲染。 这样的话,就等于是远的就遮挡了近的,透明就没有效果了,等于是不透明。

所以,透明效果,天生就和深度测试犯冲。 要想绘制出透明效果,就得同一个xy ,由近及远,不断地合成颜色,直到遇到不透明物体。 这就是用到画家算法

实际上就是,除开透明物体,找到深度最浅的不透明像素点,然后前面的透明像素全部画家算法,融合。这样,就是我们期望的效果。 这个说的是理论,实际上,显然不是这样的。 至少,在深度不冲突的情况下,透明物体能正常渲染,为此,我用原生写了一下,发现,原生就是这样的, 只要开启透明度融合,深度测试对具有正常渲染顺序的透明物体没有影响, 可见原生在开启透明度融合之后,深度缓冲算法会受到影响。

但是这个,渲染顺序的粒度是单个三维物体,也就是说,如果出现,两个透明物体,互相遮挡,那必然会有一方渲染不正常。

前面提到了,threejs默认会给透明物体排序,也就是一组图元粒度级别的画家算法,后绘制的几何体必然会覆盖先绘制的,不论每个顶点的深度差异。

而关闭深度写入之后就没有这个问题了, 深度写入是干嘛的。 前面说了,默认开启了深度测试, 会进行排序,实际上是取最小值, 为此专门开辟了一个深度缓冲区,depthBuffer ,用来记录顶点的深度,关闭depthwrite ,就不会往深度缓冲区内写入深度信息。

但是,不透明物体的深度信息还是记录了的,结果就是深度缓冲区中记录了,不透明物体的最小深度信息。 透明片元的深度吐过大于此值的, 都会被舍弃, 这就实现了透明和半透明物体混合渲染。

transimisson 实现透明效果

transmission的透明穿透问题,上一篇提到过,就是如果用transmission实现透明效果,透过这个物体,仅能看到不透明物体,就算是深度不同也看不到其他的透明物体。 这是因为,它是先把不透明物体渲染到帧缓冲区,然后用这个做纹理,从而计算透射光,自然就不包含透明物体,会发生透明穿透。

还有一点很奇怪,同为opacity为1的 开启了transmission的物体, 如果都是双面或者单面,那么互不可见。 只能透过单面的看见双面的。 如果希望他们之间是可见的,需要混用transparent opacity调整到一个合适的值。 官方文档意思是,不建议混用。

纠正之前的问题

上篇的说到,开启了transmission 之后,透明度应该与opacity无关,但是实际效果是有关,所以我疯狂的找那个transmissionSamplermap,结果还是没看出哪里有关。

来。我们再看一下那行代码

image.png 是乘法,不是直接赋值,我之前看成直接赋值了,没事了。

没有像素级的画家算法,首先这是由于webgl这里并不没有这样的接口给我们去排序。要知道,片元的生成是在顶点着色器执行之后,顶点间插值计算出来的,这里我们的手已经够不着了。

然后,上文所说的没有问题,其实就是因为两个物体前后(遮挡)关系明确,物体级的排序已然足够。

为什么要关闭深度写入

首先需要知道深度测试的含义就是,利用在深度缓冲区中只保留深度最小的深度信息,对同一xy的全部片元进行测试,深度信息小于或等于的不能通过测试,也就不会进入到后续的绘制流水线中。

而深度写入的意思,就是把当前片元的深度信息写入深度缓冲区。如果将半透明片元的深度信息写入,那么深度测试的时候,大于此深度信息的,无论透明与否都不能通过测试了。

而只记录不透明的深度信息, 在深度测试的时候,深度信息大于深度缓冲区里的透明片元就不能通过测试,也就是能被正常遮挡。

这样,半透明和不透明物体就能同时绘制出来了。