Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

WebGL(拾捌) 基本阴影处理 - 实例篇

  上一篇已经把原理说了一遍,这篇咱来看实例的代码吧。从原理上只能理解它的大概信息,只有实例才能说明问题。我想比起枯燥的原理,大家更喜欢研究实例吧。这个例子只是最基础的WebGL阴影实现,不过由于阴影效果的实现本身很复杂,所以代码有点长了。
  咱先来看着色器的代码吧,首先是顶点着色器的代码 //顶点着色器
attribute vec3 po;
attribute vec2 mp;
attribute vec3 no;
uniform vec3 li_co;
uniform vec3 li_di;
uniform bool tp; //操作类型
uniform mat4 tra; //变换矩阵
uniform mat4 pro_a; //计算深度时用的投射矩阵
uniform mat4 viw_a; //计算深度时用的视图矩阵
uniform mat4 pro_b; //绘制到屏幕的投射矩阵
uniform mat4 viw_b; //绘制到屏幕的视图矩阵
varying vec3 dif;
varying vec2 mp_v;
varying vec4 po_v; //包含深度信息的顶点数据
void main(){
  //无论是绘制到帧缓冲还是屏幕都需要计算包含深度信息的顶点数据
  po_v=pro_a*viw_a*tra*vec4(po,1.0);
  if(tp){ //绘制到帧缓冲是不需要其他计算
    gl_Position=po_v;
  }else{ //绘制到屏幕时需要计算光照效果
    gl_Position=pro_b*viw_b*tra*vec4(po,1.0);
    float v=-dot((tra*vec4(normalize(no),0.0)).xyz,li_di);
    dif=clamp(v,0.2,0.8)*li_co;
    mp_v=mp;
  };
}
  我的例子只用了一个着色器程序,也就是把帧缓冲中的深度计算和正常渲染到屏幕这两个工作都放在了同一个着色器中完成。所以我使用一个布尔参数来区分这两个操作,它就是uniform中的tp这玩意儿。为true时代表绘制到帧缓冲,为false时代表绘制到屏幕。顶点着色器的代码中有一堆uniform参数,值得注意的是有两个投射矩阵和两个视图矩阵,它们分别是绘制到帧缓冲时使用的和绘制到屏幕时使用的,具体一会儿在设置全局属性时再说。在main函数中的if语句之外计算了po_v,这意味着无论是绘制到帧缓冲中还是绘制到屏幕都需要用到它。它是一个varying变量,会被传入片段着色器中处理的。从它的计算上就可以看出,它就是我们站在光源位置时要处理的定顶点数据。其他东西都很简单,应该不用说了吧?接着看片段着色器的代码。
//片段着色器
precision highp float;
uniform sampler2D tex; //正常贴图数据
uniform sampler2D tex_fb; //帧缓冲贴图数据
uniform vec3 amb;
uniform bool tp; //操作类型
varying vec3 dif;
varying vec2 mp_v;
varying vec4 po_v; //包含深度信息的顶点数据
void main(){
  if(tp){ //绘制到帧缓冲中需要把z坐标输出就行
    gl_FragColor=vec4(po_v.zzz,1.0);
  }else{ //绘制到屏幕
    //从帧缓冲贴图数据中读取相应顶点位置的深度信息
    float dp=texture2D(tex_fb,po_v.xy/po_v.w*0.5+0.5).z;
    //判断当前顶点的深度是否和贴图中获取的深度相近
    //如果相近则使用正常的光照效果
    //如果不相近则把光照效果调到最低
    vec4 li=abs(po_v.z-dp)<0.01?vec4(dif,1.0):vec4(0.2,0.2,0.2,1.0);
    //从正常的贴图中取出颜色数据,并应用光照计算
    gl_FragColor=texture2D(tex,mp_v)*li+vec4(amb,1.0);
  };
}
  片段着色器中有两个sampler2D类型的uniform,一个是作为正常的贴图,另一个是帧缓冲中的数据。其他接口变量比较简单我就不说了。在main函数中,无论tp的取值如何,if语句的两边都是围绕着po_v这个参数工作的。绘制到帧缓冲中时,我直接把z坐标放入输出颜色的RGB三个通道上,上一篇文章中的灰度图就是来自它输出的数据。其实这方法并不好,我是懒的做其他计算,反正只要能保存下深度数据就行。在绘制的屏幕时,我们使用po_v这个顶点数据的xy坐标去帧缓冲的贴图数据中取深度信息。因为po_v的计算方式是和输出到帧缓冲中时候的计算方式一样的,所以它的坐标可以对应上贴图数据。不过在使用这个坐标去贴图上取数据是还是要做一些处理。因为我们在顶点着色器中处理输出到帧缓冲的情况时是直接把po_v丢给了gl_Position。WebGL在计算顶点坐标时候会处理齐次项,也就是它的w分量。所以这我们也需要处理w分量来把这个xy坐标变成WebGL输出图像时候的坐标。还有后面的“*0.5+0.5”是因为贴图和绘图时的坐标系不同,这个问题应该要明白的,我就不细说了。后面的操作注释中就说的很清楚了,也不太复杂,这里就不描述了。下是设置一些全局属性的代码
