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”永遠面對着主相機不產生相對旋轉了嗎,真是個好辦法,鯽魚我說干就干。

 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM