前端对div连线_《前端图形学从入门到放弃》003 三维世界

04153b5159cea86e55323691fa580b6d.png

从本篇起,我们将正式进入webgl的3D世界

本篇涵盖的内容包括:

  1. webgl它在干啥?
  2. 如何画一个正方体?
  3. 如何成为一个“有深度”的正方体?
  4. 正方体要离家出走了!

webgl它在干啥?

首先我们需要知道webgl的世界其实是一个x[-1,1],y[-1,1],z[-1,1]的小世界,所有在这个长度2的世界中的物体,才能被显示。如图所示:

56487597ded5f65a45ea3882ee6c6ada.png
黄色区域是webgl的[-1,1]区域,上方的球和下方的长方体都完全在这个区域内可以完全被显示,而中间的立方体这是部分显示

从正面看就会是

a463d145f92247be6a44eb0cbc36b838.png
段棒子和球完全显示,常棒子超出区域被截掉

简单说来,这个过程中webgl把三纬空间xyz[-1,1]压扁成xy[1,-1]再根据实际画布的尺寸,绘制到canvas上,这个过程就是光栅化。不过如果三纬空间xyz[-1,1]中的物体如图所示,而canvas是2:1的尺寸的话实际得到的图片会是这样的:

19d2767d34d7f89d7b461d9efd53e2f8.png

那么为啥我平常用的canvas不会变形?请耐心看到文末你会有答案的!

如何画一个正方体?

本篇我们并不会在作色器上大动手脚,所以按照老方法,做一遍就可以了,不懂得可以看看《前端图形学从入门到放弃》第一篇。我们对作色器的唯一改造就是,开放了标示深度的z值:

<!-- 一个顶点着色器提供裁剪空间坐标值 -->
<script id="vertex-shader-2d" type="notjs">attribute vec3 a_position;uniform mat4 u_matrix;// 这个是后续空间变化需要的,这一步void main() {vec4 position = u_matrix * vec4(a_position,1.0);gl_Position = vec4(position.xyz,1.0);}
</script>
<!-- 一个片断着色器提供颜色值 -->
<script id="fragment-shader-2d" type="notjs">precision mediump float;void main() {// 输出颜色固定即可,我选的是(1.0,0.1,.45),rgb都除以255gl_FragColor = vec4(vec3(1.0,0.1,.45), 1.0); // }</script>

我们当然可以使用gl.drawArrays方法继续绘制,但对于数量更多的图形,这个方法其实不够高效,想象一下有如图ABCD四个点组成了两个三角形:

2750375b32ec52a7a39aae5495496a6e.png

对于gl.drawArrays我们需要向buffer中传入ABC后再传入BCD绘制两个三角形,但buffer就会重复存储点BC,目前还只有位置坐标,如果是复杂的场景一个点上除了位置信息外,还会包含颜色,法线等一些列数据,这就相当浪费存储空间了。所以我们采用gl.drawElements方法。

顶点数据还是如前,往buffer中丢,只不过每个点只传入一次,例如我们需要绘制一个以[0,0,0]为中心,边长是0.4的正方形,他的的八个顶点组成的数据是:

var data = new Float32Array([0.2, 0.2, 0.2,-0.2, 0.2, 0.2,-0.2, -0.2, 0.2,0.2, -0.2, 0.2,0.2, 0.2, -0.2,-0.2, 0.2, -0.2,-0.2, -0.2, -0.2,0.2, -0.2, -0.2,
]);

这时我们还需要一个索引数组来标识,用哪些点围城图形。

//顶点索引数组
var indexes = new Uint8Array([//右侧四个点0, 1, 2, 3,//左侧四个顶点4, 5, 6, 7,//左右对应关系0, 4,1, 5,2, 6,3, 7
]);

然后把这两个数据丢给buffer:

 var indexesBuffer = gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW);var vBuffer = gl.createBuffer();// 顶点需要是 ARRAY_BUFFERgl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

最后就是绘制:

//LINE_LOOP模式绘制右侧四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0);
//LINE_LOOP模式从第五个点开始绘制左侧四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4);
//LINES模式绘制连线左右点连线
gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8);

这样运行一遍会得到如下结果:

06881566c2890514da65e6c51a57ce03.png

奇怪?为什么只有一个正方形?说好的3D?

这是因为我们现在还是正交视图,后面的点完全被前面的点挡住了,所以我们需要移动这个立方体。让后面的点被看到

