【Overload游戏引擎细节分析】编辑器对象鼠标拾取原理

这篇具有很好参考价值的文章主要介绍了【Overload游戏引擎细节分析】编辑器对象鼠标拾取原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

     Overload的场景视图区有拾取鼠标功能,单击拾取物体后会显示在Inspector面板中。本文来分析鼠标拾取这个功能背后的原理。

【Overload游戏引擎细节分析】编辑器对象鼠标拾取原理,Overload游戏引擎细节分析,游戏引擎

一、OpenGL的FrameBuffer

实现鼠标拾取常用的方式有两种:渲染id到纹理、光线投射求交。Overload使用的是渲染id到纹理,其实现需借助OpenGL的帧缓冲FrameBuffer,所以要先了解一下OpenGL的帧缓冲。

我们一般讨论的缓存默认指窗口缓存,直接显示在窗口中。我们也可以创建一个自定义的缓存,让GPU管线渲染到纹理当中,之后在其他地方可以使用这张纹理。并且纹理中的数据只是二进制值,不一定非得是颜色,可以写入任意有意义的数据。

如果我们要创建帧缓存对象,需要调用glGenFramebuffers(),得到一个未使用的标识符。在使用帧缓存的时候需要先调用glBindFramebuffer(GL_FRAMEBUFFER, bufferID)绑定。如果要渲染到纹理贴图,需调用glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENTi, textureId, level)将纹理贴图的level层级关联到帧缓存附件上。如果渲染还需要深度缓存、模板缓存那么还需要渲染缓存。

渲染缓存同样也是OpenGL所管理的一处高效内存区域,它可以存储特定格式的数据,其只有关联到一个帧缓存才有意义。调用glGenRenderbuffers可以创建渲染缓存,操作它的时候同样需要绑定操作。绑定的时候使用glBindRenderbuffer。

看到这里是不是觉得帧缓存使用起来太复杂了?其实帧缓存的设置都是固定格式的代码,套路基本一样,先用伪代码串一下。假设我们的程序是面向过程设计的,先用调用init函数进行初始化,之后主循环不断调用display函数进行渲染,大致伪代码如下:

init() {
     glGenFramebuffers(1, &m_bufferID);  // 生成帧缓存
     glGenTextures(1, &m_renderTexture)  // 生成纹理对象
     // 设置纹理格式
     glBindTexture(GL_TEXTURE_2D, m_renderTexture);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
     glBindTexture(GL_TEXTURE_2D, 0);
     // 将纹理作为颜色附件绑定到帧缓存上
     glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_renderTexture, 0);

     glGenRenderbuffers(1, &m_depthStencilBuffer); // 生成渲染对象
     // 设置渲染对象数据格式
	 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, p_width, p_height);
     // 配置成帧缓存的深度缓冲与模板缓冲附件
     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer);
	 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer);
  }

display() {
    // 1. 绑定帧缓存
    glBindFramebuffer(GL_FRAMEBUFFER, m_bufferID);

    // 2. 渲染物体到帧缓存
    glClearColor();
    glClear();
    draw();

    // 3. 解绑帧缓存
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // 4. 使用帧缓存渲染出来的纹理
    ...
    glActiveTexture();
    glBindTexture(GL_TEXTURE_2D, id);
    
}

  init函数中的代码基本保持不变。                                       

二、Overload对FrameBuffer的封装

Overload将FrameBuffer封装成类Framebuffer,代码位于Framebuffer.h、Framebuffer.cpp中。先看Framebuffer.h文件,Framebuffer类的定义如下,如果对注释中的名词不太熟悉需学习一下OpenGL。

class Framebuffer
	{
	public:
		/**
		* 构造函数,会直接创建一个帧缓冲
		* @param p_width 帧缓冲的宽
		* @param p_height 帧缓存的高
		*/
		Framebuffer(uint16_t p_width = 0, uint16_t p_height = 0);

		/**
		* 析构函数
		*/
		~Framebuffer();

		/**
		* 绑定帧缓冲,对其进行操作
		*/
		void Bind();

		/**
		* 解除绑定
		*/
		void Unbind();

		/**
		* 对帧缓冲的大小进行改变
		* @param p_width 新的帧缓冲宽度
		* @param p_height 新的帧缓冲高度
		*/
		void Resize(uint16_t p_width, uint16_t p_height);

		/**
		* 帧缓冲的id
		*/
		uint32_t GetID();

		/**
		* 返回OpenGL纹理附件的id
		*/
		uint32_t GetTextureID();

		/**
		* 返回渲染缓存的id,这个方法在Overload中其他地方没有使用
		*/
		uint32_t GetRenderBufferID();

	private:
		uint32_t m_bufferID = 0; // 帧缓冲的id
		uint32_t m_renderTexture = 0; // 纹理附件的id
		uint32_t m_depthStencilBuffer = 0; // 渲染缓存的id
	};

