《WebGL 编程指南》进阶篇
Posted 肥肠负能量
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《WebGL 编程指南》进阶篇相关的知识,希望对你有一定的参考价值。
上一篇文章中,我们将《WebGL编程指南》的一些基础用法进行了介绍,今天小编将和大家一起学习如何通过WebGL实现选中某个图形,雾化效果和阴影效果。
(一)选中某个图形
我们知道,在DOM上可以通过给元素绑定click事件来响应鼠标的点击,但是由于WebGL是通过canvas来呈现内容的,我们没有现成的方法来在某一个具体图形上响应鼠标事件,只能通过WebGL自身的一些特性来实现。WebGL通过readPixel函数可以读取canvas上具体某一个区域的像素值,为了实现选中某个物体,我们可以在点击时,将物体临时赋予不同的颜色值,这样我们通过比较读取到的像素数据来确定是否选中了某个物体。通常颜色缓冲区RGBA每个分量都是8bit,所以仅R分量就可以区分255个物体。
gl.readPixel(x,y,width,height,format,type,pixel)
因此,响应canvas的点击事件,判断当前点击在canvas上的坐标,读取像素值,比较是否与我们预设的像素值一致,即可实现canvas上物体的选中。其实现代码如下:
(二)雾化效果
在我们绘制实际的场景的时候,有时候需要用到雾化效果,也就是我们常说的大气效果。现实场景中,很多介质都有可能表现出雾化现象,实现雾化的方式有很多种,这里我们介绍的是最简单的一种,线性雾化。在线性雾化中,随着距离的增加,雾化程度越高。某一点的雾化程度被定义为雾化因子。其计算公式如下:
<雾化因子> = (<终点> - <当前点与视点的距离>) / (<终点> - <起点>)
如果雾化因子为1.0,表示该点完全没有雾化,可以很清晰的看到此处的物体,如果雾化因子为0.0,表示该点完全雾化了,此处的物体完全看不到。通过上面的公式,我们可以知道,片元着色器需要计算出雾化因子,然后结合物体表面颜色和雾的颜色计算出最终颜色,计算公式如下:
<片元颜色> = <物体表面颜色>×<雾化因子> +<雾的颜色>×(1- <雾化因子>)
其片元着色器程序如下:
这里mix函数会计算x*(1-z)+y*z,其中x,y,z分别为第一、二、三个参数。clamp函数将第一个参数的值限制在了第二个和第三个参数中间。
由于在顶点着色器中计算顶点和视点的距离会造成较大开销,因此一般采用了一种近似估算值来代替这个距离,那就是经过顶点模型视图投影矩阵变换后的坐标w分量,实际上这个w分量就是顶点视图坐标的z分量乘以-1。在视图坐标系中,视点在原点,视线沿着Z轴负方向,观察者看到物体其视图坐标系值z分量都是负的,而gl_Position的w分量值正好是z分量乘以-1,所以可以直接用该值来近似顶点和视点的距离。
(三)阴影效果
在讲阴影之前,我们需要先弄清楚WebGL的另一个缓冲区:帧缓冲区。默认情况下,WebGL在颜色缓冲区进行绘图,开启隐藏面消除时,还会用到深度缓冲区,但是绘制结果都是存储在颜色缓冲区的。帧缓冲区对象是可以用来代替颜色缓冲区或深度缓冲区的,绘制在帧缓冲区的对象不会直接显示在canvas上,你可以对其内容进行一些处理再显示,或者将其中的内容作为纹理对象,在帧缓冲区绘制的过程又称为离屏绘制。
一个帧缓冲区有3个关联对象,颜色关联对象,深度关联对象和模板关联对象,分别用来代替颜色缓冲区,深度缓冲区和模板缓冲区。WebGL通过向帧缓冲区的关联对象中写入数据来实现帧缓冲区的各项操作,每个关联对象又可以是两种类型,纹理对象和渲染缓冲区对象。渲染缓冲区对象表示一种更加通用的绘图区域,可以向其写入多种类型的数据。
使用帧缓冲区对象只需要如下八个步骤:
(1) 创建帧缓冲区对象,这与我们前一篇文章中提到的创建与顶点有关的缓冲区对象是一致的行为,先创建缓冲区对象。
gl.createFramebuffer();
(2) 创建纹理对象并设置其尺寸和参数,同上一篇内容。
var texture= gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D,texture);
gl.texImage2D(…);
gl.parameteri(…);
(3) 创建渲染缓冲区对象
gl.createRenderbuffer();
(4) 绑定渲染缓冲区对象并设置尺寸
gl.bindRenderbuffer();
gl.renderbufferStorage();
(5) 将帧缓冲区的颜色关联对象关联到我们指定的纹理对象上 gl.framebufferTexure2D();
(6) 将帧缓冲区的深度关联对象关联到我们创建的渲染缓冲区对象上 gl.framebuggerRenderbuffer();
(7) 检查帧缓冲区配置是否正确
gl.checkFrambufferStatus();
(8) 绑定帧缓冲区,在帧缓冲区进行绘制
gl.bindFramebuffer();
经过如上八个步骤,我们就完成了在帧缓冲区中的绘制,其代码实现如下:
接下来,我们需要考虑如何实现阴影。我们可以想象,如果我们顺着光线去看物体,我们是看不到阴影的,而我们没有看到的物体,其实是处在阴影中的,因此我们需要用到光源与物体之间的距离来决定物体是否可见。如下图所示,
同一光线上有P1和P2两个点,P2的z值大于P1,因此P2处在阴影里。
我们可以通过两步实现阴影,首先,计算出光源到物体的距离,然后绘制出实际场景,当然绘制是在帧缓冲区中进行,而不是在canvas上进行,然后将绘制结果作为一张纹理图像传递到颜色缓冲区,绘制要在canvas上显示的场景,因为在帧缓冲区中的绘制结果,包含了物体的深度信息,我们在颜色缓冲区进行绘制的时候,根据z值的大小,将z值较大的片元用较暗的颜色进行绘制,表示当前片元出在阴影中。
计算阴影贴图的着色器程序与我们前一篇文章中讲到的在WebGL中绘制图形基本一致,这里不再赘述,这里主要看一下绘制真实场景的着色器程序的实现。
在顶点着色器中,我们将视点在原始位置的模型视图投影矩阵uMvpMatrix和视点在光源处的模型视图投影矩阵uLightMvpMatrix传递给了顶点着色器,计算出了顶点在光源视图投影矩阵下的位置,传递给片元着色器。
在片元着色器中,通过顶点传递过来顶点在光源视图投影矩阵下的坐标,来得到纹理坐标,因为纹理坐标是在[0.0,1.0]区间上的,所以我们将WebGL坐标归一化到[0,1.0]之间,然后比较在视图投影矩阵下片元的Z值与纹理图像中Z值的大小,如果当前片元的Z值大于纹理图像中的Z值,表示当前片元处在阴影中,反之则不是阴影状态下。这里我们看到在比较的过程中,我们加了一个0.005的偏移量,这是因为纹理图像的RGBA分量都是8位,存储在阴影贴图中的z值精度也只有8位,而与其进行比较的LightCoord.z是float类型,有16位,这时候我们就需要一个略大于精度的偏移量,来避免精度产生的问题,这里0.005是大于1/256的。
除了我们介绍的以上三个WebGL的高级技术,还有很多例如⍺混合,hud(平视显示器)等等,这些比较简单,这里就不再一一讲解。
以上是关于《WebGL 编程指南》进阶篇的主要内容,如果未能解决你的问题,请参考以下文章