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

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

一 着色器中的变量

  在上一节中提到了着色器中有四种变量:attribute/uniform/varying/const

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

  - Attribute 顶点着色器专用的属性,从外部接收输入数据,主要用来表示逐顶点的信息。

  - 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,这是到目前为止,我参照最多的一个资料,特别是学习思路上。

原文地址:https://www.cnblogs.com/escage/p/8145794.html

时间: 2024-10-07 23:21:53

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

【GISER&amp;&amp;Painter】WebGL渲染初体验(一)

基于上一篇OpenGL的渲染原理,这两周又陆续接触了一些关于WebGL绘图的一些内容,因为刚入门,很多东西又很晦涩,所以特意花了小半天的时间整理了一下,特此记录. 一   画布和画笔:创建Canvas && 获取WebGL的Context 在开始WebGL的绘制故事之前,我们得先来认识一下Canvas,因为这玩意是我们之后绘图的基础底板:"Canvas元素创造了一个固定大小画布,并提供了一个或多个渲染上下文,用来绘制和处理要展示的内容"(摘自MDN).按照定义,我们可以

清华版CG 实验5 OpenGL模型视图变换

1.实验目的: 理解掌握OpenGL程序的模型视图变换. 2.实验内容: (1)阅读实验原理,运行示范实验代码,理解掌握OpenGL程序的模型视图变换: (2)根据示范代码,尝试完成实验作业: 3.实验原理: 在OpenGL程序中,视图变换必须出现在模型变换之前,但可以在绘图之前的任何时候执行投影变换和视口变换. 1.在指定的视图变换之前,应该使用glLoadIdentity()函数把当前矩阵设置为单位矩阵. 2.在载入单位矩阵之后,使用gluLookAt()函数指定视图变换.如果程序没有调用g

QML中的模型/视图 十二

QML中对于数据的存储和显示使用模型/视图框架. 1. QML数据模型 视图项目(如ListView.GridView和Repeater等)需要使用数据模型来为其提供数据进行显示.这些项目通常也需要一个委托(delegate)组件来为模型中的每一个条目创建一个实例.模型可以是静态的,也可以进行动态的修改.插入.移除或者移动项目.Qt帮助参考QML Data Models关键字. Item{ width:200; height:250 ListModel{ id:myModel ListEleme

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

光源绕一个物体旋转,按下鼠标左键时,光源位置旋转. #include <GL/glut.h> static int spin = 0;static GLdouble x_1 = 0.0;static GLdouble y_1 = 0.0;static GLdouble z_1 = 0.0;void init(void){ glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_SMOOTH); glEnable(GL_LIGHTING); glEnable

第61课 模型视图中的委托(上)

1. Qt模型视图对用户输入的处理 (1)传统的MVC设计模式 (2)Qt中的模型视图设计模式如何处理用户输入? ①视图中集成了处理用户输入的功能(即委托) ②视图将用户输入作为内部独立的子功能来实现 ③模型负责组织数据,视图负责显示数据,委托则用于编辑修改数据. 2. 模型视图中的委托 (1)委托(Delegate)是视图中处理用户输入的部件(如编辑框.单选按钮等) (2)视图可以设置委托对象用于处理用户输入 (3)委托对象负责创建和显示用户输入的上下文(内容),如编辑框的创建和显示. (4)

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

上两节简单介绍了Qt中对于模型/视图的编程,大部分助手里说的很清楚了,现在就开始实战部分吧! 在实际应用中,视图展示的数据往往并非一成不变的,那么如何实时更新成了一个很重要的问题!功能:(1)添加委托(进度条)(2)显示文件名称.大小.进度.速度.剩余时间.状态等.(3)可进行添加.更新.删除.清空等操作.(4)实时更新数据先看一个效果图:委托(进度条):ProgressBarDelegate::ProgressBarDelegate(QObject *parent): QItemDelegat

QT——模型/视图(model/view)

数据项中引入模型/视图架构,可以方便的将数据与表现层分开. ------------------------------------- 模型Model:一般来说,Model里面并不真正存储数据(数据少的话也可以直接存储在Model里),只是负责从诸如磁盘文件,数据库,网络通讯等获得源数据,并提供给View,View对数据进行修改,然后再通过Model更新源数据. Model 另一个重要工作时为源数据添加索引(ModelIndex).列表形式采用row/colum编号,树形式为建立父子间的层次关系

第55课 模型视图设计模式

1. 模型视图模式 (1)模型视图设计模式的核心思想 ①模型(数据)与视图(显示)相分离 ②模型对外提供标准接口存取数据(不关心数据如何显示) ③视图自定义数据的显示方式(不关心数据如何组织存储) (2)模型视图模式的直观理解 (3)模型视图模式的工作机制 ①当数据发生改变时,模型发出信号通知视图 ②当用户与视图进行交互时,视图发出信号提供交互信息 2. Qt中的模型-视图类层次结构 (1)Qt中的模型类的层次结构 (2)Qt中的视图类的层次结构 3. 关键技术问题 (1)模型如何为数据提供统一

第62课 模型视图中的委托(下)

1. 委托的本质 (1)为视图提供数据编辑的上下文环境 (2)产生界面元素的工厂类 (3)能够使用和设置模型中的数据 2. 自定义委托类 (1)自定义委托类的继承关系 (2)自定义委托时需要重写的函数 ①createEditor ②updateEditorGeometry ③setEditorData ④setModelData ⑤paint(可选) (3)自定义委托时重写的函数由谁调用? 由于模型视图设计模式,视图中组合了委托对象,既然委托存在于视图内部,就应该由视图来调用(可从上图的函数调用