1. 程式人生 > >WebGL three.js學習筆記 6種類型的紋理介紹及應用

WebGL three.js學習筆記 6種類型的紋理介紹及應用

透視投影 qdt 高度 我們 幾何 區別 set 亮度 cubemap

WebGL three.js學習筆記 6種類型的紋理介紹及應用

本文所使用到的demo演示:

高光貼圖Demo演示

反光效果Demo演示(因為是加載的模型,所以速度會慢)

(一)普通紋理

計算機圖形學中的紋理既包括通常意義上物體表面的紋理即使物體表面呈現凹凸不平的溝紋,同時也包括在物體的光滑表面上的彩色圖案,所謂的紋理映射就是在物體的表面上繪制彩色的圖案。

在three.js中使用紋理可以實現很多不同的效果,但是最基本的就是為網格體的每個像素指定顏色。等同於將一張紋理圖片應用在一個幾何體的材質上。

使用的方式很簡單,只需要設置
material.map = 需要設置的紋理對象
紋理對象的獲得方式也很簡單,只需要使用THREE.TextureLoader().load(url)函數就可以為url指定路徑的紋理圖片創建一個對象。具體的使用方式如下:

        let texture = new THREE.TextureLoader().load("../../../Image/metal-rust.jpg");
        let material = new THREE.MeshBasicMaterial();
        material.map = texture;
        let geometry = new THREE.BoxGeometry(10,10,10);
        let cube = new THREE.Mesh(geometry,material);
        scene.add(cube);

其中,"../../../Image/metal-rust.jpg"是我使用的紋理的路徑,圖片就是下面這一張
技術分享圖片
創建出來的帶有上圖紋理的cube就是這樣的
技術分享圖片
除了THREE.TextureLoader()這個加載器以為,three.js還為我們提供了其他自定義的加載器,如dds格式,pvr格式,tga格式。

就拿tga格式舉例,我們要加載tga格式的紋理,首先需要引用TGALoader.js這個文件,然後創建一個tga格式的加載器
let loader = new THREE.TGALoader();
我們就可以使用loader這個加載器,像上面一樣的加載tga格式的紋理了。
具體代碼如下:

        let loader = new THREE.TGALoader();
        let texture = loader.load("../../../Image/crate_color8.tga");
        let material = new THREE.MeshBasicMaterial();
        material.map = texture;
        let geometry = new THREE.BoxGeometry(10,10,10);
        let cube = new THREE.Mesh(geometry,material);
        scene.add(cube);

下面是我使用的tga格式的紋理圖片(只能上傳截圖,tga格式圖片的這裏上傳不了)
技術分享圖片
運行出來是這個樣子的
技術分享圖片
其他格式的加載也是和tga格式加載方法一樣的,只需要引入相應的js文件就可以使用了。

(二)凹凸貼圖

凹凸紋理用於為材質添加厚度與深度,如字面意思一樣,可以讓材質看上去是凹凸不平的。凹凸貼圖只包含像素的相對高度,像素的密集程度定義凹凸的高度,所以想要讓物體好看,首先還是應該設置一個普通的紋理,再在這個基礎上添加一個凹凸紋理,就可以實現凹凸不平的物體效果。
凹凸貼圖的創建方法很簡單,和普通紋理類似,只是我們設置的不是map,而是bumpMap
material.bumpMap = 需要設置的紋理對象

特別需要註意的是,這裏的材質只能使用MeshPhongMaterial,凹凸貼圖才會有效果。
具體的設置方法如下:

        let geom = new THREE.BoxGeometry(10, 10, 10);
        
        //創建普通紋理材質
        let texture = new THREE.TextureLoader().load("../../../Image/stone.jpg");
        let material = new THREE.MeshPhongMaterial({
            map:texture
        });
        cube = new THREE.Mesh(geom,material);
        cube.position.set(-7,0,0);
        scene.add(cube);

        //創建凹凸紋理材質
        let bumpTexture = new THREE.TextureLoader().load("../../../Image/stone-bump.jpg");
        let bumpMaterial = new THREE.MeshPhongMaterial({
            map:texture,
            bumpMap:bumpTexture,
            bumpScale:2
        });
        bumpCube = new THREE.Mesh(geom,bumpMaterial);
        bumpCube.position.set(7,0,0);
        scene.add(bumpCube);

