HTML5 2D游戏引擎研发系列 第二章

这一章节可以说是最重要的,也是理论知识最集中的,在开始我们讨论复杂的代码之前,我们得先了解我们这样做的目的是什么?实际上我比较喜欢分享文字性的理论知识,代码只是机器识别的语言,不合适理论性的沟通,现在我们用文字性的语言来描述我们将要做什么,那么我们来看看,要自己一个人去实现一个2D引擎应该怎么开始呢?或许有很多人已经具备了这样的能力,但无从着手,有的人则是希望自己实现但叹息能力不足,那么现在我讲告诉你实现一个2D引擎需要什么技术,你可以逐个去理解,去寻找,现在让我们淡忘之前所学习的语言规范,一切从0开始.我希望这只是一个手工制作的教程.

显示一张 带透明通道的图片

    这是首要任务,既然我们是做2D游戏,没有3D游戏一样的面拼接,那么我们如果要显示各式各样的不同的图像,我们经常使用一个大区域矩形色集来代替,这个色集就是我们的图片,通常显示一个带透明通道的图片有3种方式,第一个是寻找当前平台提供的API,通常高级一点的平台都会自带一个类似Bitmap的类给你使用,低级一点的平台可能默认只支持BMP,JPG这样的格式,那么你需要寻找第三方的比如libpng这样的解析库去实现,不过可以放心,我们目前所使用的平台是不需要的,还有一种方式是基于纹理, 纹理通常是提供3DAPI去显示,实际它属于一种压缩的图像,便于GPU的调用和3DAPI的规范,一般来说,平台提供的Bitmap接口和纹理需要合作的方式使用,比如从平台去读取图像资源,然后再把这个资源提交给3DAPI生成一份纹理,当然或者你可以使用更加高级的技术去读取纹理专用格式,这样当然是最好的,前提是你当前使用的平台技术支持他们,如果你现在已经可以成功显示一张带透明通道的图片的话,恭喜你,你的引擎正在向你招手,现在让我们讨论下一个问题什么是带透明通道的图片.

混色

    PNG是我们常用的透明格式的图片,我们经常用它做游戏的资源,我们已经习惯了从A图片的透明部分看到B图片,但我将要告诉你,永远不存在绝对的所谓透明的图片,真正的透明是物质无视反光和吸光的,现在让我们看下面2副图片,从视觉上分析哪个棵树是带透明通道的。

 shu2shu3

我相信看到这2副图像时答案已经很明显了,右边带有格子状的树看上去是带透明通道的,因为后面是经典的PS透明背景底,现在我们思考一下,如果你在新建一个黑底图层在这块透明树的底层,那么这个效果看起来和第一副是一样的,那么你还会觉得这棵树是带透明通道的吗?或者我们可以看看第三幅图像

shu4

同样的黑底,同样的树,但现在这颗树看起来是带透明通道的,因为你能从树枝的细缝中看到后面的图像,等等,是不是我们发现了什么?也就是说让一副图像看起来是否带透明,是需要至少2副图像的对比的,实际上我们看到的图像是经过至少2副图像的混色而形成的新图像,所以得一个结论,我们显示的图像并不是立即显示,而是经过了一定的逻辑处理后形成的新图像然后输出到我们的像素屏幕上,我们通常称这个逻辑区域叫颜色缓存,现在我们讨论它的实现逻辑,比如现在我们有一个显示设备,和一系列的图像,那么我们首先把当前要显示的图像全部存入这个颜色缓存中,在这一过程中,图像还未真正的显示出来,现在我们开始混合图像,这里引入一个公式:

公式:源颜色 * 源因子 + 目标颜色 * 目标因子(分量)

它的解释为源颜色和目标颜色各自乘以一个系数然后相加,这样就得到了新的颜色,分量的意思是R,G,B,A分别运算一次这样的公式,

tu3

现在我们试试如何混合出一个带透明通道的图像,要实现这个目的我们需要2个参数,源因子和目标因子。

tu7

0代表透明度为0,1代表不透明,我们可以看到这幅显示了遮挡部分的逻辑和透明区域的逻辑,通常这部分的逻辑我们是可以设置和更改的.

HTML5的可以参考:

http://www.w3school.com.cn/html5/canvas_globalcompositeoperation.asp

opengl 的可以参考:

