上一节采用 分形算法生成地形的高度值, 接着咱们须要生成每一个顶点的法向量。算法
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); 做为系数影响亮度。