我正在为 Mapbox 开发一个自定义图层,以使用 Three.js 渲染 3D 对象。我的目标是将 3D 对象加载到地图上并在特定的纬度和经度坐标处渲染它。虽然我已成功将对象加载到地图上,但我正在努力将对象放置在不同的经纬度位置。挑战是将纬度、经度坐标投影到 Threejs 世界。
这是我到目前为止编写的代码:
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { MercatorCoordinate, type Map } from 'mapbox-gl';
export type ModelOptions = {
location: [number, number];
rotation: [number, number, number];
scale: number;
};
export default class Map3DLayer {
private map: Map;
private camera: THREE.Camera;
private scene: THREE.Scene;
private renderer: THREE.WebGLRenderer;
private cameraTransform: THREE.Matrix4;
private scale: number;
constructor(map: Map) {
this.map = map;
// Build the scene
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: map.getCanvas().getContext('webgl') ?? undefined,
antialias: true,
});
this.renderer.autoClear = false;
const light = new THREE.AmbientLight(0xffffff, 5);
this.scene.add(light);
// Synchronize THREE with Mapbox
// const center = MercatorCoordinate.fromLngLat(map.getCenter(), 0);
const center = MercatorCoordinate.fromLngLat([0, 0], 0);
const { x, y, z = 0 } = center;
this.scale = center.meterInMercatorCoordinateUnits();
const scale = new THREE.Matrix4().makeScale(this.scale, this.scale, -this.scale);
const rotation = new THREE.Matrix4().multiplyMatrices(
new THREE.Matrix4().makeRotationX(-0.5 * Math.PI),
new THREE.Matrix4().makeRotationY(Math.PI),
);
this.cameraTransform = new THREE.Matrix4()
.multiplyMatrices(scale, rotation)
.setPosition(x, y, z);
}
loadModel(name: string, path: string, options: ModelOptions) {
const loader = new GLTFLoader();
return new Promise<void>((resolve, reject) => {
loader.load(path, ({ scene }: any) => {
scene.name = name;
this.scene.add(scene);
this.update(name, options);
resolve();
}, undefined, reject);
});
}
update(name: string, options: Partial<ModelOptions>) {
const item = this.scene.getObjectByName(name);
if (!item) return;
const { location, rotation, scale } = options;
if (rotation) item.rotation.set(...rotation);
if (scale) item.scale.set(scale, scale, scale);
if (location) {
const { x, y, z = 0 } = MercatorCoordinate.fromLngLat(location, 0);
item.position.set(x, y, z);
}
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
render(ctx: WebGLRenderingContext, matrix: number[]) {
this.camera.projectionMatrix = new THREE.Matrix4()
.fromArray(matrix)
.multiply(this.cameraTransform);
this.renderer.resetState();
this.renderer.render(this.scene, this.camera);
}
}
我尝试使用 Mapbox 中的 MercatorCooperative.fromLngLat 函数将 Three.js 的 lat、lon 转换为 x、y、z 坐标,但对象没有出现在预期位置。我知道这会将坐标转换为 0-1 范围,并尝试使用计算出的比例将对象移动到正确的位置,但运气不佳。
预期行为: 3D 对象应在指定的经纬度坐标处渲染。
实际行为: 对象已渲染,但未处于所需的纬度、经度坐标。
任何有关如何将经纬度坐标正确投影到 Threejs 世界的见解或建议将不胜感激!
这是一段稍作修改的工作代码:
import { CustomLayerInterface, Map, MercatorCoordinate } from 'mapbox-gl';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
export class ExternalModelDefinition {
public url: string;
public origin: [number, number];
public rotation: Array<any>;
public altitude: number;
public scaleFactor: number;
}
function add3DModelToMap(externalModelDefinition: ExternalModelDefinition, map: Map) {
const modelAsMercatorCoordinate: MercatorCoordinate = MercatorCoordinate.fromLngLat(externalModelDefinition.origin, externalModelDefinition.altitude);
const modelScale = modelAsMercatorCoordinate.meterInMercatorCoordinateUnits() * externalModelDefinition.scaleFactor;
// transformation parameters to position, rotate and scale the 3D model onto the map
const modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: externalModelDefinition.rotation[0],
rotateY: externalModelDefinition.rotation[1],
rotateZ: externalModelDefinition.rotation[2],
/* Since the 3D model is in real world meters, a scale transform needs to be
* applied since the CustomLayerInterface expects units in MercatorCoordinates.
*/
scale: modelScale,
};
//const THREE = window['THREE'];
const camera = new THREE.Camera();
const scene = new THREE.Scene();
let renderer = new THREE.WebGLRenderer();
// create two three.js lights to illuminate the model
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
scene.add(directionalLight);
const directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
scene.add(directionalLight2);
const customLayer: CustomLayerInterface = {
id: 'your-custom-3D-object-layer-id',
type: 'custom',
renderingMode: '3d',
onAdd: (map, gl) => {
// use the three.js GLTF loader to add the 3D model to the three.js scene
const loader = new GLTFLoader();
loader.load(
externalModelDefinition.url,
(gltf) => {
scene.add(gltf.scene);
},
(progressEvent: ProgressEvent) => {},
(error) => {
console.log(error);
}
);
// use the Mapbox GL JS map canvas for three.js
renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true,
});
renderer.autoClear = false;
},
render: (gl, matrix) => {
const rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), modelTransform.rotateX);
const rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), modelTransform.rotateY);
const rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), modelTransform.rotateZ);
const m = new THREE.Matrix4().fromArray(matrix);
const l = new THREE.Matrix4()
.makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ as number)
.scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))
.multiply(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
renderer.autoClear = false;
camera.projectionMatrix = m.multiply(l);
renderer.resetState();
renderer.render(scene, camera);
this.map.triggerRepaint();
},
};
map.addLayer(customLayer);
map.setLayoutProperty(customLayer.id, 'visibility', 'visible');
}
这必须提供足够的指导来实现它🙌🏼祝你好运!