Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >用 Three.js 画一个哆啦A梦的时光机

用 Three.js 画一个哆啦A梦的时光机

作者头像
神说要有光zxg
发布于 2023-08-28 11:37:06
发布于 2023-08-28 11:37:06
57300
代码可运行
举报
运行总次数:0
代码可运行

想必大家都看过哆啦A梦,时光机是里面的常用道具。

那坐时光机是什么样的体验呢?

我用 Three.js 写了一下,应该是这种感觉:

我们一起来实现一下。

首先,我们过一下 Three.js 的基础:

在二维屏幕上渲染三维物体,得有个坐标轴。

在 three.js 里以向右的方向为 x 轴,向上的方向为 y 轴,向前的方向为 z 轴:

然后管理在三维坐标系里的物体得有个对象体系。

Three.js 的对象体系是这样的:

image.png

所有三维场景中的东西都加到 scene 里来管理。

三维世界本来是黑的,有了 light 之后才能看到东西,有点光源、环境光等不同的光源。

三维世界中的物体,可以从不同角度去观察,改变位置就可以看到不同的风景,这就是相机 camera 的事情。

三维世界中的物体叫做 mesh,任何一个物体都有一个形状,比如圆柱、立方体等,也就是 geometry,然后还得有材质 material,比如金属材质可以反光、普通材质不能。材质可以指定颜色、还可以指定图片作为纹理 texture。

场景中的所有物体,会由渲染器 WebGLRenderer 渲染出来。

场景、物体、灯光、相机、渲染器,这就是 three.js 的核心概念。

每一个物体都可以设置位置 position、缩放 scale、旋转 rotation。

每一帧渲染的时候,改变物体的位置、颜色、旋转角度等就可以实现动画效果了。

大家想一下,时空隧道用什么几何体比较合适呢?

很明显,是圆柱,也就是 CylinderGeometry

在 three.js 文档中可以看到预览大概是这样样子:

示例代码是这样的:

创建一个圆柱几何体 CylinderGeometry ,传入上圆半径、下圆半径,高度,分段数量(分的多了就是圆了)。

指定材质,用 MeshBasicMaterial,指定蓝色。

然后就可以创建一个物体 Mesh,把它加到场景 scene 里。

我们可以创建一个圆柱,内部贴上图,然后相机放在圆柱内部,是不是看到的就是一个隧道了?

圆柱体的材质我们用纹理贴图,比如这种:

这个纹理是可以设置重复 repeat 和偏移 offset 的。

用 TextureLoader 加载图片作为纹理,设置 wrapS 为 repeat,也就是水平重复、wrapT 为竖直重复。

T 是 vertical 的缩写,而 S 就是 horizontal 了。

然后 repeat.set(4, 4) 每个单位内水平方向重复 4 次、竖直方向重复 4 次。

这样就完成了纹理贴图。

然后每一帧渲染的时候,让纹理的 offset 不断增加或减少,再让圆柱不断旋转,不就实现了时空隧道效果么?

我们来写下试试:

先写个 html,引入 three.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>时光机</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
    <script src="https://www.unpkg.com/three@0.154.0/build/three.js"></script>
</head>
<body>
<script>
 
</script>
</body>
</html>

我们先创建场景 scene 和 renderer:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const width = window.innerWidth;
const height = window.innerHeight;

const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer();

renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);

调整画布大小为窗口宽高。

然后创建相机:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);

camera.position.set(0,0, 500);
camera.lookAt(scene.position);

这里创建的是透视相机,也就是近大远小那种。

它有 4 个参数:

从相机往前看,会有个角度 fov,这是第一个参数。

然后视野范围的矩形会有个宽高比 aspect,这是第二个参数。

视野范围会形成一个椎体,叫做视椎体,三四个参数是指定视椎体的范围,从哪里看到哪里。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);

所以 PerspectiveCamera 的这 4个参数分别制定了 45 度的观察角度,宽高比和窗口宽高比一样。视野范围为 0.1 到 10000。

然后就可以一帧帧渲染了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

只不过现在还没有物体,我们添加个立方体 BoxGeometry 试试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function create() {
    const geometry = new THREE.BoxGeometry( 100, 100, 100 ); 
    const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); 
    const cube = new THREE.Mesh( geometry, material ); 
    cube.rotation.y = 0.5;
    cube.rotation.x = 0.5;
    scene.add( cube );
}
create();

