【SDL游戏编程入门第六卷】Texture纹理加载和Renderer渲染器 (自动处理透明图片如PNG)

在这里插入图片描述

一、前言

最初我们使用的是 SDL_Surface 进行绘图的,这个其实是 SDL1 主要使用的,之后因为各种原因到 SDL2 的时候,就用的不太多了,但还是要了解,有些需求还是需要使用这个进行绘图的

SDL2 的一个主要新增功能是纹理渲染 API。提供了快速、灵活的基于硬件的渲染。

当我们处理需要的 SDL 纹理时,将其渲染到屏幕上的 SDL_Renderer,所以还需要使用 渲染器

接下来介绍如何使用 渲染器 进行渲染,以及 加载纹理

二、使用渲染器

/**
 * @brief 启动 SDL 并创建窗口
 */
bool Init()
{
	// 初始化 SDL
	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		std::cout << "[Error]: SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
		return false;
	}
	else
	{
		// 设置纹理线性过滤
		if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"))
			std::cout << "[Warning]: Linear texture filtering not enabled!" << std::endl;

		// 创建窗口
		gWindow = SDL_CreateWindow("HelloSDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
		if (!gWindow)
		{
			std::cout << "[Error]: Window could not be created! SDL_Error: " << SDL_GetError() << std::endl;
			return false;
		}
		else
		{
			// 创建窗口渲染器
			gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED);  // -1 初始化支持所请求标志的第一个
			if (!gRenderer)
			{
				std::cout << "[Error]: Renderer could not bo created! SDL_Error: " << SDL_GetError() << std::endl;
				return false;
			}
			else
			{
				// 初始化渲染器绘制颜色(蓝色)
				SDL_SetRenderDrawColor(gRenderer, 51, 76, 204, 255);

				// 初始化 PNG 加载
				int imgFlags = IMG_INIT_PNG;
				if (!(IMG_Init(imgFlags) & imgFlags))
				{
					std::cout << "[Error]: SDL_image could not initialize! SDL_image Error: " << IMG_GetError() << std::endl;
					return false;
				}
			}
		}
	}
	return true;
}

这里在创建窗口,设置位置时更换了参数,使用的是 SDL_WINDOWPOS_CENTERED,会出现在中心,
这里 X,Y 轴都设置成了中心位置

创建窗口后,我们必须为窗口创建一个渲染器,以便我们可以在其上渲染纹理。幸运的是,这可以通过调用 SDL_CreateRenderer 轻松完成,这里创建时使用了 SDL_RENDERER_ACCELERATED 加速渲染模式。

创建渲染器后,我们希望使用 SDL_SetRenderDrawColor 初始化渲染颜色。这控制用于各种渲染操作的颜色。

// 销毁窗口渲染器
if (gRenderer)
{
    SDL_DestroyRenderer(gRenderer);
    gRenderer = nullptr;
}

注意:创建渲染器后,在程序结束前要释放资源

补充:
注意到这里使用了 SDL_SetHint 设置了纹理线性过滤的属性,这个官方文档上也能查阅到
参考文档
配置属性变量

// --- rendering -----------------------------------------------------------------------
// 清屏
SDL_RenderClear(gRenderer);

SDL_Rect dstRect{ gTexturePosX, gTexturePosY, 256, 256 };
// 渲染纹理(复制纹理到指定渲染器)
SDL_RenderCopy(gRenderer, gTexture, nullptr, &dstRect);
// 更新屏幕
SDL_RenderPresent(gRenderer);

在事件循环之后的主循环中,我们调用 SDL_RenderClear。此功能使用上次用 SDL_SetRenderDrawColor 设置的颜色填充屏幕。

清除屏幕后,我们用 SDL_RenderCopy 渲染纹理若目标矩形即最后一个参数为 nullptr,纹理将被拉伸以填充给定的矩形。

渲染纹理后,我们仍然必须 更新屏幕,但由于我们不使用 SDL_Surfaces 进行渲染,因此无法使用 SDL_UpdateWindowSurface。相反,我们必须使用 SDL_RenderPresent

现在,有一个名为 IMG_LoadTexture 的新 API 调用。您可以在 官方文档 或 SDL_image 头文件中找到它。它允许您加载纹理而无需创建临时表面,这里已经花了很多篇幅,感兴趣的伙伴可以自己先试一试。

三、加载纹理

bool Init();								// 启动 SDL 并创建窗口
bool LoadMedia();							// 加载媒体
SDL_Texture* LoadTexture(const char* path);	// 加载纹理
void Close();								// 清理资源,关闭窗口以及SDL

