IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【Overload游戏引擎】源码分析之八:OvRendering函数库(六) -> 正文阅读

[游戏开发]【Overload游戏引擎】源码分析之八:OvRendering函数库(六)

2021SC@SDUSC

目录

Loaders

1.ModelLoader

1.1Create

1.2Reload

1.3Destroy?

2.ShaderLoader

2.1CreateProgram

2.2CompileShader

2.3ParseShader


上一节我们分析了Parser的相关代码,这一节就来看与资源导入相关的另一类工具——Loader。

Loaders

1.ModelLoader

首先是ModelLoader,它的作用是处理模型的创建和销毁,我们来看具体的代码

class ModelLoader
	{
	public:
		ModelLoader() = delete;
		/**
		* Create a model
		* @param p_filepath
		* @param p_parserFlags
		*/
		static Model* Create(const std::string& p_filepath, Parsers::EModelParserFlags p_parserFlags = Parsers::EModelParserFlags::NONE);
		/**
		* Reload a model from file
		* @param p_model
		* @param p_filePath
		* @param p_parserFlags
		*/
		static void Reload(Model& p_model, const std::string& p_filePath, Parsers::EModelParserFlags p_parserFlags = Parsers::EModelParserFlags::NONE);
		/**
		* Disabled constructor
		* @param p_modelInstance
		*/
		static bool Destroy(Model*& p_modelInstance);
	private:
		static Parsers::AssimpParser __ASSIMP;
	};

可以看到这个类中除了不可用的构造函数外,还包含了一个创建函数、一个重载函数以及一个销毁函数,同时引入了一个AssimpParser对象。

1.1Create

在create函数中,我们会用到model类的内容,相关的内容会在下一节讲解。

从该函数中我们看到它只需要2个参数,一个是模型文件的路径,一个是我们上一节讲过的EModelParserFlags枚举值。最开始我们创建一个Model对象的指针,并用传入文件路径参数做初始化,这个指针将会方便我们指向对应模型参数。

OvRendering::Resources::Model* OvRendering::Resources::Loaders::ModelLoader::Create(const std::string& p_filepath, Parsers::EModelParserFlags p_parserFlags)
{
	Model* result = new Model(p_filepath);

	if (__ASSIMP.LoadModel(p_filepath, result->m_meshes, result->m_materialNames, p_parserFlags))
	{
		result->ComputeBoundingSphere();
		return result;
	}

	delete result;

	return nullptr;
}

接下来使用AssimpParser对象的LoaderModel函数加载模型信息,这里会用到model对象的参数,如果模型加载成功将会返回true,就证明当前的model对象是可用的,我们就可以用它计算当前模型的碰撞盒信息,然后将其指针返回;如果模型加载失败,就意味没有模型导入,将会返回空指针。

1.2Reload

reload函数用于重载一个模型信息,说实话这一步的作用有点迷,我们需要从外界传入一个model对象的引用,然后利用create函数创建一个newModel,并将newModel的网格信息(m_meshes)、材质信息(m_materialNames)与碰撞球信息(m_boundingSphere)赋值给最开始传入函数的model对象,最后清空newModel的信息并销毁。

void OvRendering::Resources::Loaders::ModelLoader::Reload(Model& p_model, const std::string& p_filePath, Parsers::EModelParserFlags p_parserFlags)
{
	Model* newModel = Create(p_filePath, p_parserFlags);

	if (newModel)
	{
		p_model.m_meshes = newModel->m_meshes;
		p_model.m_materialNames = newModel->m_materialNames;
        p_model.m_boundingSphere = newModel->m_boundingSphere;
		newModel->m_meshes.clear();
		delete newModel;
	}
}

怎么说呢,这个函数的作用应该为了实现资源的复用,通过不断更换一个指针指向的模型信息来简化外界的流程,所以需要使用一个newModel做中间过渡,及时销毁无用的对象。

1.3Destroy?

这个函数没什么可讲的内容,就是在判断当前实例为真的情况下删除对应的model对象。

bool OvRendering::Resources::Loaders::ModelLoader::Destroy(Model*& p_modelInstance)
{
	if (p_modelInstance)
	{
		delete p_modelInstance;
		p_modelInstance = nullptr;

		return true;
	}

	return false;
}

2.ShaderLoader

ShaderLoader与ModelLoader类似,只不过它作用的对象的着色器,同时它的内容也会更加复杂。

class ShaderLoader
	{
	public:
		/**
		* Disabled constructor
		*/
		ShaderLoader() = delete;
		/**
		* Create a shader
		* @param p_filePath
		*/
		static Shader* Create(const std::string& p_filePath);
		/**
		* Create a shader from source
		* @param p_vertexShader
		* @param p_fragmentShader
		*/
		static Shader* CreateFromSource(const std::string& p_vertexShader, const std::string& p_fragmentShader);
		/**
		* Recompile a shader
		* @param p_shader
		* @param p_filePath
		*/
		static void	Recompile(Shader& p_shader, const std::string& p_filePath);
		/**
		* Destroy a shader
		* @param p_shader
		*/
		static bool Destroy(Shader*& p_shader);
	private:
		static std::pair<std::string, std::string> ParseShader(const std::string& p_filePath);
		static uint32_t CreateProgram(const std::string& p_vertexShader, const std::string& p_fragmentShader);
		static uint32_t CompileShader(uint32_t p_type, const std::string& p_source);

		static std::string __FILE_TRACE;
	};

2.1CreateProgram

CreateProgram将会最经常用的的函数,它的作用是为我们定义的着色器生成一个着色器程序对象。

着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。

当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。当输出和输入不匹配的时候,你会得到一个连接错误。

这是一个非常工具化的函数,它只需要顶点着色器(p_vertexShader)与片段着色器(p_vertexShader)的对象作为参数。

首先我们需要调用OpenGL内置的函数创建一个着色器程序,该函数会返回program的id,同时使用CompileShader函数(该函数同样是ShaderLoader的内容,马上就会讲到)编译顶点着色器与片段着色器,该函数同样会返回着色器的id。

uint32_t OvRendering::Resources::Loaders::ShaderLoader::CreateProgram(const std::string& p_vertexShader, const std::string& p_fragmentShader)
{
	const uint32_t program = glCreateProgram();

	const uint32_t vs = CompileShader(GL_VERTEX_SHADER, p_vertexShader);
	const uint32_t fs = CompileShader(GL_FRAGMENT_SHADER, p_fragmentShader);

然后判定两种着色器全部编译成功后,我们需要把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们。

if (vs == 0 || fs == 0)
		return 0;

	glAttachShader(program, vs);
	glAttachShader(program, fs);
	glLinkProgram(program);

在这里我们需要检测链接着色器程序是否失败,并获取相应的日志,我们使用的是OpenGL的glGetProgramiv函数。如果连接着色器失败了,linkStatus 会返回false,我们就需要使用OvDebug/Utils/Logger.h中的函数获得错误日志。

    GLint linkStatus;
	glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);

	if (linkStatus == GL_FALSE)
	{
		GLint maxLength;
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);

		std::string errorLog(maxLength, ' ');
		glGetProgramInfoLog(program, maxLength, &maxLength, errorLog.data());

		OVLOG_ERROR("[LINK] \"" + __FILE_TRACE + "\":\n" + errorLog);

		glDeleteProgram(program);

		return 0;
	}

如果链接成功,我们就可以激活当前的着色器程序,并将着色器对象删除,因为对应的着色器信息已经链接在了着色器程序上。这里使用的都是OpenGL的函数。最后会将着色器程序的id返回。

	glValidateProgram(program);
	glDeleteShader(vs);
	glDeleteShader(fs);

	return program;
}

2.2CompileShader

接下来是上文曾经用过的CompileShader函数,这同样是一个封装流程的工具化函数,过程与CreateProgram有很多类似之处,在此我们需要两个参数:着色器类型与着色器路径。

我们首先要做的是创建一个着色器对象,注意还是用ID来引用的。所以我们储存这个顶点着色器为unsigned int,然后用glCreateShader创建这个着色器。

下一步我们把这个着色器源码附加到着色器对象上,然后编译它。glShaderSource函数把要编译的着色器对象作为第一个参数。第二参数指定了传递的源码字符串数量,这里只有一个。第三个参数是顶点着色器真正的源码,第四个参数我们先设置为null。这里因为传入的路径是字符串,所以需要先将它转化为字符数组。

uint32_t OvRendering::Resources::Loaders::ShaderLoader::CompileShader(uint32_t p_type, const std::string& p_source)
{
	const uint32_t id = glCreateShader(p_type);

	const char* src = p_source.c_str();

	glShaderSource(id, 1, &src, nullptr);

	glCompileShader(id);

接下来的流程与CreateProgram类似,我们同样需要判断着色器是否编译成功,并将着色器的id返回。

    GLint compileStatus;
	glGetShaderiv(id, GL_COMPILE_STATUS, &compileStatus);

	if (compileStatus == GL_FALSE)
	{
		GLint maxLength;
		glGetShaderiv(id, GL_INFO_LOG_LENGTH, &maxLength);

		std::string errorLog(maxLength, ' ');
		glGetShaderInfoLog(id, maxLength, &maxLength, errorLog.data());

		std::string shaderTypeString = p_type == GL_VERTEX_SHADER ? "VERTEX SHADER" : "FRAGMENT SHADER";
		std::string errorHeader = "[" + shaderTypeString + "] \"";
		OVLOG_ERROR(errorHeader + __FILE_TRACE + "\":\n" + errorLog);

		glDeleteShader(id);

		return 0;
	}

	return id;
}

2.3ParseShader

该函数是一个shader的解析器,它能从一个文件路径中读取对应的着色代码。

首先用输入文件流打开文件路径,然后定义着色器类型:顶点着色器=0,片段着色器=1,空=-1;然后定义一个字符串line访问文件中的字符串,以及一个字符串流数组用于存储着色器代码。

std::pair<std::string, std::string> OvRendering::Resources::Loaders::ShaderLoader::ParseShader(const std::string& p_filePath)
{
	std::ifstream stream(p_filePath);

	enum class ShaderType { NONE = -1, VERTEX = 0, FRAGMENT = 1 };

	std::string line;

	std::stringstream ss[2];

	ShaderType type = ShaderType::NONE;

做好一切准备后,我们使用getline函数遍历文件中每一行的字符串,若变量line能访问到字符串#shader,则说明访问到了一个着色器代码。于是对line进行判断,若line中出现vertex,则表示访问到顶点着色器,并将type赋上对应的枚举值;若line中出现fragment也是同理。

若是line中没有字符串#shader,则有2种情况:当前位置位于着色器内或是位于着色器外。若是type不为空,则说明已进入着色器,并将着色器的代码输入字符串流数组的对应索引。这里需要将type的枚举值做强制类型转换,才能正确使用。


	while (std::getline(stream, line))
	{
		if (line.find("#shader") != std::string::npos)
		{
			if (line.find("vertex") != std::string::npos)			type = ShaderType::VERTEX;
			else if (line.find("fragment") != std::string::npos)	type = ShaderType::FRAGMENT;
		}
		else if (type != ShaderType::NONE)
		{
			ss[static_cast<int>(type)] << line << '\n';
		}
	}

	return 
	{ 
		ss[static_cast<int>(ShaderType::VERTEX)].str(),
		ss[static_cast<int>(ShaderType::FRAGMENT)].str()
	};
}

最后我们返回一个字符串数对,first为顶点着色器代码,second为片段着色器代码。

ShaderLoader中的其他函数与ModelShader大同小异,此处就不再详细说明。

同时,loader中还有一个工具——textureloader,我们将会与texture类一起在以后讲解。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-11-29 16:37:17  更:2021-11-29 16:37:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/16 7:40:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码