Threejs开发指南(第七篇 利用AI进行threejs开发)

2014年生成式对抗网络(GAN)的提出开启了 AIGC 新时代。2017 年,微软人工智能小冰推出首部人工智能创作的诗集。2018 年,英伟达的 StyleGAN 模型可自动生成图片。2021 年,OpenAI 推出 DALL-E,后续升级版本能生成高质量绘画作品。2023 年,多家科技巨头发布生成式 AI 相关产品。2025 年,《2025 中国 AIGC 应用全景图谱报告》发布,指出国内 AIGC 模型层在推动大模型普惠,AI 产品第一轮变革基本完成,头部格局相对清晰。

在编程方面,现代 AIGC 系统能生成 PythonJavaScriptJava 等多种编程语言的代码,可根据问题描述或需求生成相应代码解决方案,从简单数学问题求解到复杂的图像识别、自然语言处理等场景都有涉及,还可用于游戏开发、企业级应用开发等,能实现代码补全、修改代码、生成注释等功能,正被集成到现有的开发工具和流程中,如 IDE、代码审查工具、持续集成 / 持续部署(CI/CD)流程等。

本章我们介绍如何利用AIGC进行threejs代码的开发,需要注意的是:尽管 AIGC 生成代码的性能和准确性不断提高,但生成的代码仍需人工审查和测试以确保质量。

7.1 利用DeepSeek进行threejs编程

2024 4 月,DeepSeek 大语言模型算法备案通过。5 月,发布了 DeepSeek-V2 模型,性能显著提升且成本更低。2025 1 月,DeepSeek 在中国区应用商店免费 APP 下载榜登顶。DeepSeek是基于 Transformer 架构的,通过长序列优化、分组查询注意力等技术,可处理更长代码片段,推理速度快,能根据自然语言描述生成代码片段或完整函数,支持 PythonJavaScriptJavaSQL 等多种编程语言,能应用于全栈开发、数据分析等多个领域,满足不同开发者需求。

本小节介绍如何利用DeepSeek进行threejs编程。

在DeepSeek中输入提示语:

“请用threejs帮我写一个表达常用数学函数图像的HTML程序,要求函数参数可调。”

DeepSeek生成的代码如下(请注意每次生成的代码可能不同):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数学函数可视化</title>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.min.js"></script>
    <style>
        body { margin: 0; overflow: hidden; }
        #info {
            position: absolute;
            top: 10px;
            left: 10px;
            color: white;
            background: rgba(0,0,0,0.5);
            padding: 10px;
            border-radius: 5px;
            font-family: Arial, sans-serif;
        }
    </style>
