webgl自学笔记——深度监测与混合

这一章中关于webgl中颜色的使用我们将深入研究。我们将从研究颜色在webgl和essl中如何被组装和获取开始。然后我们讨论在物体、光照和场景中颜色的使用。这之后我们将看到当一个物体在另一个物体前面是webgl如何来实现物体碰撞,这是通过深度检测来实现的。相反透明度混合允许我们结合所有物体的颜色当一个物体与另一个物体啮合时。我们将用透明度混合来创建透明物体。

这一章主要讨论:

  1. 在物体上使用颜色
  2. 为光源分配颜色
  3. 在ESSL中使用多光源
  4. 深度检测和z缓冲区
  5. 混合方法和公式
  6. 使用face culling来创建透明物体

webgl在RGB颜色模型中还包含第四个属性,这个属性被称为开端通道。扩展后的模型称为RGBA模型,A是为支持alpha。a的取值范围时0.0-1.0,跟其他三个一样。下图代表颜色空间。横轴代表结合rgb能够得到的颜色,纵轴代表alpha通道。

alpha为颜色带来了额外的信息。这个信息影响颜色渲染在屏幕上的方式。通常来说alpha影响颜色的透明度。一般来说我们的颜色都是不透明的,但有一些情况我们得考虑获得半透明颜色。

在webgl 3D场景中我们到处都在使用颜色:

  1. Objects: 3d物体可以通过为每一个顶点选择一个颜色来上色,或者为整个物体选择一个颜色。这通常由材料的diffuse属性决定
  2. Lights:我们可以使用颜色不是白色的环境光和反射光属性。
  3. Scene:场景的背景色可以通过gl.clearColor方法来改变。稍后我们将看到当我们使用透明物体时需要做一些特殊的操作。

·最终的颜色在片元着色器中通过设置特殊的gl_FragColor来得到。如果这个物体的所有片元都拥有同样的颜色,我们可以说这个问题有一个常量颜色。否则这个物体有per-vertex 颜色

Constant coloring

为了获得常量颜色我们把需要得到的颜色放在一个uniform存储器中,这个变量直接传递给片元着色器。这个uniform通常被称为物体的漫反射材料属性。我们也可以结合物体的法线和光源信息来获取Lambert反射系数。我们使用兰伯特系数通过依赖反射光与光源的夹角来改变反射颜色。

下图第一个是没有结合反射系数的物体,第二个是结合反射系数后的颜色。

Per-vertex coloring

在医学和工程可视化应用中,通常能够找到跟他们要渲染的物体的顶点对应的颜色地图。这些地图为每一个顶点分配一个单独的颜色。为了实现per-vertex 着色我们需要在顶点着色器中定义一个attribute来存储顶点的颜色。

attribute vec4 aVertexColor

下一步是将aVertexColor属性分配给一个varying变量,以便能够进入片元着色器中。varying变量是可以被自动插值的。因此每一个片元的颜色都是根据为绕它的顶点的颜色按权重分配的。

如果我们想让我们的颜色地图能够反映光照条件,我们可以让每一个顶点颜色乘以光照的漫反射部分。得到的结果将被分配到可以将它传递到片元着色器的varying变量中。

左侧图片没有反映光源位置信息,右侧图片反映了光源位置信息

Per-fragment coloring

我们可以为每一个像素分配一个随机的颜色,但是ESSL中并没有一个内置的随机数函数。我们可以通过其他方式来获得。这里不展开

注意:当启用alpha通道时需要的是 "attribute vec4 aVertexColor",然后要启用混合功能gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

Use of color in lights

颜色也是光照的属性;在第3章中我们看到光照属性的数量取决于为场景选择的光照模型。比如使用兰伯特反射模型我们只需要一个着色器uniform量:diffuse;相反在Phong反射模型中每一个光源需要拥有三个属性:环境光、漫反射光、镜面光。

(另外,光源位置通常也在着色器中作为一个unifrom变量,以便知道光源位置,所以Phong模型在点位置光源方式中由四个uniform:ambient、diffuse、specular、position;对于方向光这个uniform变量表示的是光源的方向)

