用three.js绘制一个三维直角坐标系

Three.js是一个3DJavaScript库,基于右手坐标系,可以创建简单或是比较复杂的三维图形并应用丰富多彩的纹理和材质,可以添加五光十色的光源,可以在3D场景中移动物体或是添加脚本动画,等等。关于three.js这些功能的教程,只要用心去找,还是可以找到不少资料的。然而,却鲜少有绘制坐标系的经验。因此我们今天主要说的是绘制三维坐标系的方法以及问题,也就是网上没找到的资料。下面的教程默认您已经学习了基本的three.js,对three.js有一定的了解。


三维坐标系最终效果图




1.首先,我们引入一些需要js文件,下载地址 

<script src="js/three.js"></script>//这个是Three.js引擎的js

<script src="js/Detector.js">/script>//探测器 就是检测当前浏览器是否支持或者开启了WEBGL

<script src="js/Stats.js">/script>//JavaScript性能监控器 监控代码的性能 这里监测动画效果

<script src="js/OrbitControls.js">/script>//鼠标控制旋转的js

<script src="js/THREEx.KeyboardState.js">/script>//这个是保持键盘的当前状态

<script src="js/THREEx.FullScreen.js">/script>//查不到,猜测和全屏控制有关

<script src="js/THREEx.WindowResize.js">/script>//处理窗口大小调整。它将在调整窗口的大小时更新渲染和相机


2.按照教程加上基础的场景,摄像机,渲染器,灯光等,再加上自己需要的功能代码,例如:

THREEx.WindowResize(renderer, camera);//处理窗口大小调整。

controls = new THREE.OrbitControls( camera, renderer.domElement );//鼠标控制旋转


3.绘制三维坐标

这里就是我们今天要说的核心内容了,由于我当时是希望将左边的三维图形放进一个mesh,因此,下面示例中添加任何物体的方法都是:

this.mesh.add();

然后在所有物体添加到mesh之后,直接用

scene.add(mesh);

如果你不需要将左边放进一个mesh的话,可以直接按照教程用:

scene.add();

a.添加网格

GridHelper辅助绘制线条的二维网格。第一个参数是网格大小,第二个参数是网格个数,后面是网格颜色。xxx.position.set( 80,0,80 )为网格的中心点坐标。

var gridXZ = new THREE.GridHelper(80, 10, 0xEED5B7, 0xEED5B7);

gridXZ.position.set( 80,0,80 );

this.mesh.add(gridXZ);

var gridXY = new THREE.GridHelper(80, 10, 0xEED5B7, 0xEED5B7);

gridXY.position.set( 80,80,0 );

gridXY.rotation.x = Math.PI/2;

this.mesh.add(gridXY);

var gridYZ = new THREE.GridHelper(80, 10, 0xEED5B7, 0xEED5B7);

gridYZ.position.set( 0,80,80 );

gridYZ.rotation.z = Math.PI/2;

this.mesh.add(gridYZ);

b.添加坐标文字

three.js没有直接写坐标刻度的方法(也许因为我没有查到),因此,我们采用three.js的文字处理方式,将字显示在需要的坐标,这里我们可以利用循环将刻度值显示出来。

定义字体的材质颜色等信息。其中specular指定该材质的光亮程度及其高光部分的颜色,如果设置成和color属性相同的颜色,则会得到另一个更加类似金属的材质,如果设置成grey灰色,则看起来像塑料;shininess指定高光部分的亮度,默认值为30。

var materialtext = new THREE.MeshPhongMaterial({

  color: 0xEEA2AD,

  specular:0xEEA2AD,

  shininess:0

});

文字引用方法,下面以x轴坐标刻度为例,y轴和z轴类似,直接在loader.load中添加:

var loader = new THREE.FontLoader();

loader.load('fonts/helvetiker_regular.typeface.json', function(font) {

  for(i=1;i<=10;i++){

    var x = 160/10*i;

    var meshtextx = new THREE.Mesh(new THREE.TextGeometry(x, {

      font: font,

      size: 3,

      height: 0.1

    }), materialtext);

    meshtextx.position.set(x,0,170);

    this.mesh.add(meshtextx);

  }

});

c.绘制直线

细心的朋友们会发现,示例图的外围描了一层边框,那是用直线绘制的,下面是直线的绘制方法,注意,不仅是用于坐标描边,可以用于任何地方,只要你定义了线段坐标。示例:

var linematerial = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors, linewidth: 1.3} );

var color1 = new THREE.Color( 0x000000 ), color2 = new THREE.Color( 0x000000 );

var p1 = new THREE.Vector3( 0, 0, 0 );

var p2 = new THREE.Vector3( 0, 0, 160 );

var geometry = new THREE.Geometry();

geometry.vertices.push(p1);

geometry.vertices.push(p2);

geometry.colors.push( color1, color2 );

var line = new THREE.Line( geometry, linematerial, THREE.LinePieces );

this.mesh.add(line);

d.添加物体

现在我们绘制好了坐标轴,可以添加物体了。geometry为物体形状,material为物体材质,两项均是可自定义的:

this.mesh = new THREE.Object3D();//忘了强调,这句话要放在所有的this.mesh.add()之前

var geometry = new THREE.SphereGeometry( 0.3, 32, 16 );

var material = new THREE.MeshLambertMaterial( { color: 0x00ff00 } );

meshpoint = new THREE.Mesh( geometry, material );

meshpoint.position.set(40,40,40);

this.mesh.add(meshpoint);

对于三维坐标系来说,就一个物体太单薄了。我们可以随机添加一些绿色的球形物体:

var geometry = new THREE.SphereGeometry( 0.3, 32, 16 );

var material = new THREE.MeshLambertMaterial( { color: 0x00ff00 } );

for(i=0;i<2000;i++){

  var sjs1=Math.random();

  var sjs2=Math.random();

  var sjs3=Math.random();

  var meshpoint = 'mesh'+i;

  meshpoint = new THREE.Mesh( geometry, material );

  meshpoint.position.set(160*sjs1,160*sjs2,160*sjs3);

  this.mesh.add(meshpoint);

}

e.添加旁边色度条

色度条是很多数据分析类图形的必备,它可以根据不同的数值反应不同的颜色,在下载的图形中也许你也可以找到three.js绘制的色度条,但是因为场景灯光等条件相同,色度条会随着三维场景的旋转而进行旋转。我们可以找到两种方法控制旋转:

整个图形不随着鼠标旋转,即类似于静态图,然后正如我在本文开头所说的,将左边图形作为一个mesh,然后在代码中写一个函数,在render中执行,使左边的mesh旋转起来,这个可以实现表面的旋转,但是不可控,会按照代码一直旋转,鼠标无法操控图形。显然这不是我们想要的效果,在此不做举例了,有小伙伴想要的话可以私我要示例代码。

按照三维图形常规来绘制三维图形,鼠标可以操作mesh旋转,可是这样的话色度条也按照鼠标操作旋转起来,怎么办呢?我们也许可以定义一个div,把色度条写在定义的div中,这里可以用css3绘制色度条,效果不错。下面用css3将色度条绘制在定义好的id为colorBar的div中。示例:

$("#colorBar")

.css({ "height": "30%",

  "width":"35px",

  "z-index":"2",

  "background":"linear-gradient(to top, #0000FF , #00FFFF,#00FF00,#FFFF00,#FF0000)",

  "position":"absolute", "top":"30.5%", "right":"15%"

})

这里只绘制了一个色度条,还需要有一些刻度值以及单位等信息,你也同样可以定义一些div,将位置和样式定义好,用js将刻度值写入定义好的div中,不做赘述。

f.将色度值与球形物体值匹配

好了,现在我们可以看到示例图片的效果了吧。哎,好像哪里不对,因为图中的球形物体是彩色的,而我们绘制的图形中球形物体只有单薄的绿色。怎么办呢?别急,下面我们将色度条和球形物体的值匹配,为了测试效果,我们将颜色值与三维图形中向上的y轴值匹配显示。

首先我们定义一个数组,存放色度条的刻度值:

var colorval = [0,40,80,120,160];

然后,再定义一个数组,与色度条的值匹配,其中colors[0]第一个参数为相应的刻度值在色度条中的位置,第二个参数为相应的刻度值对应的颜色,第三个参数是对应的刻度值,以此类推:

var colors =  [ [ 0.0, '0x0000FF',colorval[0]], [ 0.25,'0x00FFFF',colorval[1] ], [ 0.5,'0x00FF00',colorval[2] ], [ 0.75,'0xFFFF00',colorval[3] ],  [ 1.0, '0xFF0000' ],colorval[4] ];

下面就可以将色度值和y值做关联咯。我们将色度条看成一个整体,就是1,然后所有的颜色在色度条中都体现在0和1之间。假如你需要512种颜色,则将1/512得到每两种颜色之间的步长。双重循环,按照步长为标准循环出每个颜色在色度条中的体现的值i,再次循环colors数组中的每一组,得到另一个数组j,(如j为0时,得到数组[ 0.0, '0x0000FF',colorval[0]]),将i与colors[ j ][ 0 ]以及colors[ j + 1 ][ 0 ]作比较,得到i所代表的颜色所在的区间。每个区间的变化标准不同,因此,我们按照区间分开处理。分别获取colors[ j ]以及colors[ j + 1 ]的值,分别获取它们的RGB值,简单运用一些数学等比的知识就可以得到每个i对应的值以及RGB(颜色),将得到的数据写入一个数组colorsArray。代码如下:

for ( var i = 0; i <= 1; i += step ) {

  for ( var j = 0; j < colors.length - 1; j ++ ) {

    if ( i >= colors[ j ][ 0 ] && i < colors[ j + 1 ][ 0 ] ) {

      var minv = colors[ j ][ 0 ], maxv = colors[ j + 1 ][ 0 ];

      var minval = colors[ j ][ 2 ], maxval = colors[ j + 1 ][ 2 ];

      var color = new THREE.Color( 0xffffff );

      var minColor = new THREE.Color( 0xffffff ).setHex( colors[ j ][ 1 ] );

      var maxColor = new THREE.Color( 0xffffff ).setHex( colors[ j + 1 ][ 1 ] );

      var colormin = colors[ j ][ 1 ];

      var colorminr = parseInt( colormin.charAt( 2 ) + colormin.charAt( 3 ), 16 );

      var colorming = parseInt( colormin.charAt( 4 ) + colormin.charAt( 5 ), 16 );

      var colorminb = parseInt( colormin.charAt( 6 ) + colormin.charAt( 7 ), 16 );

      var colormax = colors[ j + 1 ][ 1 ];

      var colormaxr = parseInt( colormax.charAt( 2 ) + colormax.charAt( 3 ), 16 );

      var colormaxg = parseInt( colormax.charAt( 4 ) + colormax.charAt( 5 ), 16 );

      var colormaxb = parseInt( colormax.charAt( 6 ) + colormax.charAt( 7 ), 16 );

      var colorr = parseInt(((i - minv)/(maxv - minv))*(colormaxr - colorminr) + colorminr);

      var colorg = parseInt(((i - minv)/(maxv - minv))*(colormaxg - colorming) + colorming);

      var colorb = parseInt(((i - minv)/(maxv - minv))*(colormaxb - colorminb) + colorminb);

      var color = colorr*256*256 + colorg*256 + colorb;

      var colorvalue = (colorval[4] - colorval[0])*i + colorval[0];

      var colorarr = [color,colorvalue];

      colorsArray.push( colorarr );

    }

  }

}

到这里我们得到了想要的colorsArray,里面包含了512组数据:颜色和颜色对应的值。下面我们只需要在添加物体的时候循环colorsArray数组,判断物体的值与colorsArray中那组数据的colorvalue相近,就选择相应的颜色为物体着色即可。修改d中的代码,这里以y轴值为例:

var geometry = new THREE.SphereGeometry( 0.3, 32, 16 );

var material = new THREE.MeshLambertMaterial( { color: 0x00ff00 } );

for(m=0;m<1000;m++){

  var sjs1=Math.random();

  var sjs2=Math.random();

  var sjs3=Math.random();

  var meshpoint = 'mesh'+m;

  for(n=0;n<colorsArray.length;n++){

    if((Math.abs(160*sjs2 - colorsArray[n][1])) < 1){

      var material = new THREE.MeshLambertMaterial( { color:colorsArray[n][0]  } );

    }

  }

  meshpoint = new THREE.Mesh( geometry, material );

  meshpoint.position.set(160*sjs1,160*sjs2,160*sjs3);

  this.mesh.add(meshpoint);

}

g.添加箭头

坐标轴只有网格和坐标是可以区别物体坐标的,但是通常我们习惯带有箭头的坐标系,然而怎样才能加上箭头呢?

答案是用THREE.ArrowHelper。其中第一个参数是箭头起始点坐标,第二个参数是箭头终点坐标,第三个参数为箭头长度,最后为箭头颜色。用法示例:

var originz = new THREE.Vector3(0,0,160);//箭头始点坐标

var terminusz  = new THREE.Vector3(0,0,180);//箭头终点坐标

var directionz = new THREE.Vector3().subVectors(terminusz, originz).normalize();//箭头方向

var arrowz = new THREE.ArrowHelper(directionz, originz, 30, 0xffffff);

this.mesh.add(arrowz);//将箭头加入场景


4.其它可能问题

到了这里,three.js绘制三维坐标系的内容就结束了。内容比较多,上面的a、b、c、d不分顺序,这里主要介绍的是解决方法,看懂的小伙伴可以尝试一下。

另外,在实际绘图的时候可能会遇到角度不对或者绘制图形偏大或者偏小的问题。角度不对呢就调整相机的位置,相机的三个数值调整一下。图形偏大或者偏小也是调整相机的位置,图形偏大,相机三个坐标值调大一些;图形偏小的话,就将相机三个坐标调小一些。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,712评论 0 33
  • 之前用qunee做了一个2.5d的机房监控,丑的闪瞎我的卡姿兰大眼,后来含泪用three.js做个3d的换...
    我得有妖气阅读 1,567评论 1 2
  • 第一章: JS简介 从当初简单的语言,变成了现在能够处理复杂计算和交互,拥有闭包、匿名函数, 甚至元编程等...
    LaBaby_阅读 1,613评论 0 6
  • 应该是每个人都有一个共性,喜欢找好说话的人去办事,因为好说话,不会拒绝你,这样自己就能避免被拒绝的尴尬,可是为什么...
    炙热的烤猪排阅读 280评论 0 0
  • 以前会时不时开家庭会议,主题都是集中在孩子问题的解决上,比如说贝贝怎么可以开心的去上学,贝贝怎么可以遵守看电视的...
    JC贾阅读 154评论 3 0