在以前的文章里,不管是绘制图形,绘制点亦或者是改变色值,所有的内容都是静态的。

webgl 里,图形的运动分为 平移、旋转、缩放 三种类型。

接下来,我们会从零基础开始,一点一点来深入了解图形如何进行运动。

首先来从零开始了解下图形的平移

1. 图形平移

首先我们来看如何实现图形的平移操作。

平移的操作就是将图形的原始坐标加上对应的移动距离。首先来看下平移的实现

  1. const vertexShaderSource = "" +
  2. "attribute vec4 apos;" + // 定义一个坐标
  3. "uniform float x;" + // 处理 x 轴移动
  4. "uniform float y;" + // 处理 y 轴移动
  5. "void main(){" +
  6. " gl_Position.x = apos.x + x;" +
  7. " gl_Position.y = apos.y + y;" +
  8. " gl_Position.z = 0.0;" + // z轴固定
  9. " gl_Position.w = 1.0;" +
  10. "}";
  11. const fragmentShaderSource = "" +
  12. "void main(){" +
  13. " gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
  14. "}";
  15. // initShader已经实现了很多次,本次就不再赘述了
  16. const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
  17. const buffer = gl.createBuffer();
  18. const data = new Float32Array([
  19. 0.0,0.0,
  20. -0.5,-0.5,
  21. 0.5,-0.5,
  22. ]);
  23. gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
  24. gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
  25. const aposlocation = gl.getAttribLocation(program,'apos');
  26. const xlocation = gl.getUniformLocation(program,'x');
  27. const ylocation = gl.getUniformLocation(program,'y');
  28. gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
  29. gl.enableVertexAttribArray(aposlocation);
  30. let x = 0.0;
  31. let y = 0.0;
  32. function run () {
  33. gl.uniform1f(xlocation,x += 0.01);
  34. gl.uniform1f(ylocation,y += 0.01);
  35. gl.drawArrays(gl.TRIANGLES,0,3);
  36. // 使用此方法实现一个动画
  37. requestAnimationFrame(run)
  38. }
  39. run()

解释:

  • 首先声明一个变量 x 和变量 y ,用来处理 x轴 和 y轴 的坐标。这里使用的是 uniform 变量,因为平移的操作对于图形上的所有顶点都有影响。
  • 通过 gl_Position.[xyzw] 来分别设置 x、y、z、w 的值。用于改变图形位置。
  • 使用 gl.uniform1f 来为 x 和 y 赋值
  • 使用 requestAnimationFrame 实现一个缓动动画。方便观察效果。
  • 其他的操作,缓冲区,绘制,赋值,激活,

可以看到,这样处理图形移动的话很好理解,但是因为一个移动,我们声明了两个 uniform 变量来实现。并且分开设置的 xyz 坐标,非常的不方便。

所以,在处理webgl变换(平移、缩放、旋转)的时候,通常使用矩阵来实现。接下来就来看看,如何使用矩阵实现图形的平移。

2. 平移矩阵

推导平移矩阵的步骤:

  • 获取平移前后的图形坐标(三维)
  • 计算平移前后的差值
  • 带入到平移矩阵
  • 处理图形顶点
  • 获得平移后的图形

2.1 平移矩阵的推导

首先让我们来看一幅图片。

这幅图片的意义就是我们将橙色的三角形移动到蓝色虚线三角形处。

移动之后的蓝色虚线三角形的三个坐标分别为

  • x’ = x + x1
  • y' = y + y1
  • z' = z + z1
  • w=1 齐次坐标为1

2.2 获得平移矩阵

webgl 中,通常使用矩阵来实现图形变换。下面我们来看看矩阵如何表示。

左侧是平移之前的原始坐标,中间的是一个平移矩阵,经过两者相乘,可以得到一个平移之后的坐标。

现在我们来看下平移矩阵如何计算得出

首先通过上述图片中的矩阵我们来得到几个方程式。用左侧的列分别乘矩阵的行,可以得到一下公式

  • ax + by + cz + w = x'
  • ex + fy + gz + h = y'
  • ix + jy + kz + l = z'
  • mx + ny + oz + p = w'

