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

1.1构造函数

1.2InitGlew

1.3GLDebugMessageCallback

2.Renderer

2.1Draw

2.2FetchGLState

2.3ApplyStateMask


前面的文章中,我们大致梳理了一遍与图形学渲染相关了一些代码,本节我们就来看看游戏引擎是怎样呈现出我们所看到的这样的窗口的。

1.Driver

Driver类为我们创建了一个OpenGL的环境来显示窗口。

class Driver
	{
	public:
		Driver(const Settings::DriverSettings& p_driverSettings);
		~Driver() = default;
		bool IsActive() const;

	private:
		void InitGlew();
		static void __stdcall GLDebugMessageCallback(uint32_t source, uint32_t type, uint32_t id, uint32_t severity, int32_t length, const char* message, const void* userParam);

	private:
		bool m_isActive;
	};

1.1构造函数

首先,它的构造函数需要这样一个结构体类型:

	struct DriverSettings
	{
		bool debugMode = false;
	};

这里主要实现了对于OpenGL内置功能的封装,首先使用InitGlew函数初始化一个glew窗口,并将激活状态m_isActive赋值为true,然后判断p_driverSettings的debugMode。

若当前模式为debug,则利用glGetIntegerv函数获取当前环境标签flag,并将flag与GL_CONTEXT_FLAG_DEBUG_BIT进行按位取与,若结果一致(即结果为真),则调用接下来的4个函数,其具体作用就不作详述,总之将会搭建一个适用于调试的环境。

OvRendering::Context::Driver::Driver(const Settings::DriverSettings& p_driverSettings)
{
	InitGlew();
	
	m_isActive = true;

	if (p_driverSettings.debugMode)
	{
		GLint flags;
		glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
		if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
		{
			glEnable(GL_DEBUG_OUTPUT);
			glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
			glDebugMessageCallback(GLDebugMessageCallback, nullptr);
			glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
		}
	}

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glCullFace(GL_BACK);
}

最后glBlendFunc设置混合模式为alpha值混合,glCullFace设置面剔除为背面剔除,则将会使半透明的物体依据alpha值进行颜色差值混合,并将不再渲染网格中与法向量相反的面。

1.2InitGlew

InitGlew函数早构造函数中使用过,其中调用了OpenGL内置的glewInit函数初始化窗口,并进行错误检测,若glewInit返回的值不等于GLEW_OK,系统将会发出错误信息。

void OvRendering::Context::Driver::InitGlew()
{
	const GLenum error = glewInit();
	if (error != GLEW_OK)
	{
		std::string message = "Error Init GLEW: ";
		std::string glewError = reinterpret_cast<const char*>(glewGetErrorString(error));
		OVLOG_INFO(message + glewError);
	}
}

1.3GLDebugMessageCallback

GLDebugMessageCallback函数同样出现在构造函数中,它作为函数glDebugMessageCallback的第一个参数,是glDebugMessageCallback具体功能的实现,将对具体的错误信息作出反馈。

在最开始,我们会选择性忽略一些警告、错误:

void OvRendering::Context::Driver::GLDebugMessageCallback(uint32_t source, uint32_t type, uint32_t id, uint32_t severity, int32_t length, const char* message, const void* userParam)
{
	// ignore non-significant error/warning codes
	if (id == 131169 || id == 131185 || id == 131218 || id == 131204) return;

之后,我们需要设置反馈的信息的格式,利用字符串output存储信息,需要标明id(错误ID)、source(发生错误的地方)、type(错误的类型)、severity(错误的危急程度)等信息。

    std::string output;

	output += "OpenGL Debug Message:\n";
	output += "Debug message (" + std::to_string(id) + "): " + message + "\n";

	switch (source)
	{
	case GL_DEBUG_SOURCE_API:				output += "Source: API";				break;
	case GL_DEBUG_SOURCE_WINDOW_SYSTEM:		output += "Source: Window System";		break;
	case GL_DEBUG_SOURCE_SHADER_COMPILER:	output += "Source: Shader Compiler";	break;
	case GL_DEBUG_SOURCE_THIRD_PARTY:		output += "Source: Third Party";		break;
	case GL_DEBUG_SOURCE_APPLICATION:		output += "Source: Application";		break;
	case GL_DEBUG_SOURCE_OTHER:				output += "Source: Other";				break;
	}

	output += "\n";

	switch (type)
	{
	case GL_DEBUG_TYPE_ERROR:               output += "Type: Error";				break;
	case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: output += "Type: Deprecated Behaviour"; break;
	case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:  output += "Type: Undefined Behaviour";	break;
	case GL_DEBUG_TYPE_PORTABILITY:         output += "Type: Portability";			break;
	case GL_DEBUG_TYPE_PERFORMANCE:         output += "Type: Performance";			break;
	case GL_DEBUG_TYPE_MARKER:              output += "Type: Marker";				break;
	case GL_DEBUG_TYPE_PUSH_GROUP:          output += "Type: Push Group";			break;
	case GL_DEBUG_TYPE_POP_GROUP:           output += "Type: Pop Group";			break;
	case GL_DEBUG_TYPE_OTHER:               output += "Type: Other";				break;
	}

	output += "\n";

	switch (severity)
	{
	case GL_DEBUG_SEVERITY_HIGH:			output += "Severity: High";				break;
	case GL_DEBUG_SEVERITY_MEDIUM:			output += "Severity: Medium";			break;
	case GL_DEBUG_SEVERITY_LOW:				output += "Severity: Low";				break;
	case GL_DEBUG_SEVERITY_NOTIFICATION:	output += "Severity: Notification";		break;
	}

最后根据不同的危急等级,调用对应的报告宏(与信息反馈相关的内容详见OvDebug函数库)

	switch (severity)
	{
	case GL_DEBUG_SEVERITY_HIGH:			OVLOG_ERROR(output);	break;
	case GL_DEBUG_SEVERITY_MEDIUM:			OVLOG_WARNING(output);	break;
	case GL_DEBUG_SEVERITY_LOW:				OVLOG_INFO(output);		break;
	case GL_DEBUG_SEVERITY_NOTIFICATION:	OVLOG_INFO(output);			break;
	}
}

2.Renderer

Renderer类是一个集合多种工具的功能类,从它的头文件中可见一斑,它的内容存在大量重复类似的工作,大多是对于OpenGL中各种特殊功能的封装,所以这里我们只捡重要的几个来说。

#include <optional>

#include "OvRendering/Context/Driver.h"
#include "OvRendering/LowRenderer/Camera.h"
#include "OvRendering/Resources/Shader.h"
#include "OvRendering/Resources/Model.h"
#include "OvRendering/Settings/ERenderingCapability.h"
#include "OvRendering/Settings/EPrimitiveMode.h"
#include "OvRendering/Settings/ERasterizationMode.h"
#include "OvRendering/Settings/EComparaisonAlgorithm.h"
#include "OvRendering/Settings/EOperation.h"
#include "OvRendering/Settings/ECullFace.h"
#include "OvRendering/Settings/ECullingOptions.h"
#include "OvRendering/Settings/EPixelDataFormat.h"
#include "OvRendering/Settings/EPixelDataType.h"

在Renderer包含这样几个变量,m_driver(环境驱动器)、m_frameInfo(框架信息)、m_state(当前渲染状态)。


		struct FrameInfo
		{
			uint64_t batchCount		= 0;
			uint64_t instanceCount	= 0;
			uint64_t polyCount		= 0;
		};

	private:
		Context::Driver&	m_driver;
		FrameInfo			m_frameInfo;
		uint8_t				m_state;

2.1Draw

Draw的作用是绘制网格模型,传入的参数有p_mesh(网格信息)、p_primitiveMode(绘制网格的模式)、p_instances(绘制实例的数量)。

首先判断多边形数量大于0,每调用一次本函数,则批处理次数batchCount加1,绘制实例总数instanceCount加上p_instances,绘制的多边形总数加上(顶点索引数/3*本次绘制的实例数)。

接下来调用p_mesh的绑定函数,并根据顶点索引数的数量判断本次绘制是使用VBO还是EBO,根据不同的绘制方式调用不同的函数:

使用VBO时调用glDrawArrays;使用EBO时调用glDrawElements。

有关VBO与EBO的内容已在前文提到,详见【Overload游戏引擎】源码分析之四:OvRendering函数库(二)

在绘制的实例大于1时,我们还可以使用glDrawElementsInstanced/glDrawArraysInstanced进行批量绘制,只需要在最后多传入参数p_instances即可。

void OvRendering::Core::Renderer::Draw(Resources::IMesh& p_mesh, Settings::EPrimitiveMode p_primitiveMode, uint32_t p_instances)
{
	if (p_instances > 0)
	{
		++m_frameInfo.batchCount;
		m_frameInfo.instanceCount += p_instances;
		m_frameInfo.polyCount += (p_mesh.GetIndexCount() / 3) * p_instances;

		p_mesh.Bind();
		
		if (p_mesh.GetIndexCount() > 0)
		{
			/* With EBO */
			if (p_instances == 1)
				glDrawElements(static_cast<GLenum>(p_primitiveMode), p_mesh.GetIndexCount(), GL_UNSIGNED_INT, nullptr);
			else
				glDrawElementsInstanced(static_cast<GLenum>(p_primitiveMode), p_mesh.GetIndexCount(), GL_UNSIGNED_INT, nullptr, p_instances);
		}
		else
		{
			/* Without EBO */
			if (p_instances == 1)
				glDrawArrays(static_cast<GLenum>(p_primitiveMode), 0, p_mesh.GetVertexCount());
			else
				glDrawArraysInstanced(static_cast<GLenum>(p_primitiveMode), 0, p_mesh.GetVertexCount(), p_instances);
		}

		p_mesh.Unbind();
	}
}