http://blog.chinaunix.net/uid-20622737-id-2850251.html (这是网上搜索的,希望作者不要介意), 如果了解了这部分逻辑,我们就可以实现一个激动人心的效果了

粒子

tu8

哇,是不是很漂亮呢,通过粒子和图像我们可以组合出很唯美的效果,还可以自己写一个粒子编辑器去设置这些参数,实际上要实现这个效果很简单,现在我们试着让N个图片从某个点发射出去,那么越在中心的地方积累的图片是最多的,扩散到外层了,渐渐分散了,然后透明度慢慢递减,等等,我们是不是又发现了什么?想想刚才的混色,如果我们把源颜色和目标颜色相加会怎么样?那么积累的图像越多的地方叠加的颜色就越多,这样就越趋于白色,叠加得越少也就越趋于是背景色,1是白色峰值,0是黑色,这真是太棒了,现在我们只要把图像的混色模式设置为增加模式,然后想一点骚气的算法,这样就可以做出自己独一无二的粒子特效了,下面发2个我写的DEMO地址,源码就自己打开F12查看吧,其实我不太希望看源码,而是自己理解了去尝试,这一切没有想象中的那么复杂

QQ截图20130906012732

一个粒子效果:

http://jfy19771224.sinaapp.com/html5js/demo_1/index.html

QQ截图20130906012818

一个云朵效果:

http://jfy19771224.sinaapp.com/html5js/demo_2/index.html

 

下面我提供一些粒子的源图,最好用模糊过的图像,这样粒子的边缘看起来会很柔和.(右键保存吧,虽然看起来什么都没有,因为他们是白色的,只有是白色的你才能平均的调和出其他颜色)

lizi1lizi2lizi3lizi4

当然混色除了实现粒子效果之外还能实现其他的酷炫效果,我提供一些图片来激发你的灵感吧.

image1

image2

image3

image4

 动画(切片技术和UV技术)

  我们现在利用混色成功的显示了带透明通道的图片,并且还能做出粒子和特效,这感觉太棒了,现在我们开始准备做我们的游戏了,什么?没有动画?,没关系,我们现在就给引擎加入动画系统,老规矩,实现一个动画我们需要什么样的技术呢?我来告诉你2个办法,切片和UV,通常切片功能平台API有提供,目的是把某个图像的某一部分显示出来,

HTML5的参考地址:http://www.w3school.com.cn/html5/canvas_drawimage.asp,

因为平台已经代替我们实现切片功能,所以不用关心它是如何实现,我们只需要设置参数就好,接下来是UV技术,UV是什么呢?UV实际上是一套2维坐标,它是规定图像映射到某个区域的规范,如果你要放大一张256*256的图像2倍,那么我们可以理解为把一块256*256的图像色集映射到512*512的区域中,UV的坐标系的单位是0~1,所以它并不是绝对的坐标,他们起来可能是这样,

image5

把一副图像的绝对坐标值比如256*256换算成UV坐标系0~1这个过程称之为插值计算,值得高兴的是,这个过程通常不用我们去做,我们所使用的3DAPI中已经代替我们实现,我们只需要设置参数就可以在某个地方获取这个插值后的坐标(这个地方我们叫它着色器),现在我们要使用这个技术实现和切片一样的效果,现在我们先试试手,

image6

我们把U方向的最大值设置为0.5,神奇的事情发生了这个图像好像被我们拉宽了,或者说,我们在一个256*256的区域里显示一个128*256的图像,太有趣了,我们再更改参数试试,

image7

我们在一个256*256的区域显示一个512*256的内容,等等,好像不对吧,怎么是重复了,其实这也是3DAPI为我们提供的一个便捷功能,因为我们的源图本身就是256*256的尺寸,现在我们要显示的部分已经超出了它的区域,这时候我们有2个选择,重复最后一个像素,或者重复纹理从0开始,通常我们选择重复纹理,看到这里的同学是不是有一个灵感了,没错,我们可以基于这个特性去做背景的循环滚动,而不用2张一样的图片来回拼接了,基于UV的方式既节省内存,又简单,并且看来高端大气上档次不是么,下面我们试试滚动他们.

image9

现在我们让U方向的值都加上0.5,这样看起来他们好像开始滚动了,其实上我们只是设置了我们的采样区域的开始坐标和结束坐标而已,哈哈,现在回头看看,我们是不是通过UV的参数设置完成的图像的在区域内的放大和位移呢?嗯?这效果看起来好像是切片效果哦?没错,就是这样,我们看下面的图像,试试实现一个切片效果,动画即将出现.

我们现在需要一张源图,目的是只显示其中一个人物.

image11

然后开始UV缩放

image10

看,我们做到了,然后我们可以动过U或者V的值的偏移量来设置切片的位置,这样就就可以实现动画效果了,现在看起来一切都是美好的,但是这个图片是规则的,如果我们需要不同大小的动画呢?这时候你需要一个软件生成一个切片的坐标数据集合,通常他们的数据包含了,动画的X,Y,动画的偏移坐标X,Y,动画的大小width,height,他们看起来是这个样子.

image12

我们可以读取动画配置信息来实现每帧不规则的切片,所以总结一下:

image13

现在万事具备,只需要一个工具代替我们去实现这个功能并且导动画配置,你可以去下载FLASH CS6,选择你需要导出的动画,右键>导出Sprite表,它能把你的动画资源紧密的压缩在一个空间内,它看起来是这样

image14

这样我们就不需要实现规则的切片,节省了大量的空间,实现这个功能的工具网上有很多,但是我经常用的还是FLASH,所以如果你们正在使用这个切片技术,那么你们的游戏资源看起来应该是

res29be85d507adf1f0cb9cf5ded300res2

计时器

现在,有了切片技术,我们还不能满足,我们需要他们按照一定的时间去播放,或者说延迟多少时间执行一次切片操作,那么我们就需要一个Timer类,这个类平台一般都有提供,如果没有呢?那我们需要一个函数,这个函数叫返回1970年到现在的毫秒数,或者当前程序运行的毫秒数,在HTML5中它看起来是这个样子:

1
2
var date=new Date();
date.getTime();

然后我们可以用2个值,一个值保存上一次调用的函数,一个值比较当前的值,然后让当前的值减去上一次的值,再判断这个间隔是否满足的延迟的时间,如果满足了我们就调用一次播放动画的函数,1秒等于1000毫秒,如果我们需要动画按照24帧率播放,那么我们需要设置一个1000/24这样的值.

矩阵

传说中的矩阵,高富帅的矩阵,终于到了这个环节,在这个环节中可能需要你的注意力再集中一点因为它将是游戏中无处不在的核心,没有它,你移动或者旋转一个图像都寸步难行,不过,这个技术通常只有在3DAPI中需要自己去实现,如果是使用平台提供的2D绘图API或者某第三引擎是不需要的

2010051223481491

看看这个图,让我们先放松放松,它是黑客帝国的经典镜头,如果我们把里面的任何物体看成数据,那么我们可以选择坐标为依据,实际上我们看到的任何物体的运动都是基于坐标的运动,现在我们来看看,我们为什么需要矩阵,假设我们有一个点,坐标为0,0

image20

现在我们需要移动这个点的X坐标5个单位,那它看起来应该是个样子

image22

假设,现在我们需要一起移动2个点让这2个点的相对位置保持不变,那么它看来是这样

image26

这看上去很简单,我们只需要在2个顶点的X坐标上加上一个同一个值就可以实现,既然是这样,我们就用这个方式实现一个更加高级的功能,以A点旋转这2个点45度,那么他们看起来应该是这个样子.

image27

很抱歉,因为不是水平和垂直运动,我无法目测出B点被旋转后的实际位置,我可能需要一个公式去做这个操作并且得出最后结果,

1
2
B.x=A.x+cos(45)*距离
B.y=A.y+sin(45)*距离

为了让点A和点B的旋转后的距离保持不变,我们需要在他们被旋转之前计算出一个距离,需要用到勾股定律,它看起来是这样.

1
2
3
v1=B.x-A.x
v2=B.y-A.y
距离=平方根(v1*v1+v2*v2)

好吧,这一关顺利过去了,我们接下来实现更加复杂的操作,以A点为原点,缩放2个A和B,嗯,我想到一个简单的办法,距离/2

image28

既然能缩放和旋转了,我们或者可以试试倾斜?那他看起来应该是这个样子

image30

现在坐标B点的坐标不能用简单的公式实现了,很抱歉,我是用软件实现的,我不想在拿纸和笔去计算,现在我们除了倾斜,甚至需要改变原点的位置,或者把这2个顶点放在一个容器中,或者我可以选择旋转缩放倾斜这个容器,或者再嵌套一层容器,

image31

我已经开始觉得这很恐怖了,如果按照上面的计算方式估计我永远无法知道究竟A和B点的坐标是多少了,现在想想,如果我们有一套方法,能代替我们更改顶点的位置,并且可以让他们保持一定的规律性,甚至加入嵌套功能,那这时候我们就需要用到矩阵了,什么?我们究竟为什么要这么做?看看下面的图

image33

实际上,我们通过3DAPI去绘制图像,是由2个三角面组合起来的矩形来显示一张纹理的,那么我们实际用到的点在矩形中是4个,如果我们需要对这个图像进行变换操作,比如位移,旋转,倾斜,嵌套,等,实际上是对这4个顶点的操作,并且你要保证这4个顶点拉出的面不能不规则的分离和变形,这就是我们需要对N个点统一操作的理由,有了矩阵我们可以很轻松实现,矩阵我们分为2个部分,一个是操作的模版,一个是计算部分,使用了矩阵,我们移动一个这4个顶点的操作看起来应该就是:

1
2
3
4
5
6
7
0顶点的坐标=矩阵运算(X移动5,Y移动6,X缩放0.5,Y缩放2,旋转45度,倾斜5度)
  
1顶点的坐标=矩阵运算(X移动5,Y移动6,X缩放0.5,Y缩放2,旋转45度,倾斜5度)
  
2顶点的坐标=矩阵运算(X移动5,Y移动6,X缩放0.5,Y缩放2,旋转45度,倾斜5度)
  
3顶点的坐标=矩阵运算(X移动5,Y移动6,X缩放0.5,Y缩放2,旋转45度,倾斜5度)

哇,这正是我想要的,我不需要再保存什么距离,也不需要关心它的操作内容,我只需要让这4个顶点坐标统一运算这个函数就好,那么我们来认识认识矩阵究竟是什么样子的,一般在2D游戏中,我们需要一个3*3的矩阵,实际上是2*2,但是我们需要一个占位坐标来进行位移操作,因为位移操作是水平和垂直方向不包含其他逻辑,所以是3*3,矩阵和矩阵之间是可以相乘的,这个过程叫融合矩阵,通常宽度和高度相等的矩阵我们可以叫它方阵,或者模版矩阵,这样的矩阵通常保存我们要操作的值,设置一个可以旋转的模版矩阵它看来是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//旋转
public void appendRotation(rotation)
{
cos =  Math.cos(rotation * Math.PI / 180);
sin =  Math.sin(rotation * Math.PI/ 180);
spinArray[0][0]=cos;
spinArray[0][1]=sin;
spinArray[0][2]=0;
spinArray[1][0]=-sin;
spinArray[1][1]=cos;
spinArray[1][2]=0;
spinArray[2][0]=0;
spinArray[2][1]=0;
spinArray[2][2]=1;
};

没错,我们现在不能只是简单的把旋转的参数保存在一个值中,我们需要把他转换成一个模版矩阵,位移,缩放,倾斜的模版矩阵看起来是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
//平移,缩放,倾斜
public void appendTranslation(x,y,scaleX,scaleY,biasX,biasY)
{
translationArray[0][0]=scaleX;
translationArray[0][1]=biasY;
translationArray[0][2]=0;
translationArray[1][0]=biasX;
translationArray[1][1]=scaleY;
translationArray[1][2]=0;
translationArray[2][0]=x;
translationArray[2][1]=y;
translationArray[2][2]=1;
};

怎么样?是不是发现了什么,我们实际上是把我们的参数按照一定的规律放在一个二维数组中,现在我们设置好我的模版矩阵了,也就是二维数组,要如何去使用呢?那么我们需要2个乘法运算,一个是3*3的乘法,一个是1*3的乘法,3*3是用于把2个模版矩阵相乘,融合成新的模版矩阵,这个操作等于是把你一系列的操作融合在一个二维数组中,我们或许有这样的操作,移动父对象,旋转父亲对象,倾斜子对象,缩放孙对象,移动孙对象,那么我们就可以,父对象的模版矩阵*子对象的模版矩阵*孙对象的模版矩阵=最终的模版矩阵,我们最后把这个模版矩阵用于我们的实际顶点坐标,那么,3*3乘法的矩阵看来应该是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 3*3矩阵融合
* @param a1
* @param a2
* @return
*/
public float[][] add33(float[][] a1,float[][] a2)
{
at=new float[3][3];
at[0][0]=a1[0][0]*a2[0][0]+a1[0][1]*a2[1][0]+a1[0][2]*a2[2][0];
at[0][1]=a1[0][0]*a2[0][1]+a1[0][1]*a2[1][1]+a1[0][2]*a2[2][1];
at[0][2]=a1[0][0]*a2[0][2]+a1[0][1]*a2[1][2]+a1[0][2]*a2[2][2];
  
at[1][0]=a1[1][0]*a2[0][0]+a1[1][1]*a2[1][0]+a1[1][2]*a2[2][0];
at[1][1]=a1[1][0]*a2[0][1]+a1[1][1]*a2[1][1]+a1[1][2]*a2[2][1];
at[1][2]=a1[1][0]*a2[0][2]+a1[1][1]*a2[1][2]+a1[1][2]*a2[2][2];
  
at[2][0]=a1[2][0]*a2[0][0]+a1[2][1]*a2[1][0]+a1[2][2]*a2[2][0];
at[2][1]=a1[2][0]*a2[0][1]+a1[2][1]*a2[1][1]+a1[2][2]*a2[2][1];
at[2][2]=a1[2][0]*a2[0][2]+a1[2][1]*a2[1][2]+a1[2][2]*a2[2][2];
return at;
}

看上去好像很复杂,其实它只是把每个矩阵里的每个元素和新的矩阵的元素按照规律相乘然后把结果加在一起而,这个规律是,对结果中任意元素C(行,列),取A的第N行和B的第N列,将行和列中对应的元素相乘,然后将结果相加.现在我们有了融合矩阵的能力了,现在要介绍一个新的词汇,叫向量,它长这个样子[x,y,z]或者你可以叫他是1*3的矩阵,向量的意思就是,某个方向上的单位量,其中一个元素代表一个方向上的值,2D中的向量看起来就是这样[x,y],怎么样,这不是就我们经常用的坐标吗,精彩的时刻要来了,那我们要融合1*3的矩阵和3*3的矩阵,1*3的算法返回出去的是一个向量矩阵,算法看起来是这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 1*3矩阵融合
* @param a
* @param b
* @return
*/
public float[] add13(float[]a,float[][] b)
{
a1[0]=a[0]*b[0][0]+a[1]*b[1][0]+a[2]*b[2][0];
a1[1]=a[0]*b[0][1]+a[1]*b[1][1]+a[2]*b[2][1];
a1[2]=a[0]*b[0][2]+a[1]*b[1][2]+a[2]*b[2][2];
return a1;
}

我有点小兴奋了,现在我们要整合所有的方法,它看起来是这样:

1
2
3
4
5
6
public void upDataBasrMatrix(float rotation,float x,float y,float scaleX,float scaleY,float biasX,float biasY)
{
appendRotation(rotation);
appendTranslation(x,y,scaleX,scaleY,biasX,biasY);
baseMatrix = add33(spinArray, translationArray);
}

这是一个接受变化操作的函数,第一行代码,我们把角度转换成一个模版矩阵,第二行我们把剩下的变换操作转换成模版矩阵,然后融合旋转矩阵和其他矩阵,然后得到了一个新的模版矩阵,然后我们再把我们的坐标向量矩阵和这个模版矩阵做1*3操作:

1
2
list=[5,5]
最终坐标点= add13(list,baseMatrix);

这样,我们很简单的完成了坐标的变换,你们看到了复杂的算法吗?我没有看到,我只是看到了规律的排列组合而已,为什么要这么做?其实上这是一种已经被推导出来的公式而已,这是强大的老外发明的,如果你希望了解更加详细的内容,建议去买一本书<3D数学基础:图形与游戏开发>,这里面详细讲解了矩阵的算法,一般人我不告诉他,它看起来应该是这个样子:

2591258

点击

呼呼,已经3点多了,连续写了4个小时,如果下面出现了你看不懂的文字,那说明我在说梦话了,现在来总结一下,我们可以实现图片的显示,动画的播放,显示元素的变换操作和嵌套,还有粒子特效,嗯?还需要什么呢?对,交互,我们需要知道用户点了什么元素,在3D中这叫射线拾取,在2D中我们叫简单的算法,实际上我们只要检测某个点是否在一个平面之内就可以确定这个面是否被这个点点击了,常用的有2种算法,第一个主要用于未变形的边界盒子,这种算法效率相对要高,但是图像变形后就不太准确了,那么它看起来应该是这样:

1
2
3
4
if(Math.abs(chilx.x-mouseX)<=child.width/2&&Math.abs(child.y-mouseY)<=child.height/2))
{
//点中了
}

下面介绍一种基于顶点的面积检测,这是很有用的算法,不一定要检测点击事件,你甚至可以做一个不规则的面积检测和点和不规则面积的碰撞,这一定能有大用途的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 面积检测
* @param p1
* @param p2
* @param p3
* @return
*/
public static int hitTrianglePoint(Point2D p1,Point2D p2,Point2D p3)
{
if ((p2.getX()-p1.getX())*(p2.getY()+p1.getY())+(p3.getX()-p2.getX())*(p3.getY()+p2.getY())+(p1.getX()-p3.getX())*(p1.getY()+p3.getY())<0)
{
return 1;
}
else
{
return 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**顶点碰撞检测:p1,p2,p3 为范围点,p4是碰撞点*/
public static boolean hitPoint(Point2D p1,Point2D p2,Point2D p3,Point2D p4)
{
int a = hitTrianglePoint(p1,p2,p3);
int b = hitTrianglePoint(p4,p2,p3);
int c = hitTrianglePoint(p1,p2,p4);
int d = hitTrianglePoint(p1,p4,p3);
if ((b==a)&&(c==a)&&(d==a))   {
return true;
}
else
{
return false;
}  
}

校准和创建2次方图

如果我们使用3DAPI上传纹理,那么这样最好使用2的次方的图片,不然会出现你意想不到的效果,原因是因为系统内部的插值计算的实现逻辑,这个不需要在这里解释了,那么我们通常会在上传之前检测图片是否为2的次方,如果不是,则会创建一个符合标准的次方图,

检测的算法看起来是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 检测图片是否为2的次方
* @param num
* @return
*/
public boolean isPower_2(int num)
{
if (num == 1)
return true;
else
{
do
{
if (num % 2 == 0)
num = num / 2;
else
return false;
}
while (num != 1);
return true;
}
}

创建的算法看起来是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 计算图片最接近的乘方图片
* @param f
* @return
*/
public int getScope(float value)
{
int textureWidth = 2;
while(textureWidth < value) {
textureWidth <<= 1;
}
return textureWidth;
}

 

正交视口

写到最后我竟然把最重要的东西忘记了,不过这通常是在3DAPI绘图时才会需要用到的技术,在平台提供的2D绘图中我们不用去关心视口究竟是什么类型,现在来说,正交视口一般会有平台提供的矩阵,或者方法,在有些情况下则需要我们自己实现,正交实际上把我们的视图规范到一个单位坐标系中,物体不会因为近远出现透视的情况,没有正交就没有2D,一般正交视口会根据屏幕大小在游戏初始化就已经创建好了,然后让游戏中的每个顶点与这个正交视口相乘,再和自己的变换矩阵相乘,形成新的坐标信息,正交视口实际上也是一个4*4的模版矩阵,它的排列算法是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 正交视口
* @param m 正交的1维数组
* @param left 左边界
* @param right 右边界
* @param bottom 底边界
* @param top 上边界
* @param near 近截面
* @param far 远截面
*/
public static void orthoM(float[] m,float left,float right,float bottom,float top,float near,float far)
{
m[0] = -2.0f*1.0f/(right - left);
m[5] = 2.0f*1.0f/(top - bottom);
m[10] = 1.0f/(far - near);
  
m[12] = (right + left)/(right - left);
m[13] = (bottom + top)/(bottom - top);
m[14] = near/(near - far);
  
m[1] = m[2] = m[3] = m[4] =m[6] = m[7] = m[8] = m[9] = m[11] = 0;
m[15] = 1.0f;
}

 

好吧,今天就到这里了,还有什么呢?着色器?滤镜等我先睡会吧。。。

转载请注明:HTML5游戏开发者社区 » HTML5 2D游戏引擎研发系列 第二章

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值