【GISER&&Painter】Chapter02:WebGL中的模型视图变换

  上一节咱们提到了如何在一张画布上画一个简单几何图形,经过建立画布,获取WebGLRendering上下文,建立一个简单的着色器,而后将一些顶点数据绑定到gl的Buffer中,最后经过绑定buffer数据,提供buffer中顶点数据的状况,执行渲染绘制方法,将数据结果从buffer中刷新到帧缓存中。整个流程十分清晰明了,但是经过对比原来OpenGL中的整个流程,咱们会发现其中还缺乏了一些很重要的处理步骤,虽然咱们建立了属于本身的着色器,可并无对顶点数据进行相似于顶点处理管线中的模型视图变换透视投影变换等操做,仅仅只是在屏幕上实现了一个静态的几何图形。因此这一篇就让咱们经过从新构造一个可以替代顶点处理管线的着色器,并利用该着色器对咱们的几何图形进行相应的变换。html

一 着色器中的变量

  在上一节中提到了GLSL的着色器中有四种变量:顶点属性attribute/一致变量uniform/易变变量varying/常量constweb

  - Uniform 全局变量,能够出如今顶点着色器、片元着色器中。通常对于全部顶点来讲,能够共用共享的属性就能够用这种类型的变量定义。数组

  - Attribute 顶点着色器专用的属性,从外部【通常从JavaScript中动态地获取】接收输入数据,主要用来表示逐顶点的信息。一般在GLSL中内置了一下关于顶点属性:缓存

attribute vec4 gl_Color  //顶点颜色
attribute vec4 gl_SecondaryColor //辅助顶点颜色
attribute vec3 gl_Normal //顶点法线
attribute vec4 gl_Position //顶点物体空间坐标

  - Varying 如上一篇提到的,varying做为一个信使,将顶点着色器中的参数传递到片元着色器中,值得一提的是,Varying变量的数据类型只能被限制在: float、vec二、vec三、vec四、mat二、mat3以及mat4。其缘由是由于,通常从顶点着色器传递过来的顶点数据每每会通过光栅器后再传递给片元着色器。在光栅化过程当中,各个像素的值每每是根据顶点像素值进行内插的,若是顶点值是一些文本字符型的数据类型,则没法完成内插。less

  - Const常量,必须是常量,设定后不可修改。函数

 

二 仿射变换与矩阵

  众所周知,屏幕是由一个个像素格组成,而咱们在屏幕上画出来的几何图形也是由像素组成的。咱们能够把几何图形变换的问题转换成为对像素图形变换的问题,也就是说,对几何图形中的全部像素进行操做,便可完成几何图形变换。学习

  那么如何对像素进行几何图形变换呢?通常来讲,咱们会把屏幕当作一个坐标系统,屏幕上的几何图形处于这个坐标系统内,每一个像素可视为屏幕坐标系中的一个点。这样一来,咱们处理像素的问题,又转换成了处理坐标系中点位置的问题,从图形à像素à点,咱们不断地分解抽象一个复杂的问题,并最终将其转换成为了方便求解的情景,这是咱们处理问题的一个基本思路webgl

                          

现实世界到数学世界的映射spa

  既然把问题转移到了坐标系里,咱们就能够将之前学到过的数学来求解这个问题。咱们以二维坐标系为例,咱们须要将一个点A(x , y)平移(a , b)到另外一点A’,很简单,A’的坐标为(x+a, y+b),同理,将组成一个几何图形的若干个点所有进行平移操做,则这个几何图形也就完成一次平移。那若是我想对一个图形进行旋转呢?放大缩小呢?这些变换的操做仿佛没有平移那么直观,那么咱们须要借助一些外力来解决这些问题。.net

齐次坐标:此处须要引入在OpenGL基础里提到过的齐次坐标,增长坐标的维度,这样可让咱们更方便的在低维空间中表示高维度的要素。这里有一个关于齐次坐标很好的解释:http://blog.csdn.net/janestar/article/details/44244849,这篇博客用齐次坐标证实了在平面空间里出现灭点的缘由,颇有趣。因此在一个平面直角坐标系中,咱们能够用齐次坐标(x, y, 1)来表示一个点,这样一来,咱们忽然发现好像经过矩阵的点乘就能够到达实现点的绕轴旋转以及缩放图形的想法了!

         咱们但愿可以用齐次坐标(x, y, 1)表明一个点,而后用每个点的齐次坐标乘上一个3*3的变换矩阵,从而获得一个变换后的齐次坐标(x’, y’, 1),此时的x’和y’就是处理后的坐标,这是处理坐标仿射变换的一个基本思路。

           

平移矩阵PY          旋转矩阵XZ            缩放矩阵SF

  经过点(x, y, 1)·变换矩阵PY/XZ/SF可获得:

( x+tx, y+ty, 1)     ( x·cosα + y·sinα,y·cosα - x·sinα )①      ( x·sx, y·sy, 1)

  ① 关于旋转的数学证实,可参照如下证实:(主要经过三角函数的和差公式在单位圆中的关系进行证实)

  假设平面直角坐标系O中,有一个半径为r的单位圆,点A为圆上一点(x, y),OA连线与X轴所成夹角为α,先将OA绕原点O逆时针旋转β°,求A点旋转至B点后的坐标值:

  ∵|OA| = x / cos α = y / sinα; |OB| = x' / cos(α+β) = y' / sin(α+β)

  r = |OA| =|OB|

  ∴ x' = cos(α+β)·r,y' = sin(α+β)·r

  经过三角函数的和差公式可得:

  x' = r·(cosα·cosβ - sinα·sinβ) = x·cosβ - y·sinβ

  y' = r·(sinα·cosβ + sinβ·cosα) = y·cosβ + x·sinβ

  注意此处为逆时针旋转,β值小于0,因此此处获得的结果与上述公式有符号上的差别

