文档章节

3d基础知识研究--如何用js在canvas2d上做出3d效果

gonnavis
 gonnavis
发布于 2017/06/01 01:21
字数 1706
阅读 3549
收藏 92

    逛 TypeScript 官网时发现了一个 js canvas 做的 3D 灯光渲染效果(详见参考1),一看源代码,竟然是用 getContext("2d") 做的,而且代码并不长,才两百多行,当时就被震惊了!!!区区这点代码就能白手起家写出三维光线追踪渲染效果?!WTF!还有这种操作?!感觉这一定是涉及非常基础、核心的 3D 相关原理的代码。虽然之前用过很多 3D 软件,也学过一点 Direct3D 和 WebGL,但是对 3D 基础知识始终是一知半解的,向量、矩阵变换等计算也不是太清楚,于是立即决定深入研究一下。

    折腾了一阵原代码,发现他的坐标空间是这样的,X正朝右,Y正朝上,Z正深入画布:

    

    让我恍然大悟的是透视的处理方式,并不是每个物体的每个部分去计算离相机有多远,而是从相机中心点发射出许多射线(每像素一个射线),射线撞到的最近的物体就取这个物体的颜色,这样自然就能产生近大远小的效果。为了直观地理解摄像机的工作原理,我用 MaxScript 在 3DsMax 里建立了射线的模型,下面会说创建过程,先看结果:

    

    这里设置了画布大小为 10 * 10 像素,每个像素对应一根射线,起点就是相机所在位置。

    来看下近大远小的透视是如何形成的。为了清晰这里用了 5 * 5 像素的画布,同样在 3DsMax 里建立射线模型,并添加一个矩形平面,从顶视图中可以看见,矩形靠近摄像机的边交叉了 5 根射线,而远离摄像机的边只交叉了 3 根射线,每根射线对应一个像素,那么靠近摄像机的边在画布上会占了 5 个像素,远离摄像机的边则只占 3 个像素,近大远小的透视效果就这样自然而然地出来了。

    理解了摄像机的射线,灯光渲染也就容易了,无非就是在这基础上,增加了法线、反射、RGB颜色混合、距离衰减等计算,这些其实都是基于向量的。

 

    由于射线数量太多,手动创建太麻烦,幸好 3DsMax 提供了 MaxScript 这个脚本语言,我就用 js 拼接出 MaxScript 来使用。这个是 js 方法

    源码是在 getPoint 方法里生成摄像机射线的,然后就在 getPoint 调用结果后面,把生成的射线存到 MaxScript 里,最后 log 出来。

    控制台里输出的结果是这样的

    打开 3DsMax 按 F11 调出 MaxScript 编辑器,把 js 结果贴入并全选,然后按 shift+enter 执行。

    关闭 MaxScript 编辑器,按 Z 自动缩放一下,就可以看到射线已经生成了。  注意 3DsMax 的坐标系统和源代码不一样,3DsMax 里是这样的,X正同样朝右,但Y正深入画布,Z正朝上。其实和源码的坐标系统也很好转换,只要交换YZ即可。

 

    

 

    到此对原码已经有了个系统的认识了,但是必须得自己敲点代码、算点公式才能称得上真正掌握。源码使用了球形和平面来演示,但是一般3D游戏等不会直接计算球形、平面,而是所有的东西都是用三角型拼出来的。那目标就很明确了,就是自己尝试做一个三角面。
    第一步是复制源码的 Plane 类,改名为 Triangle,然后就是写 intersect 方法,一开始想自己做射线与三角面交叉的计算,初步思路是先计算射线与三角面所在平面的交叉点,然后把这点转换成平面所在坐标系,再判断这点是否落在三角形内,但因为涉及坐标系转换等计算太复杂,最后没有自己写,而是在网上找到了一个现成的、优化的非常好的代码(详见参考2)。里面也提到了一种老式算法没他好,这个老式算法基本和我一开始的思路一致。他给出了新算法的 C 代码,我转了一下 js,结果如下

    注意这里只包含单面的交叉检测,即只有正面是看得见的(会和射线碰撞),背面是透明的(射线会穿透)。正反面的判断是这样的,三个点如此排列,上面为正面

    反之用 1 3 2 的顺序创建 Triangle 的话,下面为正面。

    在源码 draw 方法的 things 数组里加入我们的 Triangle, 调整摄像机的位置和注视点参数,让他能看见三角形正面(同样可以先在 3DsMax 里摆好位置,然后取各点的位置参数放到源码里,注意YZ要互换),就可以看到结果了,透视效果 OK 符合预期

   

    至此静态模型的创建就算基本OK了,有了三角面,我们就可以拼出任意形状的模型。颜色的话前面分析过也并不是太难。当然深入的话还有反锯齿、光滑组、法线贴图、折射、粒子等等,那太高深了,留待后续研究。

    现在不妨先来点动态的玩玩。源码只给了静态的,那动态的要怎么做呢?答案是矩阵变换。参考3里给出了关于矩阵非常好的例子,甚至 js 方法都给了现成的了。只要知道用矩阵从右到左的去叉乘点就可以让一个点的位置进行变换就行了。比如  X轴移动矩阵 × Y轴旋转矩阵 × X轴旋转矩阵 × 点 ,就是把这个点先根据X轴旋转,再根据Y轴旋转,最后在X轴上移动。可以把三角形的三个点同时都转一下,也可以只转摄像机的位置点。配合鼠标事件,我们的动态旋转效果就出来了。

  

 

    在线演示 :   http://gonnavis.com/3d_raytracer

    gitoschina源码: http://git.oschina.net/gonnavis/create3dfrom2d/tree/master

 

    当然用 canvas2d 做 3d 效果只是纯粹为了研究原理,没有显卡加速,画布稍微大一点就卡得几乎不能动了。实际要做的话,肯定还是要用 canvas3d WebGL 的,或者直接用更高级的 BabylonJS ThreeJS 等库。

    http://gonnavis.com/3d/babylonjs/rotate_obj_fallout_monster_2.html

 

参考

  1. https://www.typescriptlang.org/play/index.html 左侧下拉选择 Building a Raytracer 并点击右侧 Run.
  2. http://www.cs.virginia.edu/~gfx/Courses/2003/ImageSynthesis/papers/Acceleration/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf
  3. https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web

 

© 著作权归作者所有

gonnavis
粉丝 11
博文 1
码字总数 1706
作品 0
闵行
程序员
私信 提问
加载中

评论(4)

奥道易通短信平台
奥道易通短信平台
分享
给你个兔子吃
给你个兔子吃
mark 4
邪恶胖子
邪恶胖子
牛掰了
红烧鱿鱼丝
红烧鱿鱼丝
很强势,一点都看不懂,只能当个小码农了
一个基于WebGL的仿真3D水池有逼真的水波纹效果

最近在研究WebGL,看到国外很多高手做的很多超炫的3D效果,无比羡慕。忍不住把效果趴下来研究,下面介绍一个逼真的游泳池中浮动小球的效果。效果非常绚丽,功能强大。示例可切换观察水池的视...

流浪老三
2013/10/14
2.2K
0
前端特效【第04期】|果汁混合效果-下

往期回顾 在上一期的【前端特效】☜里,我们已经把果汁混合的效果里面的圆形菜单做好了,如果你错过了上篇文章今天我们要讨论的是杯子里面的液体生成问题 先来回顾下咱们的果汁混合效果吧 果...

我的卡
2018/11/15
18
0
FusionCharts与HighCharts功能对比分析

选择一个图表组件是一项复杂的任务,因为选择图表时,既要考虑当前需要,又要能应对未来多变的复杂业务需求。 所以,在选择图表的时候,我们需要对图表有更深入的对比和研究。接下来,小编将...

flyingsnail
2014/04/18
559
0
今年和明年总结

今年: 1.今年在公司学到很多东西,主要是技术方面的,对angularjs的熟悉,对js的掌握。对egret的熟悉。 2.今年我对自己在技术方面其实学的还是很少的,有得东西是真没时间研究,对js的理解还...

卡卡就是写
2016/12/31
3
0
2014年首月的新书推荐!

大家好,这篇贴子是人民邮电出版社信息技术分社的第一期书讯,以后每个月的1号,我们都会从“人邮IT书坊”微信账号给大家推荐最新、最优秀、最热门的技术图书,希望大家多多关注信息技术分社...

生气的散人
2014/01/06
2.4K
3

没有更多内容

加载失败,请刷新页面

加载更多

02.日志系统:一条SQL更新语句是如何执行的?

我们还是从一个表的一条更新语句说起,我们创建下面一张表: create table T(ID int primary key, c int); 如果要将ID=2这一行c的值加1,SQL可以这么写: update T set c=c+1 where ID=2; 前...

scgaopan
今天
9
0
【五分钟系列】掌握vscode调试技巧

调试前端js 准备一个前端项目 index.html <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1......

aoping
今天
8
0
PhotoShop 高级应用:USM锐化/S锐化/防抖

、 高反差锐化+混合模式:叠加模式 【将更多的边缘细节添加到图像中】

东方墨天
今天
9
0
Python数据可视化之matplotlib

常用模块导入 import numpy as npimport matplotlibimport matplotlib.mlab as mlabimport matplotlib.pyplot as pltimport matplotlib.font_manager as fmfrom mpl_toolkits.mplot3d i......

松鼠大帝
昨天
7
0
我用Bash编写了一个扫雷游戏

我在编程教学方面不是专家,但当我想更好掌握某一样东西时,会试着找出让自己乐在其中的方法。比方说,当我想在 shell 编程方面更进一步时,我决定用 Bash 编写一个扫雷游戏来加以练习。 我在...

老孟的Linux私房菜
昨天
15
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部