Codex与tldraw结合:自然语言生成3D地球可视化应用
2026/9/5 11:55:49 网站建设 项目流程

最近在探索 AI 与前端可视化结合的新玩法时,发现了一个令人兴奋的技术组合:通过 Codex 的自然语言指令直接操控 tldraw 画布,实现一句话生成复杂的 3D 地球应用。这种"描述即生成"的开发模式,正在改变我们构建交互式应用的方式。

本文将完整介绍如何搭建这个技术栈,从环境配置到核心代码实现,带你一步步掌握用自然语言生成 3D 可视化应用的完整流程。无论你是前端开发者想探索 AI 赋能,还是对 3D 可视化感兴趣的技术爱好者,都能从本文获得实用的开发方案。

1. 技术栈核心概念解析

1.1 Codex:自然语言到代码的桥梁

Codex 是 OpenAI 开发的 AI 模型,专门用于理解和生成代码。它能够将自然语言描述转换为可执行的代码片段,支持多种编程语言。在本文的应用场景中,Codex 负责解析用户对 3D 地球的描述(如"创建一个带有经纬线网格的蓝色地球"),并将其转换为 Three.js 或类似 3D 库的代码。

Codex 的核心价值在于大幅降低了 3D 图形编程的门槛。传统上,创建复杂的 3D 场景需要深厚的图形学知识和大量的代码编写,而现在通过自然语言指令就能快速生成基础框架。

1.2 tldraw:无限画布与协作白板

tldraw 是一个开源的无限画布应用,提供丰富的绘图工具和协作功能。它采用可扩展的架构设计,允许开发者通过插件方式扩展其功能。在我们的项目中,tldraw 不仅作为绘图界面,更重要的是作为用户与 Codex 交互的载体。

用户可以在 tldraw 画布上绘制草图或添加文字描述,这些内容会被实时捕获并发送给 Codex 进行处理。tldraw 的实时协作特性也为多人共同设计 3D 场景提供了可能。

1.3 Three.js:Web 3D 渲染引擎

Three.js 是当前最流行的 Web 3D 库,基于 WebGL 技术,提供了高级的 3D 图形渲染能力。它封装了复杂的底层图形 API,让开发者能够用相对简单的 JavaScript 代码创建复杂的 3D 场景、模型和动画。

在我们的应用中,Three.js 负责将 Codex 生成的 3D 地球代码渲染到网页中,提供旋转、缩放、平移等交互功能,确保用户能够从不同角度查看生成的 3D 地球。

2. 环境准备与项目搭建

2.1 开发环境要求

确保你的开发环境满足以下要求:

  • Node.js 16.0 或更高版本
  • npm 或 yarn 包管理器
  • 现代浏览器(支持 WebGL)
  • 稳定的网络连接(用于调用 Codex API)

2.2 创建项目结构

首先初始化一个新的 Node.js 项目:

# 创建项目目录 mkdir codex-tldraw-3d-earth cd codex-tldraw-3d-earth # 初始化 package.json npm init -y # 安装核心依赖 npm install tldraw @tldraw/tldraw three @types/three npm install --save-dev vite @vitejs/plugin-react typescript

创建项目文件结构:

codex-tldraw-3d-earth/ ├── src/ │ ├── components/ │ │ ├── TldrawCanvas.tsx │ │ ├── ThreeEarth.tsx │ │ └── CodexIntegration.tsx │ ├── utils/ │ │ ├── codexClient.ts │ │ └── threeHelpers.ts │ ├── App.tsx │ └── main.tsx ├── index.html ├── vite.config.ts ├── tsconfig.json └── package.json

2.3 配置 Vite 开发环境

创建vite.config.ts配置文件:

import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 3000, open: true }, build: { outDir: 'dist', sourcemap: true } })

配置 TypeScript 编译选项tsconfig.json

{ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] }

3. 核心组件实现

3.1 tldraw 画布组件

创建src/components/TldrawCanvas.tsx