Using multiple lights and the scalability problem

Phong反射模型下一个光源需要四个uniform变量,如果我们有多个光源意味着我们需要的uniform会成倍数上升,而每台机器能够获取的最大unifrom数目是有限的;我们可以通过gl.getParameter()中传入gl.MAX_VERTEX_UNIFORM_VECTORS和gl.MAX_FRAGMENT_UNIFORM_VECTORS参数来获取能够使用的最大uniform数目。

Using uniform arrays to handle multiple lights

将多光源的uniform分别存储增加代码量并且难以维护,ESSL中允许我们使用unifrom 数组。这项技术可以允许我们在着色器中引入光照数组来管理多光源。通过这种方式我们可以在着色器总通过便利光照数组来计算光照颜色。虽然我们仍在需要在JavaScript中定义多个光源对象,但是传递给着色器时就变得简单了。

所以在着色器总我们需要定义:

unifrom vec3 uPositionLight[3]

注意:ESSL中不支持动态初始化unifrom 数组

也就意味着我们无法动态创建多光源

然后传递给着色器:

directional point lights

方向点光源,创建这种光源的秘诀在于利用顶点法线和光源位置向量做减法生成新的法线向量,将新法线向量传递给片元着色器:

所有的颜色都是通过光照模型算出来的

Use of color in the scene

前面我们提到过alpha通道可以存放关于被绘制的物体颜色的透明度信息。但是除非alpha混合被激活否则无法创建透明物体。当我们在场景中有多个物体时,事情就变得复杂起来。我们将学会如何在场景中绘制透明物体和非透明物体。

Transparency

获得透明物体的第一步要使用Polygon stepping(多边形点刻)技术。这项技术会抛弃一些片元所以能够穿过物体看到东西。就像在你的物体表面刻出许多小孔。

在webgl中我们需要使用ESSL的discard命令在片元着色器中去除掉一些片元。直接设置alpha厚webgl并不能自动产生透明效果。

创建透明物体意味着我们要改表已经写入帧缓冲区的片元。比如在一个场景中,这里有一个透明的物体在一个非透明物体的前面。如果这个场景被正确渲染出来,那么我们需要能够透过透明物体看到非透明物体。因此在远处和近处物体叠盖处的片元需要以某种方式来创建出透明效果。

为了实现透明效果我们需要学习两个总要概念:深度检测、alpha混合。

Updated rendering pipeline

对于片元来说一旦他们被片元着色器处理之后,深度检测和alpha混合就是两个可选的阶段。如果深度检测没有被激活,所有的片元对alpha混合来说都自动可用。如果深度检测开启了,这些在深度检测结果为false的片元都将被渲染管线自动抛弃在其他操作中不在可用。这意味着被抛弃的片元将不会被渲染。

添加了深度检测或alpha混合后的渲染管线如下图所示:

Depth testing

每一个被片元着色器处理后的片元都携带深度信息。尽管片元显示在屏幕上是二维的,深度值保留了这个片元距离相机屏幕的距离。深度值被存储在一个特殊的webgl缓冲区中称为深度缓冲区或z-buffer。

如果深度检测被启用了,在片元着色器计算完片元之后,下一步将进行深度检测。开启深度检测:

gl.enable(gl.DEPTH_TEST)

深度检测通过将一个片元色深度信息与相同片元位置处已经保存的在深度缓冲区中的深度值进行比较。深度检测将决定这个片元是否将进入渲染管线未来的处理中。只有通过深度检测的片元会被进一步处理,没有通过深度检测的片元将被抛弃。

一般情况下,深度值小的片元将被接受(距离屏幕更近)。

深度测试是一种呈现顺序的交换操作。这意味着无论先呈现哪个对象,只要启用深度测试,我们总是有一个一致的场景

开启深度检测后,距离屏幕近(深度值)小的物体被呈现出来。距离屏幕远的物体在这一叠盖区中被抛弃

默认情况下深度检测是禁用的,我们可以启用深度检测,默认情况下用gl.LESS方法来做深度测试,我们可以通过更改深度检测的方式来做一些特殊效果:gl.depthFunc(gl.LESS)

