Jump to content

Texture Atlas & Animation

IgorBgz90

264 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]);

Update 04.04.2020

+Added check for empty frames.

  • Like 3


0 Comments


Recommended Comments

There are no comments to display.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

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...