和我一起学 Three.js【初级篇】:1. 搭建 3D 场景

这篇具有很好参考价值的文章主要介绍了和我一起学 Three.js【初级篇】:1. 搭建 3D 场景。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

💡 本篇文章共 5572 字,最近更新于 2023 年 04 月 19 日。

0. 系列文章合集

本系列第 6,7,8 章节支持在我的个人公众号「前端乱步」内付费观看,将在全平台文章「点赞数」+「评论数」 >= 500(第 6 章), 1000(第 7,8 章) 时分别解锁发布。

  1. 《和我一起学 Three.js【初级篇】:0. 总论》
  2. 📍 您当前在这里 《和我一起学 Three.js【初级篇】:1. 搭建 3D 场景》
  3. 《和我一起学 Three.js【初级篇】:2. 掌握几何体》
  4. 《和我一起学 Three.js【初级篇】:3. 掌握摄影机》
  5. 《和我一起学 Three.js【初级篇】:4. 掌握纹理》
  6. 《和我一起学 Three.js【初级篇】:5. 掌握材质》
  7. 《和我一起学 Three.js【初级篇】:6. 掌握光照》
  8. 《和我一起学 Three.js【初级篇】:7. 掌握阴影》
  9. 《和我一起学 Three.js【初级篇】:8. 融会贯通》

1. 理解 3D 场景是如何被渲染的

在上一章中,我们介绍过 Three.js 基于 WebGL 向开发者暴露了更加友好的 API。让开发者可以更加便捷地在浏览器中渲染 3D 场景。这意味着绘制 3D 场景的逻辑实际上是被 WebGL 完成的。

为了绘制 3D 场景我们需要了解多深 WebGL ?」这是一些对 Three.js 刚刚产生兴趣的学习者经常会问的问题,对此,我的看法是:目前您不需要了解太多。您仅需要了解 WebGL 是一种能令开发者在 <canvas> 标签内绘制 3D 图形的 JavaScript API 即可,并且这主要都是通过 GPU 完成的。

您应该很容易理解,所谓的「3D 场景」实际上只是通过「透视」与「光影」,利用了人的视觉错觉所营造的一种假象。

您可能有能力使用 CSS3 提供的 API 绘制出一些简单的 3D 场景,并好奇为什么我们需要使用 Three.js?答案是我们期待更复杂,更具备交互性的 3D 场景,而这需要计算机更加复杂的计算!

当我们切换至计算机内部,我们会发现,想要绘制一个逼真的 3D 场景,需要完成以下工作:

  1. 在一个二维坐标系中打点,这些点将会连成线,由线结成面并最终由面组成体(这里我们所提到的「面」,在计算机看来就是一个个小的「三角形」);
  2. 通过「摄影机所在的位置(即人的观察方向)」和「光源的位置与类型」计算出每个小三角形应该被如何绘制和着色(这个过程称为光栅化);
    1. 摄像机位置 -> 物体的透视;
    2. 光源的位置和类型 -> 物体的阴影和投影;

我们绘制 3D 场景的过程,就是通过 JavaScript 代码以及 Three.js API 提供点的坐标,设置摄影机与光源的位置,然后调用 WebGL,让 GPU 完成「连线」,「涂面」工作的这样一个过程。

如果我们只是想要实现一个静态的 3D 场景,通过 CSS3 或 Canvas 2D 技术实际上也能实现,但假如我们想要完成一个交互性强的复杂场景,例如,摄像头在不断移动,或是光源在不断变化,你可以想象计算机需要实时计算多少次各个小三角形的瞬时状态。

换句话说,3D 场景中交互是否顺畅取决于两个要素:

  1. GPU 的运算效率有多快或算力有多强(硬件标准);
  2. 促使 GPU 执行操作的算法有多高效(软件标准)—— 我们将其称为「着色器」;

您也许可以由此理解这样两件事:

  • 为什么想要流畅体验巫师次时代版本的最高画质需要一片先进地显卡;
  • 为什么游戏公司要求开发者熟练掌握 C 或 C++ 语言;