先来看其构造函数的实现:

OvRendering::Buffers::Framebuffer::Framebuffer(uint16_t p_width, uint16_t p_height)
{
	/* Generate OpenGL objects */
	glGenFramebuffers(1, &m_bufferID); // 生成帧缓冲id
	glGenTextures(1, &m_renderTexture); // 生成颜色缓冲纹理
	glGenRenderbuffers(1, &m_depthStencilBuffer); // 生成渲染缓存

	// 设置m_renderTexture纹理参数
	glBindTexture(GL_TEXTURE_2D, m_renderTexture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glBindTexture(GL_TEXTURE_2D, 0);

	/* Setup framebuffer */
	Bind();
	// 将纹理设置为渲染目标
	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_renderTexture, 0);
	Unbind();

	Resize(p_width, p_height);
}

构造中直接生成帧缓存、纹理、渲染缓存对象,并将纹理作为颜色附件关联到帧缓存上。再看resize方法。

void OvRendering::Buffers::Framebuffer::Resize(uint16_t p_width, uint16_t p_height)
{
	/* Resize texture */
	// 设置纹理的大小
	glBindTexture(GL_TEXTURE_2D, m_renderTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, p_width, p_height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
	glBindTexture(GL_TEXTURE_2D, 0);

	/* Setup depth-stencil buffer (24 + 8 bits) */
	glBindRenderbuffer(GL_RENDERBUFFER, m_depthStencilBuffer);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, p_width, p_height);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);

	/* Attach depth and stencil buffer to the framebuffer */
	Bind();
	// 配置深度缓冲与模板缓冲
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer);
	Unbind();
}

这俩方法加起来跟前面的伪代码init函数基本一致,只是用面向对象的方式进行了封装而已。

三、鼠标拾取原理

Overload中鼠标拾取是先将物体的id渲染到纹理中,根据鼠标位置读取这张图上的对应的像素值,之后解码获取对象的id。下图红框中是这个函数的关键三个步骤:

【Overload游戏引擎细节分析】编辑器对象鼠标拾取原理,Overload游戏引擎细节分析,游戏引擎

 我们先来看RenderSceneForActorPicking这个函数。这个函数是把场景中的物体、摄像机、灯光进行渲染。他们三者的渲染方式很类似,以渲染摄像机为例,代码如下:

	/* Render cameras */
	for (auto camera : m_context.sceneManager.GetCurrentScene()->GetFastAccessComponents().cameras)
	{
		auto& actor = camera->owner;

		if (actor.IsActive())
		{
            // 对摄像机的id进行编码,设置到Shader的unfiorm中
			PreparePickingMaterial(actor, m_actorPickingMaterial);
			auto& model = *m_context.editorResources->GetModel("Camera");
			auto modelMatrix = CalculateCameraModelMatrix(actor);
            // 绘制摄像机,其覆盖区域的像素全部是其id
			m_context.renderer->DrawModelWithSingleMaterial(model, m_actorPickingMaterial, &modelMatrix);
		}
	}

这里有一个特殊函数PreparePickingMaterial,将id的三个字节变成颜色保持到u_Diffuse变量中,这个变量Shader中会使用。核心代码见下图红框,这种编码方式是将信息写入图像常用的方式,可以直接拿来借鉴参考。

【Overload游戏引擎细节分析】编辑器对象鼠标拾取原理,Overload游戏引擎细节分析,游戏引擎

要想在FrameBuffer中绘制肯定需要Shader。Overload的Shader是封装成了材料,对于拾取需要特殊的材料,RenderSceneForActorPicking函数中变量m_actorPickingMaterial就保存的这种材料。我们跟踪代码,找这个变量的初始化,可以找到以下代码:

/* Picking Material */
auto unlit = m_context.shaderManager[":Shaders\\Unlit.glsl"];
m_actorPickingMaterial.SetShader(unlit);
m_actorPickingMaterial.Set("u_Diffuse", FVector4(1.f, 1.f, 1.f, 1.0f));
m_actorPickingMaterial.Set<OvRendering::Resources::Texture*>("u_DiffuseMap", nullptr);
m_actorPickingMaterial.SetFrontfaceCulling(false);
m_actorPickingMaterial.SetBackfaceCulling(false);

看来这个Shader是保存在文件Unlit.glsl中的,同时注意u_DiffuseMap设成了null,记住这一点,这是故意为之,魔鬼都隐藏在这些细节当中。

我们打开这个文件,分析这个Shader:

#shader vertex
#version 430 core

layout (location = 0) in vec3 geo_Pos;
layout (location = 1) in vec2 geo_TexCoords;
layout (location = 2) in vec3 geo_Normal;

layout (std140) uniform EngineUBO
{
    mat4    ubo_Model;
    mat4    ubo_View;
    mat4    ubo_Projection;
    vec3    ubo_ViewPos;
    float   ubo_Time;
};

out VS_OUT
{
    vec2 TexCoords;
} vs_out;

void main()
{
    vs_out.TexCoords = geo_TexCoords;

    gl_Position = ubo_Projection * ubo_View * ubo_Model * vec4(geo_Pos, 1.0);
}

#shader fragment
#version 430 core

out vec4 FRAGMENT_COLOR;

in VS_OUT
{
    vec2 TexCoords;
} fs_in;

uniform vec4        u_Diffuse = vec4(1.0, 1.0, 1.0, 1.0);
uniform sampler2D   u_DiffuseMap;
uniform vec2        u_TextureTiling = vec2(1.0, 1.0);
uniform vec2        u_TextureOffset = vec2(0.0, 0.0);

void main()
{
    FRAGMENT_COLOR = texture(u_DiffuseMap, u_TextureOffset + vec2(mod(fs_in.TexCoords.x * u_TextureTiling.x, 1), mod(fs_in.TexCoords.y * u_TextureTiling.y, 1))) * u_Diffuse;
}

这个GPU程序的Vertex Shader没啥可说的,计算一下网格的NDC坐标完事。令人费解的是Fragment Shader的最后一行代码,我这里先说结论,这行代码等价于FRAGMENT_COLOR = u_Diffuse。 至于为什么,简单来说应用程序中将u_DiffuseMap设成了null,但传给CPU的时候会将值是null的纹理设置成空纹理。这个空纹理大小一个像素,值是纯白色,那么对其采样结果都是1.0 。

空文理初始化见以下代码:

【Overload游戏引擎细节分析】编辑器对象鼠标拾取原理,Overload游戏引擎细节分析,游戏引擎

 看看是不是只有一个像素,而且值都是1.0。

说道这里,拾取需要的纹理渲染核心细节基本说完了。我们再来看看如何读取这个纹理的。

【Overload游戏引擎细节分析】编辑器对象鼠标拾取原理,Overload游戏引擎细节分析,游戏引擎

先获取以下鼠标位置。由于是用imgui绘制的,需要对鼠标的绝对位置变换到图像的相对位置上。 先绑定FrameBuffer,使用glReadPixels读取像素,注意图片格式是RGB,跟初始化FrameBuffer进行的设置一致,这些细节都得注意,玄机很多。最后对像素进行解码操作获取场景物体的id。

读代码就是要将这些细节看明白,才能照猫画虎,用到我们自己的项目中!文章来源地址https://www.toymoban.com/news/detail-765823.html