其中material.bumpScale可以設置凹凸的高度,如果為負值,則表示的是深度。

運行程序截圖如下:
左邊材質的是普通的紋理貼圖,右邊的材質是帶有凹凸紋理的,當前bumpScale設置的是2,兩者看上去有比較明顯的不同
技術分享圖片
使用的紋理圖片如下:
技術分享圖片
凹凸紋理圖片:
技術分享圖片

我們可以發現,凹凸圖只包含了像素的相對高度,沒有任何的傾斜的方向信息,所以使用凹凸紋理能表達的深度信息有限,如果想用實現更多的細節可以使用下面介紹的法向貼圖。

(三)法向貼圖

法向貼圖保存的不是高度的信息,而是法向量的信息,我們使用法向貼圖,只需要很少的頂點和面就可以實現很豐富的細節。
同樣的,實現法向貼圖和凹凸貼圖也很類似,只需要設置
material.normalMap = 需要設置的紋理對象

同樣也是在MeshPhongMaterial材質中才有效果,還要註意的一點是設置normalScale指定材質的凹凸程度時,normalScale需要接受的是一個THREE.Vector2類型

具體的代碼如下:

        let geom = new THREE.BoxGeometry(10, 10, 10);

        //創建普通紋理材質
        let texture = new THREE.TextureLoader().load("../../../Image/plaster.jpg");
        let material = new THREE.MeshPhongMaterial({
            map:texture
        });
        cube = new THREE.Mesh(geom,material);
        cube.position.set(-7,0,0);
        scene.add(cube);

        //創建凹凸紋理材質
        let normalTexture = new THREE.TextureLoader().load("../../../Image/plaster-normal.jpg");
        let normalMaterial = new THREE.MeshPhongMaterial({
            map:texture,
            normalMap:normalTexture,
            normalScale:new THREE.Vector2(1,1)
        });
        normalCube = new THREE.Mesh(geom,normalMaterial);
        normalCube.position.set(7,0,0);
        scene.add(normalCube);

場景如下圖,右邊的是帶有法向紋理的物體,明顯感覺出材質的細節多出來了很多。
技術分享圖片
用到的紋理圖
技術分享圖片
法向紋理圖:
技術分享圖片
雖然法向紋理能帶給物體更逼真的效果,但是想要創建法向紋理圖,本身就比較困難,需要ps或者blender這樣的特殊工具。

(四)光照貼圖

如果我們想在場景中添加陰影,three.js給我們提供了renderer.shadowMapEnabled = true這個辦法,但是這對於資源的消耗是很大的。如果我們只是需要對靜態的物體添加陰影效果,我們就有一種開銷很小的辦法,那就是光照貼圖。
光照貼圖是預先渲染好的陰影貼圖,可以用來模擬真實的陰影。我們能使用這種技術創建出分辨率很高的陰影,並且不會損耗渲染的性能。因為是提前根據場景渲染好的,所以只對靜態的場景有效。

比如下面這張光照貼圖:
技術分享圖片
設置光照貼圖的方式很簡單,只需要設置
material.lightMap = 需要設置的紋理對象
和前面兩個沒什麽太大的區別。當紋理設置好以後,我們還需要把我們的物體擺放在正確的位置,這樣陰影效果才會真實的顯現出來。

        let lightMap = new THREE.TextureLoader().load("../../../Image/lm-1.png");
        let map =  new THREE.TextureLoader().load("../../../Image/floor-wood.jpg");
        //創建地板
        let planeGeo = new THREE.PlaneGeometry(95,95,1,1);
        planeGeo.faceVertexUvs[1] = planeGeo.faceVertexUvs[0];
        let planeMat = new THREE.MeshBasicMaterial({
            color:0x999999,
            lightMap:lightMap,//在地板的材質上添加光照貼圖
            map:map//地板的普通紋理材質
        });
        let plane = new THREE.Mesh(planeGeo,planeMat);
        plane.rotation.x = -Math.PI / 2;
        plane.position.y = 0;
        scene.add(plane);

        //創建大的cube
        var boxGeo = new THREE.BoxGeometry(12,12,12);
        var material = new THREE.MeshBasicMaterial();
        material.map = new THREE.TextureLoader().load("../../../Image/stone.jpg");
        var box = new THREE.Mesh(boxGeo,material);
        box.position.set(0.9,6,-12);
        scene.add(box);

        //創建小的cube
        var boxGeo = new THREE.BoxGeometry(6, 6, 6);
        var material = new THREE.MeshBasicMaterial();
        material.map = new THREE.TextureLoader().load("../../../Image/stone.jpg");
        var box = new THREE.Mesh(boxGeo,material);
        box.position.set(-13.2, 3, -6);
        scene.add(box);

