工作需要,研究了一下 threejs 简单逻辑动画交互方法。写了一个小示例,分享一下,挺丑的。

第一步

当然就是初始化 threejs 的渲染场景了。

  1. var camera; //相机
  2. var scene;//场景
  3. var renderer;//webGL渲染器
  4. var controls;//轨道控件,用于特定场景,模拟轨道中的卫星,可以用鼠标和键盘在场景中游走
  5. var raycaster;//THREE.Raycaster对象从屏幕上的点击位置想场景中发射一束光线,返回射线穿透物体的数组
  6. var composer;//后期特效合成器,给场景选中物体添加发光特效

第二步

在 ThreeJs Editor 中建立简单的示例模型,“Export Scene”,导出。并导入示例程序。免去了在示例程序中自己建模的麻烦,不过因为示例程序要加载本地的json,所以可以设置一个简单的 nodejs 服务器。

在 nodejs 的 anywhere 下运行该示例:

加载模型文件,将文件中的相关 object 加入 group 中:

  1.           var url = 'nofloor.json';
  2. var loader= new THREE.ObjectLoader();
              var geometry = new THREE.Geometry();//存放objects的position坐标,为之后线条的起始点坐标服务
  3. loader.load( url, function ( loadedScene ) {
  4. //scene = loadedScene;
  5.  
  6. var objects = loadedScene.children;
  7. for(var i=0;i<objects.length;i++){
  8.  
  9. if(objects[i].type == 'Mesh' ){
  10.  
  11. objects[i].receiveShadow = true;
  12. objects[i].castShadow = true;
  13. geometry.vertices.push(objects[i].position);
  14. group.add(objects[i]);
  15.  
  16. }
  17.  
  18. }
  19.  
  20. } , onProgress, onError);

导入的模型差不多就是这样子(丑一点,担待),并在示例程序中加了stats 和 dat.gui 用来检测渲染效果和改变特效参数。

 第三步

完成的交互目标是,点击上图中某个柱体选中,出现相应的连线,并且让选中的柱体和连线发光。

现在先利用 raycaster 选中物体:

  1. var mouse = new THREE.Vector2(); //鼠标经过或者点击的屏幕 canvas 上的位置           
  2. mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;//将 canvas 坐标系转换为 WebGL 坐标系
  3. mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
  4. raycaster.setFromCamera( mouse, camera );// raycaster的作用场景
  5. var intersects = raycaster.intersectObjects( [group], true );//从鼠标 mouse 位置发射线,选中 group 组中的objects ,并返回 objects 给 intersects

一旦 intersects  不为空,intersects[0].object 就是鼠标选中的物体,可以是上图中的正方体,也可以是上图中的地板。

接下来,皆可以根据选中物体来连线了。连线呢有两种方法。

第一种

代码如下:

  1. var material = new THREE.LineBasicMaterial({
  2. color: 0x0000ff
  3. });
  4.  
  5. var geometry = new THREE.Geometry();
  6. geometry.vertices.push(
  7. //各个柱体的 position 坐标,也就是上面加载模型文件时候生成的 geometry.vertices
  1. );
  1. // THREE.Line 会将 geometry.vertices 中所有坐标点连成一条连续线,但不是首尾相接。
    //比如 geometry.vertices 中存放了 v1,v2,v3,v4四个三维坐标点,就会生成v1->v2->v3->v4 连续线,中间有三条线。
  1. var line = new THREE.Line( geometry, material );

但是有一个缺点就是由于受限于 角度层(ANGLE layer),在Windows平台上使用 WebGL,线宽将总是为1而不管设置的值。这样一旦模型是五六米高,可是线条只有不到1cm的宽度,看起来模型就会很奇怪了。这时候就要用第二种方法。

第二种

画圆柱体,用圆柱体代替线段,但是生成圆柱体就比线段复杂多了。大家知道,threejs 只会指定一个object的position(中心),而不能指定两端的位置(我还没发现,若有错误,请指正),所以画的初始圆柱体是直立的,如下图

让我们要的是下图这样的圆柱体(下面统称为“柱子线”)

因为半径是 0.02,所以看起来像线段,这也是为什么要用圆柱体模拟线段的原因了,逼真,而且还能根据模型大小调整这个柱子线的“linewidth”。

下面我们看看怎么怎么根据正方体个球体的坐标动态生成柱子线吧。

上面我们讲了如何选中物体,选中之后,我们把这个物体相邻的物体(建模时候,将所有物体坐标顺序放在 geometry.vertices 中,此处默认坐标相邻就是物体相邻)的 position 坐标加入 geometryChange.vertices  中

  1.                var object = intersects[0].object;
  2. geometryChange = new THREE.Geometry();
  3. var position = intersects[0].object.position;//当前选中物体的坐标
  4. //搜索 geometry.vertices 中的 position 重新绘制选中物体相关linet
  5. var p = geometry.vertices.length;
  6. for(i=0;i<p;i++){
  7. if(geometry.vertices[i] == position){
  8.  
  9. if (i == p - 1){//将最后一个物体的前一个物体坐标加入
  10. geometryChange.vertices.push(geometry.vertices[p - 2]);
  11. geometryChange.vertices.push(position);
  12. }
  13. else if(i == 0){//将第一个物体的后一个坐标加入
  14. geometryChange.vertices.push(position);
  15. geometryChange.vertices.push(geometry.vertices[i+1]);
  16. }
  17.  
  18. else{
  19. geometryChange.vertices.push(geometry.vertices[i-1]);
  20. geometryChange.vertices.push(position);//将物体前后相邻的加入
  21. geometryChange.vertices.push(geometry.vertices[i+1]);
  22.  
  23. }
  24. }
  25. }

这样我们就把选中物体相邻的物体坐标放在 geometryChange.vertices。现在我们知道柱子线的起点和终点坐标了,那柱子线怎么画,画哪里呢?

  1.               var temp = geometryChange.vertices.length;
  2. var xyz = geometryChange.vertices;
  3.                //position(x,y,z),就是柱子线的中点位置,xw是起点和中点的 X 轴方向距离,zh是起点和中点的 Z 轴方向距离,cheight是起点和中点的空间距离
  4.                var x,y,z,xw,zh,cheight;
  1.  

先知道柱子线的position(x,y,z),xyz[i]是柱子线起点,xyz[i+1]是柱子线终点。

  1. x= (xyz[i].x+xyz[i+1].x)/2;
  2. y=0.1;//线我是画在地面附近的,所以y默认0.1
  3. z=(xyz[i].z+xyz[i+1].z)/2

再来求柱子线的长度

  1. xw=xyz[i].x-xyz[i+1].x;
  2. zh=xyz[i].z-xyz[i+1].z;
  3. cheight=Math.sqrt(xw*xw+zh*zh);//圆柱体长度,勾股定理

这下画柱子线

  1. var material = new THREE.MeshPhongMaterial( {
  2. color: 0x156289,
  3. emissive: 0x00FFFF,
  4. side: THREE.DoubleSide,
  5. shading: THREE.FlatShading,
  6. vertexColors:THREE.FaceColors
  7. } );
  8. var cylinder = new THREE.Mesh( geometryCylinderLine, material );
  9. cylinder.position.set( x, y, z );//两实体的中点,也就是柱子线的中点,自己理解

可是发现画的柱子是竖直向上的

这个时候就需要改变柱子线的模型矩阵的,对它做旋转,达到我们理想的效果。

我们先分析一下怎么旋转,首先将绕 x 轴转90° ,让柱子线躺地上。

  1. cylinder.rotation.x -= Math.PI * 0.5;

之后如下图分析所示,红线就是躺地上的柱子(自行脑补3D场景)。

其中红线和黑线长度相同,红线只需要旋转 θ 角度之后就可以和黑线重合,达到我们要的效果。

  1. θ = Math.asin(xw/cheight);//弧度制

这个时候知道转多少度了,转就ok

  1.                  //考虑到局部坐标系和全局坐标系的转换,柱体是在全局坐标系下旋转
  2. if(xyz[i].x > xyz[i+1].x && xyz[i].z < xyz[i+1].z)
  3. cylinder.rotation.z -= Math.asin(xw/cheight);//Math.asin(xw/cheight)为柱体要旋转的角度
  4. else if(xyz[i].x > xyz[i+1].x && xyz[i].z > xyz[i+1].z)
  5. cylinder.rotation.z += Math.asin(xw/cheight);
  6. else if(xyz[i].x < xyz[i+1].x && xyz[i].z < xyz[i+1].z)
  7. cylinder.rotation.z -= Math.asin(xw/cheight);
  8. else
  9. cylinder.rotation.z += Math.asin(xw/cheight);

好了,柱子线画出来了。

当然不止一条柱子线,把当前的都加入lineGroup 中

  1. lineGroup.add( cylinder );
  2. scene.add( lineGroup );

下次绘制的时候只要移除当前的 lineGroup 即可。

  1. scene.remove( lineGroup );

第四步

加发光特效

借助threejs的 outlinePass 通道

  1.           composer = new THREE.EffectComposer( renderer );
  2.  
  3. var renderPass = new THREE.RenderPass( scene, camera );
  4. composer.addPass( renderPass );
  5.  
  6. outlinePass = new THREE.OutlinePass( new THREE.Vector2( window.innerWidth, window.innerHeight ), scene, camera );
  7. composer.addPass( outlinePass );
  8.  
  9. var onLoad = function ( texture ) {
  10.  
  11. outlinePass.patternTexture = texture;
  12. texture.wrapS = THREE.RepeatWrapping;
  13. texture.wrapT = THREE.RepeatWrapping;
  14.  
  15. };
  16.  
  17. var loader = new THREE.TextureLoader();
  18.  
  19. loader.load( 'tri_pattern.jpg', onLoad );
  20.  
  21. effectFXAA = new THREE.ShaderPass( THREE.FXAAShader );
  22. effectFXAA.uniforms[ 'resolution' ].value.set( 1 / window.innerWidth, 1 / window.innerHeight );
  23. effectFXAA.renderToScreen = true;
  24. composer.addPass( effectFXAA );

在选中时,将物体和相应柱子线加入  outlinePass  渲染目标中即可。

  1.                selectedObjects = [];
  2. selectedObjects.push( lineGroup );//给选中的线条和物体加发光特效
  3. selectedObjects.push( intersects[ 0 ].object );
  4. outlinePass.selectedObjects = selectedObjects;

ok,这就实现了,点击交互的简单特效。

当然,这只是个示例,要把它用到复杂的3D场景中,还需要很多的事情要做,加油。

有错误敬请指正。

ThreeJS之动画交互逻辑及特效的更多相关文章

  1. 通通玩blend美工(6)下——仿iPhone滚动选择器的ListBox(交互逻辑)

    原文:通通玩blend美工(6)下--仿iPhone滚动选择器的ListBox(交互逻辑) 上一篇我们已经把界面画出来了,这篇我们就来制作交互的逻辑吧.上一篇的电梯: http://www.cnblo ...

  2. 如何开发一款html5(H5)跨平台 k12动画/交互课件/游戏

    flash交互课件能生动表达教学内容,也深受广大教育工作者的喜爱,但是目前flash课件只能在pc电脑平台上进行展示,且目前苹果公司已经不再支持flash各类产品,也就是后续苹果ios pc系统也已经 ...

  3. 微信小程序:JS 交互逻辑

    微信小程序:JS 交互逻辑 一.JS 交互逻辑 一个服务仅仅只有界面展示是不够的,还需要和用户做交互:响应用户的点击.获取用户的位置等等.在小程序里边,我们就通过编写 JS 脚本文件来处理用户的操作. ...

  4. 从零开始制作【立体键盘】,画UI免写CSS,【盲打练习】的交互逻辑只用了10来行表达式!

    手把手教你从空白页面开始通过拖拉拽可视化的方式制作[立体键盘]的静态页面,不用手写一行CSS代码,全程只用10来行表达式就完成了[盲打练习]的交互逻辑. 整个过程在众触应用平台进行,快速直观. 最终U ...

  5. android Animation 动画绘制逻辑

    参考:http://www.jianshu.com/p/3683a69c38ea 1.View.draw(Canvas) 其中步骤为:/* * Draw traversal performs seve ...

  6. 3、IOS开发--iPad之仿制QQ空间 (为HomeViewController添加交互逻辑 并 为导航条内容添加UISegmentedControl)

    1. 为bottomMenu添加点击效果 思路描述:        需求:        点击BottomButton的三个item,然后对应响应的是HomeViewController弹出对应的业务 ...

  7. 微信小程序--代码构成---JS 交互逻辑

    一个服务仅仅只有界面展示是不够的,还需要和用户做交互:响应用户的点击.获取用户的位置等等.在小程序里边,我们就通过编写 JS 脚本文件来处理用户的操作. <view>{{ msg }}&l ...

  8. CSS3 Transitions属性打造动画的下载按钮特效

    一个网站的下载按钮应尽量吸引读者的注意. 这意味着网页设计师应该非常重视文件的下载界面.一个页面这么多的文件,如图片,视频和插件可以通过直接HTTP下载共享.许多免费网站甚至发布图标集和PSD文件供用 ...

  9. 网页(aspx)与用户控件(ascx)交互逻辑处理实现

    几个页面(ASPX)都使用一些相同的控件,一个文本框,二个按钮(搜索和导出),为了以后好维护,把这相同的部分抽取放在一个用户控件(ASCX)上.现需要处理逻辑如下 搜索事件处理的逻辑在各个页面处理. ...

