THREE.JS 细节纹理加载级别

THREE.JS Level of Detail Texture Loading

这是带有 THREE.LOD() 对象的小 Three.JS 草图。 如您所见,共有 4 个级别,它们具有独特的纹理。

到目前为止,所有这些纹理都是在启动时预加载的。

有没有办法在放大时动态加载 1、2、3 级纹理?

是的,我可以在没有 THREE.LOD() 的情况下做同样的事情,只需编写我自己的自定义算法,这将 generate/remove 平面缩放,但我对内置 THREE.LOD().

    
var folder = "http://vault.vkuchinov.co.uk/test/assets";
var levels = [0xF25E6B, 0x4EA6A6, 0x8FD9D1, 0xF2B29B, 0xF28E85];  
    
var renderer, scene, camera, controls, loader, lod, glsl, uniforms;
    
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);

scene = new THREE.Scene();
loader = new THREE.TextureLoader();
loader.crossOrigin = "";

camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 51200);
camera.position.set(-2048, 2048, -2048);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;

controls.screenSpacePanning = false;

controls.minDistance = 8;
controls.maxDistance = 5120;

controls.maxPolarAngle = Math.PI / 2;

lod = new THREE.LOD();
lod.name = "0,0";
generateTiles(lod, 2048, 2048, 2048, 0, 0x00FFFF);
  
scene.add(lod);
    
animate();
        
function animate(){
         
    controls.update();
    renderer.render(scene, camera);
    
    requestAnimationFrame(animate);

}

