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

一 着色器中的变量

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

  - Uniform 全局变量,可以出现在顶点着色器、片元着色器中。一般对于所有顶点来说,可以共用共享的属性就可以用这种类型的变量定义。

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

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

  - Varying 如上一篇提到的,varying作为一个信使,将顶点着色器中的参数传递到片元着色器中,值得一提的是,Varying变量的数据类型只能被限制在: float、vec2、vec3、vec4、mat2、mat3以及mat4。其原因是因为,一般从顶点着色器传递过来的顶点数据往往会经过光栅器后再传递给片元着色器。在光栅化过程中,各个像素的值往往是根据顶点像素值进行内插的,如果顶点值是一些文本字符型的数据类型,则无法完成内插。

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

二 仿射变换与矩阵

  众所周知,屏幕是由一个个像素格组成,而我们在屏幕上画出来的几何图形也是由像素组成的。我们可以把几何图形变换的问题转换成为对像素图形变换的问题,也就是说,对几何图形中的所有像素进行操作,即可完成几何图形变换。

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

                          

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

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

齐次坐标:此处需要引入在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,这是到目前为止,我参照最多的一个资料,特别是学习思路上。

【GISER&&Painter】Chapter02:WebGL中的模型视图变换的更多相关文章

  1. OpenGL学习笔记4——模型视图变换

    以日月地为例的一个模型视图变换.绕了比较多的弯路,下面是几个注意点总结. 注意点: 1.GL函数对模型的操作是基于当前局部坐标系,即模型坐标系而非世界坐标系,二者只在第一次初始化完毕之后才重合: 2. ...

  2. OpenGL模型视图变换、投影变换、视口变换的理解

    OpenGL中不设置模型,投影,视口,所绘制的几何图形的坐标只能是-1到1(X轴向右,Y轴向上,Z轴垂直屏幕向外). 产生目标场景的过程类似于用照相机进行拍照: (1)把照相机固定在三角架上,并让他对 ...

  3. 简单理解OpenGL模型视图变换

    前几天学习了OpenGL的绘图原理(其实就是坐标的不停变换变换),看到网上有个比较好的例程,于是学习了下,并在自己感兴趣的部分做了注释. 首先通过glMatrixMode(GL_MODELVIEW)设 ...

  4. Opengl使用模型视图变换移动光源

    光源绕一个物体旋转,按下鼠标左键时,光源位置旋转. #include <GL/glut.h> static int spin = 0;static GLdouble x_1 = 0.0;s ...

  5. 【GISER&&Painter】Chapter01:WebGL渲染初体验

    基于上一篇OpenGL的渲染原理,这两周又陆续接触了一些关于WebGL绘图的一些内容,因为刚入门,很多东西又很晦涩,所以特意花了小半天的时间整理了一下,特此记录. 零  画一个多边形吧! 把一个多边形 ...

  6. 【GISer&&Painter】GISer

    基于上一篇OpenGL的渲染原理,这两周又陆续接触了一些关于WebGL绘图的一些内容,因为刚入门,很多东西又很晦涩,所以特意花了小半天的时间整理了一下,特此记录. 一   画布和画笔:创建Canvas ...

  7. Qt之模型/视图(实时更新数据)

    上两节简单介绍了Qt中对于模型/视图的编程,大部分助手里说的很清楚了,现在就开始实战部分吧! 在实际应用中,视图展示的数据往往并非一成不变的,那么如何实时更新成了一个很重要的问题!功能:(1)添加委托 ...

  8. 【转】Qt之模型/视图

    [本文转自]http://blog.sina.com.cn/s/blog_a6fb6cc90101hh20.html   作者: 一去丶二三里 关于Qt中MVC的介绍与使用,助手中有一节模型/视图编程 ...

  9. 【GISER && Painter】Chapter00:OpenGL原理学习笔记

    说明:简单了解一下OpenGL的工作原理,初步认识计算机对于图形渲染的底层设计与实现,第一次接触,也没学过C艹,欢迎各位批评指正. 一  什么是OpenGL? OpenGL是一个开放标准(specif ...

随机推荐

  1. PHP–图像XX因其本身有错无法显示

    1.你输出的图像格式 PHP不支持,检查GD库有没有开启 对应的图像格式是不是支持2.文件里面的格式是否一致 如果是UTF-8的检查文件格式是不是UTF-8 [UTF-8+BOM很多IDE默认是这个格 ...

  2. HEVC代码记录(删除)

    得到编码残差  TEncSearch.cpp 4543:rpcYuvResi->subtract( pcYuvOrg, pcYuvPred, 0, uiWidth );

  3. Android学习笔记(四) 定时器Timer

    Android考虑到线程安全问题,不允许在线程中执行UI线程. 所以在线程中不允许有UI操作 可以利用Handler机制来接收Timer每隔一秒发出的信息,也可以直接利用handler机制的 1.方法 ...

  4. day1 python简介和入门

    Linux的yum依赖自带Python,为防止错误,此处更新其实就是再安装一个Python: 安装Python 1.下载安装包     https://www.python.org/downloads ...

  5. 【LOJ】 #2130. 「NOI2015」软件包管理器

    题解 连树剖我都写跪一次,我现在怎么那么老年啊= = 简直滚粗预定了啊.. 我们线段树维护树剖只需要资瓷区间覆盖和区间求和就好了 安装的时候看看自己到根有多少包装了,dep减去这个数量就好 卸载的时候 ...

  6. 【51nod】1594 Gcd and Phi

    题解 跟随小迪学姐的步伐,学习一下数论 小迪学姐太巨了! 这道题的式子很好推嘛 \(\sum_{i = 1}^{n} \sum_{j = 1}^{n} \sum_{d|\phi(i),\phi(j)} ...

  7. CountDownLatch 使用方法

    CountDownLatch 使用方法 import java.util.concurrent.CountDownLatch; public class TestCountDownLatch { pu ...

  8. 深入理解正则表达式-----应用于检测csrf的正则表达式

    如何写检测和防御csrf的规则?我们可以利用正则表达式进行匹配.对POST包进行正则匹配,这里只是提供了一个思路. pcre:"/POST \/(?P<uri>.*?) HTTP ...

  9. 虚拟环境中pip install requirments.txt: Cannot fetch index base URL https://pypi.python.org/simple/

    Stackoverflow  : http://stackoverflow.com/questions/15501133/python-pip-error-cannot-fetch-index-bas ...

  10. 【费用流】BZOJ1927-[Sdoi2010]星际竞速

    [题目大意] 有一些点,它们之间存在一些有向边(由编号小的到编号大的),从一点到另一点消耗时间为边长.也可以消耗Ti时间直接抵达任意一个点.问所有点都走一遍最少需要多少时间? [思路] ①将每个点i拆 ...