我正在处理一个加载5个网格的Three.js场景,每个网格都包含一个包含单个图像纹理的材质。一旦这些图像加载,我就试图完成以下任务:
documentation on Geometry对Geometry.faceVertexUvs几乎没有什么可说的,但this SO post建议在使用faceVertexUvs时,可以使用以下方法处理几何体中的多种材质:
geometry.faceVertexUvs[ materialIndex ][ faceIndex ][ vertexIndex ]
问题是,在完成上述步骤后,我调用了meshes[meshIdx].geometry.uvsNeedUpdate = true;
,但我的新材料没有出现,我的网格保持不变。有谁知道为什么会这样?
我非常感谢其他人可以就此问题提供的任何指示或见解!
以下是我的完整代码示例:
/**
* Globals
**/
// Create a store for all images contained in the visualization
var imageList = null;
// Create a store for the image atlas files. Each key will represent
// the index position of the atlas file, and the value will point
// to the material at that index position
var materials = {32: {}, 64: {}};
// Create global configs for image and atlas sizing
var image, atlas;
// Create a store of meshes
var meshes = [];
/**
* Create Scene
**/
// Create the scene and a camera to view it
var scene = new THREE.Scene();
/**
* Camera
**/
// Specify the portion of the scene visiable at any time (in degrees)
var fieldOfView = 75;
// Specify the camera's aspect ratio
var aspectRatio = window.innerWidth / window.innerHeight;
/*
Specify the near and far clipping planes. Only objects
between those planes will be rendered in the scene
(these values help control the number of items rendered
at any given time)
*/
var nearPlane = 100;
var farPlane = 50000;
// Use the values specified above to create a camera
var camera = new THREE.PerspectiveCamera(
fieldOfView, aspectRatio, nearPlane, farPlane
);
// Finally, set the camera's position
camera.position.z = 12000;
camera.position.y = -2000;
/**
* Renderer
**/
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({ antialias: true });
// Add support for retina displays
renderer.setPixelRatio( window.devicePixelRatio );
// Specify the size of the canvas
renderer.setSize( window.innerWidth, window.innerHeight );
// Add the canvas to the DOM
document.body.appendChild( renderer.domElement );
/**
* Load External Data
**/
// Identify data endpoint
var dataUrl = 'https://s3.amazonaws.com/duhaime/blog/tsne-webgl/data/';
// Create a store for image position information
var imagePositions = null;
// Load the image position JSON file
var fileLoader = new THREE.FileLoader();
fileLoader.load(dataUrl + 'image_tsne_projections.json', function(data) {
imagePositions = JSON.parse(data);
conditionallyBuildGeometries(32)
})
/**
* Load Atlas Textures
**/
// List of all textures to be loaded, the size of subimages
// in each, and the total number of atlas files for each size
var textureSets = {
32: { size: 32, count: 5 },
64: { size: 64, count: 20 }
}
// Create a texture loader so we can load our image files
var textureLoader = new THREE.TextureLoader();
function loadTextures(size) {
for (var i=0; i<textureSets[size].count; i++) {
var url = dataUrl + 'atlas_files/' + size + 'px/atlas-' + i + '.jpg';
textureLoader.load(url, handleTexture.bind(null, size, i));
}
}
// Callback function that adds the texture to the list of textures
// and calls the geometry builder if all textures have loaded
function handleTexture(size, idx, texture) {
var material = new THREE.MeshBasicMaterial({ map: texture });
materials[size][idx] = material;
conditionallyBuildGeometries(size, idx)
}
// If the textures and the mapping from image idx to positional information
// are all loaded, create the geometries
function conditionallyBuildGeometries(size, idx) {
if (size === 32) {
var nLoaded = Object.keys(materials[size]).length;
var nRequired = textureSets[size].count;
if (nLoaded === nRequired && imagePositions) {
document.querySelector('#loading').style.display = 'none';
buildGeometry(size);
loadTextures(64)
}
} else {
updateGeometry(size, idx)
}
}
loadTextures(32)
/**
* Build Image Geometry
**/
// Iterate over the textures in the current texture set
// and for each, add a new mesh to the scene
function buildGeometry(size) {
setImageAndAtlasSize(size);
for (var i=0; i<textureSets[size].count; i++) {
// Create one new geometry per atlas
var geometry = new THREE.Geometry();
for (var j=0; j<atlas.cols*atlas.rows; j++) {
geometry = updateVertices(geometry, i, j);
geometry = updateFaces(geometry);
geometry = updateFaceVertexUvs(geometry, j, 0);
}
buildMesh(geometry, materials[size][i]);
}
}
function setImageAndAtlasSize(size) {
// Identify the subimage size in px (width/height) and the
// size of the image as it will be displayed in the map
image = { width: size, height: size, shownWidth: 64, shownHeight: 64 };
// Identify the total number of cols & rows in the image atlas
atlas = { width: 2048, height: 2048, cols: 2048/size, rows: 2048/size };
}
// Get the x, y, z coords for the subimage at index position j
// of atlas in index position i
function getCoords(i, j) {
var idx = (i * atlas.rows * atlas.cols) + j;
var coords = imagePositions[idx];
coords.x *= 2200;
coords.y *= 1200;
coords.z = (-200 + j/100);
return coords;
}
// Add one vertex for each corner of the image, using the
// following order: lower left, lower right, upper right, upper left
function updateVertices(geometry, i, j) {
// Retrieve the x, y, z coords for this subimage
var coords = getCoords(i, j);
geometry.vertices.push(
new THREE.Vector3(
coords.x,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y + image.shownHeight,
coords.z
),
new THREE.Vector3(
coords.x,
coords.y + image.shownHeight,
coords.z
)
);
return geometry;
}
// Create two new faces for a given subimage, then add those
// faces to the geometry
function updateFaces(geometry) {
// Add the first face (the lower-right triangle)
var faceOne = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-3,
geometry.vertices.length-2
)
// Add the second face (the upper-left triangle)
var faceTwo = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-2,
geometry.vertices.length-1
)
// Add those faces to the geometry
geometry.faces.push(faceOne, faceTwo);
return geometry;
}
function updateFaceVertexUvs(geometry, j, materialIdx) {
// Identify the relative width and height of the subimages
// within the image atlas
var relativeW = image.width / atlas.width;
var relativeH = image.height / atlas.height;
// Identify this subimage's offset in the x dimension
// An xOffset of 0 means the subimage starts flush with
// the left-hand edge of the atlas
var xOffset = (j % atlas.cols) * relativeW;
// Identify this subimage's offset in the y dimension
// A yOffset of 0 means the subimage starts flush with
// the bottom edge of the atlas
var yOffset = 1 - (Math.floor(j/atlas.cols) * relativeH) - relativeH;
// Create an empty list of faceVertexUvs for the given material Idx
// if it doesn't exist yet
if (!geometry.faceVertexUvs[materialIdx]) {
geometry.faceVertexUvs[materialIdx] = [];
}
// Use the xOffset and yOffset (and the knowledge that
// each row and column contains only 32 images) to specify
// the regions of the current image
geometry.faceVertexUvs[materialIdx][j*2] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH)
];
// Map the region of the image described by the lower-left,
// upper-right, and upper-left vertices to `faceTwo`
geometry.faceVertexUvs[materialIdx][(j*2) + 1] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH),
new THREE.Vector2(xOffset, yOffset + relativeH)
];
return geometry;
}
function buildMesh(geometry, material) {
// Convert the geometry to a BuferGeometry for additional performance
//var geometry = new THREE.BufferGeometry().fromGeometry(geometry);
// Combine the image geometry and material into a mesh
var mesh = new THREE.Mesh(geometry, [material]);
// Set the position of the image mesh in the x,y,z dimensions
mesh.position.set(0,0,0)
// Add the image to the scene
scene.add(mesh);
// Save this mesh
meshes.push(mesh);
}
/**
* Update Geometries with new VertexUvs and materials
**/
function updateGeometry(size, idx) {
// Update the image and atlas sizes
setImageAndAtlasSize(size)
// Determine how many of the higher resolution atlas files
// it takes to account for all subimages in a lower resolution
// atlas file
var lowResPerAtlas = (2048/32)**2;
var highResPerAtlas = (2048/64)**2;
var atlasRatio = lowResPerAtlas / highResPerAtlas;
// Determine which of the original meshes the newly-loaded high-res
// atlas corresponds to
var meshIdx = Math.floor(idx/atlasRatio);
// Determine the material index position to use in this mesh.
// The mesh's materials array will look like this:
// mesh.material = [32px, 64px_0, 64px_1, 64px_2, 64px_3, 64_px_4];
var materialIdx = (idx % atlasRatio) + 1;
// Add the newly loaded material into the appropriate mesh
meshes[meshIdx].material[materialIdx] = materials[size][idx];
//console.log(meshIdx, materialIdx, idx, meshes[materialIdx].material)
// Pluck out the geometry of this mesh update:
var geometry = meshes[meshIdx].geometry;
for (var j=0; j<highResPerAtlas; j++) {
geometry = updateFaceVertexUvs(geometry, j, materialIdx);
}
geometry.faceVertexUvs[0] = [];
meshes[meshIdx].geometry = geometry;
meshes[meshIdx].geometry.colorsNeedUpdate = true;
meshes[meshIdx].geometry.groupsNeedUpdate = true;
meshes[meshIdx].geometry.lineDistancesNeedUpdate = true;
meshes[meshIdx].geometry.normalsNeedUpdate = true;
meshes[meshIdx].geometry.uvsNeedUpdate = true;
meshes[meshIdx].geometry.verticesNeedUpdate = true;
// Indicate the material needs update
meshes[meshIdx].material.needsUpdate = true;
}
/**
* Lights
**/
// Add a point light with #fff color, .7 intensity, and 0 distance
var light = new THREE.PointLight( 0xffffff, 1, 0 );
// Specify the light's position
light.position.set(1, 1, 100);
// Add the light to the scene
scene.add(light)
/**
* Add Controls
**/
var controls = new THREE.TrackballControls(camera, renderer.domElement);
/**
* Handle window resizes
**/
window.addEventListener('resize', function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
});
/**
* Render!
**/
// The main animation function that re-renders the scene each animation frame
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
controls.update();
}
animate();
&#13;
* {
margin: 0;
padding: 0;
background: #000;
color: #fff;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/yale-dh-staging/tsne/assets/vendor/js/trackball-controls.js"></script>
<div id='loading'>Loading</div>
&#13;
答案 0 :(得分:0)
这是一个已知错误:https://github.com/mrdoob/three.js/issues/7179
不必通过索引访问器更改faceVertexUv,而是必须在faceVertexUv中的适当向量上使用.set()
方法,例如:
if (geometry.faceVertexUvs[0][faceIdx]) {
geometry.faceVertexUvs[0][faceIdx][0].set(xOffset, yOffset)
geometry.faceVertexUvs[0][faceIdx][1].set(xOffset + width, yOffset)
geometry.faceVertexUvs[0][faceIdx][2].set(xOffset + width, yOffset + height)
} else {
geometry.faceVertexUvs[0][faceIdx] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + width, yOffset),
new THREE.Vector2(xOffset + width, yOffset + height)
]
}
完整示例:
/**
* Globals
**/
// Create a store for all images contained in the visualization
var imageList = null;
// Create a store for the image atlas files. Each key will represent
// the index position of the atlas file, and the value will point
// to the material at that index position
var materials = {32: {}, 64: {}};
// Create global configs for image and atlas sizing
var image, atlas;
// Create a store of meshes
var meshes = [];
/**
* Create Scene
**/
// Create the scene and a camera to view it
var scene = new THREE.Scene();
/**
* Camera
**/
// Specify the portion of the scene visiable at any time (in degrees)
var fieldOfView = 75;
// Specify the camera's aspect ratio
var aspectRatio = window.innerWidth / window.innerHeight;
/*
Specify the near and far clipping planes. Only objects
between those planes will be rendered in the scene
(these values help control the number of items rendered
at any given time)
*/
var nearPlane = 100;
var farPlane = 50000;
// Use the values specified above to create a camera
var camera = new THREE.PerspectiveCamera(
fieldOfView, aspectRatio, nearPlane, farPlane
);
// Finally, set the camera's position
camera.position.z = 12000;
camera.position.y = -2000;
/**
* Lights
**/
// Add a point light with #fff color, .7 intensity, and 0 distance
var light = new THREE.PointLight( 0xffffff, 1, 0 );
// Specify the light's position
light.position.set(1, 1, 100);
// Add the light to the scene
scene.add(light)
/**
* Renderer
**/
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({ antialias: true });
// Add support for retina displays
renderer.setPixelRatio( window.devicePixelRatio );
// Specify the size of the canvas
renderer.setSize( window.innerWidth, window.innerHeight );
// Add the canvas to the DOM
document.body.appendChild( renderer.domElement );
/**
* Load External Data
**/
// Identify data endpoint
var dataUrl = 'https://s3.amazonaws.com/duhaime/blog/tsne-webgl/data/';
// Create a store for image position information
var imagePositions = null;
// Load the image position JSON file
var fileLoader = new THREE.FileLoader();
fileLoader.load(dataUrl + 'image_tsne_projections.json', function(data) {
imagePositions = JSON.parse(data);
conditionallyBuildGeometries(32)
})
/**
* Load Atlas Textures
**/
// List of all textures to be loaded, the size of subimages
// in each, and the total count of atlas files for each size
var textureSets = {
32: { size: 32, count: 5 },
64: { size: 64, count: 20 }
}
// Create a texture loader so we can load our image files
var textureLoader = new THREE.TextureLoader();
function loadTextures(size) {
for (var i=0; i<textureSets[size].count; i++) {
var url = dataUrl + 'atlas_files/' + size + 'px/atlas-' + i + '.jpg';
textureLoader.load(url, handleTexture.bind(null, size, i));
}
}
// Create a material from the new texture and call
// the geometry builder if all textures have loaded
function handleTexture(size, idx, texture) {
var material = new THREE.MeshBasicMaterial({ map: texture });
materials[size][idx] = material;
conditionallyBuildGeometries(size, idx)
}
// If the textures and the mapping from image idx to positional information
// are all loaded, create the geometries
function conditionallyBuildGeometries(size, idx) {
if (size === 32) {
var nLoaded = Object.keys(materials[size]).length;
var nRequired = textureSets[size].count;
if (nLoaded === nRequired && imagePositions) {
document.querySelector('#loading').style.display = 'none';
buildGeometry(size);
loadTextures(64)
}
} else {
updateGeometry(size, idx)
}
}
loadTextures(32)
/**
* Build Image Geometry
**/
// Iterate over the textures in the current texture set
// and for each, add a new mesh to the scene
function buildGeometry(size) {
setImageAndAtlasSize(size);
for (var i=0; i<textureSets[size].count; i++) {
// Create one new geometry per set of 1024 images
var geometry = new THREE.Geometry();
geometry.faceVertexUvs[0] = [];
for (var j=0; j<atlas.cols*atlas.rows; j++) {
geometry = updateVertices(geometry, i, j);
geometry = updateFaces(geometry);
geometry = updateFaceVertexUvs(geometry, j);
if ((j+1)%1024 === 0) {
buildMesh(geometry, materials[size][i]);
var geometry = new THREE.Geometry();
}
}
}
}
function setImageAndAtlasSize(size) {
// Identify the subimage size in px (width/height) and the
// size of the image as it will be displayed in the map
image = { width: size, height: size, shownWidth: 64, shownHeight: 64 };
// Identify the total number of cols & rows in the image atlas
atlas = { width: 2048, height: 2048, cols: 2048/size, rows: 2048/size };
}
// Get the x, y, z coords for the subimage at index position j
// of atlas in index position i
function getCoords(i, j) {
var idx = (i * atlas.rows * atlas.cols) + j;
var coords = imagePositions[idx];
coords.x *= 2200;
coords.y *= 1200;
coords.z = (-200 + j/100);
return coords;
}
// Add one vertex for each corner of the image, using the
// following order: lower left, lower right, upper right, upper left
function updateVertices(geometry, i, j) {
// Retrieve the x, y, z coords for this subimage
var coords = getCoords(i, j);
geometry.vertices.push(
new THREE.Vector3(
coords.x,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y + image.shownHeight,
coords.z
),
new THREE.Vector3(
coords.x,
coords.y + image.shownHeight,
coords.z
)
);
return geometry;
}
// Create two new faces for a given subimage, then add those
// faces to the geometry
function updateFaces(geometry) {
// Add the first face (the lower-right triangle)
var faceOne = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-3,
geometry.vertices.length-2
)
// Add the second face (the upper-left triangle)
var faceTwo = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-2,
geometry.vertices.length-1
)
// Add those faces to the geometry
geometry.faces.push(faceOne, faceTwo);
return geometry;
}
function updateFaceVertexUvs(geometry, j) {
// Identify the relative width and height of the subimages
// within the image atlas
var relativeW = image.width / atlas.width;
var relativeH = image.height / atlas.height;
// Identify this subimage's offset in the x dimension
// An xOffset of 0 means the subimage starts flush with
// the left-hand edge of the atlas
var xOffset = (j % atlas.cols) * relativeW;
// Identify this subimage's offset in the y dimension
// A yOffset of 0 means the subimage starts flush with
// the bottom edge of the atlas
var yOffset = 1 - (Math.floor(j/atlas.cols) * relativeH) - relativeH;
// Determine the faceVertexUvs index position
var faceIdx = 2 * (j%1024);
// Use the xOffset and yOffset (and the knowledge that
// each row and column contains only 32 images) to specify
// the regions of the current image. Use .set() if the given
// faceVertex is already defined, due to a bug in updateVertexUvs:
// https://github.com/mrdoob/three.js/issues/7179
if (geometry.faceVertexUvs[0][faceIdx]) {
geometry.faceVertexUvs[0][faceIdx][0].set(xOffset, yOffset)
geometry.faceVertexUvs[0][faceIdx][1].set(xOffset + relativeW, yOffset)
geometry.faceVertexUvs[0][faceIdx][2].set(xOffset + relativeW, yOffset + relativeH)
} else {
geometry.faceVertexUvs[0][faceIdx] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH)
]
}
// Map the region of the image described by the lower-left,
// upper-right, and upper-left vertices to `faceTwo`
if (geometry.faceVertexUvs[0][faceIdx+1]) {
geometry.faceVertexUvs[0][faceIdx+1][0].set(xOffset, yOffset)
geometry.faceVertexUvs[0][faceIdx+1][1].set(xOffset + relativeW, yOffset + relativeH)
geometry.faceVertexUvs[0][faceIdx+1][2].set(xOffset, yOffset + relativeH)
} else {
geometry.faceVertexUvs[0][faceIdx+1] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH),
new THREE.Vector2(xOffset, yOffset + relativeH)
]
}
return geometry;
}
function buildMesh(geometry, material) {
// Convert the geometry to a BuferGeometry for additional performance
//var geometry = new THREE.BufferGeometry().fromGeometry(geometry);
// Combine the image geometry and material into a mesh
var mesh = new THREE.Mesh(geometry, material);
// Set the position of the image mesh in the x,y,z dimensions
mesh.position.set(0,0,0)
// Add the image to the scene
scene.add(mesh);
// Save this mesh
meshes.push(mesh);
return mesh;
}
/**
* Update Geometries with new VertexUvs and materials
**/
function updateGeometry(size, idx) {
// Update the image and atlas sizes
setImageAndAtlasSize(size)
// Update the appropriate material
meshes[idx].material = materials[size][idx];
meshes[idx].material.needsUpdate = true;
// Update the facevertexuvs
for (var j=0; j<atlas.cols*atlas.rows; j++) {
meshes[idx].geometry = updateFaceVertexUvs(meshes[idx].geometry, j);
}
meshes[idx].geometry.uvsNeedUpdate = true;
meshes[idx].geometry.verticesNeedUpdate = true;
}
/**
* Add Controls
**/
var controls = new THREE.TrackballControls(camera, renderer.domElement);
/**
* Handle window resizes
**/
window.addEventListener('resize', function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
});
/**
* Render!
**/
// The main animation function that re-renders the scene each animation frame
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
controls.update();
}
animate();
&#13;
* {
margin: 0;
padding: 0;
background: #000;
color: #fff;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/yale-dh-staging/tsne/assets/vendor/js/trackball-controls.js"></script>
<div id='loading'>Loading</div>
&#13;