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);
这是带有 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);