2. 搭建 3D 场景的基本要素

在理解了计算机绘制 3D 场景背后的逻辑后,我们可以来到应用层看看在 Three.js 世界,绘制一个 3D 场景需要哪些基本要素。

为了让问题尽量简单化,让我们先不考虑光照和阴影的部分,仅仅从透视的角度思考这个问题,我们实际上需要以下 3 个基本要素:

  1. 一个容纳我们 3D 物体的容器,我们称其为「场景(Scene)」;
  2. 一些 3D 物体,在 Three.js 中,每个物体又由两部分组成:
    1. 物体的「形状」:某种类型的几何体;
    2. 物体的「材质」:描述物体外观信息,例如颜色,纹理以及改如何反应光线;
  3. 一个或多个确定的视角:我们将使用「摄影机(Camera)」实现;

有了以上三个要素,仅仅基于透视效果,我们就有能力在屏幕中绘制一些逼真的 3D 图形!下面让我们看看如何通过代码实现:

2.1 引入 Three.js

您有很多方式可以引入 Three.js,例如 npm 包形式引入,CDN 引入或直接使用官网提供的脚本(我们当下采用的方式):

虽然您正在下载的大约 344 MB 的压缩文件看起来有点吓人,但我们真正需要使用的 build/three.min.js 文件只有大约 599 KB。我们需要将其以脚本引入的方式嵌入 HTML 文档:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hello Web 3D World!</title>
  </head>
  <body>
    <h1>Hello Web 3D world!</h1>
    <canvas id="webgl"></canvas> // 注意这里我们添加了一个 id 为 webgl 的 canvas 标签
    <script src="./three.js-master/build/three.min.js"></script>
    <script src="./index.js"></script>
  </body>
</html>

当 Three.js 成功加载后,会在全局对象中挂载 THREE 对象,请确保您自己的脚本在 Three.js 加载完成后执行。

2.2 创建场景

场景(Scene)是一个用于装载 3D 物体,摄影机和灯光的「容器」。在 Three.js 中,我们通过实例化 Scene 构造函数的方式创建场景:

const scene = new THREE.Scene()

目前创建的这个场景实例还没什么用,别担心,我们会在后面用到它。

⚠️ 在 Three.js 中,这种实例化的调用方式非常常见!

2.3 添加物体

正如我们之前提到过的,WebGL 通过驱使 GPU 计算三角形的位置,形状与颜色来模拟 3D 物体。因此在定义一个物体时,我们需要依次指定一个物体的「形状」和「材质」,通过一种特殊的类「Mesh」,我将其称为「网格材料」,在 Three.js 中,Mesh 是表示三维物体的基础类,它将接收两个参数:

  • geometry:定义物体的形状;
  • material:定义物体的材质;

现在,让我们创建一个简单的立方体:

const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshBasicMaterial()
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

让我来对以上的代码稍作解释:

首先,我们使用 new THREE.BoxGeometry() 方法创建了一个_长宽高为 1 _的白色立方体,这是我们想要的物体形状。您可能会好奇 BoxGeometry 是什么,答案是:它是 Three.js 提供的多个基础的立体物之一。在之后的章节,我们会具体讲解所有 Three.js 提供的立体物。

您可能会好奇,这里提到的「长宽高为 1 」的单位长度是什么? 1 米?1 公里?或者是 1 毫米?答案可能会令您感到惊讶,实际上具体是什么单位取决于您的需要,Three.js 构筑的 3D 世界是一个相对距离的世界,没有绝对的标尺。例如您的主体物是一个房子,高设置为 3,那么相当于 3 的单位是「米」,那么其他物品就要参照此单位进行相应的缩放。

其次,我们使用 new THREE.MeshBasicMaterial() 方法创建了一个基础网状材质的实例,关于材质,您可以理解为是关乎物体「看起来」什么样的一些属性,这里我们使用的 MeshBasicMaterial,是一种基础的材质类型,它不会响应光照,并且通过一种简单的方法着色。关于材质的更多信息,我们同样会在后面的章节中进行详尽的讲解。

