当前位置: 首页 > Web前端 > JavaScript

入门webgl间谍

时间:2023-03-27 11:00:52 JavaScript

开篇先说说为什么要写这么一篇介绍webgl的文章,因为最近工作致力于3D交互的开发,基于webgl引擎库做一些业务层封装调用,然后输出API供前端使用,是接触webgl领域的开始。刚开始直接看shader书和引擎库的代码有点懵。后来发现自己对webgl缺乏一个整体的了解,对一些概念也是一知半解。于是看了three.js文档教程和webgl编程指南等基础教程。全面了解后,回头看代码,效果明显更好。因此,我整理了一些我认为在这个过程中需要的重要的入门知识。既是对过去的回顾,也希望能给很多准备学习webgl的同学带来一些帮助。什么是webgl?WebGL(全文Web图形库)是一种3D绘图协议。这个绘图技术标准通过添加OpenGLES的JavaScript绑定,让JavaScript和OpenGLES结合在一起(OpenGL是渲染2D、3D矢量图形的一种跨语言、跨平台的应用程序编程接口),WebGL可以提供硬件3D加速HTML5Canvas渲染,让Web开发者可以使用系统显卡更流畅的在浏览器中展示3D场景和模型。webgl基础从一个基本场景开始。上图是最简单的3D场景,由一些必不可少的元素组成。首先会有一个坐标系的概念(webgl默认是右上坐标系,y朝上),作为场景中元素位置的参考。其次,场景中必须要有一个摄像头,摄像头就是观察者,用来控制观察者站在场景中的哪个位置,从什么方向观察场景中的实体。实体是绘制在场景中的元素,比如上图中的立方体,实体有自己的位置、大小、颜色等基本属性。照明是场景中的光源。光照还有颜色、位置等基本属性,影响场景中实体的颜色和亮度。以上元素共同构成了一个3D场景。有了这个基本概念,场景中的元素就单独介绍了。坐标系和变换坐标系是用来标识实体位置的参照物,三维坐标系由x、y、z轴组成。至于x、y、z的方向,业界有两种不同的方向,左手坐标系和右手坐标系,而webgl使用的是右手坐标系。右手坐标系顾名思义,右手坐标系可以用右手的手指指向x、y、z轴。如下图,右手掌心正对自己,食指朝上为y轴正方向,拇指和中指自然伸展的方向为x轴正方向轴和z轴的正方向,三个手指的反方向为坐标轴的反方向。右手坐标系不仅定义了坐标的方向,还定义了旋转的方向。如果想让实体绕某个x、y、z轴旋转,只需要用大拇指指向对应坐标轴的正方向,四个手指指向的方向就是正方向旋转方向。所以webgl中旋转的正方形是逆时针的。有了右手坐标系后,实体的坐标还与webgl中的其他几个坐标系相关。下面详细解释一下:局部坐标系局部坐标系是指物体的初始坐标系。不同的物体可能一开始不在同一个局部坐标系下,所以两者的位置不相关,才能建立关系。两者需要放在一个统一的坐标系下,也就是世界坐标系。世界坐标系世界坐标系是指物体与WebGL相机建立连接时的坐标系,是webgl世界中所有实体的统一坐标系。有了这个坐标系,实体之间的坐标比较就有意义了。然而,实体被放置在世界坐标系空间后,虽然已经与网络摄像头建立了连接,但并没有进一步确定被观察物体的状态。相机从不同的位置和角度观察实体,看到的效果是不同的。这个时候,就引出了视图坐标系的概念。视图坐标系首先用一个简单的例子来说明视图坐标系的一些概念。我们从正面观察一个物体,从侧面观察一个物体。物体的形状不同,是因为观察者的位置和角度不同。如果保持观察者的位置和角度不变,即从正面看过去,想达到从侧面观察物体的效果,只能调整物体的位置和姿态来实现这。调整后实体的坐标是它在视图坐标系中的坐标。因此,视图坐标系描述了物体在模拟相机位姿调整下的位置。裁剪坐标系裁剪坐标系是对视图坐标系的补充和约束。实际上,人眼所能看到的区域并不是向四周无限延伸的。因此,为了在webgl中模拟人眼的视觉效果,相机的投影引入了可见空间的概念。只能绘制可见空间内的实体,可见空间外的实体将被移除。这是裁剪坐标。产地系。屏幕坐标系终于有了屏幕坐标系的概念。在裁剪坐标系中,投影平面上的实体最终显示在屏幕上,我们看到也有一个坐标转换的过程。转换后,显示在屏幕视口上的坐标就是屏幕坐标系中的坐标,WebGL会自动帮我们完成这一步。坐标系的转换最终显示在屏幕上的对象的坐标是通过上述各种坐标系之间的转换得到的。坐标系之间的转换过程是通过矩阵变化完成的(为什么后面可以单独实现矩阵变化?在矩阵中说明),具体的转换过程如下:camera相机是3D中观察者的角色scene,我们在屏幕上看到的图片其实就是camera中的图片映射到3D空间中的物体,camera有position和Attitude两个基本属性,position和attitude,影响看到的场景中的图片。此外,视觉空间和投影还有两个重要的属性:投影投影是指相机照射场景后在屏幕上的成像。webgl中有两种投影方式,分别是正交投影和透视投影。两种投影下的成像特性是不同的。正交投影正交投影是指摄像机的投影是平行发射的,所以场景中的实体无论远近最终映射到屏幕上的大小都是一样的。因此,在正射投影下,场景中实体的大小与距离无关。正射投影的这一特性常被用来绘制地图、城市模型等大场景。Perspectiveprojection透视投影是指相机的投影是从一个点以射线的形式向四周发射,所以场景中离相机越近的实体最终在屏幕上投影的越大,而离相机越远的实体摄像机最终会投射到屏幕上。小一些。这与人眼看物体远近小的事实是一致的,所以透视投影下绘制的场景更接近现实世界的特征。可见空间可见空间前面在裁剪坐标系中提到过,可见空间表示场景中可以映射到相机的范围。沿着相机的投影方向,会有两个重要的垂直平面,近裁剪平面和远裁剪平面。这两块屏幕和摄像机的视线形成的三维空间就是可视空间。根据不同的投影,正投影下的可见空间是一个长方体;而透视投影下的可见空间是一个四棱锥:在场景中绘制一个实体的过程需要一系列的处理过程,我们以场景中的一个立方体为例介绍一个物体在场景中被绘制的过程3D场景:首先会把立方体分成6个面,先得到平面上预设的顶点坐标和颜色,存入verticesBuffer,webgl的vertexshader(shader是运行在GPU上的程序)会从顶点缓冲区中读取顶点数据,逐个顶点处理后,作为下一次图形组装的输入。然后进行图形组装。webgl中图形的最小单位是三角形。任何复杂的图形都是由小三角形一个一个拼接而成。图形组装会将各个顶点按照一定的规则连接起来,一个接一个地组成三角形图元。其次是图形光栅化的过程,光栅化就是将图元转化为片段(画布上图像的每个像素对应一个片段,所以片段可以简单理解为像素,而不是像素)。光栅化后形成的片元数据作为片元着色器的输入。片元着色器会接收到每个顶点的颜色数据,将每个片元的颜色逐个计算出来,存入颜色缓冲区。colorbuffer中的分片再经过深度检测(处理两个坐标相同的分片的前后显示)、融合(处理透明度)等处理,形成最终的分片数据。最后,浏览器会读取颜色缓冲区中的片段数据,渲染到屏幕上。Texture当实体是一个表面光滑的简单物体时,可以用webgl的shader插值绘制出想要的表面效果。但如果是表面复杂的物体,用这种方法就很麻烦了。所以webgl提供了texture来解决这个问题。贴图就是在几何图形的表面贴上一张图片,使图形表面看起来像这张图片的效果,这种贴图称为贴图。在照明现实中,我们看到物体的颜色其实就是物体反射的光的颜色。根据光源和光的方向,物体不同表面的明暗不一致。3D场景中的实体模仿现实的特性,所以物体表面的明暗由物体本身的材质(决定光的漫反射率)和光线(光的颜色和方向)共同决定。照明类型场景中的照明可分为两类。一种是主光源,也就是使场景中的实体可见的主要照明。大致可分为点光源和平行光两种。点光源是从一点向周围辐射的光源。例如,在现实生活中,物体表面不同位置的入射角是不同的。平行光是从很远的光源发出的,所以光线可以看成是相互平行的,比如太阳光,物体表面不同位置的入射角是相同的。另一种类型是环境光。环境光是指由主光源发出,然后被墙壁等其他物体反射,再照射到目标实体上的光。它是用来模拟现实世界的辅助光源。为什么会有氛围灯?因为如果只有一个主光源,那么场景中平行于光线方向的平面根本无法反射光线,所以表现的是一片漆黑,与现实有明显的差异,所以即使是完全平行于主光源的平面也会有深色,而不是全黑。环境光的存在就是为了补充这种效果,让三维场景更接近真实。漫反射光源在固体表面的最终反射效果与入射角和固体的平面法向量有关。光漫反射是3D场景中用来模拟由于物体表面粗糙度不同而产生不同方向的反射光的理想模型。抛开实体本身材质因素的影响,可以认为实体漫反射的颜色是由入射光的颜色、实体表面的底色和入射角度决定的,可以用公式表示:漫射光颜色=实体表面的入射光颜色cosθ当入射角为0度时,即入射角垂直于实体表面时,cosθ=1,颜色反射光的强度不会减弱;当入射角为90度时,即入射角平行于固体表面时,cosθ=0,反射光颜色为0,呈现黑色。这与实际情况是一致的。动画场景中简单动画(比如旋转)的原理是每隔一定时间修改实体的坐标,在场景中重新绘制。在webgl中使用浏览器提供的requestAnimationFrameAPI来实现。requestAnimationFrame与setInterval相比,优点是:requestAnimationFrame采用系统时间间隔,间隔时间比较准确,不会造成丢帧卡顿,绘制时间跟随浏览器的刷新率,一般16.7ms左右(浏览器的刷新率一般是60fps/秒左右)执行一次,所以绘图效率比较好。requestAnimationFrame是浏览器原生API,所以如果当前页面没有被激活,渲染方法会自动停止执行。setInterval的执行与浏览器是否激活无关,一直运行。(例如:打开10个相同的浏览器页面,requestAnimationFrame只会在当前浏览的页面上执行,而setInterval会在所有10个页面上执行)因此,使用requestAnimationFrame的性能更好。requestAnimationFrame()告诉浏览器你要执行一个动画,并要求浏览器在下次重绘之前调用指定的回调函数来更新动画。该方法需要传入一个回调函数作为参数,回调函数会在下次浏览器重绘前执行。/***使用的伪代码*/letthen=0;//前一帧letnow=0;//当前帧constrender=function(timestamp){now=timestamp;constdeltaTime=现在-然后;//与上一帧的时间差then=now;currentAngle=animate(currentAngle,datalTime);//更新旋转角度draw(gl,n,currentAngle,modelMatrix,u_ModelMatrix);//重绘实体requestAnimationFrame(render);//在浏览器的下一帧重复渲染函数};使成为();requestAnimationFrame的使用有两点需要注意:requestAnimationFrame必须放在要执行的回调函数中,以达到重复调用的目的。requestAnimationFrame会给回调函数传入一个参数(timestamp),表示每次调用的时间间隔。通过这个间隔,可以计算出与上一帧的时间差,进一步调整绘制逻辑,保证渲染流畅。(比如:根据datalTime修正每次旋转的角度,这样就保证旋转的速度恒定,否则在高帧率浏览器中旋转会越来越快)空间几何基础向量向量,指的是大小和方向量。它可以可视化为带有箭头的线段。箭头表示向量的方向,线段的长度表示向量的大小。下面介绍webgl中最常用的点积和叉积以及应用场景点积几何意义向量a(x1,y1,z1),向量b(x2,y2,z2),则向量a点积向量b为:ab=|a||b|cosθ我们考虑当向量a和b都是单位向量时,ab=cosθcosθ可以表示为单位向量a在单位向量b方向的投影:cosθ=投影长度/1=投影长度,即a.b=向量a在向量b方向的投影长度,θ值越小,表示向量a越接近向量b。所以webgl中点积的意思就是判断两个向量的接近程度。应用场景如下图所示:已知一条路线,通过点连接路线。选择路线两侧的对应点,可以用向量点乘来确定路线的方向。两边的点形成的向量之间的向量和和点积大小用于筛选。叉积几何意义叉积是向量空间中向量的二元运算。它的运算结果是一个向量,这个向量垂直于原来两个向量所在的平面。因为叉乘后得到一个与原来两个向量垂直的向量,方向遵循右手法则,所以webgl中叉乘的几何意义就是求平面的法向量。应用场景中最常见的应用就是求一个已知平面的法向量。我用一个具体的应用例子来说明一下:为了找到点p3的坐标,可以先找到向量p2p1和向量vertVec3所在平面的法向量croVec3,然后通过向量之间的运算关系croVec3和向量p2p1产生最终点p3的坐标。伪代码如下:import{vec3}from'gl-matrix';privatep1:vec3=vec3.create();//已知坐标point_p1privatep2:vec3=vec3.create();//已知坐标点_p2privatep3:vec3=vec3.create();//求坐标点_p3constnormVec3=vec3.create();vec3.subtract(normVec3,this.p1,this.p2);//获取向量p2p1vec3.normalize(normVec3,normVec3)//归一化constvertVec3=[0,1,0]asvec3;//向量常量croVec3=vec3.create();vec3.cross(croVec3,vertVec3,normVec3);//叉乘得到垂直向量方向遵循右手法则vec3.normalize(croVec3,croVec3);//标准化的vec3.scale(normVec3,normVec3,0.5);//缩放为0.5单位长度(0.5m)的向量vec3.scale(croVec3,croVec3,1)//缩放为单位长度(1m)的向量consttempVec3=vec3.create();vec3.add(tempVec3,normVec3,croVec3);//相加得到向量p2p3vec3.add(this.p3,tempVec3,this.p2);//转换后的点p3坐标矩阵矩阵是一组复数或实数排列成矩形的数组,m行n由m×n个数aij列的数表称为有m行的矩阵和n列,或简称为m×n矩阵。注:矩阵在webgl中被广泛使用,因为坐标的变化可以用变换矩阵表示。在引入坐标系变换时,视图之间的变换都是通过模型矩阵完成的。实体从位置A到位置B的变化是通过一系列的旋转和平移得到的。坐标的变化可以用矩阵乘法表示:等式左边的矩阵为变化矩阵。通过推导(具体推导过程可以自己查)可以得出平移矩阵为:旋转矩阵为:缩放矩阵为:插值插值是离散函数逼近的一种重要方法。可以通过函数在有限几个点的值来估计其他点的函数值。近似值。插值也用于图像渲染以填充图像变换期间像素之间的间隙。计算插值的插值函数有很多种,比如最近邻法、线性插值法等,这些具??体的算法在工程上并不一定要掌握,会有常用的数学库封装插值函数。当webgl在不断变化的过程中遇到实时值时,插值就派上用场了。下面用一个具体的例子来说明插值的应用场景:应用场景假设小人从A点匀速行走到B点时,已知A点和B点的坐标,实时必须计算行走过程中的坐标。从A到B的过程中需要改变实时坐标,只需要将起点A和终点B的坐标以总时间作为插值系数进行插值即可得到各个点的坐标小人的框架。具体伪代码如下:(关键两行插值代码在update函数中)import{vec3}from'gl-matrix';私人p_start:vec3|不明确的;//起点坐标privatep_end:vec3|不明确的;//终点坐标privatep_move:vec3=vec3.create();//运动过程中的实时坐标privatespeed:number//运动速度privatetime:number//运动所需时间privatecurrentTime=0//当前时间privateratio=0//行驶距离占总距离的比例distance//只在进入onEnter()时执行一次的钩子函数{constdistance=vec3.distance(this.p_start,this.p_end);this.time=distance/this.speed;constdir=vec3.create();vec3.subtract(dir,this.p_end,this.p_end);目录[1]=0;//在地面上行走,将y坐标处理为0vec3.normalize(dir,dir);//归一化this.srcInfo.playAnimation('walk');//行走动画this.faceToDir(dir,[0,1,0]);//自定义函数,面向行走方向}//浏览器中的每一帧钩子函数update(deltaTime:number){if(!this.p_start||!this.p_end){return;}this.currentTime+=deltaTime;this.ratio=this.currentTime/this.time;如果(this.currentTime>=this.time){this.ratio=1;}vec3.lerp(this.p_move,this.p_start,this.p_end,this.ratio);//插值找到当前的移动坐标this.setPosition(this.p_move);//改变当前字符坐标的自定义函数if(this.ratio===1){this.reset();//到达终点,可以做自定义业务逻辑...}}//只有在钩子函数onExit(){this.reset();}reset(){this.currentTime=0;this.ratio=0;}结语通过上面webgl中的一些基础知识和相关空间简单介绍了几何学中常用的基础知识,对webgl的介绍有了初步的了解。后面有时间会逐步介绍和分析webgl中的一些核心模块,在webgl的方向上不断深入。