想请问有什么办法用three.js做出天涯明月刀捏脸数据的效果吗

社会化媒体
了解更多>>
桂ICP备 号
阅读下一篇
自媒体运营攻略
行业经验交流
Hi,在你登录以后,就可以永久免费的收藏任何您感兴趣的内容,关注感兴趣的作者!
手机注册或邮箱注册
点击按钮进行验证
请输入正确的邮箱
已有帐号请点击
帐号创建成功!
我们刚刚给你发送了一封验证邮件
请在48小时内查收邮件,并按照提示验证邮箱
感谢你对微口网的信任与支持
你输入的邮箱还未注册
还没有帐号请点击
点击按钮进行验证
你输入的邮箱还未注册
又想起来了?
你已成功重置密码,请妥善保管,以后使用新密码登录
邮件发送成功!
我们刚刚给你发送了一封邮件
请在5分钟内查收邮件,并按照提示重置密码
感谢你对微口网的信任与支持
对不起,你的帐号尚未验证
如果你没有收到邮件,请留意垃圾箱 或
意见与建议
请留下您的联系方式
* 留下您正确的联系方式,以便工作人员尽快与你取得联系
转藏至我的藏点他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)Three.js利用顶点绘制立方体的方法详解
转载 & & 作者:专注前端30年
最近在学习three.js,将学习中遇到的知识点总结分享出来,下面这篇文章主要给大家介绍了关于Three.js利用顶点绘制立方体的方法,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
之前我们在学些WebGL基础的时候每天都是在一直研究顶点位置,法向量,绘制下标什么的。虽然复杂,但是毕竟原生,性能没得说。
three.js也给我们提供了相关的接口供我们使用原生的方法绘制模型,下面话不多说了,来一起看看详细的介绍吧。
下面是我的个人一个案例。
首先,我创建了一个空白的形状:
var cubeGeometry = new THREE.Geometry();
立方体的形状如下:
// 创建一个立方体
// v6----- v5
// v1------v0|
// | |v7---|-|v4
// v2------v3
然后添加了立方体的顶点,一共8个
//创建立方体的顶点
var vertices = [
new THREE.Vector3(10, 10, 10), //v0
new THREE.Vector3(-10, 10, 10), //v1
new THREE.Vector3(-10, -10, 10), //v2
new THREE.Vector3(10, -10, 10), //v3
new THREE.Vector3(10, -10, -10), //v4
new THREE.Vector3(10, 10, -10), //v5
new THREE.Vector3(-10, 10, -10), //v6
new THREE.Vector3(-10, -10, -10) //v7
cubeGeometry.vertices =
接着通过顶点的坐标生成了立方体的面
//创建立方的面
var faces=[
new THREE.Face3(0,1,2),
new THREE.Face3(0,2,3),
new THREE.Face3(0,3,4),
new THREE.Face3(0,4,5),
new THREE.Face3(1,6,7),
new THREE.Face3(1,7,2),
new THREE.Face3(6,5,4),
new THREE.Face3(6,4,7),
new THREE.Face3(5,6,1),
new THREE.Face3(5,1,0),
new THREE.Face3(3,2,7),
new THREE.Face3(3,7,4)
cubeGeometry.faces =
在这里需要注意:
(1)面是由三个顶点组成的一个三角形面,也是WebGL的实现面的方式。如果需要一个长方形,那就需要由两个三角形组合而成。
(2)如果要绘制的面是朝向相机的,那这个面的顶点的书写方式是逆时针绘制的,比如图上模型的第一个面的添加里面书写的是(0,1,2)。
(3)如果能使模型有灯光的效果,还需要设置法向量,让three.js自动生成即可,如下
//生成法向量
cubeGeometry.computeFaceNormals();
当前的这些步骤只是生成了形状,还需要和以前一样设置一个纹理,再通过THTEE.Mesh()方法生成网格
var cubeMaterial = new THREE.MeshLambertMaterial({color: 0x00ffff});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
这样就实现了一个立方体的绘制:
全部代码如下:
&!DOCTYPE html&
&html lang="en"&
&meta charset="UTF-8"&
&title&Title&/title&
&style type="text/css"&
html, body {
margin: 0;
height: 100%;
&body onload="draw();"&
&script src="build/three.js"&&/script&
&script src="examples/js/controls/OrbitControls.js"&&/script&
&script src="examples/js/libs/stats.min.js"&&/script&
&script src="examples/js/libs/dat.gui.min.js"&&/script&
function initRender() {
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
//告诉渲染器需要阴影效果
renderer.shadowMap.enabled =
renderer.shadowMap.type = THREE.PCFSoftShadowM // 默认的是,没有设置的这个清晰 THREE.PCFShadowMap
document.body.appendChild(renderer.domElement);
function initCamera() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 40, 100);
camera.lookAt(new THREE.Vector3(0, 0, 0));
function initScene() {
scene = new THREE.Scene();
//初始化dat.GUI简化试验流程
function initGui() {
//声明一个保存需求修改的相关数据的对象
lightY: 30, //灯光y轴的位置
cubeX: 25, //立方体的x轴位置
cubeY: 10, //立方体的x轴位置
cubeZ: -5 //立方体的z轴的位置
var datGui = new dat.GUI();
//将设置属性添加到gui当中,gui.add(对象,属性,最小值,最大值)
datGui.add(gui, "lightY", 0, 100);
datGui.add(gui, "cubeX", -30, 30);
datGui.add(gui, "cubeY", -30, 30);
datGui.add(gui, "cubeZ", -30, 30);
function initLight() {
scene.add(new THREE.AmbientLight(0x444444));
light = new THREE.PointLight(0xffffff);
light.position.set(15, 30, 10);
//告诉平行光需要开启阴影投射
light.castShadow =
scene.add(light);
function initModel() {
//辅助工具
var helper = new THREE.AxisHelper(10);
scene.add(helper);
// 创建一个立方体
// v6----- v5
// v1------v0|
// | |v7---|-|v4
// v2------v3
var cubeGeometry = new THREE.Geometry();
//创建立方体的顶点
var vertices = [
new THREE.Vector3(10, 10, 10), //v0
new THREE.Vector3(-10, 10, 10), //v1
new THREE.Vector3(-10, -10, 10), //v2
new THREE.Vector3(10, -10, 10), //v3
new THREE.Vector3(10, -10, -10), //v4
new THREE.Vector3(10, 10, -10), //v5
new THREE.Vector3(-10, 10, -10), //v6
new THREE.Vector3(-10, -10, -10) //v7
cubeGeometry.vertices =
//创建立方的面
var faces=[
new THREE.Face3(0,1,2),
new THREE.Face3(0,2,3),
new THREE.Face3(0,3,4),
new THREE.Face3(0,4,5),
new THREE.Face3(1,6,7),
new THREE.Face3(1,7,2),
new THREE.Face3(6,5,4),
new THREE.Face3(6,4,7),
new THREE.Face3(5,6,1),
new THREE.Face3(5,1,0),
new THREE.Face3(3,2,7),
new THREE.Face3(3,7,4)
cubeGeometry.faces =
//生成法向量
cubeGeometry.computeFaceNormals();
var cubeMaterial = new THREE.MeshLambertMaterial({color: 0x00ffff});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = 25;
cube.position.y = 5;
cube.position.z = -5;
//告诉立方体需要投射阴影
cube.castShadow =
scene.add(cube);
//底部平面
var planeGeometry = new THREE.PlaneGeometry(100, 100);
var planeMaterial = new THREE.MeshLambertMaterial({color: 0xaaaaaa});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.y = -0;
//告诉底部平面需要接收阴影
plane.receiveShadow =
scene.add(plane);
//初始化性能插件
function initStats() {
stats = new Stats();
document.body.appendChild(stats.dom);
//用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
function initControls() {
controls = new THREE.OrbitControls(camera, renderer.domElement);
// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping =
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
//controls.dampingFactor = 0.25;
//是否可以缩放
controls.enableZoom =
//是否自动旋转
controls.autoRotate =
//设置相机距离原点的最远距离
controls.minDistance = 50;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan =
function render() {
renderer.render(scene, camera);
//窗口变动触发的函数
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerH
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
function animate() {
//更新控制器
//更新性能插件
stats.update();
//更新相关位置
light.position.y = gui.lightY;
cube.position.x = gui.cubeX;
cube.position.y = gui.cubeY;
cube.position.z = gui.cubeZ;
controls.update();
requestAnimationFrame(animate);
function draw() {
initGui();
initRender();
initScene();
initCamera();
initLight();
initModel();
initControls();
initStats();
animate();
window.onresize = onWindowR
以上就是这篇文章的全部内容了,希望本文的内容对大家学习或者使用Three.js具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)Welcome to My
去年之所以再次兴起了学习WebGL的念头,主要是有两个原因;第一个是想制作一个魔方玩,另外一个是想用Web技术还原一些经典电影的经典镜头,比如《Cast Away》又译《荒岛余生》中电影快结束时主人公站在十字路口的场景。
现在看来我想第一个目的已经达成了,有点可惜的是在此之前已经有很多人做过同样的事了,比如:
站在前人的肩膀上整个事情简单了很多,但是解决问题所带来的成就感也相对减少了很多,这也是没有办法的事情了。
首先我假设你是一名前端工程师而且已经初步了解WebGL和ThreeJS的基础知识,比如坐标系、相机、光线、矩阵、弧度等;
如若不清楚可以浏览以前几篇文章快速入门:
第一步:搭架子
从我短暂的ThreeJS编程经验来看,有个通用的的架构能处理大部分情况,如下:
第一步完整代码如下:
第二步:画外型
魔方的外型很简单,就是由一些小正方体组成的一个大正方体而已。
用一个方法封装起来:
基本都是些ThreeJS对象的简单运用,比如盒子对象BoxGeometry、纹理Texture、材质MeshLambertMaterial等,纹理主要是用来描述物体表面静态属性的对象,材质主要是用来描述物体表面动态属性的对象,比如处理光照等,不知道这么理解有没有问题。
其中faces方法主要是生成一块黑色边框的大正方形其内部是某种颜色填充的圆角小正方形的canvas画布,用来充当纹理渲染魔方中小正方体的某个面。
如果把这个canvas画布渲染出来,大致是下边这样的:
另外基于魔方中心在坐标系原点从而推算出所有小正方体中心点坐标可以画图理解如下:
最后需要把生成的魔方加入到场景中才会被渲染出来:
第二步完整代码如下:
此时在浏览器中运行第二步完整代码应该是下边这个样子的:
此时相比于第一步一片空白的页面而言,此时页面中多了一个类似九宫格的正方形,有人可能会说大兄弟要画这么个玩意用得着ThreeJS吗,DIV+CSS分分钟搞定……
其实之所以会这样是因为我们设置的相机的位置是在坐标系的Z轴,魔方的中心在坐标系原点,它们刚好处于同一条直线上,导致显示出来的是魔方的正视图。
第三步:操控魔方视角
第二步完成之后有个很严重的问题,我们只能看到魔方的正面,为了解决这个问题我们需要让相机随着鼠标或者触摸点的移动而移动;
在ThreeJS中作者提供了很多种视角控制类库,比如:
轨迹球控件TrackballControls(最常用的控件,用鼠标控制相机移动和转动);
飞行控件FlyControls(飞行模拟器控件,用键盘和鼠标控制相机移动和旋转);
翻滚控件RollControls(翻滚控件是飞行控件的简化版,控制相机绕Z轴旋转);
第一人称控件FirstPersonControls(类似于第一人称视角的相机控件);
轨道空间OrbitControls(类似于轨道中的卫星,控制鼠标和键盘在场景中游走);
路径控件PathControls(控制相机在预定义的轨道上移动和旋转);
在这里我使用OrbitControls控制器,具体用法很简单如下:
首先引入代码:
然后根据相机以及画布初始化即可:
第三步完整代码如下:
此时在浏览器中运行第三步完整代码应该是下边这个样子的:
第四步:转动魔方
经过前三步在视觉方面简易魔方已经完成了差不多了,但是依然欠缺很重要的东西,没办法转动连最基本的可玩性都没有;
要想转动魔方需要解决以下几个问题:
首先得确定触摸点
也就是说必须得在代码里边判断出魔方的哪个部位被触摸了,Canvas编程是没办法像DOM编程那样有完备的事件机制支持的;所以这个问题需要其它解决办法,比如在2D Canvas我们可以根据坐标来判断当前鼠标或者触摸点在哪个元素上,从而假定该元素获得了焦点;但是在WebGL中存在一个平面2D坐标映射为3D坐标的问题,万幸ThreeJS也提供了对应的解决方案Raycaster。
简单来说就是模拟一道光从屏幕点击或者触摸的位置上开始,以相机朝向为方向,然后检测光线与物体的碰撞,可以得知距离、碰撞点以及哪些物体先碰撞哪些物体慢碰撞。
首先得知道在页面的2D坐标,这里可以通过监听鼠标事件或者触摸事件来完成;
Raycaster的调用也很简单,但是需要注意的是当刚开始的接触点在魔方上且魔方没有转动时操作为转动魔方(魔方是否正在转动,这里用isRotating变量控制,开始一次转动时设置为true,转动结束之后才还原为false;而且一下转动操作必须等当前转动动画结束之后才可以被触发),屏蔽控制器转动
(控制器的enabled属性置为false即可);反之操作为控制器转动(恢复控制器的enabled属性置为true)。
然后得确定转动方向
转动魔方时应该是存在有六个方向的,分别是X轴正方向、X轴负方向、Y轴正方向、Y轴负方向、Z轴正方向、Z轴负方向;
先根据滑动时的两点确定转动向量,然后判断转动向量和这六个方向向量夹角最小的方向即为转动方向;
但是光知道方向其实还是不能够转动魔方的,比如下图中从点E滑动到点F和从点G滑动到点H,滑动方向都是X轴的正方向,而且还有其它情况滑动方向是X轴正方向的;对魔方来说这完全是两种不同的情形,所以我们还需要知道是在哪个平面滑动的。
判断是在哪个平面,我们可以通过该平面的法向量和哪个坐标轴平行来判断,比如如果滑动平面的法向量平行于坐标系的Y轴且等于Y轴正方向的单位向量,那么该滑动平面肯定是魔方的上平面,以此类推;上边那个判断转动方向的方法可以优化为如下这个样子:
那么接下来的问题就是怎么获得滑动平面的法向量了,所幸ThreeJS的光线碰撞检测机制除了能得到碰撞物体、碰撞点,还能得到碰撞平面;已知平面那么就可以获得平面法向量了。
但是ThreeJS中有个问题需要我们注意,在ThreeJS中存在物体自身坐标系和世界坐标系的区分,在初始化时物体的坐标和世界坐标系一致,但是当物体发生变化之后它自身的坐标系也是会发生变化的;比如说刚开始某个物体上平面的法向量就是其自身坐标系Y轴正方向的单位向量,同时也是世界坐标系Y轴正方向的单位向量,如果该物体旋转180度之后,其上平面的法向量还是其自身坐标系Y轴正方向的单位向量,但是却是世界坐标系Y轴负方向的单位向量了,如图:
所以不能使用魔方中小正方体的碰撞平面,因为小正方体的坐标系是会随着小正方体的变化而变化的,此时需要再加入一个和魔方整体大小一样的透明正方体,然后根据该透明正方体的碰撞平面的法向量来判断。
再然后我们得根据转动方向、触发点获取转动物体
比如上图中从点A滑动到点B,转动物体是魔方上平面的所有小正方体;
至于怎么判断,有两种方法,第一种可以根据小正方体的中心点来判断,比如如果转动的是魔方上平面的正方体,那么已知触发点所在正方体的中心点,根据其Z轴大小就可以确定其它小正方体了;
还有一种办法则是根据小正方体初始化时的编号规律来判断,转动之后更新编号,保证其规律不发生变化,后续判断依旧即可,从下边的简图很容易就能看出其编号规律。
第二种方法有个好处在于,我们可以把转动之后更新的编号和初始化时的编号进行比较,来判断魔方是否回到初始化状态,也就是被还原正常了(魔方初始化时用两个变量保存编号,一个会随着魔方的转动得到更新,一个一直保持为初始化编号,如果后续过程中两个变量值再次相同了,那么就说明当前小方块又回到了初始化位置,当全部小方块都这样时则说明魔方已经被还原正常了)。
这种方法不知道有问题没有,由于本人不会玩魔方所以一直没有测试。
最后是制作转动动画
制作转动动画的过程中使用requestAnimationFrame,这没什么要说的;唯一要注意的地方还是关于物体自身坐标系和世界坐标系的问题,举例来说,绕世界坐标系Y轴旋转的方法应该是如下图所示:
另外还要注意的是这里的转动魔方会对转动视角有影响
这和OrbitControls控制器的实现有关系;虽然该控制器为了兼容移动端和PC端同时支持触摸事件和鼠标事件,但是大体逻辑差不多,我们以移动端的触摸事件为例子来简单说明一下:
首先这个控制器会记录下刚开始触摸时的位置为触摸起点;
然后通过这个触摸起点和后续的触摸点计算位移数据;这逻辑初一看起来很完美,但是该控制器还提供了enabled属性用来对其本身起开关的作用;放到当前例子中来说,转动魔方前控制器为开启状态,我们准备转动魔方时刚一接触屏幕控制器会记录下触摸起点,然后控制器被设置为关闭状态,由于我并没有在完成魔方转动动画后立即恢复控制器为开启状态,而是在下一次触摸开始时重新进行状态判断导致如果重新进行状态判断的逻辑晚于控制器记录触摸起点执行就会出现先转动魔方然后转动视角,视角会突然变化,这是因为控制器记录的触摸起点还为转动魔方时的触摸起点。
解决办法为让控制器的事件监听晚于魔方的事件监听执行,另外还得注意得让魔方的事件监听挂在画布上,因为控制器的事件监听是挂在画布上的,如果魔方的事件监听挂在全局window对象上,那么就算初始化控制器的代码写在后边,依然会是魔方的事件监听先执行,因为事件监听都被设置为在事件捕获阶段执行。
还有一种办法是在魔方转动动画完成后立即恢复控制器状态为开启状态。
第四步完整代码如下:
微信小游戏版本
前不久微信出了小游戏,我尝试了一下把这个网页版本的简易魔方改为微信小游戏版本,虽然基本换汤不换药,但是还是遇到了一些坑,这里记录一下;
微信小游戏文档关于创建画布这里并没有描述清楚
微信小游戏文档对创建画布是这么描述的:
也就是说第一次执行wx.createCanvas()创建的画布会直接显示在屏幕上,后续创建的画布则不会;
但是我们在使用了官方提供的weapp-adater.js后就会发现我们认为的第一次执行wx.createCanvas()创建的画布并不会直接显示在屏幕上;
参考官方默认的示例程序,直接使用全局变量反而可以;
主要原因在于在weapp-adapter.js中已经调用了一次wx.createCanvas(),并把返回的canvas作为全局变量暴露出来了,所以当require了weapp-adapter.js并在此之后再调用wx.createCanvas创建的就是离屏Canvas了。
上述解释来自微信的开发童鞋,实际去看源代码时会因为已经被打包处理过,导致可读性较差,可以去官网下载真正的源代码查看。
OrbitControls控制器代码需要修改
首先得使用微信小程序的事件机制;
其次得注释或者更改掉报错点;
其实这些报错点在主游戏逻辑代码中也需要处理。
因为微信小游戏暂时没有对外开放发布功能,因此也就没办法提供预览二维码了,感兴趣的童鞋可以去查看源代码。
至此一个没什么卵用的魔方已经完成了,是时候开下脑洞了;
玩具店应该只有最简单的三阶魔方买,但是对这个例子稍加拓展,你甚至可以玩100阶魔方。
这个例子稍加扩展应该是能做出一些计时、计步的魔方游戏的。
这个例子稍加扩展结合摄像头和自动还原算法,应该是可以做到扫描现实中的魔方,然后根据自动还原算法还原,得到一步步还原魔方的动画演示例子的。
没有分类目录}

我要回帖

更多关于 剑网三捏脸 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信