基於 HTML5 WebGL 的 3D 場景中的燈光效果

圖撲軟體發表於2019-03-01

前言

構建 3D 的場景除了建立模型,對模型設定顏色和貼圖外,還需要有燈光的效果才能更逼真的反映真實世界的場景。這個例子我覺得既美觀又代表性很強,所以拿出來給大家分享一下。

本例地址:http://www.hightopo.com/guide/guide/core/lighting/examples/example_flowing.html

例子動圖:

圖片描述

上面場景中主要的知識點包括:3D 燈光以及 3D 模型的流動。

1. 場景搭建

整個場景中包括 2D 場景(也就是鷹眼部分)以及 3D 場景:

dm = new ht.DataModel();
g3d = new ht.graph3d.Graph3dView(dm);
g3d.setGridVisible(true); //指定是否顯示網格
g3d.setGridColor('#74AADA'); //指定網格線顏色
g3d.getView().className = 'main'; //設定類名
document.body.appendChild(g3d.getView()); //將3d元件新增進body體中
window.addEventListener('resize', function(e) {
    g3d.invalidate();
}, false);

g2d = new ht.graph.GraphView(dm);
g2d.setAutoScrollZone(-1); //設定自動滾動區域大小,當滑鼠距離拓撲邊緣小於這個值時,拓撲自動滾動(調整translateX或translateY)
g2d.getView().className = 'g2d';
g2d.setEditable(true); //設定拓撲中的圖元是否可編輯
document.body.appendChild(g2d.getView());
ht.Default.callLater(g2d.fitContent, g2d, [true, 50, true]); //獲取全域性下一個id編號

g3d.setHeadlightRange(2000); //燈影響範圍,預設為0代表可照射到無窮遠處,如果設定了值則光照射效果隨物體遠離光影而衰減

所有HT元件最根層都為一個 div 元件,可通過元件的 getView() 函式獲得,這裡就是利用這種方法將 3D 和 2D 元件新增進 body 體中的。只要 3D 和 2D 共用同一個資料容器,那麼資料容器中的圖元都是共用的,也就是說只要我們排布好 2D 或者 3D 中的圖元,那麼剩下的那個元件中圖元的排布以及樣式都是根據排布好的元件來排布的。

2. 新增燈光

場景中出現的燈光,除了會旋轉的燈光,還有就是兩個靜止的紅燈和黃燈,當旋轉的燈光照向其他地方的時候看得比較清楚:

redLight = new ht.Light(); //燈類
redLight.p3(0, 0, -175); //例項變數的位置
redLight.s({
    'light.color': 'red', //燈光顏色
    'light.range': 400 //燈影響範圍,預設為0代表可照射到無窮遠處,如果設定了值則光照射效果隨物體遠離光影而衰減
});
dm.add(redLight); //將例項變數新增進資料容量中

rotateLight = new ht.Light();
rotateLight.s({
    'light.color': 'green',
    'light.type': 'spot' //預設為point點光燈,可設定為spot聚光燈,以及directional的方向光型別
});
dm.add(rotateLight);

yellowLight = new ht.Light();
yellowLight.p3(0, 0, 60);
yellowLight.s({
    'light.color': 'yellow',
    'light.range': 200
});
dm.add(yellowLight);

3. 場景中模型的構建

首先是地板的建立,地板是一個圓形的地板,通過設定樣式 shape3d 為 cylinder,剩下的只要設定好大小、位置以及樣式等等即可:

floor = new ht.Node(); //Node 節點類
floor.s3(1100, 10, 1100);
floor.p3(0, -100, -110);
floor.s({
    'shape3d': 'cylinder', //設定 3D 模型為圓形
    'shape3d.side': 100, //預設值為0,決定3d圖形顯示為幾邊型,為0時顯示為平滑的曲面效果
    'shape3d.color': 'white', //預設值為#3498DB,3d圖形整體顏色
    '3d.selectable': false, //預設值為true,控制圖元在Graph3dView上是否可選中
    '2d.visible': false //預設值為true,控制圖元在GraphView上是否可見
});
dm.add(floor);