添加一个 Mesh,用 BoxGeometry 创建立方体,长宽高为 100,用 MeshBasicMaterial 指定材质,颜色为绿色。

让这个 mesh 绕 y 和 x 旋转 0.5 的角度。

渲染出来的是这样的:

确实是个立方体,只不过没有明暗变化。

我们加个点光源就好了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const pointLight = new THREE.PointLight( 0xffffff );
pointLight.position.set(0, 0, 500);
scene.add(pointLight);

创建白色的灯光,放在和相机同一个位置,来照向场景中心的位置。

但是你刷新页面会发现没有变化,因为 MeshBasicMaterial 的材质是不反光的。

换成 MeshPhongMaterial 的材质,这种材质有金属质感,会反光。

就这样了:

然后我们来创建圆柱体:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let tunnel;
function create() {
    const geometry = new THREE.CylinderGeometry( 30, 50, 100, 32, 32, true);
    const material = new THREE.MeshPhongMaterial({ 
            color: 0x0000ff
    });
    tunnel = new THREE.Mesh(geometry, material);
    scene.add(tunnel);
}

上圆半径 30、下圆半径 50,高 100,分 32 段(差不多就是圆了)。

用 MeshPhongMaterial 指定金属材质为 蓝色。

渲染出来的是这样的:

我们让它绕 z 轴旋转下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function render() {
    renderer.render(scene, camera);

    tunnel.rotation.z = tunnel.rotation.z + 0.01;
    requestAnimationFrame(render);
}

很明显,我们观察的方向不对,应该是看到下圆的底才对。所以让它绕 x 轴逆时针旋转 90 度。

然后去掉底,这个是在创建圆柱体的时候指定:

再让圆柱绕 x 轴旋转下看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
tunnel.rotation.x = tunnel.rotation.x + 0.01;

确实,没有底了。

然后我们来处理下贴图。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let tunnel;
function create() {
    const textureLoader = new THREE.TextureLoader();
    textureLoader.load('./storm.jpg', function(texture) {
        texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
        texture.repeat.set( 1, 2 );

        const geometry = new THREE.CylinderGeometry( 30, 50, 100, 32, 32, true);
        const material = new THREE.MeshPhongMaterial({ 
                map: texture,
                side: THREE.DoubleSide
        });
        tunnel = new THREE.Mesh(geometry, material);

        tunnel.rotation.x = -Math.PI / 2;

        scene.add(tunnel);
    });
}

用 TextureLoader 加载纹理图片,设置水平和竖直方向重复。

然后把这个 texture 设置为纹理。

就是这样的:

把圆柱体高度改为 1000。

看到的就像是一个隧道了:

让 tunnel 绕 y 轴转起来:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function render() {
    renderer.render(scene, camera);

    if(tunnel) {
        tunnel.rotation.y += 0.01;
    }
    requestAnimationFrame(render);
}

我们不是在 z 轴的方向看向中心点么?为什么不是绕 z 轴转?

因为这个圆柱已经绕 x 轴顺时针转了 90 度,所以是绕 y 轴转,看到的是绕 z 轴转的效果。

然后我们再让纹理的 offset 也动起来:

就有穿梭隧道的感觉了:

不过如果我们想实现变色,最好不要直接把贴图作为纹理,而是用它来做透明通道,也就是这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const material = new THREE.MeshPhongMaterial({ 
    transparent: true,
    alphaMap: texture,
    side: THREE.BackSide
});

设置透明,然后图片作为透明通道,就会根据不同像素的颜色来设置不同的透明度。

也就是这样的效果:

然后只要设置不同的颜色,并且不断地变色就好了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let H = 0;
function render() {
    renderer.render(scene, camera);

    H += 0.001;
    if (H > 1) { H = 0; }

    if(tunnel) {
        tunnel.material.color.setHSL(H, 0.5, 0.5);

        tunnel.rotation.y += 0.01;
        stormTexture.offset.y += 0.01;
    }
    requestAnimationFrame(render);
}

我们用 HSL 颜色标识法,也就是色相、饱和度、明度。

