花了一个小时研究了CullState,发现以前没学线性代数真是个错误。唉!不过这也和学校教育制度有关吧,如果学校能以2个方向培养人才:先理论后应用,先应用后理论,学生选择适合自己的方向这样多好!
话不多说,下面为译文。
注:本系列教程全部翻译完之后可能会以PDF的形式发布。
如果有什么错误可以留言或EMAIL:kakashi9bi@gmail.com给我。
jME版本 :jME_2.0.1_Stable
开发工具:MyEclipse8.5
操作系统:Window7/Vista
在这个向导中我们准备增加一个玩家和一些控制。我们将集中于交通工具的基本移动和跟随摄像机(下文以ChaseCamera代替)。所以,我们将为交通工具增加一个Box占位符,增加一个ChaseCamera,然后学习关于构建我们的自定义InputHandle(输入处理)。主要,我们将构建一个ThirdPersonHandler(第三人称控制器)。然而,jME包含一个预建的ThirdPersonHandle,它具有更多特性并值得研究。为了达到最好的教育效果,我们将创建自己的控制器。 同样地,我们这一节课将在前一节的基础上创建代码。每次迭代我们将清理现有的代码,为了达到展示怎样把事情做得更好的效果。 Lesson5首先最主要的改变是我们创建了一个ForceFieldFence的类。通过将所有force field的数据移到它自己的类里面,这将允许我们可以在之后心血来潮的时候改善它。首先,我创建了一个新的类继承自Node。我们继承Node所以我们能附加fence到这个类本身并和平常一样把fence增加到scene。我接着创建了buildFence方法,从我们游戏的buildEnvironment方法中移除代码(除了位置和缩放调用,它们是游戏相关的,和围栏没有关系)到这个新的方法。现在,我们也想要继续力场的动画,所以我们也将移动Texture t到这个类并创建update方法。游戏中的Texture代码将移动到这个方法。现在,在游戏代码中,buildEnvironment方法我们加入: fence = new ForceFieldFence("forceFieldFence"); 与此同时为游戏类增加一个类变量 private ForceFieldFence fence; 最后,我们需要告诉fence类在游戏update期间也update。在update方法中增加: fence.update(interpolation); 将这么做。 现在,游戏将和之前运行的一样,但我们有个fence让一切更容易。 package lesson5; import com.jme.bounding.BoundingBox; import com.jme.image.Texture; import com.jme.math.FastMath; import com.jme.math.Quaternion; import com.jme.math.Vector3f; import com.jme.renderer.Renderer; import com.jme.scene.Node; import com.jme.scene.SharedMesh; import com.jme.scene.shape.Box; import com.jme.scene.shape.Cylinder; import com.jme.scene.state.BlendState; import com.jme.scene.state.TextureState; import com.jme.system.DisplaySystem; import com.jme.util.TextureManager; public class ForceFieldFence extends Node { private Texture t; public ForceFieldFence(String name){ super(name); buildFence(); } public void buildFence(){ //这个圆柱体将扮演每个角落那4个主要的柱子 Cylinder postGeometry = new Cylinder("Cylinder", 10, 10, 1, 10); Quaternion q = new Quaternion(); //将圆柱体转为垂直 q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0)); postGeometry.setLocalRotation(q); postGeometry.setModelBound(new BoundingBox()); postGeometry.updateModelBound(); //我们将共享柱子4次(每个柱子一次) //增加原始的圆柱体不是一种好的方法 //因为sharedmesh将修改它的本地值 //我们然后将调整柱子到它的位置 //使用神奇的数字是不好的,但帮助我们声明它的位置^_^ SharedMesh post1 = new SharedMesh("post1",postGeometry); post1.setLocalTranslation(new Vector3f(0,0.5f,0)); SharedMesh post2 = new SharedMesh("post2",postGeometry); post2.setLocalTranslation(new Vector3f(32,0.5f,0)); SharedMesh post3 = new SharedMesh("post3",postGeometry); post3.setLocalTranslation(new Vector3f(0,0.5f,32)); SharedMesh post4 = new SharedMesh("post4",postGeometry); post4.setLocalTranslation(new Vector3f(32,0.5f,32)); //将所有的柱子放入一个tower Node Node towerNode = new Node("tower"); towerNode.attachChild(post1); towerNode.attachChild(post2); towerNode.attachChild(post3); towerNode.attachChild(post4); //将towerNode放入不透明队列(Opaque queue),我们不必看穿它 //而我们想穿过forcefield看到它 towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); //为towerNode加载纹理 TextureState ts2 = DisplaySystem.getDisplaySystem().getRenderer().createTextureState(); Texture t2 = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/post.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts2.setTexture(t2); towerNode.setRenderState(ts2); //这个圆柱体将是水平的建筑 //它将field限制在一个地方 Cylinder strutsGeometry = new Cylinder("struts",10,10,0.125f,32); strutsGeometry.setModelBound(new BoundingBox()); strutsGeometry.updateModelBound(); //同样的,我们将共享mesh SharedMesh strut1 = new SharedMesh("strut1",strutsGeometry); Quaternion rotate90 = new Quaternion(); rotate90.fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0)); strut1.setLocalRotation(rotate90); strut1.setLocalTranslation(new Vector3f(16,3f,0)); SharedMesh strut2 = new SharedMesh("strut2",strutsGeometry); strut2.setLocalTranslation(new Vector3f(0,3f,16)); SharedMesh strut3 = new SharedMesh("strut3",strutsGeometry); strut3.setLocalTranslation(new Vector3f(32,3f,16)); SharedMesh strut4 = new SharedMesh("strut4",strutsGeometry); strut4.setLocalRotation(rotate90); strut4.setLocalTranslation(new Vector3f(16,3f,32)); //将所有建筑放入一个结点 Node strutNode = new Node("strutNode"); strutNode.attachChild(strut1); strutNode.attachChild(strut2); strutNode.attachChild(strut3); strutNode.attachChild(strut4); //为建筑加载纹理 TextureState ts3 = DisplaySystem.getDisplaySystem().getRenderer().createTextureState(); Texture t3 = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/rust.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); ts3.setTexture(t3); strutNode.setRenderState(ts3); //创建真实的forcefield //第一个box控制着X轴,而第二个控制着z轴 //作为示例,我们没有旋转box,而是展示box能被不同地创建 Box forceFieldX = new Box( "forceFieldX", new Vector3f(-16, -3f, -0.1f), new Vector3f(16, 3f, 0.1f) ); forceFieldX.setModelBound(new BoundingBox()); forceFieldX.updateModelBound(); //我们也将共享这些box SharedMesh forceFieldX1 = new SharedMesh("forceFieldX1", forceFieldX); forceFieldX1.setLocalTranslation(new Vector3f(16,0,0)); SharedMesh forceFieldX2 = new SharedMesh("forceFieldX2", forceFieldX); forceFieldX2.setLocalTranslation(new Vector3f(16,0,32)); //另一个box,控制z轴的那个 Box forceFieldZ = new Box( "forceFieldY", new Vector3f(-0.1f, -3f, -16f), new Vector3f(0.1f, 3f, 16f) ); forceFieldZ.setModelBound(new BoundingBox()); forceFieldZ.updateModelBound(); //我们也将共享这些box SharedMesh forceFieldZ1 = new SharedMesh("forceFieldZ1", forceFieldZ); forceFieldZ1.setLocalTranslation(new Vector3f(0,0,16)); SharedMesh forceFieldZ2 = new SharedMesh("forceFieldZ2", forceFieldZ); forceFieldZ2.setLocalTranslation(new Vector3f(32,0,16)); //增加所有的forceField到一个单一的Node Node forceFieldNode = new Node("forceFieldNode"); forceFieldNode.attachChild(forceFieldX1); forceFieldNode.attachChild(forceFieldX2); forceFieldNode.attachChild(forceFieldZ1); forceFieldNode.attachChild(forceFieldZ2); //为force field元素增加texture TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState(); t = TextureManager.loadTexture( getClass().getClassLoader() .getResource("res/reflector.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear ); t.setWrap(Texture.WrapMode.Repeat); t.setTranslation(new Vector3f()); ts.setTexture(t); //为transparent结点增加Alpha值 BlendState as1 = DisplaySystem.getDisplaySystem().getRenderer().createBlendState(); as1.setSourceFunction( BlendState.SourceFunction.SourceAlpha ); as1.setDestinationFunction( BlendState.DestinationFunction.One ); as1.setTestFunction(BlendState.TestFunction.GreaterThan); as1.setBlendEnabled(true); as1.setTestEnabled(true); as1.setEnabled(true); forceFieldNode.setRenderState(as1); forceFieldNode.setRenderState(ts); forceFieldNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); strutNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); Node forceFieldFence = new Node("forceFieldFenceNode"); forceFieldFence.attachChild(towerNode); forceFieldFence.attachChild(strutNode); forceFieldFence.attachChild(forceFieldNode); attachChild(forceFieldFence); } public void update(float interpolation){ //我们将使用插值(interpolation)去保持forcefield //texture运动速度和计算机保持一致 //我们更新texture矩阵的Y值去让 //force-field看起来像是在移动 t.getTranslation().y += 0.3f*interpolation; //如果translation超过1,它被换行,因此回到开始 //并检查这个(防止Vector的Y值变得太大) t.getTranslation().y = t.getTranslation().y > 1 ? 0 : t.getTranslation().y; } } 最后的一点优化是在game中添加了CullState。这将是我们的首次提速。CullState处理三角形的剔除,这和视锥挑选(Frustum Culling)是不同的,因为它只和单个三角形的winding有关。顶点的winding定义了三角形的法向并接着告诉了我们三角形是否面对我们。jME使用和OpenGL一样的右手坐标系。这意味着,如果一个三角形的winding是逆时针(CCW)的,那么它面向屏幕。 我们没理由看到三角形的背后,因此,也没理由去绘制它们。所以,我们将为整个scene应用CullState去移除所有三角形后面的面。这很直接,你只需要在initGame方法中加入: CullState cs = display.getRenderer().createCullState(); cs.setCullFace(CullState.Face.Back); scene.setRenderState(cs); 耶,那很容易。我简单创建了一个新的state并告诉它剔除三角形背后的面。够还不够简单?5、增加一个跟随摄像机(Chase Camera)
5.1、清理和优化
5.2、ForceFieldFence.java
5.3、剔除/挑选状态(CullState)