到了这里,关于【Overload游戏引擎细节分析】编辑器对象鼠标拾取原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【Overload游戏引擎细节分析】鼠标键盘控制摄像机原理

    在上文中分析了摄像机类的实现,在计算投影视图矩阵时需要给摄像机输入其位置及转动四元数。这两个量一般通过鼠标键盘来控制,从而达到控制摄像机的目的。本文分析一下其控制原理。 Overload的摄像机控制实现在类CameraController中,其有三个个方法HandleCameraPanning、Hand

    2024年02月08日
    浏览(12)
  • 【Overload游戏引擎细节分析】UBO与SSBO的封装

    一、OpenGL的UBO   在OpenGL Shader中,如果逻辑比较复杂,使用的uniform变量较多。通常多个着色器使用同一个uniform变量。由于uniform变量的位置是着色器链接时候产生的,因此它在应用程序中获得的索引会有变化。Uniform Buffer Object(UBO)是一种优化uniform变量访问,不同着色器直接

    2024年02月07日
    浏览(13)
  • 【Overload游戏引擎细节分析】视图投影矩阵计算与摄像机

    【Overload游戏引擎细节分析】视图投影矩阵计算与摄像机

    本文只罗列公式,不做具体的推导。 OpenGL本身没有摄像机(Camera)的概念,但我们为了产品上的需求与编程上的方便,一般会抽象一个摄像机组件。摄像机类似于人眼,可以建立一个本地坐标系。相机的位置是坐标原点,摄像机的朝向Forward是摄像机看的方向,再给定向上的Up轴

    2024年02月07日
    浏览(23)
  • 虚幻引擎UE编辑器卡顿问题

    虚幻引擎UE编辑器卡顿问题

    使用虚幻引擎编辑器编辑蓝图页面卡顿,视角无法转动,只能放大缩小,甚至事件图表都无法拖动。 显卡驱动安装与N卡适配的studio版本。 在NVIDIA显卡控制面板3D设置,程序设置找到UE,设置UE OpenGL渲染为GPU。 设置里找到图形设置,添加UE,设置图形首选项为高性能。

    2024年02月15日
    浏览(75)
  • UE5引擎编辑器插件开发归档

    UE5引擎编辑器插件开发归档

    下面是自己在学习编辑器界面开发学习内容的总结,有错误的地方希望大家指出,谢谢~ 学习的教程为:https://www.bilibili.com/video/BV1M84y1K7m4 新添加一个编辑器的插件,修改插件的设定,\\\"Type\\\": 从 Runtime改为Editor,\\\"LoadingPhase\\\": \\\"Default\\\"改为PreDefault 此处参考文档: https://blog.csdn.net/p

    2024年02月08日
    浏览(27)
  • 基于 XAF Blazor 的规则引擎编辑器

    基于 XAF Blazor 的规则引擎编辑器

    开源项目地址:https://gitee.com/lowcodexaf/rules-engine-editor 本项目是基于XAFBlazor的规则引擎编辑器,规则引擎采用的是微软开源的RulesEngine RulesEngine项目地址:https://github.com/microsoft/RulesEngine 在软件开发中,规则引擎扮演着至关重要的角色。它允许开发人员将业务规则从代码中分离

    2024年03月12日
    浏览(12)
  • 基于 XAF Blazor 的规则引擎编辑器 - 实战篇

    基于 XAF Blazor 的规则引擎编辑器 - 实战篇

    示例项目:https://gitee.com/easyxaf/recharge-rules-engine-sample 继上一篇文章对规则引擎编辑器进行了初步介绍之后,本文将通过实际应用案例深入探讨规则引擎编辑器的使用方法。编辑器的操作相对简单,我们将重点放在RulesEngine的讲解上。请注意,本文不是RulesEngine的入门教程,如

    2024年03月15日
    浏览(9)
  • 瓦片地图编辑器——实现卡马克卷轴的编辑,键盘控制游戏移动和鼠标点击游戏编辑通过同一个视口实现。

    瓦片地图编辑器——实现卡马克卷轴的编辑,键盘控制游戏移动和鼠标点击游戏编辑通过同一个视口实现。

      左边是游戏地图编辑区,右边是地图缓冲区,解决了地图缓冲区拖动bug,成功使得缓冲区可以更新。 AWSD进行移动 鼠标左右键分别是绘制/拖动 按F1健导出为mapv3.txt F2清空数组 打印的是游戏数组 easyx开发devcpp 5.11 easyx20220922版本

    2024年01月25日
    浏览(20)
  • 虚幻引擎架构自动化及蓝图编辑器高级开发进修班

    虚幻引擎架构自动化及蓝图编辑器高级开发进修班

    课程名称:虚幻引擎架构自动化及蓝图编辑器高级开发进修班 课程介绍 大家好 我们即将推出一套课程 自动化系统开发。 自动化技术在项目开发的前中后期都大量运用。如何您是一家游戏公司,做的是网络游戏,是不是经常会遇到程序员打包加部署需要半天时间,测试demo功

    2024年04月11日
    浏览(10)
  • 从零开始入门创作游戏——Unity编辑器的使用

    从零开始入门创作游戏——Unity编辑器的使用

    还没找到工作的我继续瞎折腾中,上次搭建环境就花了我3天的时间 从零开始入门创作游戏——Unity3d的环境搭建_默哀d的博客-CSDN博客 接下来是根据油土鳖的视频学习创作的一个小恐龙跳跳跳游戏,直接上手做一次学得更多 https://www.youtube.com/watch?v=UdM9DEys-rI 选择2D核心模板就可

    2024年02月05日
    浏览(16)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包