【Overload游戏引擎分析】编辑器对象鼠标拾取原理
Overload的场景视图区有拾取鼠标功能,单击拾取物体后会显示在Inspector面板中。本文来分析鼠标拾取这个功能背后的原理。
一、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; // 帧缓冲的iduint32_t m_renderTexture = 0; // 纹理附件的iduint32_t m_depthStencilBuffer = 0; // 渲染缓存的id};
先来看其构造函数的实现:
OvRendering::Buffers::Framebuffer::Framebuffer(uint16_t p_width, uint16_t p_height)
{/* Generate OpenGL objects */glGenFramebuffers(1, &m_bufferID); // 生成帧缓冲idglGenTextures(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。下图红框中是这个函数的关键三个步骤:
我们先来看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);// 绘制摄像机,其覆盖区域的像素全部是其idm_context.renderer->DrawModelWithSingleMaterial(model, m_actorPickingMaterial, &modelMatrix);}}
这里有一个特殊函数PreparePickingMaterial,将id的三个字节变成颜色保持到u_Diffuse变量中,这个变量Shader中会使用。核心代码见下图红框,这种编码方式是将信息写入图像常用的方式,可以直接拿来借鉴参考。
要想在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 corelayout (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 coreout 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 。
空文理初始化见以下代码:
看看是不是只有一个像素,而且值都是1.0。
说道这里,拾取需要的纹理渲染核心细节基本说完了。我们再来看看如何读取这个纹理的。
先获取以下鼠标位置。由于是用imgui绘制的,需要对鼠标的绝对位置变换到图像的相对位置上。 先绑定FrameBuffer,使用glReadPixels读取像素,注意图片格式是RGB,跟初始化FrameBuffer进行的设置一致,这些细节都得注意,玄机很多。最后对像素进行解码操作获取场景物体的id。
读代码就是要将这些细节看明白,才能照猫画虎,用到我们自己的项目中!
相关文章:
【Overload游戏引擎分析】编辑器对象鼠标拾取原理
Overload的场景视图区有拾取鼠标功能,单击拾取物体后会显示在Inspector面板中。本文来分析鼠标拾取这个功能背后的原理。 一、OpenGL的FrameBuffer 实现鼠标拾取常用的方式有两种:渲染id到纹理、光线投射求交。Overload使用的是渲染id到纹理,…...
【Spring内容进阶 | 第三篇】AOP进阶内容
前言: 在前面我们已经粗略的介绍了什么是AOP以及各种基础知识点,而本篇我们将聚焦于AOP的细节,详细的讲解一下AOP中的通知类型,通知顺序,切入点表达式以及连接点。通过对AOP的熟练掌握,我们可以快速编写出低…...
华为云ModelArts:引领AI艺术创作的未来,让人人都可以成为“艺术家”!
随着科技的飞速发展,艺术创作逐渐告别传统的画布和画笔,开始走向数字化、智能化的新时代。在这个蓬勃发展的领域中,华为云ModelArts以其强大的功能和出色的性能引领着AI艺术创作的未来。 华为云ModelArts是面向开发者的一站式AI开发平台,为机器学习与深度学习提供海量数据预处…...
Elasticsearch:如何从 Elasticsearch 集群中删除数据节点
Elasticsearch 集群通常包含多个节点,并且可能存在需要从集群中删除节点的情况。 应谨慎执行此过程,以确保数据的完整性和可用性。 在本文中,我们将引导你完成从 Elasticsearch 集群安全删除节点的步骤。 确保集群是绿色的 在尝试从 Elastic…...
长假回归,回顾一下所有的电商API接口
淘宝API接口 item_get 获得淘宝商品详情item_get_pro 获得淘宝商品详情高级版item_review 获得淘宝商品评论 获取测试keyitem_fee 获得淘宝商品快递费用item_password 获得淘口令真实urlitem_list_updown 批量获得淘宝商品上下架时间seller_info 获得淘宝店铺详情item_search…...
认识计算机主板
目录 定义主要部件简单图示 主要功能 定义 计算机主板(Motherboard)是计算机系统中的核心组件之一,也被称为系统板、主板或母板。它是一个电子电路板,用于连接和支持计算机的各种硬件组件,包括中央处理器(…...
PHP乱七八糟面试题
1、请解释PHP中的JWT是什么? JWT(JSON Web Token)是一种用于认证和授权的标准,可以在不同的系统之间安全地传递信息。 在PHP中,可以使用各种JWT库来生成和解析JWT,JWT包含了一些元数据和签名, …...
pom管理规范
0. 引言 在单机架构下,我们只需要将我们的依赖在pom中引入。但是过渡到微服务架构后,会涉及到多模块引用相同的依赖,多模版之间依赖的版本太过分散难以管理的问题。 这就需要我们利用maven中依赖传递的特性,结合dependencyManage…...
AI大模型的安全隐患问题与新兴Anthropic新势力涌动
引言: 无论从社会层面或技术层面,大模型的安全隐患都是一个不容小觑的话题。也正因此,ChatGPT 初兴起时,国内的 To C 大模型产品一时受阻。而尽管 9 月初第一批 8 家大模型通过备案,各家厂商对大模型的安全问题也不敢…...
slamplay:用C++实现的SLAM工具集
0. 项目简介 slamplay 是一个功能强大的工具集合,可用于开始使用 C 来玩和试验 SLAM。这是一项正在进行的工作。它在单个 cmake 框架中安装并提供一些最重要的功能 后端框架(g2o、gtsam、ceres、se-sync 等)、 前端工具(opencv、…...
IPT2602协议-USB 快速充电端口控制器
产品描述: IPT2602是一款USB端口快速充电协议控制芯片。IPT2602智能识别多种快速充电协议,对手机等受电设备进行快速充电。IPT2602根据受电设备发送的电压请求能够精确的调整VBUS输出电压,从而实现快速充电。 IPT2602在调整5V输出电压前会自动…...
Zotero 超好用插件的下载链接及配置方法(PDF-translate/ZotFile/茉莉花/Zotero Scihub)
目录 前言插件安装方法插件一:文献翻译插件(pdf-translate)插件二:文献附件管理(ZotFile)插件三:中文文献插件(茉莉花)插件四:Sci-Hub 自动下载文献ÿ…...
Titus网关中的缓存一致性机制
API网关引入缓存可以在不影响数据一致性的前提下,有效优化接口时延。本文介绍了Netflix在Titus网关上引入缓存的实践,比较了有无缓存对访问时延的影响。原文: Consistent caching mechanism in Titus Gateway 前言 Titus是Netflix的云容器运行时…...
flutter开发实战 - inappwebview设置cookie
flutter开发实战-inappwebview设置cookie 在使用inappwebview时候,需要设置cookie,这里记录一下 一、在initialUserScripts中设置cookie 在inappwebview中有一个initialUserScripts,可以初始化设置cookie等,我们可以通过该属性…...
零基础如何自学网络安全,基于就业前景全方位讲解,包教包会
你是否对网络空间安全充满好奇?想要解开网络世界神秘的面纱?你是否对黑客技术着迷?而找不到合适的学习途径?你是否遭到过各种各样的网络攻击,却因知识的匮乏束手无策? 那么接下来将为你全面介绍,…...
Java项目防止SQL注入的几种方案
目录 一、什么是SQL注入? 二、Java项目防止SQL注入方式 1、PreparedStatement防止SQL注入 2、mybatis中#{}防止SQL注入 3、对请求参数的敏感词汇进行过滤 4、nginx反向代理防止SQL注入 一、什么是SQL注入? SQL注入即是指web应用程序对用户输入数…...
Win11 安装安卓子系统方法教程
WIN11安装安卓子系统 准备工作下载安装安装完成使用adb连接子系统结束 准备工作 开启电脑中的 控制面板>>>>程序和功能>>启用或关闭Windows功能>>>找到“Hyper-V”,把勾都勾上,确定,完成安装,并重启电…...
golang pg 数据库不存在 就创建 --chatPGT
问:linkOrCreateDatabase(addr ), 函数执行 连接 pg数据库,若数据库 不存在就创建 gpt: 要在 Go 中连接到 PostgreSQL 数据库并在数据库不存在时创建数据库,你可以使用 github.com/lib/pq 包以及 database/sql 包。以下是一个示例࿱…...
VUE3照本宣科——eslint与prettier
VUE3照本宣科——eslint与prettier VUE3照本宣科系列导航 前言一、eslint1.配置文件2.配置规则3.忽略错误 二、prettier三、总结 VUE3照本宣科系列导航 1.VUE3照本宣科——认识VUE3 2.VUE3照本宣科——应用实例API与setup 3.VUE3照本宣科——响应式与生命周期钩子 4.VUE3照本宣…...
【谷粒学院】Maven加载问题
问题 maven加载项目时候,默认不会加载src-java文件夹里面xml类型文件的 解决方案 直接赋值xml文件到target目录通过配置实现 (1)在pom.xml文件中配置 <!-- 项目打包时会将java目录中的*.xml文件也进行打包 --> <build><re…...
PostgreSQL数据库中实现字段递增
在 PostgreSQL 中,可以使用序列(sequence)来实现字段的递增。序列是一种特殊的对象,用于生成唯一的递增数字。 首先,您需要创建一个序列对象。可以使用以下命令创建一个名为 "my_sequence" 的序列ÿ…...
深度学习——深度学习计算二
深度学习——深度学习计算二 文章目录 前言三、延后初始化四、自定义层4.1. 不带参数的层4.2. 带参数的层 五、读写文件5.1. 加载和保存张量5.2. 加载和保存模型参数 六、GPU6.1. 计算设备6.2. 张量与GPU6.3. 神经网络与GPU 总结 前言 延续上一章的学习,本章继续记…...
HarmonyOS/OpenHarmony原生应用-ArkTS万能卡片组件Badge
可以附加在单个组件上用于信息标记的容器组件。该组件从API Version 7开始支持。 支持单个子组件。子组件类型:系统组件和自定义组件,支持渲染控制类型(if/else、ForEach和LazyForEach)。 一、接口 方法1: Badge(value…...
在Flink中集成和使用Hudi
本文介绍在Flink 中集成和使用Hudi。介绍Flink如何将Streaming引入Hudi。在Hudi上使用Flink,并学习Flink读写Hudi的不同模式: Flink SQL客户端写入:Flink SQL客户端写入(读取)Hudi。 配置:对于全局配置,通过$FLINK_HOME/conf/FLINK-conf.yaml进行设置。对于每个作业配置…...
docker搭建Jenkins及基本使用
1. 搭建 查询镜像 docker search jenkins下载镜像 docker pull jenkins/jenkins启动容器 #创建文件夹 mkdir -p /home/jenkins_home #权限 chmod 777 /home/jenkins_home #启动Jenkins docker run -d -uroot -p 9095:8080 -p 50000:50000 --name jenkins -v /home/jenkins_home…...
CSS初体验
目录 一、CSS初体验 二、CSS引入方式 三、选择器 3.1 标签选择器 3.2 类选择器 3.3 id选择器 3.4 通配符选择器 四、盒子尺寸和背景色 五、文字控制属性 5.1 字体大小 5.2 字体样式(是否倾斜) 5.3 行高 5.3.1 单行文字垂直居中 5.4 字体族 5.5 font复合属性 5.…...
python性能分析
基于cProfile统计函数级的时延,生成排序列表、火焰图,可以快速定位python代码的耗时瓶颈。参考如下博文结合实操,总结为三步: 使用 cProfile 和火焰图调优 Python 程序性能 - 知乎本来想坐下来写篇 2018 年的总结,仔细…...
苹果手机怎么备份所有数据?2023年iPhone 15数据备份常用的3种方法!
当苹果手机需要进行刷机、恢复出厂设置、降级iOS系统等操作时,我们需要将自己的iPhone数据提前进行备份。 特别是在苹果发布新iOS系统时,总有一些小伙伴因为升降级系统,而导致了重要数据的丢失。 iPhone中储存着重要的照片、通讯录、文件等数…...
【RV1103】如何新增一个新板级配置
文章目录 新建一个板级配置文件 新建一个板级配置文件 我的目标 通过./build.sh lunch 来选择我的板子配置。 在目录sdk/project/cfg目录下新建一个xxxx.mk文件,文件名字格式如下: BoardConfig-"启动介质"-"电源方案"-"硬件…...
ThreeJS-3D教学五-材质
我们在ThreeJS-3D教学二:基础形状展示中有简单介绍过一些常用的材质,这次我们举例来具体看下效果: 代码是这样的: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">&…...
大连手机网站制作/友情链接实例
目标:提供一个接口给第三方服务使用,但因为第三方服务没有用户账号这东西,所以需要进行安全访问控制(Java实现) 例如:http://XXXXX?nonceXXXX?×tampXXXX&signatureXXXX (请求参数在POST里面…...
厦门市建设区网站/seo外包 靠谱
客户端是TortoiseSVN的话,在该文件或文件夹上点右键,选择TortoiseSVN——revert转载于:https://www.cnblogs.com/yingjie13/p/3731479.html...
网站怎么做移动图片/宁德市安全教育平台
python的数据血缘工具 sqllineage 根据文件查看数据血缘图 注意 这里-H 必须接 hostname ,接ip会报错 OSError: [Errno 99] Cannot assign requested address sqllineage -g -H sbider-dev-01 -p 60000 -f ./ppn2/update-data/asset_sn_day_partition/asset_sn_day_partitio…...
查看网站主机/常见的网络推广方法
android-gif-drawable(https://github.com/koral--/android-gif-drawable/releases)开源项目---是一个蛮不错的android gif显示实现.本文在android-gif-drawable基础上介绍怎样实现TextView、EditText上展示Gif动态图。 网上有蛮多介绍这个框架使用的文章,比方http:…...
h5页面有哪些/湘潭网站seo
Python 用下划线作为变量前缀和后缀指定特殊变量。 _xxx 不能用from moduleimport *导入 __xxx__ 系统定义名字(类似于java 的default修饰) __xxx 类中的私有变量名(类似于java的private修饰) 核心风格:避免用下划线作为变量名的开…...
制作h5网站开发/郑州网站关键词优化公司哪家好
最小生成树是数据结构中图的一种重要应用,它的要求是从一个带权无向完全图中选择n-1条边并使这个图仍然连通(也即得到了一棵生成树),同时还要考虑使树的权最小。 prim算法就是一种最小生成树算法。 普里姆算法的基本思想ÿ…...