WebGL實現sprite精靈效果的GUI控件

  threejs已經有了sprite插件,這就方便了three的用戶,直接可使用threejs的sprite插件來製做GUI模型。sprite插件是阿里的lasoy老師改造過的,這個很厲害,要學習一哈他的源碼。閒話少敘,咱們來看一下如何用原生的webgl來實現sprite精靈效果。首先咱們來看一個樣例。html

  咱們能夠看到,這個數字模型的紋理貼圖是「2」,他具備兩個特性,第一他永遠面向主相機,第二他在屏幕上的投影尺寸不隨場景縮放而產生一絲一毫的變化。這就是sprite精靈的特色,咱們來看看具體是怎麼實現的這樣的效果。第一咱們先集中精力解決數字模型始終面向相機的問題,咱們知道,模型在場景中的modelView矩陣是隨場景的空間旋轉,平移,縮放而從新計算的,那麼問題來了,咱們怎麼知道場景的每一幀空間變換的平移,旋轉,縮放的變化量呢,鯽魚能夠負責任的告訴你們,咱們計算不出這3個空間變換的疊加。那是怎麼實現數字模型空間變換使它每一幀都面向主相機的呢?好,咱們就來看看數字模型是怎麼每一幀計算空間變換矩陣的。前端

  其中有一個技巧就是座標系轉換,咱們知道,主相機和模型都在世界座標系中,那麼咱們換個思路,能不能把數字「2」的模型放到主相機的局部座標系下面,讓他的x,y,z方向座標軸和主相機的x,y,z方向座標軸重合,這樣不就使得數字模型「2」永遠面對着主相機不產生相對旋轉了嗎,真是個好辦法,鯽魚我說幹就幹。web

 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行代碼,第一步獲取主相機;2、獲得主相機的modelView矩陣;三和4、求modelView矩陣的逆矩陣;5、將逆矩陣的xyz軸向量基賦給咱們的數字模型「2」的空間變換矩陣。作完這件事之後,鯽魚驚喜地發現數字模型2完美地跟隨相機轉動起來,永遠面對着相機。正如歌詞所云,月亮走,我也走,月亮永遠面向我,不管我走到哪兒。喝哈哈哈。緩存

  好了,第一件事情圓滿解決,咱們來看看第二件事情怎麼處理。咱們接下來要處理的是模型縮放,但數字模型「2」在屏幕上的投影大小不變。函數

  要解決這件事,首先咱們要清楚模型縮放的原理是什麼,在咱們的osg引擎中,是經過主相機靠近或遠離模型來實現的縮放效果。那麼就好辦了,鯽魚的思路就是相機靠近模型,我就把數字模型「2」縮小,相機原理模型,我就把數字模型「2」放大,經過近小遠大來對抗視覺上的近大遠小。咱們知道,透視下的模型尺寸和到眼睛的距離是呈反比的關係。來看一張示意圖。學習

咱們能夠很清楚的看明白,越遠的物體越小,越近越大,物體尺寸在屏幕上的投影和到眼睛的距離成反比。那麼鯽魚爲了固定數字模型「2」在屏幕上的投影尺寸,就要反過來縮放模型的尺寸,越近越小,越遠越大,和模型到相機眼睛的距離成正比,就達到咱們的目的了,下面是鯽魚的源碼。webgl

 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」的縮放值是多少。看一下怎麼調用的這個函數。ui

 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功能類就淨收眼底了。this

 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引擎源碼功能模塊的學習。謝謝你們的支持,在此感謝李連俊同窗的幫助,使我理清了局部座標系和全局世界座標系的關係,再次感謝各位。

  本文系原創,如需引用,請註明出處:http://www.javashuo.com/article/p-nrcluidf-ey.html

相關文章
相關標籤/搜索