随机推荐

  1. MongoDB中的映射,限制记录和记录拼排序 文档的插入查询更新删除操作

    映射 在 MongoDB 中,映射(Projection)指的是只选择文档中的必要数据,而非全部数据.如果文档有 5 个字段,而你只需要显示 3 个,则只需选择 3 个字段即可. find() 方法 ...

  2. 深入浅出数据结构C语言版(20)——快速排序

    正如上一篇博文所说,今天我们来讨论一下所谓的"高级排序"--快速排序.首先声明,快速排序是一个典型而又"简单"的分治的递归算法. 递归的威力我们在介绍插入排序时 ...

  3. 树状数组(Binary Indexed Tree,BIT)

    树状数组(Binary Indexed Tree) 前面几篇文章我们分享的都是关于区间求和问题的几种解决方案,同时也介绍了线段树这样的数据结构,我们从中可以体会到合理解决方案带来的便利,对于大部分区间 ...

  4. React——高阶组件

    1.在React中higher-order component (HOC)是一种重用组件逻辑的高级技术.HOC不是React API中的一部分.HOC是一个函数,该函数接收一个组件并且返回一个新组件. ...

  5. latex使用笔记

    1.图片自动浮动到最后一页单独占用一页 将表格中的 \begin{table}[h]\end{table} 改成 \begin{table}[H]\end{table} 即可 2.公式内容中字母之间空 ...

  6. 01.python基础知识_01

    一.编译型语言和解释型语言的区别是什么? 1.编译型语言将源程序全部编译成机器码,并把结果保存为二进制文件.运行时,直接使用编译好的文件即可 2.解释型语言只在执行程序时,才一条一条的解释成机器语言给 ...

  7. HDFS概述(1)————HDFS架构

    概述 Hadoop分布式文件系统(HDFS)是一种分布式文件系统,用于在普通商用硬件上运行.它与现有的分布式文件系统有许多相似之处.然而,与其他分布式文件系统的区别很大.HDFS具有高度的容错能力,旨 ...

  8. 你真的会阅读Java的异常信息吗?

    给出如下异常信息: java.lang.RuntimeException: level 2 exception at com.msh.demo.exceptionStack.Test.fun2(Tes ...

  9. uvalive 3029 City Game

    https://vjudge.net/problem/UVALive-3029 题意: 给出一个只含有F和R字母的矩阵,求出全部为F的面积最大的矩阵并且输出它的面积乘以3. 思路: 求面积最大的子矩阵 ...

  10. Linux Bash Shell字符串截取

    #!/bin/bash#定义变量赋值时等号两边不能有空格,否则会报命令不存在  # 运行shell脚本两种方式 # 1.作为解释参数 /bin/sh test.sh ;  2.作为可执行文件 chmo ...