本文将会讲述THREE.js渲染程序,这里的渲染程序指的是处于前后地位的物体,是如何渲染进去物体之间的遮挡关系的。

次要的讲述内容包含:

  1. 不通明物体的默认渲染程序是怎么的;
  2. 通明物体的默认渲染程序是怎么的;
  3. 不通明物体和通明物体一起渲染的时候,默认渲染程序是怎么的;
  4. 如何扭转物体的默认渲染程序。

不通明物体的默认渲染程序是怎么的

首先,咱们通过一个简略的例子介绍下咱们要实现的成果:

一个根本场景,场景中搁置两个4X4正方形,正方形与XOY立体平行,红色正方形放在(0, 0, 0)的地位,绿色正方形放在(2, 0, -2)的地位。

相机应用THREE.PerspectiveCamera,这种相机会有近大远小的成果,相机搁置在(0, 0, 6)的地位,此时,相机看向Z轴的负方向。

相机和两个立体的空间地位如下图1、图2所示:

图1


图2

通过相机和两个立体的空间地位关系,咱们晓得

  1. 红色立体在绿色立体的后面显示;
  2. 红色立体会遮挡绿色立体的一部分;

最初出现成果如下图3所示:

图3

此时,你能够先本人思考一下,如果让你本人实现,你会如何实现?

我过后本人想了一下,首先咱们是晓得这两个物体和相机之间的间隔的,咱们能够依照物体间隔相机的远近给物体排个序,而后依照由远及近的程序渲染物体。也就是先渲染绿色的立体,而后渲染红色的立体。下面这个简略的例子没有问题,那对其余简单的场景是否实用呢?

比方,这两个立体间隔相机的地位是一样的?如下图4所示:

图4

依照咱们方才的构想,对于上述情景,咱们渲染进去的是要么绿色齐全在下面,要么红色齐全在下面,如下图5或图6所示:

图5

图6

然而理论的渲染后果应该如下图7所示:

图7

所以上述依照由远及近的程序渲染的计划只能满足局部应用场景。那么,THREE.js是如何实现的呢?

THREE.js次要是应用WebGL构建3D场景的开源库,是对WebGL提供的能力的一个易用性封装。所以在探讨THREE.js中物体的渲染程序之前就得先看下WebGL是如何渲染不同地位的物体的。而WebGL是基于OpenGL的,所以这个问题就变成了OpenGL是如何渲染不同地位的物体的。

OpenGL是应用深度测试(depth testing)来保障物体渲染进去正确的遮挡关系的。深度测试应用了深度缓冲区(depth buffer)。和色彩缓冲区(color buffer)相似,只不过色彩缓冲区中存储的是每个像素点的色值,而深度缓冲区存储的是色彩缓冲区以后像素点的色值所在的深度。深度缓冲区和色彩缓冲区具备雷同的宽度和高度。

那么,在渲染过程中,对于每一个像素点,咱们存储的数据包含:

  1. 以后像素的色值(通过色彩缓冲区获取);
  2. 以后像素对应的物体片元在空间中的深度(通过深度缓冲区获取)。

上面,咱们通过上述图4的例子阐明一下,OpenGL是如何通过深度测试(depth testing)实现物体正确的遮挡关系的。

假如物体先绘制绿色的立体,物体在绘制的时候是逐像素绘制的,此时咱们已知的信息包含像素的色值和深度信息。对于绿色立体投影到的每一个像素,咱们在色彩缓冲区中该像素的地位写入色值,在深度缓冲区中该像素的地位写入深度信息。

而后,咱们开始绘制红色的物体。当绘制红色物体的每一个像素时,咱们晓得了该像素的色值和深度信息D2。而后,咱们依据该像素的坐标,获取深度缓冲区中已绘制像素对应的深度值D1,而后比拟D1和D2的值。有以下三种状况:

  1. 如果D2小于D1,那就是该物体的以后像素在后面,也就是该像素应该取该物体的以后像素的色值。此时,更新色彩缓冲区以后像素的色值为红色,更新深度缓冲区以后像素的深度为D2;
  2. 如果D2大于D1,那就是该物体的以后像素在前面,也就是该像素不会显示进去,所以以后像素的色彩缓冲区和深度缓冲区的值都不必扭转;
  3. 如果D2等于D1,行为和D2小于D1统一。

总结来说,就是咱们有一个判断该像素点是否渲染的函数。该函数的输出是

  1. 以后期待渲染的像素点的深度值;
  2. 深度缓冲区中以后像素点的深度值。

输入是一个布尔值表明以后像素是否应用新的色值渲染。

THREE.js中该函数的默认值是LessEqualDepth,也就是上述D2和D1比拟的三种状况。该函数的所有取值能够参考THREE.js官网Depth Mode。

