系列教程索引地址:http://www.mcbbs.net/thread-404402-1-1.html

由于个人才疏学浅,教程的说明难免有错漏之处,还请各位及时指出咯~> <
MC的整个渲染引擎,基于开源游戏API:lwjgl的OpenGL部分而搭建。这也就意味着MC的渲染过程中,会大量的用到OpenGL的方法(所以,在之前就写过GL程序的同学有福了~~)。
- Tessellator t = Tessellator.instance;
然后,通过
- t.startDrawingQuads();
- t.startDrawing(int MODE);
- t.setColorRGBA_F(r, g, b, a);
- t.setNormal(u, v, w);
- t.addVertexWithUV(x, y, z, u0, v0);……
- t.draw();
- Tessellator t = Tessellator.instance;
- t.startDrawing(GL11.GL_QUADS); //或t.startDrawingQuads()
- t.setColorRGBA_F(1.0F, 0.0F, 0.0F, 0.5F);
- t.setNormal(0, 0, 1);
- t.addVertex(0.0, 0.0, 0.0);
- t.addVertex(1.0, 0.0, 0.0);
- t.addVertex(1.0, 1.0, 0.0);
- t.addVertex(0.0, 1.0, 0.0);
- t.draw(); //结束绘制call
在介绍了Tessellator之后,我们已经了解了MC中基本的图形绘制是如何进行的。然而只凭借Tessellator,我们能做到的事是相当受限的。要对我们所绘制的东西进行变换、混合、遮罩操作……我们都需要用到OpenGL的原生函数。
在lwjgl中,OpenGL的库函数被包装在GL[XX](XX是GL版本号)类中。你只需要访问这些类的公有函数就可以调用GLAPI了。
实际上,大部分的GL函数在GL11类当中。
基本的变换操作
在MCMod中(以及其他所有的游戏渲染过程中),我们最常遇到的问题就是对绘制的图形进行移动和变换的问题。例如:将在原点绘制好的子弹实体平移到当前实体的位置;根据时间流逝的长度让光束旋转一个角度,等等。在OpenGL中,我们可以进行如下的几种变换:
·平移 将所有顶点坐标平移(Δx, Δy, Δz)个单位
-对应函数:glTranslate*(dx, dy, dz);
·旋转 将所有顶点绕某个轴,旋转α度。
-对应函数:glRotate*(α, u, v, w);
·缩放 将所有顶点关于原点,缩放scale倍。
-对应函数:glScale*(scaleX, scaleY, scaleZ);
关于这些OpenGL变换函数的具体效果和操作方法,请参见网上各种各样的OpenGL参考和教程。我相信随便一个教程都说的比我好>. <
变换操作和变换的复合(啥?代码要倒着读?!)
通过以上的三个变换函数,我们已经可以对绘制出来的东西干很多有趣的事了。比如说,下面的代码让我们绘制的东西绕(0, 0, 0),关于Y轴周期性旋转:
- GL11.glRotated(Minecraft.getSystemTime() / 100, 0, 1, 0);
- t.startDrawingQuads();
- //...
- t.draw();
一个变换通常是远远不够用的,我们经常想要对一个物体进行很多的复合变换。而在进行复合变换的时候,有些神奇的代码规则是你不得不注意的。
例如,下面的代码让绘制的物体绕中心周期旋转,然后再将它往Y轴正方向移动一个单位:
- GL11.glTranslated(0, 1, 0);
- GL11.glRotated(Minecraft.getSystemTime() / 100, 0, 1, 0);
- //你的绘制代码
扩展阅读:矩阵乘法,左乘和右乘
↓点我
变换的复合(记得推栈和弹栈!)
假设在渲染过程中,我们想要进行一定的复合变换。比如说:让风车的扇叶部分周期旋转,而支架部分固定不动,该怎么办呢?最简单方法是这样的:
(这里假设mainPart和fan都是一个类似模型的类的实例,调用其draw函数会把面绘制出来)
- mainPart.draw();
- GL11.glTranslated(0, offsetY, 0); //将扇叶移动到正确的位置上
- GL11.glRotated(Math.cos(Minecraft.getSystemTime() / 100D), 1, 0, 0); //让扇叶关于中心旋转
- fan.draw();
然而,假设你为了更好的渲染效果,给这个风车加了两层不同的转动,那么又该怎么绘制呢?
- mainPart.draw();
- GL11.glTranslated(0, offsetY, 0); //将扇叶移动到正确的位置上
- GL11.glRotated(Minecraft.getSystemTime() / 100D, 1, 0, 0); //让扇叶关于中心旋转
- fan.draw();
- GL11.glRotated(Minecraft.getSystemTime() / 80D, 1, 0, 0); //让扇叶2关于中心旋转(速度不同)
- fan2.draw();
如果我们想要让每个部分进行分别的变换动作,而不让它们影响到彼此,该怎么办呢?
答案是下面的代码:
- GL11.glMatrixMode(GL11.GL_MODELVIEW); //设置当前活跃的矩阵为ModelView(模型视角)矩阵
- mainPart.draw();
- GL11.glTranslated(0, offsetY, 0); //将扇叶移动到正确的位置上
- GL11.glPushMatrix(); //保存
- GL11.glRotated(Minecraft.getSystemTime() / 100D, 1, 0, 0); //旋转A
- fan.draw();
- GL11.glPopMatrix(); //还原
- GL11.glPushMatrix(); //保存
- GL11.glRotated(Minecraft.getSystemTime() / 80D, 1, 0, 0); //旋转B
- fan2.draw();
- GL11.glPopMatrix(); //还原
知识点:glPushMatrix和glPopMatrix
OpenGL提供了便捷的恢复其某一时刻的部分状态的方法:推栈和弹栈。
在这里,我们使用glPushMatrix和glPopMatrix来存储和恢复当前活跃矩阵的状态。 活跃的矩阵可以用glMatrixMode(MODE)指定。除了Modelview(模视变换)矩阵,还有Projection(投影)矩阵,用来设置摄像机位置/投影方法。
你可以把这对函数的功能想象成对一个栈的操作(实际上就是……),就像这样:

在你的渲染器里面,mc一般会给你传递类似这样的参数:
- public void render(double x, double y, double z, ......)
- GL11.glTranslated(x, y, z);
因此,强烈建议把渲染代码写成如下形式:
- //其他状态设置
- GL11.glPushMatrix();
- //坐标变换
- //绘制
- GL11.glPopMatrix();
- //恢复设置的状态