其中,planeGeo.faceVertexUvs[1] = planeGeo.faceVertexUvs[0] 這句話是我們需要明確的指定光照貼圖的uv映射(將紋理的哪一部分應用在物體表面)這樣才能將光照貼圖的使用和其他的紋理分別開來。
planeGeo.faceVertexUvs保存的就是幾何體面的uv映射信息,我們將faceVertexUvs[0]層的信息保存到faceVertexUvs[1]層

faceVertexUvs的官方文檔解釋:

.faceVertexUvs : Array
Array of face UV layers, used for mapping
textures onto the geometry. Each UV layer is an array of UVs matching
the order and number of vertices in faces.

運行結果如圖:
技術分享圖片

(五)高光貼圖

高光是光源照射到物體然後反射到人的眼睛裏時,物體上最亮的那個點就是高光,高光不是光,而是物體上最亮的部分。
而高光貼圖就是高光貼圖是反應光線照射在物體表面的高光區域時所產生的環境反射,它的作用是反映物體高光區域效果。

通過高光貼圖,我們可以為材質創建一個閃亮的、色彩明快的貼圖。高光貼圖的黑色部分會暗淡,而白色的部分會比較的亮。
創建高光貼圖的方法也和前面差不多
material.specularMap= 需要設置的紋理對象

具體的代碼如下:

        let map = new THREE.TextureLoader().load("../../../Image/Earth.png");
        let specularMap = new THREE.TextureLoader().load("../../../Image/EarthSpec.png");
        let normalMap = new THREE.TextureLoader().load("../../../Image/EarthNormal.png");
        let sphereMaterial = new THREE.MeshPhongMaterial({
            map:map,
            specularMap:specularMap,
            normalMap:normalMap,
            normalScale:THREE.Vector2(2,2),
            specular:0x0000ff,
            shininess:2
        });
        let sphereGeometry = new THREE.SphereGeometry(30,30,30);
        let sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);
        scene.add(sphere);

這段代碼創建了一個球體,並為球體的材質貼上了普通紋理,法向紋理和高光紋理,其中specular屬性可以決定反光的顏色,shininess可以決定發光的亮度。

運行出來的樣子如下:
可以看到,海洋的地方比較亮,而大陸的的顏色相對較暗。
技術分享圖片

高光貼圖Demo演示

用到的幾張紋理圖:
技術分享圖片
高光紋理:
技術分享圖片
法向紋理:
技術分享圖片

(六)環境貼圖

如果我們想要在場景中創建反光的物體,通常會使用光線追蹤的算法,但是這對cpu的消耗是巨大的,但是環境貼圖就給我們創造了更容易的方法,我們只需要使用給物體的材質貼上環境貼圖,就可以模擬反光的效果。

首先我們的場景需要有一個環境,這個環境我們可以使用CubeTextureLoader()來創建。在前面的文章裏曾經介紹過如何創建360度全景的環境,這個CubeTextureLoader()和那裏面用到的其實是一樣的,只是版本的更替,現在更多使用這個函數。
具體用法是:

        let cubeMap = new THREE.CubeTextureLoader().setPath(
        "../../../Image/MapCube/Bridge2/").load(
            [
                'posx.jpg',
                'negx.jpg',
                'posy.jpg',
                'negy.jpg',
                'posz.jpg',
                'negz.jpg'
            ]);
        scene = new THREE.Scene();
        scene.background = cubeMap;

在前面的文章已經介紹過,這裏就不再贅述。
創建cubeMap所用到的圖片在http://www.humus.name/index.php?page=Textures可以直接下載。

我們有了一個可以反射的環境以後,就可以開始為我們的物體創建材質貼圖了。
創建材質貼圖的方式和前面還是差不多
material.envMap = scene.background;

scene.background就是我們剛剛所創建的場景的背景,這樣材質的環境貼圖就相當於貼上了周圍環境,從攝像機去看物體的話,看上去就是對環境有一個反射的效果了。