三 连续变换

  经过上述简单的介绍,已经大概可以了解到如何在平面直角坐标系中的几何图形进行仿射变换,但在现实操做中,咱们每每会对一个图形进行多种变换操做,那该如何实现呢?

  很简单,咱们能够经过假设存在一个综合变换矩阵Z = F( PY·XZ·SF ),显而易见的是经过( x, y ,1 )·Z能够获得将点:1)先x, y缩放sx, sy倍;2)再旋转α弧度;3)最后x, y分别平移tx, ty个单位。不知大家发现没有,这儿有一个有趣的现象,咱们明明是按照平移PY,旋转XZ以及缩放SF的顺序来做为行矩阵相乘的输入,但是在后面的过程文字说明中,我是按照逆向进行解释的。这是为何呢?由于矩阵采用的是一个二维数组进行存储,而webGL在向着色器传入矩阵的过程当中,是按列传入着色器的,即着色器按每列的数据进行处理

  通常来讲,两个矩阵相乘,能够用以下方法表示:

  multiply: function(a, b) {
    var a00 = a[0 * 3 + 0];
    var a01 = a[0 * 3 + 1];
    var a02 = a[0 * 3 + 2];
    var a10 = a[1 * 3 + 0];
    var a11 = a[1 * 3 + 1];
    var a12 = a[1 * 3 + 2];
    var a20 = a[2 * 3 + 0];
    var a21 = a[2 * 3 + 1];
    var a22 = a[2 * 3 + 2];
    var b00 = b[0 * 3 + 0];
    var b01 = b[0 * 3 + 1];
    var b02 = b[0 * 3 + 2];
    var b10 = b[1 * 3 + 0];
    var b11 = b[1 * 3 + 1];
    var b12 = b[1 * 3 + 2];
    var b20 = b[2 * 3 + 0];
    var b21 = b[2 * 3 + 1];
    var b22 = b[2 * 3 + 2];
    return [
      b00 * a00 + b01 * a10 + b02 * a20,
      b00 * a01 + b01 * a11 + b02 * a21,
      b00 * a02 + b01 * a12 + b02 * a22,
      b10 * a00 + b11 * a10 + b12 * a20,
      b10 * a01 + b11 * a11 + b12 * a21,
      b10 * a02 + b11 * a12 + b12 * a22,
      b20 * a00 + b21 * a10 + b22 * a20,
      b20 * a01 + b21 * a11 + b22 * a21,
      b20 * a02 + b21 * a12 + b22 * a22,
    ];
  }

  经过上述代码能够发现:输入参数为a, b,但最终相乘是以b · a来实现的。在通常常见的WebGL API中,都是如此设计的:虽然从调用API的过程来看,输入顺序为:平移、旋转、缩放,可在API的具体实现中,则是按照缩放、旋转、平移的顺序来进行代码实现的。说实话,其实我也没太弄明白为何要这样作,总感受设计的比较反人类;有的资料建议能够从空间变换的角度来考虑这个问题,大意是:能够把操做对象假想成为空间而非空间中的几何体,随后的平移、旋转、缩放能够视为对空间所进行的操做,空间中的几何体不变的前提下,空间发生变换,几何体的位置坐标也就被动地发生了变化(由于几何体的位置彻底依赖于空间坐标系提供的坐标值表达,失去了坐标系,几何体的位置信息就丢失了)。具体可见:https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-2d-matrices.html;

  
  <!-- 2D vertex shader -->
  <!-- attribute:同理由应用程序提供,一眼用于顶点着色器中 -->
  <!-- uniform:全局变量,由外部传递到着色器,只能读,不能修改 -->
  <!-- varying:易变变量,做为顶点着色器和片元着色器之间的通讯 -->
  <script id="2d-vertex-shader" type="x-shader/x-vertex">
  attribute vec2 a_position;

  uniform vec2 u_resolution;
  //矩阵
  uniform mat3 u_matrix;

  void main() {
    // 2D-Vertex-shader:
    gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);

  }

  function draw(){
    .....
    //操做顺序:缩放、旋转、平移:     
//translationMatrix:平移矩阵;     //rotationMatrix:旋转矩阵;     //scaleMatrix:缩放矩阵     var matrix = m3.multiply(projectionMatrix, translationMatrix);   matrix = m3.multiply(matrix, rotationMatrix);   matrix = m3.multiply(matrix, scaleMatrix);

     gl.uniformMatrix4fv(matrixLocation, false, matrix);
     //Draw
     gl.enable(gl.DEPTH_TEST);
     gl.enable(gl.CULL_FACE);
     //发出绘制命令
     var primitiveType = gl.TRIANGLES;
     var offset = 0;
     var count = 16*6;
     gl.drawArrays(primitiveType, offset, count);

  }

  PS:不一样的变换顺序,如:平移--旋转--缩放和缩放--旋转--平移,这两个操做过程会获得彻底不同的两种效果,若是先平移再缩放的话,平移的距离会由于缩放的关系而发生伸长或缩短,彻底背离了操做者的本来意图。在通常变换过程当中,每每建议先缩放再平移。

  本篇主要简述了再WebGL中,平面直角坐标系的几何图形仿射变换与矩形计算的关联实现,下一步会继续拓展到三维坐标系中,并增长相机、投影以及纹理等内容。

  推荐一个很完整的入门教程:https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-fundamentals.html,这是到目前为止,我参照最多的一个资料,特别是学习思路上。

相关文章
相关标签/搜索