</head>
<body>
    <div id="info">
        <h2>数学函数可视化</h2>
        <div id="gui-container"></div>
    </div>
    <script>
        // 初始化场景、相机和渲染器
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x111111);
        document.body.appendChild(renderer.domElement);

        // 添加坐标轴辅助 (红色-X, 绿色-Y, 蓝色-Z)
        const axesHelper = new THREE.AxesHelper(10);
        scene.add(axesHelper);

        // 添加网格辅助
        const gridHelper = new THREE.GridHelper(20, 20, 0x555555, 0x333333);
        scene.add(gridHelper);

        // 相机位置
        camera.position.set(15, 15, 15);
        camera.lookAt(0, 0, 0);

        // 函数参数控制
        const params = {
            functionType: 'sin',
            amplitude: 2,
            frequency: 1,
            phase: 0,
            offset: 0,
            a: 0.1,
            b: 0,
            c: 0,
            range: 10,
            segments: 200,
            lineWidth: 2,
            color: '#ff6600'
        };

        // 创建GUI控制器
        const gui = new dat.GUI({ autoPlace: false });
        document.getElementById('gui-container').appendChild(gui.domElement);

        // 函数类型文件夹
        const funcFolder = gui.addFolder('函数类型');
        funcFolder.add(params, 'functionType', [
                'sin', 'cos', 'tan', 
                'parabola', 'cubic', 
                'exponential', 'logarithm',
                'sinc', 'abs'
            ]).name('选择函数').onChange(updateFunction);
        
        // 通用参数文件夹
        const commonFolder = gui.addFolder('通用参数');
        commonFolder.add(params, 'amplitude', 0.1, 5, 0.1).name('振幅').onChange(updateFunction);
        commonFolder.add(params, 'frequency', 0.1, 5, 0.1).name('频率').onChange(updateFunction);
        commonFolder.add(params, 'phase', -Math.PI, Math.PI, 0.1).name('相位').onChange(updateFunction);
        commonFolder.add(params, 'offset', -5, 5, 0.1).name('垂直偏移').onChange(updateFunction);
        commonFolder.add(params, 'range', 1, 20, 1).name('显示范围').onChange(updateFunction);
        commonFolder.add(params, 'segments', 50, 500, 10).name('分段数').onChange(updateFunction);
        
        // 特殊参数文件夹
        const specialFolder = gui.addFolder('特殊参数');
        specialFolder.add(params, 'a', -1, 1, 0.01).name('a系数').onChange(updateFunction);
        specialFolder.add(params, 'b', -5, 5, 0.1).name('b系数').onChange(updateFunction);
        specialFolder.add(params, 'c', -5, 5, 0.1).name('c系数').onChange(updateFunction);
        
        // 显示样式文件夹
        const styleFolder = gui.addFolder('显示样式');
        styleFolder.add(params, 'lineWidth', 1, 5, 1).name('线宽').onChange(updateFunction);
        styleFolder.addColor(params, 'color').name('线条颜色').onChange(updateFunction);
        
        // 默认打开通用参数文件夹
        commonFolder.open();

        // 函数曲线对象
        let curve = null;

        // 计算函数值
        function calculateFunction(x) {
            switch(params.functionType) {
                case 'sin':
                    return params.amplitude * Math.sin(params.frequency * x + params.phase) + params.offset;
                case 'cos':
                    return params.amplitude * Math.cos(params.frequency * x + params.phase) + params.offset;
                case 'tan':
                    return params.amplitude * Math.tan(params.frequency * x + params.phase) + params.offset;
                case 'parabola':
                    return params.a * x * x + params.b * x + params.c;
                case 'cubic':
                    return params.a * x * x * x + params.b * x * x + params.c * x;
                case 'exponential':
                    return params.amplitude * Math.exp(params.frequency * x) + params.offset;
                case 'logarithm':
                    return params.amplitude * Math.log(Math.abs(x) + 0.1) + params.offset;
                case 'sinc':
                    return x === 0 ? 1 : params.amplitude * Math.sin(params.frequency * x) / (params.frequency * x) + params.offset;
                case 'abs':
                    return params.amplitude * Math.abs(params.frequency * x + params.phase) + params.offset;
                default:
                    return 0;
            }
        }

        // 更新函数曲线
        function updateFunction() {
            // 移除旧的曲线
            if (curve) {
                scene.remove(curve);
            }
            
            // 创建点集
            const points = [];
            const step = (2 * params.range) / params.segments;
            
            for (let i = 0; i <= params.segments; i++) {
                const x = -params.range + (i * step);
                const y = calculateFunction(x);
                
                // 限制y值范围避免渲染问题
                const clampedY = Math.min(Math.max(y, -params.range*2), params.range*2);
                points.push(new THREE.Vector3(x, clampedY, 0));
            }
            
            // 创建曲线几何体
            const geometry = new THREE.BufferGeometry().setFromPoints(points);
            const material = new THREE.LineBasicMaterial({ 
                color: new THREE.Color(params.color),
                linewidth: params.lineWidth
            });
            
            curve = new THREE.Line(geometry, material);
            scene.add(curve);
        }

        // 初始绘制
        updateFunction();

        // 添加轨道控制器
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;

        // 窗口大小调整
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        // 动画循环
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }

        animate();
    </script>
</body>
</html>

这段代码约200行,DeepSeek可以在30秒之内写完,但这段代码并不能够直接运行,在浏览器中运行时会提示错误:“Uncaught TypeError: THREE.OrbitControls is not a constructor”。

