Three.js [09] 纹理补充

html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>09</title>
    <link rel="stylesheet" type="text/css" href="09.css">
    <script src="https://threejsfundamentals.org/threejs/resources/threejs/r125/build/three.js"></script>
    <script src="https://threejsfundamentals.org/3rdparty/dat.gui.min.js"></script>
</head>
<body>
<canvas id="c"></canvas>
<script src="09.js"></script>
</body>
</html>

css

html, body {
    height: 100%;
    margin: 0;
}
#c {
    width: 100%;
    height: 100%;
    display: block;
}

js

/*
* 纹理
* 不是所有的几何体类型都支持多种材质
* BoxGeometry 可以使用 6 种材质,每个面一个
* ConeGeometry 可以使用 2 种材质,一个用于底部,一个用于侧面
* CylinderGeometry 可以使用 3 种材质,分别是底部,顶部和侧面
* 对于其他情况, 需要自定义几何体或者修改纹理坐标
*
* 纹理的加载
*
* 一、简单的方法: 先渲染几何体,等纹理加载完成后再渲染纹理
* 创建一个 TextureLoader 对象,调用它的 load 方法,这将返回一个 Texture 对象
*   const loader = new THREE.TextureLoader();
*   const texture = loader.load('resources/images/pic.jpg');
*   const material = new THREE.MeshBasicMaterial({map: texture});
*   const cube = new THREE.Mesh(geometry, material);
* 需要注意的是,使用 load 方法,我们的纹理将是透明的,直到图片被 three.js 异步加载完成,这时它将用图片更新纹理
* 这有一个很大的好处,就是我们不必等待纹理加载,我们的页面会立即开始渲染。
*
* 二、等待纹理加载: 先等纹理加载成功后,再一同渲染几何体
*   const loader = new THREE.TextureLoader();
*   loader.load('resources/images/pic.jpg', (texture) => {
*       const material = new THREE.MeshBasicMaterial({
*           map: texture;
*       });
*       const cube = new THREE.Mesh(geometry, material);
*       scene.add(cube);
*       cubes.push(cube);
*   });
* 为了等待贴图加载,贴图加载器的 load 方法会在贴图加载完成后调用一个回调
* 在 load 的回调函数中,我们再去创建几何体,以及添加到场景中,这样就能达到渲染几何体时,所有的纹理都已经加载好了
*
* 以上两种方法,除非清理了浏览器的缓存或者链接非常缓慢,否则看不到很大的差异
*
* 对于方法二,若要实现等待多个纹理加载结束后再渲染,则需要使用 LoadingManager。
*   const loadManager = new THREE.LoadingManager();
*   const loader = new THREE.TextureLoader(loadManager);
*   const materials = [
*       new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-1.jpg')}),
*       new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-2.jpg')}),
*       new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-3.jpg')}),
*       new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-4.jpg')}),
*       new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-5.jpg')}),
*       new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-6.jpg')}),
*   ];
*
*   // 将 loadManager 的 onLoad 属性设置为回调
*   loadManager.onLoad = () => {
*       const cube = new THREE.Mesh(geometry, materials);
*       scene.add(cube);
*       cubes.push(cube);  // 添加到我们要旋转的立方体数组中
*   };
*
*  LoadingManager 也有一个 onProgress 属性,我们可以设置为另一个回调来显示进度指示器。
* */

/*
* 内存管理
* 纹理往往是three.js应用中使用内存最多的部分。重要的是要明白,一般来说,纹理会占用 宽度 * 高度 * 4 * 1.33 字节的内存。
* 注意,这里没有提到任何关于压缩的问题。我可以做一个.jpg的图片,然后把它的压缩率设置的超级高。
* 比如说我在做一个房子的场景。在房子里面有一张桌子,我决定在桌子的顶面放上这个木质的纹理
* 图片只有157k,所以下载起来会比较快,但实际上它的大小是3024×3761像素.。按照上面的公式,那就是
* 3024 * 3761 * 4 * 1.33 = 60505764.5
* 在three.js中,这张图片会占用60兆(meg)的内存!。只要几个这样的纹理,你就会用完内存。
*
* 这个故事的寓意在于,不仅仅要让你的纹理的文件大小小,还得让你的纹理尺寸小。
* 文件大小小=下载速度快。尺寸小=占用的内存少。你应该把它们做得多小?越小越好,而且看起来仍然是你需要的样子。
* */

/*
* 过滤(filtering)
* GUP 如何知道每一个像素需要使用哪些颜色呢?   GUP 使用 mipmaps 解决该问题。
* Mips 是纹理的副本,每一个都是前一个 mip 的一半宽和一半高,其中的像素已经被混合以制作下一个较小的 mip。Mips一直被创建,直到我们得到1x1像素的Mip。
*
* 1、当纹理绘制的尺寸大于其原始尺寸时,可以设置 texture.magFilter 属性
*       THREE.NearestFilter : 意味着只需从原始纹理中选取最接近的一个像素。对于低分辨率的纹理,这给你一个非常像素化的外观
*       THREE.LinearFilter  : 是指从纹理中选择离我们应该选择颜色的地方最近的4个像素,并根据实际点与4个像素的距离,以适当的比例进行混合
*
* 2、当纹理绘制的尺寸小于其原始尺寸时, 可以设置 texture.minFilter 属性
*       THREE.NearestFilter : 在纹理中选择最近的像素
*       THREE.LinearFilter  : 从纹理中选择 4 个像素,并混合它
*       THREE.NearestMipmapNearestFilter    : 选择合适的mip,然后选择一个像素。
*       THREE.NearestMipmapLinearFilter     : 选择2个mips,从每个mips中选择一个像素,混合这2个像素。
*       THREE.LinearMipmapNearestFilter     : 选择合适的mip,然后选择4个像素并将它们混合。
*       THREE.LinearMipmapLinearFilter      : 选择2个mips,从每个mips中选择4个像素,然后将所有8个像素混合成1个像素。
*
* */

/*
* 纹理有重复,偏移和旋转的设置
*
* 1、重复,使用 texture.repeat.set(timesToRepeatHorizontally, timesToRepeatVertically); 设置
* 默认情况下,three.js 中纹理是不重复的
* 要设置纹理是否重复,有 2 个属性
*       wrapS  用于水平方向
*       wrapT  用于垂直方向
* 其值是下列的一个
*       THREE.ClampToEdgeWrapping   每条边上最后一个像素无限重复
*       THREE.RepeatWrapping        纹理重复,比如设置成该方式,且重复次数(timesToRepeat*)设置成2,则会在该平面画两个该纹理。
*       THREE.MirroredRepeatWrapping 每次重复时进行镜像
*
* 2、偏移,使用 texture.offset.set(xOffset, yOffset); 设置
* 偏移的显示效果也与 wrapS 和 wrapT 的值有关系,若是 ClampToEdgeWrapping 则偏移的部分就是像素拉长,若是 RepeatWrapping 则偏移的部分就是纹理的一部分
* 纹理的偏移是以纹理为单位的,即偏移 0 表示没有偏移, 偏移 1 表示偏移一个完整的纹理数量
*
* 3、旋转,
*   texture.center.set(.5, .5);     // 设置旋转中心
*   texture.rotation = THREE.MathUtils.degToRed(45);
* */

function main() {
    const canvas = document.querySelector('#c');

    const renderer = new THREE.WebGLRenderer({canvas});

    const fov = 75;
    const aspect = 2;
    const near = 0.1;
    const far = 5;
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.z = 2;

    const scene = new THREE.Scene();

    const boxWidth = 1;
    const boxHeight = 1;
    const boxDepth = 1;
    const boxGeometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

    const cubes = [];
    const loader = new THREE.TextureLoader();

    const texture = loader.load('https://threejsfundamentals.org/threejs/resources/images/wall.jpg');
    const material = new THREE.MeshBasicMaterial({map: texture});

    const cube = new THREE.Mesh(boxGeometry, material);
    scene.add(cube);
    cubes.push(cube);

    /*
    * 使用一个简单的类来给dat.GUI提供一个可以以度数为单位进行操作的对象,让它以弧度为单位设置该属性。
    * */
    class DegRadHelper {
        constructor (obj, prop) {
            this.obj = obj;
            this.prop = prop;
        }
        get value() {
            return THREE.MathUtils.radToDeg(this.obj[this.prop]);
        }
        set value(v) {
            this.obj[this.prop] = THREE.MathUtils.degToRad(v);
        }
    }

    /*
    * 字符串转数字,将 "123" 这样的字符串转换为 123 这样的数字,因为three.js的枚举设置需要数字,比如 wrapS 和 wrapT,但dat.GUI只使用字符串来设置枚举。
    * */
    class StringToNumberHelper {
        constructor (obj, prop) {
            this.obj = obj;
            this.prop = prop;
        }
        get value() {
            return this.obj[this.prop];
        }
        set value(v) {
            this.obj[this.prop] = parseFloat(v);
        }
    }

    /*
    * 纹理重复的类型
    * */
    const wrapModes = {
        'ClampToEdgeWrapping': THREE.ClampToEdgeWrapping,
        'RepeatWrapping': THREE.RepeatWrapping,
        'MirroredRepeatWrapping': THREE.MirroredRepeatWrapping,
    };

    function updateTexture() {
        texture.needsUpdate = true;
    }

    const gui = new dat.GUI();
    gui.add(new StringToNumberHelper(texture, 'wrapS'), 'value', wrapModes).name('texture.wrapS').onChange(updateTexture);
    gui.add(new StringToNumberHelper(texture, 'wrapT'), 'value', wrapModes).name('texture.wrapT').onChange(updateTexture);
    gui.add(texture.repeat, 'x', 0, 5, .01).name('texture.repeat.x');
    gui.add(texture.repeat, 'y', 0, 5, .01).name('texture.repeat.y');
    gui.add(texture.offset, 'x', -2, 2, .01).name('texture.offset.x');
    gui.add(texture.offset, 'y', -2, 2, .01).name('texture.offset.y');
    gui.add(texture.center, 'x', -.5, 1.5, .01).name('texture.center.x');
    gui.add(texture.center, 'y', -.5, 1.5, .01).name('texture.center.y');
    gui.add(new DegRadHelper(texture, 'rotation'), 'value', -360, 360).name('texture.rotation');

    function resizeRenderToDisplaySize(renderer) {
        const canvas = renderer.domElement;
        const pixelRatio = window.devicePixelRatio;
        const width = canvas.clientWidth * pixelRatio | 0;
        const height = canvas.clientHeight * pixelRatio | 0;
        const needResize = canvas.width !== width || canvas.height !== height;
        if (needResize) {
            renderer.setSize(width, height, false);
        }
        return needResize;
    }

    function render(time) {
        time *= 0.001;

        if (resizeRenderToDisplaySize(renderer)) {
            const canvas = renderer.domElement;
            camera.aspect = canvas.clientWidth / canvas.clientHeight;
            camera.updateProjectionMatrix();
        }

        cubes.forEach((cube, ndx) => {
            const speed = .2 + ndx * .1;
            const rot = time * speed;
            cube.rotation.x = rot;
            cube.rotation.y = rot;
        });
        renderer.render(scene, camera);
        requestAnimationFrame(render);
    }
    requestAnimationFrame(render);

}

main();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值