Jump to content

Texture Atlas & Animation

IgorBgz90

448 views

guiAtlas.jpg?dl=1

Hello community, long time no see. I am working on my own graphical user interface, for my super duper rpg game :). The use separate textures for each button state, etc. I consider it not effective!

It is better to load the texture with the atlas of the whole GUI once. And use her.

In order to draw a texture from the atlas, we need to slightly modify the standard shader (drawimage), and save it under a different name (drawimagerect).

Shader

#version 400

uniform vec4 drawcolor;
uniform sampler2D texture0;
uniform vec4 rect;

in vec2 vTexCoords0;

out vec4 fragData0;

void main(void)
{
    ivec2 ts = textureSize(texture0, 0);
    vec4 coord = vec4(rect.x / ts.x, rect.y / ts.y, rect.z / ts.x, rect.w / ts.y);
    vec2 uv = coord.xy + (vTexCoords0 * coord.zw);
    fragData0 = drawcolor * texture(texture0,uv);
}

Now we can draw a texture from the atlas:

void drawImageRect(Texture* texture, float x, float y, Vec4 rect)
{
	Context* context = Context::GetCurrent();
	context->SetShader(Shader::Load("Shaders/Drawing/drawimagerect.shader"));
	context->GetShader()->SetVec4("rect", rect);
	context->DrawImage(texture, x, y, rect.z, rect.w);
	context->SetShader(NULL);
}

void drawImageRect(Texture* texture, float x, float y, float width, float height, Vec4 rect)
{
	Context* context = Context::GetCurrent();
	context->SetShader(Shader::Load("Shaders/Drawing/drawimagerect.shader"));
	context->GetShader()->SetVec4("rect", rect);
	context->DrawImage(texture, x, y, width, height);
	context->SetShader(NULL);
}

Animation

orc.png?dl=1

To play the animation we need to get the coordinates of all the frames in the atlas. Naturally, we won't do this manually, will write a small algorithm.

bool isEmptyFrame(char* pixels, int textureWidth, iVec2 position, iVec2 frameSize, 
	const bool alpha=true, const iVec3 colorBg=iVec3())
{
	unsigned char r, g, b, a;
	int level = 0;
	for (int y = position.y; y < position.y + frameSize.y; y++) {
		for (int x = position.x; x < position.x + frameSize.x; x++) {
			int p = (y * textureWidth + x) * 4;
			memcpy(&r, pixels + p + 0, 1);
			memcpy(&g, pixels + p + 1, 1);
			memcpy(&b, pixels + p + 2, 1);
			memcpy(&a, pixels + p + 3, 1);

			if (!alpha) {
				if ((int)r == colorBg.r && (int)g == colorBg.g && (int)b == colorBg.b) {
					level++;
				}
				else {
					return false;
				}
			}
			else {
				if ((int)a == 0) {
					level++;
				}
				else {
					return false;
				}
			}
		}
	}
	float percent = (float)((float)level / (float)(frameSize.x * frameSize.y)) * 100.0f;
	if (percent >= 100.0f) {
		return true;
	}
	return false;
}

void getFrames(Texture* texture, iVec2 framesize, std::vector<Vec4> &frames)
{
	if (texture == nullptr)
		return;
	int horizontLine = texture->GetWidth() / framesize.x;
	int verticalLine = texture->GetHeight() / framesize.y;
	int frameCount = horizontLine * verticalLine;

	int currentHorizont = 0;
	int currentVertical = 0;
	iVec2 framePosition = iVec2();
	iVec2 frameSize = framesize;

	char* pixels = (char*)malloc(texture->GetMipmapSize(0) * 8);
	texture->GetPixels(pixels);

	System::Print((std::string)"Get frames from texture atlas \"" + texture->GetPath() + "\"...");

	// Push first frame
	int skipCount = 0;
	if (!isEmptyFrame(pixels, texture->GetWidth(), framePosition, frameSize)) {
		frames.push_back(Vec4((float)framePosition.x, (float)framePosition.y, (float)frameSize.x, (float)frameSize.y));
	}
	else {
		skipCount++;
		System::Print((std::string)"Frame #0" + " is empty. (skip)");
	}

	for (int i = 1; i < frameCount; i++) {
		if (currentHorizont < horizontLine - 1) {
			currentHorizont++;
			framePosition.x = frameSize.x * currentHorizont;
		}
		else {
			if (currentVertical < verticalLine - 1) {
				currentVertical++;
				framePosition.x = 0;
				framePosition.y = frameSize.y * currentVertical;
				currentHorizont = 0;
			}
		}
		if (!isEmptyFrame(pixels, texture->GetWidth(), framePosition, frameSize)) {
			frames.push_back(Vec4((float)framePosition.x, (float)framePosition.y, (float)frameSize.x, (float)frameSize.y));
		}
		else {
			skipCount++;
			System::Print((std::string)"Frame #" + std::to_string(i) + " is empty. (skip)");
		}
	}

	System::Print((std::string)"Frame count: " + std::to_string(frames.size()) + ", skip: " + std::to_string(skipCount));

	free(pixels);
}

Now that we have all the frames, we can play the animation.

Texture* atlas = Texture::Load("Textures/atlas.tex");
std::vector<Vec4> frames;
getFrames(atlas, iVec2(96, 96), frames);

float frame = 0.0f;
float frameend = frames.size();
float framebegin = 0.0f;
float speed = 0.1f;

//Loop
frame += Time::GetSpeed() * speed;
frame = fmodf(frame, frameend - framebegin) + framebegin;

//Draw
drawImageRect(atlas, 25.0f, 25.0f, frames[(int)frame]);
Updates:
[04.04.2020]
[+] Added check for empty frames.

 

  • Like 4


1 Comment


Recommended Comments

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...