diff --git a/src/main/java/engine/common/math/Matrix4f.java b/src/main/java/engine/common/math/Matrix4f.java index 51733735..d7ad71cf 100644 --- a/src/main/java/engine/common/math/Matrix4f.java +++ b/src/main/java/engine/common/math/Matrix4f.java @@ -876,4 +876,10 @@ public Matrix4f translate(ConstraintPair translate) { return translate(translate.x().get(), translate.y().get()); } + public float[] toArray() { + float[] arr = new float[16]; + store(arr); + return arr; + } + } diff --git a/src/main/java/nomadrealms/context/game/world/map/area/Chunk.java b/src/main/java/nomadrealms/context/game/world/map/area/Chunk.java index 464b8b9a..a08ea120 100644 --- a/src/main/java/nomadrealms/context/game/world/map/area/Chunk.java +++ b/src/main/java/nomadrealms/context/game/world/map/area/Chunk.java @@ -1,18 +1,26 @@ package nomadrealms.context.game.world.map.area; +import static engine.common.colour.Colour.toRangedVector; import static nomadrealms.context.game.world.map.area.Tile.TILE_HORIZONTAL_SPACING; +import static nomadrealms.context.game.world.map.area.Tile.TILE_RADIUS; import static nomadrealms.context.game.world.map.area.Tile.TILE_VERTICAL_SPACING; import static nomadrealms.context.game.world.map.area.coordinate.ChunkCoordinate.CHUNK_SIZE; +import static nomadrealms.render.vao.shape.HexagonVao.HEIGHT; +import static nomadrealms.render.vao.shape.HexagonVao.SIDE_LENGTH; import java.util.ArrayList; import java.util.List; +import engine.common.math.Matrix4f; import engine.common.math.Vector2f; +import engine.common.math.Vector3f; +import engine.common.math.Vector4f; import engine.visuals.constraint.box.ConstraintPair; import nomadrealms.context.game.world.map.area.coordinate.ChunkCoordinate; import nomadrealms.context.game.world.map.area.coordinate.TileCoordinate; import nomadrealms.context.game.world.map.generation.MapGenerationStrategy; import nomadrealms.render.RenderingEnvironment; +import nomadrealms.render.vao.shape.InstancedHexagonVao; /** * A chunk is a 16x16 grid of tiles. This is the optimal size for batch rendering. Chunks are how we limit the rendering @@ -49,12 +57,51 @@ public Tile tile(int x, int y) { } public void render(RenderingEnvironment re) { + float[] transforms = new float[CHUNK_SIZE * CHUNK_SIZE * 16]; + float[] colors = new float[CHUNK_SIZE * CHUNK_SIZE * 4]; + int i = 0; + Vector2f toCenter = new Vector2f(TILE_RADIUS * SIDE_LENGTH, TILE_RADIUS * HEIGHT); + ConstraintPair chunkPos = zone.pos().add(indexPosition()); for (int row = 0; row < CHUNK_SIZE; row++) { for (int col = 0; col < CHUNK_SIZE; col++) { - tiles[row][col].render(re); + Tile tile = tiles[row][col]; + + // Calculation logic copied from Tile.indexPosition() + Vector2f base = new Vector2f(tile.coord().x() * TILE_HORIZONTAL_SPACING, tile.coord().y() * TILE_VERTICAL_SPACING); + Vector2f columnOffset = new Vector2f(0, (tile.coord().x() % 2 == 0) ? 0 : TILE_RADIUS * HEIGHT); + ConstraintPair indexPosition = new ConstraintPair(toCenter.add(base).add(columnOffset)); + + ConstraintPair position = chunkPos.add(indexPosition); + Vector2f screenPosition = position.sub(re.camera.position()).vector(); + Matrix4f transform = new Matrix4f(screenPosition.x(), screenPosition.y(), TILE_RADIUS * 2 * SIDE_LENGTH * 0.98f, + TILE_RADIUS * 2 * SIDE_LENGTH * 0.98f, re.glContext).rotate(0, new Vector3f(0, 0, 1)); + System.arraycopy(transform.toArray(), 0, transforms, i * 16, 16); + Vector4f color = toRangedVector(tile.color()); + colors[i * 4] = color.x(); + colors[i * 4 + 1] = color.y(); + colors[i * 4 + 2] = color.z(); + colors[i * 4 + 3] = color.w(); + i++; + } + } + InstancedHexagonVao.colorVBO().data(colors).updateData(); + InstancedHexagonVao.transformVBO().data(transforms).updateData(); + re.instancedShaderProgram.use(re.glContext); + InstancedHexagonVao.instance().drawInstanced(re.glContext, CHUNK_SIZE * CHUNK_SIZE); + + for (int row = 0; row < CHUNK_SIZE; row++) { + for (int col = 0; col < CHUNK_SIZE; col++) { + Tile tile = tiles[row][col]; + // Calculation logic copied from Tile.indexPosition() + Vector2f base = new Vector2f(tile.coord().x() * TILE_HORIZONTAL_SPACING, tile.coord().y() * TILE_VERTICAL_SPACING); + Vector2f columnOffset = new Vector2f(0, (tile.coord().x() % 2 == 0) ? 0 : TILE_RADIUS * HEIGHT); + ConstraintPair indexPosition = new ConstraintPair(toCenter.add(base).add(columnOffset)); + + ConstraintPair position = chunkPos.add(indexPosition); + Vector2f screenPosition = position.sub(re.camera.position()).vector(); + tile.renderContent(re, screenPosition); } } - // TODO: instanced rendering - render all tiles in a chunk together } private ConstraintPair indexPosition() { diff --git a/src/main/java/nomadrealms/context/game/world/map/area/Tile.java b/src/main/java/nomadrealms/context/game/world/map/area/Tile.java index 44c72545..dfd3adde 100644 --- a/src/main/java/nomadrealms/context/game/world/map/area/Tile.java +++ b/src/main/java/nomadrealms/context/game/world/map/area/Tile.java @@ -47,6 +47,10 @@ public abstract class Tile implements Target, HasTooltip { protected int color = rgb(126, 200, 80); + public int color() { + return color; + } + /** * No-arg constructor for serialization. */ @@ -85,6 +89,9 @@ public void render(RenderingEnvironment re) { ConstraintPair position = chunk.pos().add(indexPosition()); Vector2f screenPosition = position.sub(re.camera.position()).vector(); render(re, screenPosition, 1); + } + + public void renderContent(RenderingEnvironment re, Vector2f screenPosition) { if (re.showDebugInfo) { re.textRenderer .alignCenterHorizontal() @@ -92,6 +99,10 @@ public void render(RenderingEnvironment re) { .render(screenPosition.x(), screenPosition.y(), coord.x() + ", " + coord.y(), 0, re.font, 0.35f * TILE_RADIUS, rgb(255, 255, 255)); } + for (WorldItem item : items) { + re.textureRenderer.render(re.imageMap.get(item.item().image()), screenPosition.x() - ITEM_SIZE * 0.5f, + screenPosition.y() - ITEM_SIZE * 0.5f, ITEM_SIZE, ITEM_SIZE); + } } /** @@ -125,10 +136,7 @@ public void render(RenderingEnvironment re, Vector2f screenPosition, float scale new Matrix4f(screenPosition.x(), screenPosition.y(), TILE_RADIUS * 2 * SIDE_LENGTH * 0.98f, TILE_RADIUS * 2 * SIDE_LENGTH * 0.98f, re.glContext).rotate(radians, new Vector3f(0, 0, 1))) .use(new DrawFunction().vao(HexagonVao.instance()).glContext(re.glContext)); - for (WorldItem item : items) { - re.textureRenderer.render(re.imageMap.get(item.item().image()), screenPosition.x() - ITEM_SIZE * 0.5f, - screenPosition.y() - ITEM_SIZE * 0.5f, ITEM_SIZE, ITEM_SIZE); - } + renderContent(re, screenPosition); } public Actor actor() { diff --git a/src/main/java/nomadrealms/render/RenderingEnvironment.java b/src/main/java/nomadrealms/render/RenderingEnvironment.java index b29ebb15..f01c46b7 100644 --- a/src/main/java/nomadrealms/render/RenderingEnvironment.java +++ b/src/main/java/nomadrealms/render/RenderingEnvironment.java @@ -53,6 +53,10 @@ public class RenderingEnvironment { public FragmentShader bloomCombinationFragmentShader; public ShaderProgram bloomCombinationShaderProgram; + public VertexShader instancedVertexShader; + public FragmentShader instancedFragmentShader; + public ShaderProgram instancedShaderProgram; + public GameFont font; public Map imageMap = new HashMap<>(); @@ -111,6 +115,10 @@ private void loadShaders() { .source(new StringLoader(getFile("/shaders/bloom_combination.glsl")).load()).load(); bloomCombinationShaderProgram = new ShaderProgram().attach(bloomVertexShader, bloomCombinationFragmentShader) .load(); + + instancedVertexShader = new VertexShader().source(new StringLoader(getFile("/shaders/instancedVertex.glsl")).load()).load(); + instancedFragmentShader = new FragmentShader().source(new StringLoader(getFile("/shaders/instancedFrag.glsl")).load()).load(); + instancedShaderProgram = new ShaderProgram().attach(instancedVertexShader, instancedFragmentShader).load(); } private void loadImages() { diff --git a/src/main/java/nomadrealms/render/vao/shape/HexagonVao.java b/src/main/java/nomadrealms/render/vao/shape/HexagonVao.java index 80dc5642..41d98903 100644 --- a/src/main/java/nomadrealms/render/vao/shape/HexagonVao.java +++ b/src/main/java/nomadrealms/render/vao/shape/HexagonVao.java @@ -19,7 +19,7 @@ public class HexagonVao { private HexagonVao() { } - private static final float[] POSITIONS = { + public static final float[] POSITIONS = { 0, 0, 0, -SIDE_LENGTH, 0, 0, -SIDE_LENGTH * 0.5f, HEIGHT, 0, @@ -29,7 +29,7 @@ private HexagonVao() { -SIDE_LENGTH * 0.5f, -HEIGHT, 0, }; - private static final float[] TEXTURE_COORDINATES = { + public static final float[] TEXTURE_COORDINATES = { 0.5f, 0.5f, 0.25f, 0.5f, 0.125f, 0.25f, @@ -39,7 +39,7 @@ private HexagonVao() { 0.125f, 0.75f, }; - private static final int[] INDICES = { + public static final int[] INDICES = { 0, 1, 2, 0, 2, 3, 0, 3, 4, diff --git a/src/main/java/nomadrealms/render/vao/shape/InstancedHexagonVao.java b/src/main/java/nomadrealms/render/vao/shape/InstancedHexagonVao.java new file mode 100644 index 00000000..2e79fe3a --- /dev/null +++ b/src/main/java/nomadrealms/render/vao/shape/InstancedHexagonVao.java @@ -0,0 +1,56 @@ +package nomadrealms.render.vao.shape; + +import static nomadrealms.context.game.world.map.area.coordinate.ChunkCoordinate.CHUNK_SIZE; +import static nomadrealms.render.vao.shape.HexagonVao.INDICES; +import static nomadrealms.render.vao.shape.HexagonVao.POSITIONS; +import static nomadrealms.render.vao.shape.HexagonVao.TEXTURE_COORDINATES; + +import engine.visuals.lwjgl.render.ElementBufferObject; +import engine.visuals.lwjgl.render.InstancedVertexBufferObject; +import engine.visuals.lwjgl.render.VertexArrayObject; +import engine.visuals.lwjgl.render.VertexBufferObject; + +public class InstancedHexagonVao { + + public static final int MAX_INSTANCES = CHUNK_SIZE * CHUNK_SIZE; + + private static VertexArrayObject vao; + private static VertexBufferObject colorVBO; + private static VertexBufferObject transformVBO; + + private InstancedHexagonVao() { + } + + public static VertexArrayObject instance() { + if (vao == null) { + load(); + } + return vao; + } + + public static VertexBufferObject colorVBO() { + if (colorVBO == null) { + load(); + } + return colorVBO; + } + + public static VertexBufferObject transformVBO() { + if (transformVBO == null) { + load(); + } + return transformVBO; + } + + private static void load() { + ElementBufferObject ebo = new ElementBufferObject().indices(INDICES).load(); + VertexBufferObject positionsVBO = new VertexBufferObject().index(0).data(POSITIONS).dimensions(3).load(); + VertexBufferObject textureCoordinatesVBO = new VertexBufferObject().index(1).data(TEXTURE_COORDINATES).dimensions(2).load(); + + colorVBO = new InstancedVertexBufferObject().index(2).data(new float[4 * MAX_INSTANCES]).dimensions(4).divisor().perInstance().load(); + transformVBO = new InstancedVertexBufferObject().index(3).data(new float[16 * MAX_INSTANCES]).dimensions(16).divisor().perInstance().load(); + + vao = new VertexArrayObject().vbos(positionsVBO, textureCoordinatesVBO, colorVBO, transformVBO).ebo(ebo).load(); + } + +} diff --git a/src/main/resources/shaders/instancedFrag.glsl b/src/main/resources/shaders/instancedFrag.glsl new file mode 100644 index 00000000..62b3f5be --- /dev/null +++ b/src/main/resources/shaders/instancedFrag.glsl @@ -0,0 +1,9 @@ +#version 330 core +out vec4 outColor; + +in vec2 texCoord; +in vec4 fragColor; + +void main() { + outColor = fragColor; +} diff --git a/src/main/resources/shaders/instancedVertex.glsl b/src/main/resources/shaders/instancedVertex.glsl new file mode 100644 index 00000000..11a8d5d6 --- /dev/null +++ b/src/main/resources/shaders/instancedVertex.glsl @@ -0,0 +1,15 @@ +#version 330 core + +layout(location = 0) in vec3 position; +layout(location = 1) in vec2 uv; +layout(location = 2) in vec4 instanceColor; +layout(location = 3) in mat4 instanceTransform; + +out vec2 texCoord; +out vec4 fragColor; + +void main() { + gl_Position = instanceTransform * vec4(position, 1.0); + texCoord = uv; + fragColor = instanceColor; +}