文档章节

three.js 地形法向量生成

李勇2
 李勇2
发布于 2015/03/02 09:38
字数 1264
阅读 49
收藏 0

上一节采用 分形算法生成地形的高度值, 接着我们需要生成每个顶点的法向量。


three.js 的PlaneGeometry 自带有法向量, 法向量分为两种 即 平面法向量 和 平面每个定点法向量。

因此一个n*n 块组成的平面, 有n*n 个平面法向量, 有4*n*n 个顶点法向量。

这两种法向量区别是, 如果材质的shading属性是THREE.SmoothShading 则采用顶点法向量, 如果不是则采用平面法向量, 平面法向量 导致整个面上的法向量处处相同,所以光照可能不够真实。


平面几何体的顶点数组是(n+1)*(n+1)的长度, 因此其法向量数组长度也应该是(n+1)*(n+1) 才合适, 而如果遍历面 将会产生4*n*n个向量, 如何修正这个问题呢?

平面几何体在绘制的过程中, 由sortFacesByMaterial 函数处理生成几何体组。

首先根据材质对几何体分组,

     材质编号_当前材质几何体组编号  作为几何体组的标识。

    接着将相应的平面块 压入到对应的几何体组中。

   控制每个几何体组的定点个数 小于 65535.


为几何体组编全局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); 作为系数影响亮度。



本文转载自:http://blog.csdn.net/liyong748/article/details/7989872

共有 人打赏支持
李勇2

李勇2

粉丝 48
博文 189
码字总数 62209
作品 0
广州
程序员
私信 提问
three.js(六) 地形法向量生成

上一节采用 分形算法生成地形的高度值, 接着我们需要生成每个顶点的法向量。 three.js 的PlaneGeometry 自带有法向量, 法向量分为两种 即 平面法向量 和 平面每个定点法向量。 因此一个nn ...

李勇2
2012/09/18
0
0
three.js(八) bump map的生成

bump Map 主要用于增加表面的法向量细节。例如一个平面其法向量处处相同,即使使用了纹理,光照下的表现仍然不够真实。这时可以扰动表面面片的方向量,从而形成比较真实的光照效果。 类似于地...

李勇2
2012/09/18
0
0
three.js(五) 地形纹理混合

地形生成通常使用高度图, 而高度图的生成可以使用绘图工具,或者通过分形算法生成,例如square-diamond, fbm方法。 这里采用简单求平均值+随机波动的方法。 对于一个2^n+1 2^n+1 的网格, ...

李勇2
2012/09/18
0
0
tengge1/ShadowEditor

Shadow Editor 名称:Shadow Editor 版本:v0.0.8(开发中) 说明:基于的场景编辑器。 源码:https://github.com/tengge1/ShadowEditor 示例:https://github.com/tengge1/ShadowEditor-exa......

tengge1
2018/10/17
0
0
three.js (四)离散层次细节level of details

LOD 处理比较大的外部地面场景中比较有用, 一般用于绘制地形。 首先通过可视体的切割删除不用的地形块,接着通过LOD 对照相机不同距离的地形块进行层次细节调整。 这里采用最简单的LOD 方法...

李勇2
2012/09/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周五乱弹 —— 姑娘馋的口水都留下来了。

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @且无需多言 :分享Fall Out Boy的单曲《Disloyal Order Of Water Buffaloes》 《Disloyal Order Of Water Buffaloes》- Fall Out Boy 手机党...

小小编辑
43分钟前
15
6
vue 对对象的属性进行修改时,不能渲染页面 vue.$set()

我在vue里的方法里给一个对象添加某个属性时,我console.log出来的是已经更改的object ,但是页面始终没有变化 原因如下: **受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),...

Js_Mei
今天
2
0
开始看《Java学习笔记》

虽然书买了很久,但一直没看。这其中也写过一些Java程序,但都是基于IDE的帮助和对C#的理解来写的,感觉不踏实。 林信良的书写得蛮好的,能够帮助打好基础,看得出作者是比较用心的。 第1章概...

max佩恩
昨天
12
0
Redux 三大原则

1.单一数据源 在传统的MVC架构中,我们可以根据需要创建无数个Model,而Model之间可以互相监听、触发事件甚至循环或嵌套触发事件,这些在Redux中都是不被允许的。 因为在Redux的思想里,一个...

wenxingjun
昨天
9
0
跟我学Spring Cloud(Finchley版)-12-微服务容错三板斧

至此,我们已实现服务发现、负载均衡,同时,使用Feign也实现了良好的远程调用——我们的代码是可读、可维护的。理论上,我们现在已经能构建一个不错的分布式应用了,但微服务之间是通过网络...

周立_ITMuch
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部