Alpha blending

通过深度检测的片元可以进入alpha混合阶段,然而如果深度检测被关闭,所有片元都可以进入alpha混合阶段。alpha通过:gl.enable(gl.BLEND)开启。

对于每一个合格的片元alpha混合操作读取在帧缓冲中的当前位置片元的颜色,然后通过在片元着色器预先计算好的颜色和目前帧缓冲中已有的颜色进行线性差值来得到一个的颜色。

Blending function

启用alpha混合只有下一步就是定义一个混合函数。这个函数将决定如何将我们将渲染的物体的颜色与当前已经存在帧缓冲中的颜色进行混合处理。

我们使用如下公式来做alpha混合

注意渲染顺序将决定在上面公式中的source和destination片元;将要渲染到frame buffer中的片元称为源,已经在framebuffer中的片元称为目标。

Separate blending functions

我们也可以将rgb与alpha单独计算,使用gl.blendFuncSeperate函数,比如我们可以为rgb和alpha使用如下两个独立公式

二者单独使用不同方式来计算:

函数:

gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ZERO)

Blend equation

我们还可以为颜色混合使用不同的混合公式:

alpha 混合相关API:

gl.enable(gl.BLEND)// 启用混合

gl.blendFunc(sw, dw) // 决定对颜色模型中哪一部分进行操作

gl.blendFuncSeperate(sw_rgb, dw_rgb, sw_a, dw_a);// 决定如何对rgb和alpha分离计算

gl.blendEquation(mode)// 决定源和目标颜色的结合方式

gl.blendEquationSeparate(modeRGB, modeAlpha)// 决定分离方式中源和目标的结合方式

gl.blendColor设置混合颜色

gl.getParameter(pname)// 获取混合相关参数

Creating transparent objects

为了创建透明物体,我们需要:

  1. 启用alpha混合并选择混合插值函数
  2. 从远到近的渲染物体(物体的绘制顺序决定谁是源谁是目标)

对于同一个物体,我们使用面剔除方法来创建透明度。(因为一个物体没有其他物体来做颜色混合,所以处理思路是将一个物体的前景面和后镜面分别当成两个单独物体来绘制,这样就可以用两个物体做alpha混合。)

个人理解

深度检测通过将一个片元色深度信息与相同片元位置处已经保存的在深度缓冲区中的深度值进行比较。深度检测将决定这个片元是否将进入渲染管线未来的处理中。只有通过深度检测的片元会被进一步处理,没有通过深度检测的片元将被抛弃。

一般情况下,深度值小的片元将被接受(距离屏幕更近)。

深度测试是一种呈现顺序的交换操作。这意味着无论先呈现哪个对象,只要启用深度测试,我们总是有一个一致的场景

开启深度检测后,距离屏幕近(深度值)小的物体被呈现出来。距离屏幕远的物体在这一叠盖区中被抛弃

如果没有开启深度监测那么就跟渲染顺序相关;后渲染的物体会遮挡住先渲染的物体

确切来说深度监测判断物体远近不是以世界坐标为基准来判断的而是以投影坐标来判断的

颜色混合发生在深度监测之后,只有通过深度检测的片元才会进入到颜色混合阶段。

如果没有开启blend,那么透明物体也会把不透明物体给遮挡住

如果开启颜色混合,那么只有通过深度监测的片元才会进入颜色混合阶段,没有通过深度监测的片元将会被抛弃

这时候因为先绘制了cone,而在这个矩阵下,cone离着相机更近,所以有sphere部分片元没有通过深度测试,直接被抛弃

如果先绘制sphere,cone所有片元都会通过深度测试,与已有的颜色缓冲发生颜色混合。

如果开启颜色混合,物体的绘制顺序最终会影响颜色混合的呈现效果

如果关闭深度监测,那么混合效果完全跟渲染顺序相关(整体效果会跟物体位置相关,如下面两图都是先绘制椎体混合颜色是相同的,但是有近大远小效果,所以球形和椎体的最终呈现效果有所差异)

下面是先绘制球形

posted @ 2019-01-05 10:23  木的树  阅读(4002)  评论(1编辑  收藏  举报