色相是从 0 到 1 的数值,我们在每一帧改变色相的值。

效果是这样的:

隧道完成了,我们再加个时光机的底座。

这个比较简单,就是一个立方体,调整下位置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const geometry = new THREE.BoxGeometry( 30, 2, 30 ); 

const material = new THREE.MeshPhongMaterial( {
    color: 0x0000ff
});

const cube = new THREE.Mesh( geometry, material ); 
cube.position.z = 460;
cube.position.y = -20;
scene.add( cube );

相机在 0, 0, 500,那底座就在 460 就好了,再沿着 y 轴往下一点。

这个颜色不大好,我们还是换成贴图。

找个金属的纹理图片,比如这个:

用 TextureLoader 把纹理图片加载进来,设置水平、竖直的重复。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
textureLoader.load('./metal.png', function(texture) {
    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
    texture.repeat.set( 10, 10 );

    const geometry = new THREE.BoxGeometry( 30, 2, 30 ); 

    const material = new THREE.MeshPhongMaterial( {
        map: texture
    });

    const cube = new THREE.Mesh( geometry, material ); 
    cube.position.z = 460;
    cube.position.y = -20;
    scene.add( cube );
}); 

这样,我们的时光机效果完成了!

全部代码上传了 github:https://github.com/QuarkGluonPlasma/threejs-exercize

不到 100 行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>时光机</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
    <script src="https://www.unpkg.com/three@0.154.0/build/three.js"></script>
</head>
<body>
<script>
    const width = window.innerWidth;
    const height = window.innerHeight;
    const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);

    const scene = new THREE.Scene();
    const renderer = new THREE.WebGLRenderer();

    renderer.setSize(width, height);
    camera.position.set(0, 0, 500);
    camera.lookAt(scene.position);

    const pointLight = new THREE.PointLight( 0xffffff );
    pointLight.position.set(0, 0, 500);
    scene.add(pointLight);

    document.body.appendChild(renderer.domElement)

    let tunnel;
    let stormTexture;
    function create() {
        const textureLoader = new THREE.TextureLoader();
        textureLoader.load('storm.jpg', function(texture) {
            stormTexture = texture;

            texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
            texture.repeat.set( 1, 2 );

            const geometry = new THREE.CylinderGeometry( 30, 50, 1000, 32, 32, true);
            const material = new THREE.MeshPhongMaterial({ 
                transparent: true,
                alphaMap: texture,
                side: THREE.BackSide
            });
            tunnel = new THREE.Mesh(geometry, material);

            tunnel.rotation.x = -Math.PI / 2;

            scene.add(tunnel);
        });

        textureLoader.load('metal.png', function(texture) {
            texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
            texture.repeat.set( 10, 10 );

            const geometry = new THREE.BoxGeometry( 30, 2, 30 ); 

            const material = new THREE.MeshPhongMaterial( {
                map: texture
            });

            const cube = new THREE.Mesh( geometry, material ); 
            cube.position.z = 460;
            cube.position.y = -20;
            scene.add( cube );
        }); 
    }

    let H = 0;
    function render() {
        renderer.render(scene, camera);

        H += 0.002;
        if (H > 1) { H = 0; }

        if(tunnel) {
            tunnel.material.color.setHSL(H, 0.5, 0.5);

            tunnel.rotation.y += 0.01;
            stormTexture.offset.y += 0.01;
        }
        requestAnimationFrame(render);
    }

    create();
    render();

</script>
</body>
</html>

总结

今天我们用 Three.js 实现了时光机的效果。

首先,过了下 Three.js 的基础:

  • 向右为 x 轴、向上为 y 轴,向前为 z 轴
  • 场景 scene 管理灯光 light、相机 camera、物体mesh 等,然后通过渲染器 renderer 渲染出来
  • 物体 mesh 由几何体 geometry 和材质 material 构成,material 可以指定 color 或者纹理 texture
  • camera 一般用透视相机,也就是近大远小的效果

实现时空隧道的效果,就是创建了一个圆柱体,贴上纹理图片,然后把相机放到圆柱体内。

每帧渲染不断改变纹理的 offset 和圆柱体的 rotation。

此外,我们不是直接贴的图,而是把它作为透明度通道,这样可以实现变色效果,结合 HSL 改变色相的方式来变色。