在完成绘制后,还要调用p_mesh的解除绑定函数。

2.2FetchGLState

FetchGLState函数的作用是返回当前渲染模式的各种状态信息,例如:是否能修改RGBA缓冲、是否能修改深度缓冲、是否开启混合模式、是否开启面剔除、是否开始深度测试等。

以下代码首先定义了一个8bit整型变量result,用于储存最后的状态。

然后用glGetBooleanv函数将GL_COLOR_WRITEMASK四个颜色通道的缓冲状态写入cMask中,同时进行上述几种模式的判断,并将result与对应的二进制编号进行按位或的操作。

最后若是开始了面剔除模式,则要判断面剔除的类型,对result进行同样操作。

uint8_t OvRendering::Core::Renderer::FetchGLState()
{
	using namespace OvRendering::Settings;

	uint8_t result = 0;

	GLboolean cMask[4];
	glGetBooleanv(GL_COLOR_WRITEMASK, cMask);

	if (GetBool(GL_DEPTH_WRITEMASK))						result |= 0b0000'0001;
	if (cMask[0])											result |= 0b0000'0010;
	if (GetCapability(ERenderingCapability::BLEND))			result |= 0b0000'0100;
	if (GetCapability(ERenderingCapability::CULL_FACE))		result |= 0b0000'1000;
	if (GetCapability(ERenderingCapability::DEPTH_TEST))	result |= 0b0001'0000;

	switch (static_cast<ECullFace>(GetInt(GL_CULL_FACE)))
	{
	case OvRendering::Settings::ECullFace::BACK:			result |= 0b0010'0000; break;
	case OvRendering::Settings::ECullFace::FRONT:			result |= 0b0100'0000; break;
	case OvRendering::Settings::ECullFace::FRONT_AND_BACK:  result |= 0b0110'0000; break;
	}

	return result;
}

通过各种模式的编码方式我们可以看出,为了存储每一种模式的状态信息,函数为其分配了单独的一个bit位存储信息,使得任意两种模式的1处在不同的bit位上;而由于面剔除的三种模式只能存在一种,所以我们分出了2个bit位给它,且不要求1处在不同的bit位上。

2.3ApplyStateMask

ApplyStateMask函数将会在模式状态发生改变时起作用。

当过去状态与目前状态不一致,则进入各种模式的if判断,将状态变量与某一种模式的解码编号进行按位与操作,这样就能只留下单独一种模式的状态信息,之后将比较前后状态,当状态不一致时,更新对应模式的状态信息。

void OvRendering::Core::Renderer::ApplyStateMask(uint8_t p_mask)
{
	if (p_mask != m_state)
	{
		using namespace OvRendering::Settings;

		if ((p_mask & 0x01) != (m_state & 0x01))	SetDepthWriting(p_mask & 0x01);
		if ((p_mask & 0x02) != (m_state & 0x02))	SetColorWriting(p_mask & 0x02);
		if ((p_mask & 0x04) != (m_state & 0x04))	SetCapability(ERenderingCapability::BLEND, p_mask & 0x04);
		if ((p_mask & 0x08) != (m_state & 0x08))	SetCapability(ERenderingCapability::CULL_FACE, p_mask & 0x8);
		if ((p_mask & 0x10) != (m_state & 0x10))	SetCapability(ERenderingCapability::DEPTH_TEST, p_mask & 0x10);

		if ((p_mask & 0x08) && ((p_mask & 0x20) != (m_state & 0x20) || (p_mask & 0x40) != (m_state & 0x40)))
		{
			int backBit = p_mask & 0x20;
			int frontBit = p_mask & 0x40;
			SetCullFace(backBit && frontBit ? ECullFace::FRONT_AND_BACK : (backBit ? ECullFace::BACK : ECullFace::FRONT));
		}

		m_state = p_mask;
	}
}

同样地,当面剔除开启时,判断3种面剔除模式是否发生改变,由于双面剔除(FRONT_AND_BACK)在0x20与0x40上都为1,所以会被自然归入前面(FRONT)与背面(BACK)的判定。

对面剔除模式的判定中使用了两层三目运算符返回结果,当backBit与frontBit都为真时,代表当前模式为FRONT_AND_BACK,否则单独对backBit进行判断。

最后还需要更新m_state 的值。

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

  游戏开发 最新文章
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-12-09 12:00:40  更:2021-12-09 12:00:48 
 
开发: 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年10日历 -2024/10/18 15:12:15-

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