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