接下来,我们将两个实例对象传入 new THREE.Mesh() 方法中,生成最终的我们想要的物体。我们可以通过材质对象上的 wireframe 属性来直观地理解 WebGL 是如何绘制生成一个立体物的:

material.wireframe = true

看到一个个小三角形了吗?我们再来看一个更复杂的立体物它在「线框模式」下的样子:

很惊人不是吗?
最后,别忘了将我们定义的物体通过 scene.add() 方法添加至场景中。目前为止我们依然在页面中看不到任何东西,这很正常,因为我们还没有完成剩下的两个关键步骤:

  1. 确定 3D 场景中的观察视角;
  2. 命令 WebGL 开始渲染;

2.4 设定摄影机

摄影机是不可见的,但是它却决定了物体应该根据透视的原理被如何绘制。我们可以同时放置多个不同类型的摄像头并在其中切换。

下面的代码定义了一个透视摄像头(它最接近人眼看到的效果),并将摄影机添加到场景中:

const camera = new THREE.PerspectiveCamera(75, 800 / 600)
scene.add(camera)

我们的透视摄影机接收两个参数:

  1. 摄影机的水平视角,又称 fov,如果您拥有一台 VR 头显设备,您应该并不陌生,简单而言,它决定了您水平视野能看多广,当 fov 非常大时,类似您使用广角相机的经验,您能看到的东西会变多,但是边缘的物品会变形;
  2. 屏幕纵横比,即屏幕宽度 / 屏幕高度的值;

2.5 开始渲染

最后一步,我们需要告知 WebGL 去驱动 GPU 进行 3D 场景的渲染。这是通过 WebGLRenderer 实现的,也叫做渲染器。

const renderer = new THREE.WebGLRenderer({
    canvas: document.getElementById("webgl")
})
renderer.setSize(800, 600)
renderer.render(scene, camera)

这段代码看起来很简单:首先,我们通过配置一个 canvas 参数(一个 Canvas DOM Node)实例化了一个渲染器对象,然后我们设置了渲染的宽高,最后我们调用 render 方法,并传入我们的场景和摄影机示例。

这就是我们写一个 3D 场景所需的所有基本元素!

您可能会感到奇怪,如果您自始至终跟随我的步骤,您会发现页面中仍然没有出现期待中的立方体,是的,这是因为默认情况下,摄影机和物体都会被放置在场景的正中位置,因此现在相当于您在立方体内观看 3D 世界,为了看到我们的立方体,我们需要采用如下方式调整我们的摄影机位置:

camera.position.x = 1;
camera.position.z = 3;

这样,我们的立方体终于出现在我们的视野里!

3. 变换物体

虽然目前为止,我们已经成功让一个立方体出现在我们的 Canvas 画布中,但这看起来并不有趣,也并不神奇。别忘了,我们学习 Three.js 是为了创建出可交互的 3D 世界,因此我们需要掌握让我们创建的物体动起来的能力,为此,我们需要先学习如何变换物体。

在 Three.js 中,有一个特殊的类:Object3D,它是很多对象的基类,为很多对象提供了一系列属性和方法。这其中就包括了变换物体的三种方式:

  • 移动位置:position
  • 改变尺寸:scale
  • 旋转:rotation

下面我们依次进行简单的介绍:

3.1 移动位置

position 对象继承自 Vector3 类,它表示了 3D 物体的 3D 向量,3D 向量是一个有序的三元组数字(xyz)可以用来表示很多东西,例如:

  • 3D 空间中的一个点;
  • 3D 空间中某个点距原点的方向和距离;

对于 Vector3 类的说明先到此为止,让我们先看看如何改变一个物体的位置,首先,我们需要知道在 Three.js 中的坐标系,这和我们通常所知道的有些不同:

3.1.1 坐标系

