返回顶部
首页 > 资讯 > 精选 >怎么用Three.js实现雪糕地球
  • 330
分享到

怎么用Three.js实现雪糕地球

2023-07-02 15:07:43 330人浏览 安东尼
摘要

这篇“怎么用Three.js实现雪糕地球”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么用Three.js实现雪糕地球”文

这篇“怎么用Three.js实现雪糕地球”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么用Three.js实现雪糕地球”文章吧。

  • style

* {    -WEBkit-user-select: none;       -moz-user-select: none;        -ms-user-select: none;            user-select: none;  }  body {    height: 100vh;    background-color: hotpink;    margin: 0;    padding: 0;    overflow: hidden;  }  .loader {    display: flex;    color: white;    display: flex;    justify-content: center;    align-items: center;    font-size: 5em;    width: 100%;    height: 100%;    font-family: "Baloo Bhaijaan", cursive;  }  .loader span {    text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb,      0 6px transparent, 0 7px transparent, 0 8px transparent,      0 9px transparent, 0 10px 10px rgba(0, 0, 0, 0.4);    text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb,        0 5px #bbb, 0 6px #bbb, 0 7px #bbb, 0 8px #bbb, 0 9px #bbb,        0 50px 25px rgba(0, 0, 0, 0.2);      transfORM: translateY(-20px);  }
  •  script

let isLoaded = false; // 纹理资源是否加载完毕const loadingScreen = {  scene: new THREE.Scene(),  camera: new THREE.PerspectiveCamera(    75,    window.innerWidth / window.innerHeight,    0.1,    1000  ),  // 移除加载标志的函数  removeText() {    const loadingText = document.querySelector("#canvas-loader");    if (loadingText.parentnode) {      loadingText.parentNode.removeChild(loadingText);    }  },};// 初始化加载器let loadingManager = new THREE.LoadingManager();// 监听加载器 onLoad 事件loadingManager.onLoad = () => {  loadingScreen.removeText();  isLoaded = true;};// 创建场景const scene = new THREE.Scene();// 创建渲染器const renderer = new THREE.webGLRenderer({ antialias: true });// 渲染器基本设置renderer.setClearColor("hotpink");renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth, window.innerHeight);// canvas 外部容器const canvasWrapper = document.querySelector("#canvas-wrapper");// 创建透视相机const camera = new THREE.PerspectiveCamera(  75,  window.innerWidth / window.innerHeight,  0.1,  1000);// 设置相机位置camera.position.set(0, 0, 220);// 创建平行光源const light = new THREE.DirectionalLight();light.position.set(0, 0, 1);scene.add(light);// 创建点光源const point = new THREE.PointLight(0xeeeeee);point.position.set(400, 200, 300); //点光源位置scene.add(point); //点光源添加到场景中// 创建球体const cRadius = 100;const geometry = new THREE.SphereBufferGeometry(  cRadius,  cRadius * 6.4,  cRadius * 6.4);// 纹理图const textureLoader = new THREE.TextureLoader(loadingManager);const textureSurface = textureLoader.load(  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-surface.jpg");const textureElevation = textureLoader.load(  "Https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-elevation.jpg");const textureSpecular = textureLoader.load(  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-specular.jpg");// 材质信息const materialOpt = {  map: textureSurface,  normalMap: textureElevation,  specularMap: textureSpecular,  shininess: 80,};const material = new THREE.MeshPhongMaterial(materialOpt);// 创建网格体const sphere = new THREE.Mesh(geometry, material);// 设置环境贴图的颜色深浅sphere.material.normalScale.set(0.5, 0.5);// 将模型添加到场景中scene.add(sphere);// 将 canvas 元素添加到页面中canvasWrapper.appendChild(renderer.domElement);let mouseX = 0;let mouseY = 0;const moveAnimate = {  coordinates(clientX, clientY) {    const limit = 270;    const limitNeg = limit * -1;    mouseX = clientX - window.innerWidth / 2;    mouseY = clientY - window.innerHeight / 2;    mouseX = mouseX >= limit ? limit : mouseX;    mouseX = mouseX <= limitNeg ? limitNeg : mouseX;    mouseY = mouseY >= limit ? limit : mouseY;    mouseY = mouseY <= limitNeg ? limitNeg : mouseY;  },  onMouseMove(e) {    moveAnimate.coordinates(e.clientX, e.clientY);  },  onTouchMove(e) {    const touchX = e.changedTouches[0].clientX;    const touchY = e.changedTouches[0].clientY;    moveAnimate.coordinates(touchX, touchY);  },};document.addEventListener("mousemove", moveAnimate.onMouseMove);document.addEventListener("touchmove", moveAnimate.onTouchMove);const onWindowResize = () => {  const w = window.innerWidth;  const h = window.innerHeight;  camera.aspect = w / h;  camera.updateProjectionMatrix();  renderer.setSize(w, h);};window.addEventListener("resize", onWindowResize);const createAnimRotation = () => {  const speed = 0.005;  sphere.rotation.z += speed / 2;  sphere.rotation.y += speed;};// 渲染函数const render = () => {  if (!isLoaded) {    renderer.render(loadingScreen.scene, loadingScreen.camera);    requestAnimationFrame(render);    return;  }  camera.position.x += (mouseX * -1 - camera.position.x) * 0.05;  camera.position.y += (mouseY - camera.position.y) * 0.05;  camera.lookAt(scene.position);  createAnimRotation();  renderer.render(scene, camera);  requestAnimationFrame(render);};render();
  • 在线体验(支持PC与移动端): 雪糕地球线上预览

  • 源码仓库: 雪糕地球

ThreeJS 基础&mdash;&mdash;实现转动的球体

Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象,大家或多或少应该都见识过 Three 的传说。这是小包第一次使用 Three,因此小包会围绕雪糕地球实现的各种细节讲起。

下面首先来看一下 Three 框架的基本组成要素

怎么用Three.js实现雪糕地球

Three 中最重要的三个对象即场景、相机和渲染器。场景即放置模型、光照的场地;相机设置以何种方式何种角度来观看场景,渲染器将效果渲染到网页中。这三个概念都不难理解,下面我们用代码实现这三个对象。

// 场景const scene = new THREE.Scene();// 透视相机const camera = new THREE.PerspectiveCamera(  75,  window.innerWidth / window.innerHeight,  0.1,  1000);// 渲染器const renderer = new THREE.WebGLRenderer();// 设置渲染区域尺寸renderer.setSize(window.innerWidth, window.innerHeight);// body元素中插入canvas对象document.body.appendChild(renderer.domElement);// 设置背景颜色renderer.setClearColor("hotpink");// 执行渲染操作   指定场景、相机作为参数renderer.render(scene, camera);

Three 中有多种相机,本文章主要使用透视相机(PerspectiveCamera),其原理与人眼所看的景象类似,共有四个参数:

PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )

fov: 表示能看到的角度范围,值为角度,类似于人的视角。

aspect: 表示渲染窗口的长宽比,如果网页中只有一个 canvas,其值通常设置为网页视口的宽高比

near/far: near/far 分别代表摄像机的近剪切面和远剪切面

文字有些难以理解,可以参考一下下图:

怎么用Three.js实现雪糕地球

打开浏览器,看一下会渲染出什么?目前只能看到全粉色的网页,这是因为目前的场景中并没有添加 3D 模型对象。

接下来我们来添加一个球体模型,作为地球的基底。

const cRadius = 100;const geometry = new THREE.SphereBufferGeometry(  cRadius,  cRadius * 6.4,  cRadius * 6.4);

SphereBufferGeometryThree 中实现球体的 api,参数非常多,这里只介绍前三个参数

radius: 球体半径

widthSegments: 沿经线方向分段数

heightSegments: 沿纬线方向分段数

为球体添加材质 Material,目前我们只添加一个颜色属性。

// 材质对象Materialconst material = new THREE.MeshLambertMaterial({  color: 0x0000ff,});

渲染网格体 Mesh,并将其添加到场景 Scene 中。

// 网格体 Mesh,两个参数分别为几何体和材质const sphere = new THREE.Mesh(geometry, material);scene.add(sphere);

重新打开网站,并没有看到球体,还是一片粉茫茫的寂寥,天理何在?

怎么用Three.js实现雪糕地球