公式合并:

第一节 里的四个方程式和第二节里的四个方程式合并,可以得到如下结果:

  • ax + by + cz + w = x + x1':只有当 a = 1,b = c = 0, w = x1 的时候,等式左右两边成立
  • ex + fy + gz + h = y + y1':只有当 f = 1, e = g = 0, h = y1 的时候,等式左右两边成立
  • ix + jy + kz + l = z + z1':只有当 k = 1,i = j = 0, l = z1 的时候,等式左右两边成立
  • mx + ny + oz + p = 1':只有当 m = n = o = 0, p = 1 的时候,等式左右两边成立

经过上述方程式,可以得到一个平移的矩阵:

| 1 0 0 x |

| 0 1 0 y |

| 0 0 1 z |

| 0 0 0 1 |

之后将平移矩阵和原始坐标相乘,就可以得到平移之后的坐标。

3. 矩阵实战

来看看使用矩阵如何处理图形的平移。

第一步,创建着色器源代码
  1. const vertexShaderSource = "" +
  2. "attribute vec4 apos;" +
  3. "uniform mat4 mat;" + // 创建一个 uniform 变量,代表平移矩阵
  4. "void main(){" +
  5. " gl_Position = mat * apos;" + // 矩阵与原始坐标相乘
  6. "}";
  7. const fragmentShaderSource = "" +
  8. "void main(){" +
  9. " gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
  10. "}";
第二步,创建平移矩阵
  1. let Tx = 0.1; //x坐标的位置
  2. let Ty = 0.1; //y坐标的位置
  3. let Tz = 0.0; //z坐标的位置
  4. let Tw = 1.0; //差值
  5. const mat = new Float32Array([
  6. 1.0,0.0,0.0,0.0,
  7. 0.0,1.0,0.0,0.0,
  8. 0.0,0.0,1.0,0.0,
  9. Tx,Ty,Tz,Tw,
  10. ]);

这里可以看到,使用的矩阵和我们推导出来的矩阵不太一样,推导的平移矩阵里 xyzw 位于矩阵的右侧,现在是位于矩阵的底部,这是为什么呢?

这是因为在 webgl 中,矩阵的使用需要按照 左上右下 的对角线做一次翻转。所以使用的矩阵,xyzw 位于底部

第三步,绘制一个三角形
  1. const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
  2. const aposlocation = gl.getAttribLocation(program,'apos');
  3. const data = new Float32Array([
  4. 0.0,0.0,
  5. -.3,-.3,
  6. .3,-.3
  7. ]);
  8. const buffer = gl.createBuffer();
  9. gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
  10. gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
  11. gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
  12. gl.enableVertexAttribArray(aposlocation);
  13. gl.drawArrays(gl.TRIANGLES,0,3); // 第五步的时候会重写
第四步,获取矩阵变量,给矩阵赋值
  1. const matlocation = gl.getUniformLocation(program,'mat');
  2. gl.uniformMatrix4fv(matlocation,false,mat);

这里使用 gl.uniformMatrix4fv 来给矩阵赋值。

第五步,添加缓动动画
  1. function run () {
  2. Tx += 0.01
  3. Ty += 0.01
  4. const mat = new Float32Array([
  5. 1.0,0.0,0.0,0.0,
  6. 0.0,1.0,0.0,0.0,
  7. 0.0,0.0,1.0,0.0,
  8. Tx,Ty,Tz,Tw,
  9. ]);
  10. gl.uniformMatrix4fv(matlocation,false,mat);
  11. gl.drawArrays(gl.TRIANGLES,0,3);
  12. // 使用此方法实现一个动画
  13. requestAnimationFrame(run)
  14. }
  15. run()