SDL_Window* gWindow = nullptr;				// 主窗口
SDL_Renderer* gRenderer = nullptr;			// 主窗口渲染器

SDL_Texture* gTexture = nullptr;			// 需要加载的纹理(表面)

SDL 中的纹理有自己的数据类型,直观地称为 SDL_Texture。当我们处理需要的 SDL 纹理时,将其渲染到屏幕上的 SDL_Renderer,这就是我们声明一个名为 “gRenderer” 的全局渲染器的原因。

正如你所看到的,我们有一个新的图像加载例程,其中包含 LoadTexture 和一个我们将要加载的全局声明的纹理。

/**
 * @brief 加载纹理
 * @param path:资源路径
 */
SDL_Texture* LoadTexture(const char* path)
{
	// 最终纹理
	SDL_Texture* newTexture = nullptr;
	SDL_Surface* loadedSurface = IMG_Load(path);
	if (!loadedSurface)
		std::cout << "[Error]: Unable to load image " << path << " SDL Error: " << SDL_GetError() << std::endl;
	else
	{
		// 利用表面创建纹理
		newTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);
		if (!newTexture)
			std::cout << "[Error]: Unable to create Texture from" << path << " SDL_Error: " << SDL_GetError() << std::endl;
		// 释放表面
		SDL_FreeSurface(loadedSurface);
	}
	// 返回最终优化的资源
	return newTexture;
}

我们的纹理加载函数看起来与以前大致相同,只是现在不是将加载的表面转换为显示格式,而是从加载的表面创建纹理 使用 SDL_CreateTextureFromSurface。同样记得释放不使用的表面资源。

// 清理纹理资源
if (gTexture)
{
    SDL_DestroyTexture(gTexture);
    gTexture = nullptr;
}

由于纹理加载是使用我们的图像加载函数抽象的,因此 LoadMedia() 函数的工作方式与以前几乎相同。

在我们的清理函数中,我们必须记住使用 SDL_DestroyTexture 来释放我们的纹理。

四、示例代码

/* 此源代码版权归 AnnihilateSword (2022-*)所有,未经书面许可不得转载。*/

// 使用 SDL 和 iostream
#include <SDL.h>
#include <SDL_image.h>
#include <iostream>

// 链接库
#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")
#pragma comment(lib, "SDL2_image.lib")

// 屏幕尺寸常量
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;

bool Init();								// 启动 SDL 并创建窗口
bool LoadMedia();							// 加载媒体
SDL_Texture* LoadTexture(const char* path);	// 加载纹理
void Close();								// 清理资源,关闭窗口以及SDL

SDL_Window* gWindow = nullptr;				// 主窗口
SDL_Renderer* gRenderer = nullptr;			// 主窗口渲染器

SDL_Texture* gTexture = nullptr;			// 需要加载的纹理(表面)

// 控制纹理坐标
int gTexturePosX = 100;						// 纹理 X 坐标
int gTexturePosY = 100;						// 纹理 Y 坐标
int gTextureMovementSpeed = 20;				// 纹理移动速度

int main(int argc, char* argv[])			// 必须要填写此参数,不然会出现链接错误
{
	// 初始化 SDL
	if (Init())
	{
		// 加载媒体资源
		if (!LoadMedia())
			std::cout << "[Error]: Failed to load media!" << std::endl;
		
		// 窗口循环
		SDL_Event e;
		bool quit = false;
		while (quit == false)
		{
			// 处理事件
			while (SDL_PollEvent(&e))
			{
				if (e.type == SDL_QUIT)
					quit = true;
				else if (e.type == SDL_KEYDOWN)  // 处理按键按下事件
				{
					switch (e.key.keysym.sym)
					{  // 按键移动纹理坐标位置
					case SDLK_w: gTexturePosY -= gTextureMovementSpeed; break;
					case SDLK_s: gTexturePosY += gTextureMovementSpeed; break;
					case SDLK_a: gTexturePosX -= gTextureMovementSpeed; break;
					case SDLK_d: gTexturePosX += gTextureMovementSpeed; break;
					}
				}
			}

			// --- rendering -----------------------------------------------------------------------
			// 清屏
			SDL_RenderClear(gRenderer);

			SDL_Rect dstRect{ gTexturePosX, gTexturePosY, 256, 256 };
			// 渲染纹理(复制纹理到指定渲染器)
			SDL_RenderCopy(gRenderer, gTexture, nullptr, &dstRect);
			// 更新屏幕
			SDL_RenderPresent(gRenderer);
		}
	}
	// 清理资源
	Close();
	return 0;
}

/**
 * @brief 启动 SDL 并创建窗口
 */
bool Init()
{
	// 初始化 SDL
	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		std::cout << "[Error]: SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
		return false;
	}
	else
	{
		// 设置纹理线性过滤
		if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"))
			std::cout << "[Warning]: Linear texture filtering not enabled!" << std::endl;

		// 创建窗口
		gWindow = SDL_CreateWindow("HelloSDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
		if (!gWindow)
		{
			std::cout << "[Error]: Window could not be created! SDL_Error: " << SDL_GetError() << std::endl;
			return false;
		}
		else
		{
			// 创建窗口渲染器
			gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED);  // -1 初始化支持所请求标志的第一个
			if (!gRenderer)
			{
				std::cout << "[Error]: Renderer could not bo created! SDL_Error: " << SDL_GetError() << std::endl;
				return false;
			}
			else
			{
				// 初始化渲染器绘制颜色(蓝色)
				SDL_SetRenderDrawColor(gRenderer, 51, 76, 204, 255);

				// 初始化 PNG 加载
				int imgFlags = IMG_INIT_PNG;
				if (!(IMG_Init(imgFlags) & imgFlags))
				{
					std::cout << "[Error]: SDL_image could not initialize! SDL_image Error: " << IMG_GetError() << std::endl;
					return false;
				}
			}
		}
	}
	return true;
}

/**
 * @brief 加载媒体
 */
bool LoadMedia()
{
	gTexture = LoadTexture(R"(res\textures\awesomeface.png)");
	if (!gTexture)
	{
		std::cout << "[Error]: Failed to load texture!" << std::endl;
		return false;
	}
	return true;
}

/**
 * @brief 加载纹理
 * @param path:资源路径
 */
SDL_Texture* LoadTexture(const char* path)
{
	// 最终纹理
	SDL_Texture* newTexture = nullptr;
	SDL_Surface* loadedSurface = IMG_Load(path);
	if (!loadedSurface)
		std::cout << "[Error]: Unable to load image " << path << " SDL Error: " << SDL_GetError() << std::endl;
	else
	{
		// 利用表面创建纹理
		newTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);
		if (!newTexture)
			std::cout << "[Error]: Unable to create Texture from" << path << " SDL_Error: " << SDL_GetError() << std::endl;
		// 释放表面
		SDL_FreeSurface(loadedSurface);
	}
	// 返回最终优化的资源
	return newTexture;
}

/**
 * @brief 清理资源,关闭窗口以及SDL
 */
void Close()
{
	// 清理纹理资源
	if (gTexture)
	{
		SDL_DestroyTexture(gTexture);
		gTexture = nullptr;
	}
	// 销毁窗口渲染器
	if (gRenderer)
	{
		SDL_DestroyRenderer(gRenderer);
		gRenderer = nullptr;
	}
	// 销毁窗口
	if (gWindow)
	{
		SDL_DestroyWindow(gWindow);
		gWindow = nullptr;
	}
	// 退出 IMG 和 SDL
	IMG_Quit();
	SDL_Quit();
}

1. 运行结果

可以看到跟上一卷不一样了,实现了透明效果,SDL_Texture 和 渲染器帮我们处理了

2. 【优化纹理加载】

优化前,要写的代码

SDL_Texture* LoadTexture(const char* path)
{
	// 最终纹理
	SDL_Texture* newTexture = nullptr;
	SDL_Surface* loadedSurface = IMG_Load(path);
	if (!loadedSurface)
		std::cout << "[Error]: Unable to load image " << path << " SDL Error: " << SDL_GetError() << std::endl;
	else
	{
		// 利用表面创建纹理
		newTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);
		if (!newTexture)
			std::cout << "[Error]: Unable to create Texture from" << path << " SDL_Error: " << SDL_GetError() << std::endl;
		// 释放表面
		SDL_FreeSurface(loadedSurface);
	}
	// 返回最终优化的资源
	return newTexture;
}

这里因为 SDL2 的改动,可以直接使用 IMG_LoadTexture 一步到位,推荐直接使用这函数

SDL_Texture* LoadTexture(const char* path)
{
	// 使用这个一步到位
	return IMG_LoadTexture(gRenderer, path);
}



本节内容就到这里了,下卷会继续分享 SDL 的基本使用

The End.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值