diff --git a/src/com/blarg/gdx/graphics/AlphaTestCameraGroupStrategy.java b/src/com/blarg/gdx/graphics/AlphaTestCameraGroupStrategy.java
new file mode 100644
index 0000000..ff89540
--- /dev/null
+++ b/src/com/blarg/gdx/graphics/AlphaTestCameraGroupStrategy.java
@@ -0,0 +1,220 @@
+package com.blarg.gdx.graphics;
+
+import java.util.Comparator;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.Camera;
+import com.badlogic.gdx.graphics.GL10;
+import com.badlogic.gdx.graphics.g3d.decals.Decal;
+import com.badlogic.gdx.graphics.g3d.decals.DecalMaterial;
+import com.badlogic.gdx.graphics.g3d.decals.GroupStrategy;
+import com.badlogic.gdx.graphics.glutils.ShaderProgram;
+import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.Disposable;
+import com.badlogic.gdx.utils.ObjectMap;
+import com.badlogic.gdx.utils.Pool;
+
+/**
+ *
+ * Note:
+ * This is almost exactly the same as {@link com.badlogic.gdx.graphics.g3d.decals.CameraGroupStrategy} except that
+ * this version requires shader support and it's shader has been slightly modified to perform a simple alpha-test
+ * in the fragment shader.
+ *
+ *
+ *
+ * Minimalistic grouping strategy that splits decals into opaque and transparent ones enabling and disabling blending as needed.
+ * Opaque decals are rendered first (decal color is ignored in opacity check).
+ * Use this strategy only if the vast majority of your decals are opaque and the few transparent ones are unlikely to overlap.
+ *
+ *
+ * Can produce invisible artifacts when transparent decals overlap each other.
+ *
+ *
+ * Needs to be explicitely disposed as it might allocate a ShaderProgram when GLSL 2.0 is used.
+ *
+ *
+ * States (* = any, EV = entry value - same as value before flush):
+ *
+ *
+ * |
+ * expects |
+ * exits on |
+ *
+ *
+ * glDepthMask |
+ * true |
+ * EV |
+ *
+ *
+ * GL_DEPTH_TEST |
+ * enabled |
+ * EV |
+ *
+ *
+ * glDepthFunc |
+ * GL_LESS | GL_LEQUAL |
+ * EV |
+ *
+ *
+ * GL_BLEND |
+ * disabled |
+ * EV | disabled |
+ *
+ *
+ * glBlendFunc |
+ * * |
+ * * |
+ *
+ *
+ * GL_TEXTURE_2D |
+ * * |
+ * disabled |
+ *
+ *
+ * */
+public class AlphaTestCameraGroupStrategy implements GroupStrategy, Disposable {
+ private static final int GROUP_OPAQUE = 0;
+ private static final int GROUP_BLEND = 1;
+
+ Pool> arrayPool = new Pool>(16) {
+ @Override
+ protected Array newObject () {
+ return new Array();
+ }
+ };
+ Array> usedArrays = new Array>();
+ ObjectMap> materialGroups = new ObjectMap>();
+
+ Camera camera;
+ ShaderProgram shader;
+ private final Comparator cameraSorter;
+
+ public AlphaTestCameraGroupStrategy (final Camera camera) {
+ this(camera, new Comparator() {
+ @Override
+ public int compare (Decal o1, Decal o2) {
+ float dist1 = camera.position.dst(o1.getPosition());
+ float dist2 = camera.position.dst(o2.getPosition());
+ return (int)Math.signum(dist2 - dist1);
+ }
+ });
+ }
+
+ public AlphaTestCameraGroupStrategy(Camera camera, Comparator sorter) {
+ if (!Gdx.graphics.isGL20Available())
+ throw new UnsupportedOperationException("AlphaTestCameraGroupStrategy requires shader support.");
+
+ this.camera = camera;
+ this.cameraSorter = sorter;
+ createDefaultShader();
+
+ }
+
+ public void setCamera (Camera camera) {
+ this.camera = camera;
+ }
+
+ public Camera getCamera () {
+ return camera;
+ }
+
+ @Override
+ public int decideGroup (Decal decal) {
+ return decal.getMaterial().isOpaque() ? GROUP_OPAQUE : GROUP_BLEND;
+ }
+
+ @Override
+ public void beforeGroup (int group, Array contents) {
+ if (group == GROUP_BLEND) {
+ Gdx.gl.glEnable(GL10.GL_BLEND);
+ contents.sort(cameraSorter);
+ } else {
+ for (int i = 0, n = contents.size; i < n; i++) {
+ Decal decal = contents.get(i);
+ Array materialGroup = materialGroups.get(decal.getMaterial());
+ if (materialGroup == null) {
+ materialGroup = arrayPool.obtain();
+ materialGroup.clear();
+ usedArrays.add(materialGroup);
+ materialGroups.put(decal.getMaterial(), materialGroup);
+ }
+ materialGroup.add(decal);
+ }
+
+ contents.clear();
+ for (Array materialGroup : materialGroups.values()) {
+ contents.addAll(materialGroup);
+ }
+
+ materialGroups.clear();
+ arrayPool.freeAll(usedArrays);
+ usedArrays.clear();
+ }
+ }
+
+ @Override
+ public void afterGroup (int group) {
+ if (group == GROUP_BLEND) {
+ Gdx.gl.glDisable(GL10.GL_BLEND);
+ }
+ }
+
+ @Override
+ public void beforeGroups () {
+ Gdx.gl.glEnable(GL10.GL_DEPTH_TEST);
+ shader.begin();
+ shader.setUniformMatrix("u_projectionViewMatrix", camera.combined);
+ shader.setUniformi("u_texture", 0);
+ }
+
+ @Override
+ public void afterGroups () {
+ shader.end();
+ Gdx.gl.glDisable(GL10.GL_TEXTURE_2D);
+ Gdx.gl.glDisable(GL10.GL_DEPTH_TEST);
+ }
+
+ private void createDefaultShader () {
+ String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
+ + "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
+ + "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
+ + "uniform mat4 u_projectionViewMatrix;\n" //
+ + "varying vec4 v_color;\n" //
+ + "varying vec2 v_texCoords;\n" //
+ + "\n" //
+ + "void main()\n" //
+ + "{\n" //
+ + " v_color = " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
+ + " v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
+ + " gl_Position = u_projectionViewMatrix * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
+ + "}\n";
+ String fragmentShader = "#ifdef GL_ES\n" //
+ + "precision mediump float;\n" //
+ + "#endif\n" //
+ + "varying vec4 v_color;\n" //
+ + "varying vec2 v_texCoords;\n" //
+ + "uniform sampler2D u_texture;\n" //
+ + "void main()\n"//
+ + "{\n" //
+ + " vec4 texColor = texture2D(u_texture, v_texCoords);\n" //
+ + " if (texColor.a > 0.0)\n" //
+ + " gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" //
+ + " else\n" //
+ + " discard;\n" //
+ + "}";
+
+ shader = new ShaderProgram(vertexShader, fragmentShader);
+ if (shader.isCompiled() == false) throw new IllegalArgumentException("couldn't compile shader: " + shader.getLog());
+ }
+
+ @Override
+ public ShaderProgram getGroupShader (int group) {
+ return shader;
+ }
+
+ @Override
+ public void dispose () {
+ if (shader != null) shader.dispose();
+ }
+}
diff --git a/src/com/blarg/gdx/graphics/RenderContext.java b/src/com/blarg/gdx/graphics/RenderContext.java
index 10663b9..b4ec487 100644
--- a/src/com/blarg/gdx/graphics/RenderContext.java
+++ b/src/com/blarg/gdx/graphics/RenderContext.java
@@ -22,7 +22,7 @@ public class RenderContext implements Disposable {
public final ScreenPixelScaler pixelScaler;
public final SolidColorTextureCache solidColorTextures;
- CameraGroupStrategy cameraGroupStrategy;
+ AlphaTestCameraGroupStrategy cameraGroupStrategy;
Camera perspectiveCamera;
OrthographicCamera orthographicCamera;
@@ -43,7 +43,7 @@ public class RenderContext implements Disposable {
setDefaultPerspectiveCamera();
- cameraGroupStrategy = new CameraGroupStrategy(perspectiveCamera);
+ cameraGroupStrategy = new AlphaTestCameraGroupStrategy(perspectiveCamera);
decalBatch = new DecalBatch(cameraGroupStrategy);
billboardSpriteBatch = new BillboardSpriteBatch();
}