错误的原因是因为没有引入“THREE.OrbitControls”类,导致轨道控制器实例化失败,这说明AI生成的代码是不保证完全正确的,有时候需要经过人工修改才行。

修改这段代码,在网页的<head>标记中引入“THREE.OrbitControls”类,如下:

<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>

修改后程序可以运行了,运行效果如图7.1所示。

图7.1 正弦函数可视化

可以看到这是一个三维空间中的正弦波函数图像,可以利用鼠标进行缩放、旋转、拖拽操作,振幅、频率、相位等函数参数均可动态调整。

代码中一共提供了9种常见的数学函数:sin(正弦)、cos(余弦)、tan(正切)、parabola(二次函数,抛物线)、cubic(三次函数)、exponential(自然指数)、logarithm(自然对数)、sinc(辛格函数)、abs(绝对值),当选中抛物线函数时,显示图像如图7.2所示。

图7.2 抛物线函数可视化

这段代码中包含了axesHelper、gridHelper、dat.GUI、BufferGeometry、LineBasicMaterial、OrbitControls等常用的Threejs组件,并能够将他们和数学函数表达式关联并生成函数图像,说明DeepSeek理解了我们输入的提示语,并进行了有效的编程,同时在代码的关键地方都标有注释,这个工作对于计算机来说其实是多余的,说明DeepSeek还能够模拟人类的编程习惯,这有助于我们更好的阅读、使用他生成的代码。

这种通过文本描述来生成threejs程序的方法,给我们提供了极大的灵活性,大大拓展了自由创意的空间,而且几乎没有什么工作量,又比如我们输入提示语:

“请用threejs帮我制作一个科幻风格的太空站。”

生成的代码可以直接运行,效果如图7.3所示,一个太空站在背景星空中缓慢旋转,尽管太空站本身看起来有些简陋,但场景已经具备了一些科幻风格。

图7.3 科幻风格的太空站

7.2 利用ima部署本地知识库

直接利用DeepSeek进行threejs编程有一个问题,他无法利用本书之前介绍的ThreeSim框架,因为ThreeSim框架并没有包含在通用大模型的语料库中,这导致AI生成的代码无法与我们的真实项目紧密耦合。

本小节我们利用ima,部署本地的个人知识库,将所有的程序分成两大类导入知识库,并基于本地知识库进行AI编程,解决代码耦合问题。

  1. 通用类:如ThreeBase、ThreeSim、TextObject、Sound3D、Octree、Logger、KeyFrameAnimation、GPLControls、GameControls等等,用于定义程序的基本框架。
  2. 程序类:如chapter1.firstApp、chapter2.keyFrameAnimation等等,用于解释如何利用通用类进行代码编写。

ima的个人知识库仅支持特定格式的文件,比如doc、pdf、jpg、png、ppt等等,而我们的文件扩展名是html(网页文件)或者js(脚本文件),在导入之前需要将他们另存成doc格式,导入后的效果如图7.4所示,根据需要,还可以为每一个文件添加标签,这可以提高搜索的精度。

需要注意一点,ima的个人知识库并没有集成到我们的IDE开发环境中来,因此生成的代码中,个别地方是需要略做修改的,比如修改文件路径、添加材质文件问题等。

 

图7.4 ima个人知识库

利用本地知识库发起提问:

“请利用ThreeApp、ThreeObject类,开发一个投篮游戏,要求生成两个文件:game.html和game.js。。”

ima的思考过程如下:

图7.5 ima思考过程

最终生成的网页网页和脚本文件,在程序结构、代码逻辑上基本和我们开发的项目文件一致,几乎不需要修改就能正常运行。

7.2.1 网页文件

<!DOCTYPE html>
<html lang="zh-CN">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>投篮游戏</title>
		<style>
			* {
				margin: 0;
				padding: 0;
				box-sizing: border-box;
			}
			body {
				overflow: hidden;
				font-family: 'Arial', sans-serif;
				background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
				height: 100vh;
				display: flex;
				flex-direction: column;
			}
			#container {
				flex: 1;
				position: relative;
			}
			#ui {
				position: absolute;
				top: 20px;
				left: 20px;
				background: rgba(0, 0, 0, 0.7);
				color: white;
				padding: 15px;
				border-radius: 10px;
				z-index: 10;
			}
			#score {
				font-size: 24px;
				margin-bottom: 10px;
			}
			#controls {
				display: flex;
				flex-direction: column;
				gap: 10px;
			}
			button {
				background: #ff8c00;
				color: white;
				border: none;
				padding: 8px 15px;
				border-radius: 5px;
				cursor: pointer;
				font-size: 16px;
				transition: all 0.3s;
			}
			button:hover {
				background: #ff6a00;
				transform: scale(1.05);
			}
			#power-bar {
				width: 100%;
				height: 20px;
				background: #333;
				border-radius: 10px;
				margin-top: 10px;
				overflow: hidden;
			}
			#power-fill {
				height: 100%;
				width: 0%;
				background: linear-gradient(to right, #4CAF50, #FFC107, #FF5722);
				transition: width 0.1s;
			}
			#instructions {
				position: absolute;
				bottom: 20px;
				left: 20px;
				background: rgba(0, 0, 0, 0.7);
				color: white;
				padding: 15px;
				border-radius: 10px;
				max-width: 400px;
			}
			#instructions h3 {
				margin-bottom: 10px;
				color: #ff8c00;
			}
			#instructions p {
				margin: 5px 0;
				line-height: 1.5;
			}
		</style>
	</head>
	<body>
		<div id="container"></div>
		<div id="ui">
			<div id="score">得分: 0</div>
			<div id="controls">
				<button id="reset-btn">重置篮球</button>
				<button id="reset-game">新游戏</button>
				<div id="power-container">
					<div>力度控制:</div>
					<div id="power-bar">
						<div id="power-fill"></div>
					</div>
				</div>
			</div>
		</div>
		<div id="instructions">
			<h3>游戏说明</h3>
			<p>1. 按住鼠标左键蓄力,松开投篮</p>
			<p>2. 力度条显示当前投篮力度</p>
			<p>3. 篮球入网得1分</p>
			<p>4. 按"重置篮球"重新放置篮球</p>
			<p>5. 按"新游戏"重置得分</p>
		</div>
		<script type="importmap">
			{ "imports": { "three": "./js_three/build/three.module.min.js" } }
		</script>
		<script type="module" src="chapter7.2.ima.js"></script>
	</body>
</html>

该文件完全有ima自动生成,未做任何修改。

7.2.2 脚本文件

脚本文件共约380行,由于代码量较大,此处仅列出文件的结构,如图7.6所示。

图7.6 脚本文件结构

这段生成的代码中,共设计了两个ThreeObject类:篮筐类(BasketballHoop)和篮球类(Basketball),一个ThreeApp类:应用程序类(MyApp),最后实例化MyApp类并运行程序。

我们在两个地方对程序进行了细微调整,一是篮球的材质中要求一副图片纹理(见下面代码中第二行),我们添加了一副(这项工作其实已经超出了AI的能力范畴)。

const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('./images/basketball_texture.jpg');
const material = new THREE.MeshPhongMaterial({
    map: texture,
    shininess: 30
});

二是修改了投篮力度,原代码中,投篮力度不够,篮球永远无法入框,我们将投篮力度提高了10倍(见下面代码中的第二行)。

shoot(angle, power) {
    let k = 10;
    this.velocity.set(
        Math.sin(angle) * power * k,
        8 + power * k * 0.5,
        Math.cos(angle) * power * k
    );
    this.inMotion = true;
}

游戏运行截图如图7.7所示,我试着投了几次,只有一次投入了篮筐,游戏难度偏大,后期可以进行人工调整。

图7.7 投篮游戏

从该案例中我们可以发现,ima自动生成的代码,其原理和结构与我们之前喂给ima的语料库的内容是完全一致的,表明ima已经掌握了ThreeSim框架编程的要领,完全可以自主开发项目了。

更好的消息是随着语料库内容的不断增加,ima自动编程的能力也会越来越强,这将极大的解放我们的编程工作,在未来,通过文本性的需求描述来进行软件项目开发或许是软件工程师的工作常态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值