4. 完整代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <canvas id="webgl" width="500" height="500"></canvas>
  9. <script>
  10. const gl = document.getElementById('webgl').getContext('webgl');
  11. const vertexShaderSource = "" +
  12. "attribute vec4 apos;" +
  13. "uniform mat4 mat;" +
  14. "void main(){" +
  15. " gl_Position = mat * apos;" +
  16. "}";
  17. const fragmentShaderSource = "" +
  18. "void main(){" +
  19. " gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
  20. "}";
  21. const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
  22. const aposlocation = gl.getAttribLocation(program,'apos');
  23. const matlocation = gl.getUniformLocation(program,'mat');
  24. const data = new Float32Array([
  25. 0.0,0.0,
  26. -.3,-.3,
  27. .3,-.3
  28. ]);
  29. const buffer = gl.createBuffer();
  30. gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
  31. gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
  32. gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
  33. gl.enableVertexAttribArray(aposlocation);
  34. let Tx = 0.1; //x坐标的位置
  35. let Ty = 0.1; //y坐标的位置
  36. let Tz = 0.0; //z坐标的位置
  37. let Tw = 1.0; //差值
  38. function run () {
  39. Tx += 0.01
  40. Ty += 0.01
  41. const mat = new Float32Array([
  42. 1.0,0.0,0.0,0.0,
  43. 0.0,1.0,0.0,0.0,
  44. 0.0,0.0,1.0,0.0,
  45. Tx,Ty,Tz,Tw,
  46. ]);
  47. gl.uniformMatrix4fv(matlocation,false,mat);
  48. gl.drawArrays(gl.TRIANGLES,0,3);
  49. // 使用此方法实现一个动画
  50. requestAnimationFrame(run)
  51. }
  52. run()
  53. function initShader(gl,vertexShaderSource,fragmentShaderSource){
  54. const vertexShader = gl.createShader(gl.VERTEX_SHADER);
  55. const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  56. gl.shaderSource(vertexShader,vertexShaderSource);
  57. gl.shaderSource(fragmentShader,fragmentShaderSource);
  58. gl.compileShader(vertexShader);
  59. gl.compileShader(fragmentShader);
  60. const program = gl.createProgram();
  61. gl.attachShader(program,vertexShader);
  62. gl.attachShader(program,fragmentShader)
  63. gl.linkProgram(program);
  64. gl.useProgram(program);
  65. return program;
  66. }
  67. </script>
  68. </body>
  69. </html>

至此,通过矩阵控制图形移动就全部实现完成了。

今天的分享就到这儿了,

Bye~


更多精彩内容,定制礼品图书赠送,高薪职位内推,微信搜索关注“豆皮范儿”

webgl变换:深入图形平移的更多相关文章

  1. webgl学习笔记三-平移旋转缩放

    写在前面 建议先阅读下前面我的两篇文章. webgl学习笔记一-绘图单点 webgl学习笔记二-绘图多点 平移 1.关键点说明 顶点着色器需要加上 uniform vec4 u_Translation ...

  2. WebGL简易教程(五):图形变换(模型、视图、投影变换)

    [toc] 1. 概述 通过之前的教程,对WebGL中可编程渲染管线的流程有了一定的认识.但是只有前面的知识还不足以绘制真正的三维场景,可以发现之前我们绘制的点.三角形的坐标都是[-1,1]之间,Z值 ...

  3. 《Real Time Rendering》第四章 图形变换

    图形变换是一个将例如点.向量或者颜色等实体进行某种转换的操作.对于计算机图形学的先驱者,掌握图形变换是极为重要的.有了他们,你就可以对象.光源以及摄像机进行定位,变形以及动画添加.你也可以确认所有的计 ...

  4. WebGL高级编程:开发Web3D图形 PDF(中文版带书签)

    WebGL高级编程:开发Web3D图形 目录 WebGL简介11.1 WebGL基础11.2 浏览器3D图形吸引人的原因21.3 设计一个图形API31.3.1 即时模式API31.3.2 保留模式A ...

  5. WebGL学习笔记(1)

    基本的WebGL图形操作(详细参考教程:https://www.yiibai.com/webgl,需要1周左右熟悉webgl的对象方法以及着色器代码):绘制三角形 drawElements gl.TR ...

  6. WEBGL学习【八】模型视图投影矩阵

    <!--探讨WEBGL中不同图形的绘制方法:[待测试2017.11.6]--> <!DOCTYPE HTML> <html lang="en"> ...

  7. WebGL简易教程(六):第一个三维示例(使用模型视图投影变换)

    目录 1. 概述 2. 示例:绘制多个三角形 2.1. Triangle_MVPMatrix.html 2.2. Triangle_MVPMatrix.js 2.2.1. 数据加入Z值 2.2.2. ...

  8. 浅谈 GPU图形固定渲染管线

    图形渲染管道被认为是实时图形渲染的核心,简称为管道.管道的主要功能是由给定的虚拟摄像机.三维物体.灯源.光照模型.纹理贴图或其他来产生或渲染一个二维图像.由此可见,渲染管线是实时渲染技术的底层工具.图 ...

  9. OpenGL基础图形编程

    一.OpenGL与3D图形世界1.1.OpenGL使人们进入三维图形世界 我们生活在一个充满三维物体的三维世界中,为了使计算机能精确地再现这些物体,我们必须能在三维空间描绘这些物体.我们又生活在一个充 ...

随机推荐

  1. PAT 乙级 -- 1001 -- 害死人不偿命的(3n+1)猜想

    题目: 卡拉兹(Callatz)猜想: 对任何一个自然数n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把(3n+1)砍掉一半.这样一直反复砍下去,最后一定在某一步得到n=1.卡拉兹在1950年 ...

  2. hdu2276 矩阵构造

    题意:      给了n个灯泡的状态,他们绕成一个环,0是灭,1是亮,每一秒灯泡的状态都会改变,规则是如果当前这个灯泡的左边的灯泡当前是状态1,那么下一秒当前的这个灯泡状态就改变0变1,1变0,最后问 ...

  3. Python脚本写端口扫描器(socket,python-nmap)

    目录 Socket模块编写 扫描给定主机是否开放了指定的端口 python-nmap模块编写 扫描给定ip或给定网段内指定端口是否开放 一个用python写的简单的端口扫描器,python环境为 3. ...

  4. NetBIOS名称欺骗和LLMNR欺骗

    目录 LLMNR和NetBios 攻击原理 Responder 攻击过程 LLMNR和NetBios 什么是LLMNR和NetBIOS名称服务器广播? 当DNS名称服务器请求失败时,Microsoft ...

  5. Windows核心编程 第四章 进程(中)

    4.2 CreateProcess函数 可以用C r e a t e P r o c e s s函数创建一个进程: BOOL CreateProcessW( _In_opt_ LPCWSTR lpAp ...

  6. .NET Core with 微服务 - 什么是微服务

    微服务是这几年最流行的架构,说起架构不提微服务都不好意思跟人家打招呼.最近想要再梳理一下关于微服务的知识,并且结合本人的一些实践经验来做一些总结与分享.前面会分享一些概念性的东西,后面也会使用.net ...

  7. Windows进程间通讯(IPC)----管道

    管道的分类 管道其实际就是一段共享内存,只不过Windows规定需要使用I/O的形式类访问这块共享内存,管道可以分为匿名管道和命名管道. 匿名管道就是没有名字的管道,其支持单向传输数据,如果需要双向传 ...

  8. 自定义元类 __call__,__init__,__new__总结

    只要对象能被调用 产生对象的类里必然有__call__方法 在调用类时,必定先触发type里的__call__ __call__下有: 1.产生对象的object.__new__ 2..被调用的类自己 ...

  9. 7.CentOS文件和目录 以及系统与设置命令

    CentOS文件和目录 etc------系统中的配置文件 bin------系统预设执行文件的放置目录 sbin------系统预设执行文件的放置目录 usr------系统预设执行文件的放置目录 ...

  10. 技术干货 | 基于MindSpore更好的理解Focal Loss

    [本期推荐专题]物联网从业人员必读:华为云专家为你详细解读LiteOS各模块开发及其实现原理. 摘要:Focal Loss的两个性质算是核心,其实就是用一个合适的函数去度量难分类和易分类样本对总的损失 ...