最后,还加了一个立方体的几何体作为时光机底座。

用 Three.js 画一个时光机,一起穿越时空隧道吧。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 神光的编程秘籍 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
32. 镜头、曝光,以及对焦(下)
在镜头、曝光,以及对焦(上)中,我们看到了采用镜头能获得更加清晰和更高信噪比的图像,理解了薄透镜模型的几何关系,景深以及影响景深大小的典型因素,真实镜头和薄透镜模型不同的地方。
HawkWang
2020/04/17
1.1K0
32. 镜头、曝光,以及对焦(下)
手机中的计算摄影1——人像模式(双摄虚化)
很多人咨询我,手机上到底有哪些计算摄影的应用和技术。那么接下来就准备抽空写一系列文章做一下介绍。
HawkWang
2021/09/01
2.6K0
IQE14: 色差(Chromatic aberration)产生原理及去除
各位朋友,欢迎来到新的篇章!在前面的文章中,我们学习了如何去除图像中的镜头畸变和晕影(Vignetting),特别的是我还在上一篇文章中讨论了去除晕影操作的合理的位置。如果你忘记了细节,请回顾以下的文章:
HawkWang
2022/11/30
1.8K0
IQE14: 色差(Chromatic aberration)产生原理及去除
焦距 (衡量光聚集或发散的度量方式)
焦距,是光学系统中衡量光的聚集或发散的度量方式,指平行光入射时从透镜光心到光聚集之焦点的距离 。具有短焦距的光学系统比长焦距的光学系统有更佳聚集光的能力。简单的说焦距是焦点到面镜的中心点之间的距离。照相机中 焦距f<像距<2f 才能成像。
云深无际
2020/09/14
1.2K0
焦距 (衡量光聚集或发散的度量方式)
机器视觉(第3期)----图像采集之镜头原理详述
上期我们一起学习了光源相关的知识,知道了怎么选择光源,链接如下: 机器视觉(第2期)----图像采集之照明综述 镜头是一种光学设备,用于聚集光线在摄像机内部成像。镜头的作用是产生锐利的图像,以得到被测物的细节,这一期我们将一起学习使用不同镜头产生不同的成像几何,以及镜头像差是如何产生的。希望通过本期学习,我们能够掌握如何选择镜头以及像差产生的原因。 作为一个机器视觉算法人员,来介绍光学系统方面的知识,有些地方理解起来还是有些难度的,小编已经再旁边放了几摞砖,欢迎大家来拍。希望能够和大家一起交流,共同进步。
智能算法
2018/04/03
3.3K0
机器视觉(第3期)----图像采集之镜头原理详述
机器视觉系统硬件:镜头
本文的主要内容为镜头的光学原理、基本参数和选用原则。作为工程人员,应重点掌握镜头的基本参数和选用原则。
用户7699929
2020/08/27
1.3K0
66. 三维重建——相机几何模型和投影矩阵
在文章29. 小孔相机中,我介绍了小孔相机的成像模型。如果你看了这篇文章,你应该至少有了一个重要印象,即相机是一个将三维物体投影为二维图像的设备。
HawkWang
2022/01/19
2.9K0
66. 三维重建——相机几何模型和投影矩阵
一文详解工业相机和镜头选取
一问价格,至少都是大几千,贵的在十几万,心里就不禁有疑问,就这么一个破相机,为啥就卖这么贵?它跟我们常见的单反相机有什么区别?我用单反相机来拍,色彩又好,成像又清晰,它不香吗?为啥一定要用工业相机?
3D视觉工坊
2020/12/11
1.5K0
一文详解工业相机和镜头选取
相机成像模型分析
相机对于机器人来说就相当于人的眼睛,景物在相机中呈现的样子就是机器看到的世界的样子。当我们理解了相机的成像原理,才能理解图像中的景物与实际世界中景物的对应关系。
小白学视觉
2019/10/24
2.3K0
相机成像模型分析
【GAMES101】Lecture 19 透镜
所以方便研究提出了一种理想化的棱镜,这个棱镜没有厚度,非常薄,它可以成功的将平行光线聚焦到一个点上,并且我们认为这个薄棱镜的焦距可以改变,实际上可以通过现实中的一组棱镜来达到这个效果
叶茂林
2024/02/09
1470
【GAMES101】Lecture 19 透镜
高通Camera数字成像系统简介
转载: https://deepinout.com/qcom-camx-chi/qcom-camx-system-intro.html
音视频开发进阶
2021/05/10
2.2K0
高通Camera数字成像系统简介
干货 | 鱼眼镜头模型和校正方法详解
相机镜头大致上可以分为变焦镜头和定焦镜头两种。顾名思义,变焦镜头可以在一定范围内变换焦距,随之得到不同大小的视野;而定焦镜头只有一个固定的焦距,视野大小是固定的。鱼眼镜头是定焦镜头中的一种视野范围较大的镜头,视角通常大于180°。如下图所示,在获取更大视野范围的同时,鱼眼镜头成像的畸形变也更大。
Color Space
2024/06/19
1.7K0
干货 | 鱼眼镜头模型和校正方法详解
34. 光场--捕获场景中所有的光线
在27. HDR - 高动态范围成像中,我向你介绍了把多个不同曝光程度的有限动态范围的图像融合起来,我们可以得到高动态范围的图像
HawkWang
2020/04/17
8720
34.  光场--捕获场景中所有的光线
机器视觉镜头基础知识详解
镜头相当于充当晶状体这一环节,简而言之,镜头主要的作用就是聚光。为什么要聚光?比如说在大晴天用放大镜生火,你会发现阳光透过放大镜聚集到一点上,也就是说,想通过一块小面积的芯片去承载这么一片区域就不得不使用镜头聚焦。
小白学视觉
2020/08/13
1.7K0
相机标定究竟在标定什么?
相机标定可以说是计算机视觉/机器视觉的基础,但是初学者不易上手,本文将给读者整理一遍相机标定的逻辑,并在文末回答评论区提出的问题。分为以下内容:
小白学视觉
2022/04/06
1.3K0
相机标定究竟在标定什么?
38. 对焦扫描技术是如何实现EDOF(扩展景深)的?
我们之前讲解了37. 如何从失焦的图像中恢复景深并将图像变清晰?,我们看到了编码光圈的应用,它可以让我们很容易估计出局部卷积核,从而可以利用去卷积技术得到全焦图像,甚至还可以得到场景的相对深度图。同时,我们也看到了计算摄影学不仅仅是软件的事情,有的时候也会涉及到一些必要的硬件。
HawkWang
2020/04/17
1.7K0
38. 对焦扫描技术是如何实现EDOF(扩展景深)的?
工业机器视觉系统相机如何选型?(理论篇—3)
数字图像是机器视觉系统工作的前提和基础,工业机器视觉系统把成像子系统的信号转换为反映现实场景的二维数字图像,并对其进行分析、处理,得出各种指令来控制机器的动作。
不脱发的程序猿
2021/05/08
1.9K0
工业机器视觉系统相机如何选型?(理论篇—3)
机器视觉中如何选择工业相机与合适的相机镜头
相机和镜头是计算机视觉中重要的组成部分,合适的相机和镜头决定了系统的好坏。但是大部分的计算机视觉工程师对如何选择工业用相机和合适的镜头上犯了难。本文主要介绍如何选择相机与对应的镜头。
不脱发的程序猿
2021/01/20
1.8K0
图像处理-激光测距技术&工业相机基本原理概述「建议收藏」
景深随镜头的焦距、光圈值、拍摄距离而变化。对于固定焦距和拍摄距离,使用光圈越小,景深越大。 主要不要过光了
全栈程序员站长
2022/09/02
9350
图像处理-激光测距技术&工业相机基本原理概述「建议收藏」
摄影构图:如何处理对焦、快门速度、光圈大小、ISO 以及拍摄方式
99%的焦虑都来自于虚度时间和没有好好做事,所以唯一的解决办法就是行动起来,认真做完事情,战胜焦虑,战胜那些心里空荡荡的时刻,而不是选择逃避。不要站在原地想象困难,行动永远是改变现状的最佳方式
山河已无恙
2024/06/21
2480
摄影构图:如何处理对焦、快门速度、光圈大小、ISO 以及拍摄方式
推荐阅读
相关推荐
32. 镜头、曝光,以及对焦(下)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验