加載和使用紋理


    加載和使用紋理需要了解以下幾個方面:在Three.js里加載紋理並應用到網格上;使用凹凸貼圖和法線貼圖為網格添加深度和細節;使用光照貼圖創建假陰影;使用環境貼圖在材質上添加反光細節;使用光亮貼圖,讓網格的某些部分變得“閃亮”;通過修改網格的UV貼圖,對貼圖進行微調;將HTML5畫布和視頻元素作為紋理輸入。本章節將會從以上幾方面來了解紋理的使用。

1.使用凹凸貼圖創建皺紋

    之前我們學習了THREE.MeshPhongMaterial對象的map屬性,知道它用來設置外部資源作為材質的紋理。這里再介紹它的bumpMap屬性,用來實現凹凸貼圖效果。代碼和創建不同紋理一樣,僅僅多個bumpMap屬性的設置。代碼如下:

function createMesh(geom, imageFile, bump){
                var texture = THREE.ImageUtils.loadTexture("../assets/textures/general/" + imageFile);
                var material = new THREE.MeshPhongMaterial({
                    map: texture
                });

                if(bump){
                    var bumpTex = THREE.ImageUtils.loadTexture("../assets/textures/general/" + bump);
                    material.bumpMap = bumpTex;
                }

                var mesh = new THREE.Mesh(geom, material);

                return mesh;
            }

    createMesh函數用來創建包含外部資源作為紋理的網格,第三個參數bump就是我們的凹凸貼圖的圖片名稱,如果該名稱不為空,則加載凹凸貼圖並設置到bumpMap屬性。

2.使用法向量貼圖創建更加細致的凹凸和皺紋

    和使用凹凸貼圖非常相似,區別在於法向量設置的是材質的normalMap屬性,而凹凸貼圖設置的是bumpMap屬性。使用法向貼圖的問題時不容易創建。你要使用特殊的工具,例如Blender和Photoshop。它們可以將高度解析的渲染結果或圖片作為輸入,從中創建出法向的貼圖。

3.使用光照貼圖創建假陰影

    光照貼圖是預先渲染好的陰影,你可以用它來模擬真實的陰影。光照陰影其實是事先准備好的陰影圖片。例如:

image

    你可以用這種技術創建出解析度很高的陰影,而且不會損害渲染的性能。當時只能使用在靜態場景。光照貼圖的使用跟其他紋理基本一樣,只有幾處小小的不同:

var groundGeom = new THREE.PlaneGeometry(95, 95, 1, 1);
        var lm = THREE.ImageUtils.loadTexture("../assets/textures/lightmap/lm-1.png");
        var wood = THREE.ImageUtils.loadTexture("../assets/textures/general/floor-wood.jpg");
        var groundMaterial = new THREE.MeshBasicMaterial({
            map: wood,
            color: 0x777777,
            lightMap: lm,
        });

        groundGeom.faceVertexUvs[1] = groundGeom.faceVertexUvs[0];

    應用貼圖時,只要將材質的lightMap屬性設置成剛才所示的紋理即可。但是要講光照貼圖顯示出來,我們需要為光照貼圖明確指定UV映射(將紋理的那一部分應用到表面)。只有這樣才能將光照貼圖與其他紋理獨立開來。設置代碼如下:

groundGeom.faceVertexUvs[1] = groundGeom.faceVertexUvs[0];

    下面的地址詳細解釋了為什么需要明確指定UV映射:

http://stackoverflow.com/questions/15137695/three-js-lightmap-causes-an-error-webglrenderingcontext-gl-error-gl-invalid-op

4.用環境貼圖創建虛假的反光效果

    計算環境反射光非常耗費CPU,而且通常會使用光線追蹤算法。如果你想在Three.js里邊使用反光,你可以做,但是你不得不做一個假的。要創建一個這樣的場景,需要執行以下步驟:

    1)創建一個CubeMap對象:我們首先需要創建一個CubeMap對象。一個CubeMap是有6個紋理的集合,而這些紋理可以應用到方塊的每個面上。

    2)創建一個帶有這個CubeMap對象的方塊:帶有CubeMap對象的方塊就是移動相機時你所看到的環境。你可以在你想四周看時制造一種身臨其境的感覺。

    3)將CubeMap作為紋理:我們用來模擬環境的CubeMap對象也可以用來做網格的紋理。Three.js會讓它看上去像是環境的反光。

    創建CubeMap對象,需要六張用來構建整個場景的額圖片。圖片分別是朝前的(posz)、朝后的(negz)、朝上的(posy)、朝下的(negy)、朝右的(posx)、朝左的(negx)。圖片有了,你就可以像相面這樣加載它們:

function createCubeMap(){
                var path = "../assets/textures/cubemap/parliament/";
                var format = ".jpg";
                var urls = [
                        path + "posx" + format, path + "negx" + format,
                        path + "posy" + format, path + "negy" + format,
                        path + "posz" + format, path + "negz" + format
                ];

                var textureCube = THREE.ImageUtils.loadTextureCube(urls, new THREE.CubeReflectionMapping());
                return textureCube;
            }

    這里我們用到了Three.ImageUtils的loadTextureCube函數,創建一個方塊紋理textureCube。接下來我們需要創建一個方塊作為我們的所看到的環境(看到的是方塊的內部):

var textureCube = createCubeMap();
            var shader = THREE.ShaderLib["cube"];
            shader.uniforms["tCube"].value = textureCube;
            var material = new THREE.ShaderMaterial({
                vertexShader: shader.vertexShader,
                fragmentShader: shader.fragmentShader,
                uniforms: shader.uniforms,
                depthWrite: false,
                side: THREE.BackSide
            });

            var cubeMesh = new THREE.Mesh(new THREE.BoxGeometry(100, 100, 100), material);
            sceneCube.add(cubeMesh);

    Three.js提供了一個特別的着色器(Three.ShaderLib[“cube”]),結合THREE.ShaderMaterial類,我們可以基於CubeMap對象創建一個環境。我們用CubeMap配置這個着色器。

    同一個CubeMap對象可以應用到某個網格上,用來創建虛假的放光:

var sphere1 = createMesh(new THREE.SphereGeometry(10, 15, 15), "plaster.jpg");
            sphere1.material.envMap = textureCube;
            sphere1.rotation.y = -0.5;
            sphere1.position.y = 5;
            sphere1.position.x = 12;
            scene.add(sphere1);

            var sphere2 = createMesh(new THREE.BoxGeometry(10, 15, 15), "plaster.jpg", "plaster-normal.jpg");
            sphere2.material.envMap = textureCube;

            sphere2.rotation.y = 0.5;
            sphere2.position.x = -12;
            sphere2.position.y = 5;
            scene.add(sphere2);

    我們將材質頂點evnMap屬性設置為我們創建的cubeMap對象,結果看上去好像我們站在一個寬闊的室外環境中,而且這些網格上回映射環境。

5.使用CubeCamera模擬反光

    CubeCamera一般都結合包含有CubeMap的虛假環境使用。用來作為某個物體的反光使用。例如下圖是一個用6個面CubeMap作為紋理的6面盒子環境。我想要中間的球實現動態的環境反射,旋轉場景,球中可以看到左右兩個網格的投影。

image

    實現代碼如下,代碼創建了一個CubeCamera對象,模型position是(0, 0, 0)。后面再創建sphere的時候我們使用的紋理時dynamicEnvMaterial材質,該材質的envMap是從cubeCamera.renderTaget取紋理。cubeCamera的renderTarget實際就是這個攝像頭向四周看到的環境。直接用到sphere上,感覺就像是sphere反光的效果。

cubeCamera = new THREE.CubeCamera(0.1, 20000, 256);
            scene.add(cubeCamera);

            var sphereGeometry = new THREE.SphereGeometry(4, 15, 15);
            var boxGeometry = new THREE.BoxGeometry(5, 5, 5);
            var cylinderGeometry = new THREE.CylinderGeometry(2, 4, 10 ,20, 20, false);

            var dynamicEvnMaterial = new THREE.MeshBasicMaterial({
                envMap: cubeCamera.renderTarget,
                side: THREE.DoubleSide
            });
            var envMaterial = new THREE.MeshBasicMaterial({
                envMap: textureCube, side: THREE.DoubleSide
            });

            sphere = new THREE.Mesh(sphereGeometry, dynamicEvnMaterial);
            sphere.name = "sphere";
            scene.add(sphere);

            var cylinder = new THREE.Mesh(cylinderGeometry, envMaterial);
            cylinder.name = "cylinder";
            cylinder.position.set(10, 0, 0);
            scene.add(cylinder);

    每次渲染 的時候我們還得去調用CubeCamera的updateCubeMap函數更新渲染。但在渲染時記得把球隱藏掉,不然就看不到反射了。

function render(){
                orbit.update();

                sphere.visible = false;
                cubeCamera.updateCubeMap(renderer, scene);
                sphere.visible = true;

                renderer.render(scene, camera);
                scene.getObjectByName("cube").rotation.x += control.rotationSpeed;
                scene.getObjectByName("cube").rotation.y += control.rotationSpeed;
                scene.getObjectByName("cylinder").rotation.x += control.rotationSpeed;

                requestAnimationFrame(render);
            }

7.定制UV映射

    通過UV映射你可以指定文理的哪部分顯示在物體表面上。多數情況下,你不必修改默認的UV映射。UV映射的定制一般是在諸如Blender這樣的軟件中完成的,特別是當模型變得復雜時。這里需要記住的是UV映射有兩個維度,U和V,取值范圍是0到1.定制UV映射時,你需要為物體的每個面指定其需要顯示文理的哪個部分。為此你要為構成面的每個頂點指定u和v坐標。下面是一段加載文理的代碼:

this.loadCube1 = function(){
    var loader = new THREE.OBJLoader();
    loader.load("../assets/models/UVCube1.obj", function(object){
        if(mesh) scene.remove(mesh);
        var material = new THREE.MeshBasicMaterial({
        color: 0xffffff
        });
        material.map = THREE.ImageUtils.loadTexture("../assets/textures/ash_uvgrid01.jpg");

        object.children[0].material = material;
        mesh = object;

        object.scale.set(15, 15, 15);

        scene.add(mesh);
    });
}

8.重復映射

    當你在Three.js幾何體上創建文理的時候,Three.js會盡量做到最優。例如,對於方塊,Three.js會在每個面上顯示完整的文理。但有些情況,你可能不想講文理遍布整個面或整個幾何體,而是讓文理自己重復。Three.js提供了一些功能可以實現這種控制。
在用這個屬性達到所需的效果之前,你需要保證將文理的包裹屬性設置為THREE.RepeatWrapping。例如:

cube.material.map.wrapS = THREE.RepeatWrapping;
cube.material.map.wrapT = THREE.RepeatWrapping;

    wrapS定義了文理沿x軸方向的行為,而wrapT定義文理沿y軸方向的行為。Three.js提供了如下兩個選項:
    TTREE.RepeatWrapping 允許文理重復自己
    THREE.ClampToEdgeWrapping是默認設置。如果是THREE.ClampToEdgeWrapping,那么文理邊緣像素會被拉伸,以填滿剩下的空間。
    如果使用THREE.RepeatWraping,我們可以用下面的代碼來設置repeat屬性:

cube.material.map.repeat.set(controls.repeatX, controls.repeatY);
sphere.material.map.repeat.set(controls.repeatX, controls.repeatY);

    controls.repeatX變量指定文理在x軸方向多久重復一次,而變量controls.repeatY指定文理在y軸方向多久重復一次。如果設置為1,則文理不會重復;如果設置成大一點的值,你就會看到文理開始重復。你也可以將值設置成小於1.如果是這樣,你就會看到紋理被放大了。如果將這個值設置成負數,那么會產生一個文理的鏡像。
    當你修改repeat屬性,Three.js會自動更新文理,並用新的設置進行渲染。但如果你把Three.RepeatWrapping改成THREE.ClampToEdgeWrapping,你要明確更新紋理:

cube.material.map.needsUpdate = true;

    下面是一個使用紋理重復的示例代碼:

var sphere = createMesh(new THREE.SphereGeometry(5, 20, 20), "floor-wood.jpg");
scene.add(sphere);
sphere.position.x = 7;

var cube = createMesh(new THREE.BoxGeometry(5, 5, 5), "brick-wall.jpg");
cube.position.x = -7;
scene.add(cube);

var ambientLight = new THREE.AmbientLight(0x141414);
scene.add(ambientLight);

var light = new THREE.DirectionalLight();
light.position.set(0, 30, 20);
scene.add(light);

render();

function createMesh(geom, textureName){
    var texture = THREE.ImageUtils.loadTexture("../assets/textures/general/" + textureName);
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapS = THREE.RepeatWrapping;

    geom.computeVertexNormals();

    var mat = new THREE.MeshPhongMaterial({map: texture});

    var mesh = new THREE.Mesh(geom, mat);

    return mesh;
    }

    var step = 0;

    function render(){
    stats.update();
    step += 0.01;
    cube.rotation.y = step;
    cube.rotation.z = step;
    sphere.rotation.y = step;
    sphere.rotation.z = step;

    requestAnimationFrame(render);
    webGLRenderer.render(scene, camera);
}

9.用畫布作為紋理

    在介紹如何使用之前,先介紹個畫布工具,我們這里使用literally庫(http://literallycanvas.com)創建一個交互時畫布,你可以再上面繪圖。界面如下:

image

    首先我們創建一個畫布元素,然后配置該畫布使用literally庫:

<div class="fs-container">
        <div id="canvas-output" style="float:left">
        </div>
    </div>
...
  var canvas = document.createElement("canvas");
        document.getElementById("canvas-output").appendChild(canvas);
        $("#canvas-output").literallycanvas({imageURLPrefix: "../libs/literally/img"});

    我們使用Javascript創建了一個canvas畫布,並將它添加到指定的div元素中。通過調用literallycanvas我們可以創建一個繪圖工具。接下來我們要將畫布上的繪制結果作為輸入創建一個紋理:

function createMesh(geom){
                var canvasMap = new THREE.Texture(canvas);
                var mat = new THREE.MeshPhongMaterial();
                mat.map = canvasMap;

                var mesh = new THREE.Mesh(geom, mat);
                return mesh;
            }

    代碼唯一要做的就是在創建紋理時把canvas對象傳遞給紋理構造器。浙江就可以把畫布作為紋理來源。剩下要做的就是在渲染時更新材質,這樣畫布上最新的內容才會顯示在方塊上:

function render(){
                stats.update();

                cube.rotation.y += 0.01;
                cube.rotation.x += 0.01;

                cube.material.map.needsUpdate = true;
                requestAnimationFrame(render);
                webGLRenderer.render(scene, camera);
            }

10.用畫布作凹凸貼圖

    我們可以使用凹凸貼圖創建簡單的有皺紋的紋理。貼圖像素的密集程度越高,貼圖看上去越皺。我們也可以使用畫布上的畫圖作為貼圖。我們可以在畫布上隨機生成一副灰度圖,並將該圖作為方塊上的凹凸貼圖的輸入。

    這里介紹一個用一些隨機噪音填充畫布的庫,叫做Perlin噪音。Perlin噪音(http://en.wikipedia.org/wiki/Perlin_noise)可以產生看上去非常自然的隨機紋理,如下圖所示:

    image

    我們可以使用http://github.com/wwwtyro/perlin.js中的Perlin噪音函數如下所示:

function fillWidthPerlin(pn, ctx){
            for(var x = 0; x < 512; x++){
                for(var y = 0; y < 512; y++){
                    var base = new THREE.Color(0xffffff);
                    var value = pn.noise(x/10, y/10, 0);
                    base.multiplyScalar(value);
                    ctx.fillStyle = "#" + base.getHexString();
                    ctx.fillRect(x, y, 1, 1);
                }
            }
        }

    我們使用perlin.noise函數在畫布x坐標和y坐標的基礎上生成一個0到1之間的值。該值可以從來在畫布上畫一個像素點。可以用這個方法生成所有的像素點其結果如上圖所示。生成后直接使用這個canvas即可:

function createMesh(geom){
                var bumpMap = new THREE.Texture(canvas);

                geom.computeVertexNormals();
                var mat = new THREE.MeshPhongMaterial();
                mat.color = new THREE.Color(0x77ff77);
                mat.bumpMap = bumpMap;
                bumpMap.needsUpdate = true;

                var mesh = new THREE.Mesh(geom, mat);
                return mesh;
            }

10.使用視頻輸出作為紋理

    Three.js直接致辭HTML5視頻元素作為紋理。直接使用THREE.VideoTexture(videoElement)即可。如下面的代碼使用了一個video元素直接作為紋理輸出:

var video = document.getElementById("video");
        texture = new THREE.VideoTexture(video);

    由於視頻不是正方形,所喲要保證材質不會生成mipmap。由於材質變化的很頻繁,所以我們還需要設置簡單高效的過濾器。

texture.minFilter = THREE.LinearFilter;
        texture.magFilter = THREE.LinearFilter;
        texture.format = THREE.RGBFormat;
        texture.generateMipmaps = false;

    接下來可以直接使用這個紋理作為材質的map:

function createMesh(geom){
            var materialArray = [];
            materialArray.push(new THREE.MeshBasicMaterial({color: 0x0051ba}));
            materialArray.push(new THREE.MeshBasicMaterial({color: 0x0051ba}));
            materialArray.push(new THREE.MeshBasicMaterial({color: 0x0051ba}));
            materialArray.push(new THREE.MeshBasicMaterial({color: 0x0051ba}));
            materialArray.push(new THREE.MeshBasicMaterial({map: texture}));
            materialArray.push(new THREE.MeshBasicMaterial({color: 0x0051ba}));

            var faceMaterial = new THREE.MeshFaceMaterial(materialArray);

            var mesh = new THREE.Mesh(geom, faceMaterial);

            return mesh;
        }

     代碼創建了六個材質的數組,作為THREE.MeshFaceMaterial對象的構造產生,假如我們使用的是BoxGeometry,那么剛好對應六個面。第五個面的材質是:new THREE.MeshBasicMaterial({map: texture})。texture就是我們上面創建的視頻紋理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM