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

目录

1.Uniform

1.1UniformType

1.2UniformInfo

2.Shader

2.1SetUniform和GetUniform

2.2GetUniformLocation


本章我们继续来讲OvRendering中与shader(着色器)有关的函数。

1.Uniform

首先我们来介绍一下什么是uniform。

uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。

首先,uniform是全局的(Global)。

全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。

第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。

总之,uniform对于设置一个在渲染迭代中会改变的属性是一个非常有用的工具,它也是一个在程序和着色器间数据交互的很好工具。

了解了uniform之后,我就来看与它相关的一些数据定义,其中包含了shader类中将会使用的结构体和枚举值。

1.1UniformType

这个枚举类中列出来一个uniform可以使用的数据类型:

#include <stdint.h>

namespace OvRendering::Resources
{
	/**
	* Defines the types that a uniform can take
	*/
	enum class UniformType : uint32_t
	{
		UNIFORM_BOOL			= 0x8B56,
		UNIFORM_INT				= 0x1404,
		UNIFORM_FLOAT			= 0x1406,
		UNIFORM_FLOAT_VEC2		= 0x8B50,
		UNIFORM_FLOAT_VEC3		= 0x8B51,
		UNIFORM_FLOAT_VEC4		= 0x8B52,
		UNIFORM_FLOAT_MAT4		= 0x8B5C,
		UNIFORM_DOUBLE_MAT4		= 0x8F48,
		UNIFORM_SAMPLER_2D		= 0x8B5E,
		UNIFORM_SAMPLER_CUBE	= 0x8B60
	};
}

1.2UniformInfo

在这个结构体中,使用UniformType定义数据类型,之后定义了uniform的变量名称与着色器位置,其中std::any能够接收任意类型的单值数据。

#include <string>
#include <any>

#include "OvRendering/Resources/UniformType.h"

namespace OvRendering::Resources
{
	/**
	* Data structure containing information about a uniform
	*/
	struct UniformInfo
	{
		UniformType		type;
		std::string		name;
		uint32_t		location;
		std::any		defaultValue;
	};
}

2.Shader

了解了以上内容后,我们进入本文的正题。

我们可以看到,原本的作用域中嵌套定义了一个命名空间Loader,其中包含以一个ShaderLoader类,这个类将会在以后的文章中提及,它将作为Shader类的友元类。

namespace OvRendering::Resources
{
	namespace Loaders { class ShaderLoader; }

	/**
	* OpenGL shader program wrapper
	*/
	class Shader
    {
    friend class Loaders::ShaderLoader;

接下来我们来看它的成员变量,其中共有变量包括一个uint32_t型的着色器ID,一个string型的路径名称,依旧一个UniformInfo类型的向量。前两个变量用于指定着色的唯一标识与位置,所以不可以更改。

然后私有变量定义了一个无向图类型的uniform位置缓冲,具体的作用将在下文提到。

	public:
		const uint32_t id;
		const std::string path;
		std::vector<UniformInfo> uniforms;

	private:
		std::unordered_map<std::string, int> m_uniformLocationCache;

在Shader的构造函数中,它将两个常量变量初始化,然后调用了一个私有函数QueryUniforms(),这个函数我们将在下文来分析。

OvRendering::Resources::Shader::Shader(const std::string p_path, uint32_t p_id) : path(p_path), id(p_id)
{
	QueryUniforms();
}

2.1SetUniform和GetUniform

Shader类中的函数主要分为2大类:设置uniform和获取uniform,接下来我们将用int型数据来举例,其他类型的函数可以以此类推:

SetUniformInt(const std::string& p_name, int p_value):

void OvRendering::Resources::Shader::SetUniformInt(const std::string& p_name, int p_value)
{
	glUniform1i(GetUniformLocation(p_name), p_value);
}

glUniform1i()是OpenGL内置的设置uniform的函数,函数的后缀表示传递数据的个数+传递数据的类型的首字母,就像本例中传递了1个int型数据,函数的后缀就是1i,其余情况下也可以是2i、1f、2f等,就像下列函数这样:

void OvRendering::Resources::Shader::SetUniformVec4(const std::string& p_name, const OvMaths::FVector4& p_vec4)
{
	glUniform4f(GetUniformLocation(p_name), p_vec4.x, p_vec4.y, p_vec4.z, p_vec4.w);
}

但是,在这里还有个特殊的存在就是矩阵。当传递的数据时矩阵时,函数的结构也会发生细微的改变,我们可以看到在下列代码中,函数的后缀是“Matrix4fv”,它由Matrix+传递数据的个数+传递数据的类型的首字母+vector首字母组成,表示传递1个4×4的浮点型矩阵(使用v作后缀的glUniform函数时,只能传入4维向量)。

void OvRendering::Resources::Shader::SetUniformMat4(const std::string& p_name, const OvMaths::FMatrix4& p_mat4)
{
	glUniformMatrix4fv(GetUniformLocation(p_name), 1, GL_TRUE, &p_mat4.data[0]);
}

接下来我们统一来讲glUniform的传入参数,对于所有的glUniform来说,第一个参数都是对应着色器中变量的位置。在这里它调用了GetUniformLocation()函数,该函数可以通过一个字符串来查找对应的着色器变量,同时返回该着色器变量的位置。

对于大部分glUniform函数来说,它的其余参数只是将要传递的数据按序传入,但是在glUniformMatrix4fv中却发生了变化,它的第二个参数表示传入的矩阵个数,第三个参数指明矩阵是列优先(column major)矩阵(GL_FALSE)还是行优先(row major)矩阵(GL_TRUE),最后一个参数是矩阵首个元素的引用。

由于uniform是全局操作,所以所有名称相同的变量都会接收传递的数据。

GetUniformInt(const std::string& p_name):

int OvRendering::Resources::Shader::GetUniformInt(const std::string& p_name)
{
	int value;
	glGetUniformiv(id, GetUniformLocation(p_name), &value);
	return value;
}

GetUniform函数与前者殊途同归,同样调用了OpenGL的内置函数glGetUniformiv,它会比glUniformiv多出一个id用于访问当前的shader,然后通过变量名查找变量的位置,将变量中的数据传给第三个参数的引用,由此可以实现从GPU到CPU的数据传递。


讲完了以上内容,我们回来看QueryUniforms()函数的具体实现。

void OvRendering::Resources::Shader::QueryUniforms()
{
	GLint numActiveUniforms = 0;
	uniforms.clear();
	glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &numActiveUniforms);
	std::vector<GLchar> nameData(256);
	

在函数开头,一共完成了4件事:

1.初始化numActiveUniforms 指明激活的uniform的数目;

2.将Shader类中的uniforms变量清空;

3.利用glGetProgramiv函数返回当前id的shader激活的uniform的数目;

4.定义长度为256的GLchar型数组。

在完成了以上操作后,进入了一个for循环,遍历每一个uniform。在这里涉及到1个重要的函数

void?glGetActiveUniform(GLuint?program,GLuint?index,GLsizei?bufSize,GLsizei *length,GLint *size,GLenum *type,GLchar *name)。

