分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 技术分享

WebGL实现sprite精灵效果的GUI控件

发布时间:2023-09-06 02:30责任编辑:苏小强关键词:WebWebGL

  threejs已经有了sprite插件,这就方便了three的用户,直接可以使用threejs的sprite插件来制作GUI模型。sprite插件是阿里的lasoy老师改造过的,这个很厉害,要学习一哈他的源码。闲话少叙,我们来看一下如何用原生的webgl来实现sprite精灵效果。首先我们来看一个样例。

  我们可以看到,这个数字模型的纹理贴图是“2”,他具有两个特性,第一他永远面向主相机,第二他在屏幕上的投影尺寸不随场景缩放而产生一丝一毫的变化。这就是sprite精灵的特点,我们来看看具体是怎么实现的这样的效果。第一我们先集中精力解决数字模型始终面向相机的问题,我们知道,模型在场景中的modelView矩阵是随场景的空间旋转,平移,缩放而重新计算的,那么问题来了,我们怎么知道场景的每一帧空间变换的平移,旋转,缩放的变化量呢,鲫鱼可以负责任的告诉大家,我们计算不出这3个空间变换的叠加。那是怎么实现数字模型空间变换使它每一帧都面向主相机的呢?好,我们就来看看数字模型是怎么每一帧计算空间变换矩阵的。

  其中有一个技巧就是坐标系转换,我们知道,主相机和模型都在世界坐标系中,那么我们换个思路,能不能把数字“2”的模型放到主相机的局部坐标系下面,让他的x,y,z方向坐标轴和主相机的x,y,z方向坐标轴重合,这样不就使得数字模型“2”永远面对着主相机不产生相对旋转了吗,真是个好办法,鲫鱼我说干就干。

 1 /** 2 ?* 统一两个矩阵的基 3 ?* mat1:参考矩阵 4 ?* mat2:要变换基的矩阵 5 ?* */ 6 Mat4.copyBasis = function(mat1, mat2){ 7 ????//x轴基向量 8 ????mat2[0] = mat1[0]; 9 ????mat2[1] = mat1[1];10 ????mat2[2] = mat1[2];11 ????//y轴基向量12 ????mat2[4] = mat1[4];13 ????mat2[5] = mat1[5];14 ????mat2[6] = mat1[6];15 ????//z轴基向量16 ????mat2[8] = mat1[8];17 ????mat2[9] = mat1[9];18 ????mat2[10] = mat1[10];19 };20 21 module.exports = Mat4;

  首先理解空间变换矩阵的同学都知道列主序的矩阵的x轴分量即x轴基向量是mat[0],mat[1],mat[2];y轴分量即y轴基向量是mat[4],mat[5],mat[6];z轴分量即z轴基向量是mat[8],mat[9],mat[10];平移和缩放向量是mat[12],mat[13],mat[14]。那么好了,我们现在不关心平移和缩放,只关心旋转,所以我们只需要把数字模型的空间变换矩阵的x基,y基,z基照搬主相机的modelView矩阵的逆矩阵即可,注意是逆矩阵,因为主相机也在世界坐标系下,他的空间变换矩阵还是世界坐标系下的空间位置描述,他的空间变换矩阵的逆矩阵才是他的局部坐标系矩阵。我们直接按照这个步骤来操作。

 1 /** 2 ??* 计算文字相对主相机的变换矩阵 3 ??* mat:要计算的缩放旋转矩阵 4 ??* */ 5 computeMatrix4MainCamera:function(mat){ 6 ????//场景主相机 7 ????let camera = this._viewer.getMainCamera(); 8 ????//相机坐标系矩阵 9 ????let modelViewMat = camera.getModelViewMatrix();10 ????//相机坐标系矩阵的逆矩阵11 ????let invMVMat = Mat4.MemoryPool.alloc();12 ????Mat4.invert(invMVMat, modelViewMat);13 ????//构造文字变换矩阵14 ????Mat4.copyBasis(invMVMat, mat);15 },

  总共5行代码,第一步获取主相机;二、得到主相机的modelView矩阵;三和四、求modelView矩阵的逆矩阵;五、将逆矩阵的xyz轴向量基赋给我们的数字模型“2”的空间变换矩阵。做完这件事以后,鲫鱼惊喜地发现数字模型2完美地跟随相机转动起来,永远面对着相机。正如歌词所云,月亮走,我也走,月亮永远面向我,无论我走到哪儿。喝哈哈哈。

  好了,第一件事情圆满解决,我们来看看第二件事情怎么处理。我们接下来要处理的是模型缩放,但数字模型“2”在屏幕上的投影大小不变。

  要解决这件事,首先我们要清楚模型缩放的原理是什么,在我们的osg引擎中,是通过主相机靠近或远离模型来实现的缩放效果。那么就好办了,鲫鱼的思路就是相机靠近模型,我就把数字模型“2”缩小,相机原理模型,我就把数字模型“2”放大,通过近小远大来对抗视觉上的近大远小。我们知道,透视下的模型尺寸和到眼睛的距离是呈反比的关系。来看一张示意图。

我们可以很清楚的看明白,越远的物体越小,越近越大,物体尺寸在屏幕上的投影和到眼睛的距离成反比。那么鲫鱼为了固定数字模型“2”在屏幕上的投影尺寸,就要反过来缩放模型的尺寸,越近越小,越远越大,和模型到相机眼睛的距离成正比,就达到我们的目的了,下面是鲫鱼的源码。

 1 /** 2 ?????* 在透视相机下令模型随相机远近变化而放大缩小,使得文字看上去大小不变 3 ?????* position:文字模型在场景中的位置坐标 4 ?????* */ 5 ????againstScale:function(position){ 6 ????????//拷贝参数,防止污染 7 ????????let textPos = Vec3.MemoryPool.alloc(); 8 ????????Vec3.copy(textPos, position); 9 ????????//场景主相机10 ????????let camera = this._viewer.getMainCamera();11 ????????//求模型到相机的垂直距离12 ????????let distance = camera.distancePointToEye(textPos);13 ????????//返回缩放比14 ????????return distance * this._scaleRatio;15 ????}

这个函数返回的就是一个缩放比例和数字模型“2”到相机距离的乘积,调用这个函数鲫鱼就能获取到数字模型“2”的缩放值是多少。看一下怎么调用的这个函数。

 1 /** 2 ?????* 创建几何 3 ?????* root:几何体挂载的根节点 4 ?????* width:宽 5 ?????* height:高 6 ?????* position:位置坐标 7 ?????* img:图片对象 8 ?????* */ 9 ????addGeometry:function(root, width, height, position, img){10 ????????//顶点缓存11 ????????let w = 0.5*width;12 ????????let h = 0.5*height;13 ????????//缩放比14 ????????let scaleRatio = 1;15 ????????scaleRatio = this.againstScale(position);16 ????????w = w*scaleRatio;17 ????????h = h*scaleRatio;18 ????????//顶点数组19 ????????let vertices = [-w, h, 0, -w, -h, 0, w, -h, 0, w, h, 0];20 ????????let array = new Float32Array(vertices);21 ????????let vertexBuffer = new BufferArray(BufferArray.ARRAY_BUFFER, array, 3);22 ????????//索引缓存23 ????????let indices = [0, 1, 2, 2, 3, 0];24 ????????let index = new Int8Array(indices);25 ????????let indexBuffer = new BufferArray(BufferArray.ELEMENT_ARRAY_BUFFER, index, index.length);26 ????????//绘制图元27 ????????let prim = new DrawElements(Primitives.TRIANGLES, indexBuffer);28 ????????//几何对象29 ????????let geom = new Geometry();30 ????????geom.setBufferArray(‘Vertex‘, vertexBuffer);31 ????????geom.setPrimitive(prim);32 ????????//纹理坐标33 ????????let uvs = [0, 1, 0, 0, 1, 0, 1, 1];34 ????????let uv = new Float32Array(uvs);35 ????????let uvBuffer = new BufferArray(BufferArray.ARRAY_BUFFER, uv, 2);36 ????????geom.setBufferArray(‘Texture‘, uvBuffer);37 ????????//纹理对象38 ????????let texture = new Texture();39 ????????texture._target = Texture.TEXTURE_2D;40 ????????texture.setInternalFormat(Texture.RGBA);41 ????????texture._magFilter = Texture.LINEAR;42 ????????texture._minFilter = Texture.LINEAR;43 ????????texture._wrapS = Texture.CLAMP_TO_EDGE;44 ????????texture._wrapT = Texture.CLAMP_TO_EDGE;45 ????????texture.setImage(img);46 ????????geom.getStateSet(true).addAttribute(texture, StateAttribute.OVERRIDE);47 ????????//图片背景透明48 ????????let bf = new BlendFunc(BlendFunc.SRC_ALPHA, BlendFunc.ONE_MINUS_SRC_ALPHA);49 ????????geom.getStateSet(true).addAttribute(bf, StateAttribute.OVERRIDE);50 ????????//几何对象加入根节点51 ????????root.addChild(geom);52 ????????//将root的位置平移到position位置53 ????????let translateMat = Mat4.MemoryPool.alloc();54 ????????Mat4.fromTranslation(translateMat, position);55 ????????Mat4.copy(root._matrix, translateMat);56 ????????//根据主相机视口调整模型旋转,保证文字总是面向相机57 ????????this.computeMatrix4MainCamera(root._matrix);58 ????????//析构59 ????????Mat4.MemoryPool.free(translateMat);60 ????},

好了,再看一下初始化的函数,鲫鱼写的这个sprite功能类就净收眼底了。

 1 /** 2 ?* 文字显示类 3 ?* */ 4 let Geometry = require(‘../core/Geometry‘); 5 let DrawElements = require(‘../core/DrawElements‘); 6 let Primitives = require(‘../core/Primitives‘); 7 let StateSet = require(‘../core/StateSet‘); 8 let BufferArray = require(‘../core/BufferArray‘); 9 let Depth = require(‘../core/Depth‘);10 let Texture = require(‘../core/Texture‘);11 let Texture2D = require(‘../core/Texture2D‘);12 let BlendFunc = require(‘../core/BlendFunc‘);13 let StateAttribute = require(‘../core/StateAttribute‘);14 let ShaderFactory = require(‘../render/ShaderFactory‘);15 let Mat4 = require(‘../util/Mat4‘);16 let Vec3 = require(‘../util/Vec3‘);17 let MatrixTransform = require(‘../core/MatrixTransform‘);18 19 let Text = function(){20 ????this._viewer = undefined;//视图,为了确定相机视口21 ????this._root = undefined;//根节点,在这个根节点下挂载文字长方形22 ????this._scaleRatio = 0.0004//缩放比率,可调节23 };24 25 Text.prototype.constructor = Text;26 Text.prototype = {27 28 ????/**29 ?????* 创建文字对象30 ?????* viewer:视图对像31 ?????* root:根节点32 ?????* width:长方形宽度33 ?????* height:长方形高度34 ?????* position:平面位置坐标35 ?????* */36 ????create:function(viewer, root, width, height, position){37 ????????this._viewer = viewer;38 ????????this._root = root;39 ????????this.createText(width, height, position);40 ????},41 42 ????/**43 ?????* 创建文字对象,文字纹理的载体44 ?????* width:长方形宽度45 ?????* height:长方形高度46 ?????* position:平面位置坐标47 ?????* */48 ????createText:function(width, height, position){49 ????????//长方形对象50 ????????let plane = new MatrixTransform(true);51 ????????//状态对象52 ????????let stateSet = new StateSet();53 ????????//选择纹理着色器54 ????????stateSet.addAttribute(ShaderFactory.createNavigateAssist.call(this));55 ????????//设置深度值,几何显示在最前端56 ????????stateSet.addAttribute(new Depth(Depth.LEQUAL, 0, 0.1));57 ????????//自动启用sampler2D采样器58 ????????stateSet.addAttribute(new Texture2D());59 ????????//设置根节点状态60 ????????this._root.setStateSet(stateSet);61 ????????//加载图片62 ????????let img = new Image();63 ????????img.src = TWO_URL;64 ????????//创建几何带纹理65 ????????this.addGeometry(plane, width, height, position, img);66 ????????//加入根节点67 ????????this._root.addChild(plane);68 ????},

  以上是Text.js的构造,鲫鱼是为了做出sprite精灵效果的GUI功能组建单独开发的一个功能类,希望各位同学能喜欢,欢迎讨论学习。下周继续我们的osg引擎源码功能模块的学习。谢谢大家的支持,在此感谢李连俊同学的帮助,使我理清了局部坐标系和全局世界坐标系的关系,再次感谢各位。

  本文系原创,如需引用,请注明出处:https://www.cnblogs.com/ccentry/p/10294006.html

WebGL实现sprite精灵效果的GUI控件

原文地址:https://www.cnblogs.com/ccentry/p/10294006.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved