在THREE.js中使用纹理

71

我正在学习THREE.js,并尝试绘制一个矩形,并在其上放置一个纹理,由单一光源照亮。我认为这是最简单的(为了简洁起见省略了HTML代码):

function loadScene() {
    var world = document.getElementById('world'),
        WIDTH = 1200,
        HEIGHT = 500,
        VIEW_ANGLE = 45,
        ASPECT = WIDTH / HEIGHT,
        NEAR = 0.1,
        FAR = 10000,

        renderer = new THREE.WebGLRenderer(),
        camera = new THREE.Camera(VIEW_ANGLE, ASPECT, NEAR, FAR),
        scene = new THREE.Scene(),
        texture = THREE.ImageUtils.loadTexture('crate.gif'),
        material = new THREE.MeshBasicMaterial({map: texture}),
        // material = new THREE.MeshPhongMaterial({color: 0xCC0000});
        geometry = new THREE.PlaneGeometry(100, 100),
        mesh = new THREE.Mesh(geometry, material),
        pointLight = new THREE.PointLight(0xFFFFFF);

    camera.position.z = 200;    
    renderer.setSize(WIDTH, HEIGHT);
    scene.addChild(mesh);
    world.appendChild(renderer.domElement);
    pointLight.position.x = 50;
    pointLight.position.y = 50;
    pointLight.position.z = 130;
    scene.addLight(pointLight); 
    renderer.render(scene, camera);
}

问题是我看不到任何东西。如果我更改材质并使用已注释的材质,一个正方形会按照预期出现。请注意:

  • 纹理为256x256,因此其边缘是2的幂
  • 实际上,当页面主体加载时才调用该函数;确实它可以与不同的材质配合使用。
  • 即使我从Web服务器提供文件,它也无法工作,因此这不是允许加载图像的跨域策略的问题。

我做错了什么?

6个回答

74

图片加载时,渲染器已经绘制了场景,因此为时已晚。解决方法是改变

texture = THREE.ImageUtils.loadTexture('crate.gif'),

转化为

texture = THREE.ImageUtils.loadTexture('crate.gif', {}, function() {
    renderer.render(scene);
}),

11
现在的解决方法是弃用已过时的THREE.ImageUtils.loadTexture,改用THREE.TextureLoader.load。对于我来说,ImageUtils完全不起作用,但是我第一次尝试TextureLoader就完美地解决了问题。文档链接:https://threejs.org/docs/index.html#api/loaders/TextureLoader - ArtOfWarfare

27

Andrea的解决方案是完全正确的,我只是基于同样的思路写了另一种实现方式。如果你查看了THREE.ImageUtils.loadTexture()的源代码,你会发现它使用了Javascript中的Image对象。当所有的图片都加载完成时,$(window).load事件会被触发!所以我们可以在该事件中使用已经加载好的纹理来渲染场景...

CoffeeScript

$(document).ready ->

    material = new THREE.MeshLambertMaterial(map: THREE.ImageUtils.loadTexture("crate.gif"))

    sphere   = new THREE.Mesh(new THREE.SphereGeometry(radius, segments, rings), material)

    $(window).load ->
        renderer.render scene, camera
  • JavaScript

  • $(document).ready(function() {
    
        material = new THREE.MeshLambertMaterial({ map: THREE.ImageUtils.loadTexture("crate.gif") });
    
        sphere = new THREE.Mesh(new THREE.SphereGeometry(radius, segments, rings), material);
    
        $(window).load(function() {
            renderer.render(scene, camera);
        });
    });
    

    谢谢...


    24

    在three.js的r75版本中,您应该使用:

    var loader = new THREE.TextureLoader();
    loader.load('texture.png', function ( texture ) {
      var geometry = new THREE.SphereGeometry(1000, 20, 20);
      var material = new THREE.MeshBasicMaterial({map: texture, overdraw: 0.5});
      var mesh = new THREE.Mesh(geometry, material);
      scene.add(mesh);
    });
    

    太棒了!我猜测自原回答以来API已经发生了变化。你的回答是可行的!非常感谢! - CowWarrior
    你应该明确说明你所指的 latest 版本是哪个 three.js 的修订版本。 - Harsh

    6
    在Three.js的r82版本中,TextureLoader是用于加载纹理的对象。
    加载一个纹理的方法如下(源代码演示):
    提取代码(test.js):
    var scene = new THREE.Scene();
    var ratio = window.innerWidth / window.innerHeight;
    var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight,
      0.1, 50);
    
    var renderer = ...
    
    [...]
    
    /**
     * Will be called when load completes.
     * The argument will be the loaded texture.
     */
    var onLoad = function (texture) {
      var objGeometry = new THREE.BoxGeometry(20, 20, 20);
      var objMaterial = new THREE.MeshPhongMaterial({
        map: texture,
        shading: THREE.FlatShading
      });
    
      var mesh = new THREE.Mesh(objGeometry, objMaterial);
    
      scene.add(mesh);
    
      var render = function () {
        requestAnimationFrame(render);
    
        mesh.rotation.x += 0.010;
        mesh.rotation.y += 0.010;
    
        renderer.render(scene, camera);
      };
    
      render();
    }
    
    // Function called when download progresses
    var onProgress = function (xhr) {
      console.log((xhr.loaded / xhr.total * 100) + '% loaded');
    };
    
    // Function called when download errors
    var onError = function (xhr) {
      console.log('An error happened');
    };
    
    var loader = new THREE.TextureLoader();
    loader.load('texture.jpg', onLoad, onProgress, onError);
    

    多重纹理的加载 (源代码, 演示)

    在这个例子中,纹理是在网格构造函数内加载的,使用Promises加载多重纹理。

    提取(Globe.js):

    使用Object3D创建一个新容器,以便在同一容器中有两个网格:

    var Globe = function (radius, segments) {
    
      THREE.Object3D.call(this);
    
      this.name = "Globe";
    
      var that = this;
    
      // instantiate a loader
      var loader = new THREE.TextureLoader();
    

    一个名为textures的地图,其中每个对象都包含纹理文件的url和用于存储Three.js texture对象值的val

      // earth textures
      var textures = {
        'map': {
          url: 'relief.jpg',
          val: undefined
        },
        'bumpMap': {
          url: 'elev_bump_4k.jpg',
          val: undefined
        },
        'specularMap': {
          url: 'wateretopo.png',
          val: undefined
        }
      };
    

    对于地图中名为textures的每个对象,在承诺数组中推入一个新承诺texturePromises,每个承诺都将调用loader.load。如果entry.val的值是有效的THREE.Texture对象,则解决该承诺。

      var texturePromises = [], path = './';
    
      for (var key in textures) {
        texturePromises.push(new Promise((resolve, reject) => {
          var entry = textures[key]
          var url = path + entry.url
    
          loader.load(url,
            texture => {
              entry.val = texture;
              if (entry.val instanceof THREE.Texture) resolve(entry);
            },
            xhr => {
              console.log(url + ' ' + (xhr.loaded / xhr.total * 100) +
                '% loaded');
            },
            xhr => {
              reject(new Error(xhr +
                'An error occurred loading while loading: ' +
                entry.url));
            }
          );
        }));
      }
    

    Promise.all 接收一个 Promise 数组 texturePromises 作为参数。这样做会使浏览器等待所有 Promise 完成,当它们完成时,我们可以加载几何体和材质。

      // load the geometry and the textures
      Promise.all(texturePromises).then(loadedTextures => {
    
        var geometry = new THREE.SphereGeometry(radius, segments, segments);
        var material = new THREE.MeshPhongMaterial({
          map: textures.map.val,
          bumpMap: textures.bumpMap.val,
          bumpScale: 0.005,
          specularMap: textures.specularMap.val,
          specular: new THREE.Color('grey')
        });
    
        var earth = that.earth = new THREE.Mesh(geometry, material);
        that.add(earth);
      });
    

    云领域只需要一张纹理:

      // clouds
      loader.load('n_amer_clouds.png', map => {
        var geometry = new THREE.SphereGeometry(radius + .05, segments, segments);
        var material = new THREE.MeshPhongMaterial({
          map: map,
          transparent: true
        });
    
        var clouds = that.clouds = new THREE.Mesh(geometry, material);
        that.add(clouds);
      });
    }
    
    Globe.prototype = Object.create(THREE.Object3D.prototype);
    Globe.prototype.constructor = Globe;
    

    1
    没有错误处理
    //Load background texture
     new THREE.TextureLoader();
    loader.load('https://images.pexels.com/photos/1205301/pexels-photo-1205301.jpeg' , function(texture)
                {
                 scene.background = texture;  
                });
    

    带有错误处理的。
    // Function called when download progresses
    var onProgress = function (xhr) {
      console.log((xhr.loaded / xhr.total * 100) + '% loaded');
    };
    
    // Function called when download errors
    var onError = function (error) {
      console.log('An error happened'+error);
    };
    
    //Function  called when load completes.
    var onLoad = function (texture) {
      var objGeometry = new THREE.BoxGeometry(30, 30, 30);
      var objMaterial = new THREE.MeshPhongMaterial({
        map: texture,
        shading: THREE.FlatShading
      });
    
      var boxMesh = new THREE.Mesh(objGeometry, objMaterial);
      scene.add(boxMesh);
    
      var render = function () {
        requestAnimationFrame(render);
        boxMesh.rotation.x += 0.010;
        boxMesh.rotation.y += 0.010;
          sphereMesh.rotation.y += 0.1;
        renderer.render(scene, camera);
      };
    
      render();
    }
    
    
    //LOAD TEXTURE and on completion apply it on box
    var loader = new THREE.TextureLoader();
        loader.load('https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/The_Earth_seen_from_Apollo_17.jpg/1920px-The_Earth_seen_from_Apollo_17.jpg', 
                    onLoad, 
                    onProgress, 
                    onError);
    

    Result:

    enter image description here

    https://codepen.io/hiteshsahu/pen/jpGLpq/


    0
    使用TextureLoader将图像作为纹理加载,然后将该纹理简单地应用于场景背景。
     new THREE.TextureLoader();
         loader.load('https://images.pexels.com/photos/1205301/pexels-photo-1205301.jpeg' , function(texture)
                    {
                     scene.background = texture;  
                    });
    

    结果:

    https://codepen.io/hiteshsahu/pen/jpGLpq?editors=0011

    请看 Hitesh Sahu 在 CodePen 上的 Flat Earth Three.JS 作品:{{link1}},他的账号是 {{link2}}。
    .

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接