所以,还是下面那个例子:

  1. 对于红色物体右边的每一个像素,该深度值D2小于深度缓冲区中的深度值D1,所以色彩缓冲区更新为红色。
  2. 对于红色物体左边的每一个像素,该深度值D2大于深度缓冲区中的深度值D1,所以放弃原来的色彩。

最终的后果就是左半边是红色,右半边是绿色。

咱们下面剖析了先绘制绿色立体,再绘制红色立体的状况。你能够本人尝试剖析先绘制红色立体,再渲染绿色立体的状况。

最初的后果就是渲染后果的遮挡关系根本和绘制的先后顺序无关。

通明物体的默认渲染程序是怎么的

后面讲述了不通明物体的渲染程序,那么,如果场景中的物体都是通明物体的时候,又是如何渲染的呢?

还是拿后面的例子举例,此时咱们把两个立体都设置成半透明的。如下图8所示:

图8

如果还是用后面那个逻辑,每个像素点的色彩要么不变,要么应用新物体的色彩,加上深度测试的逻辑之后,渲染进去的成果如下图9所示:

图9

在现实生活中,透过通明的物体咱们应该是能够看到该通明物体前面的物体的。显然,图9并没有实现这样的成果。那么,问题出在什么中央呢?

咱们后面在深度测试的时候,在往色彩缓冲区中写入色值的时候,要么写入以后物体的色值,要么抛弃以后物体的色值。而对于通明物体来说,最终显示的色值并不是单个物体的色彩,而是多个可见物体色彩的一个混合(blend)。

那么,在后面步骤中,当咱们判断以后物体是在后面时,能够从简略粗犷的间接应用该色值变为依据以后物体的色值和在色彩缓冲区中的色值依照透明度进行一个混合,而后应用混合后的色值更新色彩缓冲区。

THREE.js提供了多种blend办法,默认是NormalBlending。NormalBlending的计算公式如下:
color(RGB) = (sourceColor * sourceAlpha) + (destinationColor * (1 - sourceAlpha))
color(A) = (sourceAlpha * 1) + (destinationAlpha * (1 - sourceAlpha))

增加上混合逻辑,最初实现进去的成果如下图10所示,这个成果也是合乎咱们心理预期的一个成果:

图10

在渲染不通明物体的时候,咱们发现最终的实现成果和物体绘制的前后程序没有关系。那么,对于通明物体呢?咱们试验一下:

先渲染红色立体,再渲染绿色立体

渲染成果如下图11所示:

图11

先渲染绿色立体,再渲染红色立体

渲染成果如下图12所示:

图12

能够看到,对于通明物体的渲染来说,绘制的先后顺序会影响渲染后果的遮挡关系。那么这是什么起因导致的呢?

咱们剖析下先渲染红色立体,而后渲染绿色立体的状况。首先,绘制完红色立体之后,色彩缓冲区和深度缓冲区中存储的是红色立体相干的数据。此时,对于被红色立体遮挡的每一个绿色像素来说,先进行深度测试,深度测试失败了,所以该像素间接抛弃了。

所以这里的问题就是,当深度测试胜利的时候,咱们能够抉择是否混合以及混合的函数;然而当深度测试失败的时候,是间接抛弃该像素,而不是也给你提供一个函数,让你自定义这个像素的色值。

综上,通明物体的最终渲染后果和物体的绘制程序相干。当通明物体依照由远及近的程序绘制时,后果会在更大程度上合乎咱们的预期;当通明物体依照由近及远的程序绘制时,后果基本上不会合乎咱们的预期,除非你是无意为之。

下面之所以说是在更大程度上而不是肯定的起因是存在一些非凡的状况。从上述论断中,咱们也能够晓得渲染后果和绘制程序相干。咱们能够在绘制物体之前先给物体排个序。然而须要留神的是,咱们排序应用的是一个示意物体整体地位的坐标信息,而不是依据物体的每个像素进行排序。所以对于两个穿插的物体,无论绘制程序是什么样的,最初的渲染后果都是不正确的。如下图13、14所示:

图13

图14

就上述这种状况,目前我还没有找到解决方案。

不通明物体和通明物体一起渲染的时候,默认渲染程序是怎么的

如果咱们的场景中既有不通明物体又有通明物体,那么,在后面的根底上试想一下,咱们应该如何实现呢?

首先,

  1. 对于不通明物体来说,不要求绘制程序;
  2. 对于通明物体来说,须要依照由远及近的程序绘制;

那总结起来,是不是能够把所有的物体依照由远及近的程序进行排序,而后依照这个程序进行绘制呢?