import React, { useCallback } from 'react' import { Tldraw, TDAsset, TDBinding, TDShape, TDDocument, TDUser, useFileSystem } from '@tldraw/tldraw' interface TldrawCanvasProps { onShapeChange: (shapes: TDShape[]) => void onTextChange: (text: string) => void } export const TldrawCanvas: React.FC<TldrawCanvasProps> = ({ onShapeChange, onTextChange }) => { const handlePersist = useCallback((document: TDDocument) => { // 提取画布上的所有形状 const shapes = Object.values(document.pages[document.currentPageId].shapes) onShapeChange(shapes) // 提取文本内容 const textContent = shapes .filter(shape => shape.type === 'text') .map(shape => (shape as any).text) .join(' ') if (textContent) { onTextChange(textContent) } }, [onShapeChange, onTextChange]) const handleMount = useCallback((editor: any) => { // 设置画布初始提示文本 editor.createShapes([ { type: 'text', x: 100, y: 100, props: { text: '在这里描述你想要的3D地球...\n例如:蓝色地球,带有经纬线网格', color: 'black', size: 'm', font: 'draw', align: 'start', scale: 1, }, }, ]) }, []) return ( <div style={{ width: '100%', height: '600px', border: '1px solid #ccc' }}> <Tldraw onMount={handleMount} onPersist={handlePersist} showMenu={false} showPages={false} showStyles={false} showZoom={false} showUI={true} /> </div> ) }

3.2 Codex 集成服务

创建src/utils/codexClient.ts

interface CodexRequest { prompt: string max_tokens?: number temperature?: number } interface CodexResponse { choices: Array<{ text: string index: number logprobs: any finish_reason: string }> } export class CodexClient { private apiKey: string private baseURL: string = 'https://api.openai.com/v1' constructor(apiKey: string) { this.apiKey = apiKey } async generateThreeJSCode(description: string): Promise<string> { const prompt = `根据以下描述生成Three.js代码来创建一个3D地球: 描述: ${description} 要求: 1. 使用Three.js最新语法 2. 包含相机控制、光照和基础材质 3. 代码要完整可运行 4. 添加适当的注释 Three.js代码:` const response = await this.makeRequest({ prompt, max_tokens: 1500, temperature: 0.7 }) return response.choices[0]?.text?.trim() || '' } private async makeRequest(request: CodexRequest): Promise<CodexResponse> { const response = await fetch(`${this.baseURL}/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` }, body: JSON.stringify({ model: 'code-davinci-002', prompt: request.prompt, max_tokens: request.max_tokens, temperature: request.temperature }) }) if (!response.ok) { throw new Error(`Codex API请求失败: ${response.statusText}`) } return response.json() } } // 环境变量配置示例(实际使用时请使用环境变量) export const codexClient = new CodexClient('your-api-key-here')

3.3 Three.js 地球渲染组件

创建src/components/ThreeEarth.tsx

import React, { useRef, useEffect, useState } from 'react' import * as THREE from 'three' interface ThreeEarthProps { earthCode?: string onRenderComplete?: () => void } export const ThreeEarth: React.FC<ThreeEarthProps> = ({ earthCode, onRenderComplete }) => { const mountRef = useRef<HTMLDivElement>(null) const [scene, setScene] = useState<THREE.Scene | null>(null) const [camera, setCamera] = useState<THREE.PerspectiveCamera | null>(null) const [renderer, setRenderer] = useState<THREE.WebGLRenderer | null>(null) useEffect(() => { if (!mountRef.current) return // 初始化Three.js场景 const initScene = () => { const width = mountRef.current!.clientWidth const height = mountRef.current!.clientHeight // 创建场景 const scene = new THREE.Scene() scene.background = new THREE.Color(0x000000) // 创建相机 const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000) camera.position.z = 5 // 创建渲染器 const renderer = new THREE.WebGLRenderer({ antialias: true }) renderer.setSize(width, height) mountRef.current!.appendChild(renderer.domElement) setScene(scene) setCamera(camera) setRenderer(renderer) // 添加基础光照 const ambientLight = new THREE.AmbientLight(0x404040) scene.add(ambientLight) const directionalLight = new THREE.DirectionalLight(0xffffff, 1) directionalLight.position.set(1, 1, 1) scene.add(directionalLight) return { scene, camera, renderer } } const { scene, camera, renderer } = initScene() // 动画循环 const animate = () => { requestAnimationFrame(animate) if (scene && camera) { renderer.render(scene, camera) } } animate() // 清理函数 return () => { if (mountRef.current && renderer?.domElement) { mountRef.current.removeChild(renderer.domElement) } renderer?.dispose() } }, []) useEffect(() => { if (earthCode && scene) { try { // 清空现有场景 while (scene.children.length > 0) { scene.remove(scene.children[0]) } // 执行生成的代码 const createEarth = new Function( 'THREE', 'scene', earthCode ) createEarth(THREE, scene) onRenderComplete?.() } catch (error) { console.error('执行生成的3D代码时出错:', error) // 创建默认地球作为回退 createDefaultEarth(scene) } } }, [earthCode, scene, onRenderComplete]) const createDefaultEarth = (scene: THREE.Scene) => { // 创建默认的蓝色地球 const geometry = new THREE.SphereGeometry(2, 32, 32) const material = new THREE.MeshPhongMaterial({ color: 0x1e90ff, specular: 0x555555, shininess: 30 }) const earth = new THREE.Mesh(geometry, material) scene.add(earth) // 添加经纬线网格 const gridGeometry = new THREE.SphereGeometry(2.02, 32, 32) const gridMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true, transparent: true, opacity: 0.3 }) const grid = new THREE.Mesh(gridGeometry, gridMaterial) scene.add(grid) } return ( <div ref={mountRef} style={{ width: '100%', height: '600px', border: '1px solid #ccc', background: 'linear-gradient(to bottom, #001122, #003344)' }} /> ) }

4. 集成与交互逻辑

4.1 主应用组件集成

创建src/App.tsx

import React, { useState, useCallback } from 'react' import { TldrawCanvas } from './components/TldrawCanvas' import { ThreeEarth } from './components/ThreeEarth' import { codexClient } from './utils/codexClient' import './App.css' const App: React.FC = () => { const [earthCode, setEarthCode] = useState<string>('') const [isGenerating, setIsGenerating] = useState<boolean>(false) const [generationHistory, setGenerationHistory] = useState<string[]>([]) const handleTextChange = useCallback(async (text: string) => { if (text.length < 10) return // 忽略过短的文本 setIsGenerating(true) try { const generatedCode = await codexClient.generateThreeJSCode(text) setEarthCode(generatedCode) setGenerationHistory(prev => [...prev, text]) } catch (error) { console.error('生成3D代码失败:', error) alert('代码生成失败,请检查API密钥和网络连接') } finally { setIsGenerating(false) } }, []) const handleShapeChange = useCallback((shapes: any[]) => { // 可以在这里处理图形变化,比如根据绘制的形状生成对应的3D模型 console.log('画布形状变化:', shapes) }, []) const handleRenderComplete = useCallback(() => { console.log('3D地球渲染完成') }, []) return ( <div className="app"> <header className="app-header"> <h1>Codex + tldraw 3D地球生成器</h1> <p>在左侧画布描述或绘制你想要的3D地球,右侧将实时生成</p> </header> <div className="app-content"> <div className="canvas-section"> <h2>设计画布</h2> <TldrawCanvas onShapeChange={handleShapeChange} onTextChange={handleTextChange} /> <div className="generation-status"> {isGenerating ? '正在生成3D代码...' : '准备就绪'} </div> </div> <div className="preview-section"> <h2>3D预览</h2> <ThreeEarth earthCode={earthCode} onRenderComplete={handleRenderComplete} /> <div className="code-preview"> <h3>生成的Three.js代码</h3> <pre>{earthCode || '// 生成的代码将显示在这里'}</pre> </div> </div> </div> {generationHistory.length > 0 && ( <div className="history-section"> <h3>生成历史</h3> <ul> {generationHistory.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </div> )} </div> ) } export default App

4.2 样式文件配置

创建src/App.css

.app { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; max-width: 1400px; margin: 0 auto; padding: 20px; } .app-header { text-align: center; margin-bottom: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px; } .app-header h1 { margin: 0 0 10px 0; font-size: 2.5em; } .app-header p { margin: 0; opacity: 0.9; font-size: 1.1em; } .app-content { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px; } .canvas-section, .preview-section { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .canvas-section h2, .preview-section h2 { color: #333; margin-top: 0; border-bottom: 2px solid #667eea; padding-bottom: 10px; } .generation-status { margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 5px; text-align: center; font-weight: bold; color: #495057; } .code-preview { margin-top: 20px; } .code-preview h3 { margin-bottom: 10px; color: #333; } .code-preview pre { background: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto; max-height: 200px; font-size: 0.9em; border: 1px solid #dee2e6; } .history-section { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .history-section h3 { margin-top: 0; color: #333; } .history-section ul { list-style: none; padding: 0; } .history-section li { padding: 8px 12px; margin-bottom: 5px; background: #f8f9fa; border-radius: 5px; border-left: 4px solid #667eea; } @media (max-width: 768px) { .app-content { grid-template-columns: 1fr; } .app-header h1 { font-size: 2em; } }

5. 应用启动与测试

5.1 主入口文件

创建src/main.tsx

import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App /> </React.StrictMode> )

创建index.html

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Codex + tldraw 3D地球生成器</title> <style> body { margin: 0; padding: 0; background: #f5f5f5; } #root { min-height: 100vh; } </style> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html>

5.2 启动开发服务器

package.json中添加启动脚本:

{ "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" } }

启动开发服务器:

npm run dev

访问http://localhost:3000即可看到应用界面。

5.3 测试用例验证

测试不同的自然语言描述,验证生成效果:

  1. 基础地球测试

    • 输入:"创建一个蓝色的3D地球,带有经纬线网格"
    • 预期:生成蓝色球体,带有白色网格线
  2. 详细特征测试

    • 输入:"创建有陆地海洋区别的地球,陆地绿色,海洋蓝色,添加云层效果"
    • 预期:生成有纹理差异的地球,可能带有透明云层
  3. 高级效果测试

    • 输入:"创建带自转动画的逼真地球,有大气层光晕效果"
    • 预期:生成自动旋转的地球,带有光晕特效

6. 常见问题与解决方案

6.1 Codex API 相关问题

问题1:API 密钥配置错误

  • 现象:控制台报错 "Codex API请求失败"
  • 解决方案:
    • 检查 API 密钥是否正确配置
    • 确认 API 密钥有足够的额度
    • 验证网络连接是否正常
// 正确的API密钥配置方式 // 方式1:环境变量(推荐) const apiKey = import.meta.env.VITE_OPENAI_API_KEY // 方式2:配置文件 const apiKey = process.env.REACT_APP_OPENAI_API_KEY

问题2:生成代码质量不稳定

  • 现象:生成的 Three.js 代码有时无法运行
  • 解决方案:
    • 优化提示词(prompt)的准确性
    • 调整 temperature 参数(0.3-0.7 之间)
    • 添加代码验证和错误处理机制

6.2 Three.js 渲染问题

问题1:3D 场景不显示

  • 现象:画布空白,控制台无报错
  • 解决方案:
    • 检查 WebGL 支持:detector.webgl
    • 验证相机位置和朝向
    • 确认光照设置正确
// 检测WebGL支持 if (!Detector.webgl) { Detector.addGetWebGLMessage() }

问题2:性能问题

  • 现象:3D 场景卡顿,帧率低
  • 解决方案:
    • 减少几何体面数
    • 使用 LOD(Level of Detail)技术
    • 优化材质和纹理大小

6.3 tldraw 集成问题

问题1:画布事件不触发

  • 现象:绘制内容变化时没有回调
  • 解决方案:
    • 检查组件挂载顺序
    • 验证事件监听器是否正确绑定
    • 确认 tldraw 版本兼容性

问题2:自定义形状支持

  • 现象:需要扩展 tldraw 支持更多图形类型
  • 解决方案:
    • 实现自定义工具(Tools)
    • 扩展形状定义(ShapeUtil)
    • 注册自定义组件

7. 高级功能扩展

7.1 实时协作功能

实现多用户同时编辑和查看 3D 生成结果:

// 使用Socket.io实现实时协作 import { io, Socket } from 'socket.io-client' class CollaborationService { private socket: Socket constructor() { this.socket = io('http://localhost:3001') } joinRoom(roomId: string) { this.socket.emit('join-room', roomId) } onEarthCodeUpdate(callback: (code: string) => void) { this.socket.on('earth-code-update', callback) } broadcastEarthCode(code: string) { this.socket.emit('earth-code-broadcast', code) } }

7.2 3D 模型导出功能

添加将生成的 3D 地球导出为标准格式的功能:

import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter' class ModelExporter { static exportToGLTF(scene: THREE.Scene): Promise<Blob> { return new Promise((resolve) => { const exporter = new GLTFExporter() exporter.parse(scene, (gltf) => { const blob = new Blob([JSON.stringify(gltf)], { type: 'application/json' }) resolve(blob) }) }) } static downloadBlob(blob: Blob, filename: string) { const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = filename link.click() URL.revokeObjectURL(url) } }

7.3 提示词优化模板

创建针对不同 3D 效果的专用提示词模板:

const promptTemplates = { basicEarth: `生成一个基础的3D地球模型,包含以下特征: - 球体几何体 - 蓝色材质表示海洋 - 经纬线网格 - 适当的光照设置`, realisticEarth: `生成一个逼真的3D地球模型,包含: - 高分辨率纹理贴图 - 法线贴图增强立体感 - 镜面反射效果 - 大气层光晕 - 自转动画`, stylizedEarth: `生成一个风格化的3D地球模型: - 卡通渲染风格 - 鲜艳的色彩 - 简化的几何形状 - 特殊的着色器效果` } export class AdvancedCodexClient extends CodexClient { async generateWithTemplate(templateType: keyof typeof promptTemplates, customDescription: string) { const basePrompt = promptTemplates[templateType] const fullPrompt = `${basePrompt}\n用户额外要求: ${customDescription}` return this.generateThreeJSCode(fullPrompt) } }

8. 生产环境部署

8.1 构建优化配置

优化 Vite 构建配置以提高性能:

// vite.config.prod.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], build: { outDir: 'dist', sourcemap: false, minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true } }, rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], three: ['three'], tldraw: ['@tldraw/tldraw'] } } } } })

8.2 环境变量管理

创建环境配置文件:

# .env.development VITE_OPENAI_API_KEY=your_dev_api_key VITE_API_BASE_URL=http://localhost:3001 # .env.production VITE_OPENAI_API_KEY=your_prod_api_key VITE_API_BASE_URL=https://api.yourdomain.com

8.3 部署脚本

创建自动化部署脚本:

#!/bin/bash # deploy.sh echo "开始构建生产版本..." npm run build echo "检查构建结果..." if [ -d "dist" ]; then echo "构建成功,开始部署..." # 这里添加你的部署逻辑 # 例如:rsync到服务器、上传到CDN等 echo "部署完成" else echo "构建失败,请检查错误信息" exit 1 fi

通过本文的完整实现,你已经掌握了使用 Codex 和 tldraw 创建智能 3D 地球应用的核心技术。这种自然语言驱动的前端开发模式,为快速原型设计和创意表达提供了新的可能性。在实际项目中,你可以根据具体需求进一步扩展功能,如支持更多 3D 模型类型、集成更多 AI 能力或优化用户体验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询