在 Three.js 中,三维直角坐标系分为 xyz 三个轴,它们的关系如下:

  • x:表示水平轴上移动位置,向是正值;
  • y:垂直轴上移动位置,向是正值;
  • z:纵深轴上移动位置,向是正值;

我们可以通过 new THREE.AxesHelper() 方法实例化一个坐标轴,它接收一个数字作为坐标轴的长度。请不要忘记使用 scene.add(axesHelper) 方法将坐标轴加入场景中。

3.1.2 移动位置的方法

Vector3 类还提供了很多用于操作或计算物体距离的方法,例如:

  • length():计算物体至 (0, 0, 0) 点的距离;
  • distanceTo():计算物体到另一指定物体的距离(mesh.position.distanceTo(camera.position));
  • normalize():用来计算向量的标准化(即规格化)长度(标准化是将向量调整为单位长度(长度为 1)的过程);

这样做的好处是,可以将向量的长度与某种物理量(例如速度或加速度)进行比较,而不会因为向量的长度不同而导致比较的不准确。例如,如果两个单位长度的向量都表示速度,那么它们的长度就是相同的,因此可以直接比较它们的大小。标准化向量在许多情况下都很有用,因为它们具有单位长度,因此可以直接比较它们的大小和方向。例如,在游戏中,你可能会使用标准化向量来表示角色的速度,这样可以保证所有角色的速度大小都是相同的,只有方向不同。

  • set():该方法可以一次性依次设置 xyz 的值;

因此,我们可以通过下面的代码移动我们的立方体:

mesh.position.set(1, 0 ,1)

可以看到,我们的立方体向上移动了 0.5 个单位的距离,并且向前移动了 1 个单位的距离,这让它看起来更大了。

3.2 改变尺寸

scale 对象和 position 对象一样,同样有 xyz 三个属性,对它们设置一个正数意味着将其放大或缩小多少倍。默认值为 1

3.3 旋转

不同于 positionscale 对象,rotation 对象继承自 Euler 类。顾名思义,Euler 表示欧拉角。

⚠️ 欧拉角描述了一个旋转变换,它通过以每个轴指定的量和指定的轴顺序在其各个轴上旋转对象。这意味着当旋转一个物体时,旋转的顺序非常重要。

rotation 对象同样提供 xyz 属性,但是它们的单位为「弧度」。( Math.PI = 180 度)

为了确保物体旋转的顺序符合我们的预期,我们还可以使用 Euler 类提供的 .reorder() 方法,手动指定旋转轴的顺序,例如当我们不做设置时,默认轴的顺序为 X -> Y -> Z

mesh.rotation.set(Math.PI * 0.5, Math.PI * 0.3, Math.PI * 0.6)

但当我们优先设置轴的顺序时,物体的最终位置就会发生变化:

mesh.rotation.reorder("YXZ");
mesh.rotation.set(Math.PI * 0.5, Math.PI * 0.3, Math.PI * 0.6);

3.4 注视物体

所有的 Object3D 对象实例都包含一个 lookAt() 方法,该方法指定方法调用者始终注视另一个物体。

可以使用该方法将相机旋转到一个物体,例如将大炮对准敌人,或是让角色注视某一物体。

该方法接收一个 Vector 实例或三个参数(xyz),别忘了 mesh.position 对象正是一个 Vector 对象的实例,因此我们可以通过下方的代码让摄影机注视我们的立方体:

camera.lookAt(mesh.position);

4. 添加动画

目前为止,我们学会了如何创建 3D 场景,并在场景中添加物体并改变物体的位置和大小。但这依然有些无聊,所以在这一章,我们要让物体「动起来」以增加一些趣味性,这将通过「动画」技巧来实现。

就像在 2D 环境绘制 3D 物体是通过透视和阴影欺骗人的双眼来实现一样,动画效果本质上也是对人眼的欺骗。只要我们将一组连贯的静态图像以一定的速率快速播放,就会形成图像在运动的效果,即为「动画」。我们将每秒钟播放的图片数量称为「帧率(Frame Rates)」。如果您玩游戏,您可能听说过 FPS 这个名词,它是指每秒渲染帧数(Frame Per Second)这个值越大,意味着越流畅的动画效果和交互体验。

