第11章-二次开发进阶
第十一章:二次开发进阶
11.1 自定义加载器
11.1.1 加载器架构
Astral3D的加载器系统采用可扩展的设计,允许开发者注册自定义加载器以支持新的文件格式。
// 加载器基类
abstract class BaseLoader {
// 支持的文件扩展名
abstract extensions: string[];
// 检查是否支持该文件
canLoad(url: string): boolean {
const ext = url.split('.').pop()?.toLowerCase() || '';
return this.extensions.includes(ext);
}
// 加载文件
abstract load(url: string, options?: LoadOptions): Promise<THREE.Object3D>;
// 解析数据
abstract parse(data: ArrayBuffer | string): Promise<THREE.Object3D>;
}
11.1.2 创建自定义加载器
import * as THREE from 'three';
import { BaseLoader, LoadOptions, LoaderManager } from '@astral3d/engine';
// 自定义JSON模型加载器
class CustomJSONLoader extends BaseLoader {
extensions = ['custom', 'cjson'];
async load(url: string, options?: LoadOptions): Promise<THREE.Object3D> {
const response = await fetch(url);
const data = await response.json();
return this.parse(data);
}
async parse(data: any): Promise<THREE.Object3D> {
const group = new THREE.Group();
group.name = data.name || 'CustomModel';
// 解析几何体
if (data.geometries) {
for (const geoData of data.geometries) {
const geometry = this.parseGeometry(geoData);
const material = this.parseMaterial(geoData.material);
const mesh = new THREE.Mesh(geometry, material);
if (geoData.position) {
mesh.position.fromArray(geoData.position);
}
if (geoData.rotation) {
mesh.rotation.fromArray(geoData.rotation);
}
if (geoData.scale) {
mesh.scale.fromArray(geoData.scale);
}
mesh.name = geoData.name || '';
group.add(mesh);
}
}
return group;
}
private parseGeometry(data: any): THREE.BufferGeometry {
const geometry = new THREE.BufferGeometry();
if (data.vertices) {
geometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(data.vertices, 3)
);
}
if (data.normals) {
geometry.setAttribute(
'normal',
new THREE.Float32BufferAttribute(data.normals, 3)
);
}
if (data.uvs) {
geometry.setAttribute(
'uv',
new THREE.Float32BufferAttribute(data.uvs, 2)
);
}
if (data.indices) {
geometry.setIndex(data.indices);
}
return geometry;
}
private parseMaterial(data: any): THREE.Material {
return new THREE.MeshStandardMaterial({
color: data?.color || 0xffffff,
metalness: data?.metalness || 0,
roughness: data?.roughness || 1
});
}
}
// 注册加载器
LoaderManager.register(new CustomJSONLoader());
// 使用
const model = await viewer.loader.load('model.custom');
11.1.3 点云加载器示例
// 点云加载器
class PointCloudLoader extends BaseLoader {
extensions = ['pts', 'xyz', 'pcd'];
async load(url: string, options?: LoadOptions): Promise<THREE.Points> {
const response = await fetch(url);
const text = await response.text();
const ext = url.split('.').pop()?.toLowerCase();
switch (ext) {
case 'pts':
return this.parsePTS(text);
case 'xyz':
return this.parseXYZ(text);
default:
throw new Error(`Unsupported format: ${ext}`);
}
}
private parsePTS(text: string): THREE.Points {
const lines = text.trim().split('\n');
const count = parseInt(lines[0]);
const positions: number[] = [];
const colors: number[] = [];
for (let i = 1; i <= count && i < lines.length; i++) {
const parts = lines[i].trim().split(/\s+/);
// X Y Z R G B Intensity
positions.push(
parseFloat(parts[0]),
parseFloat(parts[1]),
parseFloat(parts[2])
);
if (parts.length >= 6) {
colors.push(
parseFloat(parts[3]) / 255,
parseFloat(parts[4]) / 255,
parseFloat(parts[5]) / 255
);
}
}
return this.createPoints(positions, colors);
}
private parseXYZ(text: string): THREE.Points {
const lines = text.trim().split('\n');
const positions: number[] = [];
const colors: number[] = [];
for (const line of lines) {
const parts = line.trim().split(/\s+/);
if (parts.length >= 3) {
positions.push(
parseFloat(parts[0]),
parseFloat(parts[1]),
parseFloat(parts[2])
);
if (parts.length >= 6) {
colors.push(
parseFloat(parts[3]) / 255,
parseFloat(parts[4]) / 255,
parseFloat(parts[5]) / 255
);
}
}
}
return this.createPoints(positions, colors);
}
private createPoints(positions: number[], colors: number[]): THREE.Points {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(positions, 3)
);
if (colors.length > 0) {
geometry.setAttribute(
'color',
new THREE.Float32BufferAttribute(colors, 3)
);
}
const material = new THREE.PointsMaterial({
size: 0.01,
vertexColors: colors.length > 0,
sizeAttenuation: true
});
return new THREE.Points(geometry, material);
}
}
11.2 自定义着色器
11.2.1 着色器材质
import * as THREE from 'three';
// 自定义着色器材质
const customShaderMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
color1: { value: new THREE.Color(0xff0000) },
color2: { value: new THREE.Color(0x0000ff) },
resolution: { value: new THREE.Vector2() }
},
vertexShader: `
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vUv = uv;
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float time;
uniform vec3 color1;
uniform vec3 color2;
varying vec2 vUv;
varying vec3 vPosition;
void main() {
// 渐变效果
float gradient = sin(vUv.y * 3.14159 + time) * 0.5 + 0.5;
vec3 color = mix(color1, color2, gradient);
gl_FragColor = vec4(color, 1.0);
}
`,
side: THREE.DoubleSide
});
// 更新时间uniform
function updateShader(delta: number) {
customShaderMaterial.uniforms.time.value += delta;
}
11.2.2 后处理着色器
import { EffectComposer, ShaderPass } from 'three/examples/jsm/postprocessing';
// 自定义后处理效果
const CustomEffect = {
uniforms: {
tDiffuse: { value: null },
amount: { value: 1.0 },
time: { value: 0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float amount;
uniform float time;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
// 色相偏移效果
float hueShift = sin(time) * amount;
// RGB到HSV
float maxC = max(color.r, max(color.g, color.b));
float minC = min(color.r, min(color.g, color.b));
float delta = maxC - minC;
float h = 0.0;
if (delta > 0.0) {
if (maxC == color.r) {
h = mod((color.g - color.b) / delta, 6.0);
} else if (maxC == color.g) {
h = (color.b - color.r) / delta + 2.0;
} else {
h = (color.r - color.g) / delta + 4.0;
}
h /= 6.0;
}
// 应用色相偏移
h = mod(h + hueShift, 1.0);
// HSV到RGB
float s = delta / maxC;
float v = maxC;
float c = v * s;
float x = c * (1.0 - abs(mod(h * 6.0, 2.0) - 1.0));
float m = v - c;
vec3 rgb;
if (h < 1.0/6.0) rgb = vec3(c, x, 0.0);
else if (h < 2.0/6.0) rgb = vec3(x, c, 0.0);
else if (h < 3.0/6.0) rgb = vec3(0.0, c, x);
else if (h < 4.0/6.0) rgb = vec3(0.0, x, c);
else if (h < 5.0/6.0) rgb = vec3(x, 0.0, c);
else rgb = vec3(c, 0.0, x);
gl_FragColor = vec4(rgb + m, color.a);
}
`
};
// 添加到效果合成器
const composer = new EffectComposer(renderer);
const customPass = new ShaderPass(CustomEffect);
composer.addPass(customPass);
11.2.3 实例化着色器
// 实例化渲染着色器
const instancedMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 }
},
vertexShader: `
attribute vec3 instancePosition;
attribute vec3 instanceColor;
attribute float instanceScale;
varying vec3 vColor;
void main() {
vColor = instanceColor;
vec3 pos = position * instanceScale + instancePosition;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`
});
// 创建实例化几何体
function createInstancedMesh(geometry: THREE.BufferGeometry, count: number) {
const positions = new Float32Array(count * 3);
const colors = new Float32Array(count * 3);
const scales = new Float32Array(count);
for (let i = 0; i < count; i++) {
positions[i * 3] = (Math.random() - 0.5) * 100;
positions[i * 3 + 1] = (Math.random() - 0.5) * 100;
positions[i * 3 + 2] = (Math.random() - 0.5) * 100;
colors[i * 3] = Math.random();
colors[i * 3 + 1] = Math.random();
colors[i * 3 + 2] = Math.random();
scales[i] = Math.random() * 0.5 + 0.5;
}
geometry.setAttribute('instancePosition', new THREE.InstancedBufferAttribute(positions, 3));
geometry.setAttribute('instanceColor', new THREE.InstancedBufferAttribute(colors, 3));
geometry.setAttribute('instanceScale', new THREE.InstancedBufferAttribute(scales, 1));
return new THREE.InstancedMesh(geometry, instancedMaterial, count);
}
11.3 性能优化
11.3.1 渲染优化
// 性能监控
import Stats from 'three/examples/jsm/libs/stats.module';
const stats = new Stats();
document.body.appendChild(stats.dom);
// 渲染循环中更新
function animate() {
stats.begin();
// 渲染
renderer.render(scene, camera);
stats.end();
requestAnimationFrame(animate);
}
// 视锥裁剪优化
scene.traverse((obj) => {
if (obj instanceof THREE.Mesh) {
obj.frustumCulled = true;
}
});
// LOD设置
function createLOD(meshes: THREE.Mesh[]) {
const lod = new THREE.LOD();
meshes.forEach((mesh, i) => {
lod.addLevel(mesh, i * 50);
});
return lod;
}
// 合并几何体
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils';
function mergeStaticMeshes(meshes: THREE.Mesh[]) {
const geometries = meshes.map(m => m.geometry);
const merged = mergeGeometries(geometries);
return new THREE.Mesh(merged, meshes[0].material);
}
11.3.2 内存优化
// 资源清理
function disposeObject(object: THREE.Object3D) {
object.traverse((child) => {
if (child instanceof THREE.Mesh) {
// 清理几何体
child.geometry.dispose();
// 清理材质
if (Array.isArray(child.material)) {
child.material.forEach(mat => disposeMaterial(mat));
} else {
disposeMaterial(child.material);
}
}
});
// 从父节点移除
object.parent?.remove(object);
}
function disposeMaterial(material: THREE.Material) {
// 清理贴图
for (const key of Object.keys(material)) {
const value = (material as any)[key];
if (value instanceof THREE.Texture) {
value.dispose();
}
}
material.dispose();
}
// 贴图缓存管理
class TextureCache {
private cache = new Map<string, THREE.Texture>();
private maxSize = 100;
get(url: string): THREE.Texture | undefined {
return this.cache.get(url);
}
set(url: string, texture: THREE.Texture) {
if (this.cache.size >= this.maxSize) {
// 移除最旧的
const firstKey = this.cache.keys().next().value;
const oldTexture = this.cache.get(firstKey);
oldTexture?.dispose();
this.cache.delete(firstKey);
}
this.cache.set(url, texture);
}
clear() {
this.cache.forEach(tex => tex.dispose());
this.cache.clear();
}
}
11.3.3 加载优化
// 渐进式加载
class ProgressiveLoader {
private queue: Array<{ url: string; priority: number; callback: Function }> = [];
private loading = 0;
private maxConcurrent = 4;
add(url: string, priority: number, callback: Function) {
this.queue.push({ url, priority, callback });
this.queue.sort((a, b) => b.priority - a.priority);
this.processQueue();
}
private async processQueue() {
while (this.loading < this.maxConcurrent && this.queue.length > 0) {
const item = this.queue.shift();
if (item) {
this.loading++;
try {
const result = await this.loadItem(item.url);
item.callback(null, result);
} catch (error) {
item.callback(error, null);
}
this.loading--;
this.processQueue();
}
}
}
private async loadItem(url: string) {
// 加载逻辑
return await viewer.loader.load(url);
}
}
// 基于距离的加载
class DistanceBasedLoader {
private objects = new Map<string, { position: THREE.Vector3; loaded: boolean }>();
private loadDistance = 100;
private unloadDistance = 150;
register(id: string, position: THREE.Vector3) {
this.objects.set(id, { position, loaded: false });
}
update(cameraPosition: THREE.Vector3) {
this.objects.forEach((obj, id) => {
const distance = cameraPosition.distanceTo(obj.position);
if (!obj.loaded && distance < this.loadDistance) {
this.loadObject(id);
obj.loaded = true;
} else if (obj.loaded && distance > this.unloadDistance) {
this.unloadObject(id);
obj.loaded = false;
}
});
}
private async loadObject(id: string) {
// 加载对象
}
private unloadObject(id: string) {
// 卸载对象
}
}
11.4 WebWorker集成
11.4.1 Worker基础
// worker.ts
self.onmessage = async (event) => {
const { type, data } = event.data;
switch (type) {
case 'processGeometry':
const result = processGeometry(data);
self.postMessage({ type: 'geometryProcessed', data: result });
break;
case 'calculatePhysics':
const physicsResult = calculatePhysics(data);
self.postMessage({ type: 'physicsCalculated', data: physicsResult });
break;
}
};
function processGeometry(vertices: Float32Array) {
// 在Worker中处理几何体计算
const processed = new Float32Array(vertices.length);
for (let i = 0; i < vertices.length; i += 3) {
// 示例:简化顶点处理
processed[i] = Math.round(vertices[i] * 100) / 100;
processed[i + 1] = Math.round(vertices[i + 1] * 100) / 100;
processed[i + 2] = Math.round(vertices[i + 2] * 100) / 100;
}
return processed;
}
function calculatePhysics(bodies: any[]) {
// 物理计算
return bodies.map(body => ({
id: body.id,
position: [
body.position[0] + body.velocity[0],
body.position[1] + body.velocity[1],
body.position[2] + body.velocity[2]
]
}));
}
11.4.2 Worker管理器
// WorkerManager.ts
class WorkerManager {
private workers: Worker[] = [];
private taskQueue: Array<{ task: any; resolve: Function; reject: Function }> = [];
private busyWorkers = new Set<Worker>();
constructor(workerUrl: string, count = navigator.hardwareConcurrency || 4) {
for (let i = 0; i < count; i++) {
const worker = new Worker(workerUrl, { type: 'module' });
worker.onmessage = (event) => this.onWorkerMessage(worker, event);
worker.onerror = (error) => this.onWorkerError(worker, error);
this.workers.push(worker);
}
}
async runTask<T>(type: string, data: any): Promise<T> {
return new Promise((resolve, reject) => {
this.taskQueue.push({ task: { type, data }, resolve, reject });
this.processQueue();
});
}
private processQueue() {
for (const worker of this.workers) {
if (!this.busyWorkers.has(worker) && this.taskQueue.length > 0) {
const { task, resolve, reject } = this.taskQueue.shift()!;
this.busyWorkers.add(worker);
// 保存回调
(worker as any)._resolve = resolve;
(worker as any)._reject = reject;
worker.postMessage(task);
}
}
}
private onWorkerMessage(worker: Worker, event: MessageEvent) {
this.busyWorkers.delete(worker);
const resolve = (worker as any)._resolve;
if (resolve) {
resolve(event.data);
}
this.processQueue();
}
private onWorkerError(worker: Worker, error: ErrorEvent) {
this.busyWorkers.delete(worker);
const reject = (worker as any)._reject;
if (reject) {
reject(error);
}
this.processQueue();
}
terminate() {
this.workers.forEach(worker => worker.terminate());
this.workers = [];
}
}
// 使用
const workerManager = new WorkerManager('/workers/geometry-worker.js');
const processedData = await workerManager.runTask<Float32Array>(
'processGeometry',
geometryData
);
11.5 数据可视化
11.5.1 热力图
// 热力图生成
class HeatmapGenerator {
private width: number;
private height: number;
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
constructor(width = 256, height = 256) {
this.width = width;
this.height = height;
this.canvas = document.createElement('canvas');
this.canvas.width = width;
this.canvas.height = height;
this.ctx = this.canvas.getContext('2d')!;
}
generate(points: Array<{ x: number; y: number; value: number }>) {
// 清空画布
this.ctx.clearRect(0, 0, this.width, this.height);
// 绘制热点
points.forEach(point => {
const gradient = this.ctx.createRadialGradient(
point.x * this.width,
point.y * this.height,
0,
point.x * this.width,
point.y * this.height,
point.value * 50
);
gradient.addColorStop(0, 'rgba(255, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(255, 0, 0, 0)');
this.ctx.fillStyle = gradient;
this.ctx.fillRect(0, 0, this.width, this.height);
});
// 应用颜色映射
this.applyColorMap();
// 创建纹理
const texture = new THREE.CanvasTexture(this.canvas);
return texture;
}
private applyColorMap() {
const imageData = this.ctx.getImageData(0, 0, this.width, this.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const value = data[i] / 255;
const color = this.valueToColor(value);
data[i] = color.r;
data[i + 1] = color.g;
data[i + 2] = color.b;
}
this.ctx.putImageData(imageData, 0, 0);
}
private valueToColor(value: number) {
// 蓝 -> 绿 -> 黄 -> 红
if (value < 0.25) {
return { r: 0, g: Math.floor(value * 4 * 255), b: 255 };
} else if (value < 0.5) {
return { r: 0, g: 255, b: Math.floor((0.5 - value) * 4 * 255) };
} else if (value < 0.75) {
return { r: Math.floor((value - 0.5) * 4 * 255), g: 255, b: 0 };
} else {
return { r: 255, g: Math.floor((1 - value) * 4 * 255), b: 0 };
}
}
}
11.5.2 数据图表集成
// ECharts集成
import * as echarts from 'echarts';
class ChartPanel {
private chart: echarts.ECharts;
private texture: THREE.CanvasTexture;
constructor(container: HTMLElement) {
this.chart = echarts.init(container);
this.texture = new THREE.CanvasTexture(this.chart.getDom() as HTMLCanvasElement);
}
setOption(option: echarts.EChartsOption) {
this.chart.setOption(option);
this.texture.needsUpdate = true;
}
getTexture(): THREE.CanvasTexture {
return this.texture;
}
update() {
this.texture.needsUpdate = true;
}
dispose() {
this.chart.dispose();
this.texture.dispose();
}
}
// 创建3D图表
function create3DChart(data: any[]) {
const container = document.createElement('div');
container.style.width = '512px';
container.style.height = '512px';
container.style.position = 'absolute';
container.style.left = '-9999px';
document.body.appendChild(container);
const chartPanel = new ChartPanel(container);
chartPanel.setOption({
title: { text: '数据统计' },
xAxis: { type: 'category', data: data.map(d => d.name) },
yAxis: { type: 'value' },
series: [{
type: 'bar',
data: data.map(d => d.value)
}]
});
// 创建3D平面显示图表
const geometry = new THREE.PlaneGeometry(10, 10);
const material = new THREE.MeshBasicMaterial({
map: chartPanel.getTexture(),
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry, material);
return { mesh, chartPanel };
}
11.6 网络与协作
11.6.1 WebSocket集成
// 实时协作客户端
class CollaborationClient {
private ws: WebSocket;
private userId: string;
private callbacks = new Map<string, Function[]>();
constructor(serverUrl: string) {
this.userId = this.generateUserId();
this.ws = new WebSocket(serverUrl);
this.ws.onopen = () => this.onOpen();
this.ws.onmessage = (event) => this.onMessage(event);
this.ws.onclose = () => this.onClose();
this.ws.onerror = (error) => this.onError(error);
}
private generateUserId(): string {
return Math.random().toString(36).substring(2);
}
private onOpen() {
console.log('连接已建立');
this.send('join', { userId: this.userId });
}
private onMessage(event: MessageEvent) {
const { type, data, userId } = JSON.parse(event.data);
if (userId === this.userId) return; // 忽略自己的消息
const handlers = this.callbacks.get(type) || [];
handlers.forEach(handler => handler(data, userId));
}
private onClose() {
console.log('连接已关闭');
}
private onError(error: Event) {
console.error('WebSocket错误:', error);
}
send(type: string, data: any) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type, data, userId: this.userId }));
}
}
on(type: string, callback: Function) {
if (!this.callbacks.has(type)) {
this.callbacks.set(type, []);
}
this.callbacks.get(type)!.push(callback);
}
off(type: string, callback: Function) {
const handlers = this.callbacks.get(type);
if (handlers) {
const index = handlers.indexOf(callback);
if (index > -1) handlers.splice(index, 1);
}
}
// 同步场景操作
syncObjectTransform(objectId: string, transform: any) {
this.send('transform', { objectId, transform });
}
syncObjectAdd(objectData: any) {
this.send('addObject', objectData);
}
syncObjectRemove(objectId: string) {
this.send('removeObject', { objectId });
}
}
// 使用
const collab = new CollaborationClient('wss://example.com/collab');
collab.on('transform', (data, userId) => {
const object = scene.getObjectByProperty('uuid', data.objectId);
if (object) {
object.position.fromArray(data.transform.position);
object.rotation.fromArray(data.transform.rotation);
object.scale.fromArray(data.transform.scale);
}
});
// 当本地对象变换时同步
viewer.on('objectTransformed', (object) => {
collab.syncObjectTransform(object.uuid, {
position: object.position.toArray(),
rotation: object.rotation.toArray(),
scale: object.scale.toArray()
});
});
11.7 本章小结
本章介绍了Astral3D的二次开发进阶内容,主要包括:
- 自定义加载器:加载器架构和创建自定义格式加载器
- 自定义着色器:着色器材质、后处理效果、实例化渲染
- 性能优化:渲染优化、内存管理、加载优化
- WebWorker集成:将计算密集型任务移至Worker线程
- 数据可视化:热力图生成、图表集成
- 网络协作:WebSocket实时协作
通过本章的学习,读者应该能够进行Astral3D的深度定制开发,构建复杂的3D应用。
下一章预告:第十二章将介绍Astral3D的实战案例与最佳实践,包括数字孪生应用、BIM可视化平台、工业监控系统等完整案例。

浙公网安备 33010602011771号