所以,接下来我们创建6个滑块分别控制正方形向x,y,z移动和绕x,y,z旋转。你怎么实现都可以:

<div id="control"><div class="item item-x"><span>x:</span><input type="range" id="item-x" value="0" min="-1" max="1" step="0.01" ></div><div class="item item-y"><span>y:</span><input type="range" id="item-y" value="0" min="-1" max="1" step="0.01" ></div><div class="item"><span>z:</span><input type="range" id="item-z" value="0" min="-1" max="1" step="0.01" ></div><div class="item item-x"><span>x轴角度:</span><input type="range" id="item-r-x" value="0" min="-3.14" max="3.14" step="0.001" ></div><div class="item item-y"><span>y轴角度:</span><input type="range" id="item-r-y" value="0" min="-3.14" max="3.14" step="0.001" ></div><div class="item"><span>z轴角度:</span><input type="range" id="item-r-z" value="0" min="-3.14" max="3.14" step="0.001" ></div></div>

而通过这些数据我们可以组成一个矩阵对立方体的点进行变换。只是从《前端图形学从入门到放弃》002第二篇的2D(支持齐次变换的3维矩阵)变成了3D(支持齐次变换的4维矩阵)。

他们分别是一个平移矩阵和3个旋转矩阵:

var m4 = {  // 用来存放三位变换方法的对象transform: function (x, y, z) {return [1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, 0,x, y, z, 1,];},rotateX: function (deg) {var c = Math.cos(deg);var s = Math.sin(deg);return [1, 0, 0, 0,0, c, s, 0,0, -s, c, 0,0, 0, 0, 1,]},rotateY: function (deg) {var c = Math.cos(deg);var s = Math.sin(deg);return [c, 0, -s, 0,0, 1, 0, 0,s, 0, c, 0,0, 0, 0, 1,]},rotateZ: function (deg) {var c = Math.cos(deg);var s = Math.sin(deg);return [c, s, 0, 0,-s, c, 0, 0,0, 0, 1, 0,0, 0, 0, 1,]},
}

当然我们还需要一个矩阵的乘法方法multiply,以及可以支持多个矩阵连乘的multiplys方法:

var m4 = {
// .......multiply: function (a, b) {var a00 = a[0 * 4 + 0];var a01 = a[0 * 4 + 1];var a02 = a[0 * 4 + 2];var a03 = a[0 * 4 + 3];var a10 = a[1 * 4 + 0];var a11 = a[1 * 4 + 1];var a12 = a[1 * 4 + 2];var a13 = a[1 * 4 + 3];var a20 = a[2 * 4 + 0];var a21 = a[2 * 4 + 1];var a22 = a[2 * 4 + 2];var a23 = a[2 * 4 + 3];var a30 = a[3 * 4 + 0];var a31 = a[3 * 4 + 1];var a32 = a[3 * 4 + 2];var a33 = a[3 * 4 + 3];var b00 = b[0 * 4 + 0];var b01 = b[0 * 4 + 1];var b02 = b[0 * 4 + 2];var b03 = b[0 * 4 + 3];var b10 = b[1 * 4 + 0];var b11 = b[1 * 4 + 1];var b12 = b[1 * 4 + 2];var b13 = b[1 * 4 + 3];var b20 = b[2 * 4 + 0];var b21 = b[2 * 4 + 1];var b22 = b[2 * 4 + 2];var b23 = b[2 * 4 + 3];var b30 = b[3 * 4 + 0];var b31 = b[3 * 4 + 1];var b32 = b[3 * 4 + 2];var b33 = b[3 * 4 + 3];return [b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33,];},multiplys: function (list) {var that = this;return list.reduce(function (a, b) {return that.multiply(a, b);})},
// .......
}

要完成对立方体的控制,我们需要把渲染写入一个循环中:

      var xNode = document.querySelector('#item-x');var yNode = document.querySelector('#item-y');var zNode = document.querySelector('#item-z');var rXNode = document.querySelector('#item-r-x');var rYNode = document.querySelector('#item-r-y');var rZNode = document.querySelector('#item-r-z');// 。。。。。loop();function loop() {var mx = m4.rotateX(rXNode.value); var my = m4.rotateY(rYNode.value);var mz = m4.rotateZ(rZNode.value);var mTransform = m4.transform(xNode.value,yNode.value,zNode.value);var matrix = m4.multiplys([mTransform,mz,my,mx]);gl.uniformMatrix4fv(u_matrix, false, matrix);// 把矩阵传入顶点作色器gl.clearColor(0.75, 0.85, 0.8, 1.0);gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);//LINE_LOOP模式绘制右侧四个点gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0);//LINE_LOOP模式从第五个点开始绘制左侧四个点gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4);//LINES模式绘制连线左右点连线gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8);requestAnimationFrame(loop)}