帧率一般由显示器决定,但也受到计算机性能的限制,大多数显示器能够以每秒渲染 60 帧的速度播放动画,这意味着每 16 毫秒显示器就要完成一次对图片的渲染。我们知道 JavaScript 运行在单线程上,要想保障 16 毫秒内的任务不被其他耗时任务阻塞,我们需要使用 window.requestAnimationFrame() 方法。

4.1 requestAnimationFrame API

与事件循环机制不同,requestAnimationFrame 所定义的函数的触发时机并非是「执行栈清空时」,而是「浏览器刷新开始时」。因此我们可以通过下面的方式让我们的立方体动起来:

const animate = () => {
  mesh.rotation.y += 0.01;
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
};

animate();

这有点像是一个 while (true) 的无限循环。但是使用这个方法还有一个问题,即 requestAnimationFrame API 内定义的函数的执行时机和浏览器刷新频率相一致,当浏览器刷新频率较低时,我们的动画就会执行的很慢。为了解决这个问题,我们可以获取当前帧和上一帧之间的时间,取名为 deltaTime,然后在每次动画中乘以该时间作为补偿,这样我们就可以不关心浏览器的具体刷新频率,在任何设备中保持相同的动画速率。

const animate = () => {
  const currentTime = Date.now();
  const deltaTime = currentTime - time;
  
  time = currentTime;
  mesh.rotation.y += 0.001 * deltaTime; // 注意这里使用 0.001
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
};

animate();

4.2 Clock 对象

Three.js 为我们提供了 Clock 对象来处理时间计算,我们可以直接通过 getElapsedTime() 获得我们的 deltaTime

const animate = () => {
  const elapsedTime = clock.getElapsedTime();

  mesh.rotation.y = elapsedTime;
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
};

animate();

现在,我们获得了一种优雅的方式去执行动画!事情终于开始变得有意思起来了!

5. 🤔 思考题

  1. 不知道您是否注意到,在「添加动画」这一章节,根据使用的方法不同,每次立方体旋转的定义也不一样,不知道您是否能明白为什么会由此不同?
  2. 当使用 requestAnimationFrame API 时,假如上一帧的函数尚未执行完毕,有到了下一帧执行的时机,那么我们的程序逻辑会如何执行?

欢迎在评论区分享您的见解:)

6. 总结

至此,本篇文章介绍了 Web 3D 世界的渲染原理,以及如何通过 Three.js 搭建一个 3D 场景并添加必要组件,在文章的最后,我们甚至还通过动画和变换属性得到了一个不断旋转的立方体!这便是我们在 Web 3D 世界撰写 Hello World 所需的全部工作。

不得不说,这个过程的确有些复杂,但是您已经和我一起迈入了 Web 3D 世界的大门!恭喜您!未来,我们将共同深入今天所谈及的摄影机,物体,材质等各种概念,当您完全掌握这些概念后,您就可以充分发挥您的创作力在 Web 3D 世界创造任何事物,不知道您是否期待那一天的到来?

PS: 希望您妥善保存这一章节我们共同编写的代码,因为在后续的章节中,我们将使用该代码作为样板代码,并不断深化讲解其中的某一部分内容。


👋 欢迎关注「前端乱步」公众号,我会在此分享 Web 开发技术,前沿科技与互联网资讯。文章来源地址https://www.toymoban.com/news/detail-409562.html