Three 相机的初始位置默认为 (0,0,0),相机焦点默认为 Z 轴负半轴方向,球体的半径是 100,也就是说目前相机位于球体内部,因此我们需要调整相机位置。

// 设置相机的位置camera.position.set(0, 0, 220);// 设置相机焦点的方向camera.lookAt(scene.position);

当当当当,网页中就可以成功看到一个黑色球体了,额有点奇怪,我们明明设置的是 0x0000ff 颜色,怎么会显示一个黑色模型?

怎么用Three.js实现雪糕地球

小包苦思冥想: 万物本没有颜色,颜色是光的反射。在整个场景中,目前是没有光源的,因此下面分别添加平行光(DirectionalLight)和点光源(PointLight)

平行光是沿着特定方向发射的光,其表现类似无限远的阳光,文章使用它来模拟太阳光。点光源是从一个点向各个方向发射的光源,使用它来增加整体的亮度。

// 声明平行光const light = new THREE.DirectionalLight();// 设置平行光源位置light.position.set(0, 0, 1);// 将平行光源添加到场景中scene.add(light);// 声明点光源const point = new THREE.PointLight(0xeeeeee);// 设置点光源位置point.position.set(400, 200, 300);// 点光源添加到场景中scene.add(point);

怎么用Three.js实现雪糕地球

立体效果看起来不明显,没事,接下来我们让球体动起来。接下来,给球体添加一个 z 轴和 y 轴的转动。

const createAnimRotation = () =&gt; {  const speed = 0.005;  sphere.rotation.z += speed / 2;  sphere.rotation.y += speed;};const render = () =&gt; {  createAnimRotation();  renderer.render(scene, camera);  requestAnimationFrame(render);};render();

由于球体是对称的,转动看起来并不明显,如果你特别想看到转动效果,可以将 SphereBufferGeometry 暂时更换为 BoxBufferGeometry

ThreeJS 纹理&mdash;&mdash;实现转动的地球

上文已经成功实现地球,接下来我们来为地球披上衣服。本文实现的是雪糕地球,因此小包直接为其披上雪糕外衣。

Three 可以将一张纹理图映射到几何体上,具体的映射原理我们不做探究,映射的思想可以参考下图。

怎么用Three.js实现雪糕地球

选取一张雪糕地球的纹理图,使用下面的代码实现纹理贴图效果。

怎么用Three.js实现雪糕地球

// 纹理加载器对象const textureLoader = new THREE.TextureLoader();const textureSurface = textureLoader.load(  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-surface.jpg");// 设置纹理贴图const material = new THREE.MeshLambertMaterial({ map: textureSurface });

只使用普通贴图的雪糕地球看起来已经非常不错了,但还有进一步美化的空间,Three 提供了高光贴图,使用高光贴图,会有高亮部分显示。

