Rendering style for maps in a procedural way

I have a shader which I pass in each position of my vertex. It draws a neat square which I render my tileset to. It uses logic to iterate through the texture by index. It is in fact 2D. However, in my renderer, I render each tile by iteration of the region (basically a sub-section of an entire map in memory), however my problem is that I pass a uniform through dictating the final position of the map. How would I not have to pass the size of the current tile through the shader in order for it to correctly render in a grid? I use one shader for the map being rendered. Would initially setting the shader and allowing the shader to change this value be efficient? Would it stay for the others?

Shader.v

#version 400 core

in vec2 position;
in vec2 texcoord;

out vec2 pass_texcoord;

uniform mat4 matrix_transform;
uniform mat4 matrix_projection;
uniform mat4 matrix_camera;

uniform int index;

uniform ivec2 stride;
uniform ivec2 tiles;

uniform ivec2 map_position;

uniform sampler2D u_texture;

void main(void) {
	ivec2 texsize = textureSize(u_texture, 0);
	
	float xratio = float(stride.x)/texsize.x;
	float yratio = float(stride.y)/texsize.y;
	
	float yoffset = floor(index/tiles.x);
	
	pass_texcoord = vec2(texcoord.s*xratio + mod(index, tiles.x)*xratio, texcoord.t*yratio + yoffset*yratio);
	gl_Position = matrix_projection * matrix_camera * matrix_transform * vec4(position.x+map_position.x, position.y-map_position.y, 1.0, 1.0);
	
}

Renderer

	public static void renderMap(Map map, Region2f region, SpriteElement element, Matrix4f projection, Camera camera) {
		final Model model = element.getModel();
		final ShaderModel shader = element.getShaderModel();
		GL30.glBindVertexArray(model.getVao());
			GL20.glEnableVertexAttribArray(0);
			GL20.glEnableVertexAttribArray(1);
			GL20.glUseProgram(shader.getShader().getProgram());
				shader.bindTexture(element.getTexture());
				shader.updateView(projection, element.getMatrix(), camera.getMatrix());
				shader.updateTileset(element.getSprite());
				for (int layer=0; layer<map.getLayers(); layer++) {
					for (int y=(int)region.getPoint1().y; y<region.getPoint2().x; y++) {
						for (int x=(int)region.getPoint1().x; x<region.getPoint2().y; x++) {
							final int current = map.getData()[layer][x][y];
							if(current == -1)
								continue;
							shader.updateInts(current, y, x);
							GL11.glDrawElements(GL11.GL_TRIANGLES, model.getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
						}
					}
				}
			GL20.glUseProgram(0);
			GL20.glDisableVertexAttribArray(0);
			GL20.glDisableVertexAttribArray(1);
		GL30.glBindVertexArray(0);
	}

SpriteShader.class, extends ShaderModel which just contains my vao and vbo.

public class SpriteShader extends ShaderModel {

	public SpriteShader(Shader shader) {
		super(shader);
	}
	
	private int u_texture;
	
	private int index, stride, tiles;
	
	private int matrix_projection, matrix_transform, matrix_camera;
	
	@Override
	public void init() {
		shader.bindAttribute(0, "position");
		shader.bindAttribute(1, "texcoord");
		
		u_texture = shader.getUniform("u_texture");
		
		index = shader.getUniform("index");
		stride = shader.getUniform("stride");
		tiles = shader.getUniform("tiles");
		
		matrix_projection = shader.getUniform("matrix_projection");
		matrix_transform = shader.getUniform("matrix_transform");
		matrix_camera = shader.getUniform("matrix_camera");
	}
	
	@Override
	public void updateView(Matrix4f... matrices) {
		shader.bindUniformm(matrix_projection, matrices[0]);
		shader.bindUniformm(matrix_transform, matrices[1]);
		shader.bindUniformm(matrix_camera, matrices[2]);
	}
	
	@Override
	public void updateInts(int... ints) {
		shader.bindUniformi(index, ints[0]);
	}
	
	@Override
	public void bindTexture(Texture texture) {
		GL13.glActiveTexture(GL13.GL_TEXTURE0);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId());
		shader.bindUniformi(u_texture, 0);
	}
	
	@Override
	public void updateTileset(Sprite sprite) {
		shader.bindUniformi(stride, sprite.getStrideX(), sprite.getStrideY());
		shader.bindUniformi(tiles, sprite.getTilesX(), sprite.getTilesY());
	}
	
}

Hi,

I see you are using GL4, some tips:

  • don’t pollute shaders with spare uniforms, use UBOs (with explicit locations)

  • exploit the vector component wise operations

  • multiply matrices using brackets in order to avoid a mat * mat and perform instead a mat * vec multiplication

#version 400 core

#define POSITION           0
#define TEXCOORD  	1
#define MATRICES          0
#define TILESET	        1
#define TEXTURE           0

layout(std140, column_major) uniform;

layout (location = POSITION) in vec2 position;
layout (location = TEXCOORD) in vec2 texcoord;

out Block {
   vec2 texcoord;
} outB;

layout(binding = MATRICES) uniform Matrices
{
    mat4 transf;
    mat4 proj;
    mat4 cam;
} mat;

layout(binding = TILESET) uniform Tileset
{
    ivec2 stride;
    ivec2 tiles;
} set;

layout (binding = TEXTURE) uniform sampler2D Texture;

uniform int index;
uniform ivec2 map_position;

void main(void) {

   vec2 ratio = vec2(set.stride) / textureSize(Texture, 0);
   
   vec2 offset = vec2(mod(index, set.tiles.x), floor(index / set.tiles.x));
   
   outBlock.texcoord = (texcoord + offset) * ratio;

   gl_Position = mat.proj * (mat.cam * (mat.transf * vec4(position.x + map_position.x, position.y - map_position.y, 1.0, 1.0)));
   
}
  • make sure all the ‘#define’ match in your program, where you should have something like this

  • are the renderMap call contiguous? Don’t switch vao and program

  • don’t glEnableVertexAttribArray, that’s part of the vao

  • bind now the texture with glBindTextureUnit(0, texture.getId())

  • what’s the point of calling updateInts(current, y, x) if you just want to load the index?

  • if you have less than 65536 indices use unsigned shorts

About your question, I don’t get what you mean exactly… can you elaborate? What’s the problem? Is the tile size fix?

I don’t have any idea what any of that means.

This code is what I was talking about. I call GL11.glDrawElements over and over again. I want to minimize my calls.

for (int layer=0; layer<map.getLayers(); layer++) {
               for (int y=(int)region.getPoint1().y; y<region.getPoint2().x; y++) {
                  for (int x=(int)region.getPoint1().x; x<region.getPoint2().y; x++) {
                     final int current = map.getData()[layer][x][y];
                     if(current == -1)
                        continue;
                     shader.updateInts(current, y, x);
                     GL11.glDrawElements(GL11.GL_TRIANGLES, model.getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
                  }
               }
            }

I am not using LWJGL3, nor OGL4.

I started from immediate>display list>vao,vbo, and shaders

[quote]> - exploit the vector component wise operations
[/quote]
What is so wise?

[quote]- multiply matrices using brackets in order to avoid a mat * mat and perform instead a mat * vec multiplication
[/quote]
How does this multiply?

[quote]- are the renderMap call contiguous? Don’t switch vao and program
[/quote]
What?

[quote]- don’t glEnableVertexAttribArray, that’s part of the vao
[/quote]
That matches my use of in variables :confused:

[quote]- what’s the point of calling updateInts(current, y, x) if you just want to load the index?
[/quote]
I load in the index of the tile in the tileset. 1-16 in x, 17 being line 2 of tiles.

[quote]- if you have less than 65536 indices use unsigned shorts
[/quote]
I can use bytes :stuck_out_tongue:

Build a single VBO containing the whole grid of quads with proper texture coordinates, instead of rendering each quad of the grid individually :point:

[quote]Build a single VBO containing the whole grid of quads
[/quote]
Okay, so let me imagine this.
I have a vbo to feed in the vertexes I only define once.

In my code I create a model like so…

final Model model = new Model(new float[]{vertexes}, new float[]{texcoords}, new int[]{indices});
And only create one quad.

I use a region to determine what tiles to draw.

My biggest fear is when I do this, how will a specify the index and texture each individual piece of the triangle structure?

I could pass in 3-length attribute values, but the index is subject to change

Which opengl version do you use/target?

Good, but dont stop and continue learning, opengl evolved a lot

This:

float xratio = float(stride.x)/texsize.x;
float yratio = float(stride.y)/texsize.y;

could be written as:

vec2 ratio = vec2(stride)/texsize;

and so on… it makes code more compact and easier to read

if you write this:

gl_Position = projection * camera * model * position;

the shader may perform matrix * matrix multiplication. Using parenthesis you make sure it executes instead a matrix * vector multiplication, that should be a little lighter

gl_Position = projection * (camera * (model * position));

Are you calling renderMap in a loop? If yes, this:

for( ) {
renderMap
}

renderMap() {
bindVAO
useProgram
}

becomes:

bindVAO
useProgram
for( )
renderMap

they should be enabled/disabled when you create the vao

How much big is your area? 16x16 tiles? Is it fixed?

Anyway, don’t be scared to set the index via attribute, you can easily update them whenever you need

:slight_smile: In theory it’d be better, but since most of the gpus work with 16 or 32 bits indices the driver is likely to convert the byte indices to short, cancelling all the benefits

[quote]Which opengl version do you use/target?
[/quote]
3.1 with matching GLSL

[quote]could be written as:
[/quote]
I noticed that and changed it in my newest shader and was going to push changes.

[quote]gl_Position = projection * (camera * (model * position));
[/quote]
I see. Well. I hope it is going right to left D: how do I tell?

[quote]Are you calling renderMap in a loop? If yes, this:
[/quote]
It goes…

  • Render Loop
  • Bind vao
  • Enable and set things that need to
  • Draw everything using for(){GL11.glElementArrays() or whatever}
  • Closure stuff

[quote]Anyway, don’t be scared to set the index via attribute, you can easily update them whenever you need
[/quote]
I don’t want to push any new changes to a vbo. I would have to bind it, change ALL the data (most of data won’t be changed), and rebind it. This is very expensive, as in my past it completely destroyed my cpu when drawing 100x100 tiles. Now when I do it with my current method, I get around 20fps.

[quote]they should be enabled/disabled when you create the vao
[/quote]
This is important. So I only ever need to glEnableAttribArrays() once? Do I even need to disable them?

This…

   public static void renderMap(Map map, Region2f region, SpriteElement element, Matrix4f projection, Camera camera) {
      final Model model = element.getModel();
      final ShaderModel shader = element.getShaderModel();
      GL30.glBindVertexArray(model.getVao());
         GL20.glEnableVertexAttribArray(0);
         GL20.glEnableVertexAttribArray(1);
         GL20.glUseProgram(shader.getShader().getProgram());
            shader.bindTexture(element.getTexture());
            shader.updateView(projection, element.getMatrix(), camera.getMatrix());
            shader.updateTileset(element.getSprite());
            for (int layer=0; layer<map.getLayers(); layer++) {
               for (int y=(int)region.getPoint1().y; y<region.getPoint2().x; y++) {
                  for (int x=(int)region.getPoint1().x; x<region.getPoint2().y; x++) {
                     final int current = map.getData()[layer][x][y];
                     if(current == -1)
                        continue;
                     shader.updateInts(current, y, x);
                     GL11.glDrawElements(GL11.GL_TRIANGLES, model.getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
                  }
               }
            }
         GL20.glUseProgram(0);
         GL20.glDisableVertexAttribArray(0);
         GL20.glDisableVertexAttribArray(1);
      GL30.glBindVertexArray(0);
   }

Becomes this…

   public static void renderMap(Map map, Region2f region, SpriteElement element, Matrix4f projection, Camera camera) {
      final Model model = element.getModel();
      final ShaderModel shader = element.getShaderModel();
      GL30.glBindVertexArray(model.getVao());
         // GL20.glUseProgram(shader.getShader().getProgram()); See bottom comment
            shader.bindTexture(element.getTexture());
            shader.updateView(projection, element.getMatrix(), camera.getMatrix());
            shader.updateTileset(element.getSprite());
            for (int layer=0; layer<map.getLayers(); layer++) {
               for (int y=(int)region.getPoint1().y; y<region.getPoint2().x; y++) {
                  for (int x=(int)region.getPoint1().x; x<region.getPoint2().y; x++) {
                     final int current = map.getData()[layer][x][y];
                     if(current == -1)
                        continue;
                     shader.updateInts(current, y, x);
                     GL11.glDrawElements(GL11.GL_TRIANGLES, model.getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
                  }
               }
            }
            //GL20.glUseProgram(0); Should I get rid of this too?? Would I never have to glUseProgram(shader) again once I set it for this vao?
      GL30.glBindVertexArray(0);
   }

Yep, it produces exactly the same output of your original shader

in b4 last famous words :smiley:

Good, then you can take out the vao and the program binding.
Avoid useless calls such as:
glUseProgram(0)
glBindVertexArray(0)

  • you don’t need to change all the data, glbufferSubData will help to modify only the part you need to
  • how is the design? 100x100 tiles, a texture for each tile? Render loop updates always all of them?
    Can you elaborate? Sorry but I am not really in tile-games (cad dev)
  • 100x100 tiles is not expensive at all for modern GPUs, which one do you have?

yep, glEnableVertexAttribArray and glDisableVertexAttribArray. They are part of the vao.

I imagine it would go like this…

int vbo = model.getTileVBO();

int tile = 10; //the 10th tile
final int STRIDE = 3;
int newdata = 1;
glbufferSubData(vbo, tile*STRIDE, newdata); //Stride is the length of each thing. The tile X and Y fill STRIDE-1 and STRIDE-2, while the index is STRIDE

Since I made it so it only uses one vbo, it now forces me to set all shaders once, aswell as update its textures and stuff specific to the shader. It seems like texture is stored via vao and not via shader. Strange. I wonder if this is true even when you render multiple vaos… if it would also texture it. Of course I never GL11.glUseTexture(GL_TEXTURE_2D, 0); or anything or the sort.

I need to know if I am going about this correctly because I don’t like the way I will take here, continuing with this advise.

-> Create VAO for each element
-1 For map
-2 For each singularly shaded element (which will be just the player and a few characters)
-3 For each text set I render

The thing that bugs me is I am going to create a vao with 3 vbos - texcoord, verts, indices - 3 times when the texcoord is dynamic in the shader, verts never change, and indices never change.

Also with that last part, I suppose I should modify, like my last post had, my map to where I am not drawing 2 triangles per block which act as it’s own entity, but instead calculates the number of triangles I have in x and y axis and draw it like a .OBJ (without texturing ofc).

I have determined that GL20.glUseShader() is necessary always, as it GL11.glBindTexture()… Which I completely forgot about active textures and I am redoing.

However… my question really comes to how to send the data I want to send about the tiles to the shader to compute…
I know it’s a vbo, but idk how to structure it… well I do… but I don’t see any gain or fit inside my code for it.

Firstly, you say you are using OpenGL/GLSL for 3.1, however in your code you clearly are using the GLSL corresponding to OpenGL 4.0 with

#version 400 core

Did you write this code yourself? If so, why are you using GLSL [icode]#version 400[/icode]?
If not, you should really find some resources about learning GLSL (I recommend playing around on shadertoy.com), and don’t copy code that you understand in the future, it’ll just cause you a lot of confusion down the road. (or in this case, a lot of confusion now.)

Secondly,

[quote]However… my question really comes to how to send the data I want to send about the tiles to the shader to compute…
I know it’s a vbo, but idk how to structure it… well I do… but I don’t see any gain or fit inside my code for it.
[/quote]
I’m not sure that I fully understand what you are trying to accomplish. Are you trying to send per tile data? Why can this not be computed on CPU?

I’ve wrote everything myself.

Using OpenGL Version: 3.1.0 - Build 9.17.10.3040
Using Shader Version: 1.40 - Intel Build 9.17.10.3040

This is from my log. I learned from ThinMatrix from youtube and any flaws that he made reflects on the poor amount of decent material there is unless you want to pitch in money for a book or post on forums for help (which is my least favorite).

Any bad habits and errors he taught, I know. But I’ve gotten rid of the major ones.
However, I am still using outdated stuff.

There is a lot of content for OpenGL, you just have to spend a few minutes looking.

I took a look through it. It’s pretty interesting. Although, I have no source code to see their implementation, only the shader code.