webgl.uniformMatrix4fv(viw_b,false,[
  1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1
]);
webgl.uniformMatrix4fv(pro_b,false,[
  1,0,0,0, 0,1,0,0, 0,0,-1,-1, 0,0,-2,0
]);
webgl.uniformMatrix4fv(pro_a,false,[
  1,0,0,0, 0,1,0,0, 0,0,-1/16,0, 0,0,0,3
]);
var D=normalize3D([-0.3,-1,0]); //平行光方向和帧缓冲中的摄像机方法
webgl.uniform3fv(li_di,D);
//计算视图矩阵(把摄像机移动到光源位置)
webgl.uniformMatrix4fv(viw_a,false,(function(P,F,R){
  var i,s,F=normalize3D(F),R=normalize3D(R),U=cross3D(R,F);
  for(i=0,s=[];i<3;i++)s.push(R[i],U[i],-F[i],0);
  return s.concat([-dot3D(P,R),-dot3D(P,U),dot3D(P,F),1]);
})([1.2,4,-2.5],D,[0,0,-1]));
  这里设置了一些矩阵。viw_b是单位矩阵,我们最终效果的摄像机是在原点上的,不做变化。pro_b是普通的投射矩阵,之前文章的例子用过好多次了。pro_a是正交投影的矩阵,如果你有好好推导过我们的投射矩阵,这就很容易理解。其实它就是单位矩阵上做一些微调。比如里面的“-1/16”就意味着z坐标要被除以16。这是为了让场景的z坐标不超出WebGL规范化设备坐标,具体的取值要看场景是什么样的。最好取场景的最大z坐标值,如果取太大会损失计算精度。至于为什么是负号,原因和和投射矩阵中的负号一样。最后的“3”是对整个场景的缩小。如果为1,你会发现绘制出来的东西太大了,CANVAS容纳不下,超出的区域就无法计算深度。所以要适当的缩小,这个值也是根据场景来取的。和上面一样,如果取值太大绘制出的图像太小也会影响深度计算的精度。最后是光线方向的的设置和生成viw_a这个视图矩阵来把摄像机移动到光源的位置,并设置上和光源相同的朝向。具体的算法在之前的文章中有介绍过。这里还用了几个操作向量的函数,我就不贴出来了,它们在程序的末尾有定义。最后是绘制的代码
function draw(){
  var a=0;
  setInterval(function(){
    a+=0.02;
    /*绘制到帧缓冲*/
    webgl.bindFramebuffer(webgl.FRAMEBUFFER,fb);
    webgl.uniform1i(tp,true);
    draw_objects(a);
    /*绘制到屏幕*/
    webgl.bindFramebuffer(webgl.FRAMEBUFFER,null);
    webgl.uniform1i(tp,false);
    draw_objects(a);
  },16);
};
  绘制球体什么的由于要绘制两次,我把它们丢到了draw_objects这个函数中了,绘制的代码很无聊我就不贴出来了。这个绘制过程就是先绘制到帧缓冲,然后再绘制到屏幕上。在帧缓冲的文章中也有过类似的代码,没啥特别需要说明的地方。知道这里使用帧缓冲就好了。关键的代码就这些,其他代码都是看一眼就能明白的,这篇文章就不说那些了。

  这个例子还是很粗糙的,只是实现了最基本的阴影效果而已。要作出漂亮的效果,我们还需要用到模糊等一些列方法来修饰。另外,我的例子使用的CANVAS大小刚好是2的n次方,所以帧缓冲可以直接转换成贴图。如果非要用其他大小应该用多个着色器程序来处理不同的工作。不过那样代码量或多出好多,所以我懒的那样做了。最后是这个例子的完整代码。

  WebGL基本阴影处理.html
网名:
34.203.245.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^