創建的代碼如下:

       function initObject()
       {
       let material = new THREE.MeshPhongMaterial();
       material.envMap = scene.background;
       let boxGeometry = new THREE.BoxGeometry(5,50,50);
       let box = new THREE.Mesh(boxGeometry,material);
       box.position.set(-70,0,-10);
       box.rotation.y-=Math.PI/2;
       scene.add(box);
       let sphereGeometry = new THREE.SphereGeometry(30,30,30);
       let sphere = new THREE.Mesh(sphereGeometry,material);
       sphere.position.set(70,0,-10);
       scene.add(sphere);
       }

和前面的代碼沒有太大的區別,這裏主要是創建了兩個物體,都使用的相同環境貼圖的材質。

運行的結果:
技術分享圖片
可以看到,這兩個物體都對環境有反射的效果。

值得註意的是,我們使用環境貼圖創建的材質僅僅靜態的環境貼圖。我們只能看到物體上面有周圍環境的反射,看不到物體對其他物體的反射。

如果我們要看到物體對其他物體的反射,我們可以使用一個新的對象——cubeCamera
創建cubeCamera的方法很簡單.
let cubeCamera = new THREE.CubeCamera(0.1, 2000, 2048);
scene.add(cubeCamera);

其中:
第一個參數0.1是相機的近裁剪距離
第二個參數2000是相機遠裁剪距離
第三個參數2048是相機分辨率

使用THREE.CubeCamera可以為場景中所要渲染的物體創建快照,並使用這些快照創建CubeMap對象。但是需要確保攝像機被放置在THREE.Mesh網格上你所想顯示反射的位置上。例如,我們想在球體的中心顯示反射,由於球體所處的位置是(0, 0, 0),所以我們沒有顯示的指定THREE.CubeCamera的位置。我們只是將動態反射應用於球體上,所以把它的envMap設置為cubeCamera.renderTarget
即material.envMap = cubeCamera.renderTarget;

簡單來說,就是把我們所要顯示反射的“鏡子”的material.envMap設置為cubeCamera.renderTarget,同時還要把cubeCamera的位置設置到鏡子的位置,cubeCamera.position.copy(鏡子.position);

代碼如下:

        let loader = new THREE.STLLoader();
        loader.load("../../../asset/LibertStatue.obj.stl",function (bufferGeometry)
        {
            let material = new THREE.MeshBasicMaterial();
            material.envMap=scene.background;
            obj = new THREE.Mesh(bufferGeometry,material);
            obj.scale.set(50,50,50);
            scene.add(obj);
        });//加載stl模型
       
        let cubeMaterial = new THREE.MeshPhongMaterial();
        cubeMaterial.envMap = cubeCamera.renderTarget;
        let boxGeometry = new THREE.BoxGeometry(3, 400, 400);
        let box = new THREE.Mesh(boxGeometry, cubeMaterial);
        box.position.set(0, 0, -300);
        box.rotation.y -= Math.PI / 2;
        scene.add(box);
        cubeCamera.position.copy(box.position);

這段代碼中,我們從外部加載了一個stl格式的模型,也可以就使用簡單的幾何體來演示。下面的一部分代碼就創建了可以反射的鏡子。

最後,我們還需要在render()中添加cubeCamera.update(renderer, scene)用cubeCamera進行渲染

function render()
    {
        if(obj) obj.rotation.y+=0.02;
        cubeCamera.update(renderer, scene);
        stats.update();
        renderer.clear();
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    }

運行後的情況如下:
技術分享圖片
我們可以看到我們加載的stl模型在我們創建的鏡子中反射出來了,並且會根據模型的移動,鏡子的反射也會自動變化。

反光效果Demo演示

以上就是介紹的全部類型的紋理。

反光效果demo的完整代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Environment Map</title>
    <script src="../../../import/three.js"></script>
    <script src="../../../import/stats.js"></script>
    <script src="../../../import/Setting.js"></script>
    <script src="../../../import/OrbitControls.js"></script>
    <script src="../../../import/STLLoader.js"></script>
    <style type="text/css">
        body {
            border: none;
            cursor: pointer;
            width: 100%;
            height: 1000px;
            /*全屏顯示的設置*/
            margin: 0;
            overflow: hidden; /*消除瀏覽器的滾動條*/

        }

        /*加載動畫*/
        #loading {
            width: 100%;
            height: 850px;
            background-color: #333333;
        }

        #spinner {
            width: 100px;
            height: 100px;
            position: fixed;
            top: 50%;
            left: 50%;
        }

        .double-bounce1, .double-bounce2 {
            width: 100%;
            height: 100%;
            border-radius: 50%;
            background-color: #67CF22;
            opacity: 0.6;
            position: absolute;
            top: 0;
            left: 0;
            -webkit-animation: bounce 2.0s infinite ease-in-out;
            animation: bounce 2.0s infinite ease-in-out;
        }

        .double-bounce2 {
            -webkit-animation-delay: -1.0s;
            animation-delay: -1.0s;
        }

        @-webkit-keyframes bounce {
            0%, 100% {
                -webkit-transform: scale(0.0)
            }
            50% {
                -webkit-transform: scale(1.0)
            }
        }

        @keyframes bounce {
            0%, 100% {
                transform: scale(0.0);
                -webkit-transform: scale(0.0);
            }
            50% {
                transform: scale(1.0);
                -webkit-transform: scale(1.0);
            }
        }
    </style>
</head>
<body onload="Start()">
<!--加載動畫的div-->
<div id="loading">
    <div id="spinner">
        <div class="double-bounce1"></div>
        <div class="double-bounce2"></div>
    </div>
</div>
<script>
    let camera, renderer, scene, cubeCamera, light;
    let controller;

    function initThree()
    {
        //渲染器初始化
        renderer = new THREE.WebGLRenderer({
            antialias: true
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x333333);
        document.body.appendChild(renderer.domElement);//將渲染添加到body中
        //初始化攝像機,這裏使用透視投影攝像機
        camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000);
        camera.position.set(20, 15, 200);
        camera.up.x = 0;//設置攝像機的上方向為哪個方向,這裏定義攝像的上方為Y軸正方向
        camera.up.y = 1;
        camera.up.z = 0;
        camera.lookAt(0, 0, 0);

        let cubeMap = new THREE.CubeTextureLoader().setPath("../../../Image/MapCube/Bridge2/").load(
            [
                'posx.jpg',
                'negx.jpg',
                'posy.jpg',
                'negy.jpg',
                'posz.jpg',
                'negz.jpg'
            ]);
        scene = new THREE.Scene();
        scene.background = cubeMap;

        cubeCamera = new THREE.CubeCamera(0.1, 1000, 2048);
        scene.add(cubeCamera);
        //相機的移動
        controller = new THREE.OrbitControls(camera, renderer.domElement);
        controller.target = new THREE.Vector3(0, 0, 0);

        light = new THREE.AmbientLight(0xffffff);
        light.position.set(-50, -50, -50);
        scene.add(light);
    }
    
     let obj;
    function initObject()
    {
        let loader = new THREE.STLLoader();
        loader.load("../../../asset/LibertStatue.obj.stl",function (bufferGeometry)
        {
            let material = new THREE.MeshBasicMaterial();
            material.envMap=scene.background;
            obj = new THREE.Mesh(bufferGeometry,material);
            obj.scale.set(50,50,50);
            scene.add(obj);
            console.log(obj);
        });
        let cubeMaterial = new THREE.MeshPhongMaterial();
        cubeMaterial.envMap = cubeCamera.renderTarget;
        let boxGeometry = new THREE.BoxGeometry(3, 400, 400);
        let box = new THREE.Mesh(boxGeometry, cubeMaterial);
        box.position.set(0, 0, -300);
        box.rotation.y -= Math.PI / 2;
        scene.add(box);
        cubeCamera.position.copy(box.position);
        document.getElementById('loading').style.display = 'none';
    }
    //渲染函數
    function render()
    {
        if(obj) obj.rotation.y+=0.02;
        cubeCamera.update(renderer, scene);
        stats.update();
        renderer.clear();
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    }

    //功能函數
    function setting()
    {
        loadFullScreen();
        loadAutoScreen(camera, renderer);
        loadStats();
    }

    //運行主函數
    function Start()
    {
        initThree();
        initObject();
        setting();
        render();
    }
</script>
</body>
</html>

WebGL three.js學習筆記 6種類型的紋理介紹及應用