我本人想了下感觉没有问题,然而发现THREE.js并不是依照这个逻辑实现的。咱们先说下THREE.js的默认渲染程序:

  1. 首先,把场景中的物体依据是否通明划分为两个数组;
  2. 对于不通明物体所在的数组,依照由近及远的程序排序;
  3. 对于通明物体所在的数组,依照由远及近的程序排序;
  4. 绘制不通明物体所在的数组;
  5. 绘制通明物体所在的数组。

我想了下,THREE.js之所以这样实现的起因应该是从性能方面思考。

首先,对于不通明物体来说,尽管绘制程序对渲染后果没有影响,然而对渲染性能还是有影响的。举例来说,比方两个平行的立体AB,立体A比立体B的间隔近,此时:

  1. 先绘制A,再绘制B:

    1. 立体A的所有像素执行深度测试,测试胜利,重写色彩和深度缓冲区;
    2. 立体B的所有像素执行深度测试,对于不被立体A遮挡的局部,重写色彩和深度缓冲区;对于被遮挡的局部,深度测试失败,间接返回;
  2. 先绘制B,再绘制A:

    1. 立体B的所有像素执行深度测试,测试胜利,重写色彩和深度缓冲区;
    2. 立体A的所有像素执行深度测试,测试胜利,重写色彩和深度缓冲区。

通过上述比照能够发现,当对不通明物体依照由近及远的程序绘制的时候,是能够省掉前面遮挡局部重写色彩和深度缓冲区的操作的,所以在肯定水平上进步了性能。

其次,当咱们依照由近及远的程序绘制完不通明物体,开始绘制通明物体的时候,在不通明物体前面的通明物体深度测试失败,所以不会执行上面的色彩和深度缓冲区的更新操作,所以也能在肯定水平上进步渲染通明物体的性能。

所以,如果不做辨别,一起绘制不通明物体和通明物体的时候,那么所有的物体都得依照由远及近的程序绘制。那么,大部分状况下的深度测试都会胜利,也就是会有更多的色彩和深度缓冲区的更新操作,在肯定水平上影响了性能。

如何扭转物体的默认渲染成果

后面咱们提到的大部分都是默认绘制程序的成果,然而如果你想扭转默认渲染成果,那有没有什么办法呢?

答案是有的。

管制深度测试

后面咱们有说到深度测试,深度测试的三个步骤都是能够管制的:

  1. 是否进行深度测试;
  2. 深度测试函数的行为;
  3. 是否更新深度缓冲区。

这三个步骤别离是通过Material的上面三个属性管制的:

  1. depthTest:是否进行深度测试;
  2. depthFunc:深度测试函数的行为;
  3. depthWrite:是否更新深度缓冲区。

此外,当你须要开启深度测试的时候,须要在初始化WebGLRenderer的时候开启depth参数,这个参数会创立一个深度缓冲区。这个参数的默认值是true,也就是个别状况下你不必关注这个属性。当然,如果你的需要明确不须要深度测试,并且性能要求比拟高的话,你能够手动敞开这个值,缩小肯定的存储老本。

管制绘制程序

后面咱们有提到,THREE.js绘制不通明物体和通明物体别离是依照由近及远和由远及近的程序绘制的,那么这个排序是THREE.js给咱们实现的吗?还是须要咱们本人管制绘制程序?

THREE.js默认是开启排序的,这个是通过WebGLRenderer的sortObjects属性实现的。如果不开启主动排序,绘制程序就是物体的增加程序(留神,此时通明物体和非通明物体依然是离开渲染的)。

不通明物体和通明物体的默认排序程序能够参考源码的painterSortStable和reversePainterSortStable办法。

那么,咱们如何干涉上述排序过程呢?次要有如下两种形式:

  1. 上述两个办法,咱们能够留神到其中有renderOrder属性,这个属性就是咱们须要的,具体阐明能够参见renderOrder文档;
  2. 通过setOpaqueSort和setTransparentSort齐全自定义排序逻辑。

自定义混合(blend)函数

后面咱们讲通明物体渲染的时候有提到通明物体的默认混合函数是NormalBlending。这个混合函数的行为也是可选的,具体可反对的行为能够参考Material.blending。

总结

本文次要讲述了THREE.js中的不通明物体和通明物体的渲染程序,次要波及THREE.js的以下内容:

  1. Material

    1. depthWrite(default is true)
    2. depthTest(default is true)
    3. depthFunc(default is LessEqualDepth)
    4. blending及blending相干的一系列属性
  2. Object3D

    1. renderOrder(default is 0)
  3. WebGLRenderer

    1. depth
    2. sortObjects(default is true)
    3. setOpaqueSort
    4. setTransparentSort

上述观点是基于目前对THREE.js的钻研后果,可能会有认知谬误。如有,欢送留言评论。