three.js 地形法向量生成

上一節採用 分形算法生成地形的高度值, 接着咱們須要生成每一個頂點的法向量。算法


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,
    
    });

其中vertexNormal 就是逐頂點法向量,固然咱們也能夠直接修改默認每一個面塊的法向量或者修改平面法向量這兩種方法都不方便,因此仍是使用一個額外的屬性來處理。

這個屬性是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,
    
    });

將平面位置調整以後, updateMatrixWorld 更新平面的世界矩陣, 接着將平面的matrixWorld的逆轉置賦值給normalWorldMatrix.

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); 做爲係數影響亮度。

相關文章
相關標籤/搜索