function generateTiles(parent_, width_, height_, zoom_, level_, hex_){
    
    var id = parent_.name.split(",");
    var colors = [0xFFFF00, 0xFF000, 0x00FF00, 0x0000FF, 0xFF00FF, 0xF0F0F0];
    
    var group = new THREE.Group(), geometry, plane;

    var dx = 0, dy  = 0;
    dy *= Math.pow(2, level_); dx *= Math.pow(2, level_); 
    
    var url = folder + "/textures/level" + level_ + "/" + id[0] + "_" + id[1] + ".jpg";
    
    if(level_ < 3){
        
        var uniforms = {

            satellite: {
                type: "t",
                value: loader.load(url)
            }
            
        };

        var glsl = new THREE.ShaderMaterial({

        uniforms: uniforms,
        vertexShader: document.getElementById("vertexTerrain").textContent,
        fragmentShader: document.getElementById("fragmentTerrain").textContent,
        lights: false,
        fog: false,
        transparent: true

        });

        glsl.extensions.derivatives = true;
        
        geometry = new THREE.PlaneGeometry(width_, height_, 256, 256);
        plane = new THREE.Mesh(geometry, glsl); 
        plane.rotation.set(-Math.PI / 2, 0, 0);
        parent_.addLevel(plane, zoom_);

        geometry = new THREE.PlaneGeometry(width_ / 2, height_ / 2, 128, 128);

        var ix = (Number(id[0]) * 2);
        var iy  = (Number(id[1]) * 2);

        var lod1 = new THREE.LOD();
        var url1 = getURL(ix + "," + iy, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
        
        var uniforms1 = {

            satellite: {
                type: "t",
                value: loader.load(url1)
            }
            
        };

        var glsl1 = new THREE.ShaderMaterial({

        uniforms: uniforms1,
        vertexShader: document.getElementById("vertexTerrain").textContent,
        fragmentShader: document.getElementById("fragmentTerrain").textContent,
        lights: false,
        fog: false,
        transparent: true

        });

        glsl1.extensions.derivatives = true;
        
        plane = new THREE.Mesh(geometry, glsl1);
        plane.rotation.set(-Math.PI / 2, 0, 0);
        lod1.addLevel(plane, zoom_ / 2);
        lod1.position.set(-width_ / 4, 0, -height_ / 4);
        lod1.name = ix + "," + iy;
        group.add(lod1);

        var lod2 = new THREE.LOD();
        var url2 = getURL(ix + "," + (iy + 1), width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
        
        var uniforms2 = {

            satellite: {
                type: "t",
                value: loader.load(url2)
            }
            
        };

        var glsl2 = new THREE.ShaderMaterial({

        uniforms: uniforms2,
        vertexShader: document.getElementById("vertexTerrain").textContent,
        fragmentShader: document.getElementById("fragmentTerrain").textContent,
        lights: false,
        fog: false,
        transparent: true

        });

        glsl2.extensions.derivatives = true;
        
        plane = new THREE.Mesh(geometry, glsl2);
        plane.rotation.set(-Math.PI / 2, 0, 0);
        lod2.addLevel(plane, zoom_ / 2);
        lod2.position.set(width_ / 4, 0, -height_ / 4);
        lod2.name = ix + "," + (iy + 1);
        group.add(lod2);

        var lod3 = new THREE.LOD();
        var url3 = getURL((ix + 1) + "," + iy, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
        
        var uniforms3 = {

            satellite: {
                type: "t",
                value: loader.load(url3)
            }
            
        };

        var glsl3 = new THREE.ShaderMaterial({

        uniforms: uniforms3,
        vertexShader: document.getElementById("vertexTerrain").textContent,
        fragmentShader: document.getElementById("fragmentTerrain").textContent,
        lights: false,
        fog: false,
        transparent: true

        });

        glsl3.extensions.derivatives = true;
        
        plane = new THREE.Mesh(geometry, glsl3);
        plane.rotation.set(-Math.PI / 2, 0, 0);
        lod3.addLevel(plane, zoom_ / 2);
        lod3.position.set(-width_ / 4, 0, height_ / 4);
        lod3.name = (ix + 1) + "," + iy;
        group.add(lod3);

        var lod4 = new THREE.LOD();
        var url4 = getURL((ix + 1) + "," + (iy + 1), width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
        
        var uniforms4 = {

            satellite: {
                type: "t",
                value: loader.load(url4)
            }
            
        };

        var glsl4 = new THREE.ShaderMaterial({

        uniforms: uniforms4,
        vertexShader: document.getElementById("vertexTerrain").textContent,
        fragmentShader: document.getElementById("fragmentTerrain").textContent,
        lights: false,
        fog: false,
        transparent: true

        });

        glsl4.extensions.derivatives = true;
        
        plane = new THREE.Mesh(geometry, glsl4);
        plane.rotation.set(-Math.PI / 2, 0, 0);
        lod4.addLevel(plane, zoom_ / 2);
        lod4.position.set(width_ / 4, 0, height_ / 4);
        lod4.name = (ix + 1) + "," + (iy + 1);
        group.add(lod4);

        parent_.addLevel(group, zoom_ / 2);

        generateTiles(lod1, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
        generateTiles(lod2, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
        generateTiles(lod3, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
        generateTiles(lod4, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
        
    }

}
    
function getURL(name_, width_, height_, zoom_, level_){
    
    var id = name_.split(",");  
    return folder + "/textures/level" + level_ + "/" + id[0] + "_" + id[1] + ".jpg"; 

}
body { margin: 0; }
<!DOCTYPE html>
<html>
<head>
    
    <meta charset="utf-8" />
    <title>GLSL Intersection</title>
  
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <script src="https://unpkg.com/three@0.116.0/build/three.min.js"></script>
    <script src="https://unpkg.com/three@0.116.0/examples/js/controls/OrbitControls.js"></script>

</head>
<body>

<script id="vertexTerrain" type="x-shader/x-vertex">

uniform sampler2D satellite;
varying vec2 vUv;

void main() {

  vUv = uv;
  vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
  gl_Position = projectionMatrix * mvPosition;


}

</script>
    
<script id="fragmentTerrain" type="x-shader/x-fragment">

precision highp float;
precision highp int;

uniform sampler2D satellite;

varying vec2 vUv;

void main() {

    gl_FragColor = texture2D(satellite, vUv);

}
        
</script>

    
</body>
</html>

查看 the code 你可以扫描你的 lods,看看他们当前的水平,并检查它是否已加载?

body {
  margin: 0;
}
#c {
  width: 100vw;
  height: 100vh;
  display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/controls/OrbitControls.js';

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 500;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const controls = new OrbitControls(camera, canvas);
  controls.update();

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    scene.add(light);
  }

  const numLevels = 4;
  const lodInfos = [];
  function createLod(pos) {
    const lod = new THREE.LOD();
    lod.position.set(...pos);
    scene.add(lod);
    for (let level = 0; level < numLevels; ++level) {
      const obj = new THREE.Object3D();
      lod.addLevel(obj, 3 + Math.pow(2, level));
    }
    lodInfos.push({
      lod,
      levels: [],
    });
  }
  
  createLod([0, 0, 0]);
  
  function scanLods() {
    for (const {lod, levels} of lodInfos) {
      const level = lod.getCurrentLevel();
      if (!levels[level]) {
        // this level is not loaded
        levels[level] = true; // mark it as loaded
        
        // load it
        loadLodLevel(level, lod.levels[level].object);
        
        // optimization: if all levels are loaded 
        // remove this from the lodInfos
      }
    }
  }
  
  function loadLodLevel(level, obj) {
    // obviously I'd use some kind of data structure but just to
    // get something working
    let geometry;
    let material;
    switch(level) {
      case 0:
        geometry = new THREE.BoxBufferGeometry(1, 1, 1);
        material = new THREE.MeshPhongMaterial({color: 'red'});
        break;
      case 1:
        geometry = new THREE.SphereBufferGeometry(0.5, 12, 6);
        material = new THREE.MeshPhongMaterial({color: 'yellow'});
        break;
      case 2:
        geometry = new THREE.ConeBufferGeometry(0.5, 1, 12);
        material = new THREE.MeshPhongMaterial({color: 'green'});
        break;
      case 3:
        geometry = new THREE.CylinderBufferGeometry(0.5, 0.5, 1, 12);
        material = new THREE.MeshPhongMaterial({color: 'purple'});
        break;
    }
    const lodMesh = new THREE.Mesh(geometry, material);
    obj.add(lodMesh);
  }

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;

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

    scanLods();

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
</script>

上面的解决方案为每个 lod 添加一个 THREE.Object3D,然后在它可见时为其添加一个网格。

您也可以替换 THREE.Object3D 而不是

obj.add(lodMesh);

会是这样的

obj.levels[level].object = lodMesh;
obj.parent.add(lodMesh);
obj.parent.remove(obj);