这样一来我们就能看到立方体的屁股了:

知乎视频​www.zhihu.com

如何成为一个“有深度”的正方体?

虽然我们实现了3D效果,但这种正交效果并不会有日常我们见到的透视中的近大远小。

ddcc1e012c3b9ebda6466af55d42631a.png

我们要如何实现呢?

我们先来想想近大远小是什么,近大远小不就是近处的看起来大一点远处的看起来小一点么。那么最直接的近大远小就是根据z值大小去缩放点的位置。

这里我们添加一个控制点来缩放透视程度:

<div class="item"><span>z透视参数:</span><input type="range" id="item-z-factor" value="0" min="0" max="2" step="0.001" ></div>
// .......
<script>
//........
var zFactorNode = document.querySelector("#item-z-factor")
// ........
</script>

然后我们把这个变换写成一个矩阵的形式:

var zFactorMatrix = [1,0,0,0,0,1,0,0,0,0,1,zFactorNode.value,0,0,0,1
];
var matrix = m4.multiplys([zFactorMatrix,mTransform,mz,my,mx]);

这样通过调节z透视参数,我们的立方体就有了纵深感:

知乎视频​www.zhihu.com

需要注意的是,xyz经过zFactorMatrix处理后没有变化:

out_x = inx+0+0+0;out_y = in_y+0+0+0;out_z = in_z+0+0+0

但w会变成

out_w = in_w+1

给 gl_Position 的 x,y,z,w 值自动除以 w,所以所有点的x,y才会受到z的控制。

但实际开发中,我们不会使用zFactor来控制深度。而是用透视矩阵:

1ebf1cc442fec2d12ef68d176cc68c48.png

不过在此之前我们先来解决,物体超出xyz[-1,1]空间的问题。

正方体要离家出走了!

例如我把立方体变成一个边长20,中心在(0,0,70)的立方体:

var data = new Float32Array([10, 10, 80,-10, 10, 80,-10, -10, 80,10, -10, 80,10, 10, 60,-10, 10, 60,-10, -10, 60,10, -10, 60,
]);

它立刻消失在屏幕上。

所以我们现在要做的是指定一个区域(假设是以(0,0,70)为中心,变成是50的空间,图中黑色区域)将其矩阵变换到空间[-1,1]之中。

404ab7ddd0c7a96db8991cc55079899e.png

第一步我们自然是要把这个空间移动到(0,0,0),第二部就是把这个空间缩小到[-1,1]

这里我们在m4对象中再创建一个方法orthographic,它接受6个参数(t, b, l, r, n, f)分别所需要呈现空间的y,x,z的最小和最大值,对于以(0,0,70)为中心,变成是50的空间,这6个参数是(25,-25,25,-25,45,95):

var m4 = {
// ......orthographic: function (t, b, l, r, n, f) {//先把空间移动到0,0,0var trans = [1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, 0,-(r + l) / 2, -(t + b) / 2, -(n + f) / 2, 1,]//在把空间缩放到[-1,1]var scale = [2 / (r - l), 0, 0, 0,0, 2 / (t - b), 0, 0,0, 0, 2 / (n - f), 0,0, 0, 0, 1,]return this.multiply(scale, trans);},
//......
}

大家可以用原始空间的顶点带入矩阵计算看看是不是都被移动到了[-1,1]的八个顶点上。

这样我们在loop中再创建一个矩阵:

var Ortho = m4.orthographic(25,-25,25,-25,45,95);

而此时传入作色器的matrix矩阵将变成:

var matrix = m4.multiplys([Ortho, mTransform, mx, my, mz]);

而运行后通过调整参数我们又得到立方体,这样我们就实现了将空间中任意区域的几何体画到canvas上了:

17c83d9af1e2e06f28ad8d35a76ab29b.png

虽然如此但我们的透视又消失了回到了正交的状态,而且旋转的时候,物体好像并不是绕着自己的中心在转?这又是怎么回事?由于篇幅有限,这些事我们留到下回再说吧!

下回我们将解决:

  1. 如何加入透视?
  2. 如何让物体绕着自己旋转
  3. 如何让相机移动与旋转
  4. 如何盯住物体

参考资料:

https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-3d-perspective.html​webglfundamentals.org
11115361db583045414821730c97d39e.png

WebGL 三维透视投影

WebGL 三维透视投影​webglfundamentals.org
11115361db583045414821730c97d39e.png

Marschner, S., & Shirley, P. (2018). Fundamentals of computer graphics. Place of publication not identified: A K Peters/CRC Press.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/302446.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

通过R,让你的数据分析更简便!

R作为一种统计分析软件&#xff0c;广泛应用于生物、医学、电商、新闻等数据相关行业&#xff0c;是目前主流数据应用软件之一。为了更好地帮助大家了解并快速入门R语言&#xff0c;现超级数学建模携手柯老师以R语言为基础&#xff0c;向大家隆重推出《R语言基础》系列课。柯老…

r语言用行名称提取数据框信息显示na_学会这些R语言技巧至少可以节省半年时间...

ubuntu备忘定期清空回收站扩增子数据牢记r ubuntu 相关技巧和备忘待解决问题1&#xff1a;phyloseq有一篇文章案例使用输入和输出文件相同的文件名&#xff0c;无法执行待解决问题2&#xff1a;待解决问题3&#xff1a;样品分组文件太长了&#xff0c;导致提取出来数据存在NA值…

hibernate.cfg.xml的一些事

Hibernate连接数据库的配置文件书写一般有两种方式&#xff1a; 第一种方式&#xff1a;使用开发环境直接连接数据库最后生成hibernate.cfg.xml文件 第二种方式&#xff1a;使用已有的模版直接拷贝的工作的目录下&#xff0c;通过相应的修改获得需要的连接数据库的配置文件&…

配置静态路由下一跳为本地出战接口和IP地址的区别

配置静态路由下一跳为本地出战接口和IP地址的区别 在配置静态路由时&#xff0c;下一跳可以使用下一路由器的IP地址&#xff0c;也可以使用本路由器的出站接口。在点对点的网络中&#xff0c;两者可能没有什么差别&#xff0c;但在以太网中或者NBMA网络类型&#xff0c;两者有很…

使用 Source Generator 自动生成 WEB API

使用 Source Generator 自动生成 WEB APIIntro上次我们介绍了使用 Source Generator 的应用&#xff0c;有小伙伴留言说想要自动生成一套 ABP 相关的东西&#xff0c;我对 ABP 不怎么熟悉&#xff0c;所以写了一个简单版的雏形&#xff0c;可以根据自定义的模板去动态生成&…

Excel中的散点图这么强大,学习了!

全世界只有3.14 % 的人关注了数据与算法之美平时见得最多的也许是柱形图了&#xff0c;但我个人最喜欢的却是散点图。在讲散点图之前&#xff0c;我先阐述一个不太严谨的个人观点。我认为&#xff0c;所有的数据图表都可以分为两类&#xff0c;一类是偏重于展示&#xff0c;一类…

mysql修改字段的顺序_Mysql中如何修改字段的排列顺序?

创建数据表的数据,字段在表中的位置已经确定了。但要修改字段在表中的排列位置,则需要使用ALTER TABLE语句来处理。在MySQL中,修改字段排列位置的基本语法格式如下: ALTER TABLE MODLFY 字段名1 数据类型 FIRST|AFTER 字段名2 在上述格式中,“字段名1”指的是修改位置的字段…

趣读:程序员泪流满面的20个瞬间!

全世界只有3.14 % 的人关注了数据与算法之美【一】老板突然说&#xff0c;想跟你聊一聊你的年终review结果【二】记一次难忘的debug经历【三】——昨晚又加班了吧&#xff1f;——你怎么知道的&#xff1f;【四】老板定下了春节期间on call的人!【五】产品crash了&#xff0c;最…

记一次 .NET 某医院HIS系统 CPU爆高分析

一&#xff1a;背景 1. 讲故事前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高&#xff0c;求助如何分析&#xff1f;和这位朋友沟通下来&#xff0c;据说这问题困扰了他们几年????&#xff0c;还请了微软的工程师过来解决&#xff0c;无疾而终&#xff0c…

mysql判断表存在的sql语句_SQL 语句判断已知表是否存在_MySQL

问:怎样用SQL语句来判断已知表是否存在?答:具体解决方法如下:注释:以下代码为通常的引用Dao做的一模块以下为引用的内容&#xff1a;Function fExistTable(strTableName As String) As IntegerDim db As DatabaseDim i As IntegerSet db DBEngine.Workspaces(0).Databases(0)…

全球六大顶级域名动态:7月上旬.COM新增18.4万个

中国IDC评述网07月17日报道&#xff1a;据域名统计机构Whois Source公布的最新数据显示&#xff0c;截至7月15日&#xff0c;全球六大顶级域名&#xff08;.COM、.NET、.ORG、.INFO和.US&#xff09;的域名总量达到了 141,259,651个。其中&#xff0c;.COM域名注册量达104,165,…

如何用大数据找到男/女朋友?

全世界只有3.14 % 的人关注了数据与算法之美小柯25岁&#xff0c;单身男&#xff0c;热衷大数据&#xff0c;并决定认真钻研&#xff0c;用数据分析来实现自己的“脱单计划”。找女友第一步&#xff1a;整理思路找女友第二步&#xff1a;界定问题1、为什么要找女朋友&#xff0…

.NET5 开发手机提词应用,基于内嵌Web服务器及PowerPoint自动化

项目说明我使用电脑录制视频教程的时候&#xff0c;会展示PPT给观众&#xff0c;同时也有一些提示性的文字给我自己看。这就类似于很多电视节目录制现场的“提词器”。节目录制现场的提词器在PC环境下&#xff0c;PowerPoint也具有提词器功能&#xff0c;在编辑PPT的时候&#…

[Linux程序设计][调试][ElectricFence]

gcc –o test test.c –lefence 提前发现动态内存的错误 转载于:https://blog.51cto.com/honglei/934379

薄如冈本,37°恒温发热超薄保暖内衣,既要风度也要温度

问君能有几多愁恰似没穿秋裤遇寒流俗话说的好你在北方的暖气里穿着短袖我在南方的艳阳里瑟瑟发抖为了暖和一点大家都穿得里三层外三层的什么大衣棉袄厚外套都搬出来了但谁都不愿意在冬天穿的像个200斤的胖子好吗&#xff01;&#xff1f;可是为了风度连狗命都不要了吗&#xff…

Blazor Day

关注我们Blazor 是一个 Web UI 框架&#xff0c;Blazor 旨在简化快速的单页面 .Net 浏览器应用的构建过程&#xff0c;它虽然使用了诸如 CSS 和 HTML 之类的 Web 技术&#xff0c;但它使用 C&#xff03;语言和 Razor 语法代替 JavaScript 来构建可组合的 Web UI 。通过提供用于…

学会了Python之后,我的职业生涯突飞猛进

全世界只有3.14 % 的人关注了数据与算法之美身为职场人&#xff0c;收集上万条表格数据做商业分析&#xff0c;裁剪上千张图片&#xff0c;发送数百封邮件...这些都是经常会遇到的场景。我一直期待能有个工具解放我&#xff0c;直到我遇到了Python。Python的魅力很多小伙伴入坑…

Android网络编程

2019独角兽企业重金招聘Python工程师标准>>> Android平台有三种网络接口可以使用&#xff0c;他们分别是&#xff1a;java.net.*(标准Java接口)、Org.apache接口和Android.net.*(Android网络接口)。下面分别介绍这些接口的功能和作用。 1.标准Java接口 java.net.*提…

mac 启动mysql多实例_实践:mysql单机多实例部署(mac)

背景&#xff1a;在自己电脑搭建或测试分布式服务框架时&#xff0c;经常会用多个数据库实例模拟多个环境的情况&#xff0c;因此我把搭建多实例mysql的过程记录下来&#xff0c;方便互相学习和沟通。1.搭建环境1) mac 电脑&#xff0c;版本 10.15.62) mysql版本 8.0.202.搭建…

毫无疑问的是.NET 在信创常用软件适配清单之中

2020年8月份写了一篇文章《.NET Core也是国产化信息系统开发的重要选项》&#xff0c; 这又过去了大半年了&#xff0c;在信创领域发生了很大的变化&#xff0c;今天写这篇文章主要是想从信创常用软件适配清单 看一看.NET 在信创里面的情况。信创常用软件适配清单 是由中国电子…