WebGL实现sprite精灵效果的GUI控件
threejs已经有了sprite插件,这就方便了three的用户,直接可以使用threejs的sprite插件来制作GUI模型。sprite插件是阿里的lasoy老师改造过的,这个很厉害,要学习一哈他的源码。闲话少叙,我们来看一下如何用原生的webgl来实现sprite精灵效果。首先我们来看一个样例。
我们可以看到,这个数字模型的纹理贴图是“2”,他具有两个特性,第一他永远面向主相机,第二他在屏幕上的投影尺寸不随场景缩放而产生一丝一毫的变化。这就是sprite精灵的特点,我们来看看具体是怎么实现的这样的效果。第一我们先集中精力解决数字模型始终面向相机的问题,我们知道,模型在场景中的modelView矩阵是随场景的空间旋转,平移,缩放而重新计算的,那么问题来了,我们怎么知道场景的每一帧空间变换的平移,旋转,缩放的变化量呢,鲫鱼可以负责任的告诉大家,我们计算不出这3个空间变换的叠加。那是怎么实现数字模型空间变换使它每一帧都面向主相机的呢?好,我们就来看看数字模型是怎么每一帧计算空间变换矩阵的。
其中有一个技巧就是坐标系转换,我们知道,主相机和模型都在世界坐标系中,那么我们换个思路,能不能把数字“2”的模型放到主相机的局部坐标系下面,让他的x,y,z方向坐标轴和主相机的x,y,z方向坐标轴重合,这样不就使得数字模型“2”永远面对着主相机不产生相对旋转了吗,真是个好办法,鲫鱼我说干就干。
/**
* 统一两个矩阵的基
* mat1:参考矩阵
* mat2:要变换基的矩阵
* */
Mat4.copyBasis = function(mat1, mat2){
//x轴基向量
mat2[0] = mat1[0];
mat2[1] = mat1[1];
mat2[2] = mat1[2];
//y轴基向量
mat2[4] = mat1[4];
mat2[5] = mat1[5];
mat2[6] = mat1[6];
//z轴基向量
mat2[8] = mat1[8];
mat2[9] = mat1[9];
mat2[10] = mat1[10];
}; 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矩阵的逆矩阵即可,注意是逆矩阵,因为主相机也在世界坐标系下,他的空间变换矩阵还是世界坐标系下的空间位置描述,他的空间变换矩阵的逆矩阵才是他的局部坐标系矩阵。我们直接按照这个步骤来操作。
/**
* 计算文字相对主相机的变换矩阵
* mat:要计算的缩放旋转矩阵
* */
computeMatrix4MainCamera:function(mat){
//场景主相机
let camera = this._viewer.getMainCamera();
//相机坐标系矩阵
let modelViewMat = camera.getModelViewMatrix();
//相机坐标系矩阵的逆矩阵
let invMVMat = Mat4.MemoryPool.alloc();
Mat4.invert(invMVMat, modelViewMat);
//构造文字变换矩阵
Mat4.copyBasis(invMVMat, mat);
},
总共5行代码,第一步获取主相机;二、得到主相机的modelView矩阵;三和四、求modelView矩阵的逆矩阵;五、将逆矩阵的xyz轴向量基赋给我们的数字模型“2”的空间变换矩阵。做完这件事以后,鲫鱼惊喜地发现数字模型2完美地跟随相机转动起来,永远面对着相机。正如歌词所云,月亮走,我也走,月亮永远面向我,无论我走到哪儿。喝哈哈哈。
好了,第一件事情圆满解决,我们来看看第二件事情怎么处理。我们接下来要处理的是模型缩放,但数字模型“2”在屏幕上的投影大小不变。
要解决这件事,首先我们要清楚模型缩放的原理是什么,在我们的osg引擎中,是通过主相机靠近或远离模型来实现的缩放效果。那么就好办了,鲫鱼的思路就是相机靠近模型,我就把数字模型“2”缩小,相机原理模型,我就把数字模型“2”放大,通过近小远大来对抗视觉上的近大远小。我们知道,透视下的模型尺寸和到眼睛的距离是呈反比的关系。来看一张示意图。
我们可以很清楚的看明白,越远的物体越小,越近越大,物体尺寸在屏幕上的投影和到眼睛的距离成反比。那么鲫鱼为了固定数字模型“2”在屏幕上的投影尺寸,就要反过来缩放模型的尺寸,越近越小,越远越大,和模型到相机眼睛的距离成正比,就达到我们的目的了,下面是鲫鱼的源码。
/**
* 在透视相机下令模型随相机远近变化而放大缩小,使得文字看上去大小不变
* position:文字模型在场景中的位置坐标
* */
againstScale:function(position){
//拷贝参数,防止污染
let textPos = Vec3.MemoryPool.alloc();
Vec3.copy(textPos, position);
//场景主相机
let camera = this._viewer.getMainCamera();
//求模型到相机的垂直距离
let distance = camera.distancePointToEye(textPos);
//返回缩放比
return distance * this._scaleRatio;
}
这个函数返回的就是一个缩放比例和数字模型“2”到相机距离的乘积,调用这个函数鲫鱼就能获取到数字模型“2”的缩放值是多少。看一下怎么调用的这个函数。
/**
* 创建几何
* root:几何体挂载的根节点
* width:宽
* height:高
* position:位置坐标
* img:图片对象
* */
addGeometry:function(root, width, height, position, img){
//顶点缓存
let w = 0.5*width;
let h = 0.5*height;
//缩放比
let scaleRatio = 1;
scaleRatio = this.againstScale(position);
w = w*scaleRatio;
h = h*scaleRatio;
//顶点数组
let vertices = [-w, h, 0, -w, -h, 0, w, -h, 0, w, h, 0];
let array = new Float32Array(vertices);
let vertexBuffer = new BufferArray(BufferArray.ARRAY_BUFFER, array, 3);
//索引缓存
let indices = [0, 1, 2, 2, 3, 0];
let index = new Int8Array(indices);
let indexBuffer = new BufferArray(BufferArray.ELEMENT_ARRAY_BUFFER, index, index.length);
//绘制图元
let prim = new DrawElements(Primitives.TRIANGLES, indexBuffer);
//几何对象
let geom = new Geometry();
geom.setBufferArray('Vertex', vertexBuffer);
geom.setPrimitive(prim);
//纹理坐标
let uvs = [0, 1, 0, 0, 1, 0, 1, 1];
let uv = new Float32Array(uvs);
let uvBuffer = new BufferArray(BufferArray.ARRAY_BUFFER, uv, 2);
geom.setBufferArray('Texture', uvBuffer);
//纹理对象
let texture = new Texture();
texture._target = Texture.TEXTURE_2D;
texture.setInternalFormat(Texture.RGBA);
texture._magFilter = Texture.LINEAR;
texture._minFilter = Texture.LINEAR;
texture._wrapS = Texture.CLAMP_TO_EDGE;
texture._wrapT = Texture.CLAMP_TO_EDGE;
texture.setImage(img);
geom.getStateSet(true).addAttribute(texture, StateAttribute.OVERRIDE);
//图片背景透明
let bf = new BlendFunc(BlendFunc.SRC_ALPHA, BlendFunc.ONE_MINUS_SRC_ALPHA);
geom.getStateSet(true).addAttribute(bf, StateAttribute.OVERRIDE);
//几何对象加入根节点
root.addChild(geom);
//将root的位置平移到position位置
let translateMat = Mat4.MemoryPool.alloc();
Mat4.fromTranslation(translateMat, position);
Mat4.copy(root._matrix, translateMat);
//根据主相机视口调整模型旋转,保证文字总是面向相机
this.computeMatrix4MainCamera(root._matrix);
//析构
Mat4.MemoryPool.free(translateMat);
},
好了,再看一下初始化的函数,鲫鱼写的这个sprite功能类就净收眼底了。
/**
* 文字显示类
* */
let Geometry = require('../core/Geometry');
let DrawElements = require('../core/DrawElements');
let Primitives = require('../core/Primitives');
let StateSet = require('../core/StateSet');
let BufferArray = require('../core/BufferArray');
let Depth = require('../core/Depth');
let Texture = require('../core/Texture');
let Texture2D = require('../core/Texture2D');
let BlendFunc = require('../core/BlendFunc');
let StateAttribute = require('../core/StateAttribute');
let ShaderFactory = require('../render/ShaderFactory');
let Mat4 = require('../util/Mat4');
let Vec3 = require('../util/Vec3');
let MatrixTransform = require('../core/MatrixTransform'); let Text = function(){
this._viewer = undefined;//视图,为了确定相机视口
this._root = undefined;//根节点,在这个根节点下挂载文字长方形
this._scaleRatio = 0.0004//缩放比率,可调节
}; Text.prototype.constructor = Text;
Text.prototype = { /**
* 创建文字对象
* viewer:视图对像
* root:根节点
* width:长方形宽度
* height:长方形高度
* position:平面位置坐标
* */
create:function(viewer, root, width, height, position){
this._viewer = viewer;
this._root = root;
this.createText(width, height, position);
}, /**
* 创建文字对象,文字纹理的载体
* width:长方形宽度
* height:长方形高度
* position:平面位置坐标
* */
createText:function(width, height, position){
//长方形对象
let plane = new MatrixTransform(true);
//状态对象
let stateSet = new StateSet();
//选择纹理着色器
stateSet.addAttribute(ShaderFactory.createNavigateAssist.call(this));
//设置深度值,几何显示在最前端
stateSet.addAttribute(new Depth(Depth.LEQUAL, 0, 0.1));
//自动启用sampler2D采样器
stateSet.addAttribute(new Texture2D());
//设置根节点状态
this._root.setStateSet(stateSet);
//加载图片
let img = new Image();
img.src = TWO_URL;
//创建几何带纹理
this.addGeometry(plane, width, height, position, img);
//加入根节点
this._root.addChild(plane);
},
以上是Text.js的构造,鲫鱼是为了做出sprite精灵效果的GUI功能组建单独开发的一个功能类,希望各位同学能喜欢,欢迎讨论学习。下周继续我们的osg引擎源码功能模块的学习。谢谢大家的支持,在此感谢李连俊同学的帮助,使我理清了局部坐标系和全局世界坐标系的关系,再次感谢各位。
本文系原创,如需引用,请注明出处:https://www.cnblogs.com/ccentry/p/10294006.html
WebGL实现sprite精灵效果的GUI控件的更多相关文章
- WebGL之sprite精灵效果显式数字贴图
接着前一篇<WebGL实现sprite精灵效果的GUI控件>,我们继续开发我们的数字系统GUI控件,因为这套数字系统是基于sprite效果的,所以数字随相机转动而旋转(永远面对相机),随场 ...
- 2.GUI控件的使用 --《UNITY 3D 游戏开发》笔记
1.Label 控件 编写脚本文件,直接绑定在main camera上 public class labelScript : MonoBehaviour { //设定一个值来接收外部赋值的字符串 pu ...
- paip.gui控件tabs控件加载内容的原理以及easyui最佳实现
paip.gui控件tabs控件加载内容的原理以及easyui最佳实现 //////////////tabs控件的加载 同form窗体一样,俩个方式 两个方式:一个是url,简单的文本可以使用这个,不 ...
- paip.gui控件form窗体的原理实现以及easyui的新建以及编辑实现
paip.gui控件form窗体的原理实现以及easyui的新建以及编辑实现 //////新建 与编辑 var EditForm=new Form_easyui(); if(row) ...
- Unity3D入门之GUI基础以及常用GUI控件使用(2)
1.GUI基础 (1)GUI部分是每帧擦除重绘的,只应该在OnGUI中绘制GUI,按钮:GUILayout.Button(“Hello”); 只读标签:GUILayout.Label() (2)修改控 ...
- 从一点儿不会开始——Unity3D游戏开发学习(二) ——GUI控件之Button
一些废话 我在上一篇“一点儿不会”的系列随笔中说大概一周会发个2~3篇关于Unity的学习笔记.可这就两周过去了,我还停留在一篇的进度上,主要是这两周发生了一些事情导致我更新缓慢.其实截至目前为止,上 ...
- [原]Unity3D深入浅出 - GUI控件
Unity的GUI类提供了丰富的界面控件,通过组合这些控件,完成和用户交互的界面. Lable:绘制文本和图片 Box:绘制一个图形框 Button:绘制一个响应单击事件的按钮 RepeatButto ...
- Atitit.Gui控件and面板----数据库区-mssql 2008 权限 配置 报表查看成员
Atitit.Gui控件and面板----数据库区-mssql 2008 权限 配置 报表查看成员 1. 配置server连接权限 1 2. 配置数据库权限 1 3. 设置表格/视图安全性 2 1. ...
- Atitit.Gui控件and面板----web server区----- web服务器监控面板and控制台条目
Atitit.Gui控件and面板----web server区----- web服务器监控面板and控制台条目 1. Resin4.0.22 1 2. 查看http连接数::Summary>& ...
随机推荐
- [微信小程序直播平台开发]___(二)Nginx+rtmp在Windows中的搭建
1.一个可以忽略的前言 Nginx (engine x) 是一个高性能的HTTP和反向代理服务,也是一个IMAP/POP3/SMTP服务.Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Ramble ...
- php 上传大文件主要涉及配置upload_max_filesize和post_max_size两个选项。
今天在做上传的时候出现一个非常怪的问题,有时候表单提交可以获取到值,有时候就获取不到了,连普通的字段都获取不到了,苦思冥想还没解决,群里人问我upload_max_filesize的值改了吗,我说改了 ...
- Java Classloader机制解析
做Java开发,对于ClassLoader的机制是必须要熟悉的基础知识,本文针对Java ClassLoader的机制做一个简要的总结.因为不同的JVM的实现不同,本文所描述的内容均只限于Hotspo ...
- javascript库概念与连缀
一.JavaScript 库 1.什么是javascript库: javascript库,说白了,就是把各种常用的代码片段,组织起来放在一个 js 文件里,组成一个包,这个包就是 JavaScript ...
- jQuery页面滚动数字增长插件
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- windows禁用端口命令
netstat -aon|findstr 1099 找出占用1099端口的进程 然后关闭占用该端口的进程:taskkill -f -pid 3756(进程id)
- [转]C结构体之位域(位段)
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位.例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可.为了节省存储空间,并使处理简便,C语言又提供了一种数据结构 ...
- VC++中关于控件重绘函数/消息 OnPaint,OnDraw,OnDrawItem,DrawItem的区别
而OnPaint()是CWnd的类成员,同时负责响应WM_PAINT消息. OnDraw()是CVIEW的成员函数,并且没有响应消息的功能.这就是为什么你用VC成的程序代码时,在视图类只有OnDraw ...
- NYOJ-171 聪明的kk 填表法 普通dp
题目链接: http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=171 聪明的kk 时间限制:1000 ms | 内存限制:65535 KB 难 ...
- 简单部署iRedMail-0.9.8 - 邮件服务器架构和错误代码
1.去官网下载最新稳定版软件 https://www.iredmail.com/index.html 2.https://docs.iredmail.org/install.iredmail.on.r ...