• 加载和使用纹理


        加载和使用纹理需要了解以下几个方面:在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就是我们上面创建的视频纹理。

  • 相关阅读:
    迭代器
    关于文章cisco漏洞4786
    Python学习目录
    Python的xml模块
    【JS30】Event Capture, Propagation, Bubbling and Once
    mysql安装
    CS193P 第四课笔记 · Hexo
    CSS变量
    在CentOS7上开启和挂载NFS共享
    《Android 编程实战》Chap5_重识IPC
  • 原文地址:https://www.cnblogs.com/w-wanglei/p/6766220.html
Copyright © 2020-2023  润新知