原文地址:http://blog.xoppa.com/3d-frustum-culling-with-libgdx/
非翻译,详细内容请见原文,捡点我能理解的说说吧~
关于矩阵之类的知识可以看一下 http://www.cppblog.com/lovedday/archive/2008/01/09/40813.html 我也不懂~~
渲染3D场景的时候,可见物件的数量要远远小于物件总数。
试想,在铺了一地地砖的时候,视角对着天,这时候需要渲染的地砖就没有了。摄像机照不到的地方,完全没有必要渲染。
只渲染可视视角范围内的物件,对于性能的提升是很大的。
直接上测试代码:
1 package com.mygdx.game; 2 3 import com.badlogic.gdx.ApplicationListener; 4 import com.badlogic.gdx.Gdx; 5 import com.badlogic.gdx.assets.AssetManager; 6 import com.badlogic.gdx.graphics.Camera; 7 import com.badlogic.gdx.graphics.Color; 8 import com.badlogic.gdx.graphics.GL20; 9 import com.badlogic.gdx.graphics.PerspectiveCamera; 10 import com.badlogic.gdx.graphics.g2d.BitmapFont; 11 import com.badlogic.gdx.graphics.g3d.Environment; 12 import com.badlogic.gdx.graphics.g3d.Model; 13 import com.badlogic.gdx.graphics.g3d.ModelBatch; 14 import com.badlogic.gdx.graphics.g3d.ModelInstance; 15 import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; 16 import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight; 17 import com.badlogic.gdx.graphics.g3d.utils.CameraInputController; 18 import com.badlogic.gdx.math.Vector3; 19 import com.badlogic.gdx.math.collision.BoundingBox; 20 import com.badlogic.gdx.scenes.scene2d.Stage; 21 import com.badlogic.gdx.scenes.scene2d.ui.Label; 22 import com.badlogic.gdx.utils.Array; 23 24 /** 25 * Created by HanHongmin on 14-7-24. 26 */ 27 public class FrustumCullingTestA implements ApplicationListener { 28 protected PerspectiveCamera cam; 29 protected CameraInputController camController; 30 protected ModelBatch modelBatch; 31 protected AssetManager assets; 32 protected Array<ModelInstance> instances = new Array(); 33 protected Environment environment; 34 protected boolean loading; 35 36 protected Stage stage; 37 protected Label label; 38 protected BitmapFont font; 39 protected StringBuilder stringBuilder; 40 private Vector3 tempPosition = new Vector3(); 41 42 @Override 43 public void create () { 44 stage = new Stage(); 45 font = new BitmapFont(); 46 label = new Label(" ", new Label.LabelStyle(font, Color.WHITE)); 47 stage.addActor(label); 48 stringBuilder = new StringBuilder(); 49 50 modelBatch = new ModelBatch(); 51 environment = new Environment(); 52 environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f)); 53 environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f)); 54 55 cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 56 cam.position.set(0f, 7f, 10f); 57 cam.lookAt(0,0,0); 58 cam.near = 1f; 59 cam.far = 300f; 60 cam.update(); 61 62 camController = new CameraInputController(cam); 63 Gdx.input.setInputProcessor(camController); 64 65 assets = new AssetManager(); 66 assets.load("data/box.g3db", Model.class); 67 assets.load("data/box2.g3db", Model.class); 68 loading = true; 69 } 70 71 private void doneLoading() { 72 Model t1 = assets.get("data/box.g3db", Model.class); 73 Model t2 = assets.get("data/box2.g3db", Model.class); 74 75 for(int i=0;i<100;i++){ 76 for(int j=0;j<100;j++){ 77 Model t; 78 if((i+j)%2==0){ 79 t = t1; 80 }else{ 81 t=t2; 82 } 83 ModelInstance shipInstance = new ModelInstance(t); 84 shipInstance.transform.setToTranslation(i, j, 0);//设置位置 85 instances.add(shipInstance); 86 } 87 } 88 89 loading = false; 90 } 91 92 private int visibleCount; 93 @Override 94 public void render () { 95 if (loading && assets.update()) 96 doneLoading(); 97 camController.update(); 98 99 Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 100 Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); 101 102 modelBatch.begin(cam); 103 visibleCount = 0; 104 for (final ModelInstance instance : instances) { 105 if (isVisible(cam, instance)) { 106 modelBatch.render(instance, environment); 107 visibleCount++; 108 } 109 } 110 modelBatch.end(); 111 112 stringBuilder.setLength(0); 113 stringBuilder.append(" FPS: ").append(Gdx.graphics.getFramesPerSecond()); 114 stringBuilder.append(" Visible: ").append(visibleCount); 115 label.setText(stringBuilder); 116 stage.draw(); 117 } 118 119 protected boolean isVisible(final Camera cam, final ModelInstance instance) { 120 instance.transform.getTranslation(tempPosition);//把位置信息放到tempPosition中 121 return cam.frustum.pointInFrustum(tempPosition);//frustum平截头体,表示的就是可视范围 122 } 123 124 @Override 125 public void dispose () { 126 modelBatch.dispose(); 127 instances.clear(); 128 assets.dispose(); 129 } 130 131 @Override 132 public void resize(int width, int height) { 133 stage.getViewport().update(width, height, true); 134 } 135 136 @Override 137 public void pause() { 138 } 139 140 @Override 141 public void resume() { 142 } 143 }
以上代码中,关键部分是isVisible函数,该函数中计算了物件位置是否是在可视范围内。
这种算法稍稍有点瑕疵,运行即可看出效果。如果一个物件的位置不在可视范围内,但是此物件有一角却在,常理来说是应该显示的,而实际却没有显示出来。
上代码吧:
1 package com.mygdx.game; 2 3 import com.badlogic.gdx.ApplicationListener; 4 import com.badlogic.gdx.Gdx; 5 import com.badlogic.gdx.assets.AssetManager; 6 import com.badlogic.gdx.graphics.Camera; 7 import com.badlogic.gdx.graphics.Color; 8 import com.badlogic.gdx.graphics.GL20; 9 import com.badlogic.gdx.graphics.PerspectiveCamera; 10 import com.badlogic.gdx.graphics.g2d.BitmapFont; 11 import com.badlogic.gdx.graphics.g3d.Environment; 12 import com.badlogic.gdx.graphics.g3d.Model; 13 import com.badlogic.gdx.graphics.g3d.ModelBatch; 14 import com.badlogic.gdx.graphics.g3d.ModelInstance; 15 import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; 16 import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight; 17 import com.badlogic.gdx.graphics.g3d.utils.CameraInputController; 18 import com.badlogic.gdx.math.Vector3; 19 import com.badlogic.gdx.math.collision.BoundingBox; 20 import com.badlogic.gdx.scenes.scene2d.Stage; 21 import com.badlogic.gdx.scenes.scene2d.ui.Label; 22 import com.badlogic.gdx.utils.Array; 23 24 /** 25 * Created by HanHongmin on 14-7-24. 26 */ 27 public class FrustumCullingTest implements ApplicationListener { 28 protected PerspectiveCamera cam; 29 protected CameraInputController camController; 30 protected ModelBatch modelBatch; 31 protected AssetManager assets; 32 protected Array<GameObject> instances = new Array(); 33 protected Environment environment; 34 protected boolean loading; 35 36 protected Stage stage; 37 protected Label label; 38 protected BitmapFont font; 39 protected StringBuilder stringBuilder; 40 private Vector3 tempPosition = new Vector3(); 41 42 @Override 43 public void create () { 44 stage = new Stage(); 45 font = new BitmapFont(); 46 label = new Label(" ", new Label.LabelStyle(font, Color.WHITE)); 47 stage.addActor(label); 48 stringBuilder = new StringBuilder(); 49 50 modelBatch = new ModelBatch(); 51 environment = new Environment(); 52 environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f)); 53 environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f)); 54 55 cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 56 cam.position.set(0f, 7f, 10f); 57 cam.lookAt(0,0,0); 58 cam.near = 1f; 59 cam.far = 30f;//视角最远 60 cam.update(); 61 62 camController = new CameraInputController(cam); 63 Gdx.input.setInputProcessor(camController); 64 65 assets = new AssetManager(); 66 assets.load("data/box.g3db", Model.class); 67 assets.load("data/box2.g3db", Model.class); 68 loading = true; 69 } 70 71 private void doneLoading() { 72 Model t1 = assets.get("data/box.g3db", Model.class); 73 Model t2 = assets.get("data/box2.g3db", Model.class); 74 75 for(int i=0;i<100;i++){ 76 for(int j=0;j<100;j++){ 77 Model t; 78 if((i+j)%2==0){ 79 t = t1; 80 }else{ 81 t=t2; 82 } 83 GameObject shipInstance = new GameObject(t); 84 shipInstance.transform.setToTranslation(i, j, 0);//设置位置 85 instances.add(shipInstance); 86 } 87 } 88 89 loading = false; 90 } 91 92 private int visibleCount; 93 @Override 94 public void render () { 95 if (loading && assets.update()) 96 doneLoading(); 97 camController.update(); 98 99 Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 100 Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); 101 102 modelBatch.begin(cam); 103 visibleCount = 0; 104 for (final GameObject instance : instances) { 105 if (isVisible(cam, instance)) { 106 modelBatch.render(instance, environment); 107 visibleCount++; 108 } 109 } 110 modelBatch.end(); 111 112 stringBuilder.setLength(0); 113 stringBuilder.append(" FPS: ").append(Gdx.graphics.getFramesPerSecond()); 114 stringBuilder.append(" Visible: ").append(visibleCount); 115 label.setText(stringBuilder); 116 stage.draw(); 117 } 118 119 protected boolean isVisible(final Camera cam, final GameObject instance) { 120 instance.transform.getTranslation(tempPosition); 121 tempPosition.add(instance.center); 122 return cam.frustum.boundsInFrustum(tempPosition, instance.dimensions); 123 } 124 125 @Override 126 public void dispose () { 127 modelBatch.dispose(); 128 instances.clear(); 129 assets.dispose(); 130 } 131 132 @Override 133 public void resize(int width, int height) { 134 stage.getViewport().update(width, height, true); 135 } 136 137 @Override 138 public void pause() { 139 } 140 141 @Override 142 public void resume() { 143 } 144 145 146 147 public static class GameObject extends ModelInstance { 148 public final Vector3 center = new Vector3(); 149 public final Vector3 dimensions = new Vector3(); 150 151 private final static BoundingBox bounds = new BoundingBox(); 152 153 public GameObject(Model model) { 154 super(model); 155 calculateBoundingBox(bounds); 156 center.set(bounds.getCenter()); 157 dimensions.set(bounds.getDimensions()); 158 } 159 } 160 }
这个怎么算的,其实我想说:呵呵!我也不懂!!!谁整明白了请告诉我啊~
估计是看见一点就显示吧。反正是打到我们想要的效果了。
原文作者提到,当物件旋转时,可能会出现问题。贴一下原作最后的代码:
1 package com.mygdx.game; 2 3 import com.badlogic.gdx.ApplicationListener; 4 import com.badlogic.gdx.Gdx; 5 import com.badlogic.gdx.assets.AssetManager; 6 import com.badlogic.gdx.graphics.Camera; 7 import com.badlogic.gdx.graphics.Color; 8 import com.badlogic.gdx.graphics.GL20; 9 import com.badlogic.gdx.graphics.PerspectiveCamera; 10 import com.badlogic.gdx.graphics.g2d.BitmapFont; 11 import com.badlogic.gdx.graphics.g3d.Environment; 12 import com.badlogic.gdx.graphics.g3d.Model; 13 import com.badlogic.gdx.graphics.g3d.ModelBatch; 14 import com.badlogic.gdx.graphics.g3d.ModelInstance; 15 import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; 16 import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight; 17 import com.badlogic.gdx.graphics.g3d.utils.CameraInputController; 18 import com.badlogic.gdx.math.Vector3; 19 import com.badlogic.gdx.math.collision.BoundingBox; 20 import com.badlogic.gdx.scenes.scene2d.Stage; 21 import com.badlogic.gdx.scenes.scene2d.ui.Label; 22 import com.badlogic.gdx.utils.Array; 23 24 /** 25 * Created by HanHongmin on 14-7-24. 26 */ 27 public class FrustumCullingTestC implements ApplicationListener { 28 protected PerspectiveCamera cam; 29 protected CameraInputController camController; 30 protected ModelBatch modelBatch; 31 protected AssetManager assets; 32 protected Array<GameObject> instances = new Array(); 33 protected Environment environment; 34 protected boolean loading; 35 36 protected Stage stage; 37 protected Label label; 38 protected BitmapFont font; 39 protected StringBuilder stringBuilder; 40 private Vector3 tempPosition = new Vector3(); 41 42 @Override 43 public void create () { 44 stage = new Stage(); 45 font = new BitmapFont(); 46 label = new Label(" ", new Label.LabelStyle(font, Color.WHITE)); 47 stage.addActor(label); 48 stringBuilder = new StringBuilder(); 49 50 modelBatch = new ModelBatch(); 51 environment = new Environment(); 52 environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f)); 53 environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f)); 54 55 cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 56 cam.position.set(0f, 7f, 10f); 57 cam.lookAt(0,0,0); 58 cam.near = 1f; 59 cam.far = 30f;//视角最远 60 cam.update(); 61 62 camController = new CameraInputController(cam); 63 Gdx.input.setInputProcessor(camController); 64 65 assets = new AssetManager(); 66 assets.load("data/box.g3db", Model.class); 67 assets.load("data/box2.g3db", Model.class); 68 loading = true; 69 } 70 71 private void doneLoading() { 72 Model t1 = assets.get("data/box.g3db", Model.class); 73 Model t2 = assets.get("data/box2.g3db", Model.class); 74 75 for(int i=0;i<100;i++){ 76 for(int j=0;j<100;j++){ 77 Model t; 78 if((i+j)%2==0){ 79 t = t1; 80 }else{ 81 t=t2; 82 } 83 GameObject shipInstance = new GameObject(t); 84 shipInstance.transform.setToTranslation(i, j, 0);//设置位置 85 instances.add(shipInstance); 86 } 87 } 88 89 loading = false; 90 } 91 92 private int visibleCount; 93 @Override 94 public void render () { 95 if (loading && assets.update()) 96 doneLoading(); 97 camController.update(); 98 99 Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 100 Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); 101 102 modelBatch.begin(cam); 103 visibleCount = 0; 104 for (final GameObject instance : instances) { 105 if (isVisible(cam, instance)) { 106 modelBatch.render(instance, environment); 107 visibleCount++; 108 } 109 } 110 modelBatch.end(); 111 112 stringBuilder.setLength(0); 113 stringBuilder.append(" FPS: ").append(Gdx.graphics.getFramesPerSecond()); 114 stringBuilder.append(" Visible: ").append(visibleCount); 115 label.setText(stringBuilder); 116 stage.draw(); 117 } 118 119 protected boolean isVisible(final Camera cam, final GameObject instance) { 120 instance.transform.getTranslation(tempPosition); 121 tempPosition.add(instance.center); 122 return cam.frustum.sphereInFrustum(tempPosition, instance.radius); 123 } 124 125 @Override 126 public void dispose () { 127 modelBatch.dispose(); 128 instances.clear(); 129 assets.dispose(); 130 } 131 132 @Override 133 public void resize(int width, int height) { 134 stage.getViewport().update(width, height, true); 135 } 136 137 @Override 138 public void pause() { 139 } 140 141 @Override 142 public void resume() { 143 } 144 145 146 147 public static class GameObject extends ModelInstance { 148 public final Vector3 center = new Vector3(); 149 public final Vector3 dimensions = new Vector3(); 150 public final float radius; 151 152 private final static BoundingBox bounds = new BoundingBox(); 153 154 public GameObject(Model model) { 155 super(model); 156 calculateBoundingBox(bounds); 157 center.set(bounds.getCenter()); 158 dimensions.set(bounds.getDimensions()); 159 radius = dimensions.len() / 2f; 160 } 161 } 162 }
注:以上代码我都有改过,比如GameObject的构造方法。会不会有问题我真不清楚。
另外还有两点:
1. Camera的far属性能够影响可视范围,不要忘了。
2. far边缘像是被刀切了一样,一条线,far越近越明显,是否有办法根据物件和摄像机位置再次筛选是否渲染呢?
far是一条直线,摄像机正对的中点看得更远一下可能更真实。