const textureSpecular = textureLoader.load(  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-specular.jpg");const material = new THREE.MeshPhongMaterial({  map: textureSurface,  specularMap: textureSpecular,  shininess: 80, // 高光部分的亮度});

虽然动图录制的帧数太低,还是依稀可以看到一些高亮区域。

Three 还提供了环境贴图,环境贴图可以增加表面的细节,使三维模型更加立体。

const textureElevation = textureLoader.load(  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-elevation.jpg");const material = new THREE.MeshPhongMaterial({  map: textureSurface,  normalMap: textureElevation,  specularMap: textureSpecular,  shininess: 80,});

立体效果是有了,但是具体看起来一言难尽,颜色有些许暗淡,不符合雪糕的风格。

小包继续开始查看文档,环境贴图中有 normalScale 属性,可以设置颜色的深浅程度,减少对应属性值为 0.5,0.5

sphere.material.normalScale.set(0.5, 0.5);

交互式雪糕地球

给地球加一些交互效果:

  • 当鼠标靠近,地球放大;鼠标远离时,地球缩小

  • 地球随鼠标方向转动

上述动效我们可以通过移动相机位置实现。首先设定地球旋转的最大正负角度为 270

// 定义动效对象let mouseX = 0;let mouseY = 0;const moveAnimate = {  coordinates(clientX, clientY) {    const limit = 270;    const limitNeg = limit * -1;    mouseX = clientX - window.innerWidth / 2;    mouseY = clientY - window.innerHeight / 2;    mouseX = mouseX &gt;= limit ? limit : mouseX;    mouseX = mouseX &lt;= limitNeg ? limitNeg : mouseX;    mouseY = mouseY &gt;= limit ? limit : mouseY;    mouseY = mouseY &lt;= limitNeg ? limitNeg : mouseY;  },  onMouseMove(e) {    moveAnimate.coordinates(e.clientX, e.clientY);  },};document.addEventListener("mousemove", moveAnimate.onMouseMove);

通过上述事件计算出 mouseXmouseY 的值,在 render 函数中,修改 camera 的位置。

camera.position.x += (mouseX * -1 - camera.position.x) * 0.05;camera.position.y += (mouseY - camera.position.y) * 0.05;camera.lookAt(scene.position);

移动端同步监听 touchmove 事件,手机也可以看到雪糕地球的动态效果。

const moveAnimate = {  onTouchMove(e) {    const touchX = e.changedTouches[0].clientX;    const touchY = e.changedTouches[0].clientY;    moveAnimate.coordinates(touchX, touchY);  },};document.addEventListener("touchmove", moveAnimate.onTouchMove);

添加 loading 效果

纹理的加载需要一定的时间,因此添加一个转场 loading 效果。

loading 效果使用小包前面的实现跃动的文字中的效果。

.loader {  display: flex;  color: white;  display: flex;  justify-content: center;  align-items: center;  font-size: 5em;  width: 100%;  height: 100%;  font-family: "Baloo Bhaijaan", cursive;}.loader span {  text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb, 0 6px      transparent, 0 7px transparent, 0 8px transparent, 0 9px transparent, 0      10px 10px rgba(0, 0, 0, 0.4);  text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb, 0 6px      #bbb, 0 7px #bbb, 0 8px #bbb, 0 9px #bbb, 0 50px 25px rgba(0, 0, 0, 0.2);  transform: translateY(-20px);}

Three 提供了 LoadingManager,其功能是处理并跟踪已加载和待处理的数据。当所有加载器加载完成后,会调用 LoadingManager 上的 onLoad 事件。

因此我们定义一个 LoadingManager,当触发 onLoad 事件后,将页面中的加载标志移除。

const loadingScreen = {  scene: new THREE.Scene(),  camera: new THREE.PerspectiveCamera(    75,    window.innerWidth / window.innerHeight,    0.1,    1000  ),  // 移除加载标志的函数  removeText() {    const loadingText = document.querySelector("#canvas-loader");    if (loadingText.parentNode) {      loadingText.parentNode.removeChild(loadingText);    }  },};// 初始化加载器let loadingManager = new THREE.LoadingManager();// 监听加载器 onLoad 事件loadingManager.onLoad = () =&gt; {  loadingScreen.removeText();  isLoaded = true;};// 纹理图加载器传入 loadingManagerconst textureLoader = new THREE.TextureLoader(loadingManager);

以上就是关于“怎么用Three.js实现雪糕地球”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网精选频道。

--结束END--

本文标题: 怎么用Three.js实现雪糕地球

本文链接: https://lsjlt.com/news/342776.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 怎么用Three.js实现雪糕地球
    这篇“怎么用Three.js实现雪糕地球”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么用Three.js实现雪糕地球”文...
    99+
    2023-07-02
  • Three.js实现雪糕地球的使用示例详解
    目录前言ThreeJS 基础——实现转动的球体ThreeJS 纹理——实现转动的地球交互式雪糕地球添加 loading 效果前言 最近...
    99+
    2024-04-02
  • 怎么使用Three.js实现3D乒乓球小游戏
    本篇内容介绍了“怎么使用Three.js实现3D乒乓球小游戏”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!效果原理React-Three-F...
    99+
    2023-07-05
  • 怎么用PHP实现雪花算法
    本篇内容主要讲解“怎么用PHP实现雪花算法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么用PHP实现雪花算法”吧!<phpclass SnowFlake{ &nbs...
    99+
    2023-06-21
  • MyBatis使用雪花ID怎么实现
    这篇文章主要介绍了MyBatis使用雪花ID怎么实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇MyBatis使用雪花ID怎么实现文章都会有所收获,下面我们一起来看看吧。一、实现MyBatis ID构建接口@...
    99+
    2023-06-29
  • 怎么用Python实现滑雪游戏
    这篇文章主要介绍了怎么用Python实现滑雪游戏的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么用Python实现滑雪游戏文章都会有所收获,下面我们一起来看看吧。开发工具Python版本:3.6.4相关模块:...
    99+
    2023-06-27
  • 怎么利用C#实现绘制出地球旋转效果
    这篇文章主要介绍“怎么利用C#实现绘制出地球旋转效果”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“怎么利用C#实现绘制出地球旋转效果”文章能帮助大家解决问题。将方形的图像映射到正方形上似乎并没有什么...
    99+
    2023-07-05
  • 怎么用Python实现雪夜烟花景
    这篇文章主要为大家展示了“怎么用Python实现雪夜烟花景”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“怎么用Python实现雪夜烟花景”这篇文章吧。运行截图运行效果:什么?你说你看不清烟花?那...
    99+
    2023-06-29
  • 怎么用Python实现环球旅行
    这篇文章主要讲解了“怎么用Python实现环球旅行”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么用Python实现环球旅行”吧!1、.准备工作在开始编写脚本之前,需要做如下准备工作:一部...
    99+
    2023-06-16
  • Three.js GLTF模型加载怎么实现
    这篇文章主要介绍“Three.js GLTF模型加载怎么实现”,在日常操作中,相信很多人在Three.js GLTF模型加载怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Three...
    99+
    2023-07-06
  • 怎么利用Three.js实现跳一跳小游戏
    本篇内容介绍了“怎么利用Three.js实现跳一跳小游戏”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!游戏规则十分简单:长按鼠标蓄力、放手,...
    99+
    2023-06-30
  • 怎么用html5实现乒乓球游戏
    这篇文章主要讲解了“怎么用html5实现乒乓球游戏”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么用html5实现乒乓球游戏”吧!演示地址http://k...
    99+
    2024-04-02
  • 怎么用html5 canvas实现漫天飞雪效果
    这篇文章主要讲解了“怎么用html5 canvas实现漫天飞雪效果”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么用html5 canvas实现漫天飞雪效...
    99+
    2024-04-02
  • 怎么用CSS3实现雪花飘落的效果
    这篇文章将为大家详细讲解有关怎么用CSS3实现雪花飘落的效果,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。   我们可以根据自己想要的动画效果来设置动画,比如在本例中:...
    99+
    2024-04-02
  • 怎么用Three.js+React实现3D文字悬浮效果
    本篇内容介绍了“怎么用Three.js+React实现3D文字悬浮效果”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!背景在 Thr...
    99+
    2023-06-29
  • 如何用CSS实现地球和月亮的公转
    如何用CSS实现地球和月亮的公转,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。纯CSS实现日地月的公转前言为了这次掘金的中秋活...
    99+
    2024-04-02
  • 利用C#实现绘制出地球旋转效果
    将方形的图像映射到正方形上似乎并没有什么难度,所以接下来要做的是把图像映射到球面上。 而球的参数方程为 x​=rcosϕcosθ y=rcosϕsinθ z=...
    99+
    2023-02-28
    C#实现地球旋转效果 C#地球旋转 C#地球
  • 怎么用jQuery实现弹弹球小游戏
    本篇内容介绍了“怎么用jQuery实现弹弹球小游戏”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!本文实例为大家分享了jQuery实现弹弹球小...
    99+
    2023-06-20
  • vue怎么实现吸壁悬浮球
    这篇文章主要介绍“vue怎么实现吸壁悬浮球”,在日常操作中,相信很多人在vue怎么实现吸壁悬浮球问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”vue怎么实现吸壁悬浮球”的疑惑有所帮助!接下来,请跟着小编一起来...
    99+
    2023-06-30
  • C语言游戏项目球球大作战怎么实现
    这篇文章的内容主要围绕C语言游戏项目球球大作战怎么实现进行讲述,文章内容清晰易懂,条理清晰,非常适合新手学习,值得大家去阅读。感兴趣的朋友可以跟随小编一起阅读吧。希望大家通过这篇文章有所收获!项目代码  直接进入代码阶段...
    99+
    2023-06-28
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作