上一節採用 分形算法生成地形的高度值, 接着咱們須要生成每一個頂點的法向量。算法
three.js 的PlaneGeometry 自帶有法向量, 法向量分爲兩種 即 平面法向量 和 平面每一個定點法向量。數組
所以一個n*n 塊組成的平面, 有n*n 個平面法向量, 有4*n*n 個頂點法向量。函數
這兩種法向量區別是, 若是材質的shading屬性是THREE.SmoothShading 則採用頂點法向量, 若是不是則採用平面法向量, 平面法向量 致使整個面上的法向量到處相同,因此光照可能不夠真實。code
平面幾何體的頂點數組是(n+1)*(n+1)的長度, 所以其法向量數組長度也應該是(n+1)*(n+1) 才合適, 而若是遍歷面 將會產生4*n*n個向量, 如何修正這個問題呢?orm
平面幾何體在繪製的過程當中, 由sortFacesByMaterial 函數處理生成幾何體組。three
首先根據材質對幾何體分組,get
材質編號_當前材質幾何體組編號 做爲幾何體組的標識。it
接着將相應的平面塊 壓入到對應的幾何體組中。io
控制每一個幾何體組的定點個數 小於 65535.form
爲幾何體組編全局id號, 並將幾何體組壓入到 幾何體組的List中
geometry.geometryGroups----->map形式訪問幾何體組
geometry.geometryGroupList-----> 數組形式訪問幾何體組
首先構建頂點 法向量 tangent, 顏色, 紋理座標, 面, 線 等buffer。
接着初始化這些buffers。
接着在setMeshBuffers 中爲這些buffer賦值, 根據每一個獨立的面都有將(n+1)*(n+1)個定點值寫入到 4*n*n的頂點數組中去,
用戶本身定義的屬性,若是按照點綁定,則根據面的數量將(n+1)*(n+1)個值寫入到 4*n*n 長度的數組中。
若是按照面綁定則把 n*n 個值 寫入到 4*n*n 個長度的數組中。
經過以上咱們能夠看到,繪製平面的時候, 雖然咱們只寫了(n+1)*(n+1)個定點值,可是引擎實際擴展到 4*n*n 個值,這樣最大化了空間的使用,具備最大的靈活性。
知道了引擎的處理方法,咱們構建一個(n+1)*(n+1)的shader屬性,默認綁定在頂點上,接着計算向量值並賦值給這個屬性就能夠了。
材質以下:
var pmat = new THREE.ShaderMaterial({ uniforms:{ texture_grass:{type:'t', value:0, texture:THREE.ImageUtils.loadTexture("grassa512.bmp")}, texture_rock:{type:'t', value:1, texture:THREE.ImageUtils.loadTexture("dirt512.bmp")}, light:{type:'v3', value:new THREE.Vector3()}, maxHeight:{type:'f', value:0}, minHeight:{type:'f', value:1}, }, attributes:{ displacement: {type:'f', value:[]}, vexNormal:{type:'v3', value:[]}, }, vertexShader: document.getElementById("vert").textContent, fragmentShader: document.getElementById("frag").textContent, });
這個屬性是v3 類型即對應的THREE數據類型是Vector3, 法向量的生成,對於每個定點其左右定點鏈接的向量和上下頂點鏈接的向量的叉乘, 做爲自身的法向量。
var v1 = new THREE.Vector3(); var v2 = new THREE.Vector3(); var distX = 2*3/(WIDTH-1); var distY = 2*3/(HEIGHT-1); var vexNormal = pmat.attributes.vexNormal.value; var vertices = pmesh.geometry.vertices; var lmat = new THREE.LineBasicMaterial({color:0xff0000}); for(var i = 0; i < vertices.length; i++) { var row = ~~(i/WIDTH); var col = i%WIDTH; var left = (col-1+WIDTH)%WIDTH; var right = (col+1)%WIDTH; var up = (row-1+HEIGHT)%HEIGHT; var bottom = (row+1)%HEIGHT; var l = value[row*WIDTH+left]; var r = value[row*WIDTH+right]; v1.set(distX, 0, r-l); var u = value[up*WIDTH+col]; var b = value[bottom*WIDTH+col]; v2.set(0, distY, b-u); v1.crossSelf(v2.clone()).normalize(); vexNormal.push(v1.clone()); var lgeo = new THREE.Geometry(); lgeo.vertices.push(new THREE.Vertex()); lgeo.vertices.push(new THREE.Vertex(v1.clone())); var line = new THREE.Line(lgeo, lmat); line.position.set(vertices[i].position.x, vertices[i].position.y, value[i]); pmesh.add(line); }
這裏計算的法向量是屬於物體空間的, 在shader中咱們須要將其轉化成世界座標, normalMatrix 是 世界視圖modelView 矩陣的逆轉置, 不能將法向量轉化到世界座標,所以,咱們傳入一個額外的矩陣, 當前引擎彷佛只有mat4 的4*4 的矩陣, 所以咱們傳入4*4 objectMatrix 的逆轉置。
normalWorldMatrix 是 要的矩陣。
var pmat = new THREE.ShaderMaterial({ uniforms:{ texture_grass:{type:'t', value:0, texture:THREE.ImageUtils.loadTexture("grassa512.bmp")}, texture_rock:{type:'t', value:1, texture:THREE.ImageUtils.loadTexture("dirt512.bmp")}, light:{type:'v3', value:new THREE.Vector3()}, normalWorldMatrix:{type:'m4', value:new THREE.Matrix4()}, maxHeight:{type:'f', value:0}, minHeight:{type:'f', value:1}, }, attributes:{ displacement: {type:'f', value:[]}, vexNormal:{type:'v3', value:[]}, }, vertexShader: document.getElementById("vert").textContent, fragmentShader: document.getElementById("frag").textContent, //wireframe:true, });
normalWorldmatrix.value.getInverse(pmesh.matrixWorld).transpose();
固然在shader裏面咱們只使用它的3*3 部分, 先將定點法向擴充成 4維 接着只取其前3維度便可。
nor = (normalWorldMatrix * vec4(vexNormal, 0)).xyz
固然加入法向量的目的是 計算光照, 在平面上方設置一個光源位置 做爲uniform傳入 light.
lightDir = light-pos;
diffuse = max(dot(normalize(lightDir), nor), 0); 做爲係數影響亮度。