到了这里,关于和我一起学 Three.js【初级篇】:1. 搭建 3D 场景的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Three.JS教程1 环境搭建、场景与相机

    Three.js 是一款基于JavaScript的开源3D图形库,它简化了在Web上创建复杂的3D场景和动画的过程。 Three.js 由Ricardo Cabello(也称为mr.doob)于2010年创建,最初是为了填补WebGL技术在那个时候的不足而设计的。随着WebGL的普及和浏览器性能的提升,Three.js逐渐成为Web上3D图形编程的事实标

    2024年01月25日
    浏览(49)
  • web 3d场景构建+three.js+室内围墙,仓库,楼梯,货架模型等,第一人称进入场景案例

      翻到了之前的一个案例,基于three.js做的仓库布局模拟,地图元素除了大模型外,其他都是通过JSON数据解析动态生成的,例如墙体,柱子门口,地标等,集成了第一人称的插件可以第一人称进入场景有需要的可以下载看看,对想入门的朋友应该有一些参考价值。 /**    *创

    2024年02月10日
    浏览(55)
  • Three.js--》实现3d小岛模型搭建

    目录 项目搭建 初始化three.js基础代码 设置环境背景 设置水面样式 添加天空小岛 今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。 项目搭建 本案例还是借助框架书写three项目,借用vite构建

    2024年02月05日
    浏览(87)
  • web3d-three.js场景设计器-天空包围盒-TWEEN.js

    THREE.JS 实现场景天空包围盒,为了让场景背景更具体,而不是呆板的纯色,可以给厂家添加围绕的包围盒。 这里使用球体来实现,球体中央则是场景 给球体添加天空的渐变色 加入场景 代码如下 function createSky( hemiLight) {   const vertexShader = `varying vec3 vWorldPosition;     void main

    2024年01月23日
    浏览(46)
  • web3d-three.js场景设计器-sprite广告牌

    three.js使用Sprite精灵实现文字或者图片广告牌 1.将文字绘制到Canvas,调整对应宽高。 2.作为Cavans材质绑定到Sprite 3.加载到场景调整适当的scale function createLabel({ text, fontSize, textColor, color, imageUrl }) {     return new Promise((resolve, reject) = {         let canvas = document.createElement(\\\'canvas\\\')

    2024年02月02日
    浏览(52)
  • Three.js3D可视化介绍,以及本地搭建three.js官网

    一、什么是Three.js three.js官网 :https://threejs.org/ Three.js 是一个基于 WebGL 的 JavaScript 3D 图形库,它可以轻松地在浏览器中 创建3D场景和动画 。同时,它支持外部模型和纹理的导入,让开发者可以更加便捷地创建出震撼的 3D场景 。 Three.js 的应用场景非常广泛,主要包括以下几个

    2024年02月09日
    浏览(71)
  • Three.js--》实现3d汽车模型展览搭建

    目录 项目搭建 初始化three.js基础代码 添加汽车模型展示 动态修改汽车模型 今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。 项目搭建 本案例还是借助框架书写three项目,借用vite构建工具

    2024年02月06日
    浏览(84)
  • WEB 3D技术 three.js 3D贺卡(1) 搭建基本项目环境

    好 今天 我也是在网上学的 带着大家一起来做个3D贺卡 首先 我们要创建一个vue3的项目、 先创建一个文件夹 装我们的项目 终端执行 vue create 项目名称 例如 我的名字想叫 greetingCards 就是 因为这个名录 里面是全部都小写的 然后 下面选择 vue3 然后按下回车 等待项目创建完成

    2024年01月19日
    浏览(53)
  • Three.js--》实现3d水晶小熊模型搭建

    目录 项目搭建 初始化three.js基础代码 加载背景纹理 加载小熊模型 今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。 项目搭建 本案例还是借助框架书写three项目,借用vite构建工具搭建vue项

    2024年02月06日
    浏览(52)
  • 3D数字孪生 - Three.js 项目介绍与基础环境搭建(一)

    根据WMS系统基础仓库数据以及RCS调度坐标系统,生成3D可视化仓库地图,能够实时监控仓库库位坐标、调度任务状态、车辆位置等信息。 社区对于threejs的实战案例太少,于是,花了一个月的时间,手撕了这个需求。此篇重点不会对threejs做深入讲解,毕竟我也是刚上车不到一

    2024年04月11日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包