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); 做为系数影响亮度。

相关文章
相关标签/搜索