	for (int unif = 0; unif < numActiveUniforms; ++unif)
	{
		GLint arraySize = 0;
		GLenum type = 0;
		GLsizei actualLength = 0;
		glGetActiveUniform(id, unif, static_cast<GLsizei>(nameData.size()), &actualLength, &arraySize, &type, &nameData[0]);
		std::string name(static_cast<char*>(nameData.data()), actualLength);

id指定要查询的shader对象的id;

unif指定要查询的shader中uniform变量的索引;

&nameData[0]返回包含uniform变量名称的,以null结尾的字符串;

static_cast<GLsizei>(nameData.size())指定允许OpenGL在由name指示的字符缓冲区中写入的最大字符数;

&actualLength如果传递NULL以外的值,则返回由name指示的字符串中的OpenGL实际写入的字符数(不包括空终止符);

&arraySize返回shader中uniform变量的大小;

&type返回shader中uniform变量的数据类型。

之后,用nameData与actualLength构建字符串name,包含当前uniform变量的名称。

完成以上任务后,函数进行if判断,返回是否name中包含字符串‘ubo_’,是返回True。

if (!IsEngineUBOMember(name))
		{
			std::any defaultValue;

			switch (static_cast<UniformType>(type))
			{
			case OvRendering::Resources::UniformType::UNIFORM_BOOL:			defaultValue = std::make_any<bool>(GetUniformInt(name));					break;
			case OvRendering::Resources::UniformType::UNIFORM_INT:			defaultValue = std::make_any<int>(GetUniformInt(name));						break;
			case OvRendering::Resources::UniformType::UNIFORM_FLOAT:		defaultValue = std::make_any<float>(GetUniformFloat(name));					break;
			case OvRendering::Resources::UniformType::UNIFORM_FLOAT_VEC2:	defaultValue = std::make_any<OvMaths::FVector2>(GetUniformVec2(name));		break;
			case OvRendering::Resources::UniformType::UNIFORM_FLOAT_VEC3:	defaultValue = std::make_any<OvMaths::FVector3>(GetUniformVec3(name));		break;
			case OvRendering::Resources::UniformType::UNIFORM_FLOAT_VEC4:	defaultValue = std::make_any<OvMaths::FVector4>(GetUniformVec4(name));		break;
			case OvRendering::Resources::UniformType::UNIFORM_SAMPLER_2D:	defaultValue = std::make_any<OvRendering::Resources::Texture*>(nullptr);	break;
			}

之后,定义一个any类型的默认值变量defaultValue,并将name的类型转化为非静态的UniformType类型。

接着,对name的类型进行switch判定,在每一个case中,用GetUniform函数获取name对应的uniform变量中的数据,并传入defaultValue中。

			if (defaultValue.has_value())
			{
				uniforms.push_back
				({
					static_cast<UniformType>(type),
					name,
					GetUniformLocation(nameData.data()),
					defaultValue
				});
			}
		}

最后,判断defaultValue是否非空,若defaultValue存入数据,则构造一个UniformInfo结构体并存入向量uniforms中,然后进入下一个循环。


除了上述几个函数后,Shader类中剩余的几个功能函数,它们在上述的函数中出现过,我们在此简单看看。

2.2GetUniformLocation

uint32_t OvRendering::Resources::Shader::GetUniformLocation(const std::string& name)
{
	if (m_uniformLocationCache.find(name) != m_uniformLocationCache.end())
		return m_uniformLocationCache.at(name);

	const int location = glGetUniformLocation(id, name.c_str());

	if (location == -1)
		OVLOG_WARNING("Uniform: '" + name + "' doesn't exist");

	m_uniformLocationCache[name] = location;

	return location;
}

函数传入了一个uniform变量名,并通过这个变量名在m_uniformLocationCache中查找对应的key(关键字),并返回一个迭代器与m_uniformLocationCache末尾的迭代器(即空指针)对比。

若两者不相等,证明name存在,则返回m_uniformLocationCachename对应的路径;

若name不存在,在调用OpenGL内置函数glGetUniformLocation,获取name对应的路径,若路径为-1则报错,最后将路径的值传入m_uniformLocationCache中以name为关键字的节点。

2.3GetUniformInfo(const std::string& p_name) const

const OvRendering::Resources::UniformInfo* OvRendering::Resources::Shader::GetUniformInfo(const std::string& p_name) const
{
	auto found = std::find_if(uniforms.begin(), uniforms.end(), [&p_name](const UniformInfo& p_element)
	{
		return p_name == p_element.name;
	});

	if (found != uniforms.end())
		return &*found;
	else
		return nullptr;
}

该函数用于获取uniform的信息,它的关键在于std::find_if,它的定义如下:

template<class InputIterator, class UnaryPredicate>
? InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate p)
{
? ? ? //迭代容器对结果进行判定操作
? ? ? for (;first != last; ++first) {
? ? ? ? ? if (true == p(*first)) {
? ? ? ? ? ? ? return first;
? ? ? ? ? }
? ? ? }
?return last;
}

第一个参数是头迭代器,第二个参数是末尾迭代器,最后一个参数p是一个可调用对象。

我们可以将p定义为一个函数,find_if将会在每一个for循环中调用p对象,其中[……]中传入外界参数,(……)中定义函数内部变量。

在本例中,p函数传入外界参数p_name,并定义一个UniformInfo迭代器p_element的引用,将p_element中的name值与我们传入的p_name比较,将比较结果返回find_if:

[&p_name](const UniformInfo& p_element)
?? ?{
?? ??? ?return p_name == p_element.name;
?? ?});

若两者对比的结果相同,则find_if会返回当前的迭代器,并存入found。

若found等于uniforms的末尾的迭代器(即空指针),则表示为找到name对应的uniform变量;反之则返回name对应的迭代器。

以上就是本章的全部内容。

  游戏开发 最新文章
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-14 22:03:23  更:2021-11-14 22:03:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 22:24:31-

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