接著新增地板外圍的 8 根圓柱:

for(var i=0; i<8; i++){
    var angle = Math.PI*2*i/8;
          pillar = new ht.Node();
    pillar.s({
        'shape3d': 'cylinder',
         'shape3d.color': 'white',
         'shape': 'circle', //多邊形型別圖元,為空時顯示為圖片
         'shape.background': 'gray' //多邊形型別圖元背景
    });
    pillar.s3(50, 180, 50);
    pillar.p3(Math.cos(angle)*480, 0, -110+Math.sin(angle)*480);
    dm.add(pillar);
}

還有就是這些“箭頭”作為貼圖的模型,各種各樣的,這裡我就只解析一個,比較靠前的“波動”部分,具體的多邊形的描述請參考形狀手冊

圖片描述

其中 image 的部分是通過 ht.Default.setImage 函式來建立的名為 arrow 的貼圖。

shape3 = new ht.Shape(); //多邊形類
dm.add(shape3);
shape3.setTall(60); //設定高度
shape3.setThickness(0); //設定厚度
shape3.s({ //設定樣式
    'shape.background': null,
    'shape.border.width': 10, //多邊形型別圖元邊框寬度
    'shape.border.color': 'blue',

    'all.visible': false, //六面是否可見
    'front.visible': true,
    'front.blend': 'blue', //前面染色顏色
    'front.reverse.flip': true, //前面的反面是否顯示正面的內容
    'front.image': 'arrow', //前面貼圖
    'front.uv.scale': [16, 3] //前面貼圖的uv縮放,格式為[3,2]
});
shape3.setPoints([ //設定點陣列
    {x: 0, y: 0},
    {x: 25, y: -25},
    {x: 50, y: 0},
    {x: 75, y: 25},
    {x: 100, y: 0},
    {x: 125, y: -25},
    {x: 150, y: 0},
    {x: 175, y: 25},
    {x: 200, y: 0}
]);
shape3.setSegments([ //描述點連線樣式
    1, // moveTo
    3, // quadraticCurveTo
    3, // quadraticCurveTo
    3, // quadraticCurveTo
    3 // quadraticCurveTo
]);
shape3.p3(-100, 0, 100);
shape3.setRotationZ(-Math.PI/2); //設定圖元在3D拓撲中沿z軸的旋轉角度(弧度制)

4. 設定定時器使各個模型中的圖片“流動”以及旋轉燈光的旋轉

offset = 0;
angle = 0;
setInterval(function(){
    angle += Math.PI/50;
    rotateLight.p3(400*Math.cos(angle), 70, -110+400*Math.sin(angle)); //設定旋轉燈光的座標

    offset += 0.1;
    uvOffset = [offset, 0];
    shape1.s({
        'front.uv.offset': uvOffset //前面貼圖的uv縮放,格式為[3,2]
    });
    shape2.s({
        'front.uv.offset': uvOffset
    });
    shape3.s({
        'front.uv.offset': uvOffset
    });
    shape4.s({
        'front.uv.offset': uvOffset
    });
    shape5.s({
        'shape3d.uv.offset': uvOffset, //決定3d圖形整體貼圖的uv縮放,格式為[3,2]
        'shape3d.top.uv.offset': uvOffset, //決定3d圖形頂面貼圖的uv縮放,格式為[3,2]
        'shape3d.bottom.uv.offset': uvOffset //決定3d圖形底面貼圖的uv縮放,格式為[3,2]
    });
    cylinder.s({
        'shape3d.uv.offset': uvOffset
    });
    torus.s({
        'shape3d.uv.offset': uvOffset
    });
}, 200);

總結

整個例子結束,感覺就是“小程式碼大效果”,程式碼量少而且簡單,效果又非常不錯,大家有興趣可以去官網或者手冊中檢視其它的例子。

相關文章