LearnOpenGL-笔记-其十二
今天我们来将LearnOpenGL的高级光照部分彻底完结:
Bloom
泛光是一个非常常见的用于改善图像质量的手段,其主要做法就是将某个高亮度区域的亮度向四周发善以实现该区域更亮的视觉效果(因为显示器的亮度范围有限,需要通过泛光来体现出更亮的地方更亮)
明亮的光源和区域经常很难向观察者表达出来,因为显示器的亮度范围是有限的。一种在显示器上区分明亮光源的方式是使它们发出光芒,光芒从光源向四周发散。这有效地给观众一种这些光源或明亮的区域非常亮的错觉。(译注:这个问题的提出简单来说是为了解决这样的问题:例如有一张在阳光下的白纸,白纸在显示器上显示出是出白色,而前方的太阳也是纯白色的,所以基本上白纸和太阳就是一样的了,给太阳加一个光晕,这样太阳看起来似乎就比白纸更亮了)
这种光流,或发光效果,是通过一种叫做泛光(Bloom)的后期处理效果来实现的。泛光使场景中所有明亮的区域都具有类似发光的效果。
光看这些介绍你会联想起之前我们知道的HDR(高动态范围),事实上二者确实有着密不可分的联系。
简单地总结一下就是,我们本身的显示器的亮度范围是0到1的话,HDR允许我们暂时的突破亮度的上限,我们通过HDR(更准确地说是浮点数类型的帧缓冲)将亮度超过阈值的部分提取出来后对其进行泛光处理(本质上就是模糊以及扩散化),最后再执行诸如色调映射和曝光调整的操作即可。
那现在我们先来实现提取超过阈值的亮度这一操作:
vec3 result = ambient + lighting;// 计算亮度值(使用标准的亮度转换权重)float brightness = dot(result, vec3(0.2126, 0.7152, 0.0722));// 如果亮度超过阈值,输出到BrightColorif(brightness > 1.0)BrightColor = vec4(result, 1.0);elseBrightColor = vec4(0.0, 0.0, 0.0, 1.0);// 输出普通渲染结果FragColor = vec4(result, 1.0);
这个result是我们最终着色器接收到的光照强度,我们根据亮度转换权重得到亮度之后再判断是否超过阈值,超过的话我们记录下来,否则我们输出黑色。
这里补充一下这个亮度转换权重的概念,我也记不得之前的笔记中有没有提到过了:
现在我们获取了图像中所有亮度超过阈值的部分,接下来我们要进行高斯模糊,但是首先的问题是:为什么是高斯模糊?
这就是为什么在Bloom效果中特别适合使用高斯模糊。它不仅能够产生更真实的光线扩散效果,而且在性能和效果之间取得了很好的平衡。通过调整高斯模糊的参数,我们可以控制Bloom效果的强度和范围,从而创造出各种不同的视觉效果。
uniform float weight[5] = float[] (0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162);void main()
{ vec2 tex_offset = 1.0 / textureSize(image, 0); // gets size of single texelvec3 result = texture(image, TexCoords).rgb * weight[0];if(horizontal){for(int i = 1; i < 5; ++i){result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];}}else{for(int i = 1; i < 5; ++i){result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i];result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i];}}FragColor = vec4(result, 1.0);
}
可以看到首先定义了一组五个权重(归一化处理,总和为一),分别代表当前片元右边、左边、上边、下边以及本身保留的亮度的权重,对于每个片元分别取上下或左右共九个片元(包含自己,其实就是一边各四个)的亮度和自己原来的亮度进行一个混合即可,然后先上下后左右混合两次。
高斯模糊后,我们最后把模糊处理过的超过亮度阈值的部分加到原来的图像中并通过色调映射和曝光处理即可:
uniform sampler2D scene; // 原始场景
uniform sampler2D bloomBlur; // 经过高斯模糊的亮部
uniform bool bloom; // 是否启用泛光效果
uniform float exposure; // 曝光度
...
vec3 hdrColor = texture(scene, TexCoords).rgb; // 获取原始场景颜色
vec3 bloomColor = texture(bloomBlur, TexCoords).rgb; // 获取模糊后的亮部颜色
...
if(bloom)hdrColor += bloomColor; // 加法混合
...
vec3 result = vec3(1.0) - exp(-hdrColor * exposure);//色调映射
...
result = pow(result, vec3(1.0 / gamma));//gamma校正
效果如图:
Deferred Shading
延迟着色,当然在Unity里写Shader的时候我们也叫他延迟渲染,终究还是到了这一步。
我们现在一直使用的光照方式叫做正向渲染(Forward Rendering)或者正向着色法(Forward Shading),它是我们渲染物体的一种非常直接的方式,在场景中我们根据所有光源照亮一个物体,之后再渲染下一个物体,以此类推。它非常容易理解,也很容易实现,但是同时它对程序性能的影响也很大,因为对于每一个需要渲染的物体,程序都要对每一个光源每一个需要渲染的片段进行迭代,这是非常多的!因为大部分片段着色器的输出都会被之后的输出覆盖,正向渲染还会在场景中因为高深的复杂度(多个物体重合在一个像素上)浪费大量的片段着色器运行时间。
延迟着色法(Deferred Shading),或者说是延迟渲染(Deferred Rendering),为了解决上述问题而诞生了,它大幅度地改变了我们渲染物体的方式。这给我们优化拥有大量光源的场景提供了很多的选择,因为它能够在渲染上百甚至上千光源的同时还能够保持能让人接受的帧率。
比较直白地说,正向渲染就是我们有一个物体就去渲染一个物体,哪怕现在有一千万个光源我们也先把这一千万个光源在这一个物体上的光照效果全部计算完再进行下一个物体的光照效果计算。从我这略显夸张的措辞也可以看出,当光源很多时这个渲染方式效率非常低下,所以针对多光源场景,我们需要换一种思路去渲染。
// 1. 几何处理阶段(Geometry Pass)
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);// 渲染场景到G-Buffer// 存储位置、法线、颜色等信息
glBindFramebuffer(GL_FRAMEBUFFER, 0);// 2. 光照处理阶段(Lighting Pass)
// 使用G-Buffer中的信息计算光照
shaderLightingPass.use();
// 绑定G-Buffer纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gPosition);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
我们可以把当前场景中的所有物体中涉及到光照计算的数据先缓冲起来,等到后续需要计算的时候我们再统一将所有缓存起来的数据进行计算,就好比去餐馆里面点菜,我们先把所有客人的需求都收集好再拿给厨师来做菜,而不是先收集一个客人的需求后马上拿给厨师做,省去了大量的中间过程开销。而这个缓冲我们一般叫:G-Buffer。
// 创建G-Buffer帧缓冲
unsigned int gBuffer;
glGenFramebuffers(1, &gBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);// 创建三个颜色附件
// 1. 位置信息
glGenTextures(1, &gPosition);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);// 2. 法线信息
glGenTextures(1, &gNormal);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);// 3. 颜色和镜面反射信息
glGenTextures(1, &gAlbedoSpec);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
虽然延迟渲染在针对多光源场景下有着奇效,但是并非完美:事实上,倒不如说延迟渲染有着非常大的局限性,那就是其 单层几何信息存储机制(即G-Buffer仅记录最靠近相机的片元数据),这直接导致其在半透明渲染和深度相关技术上的不足。
好在我们的LearnOpenGL中给出了结合正向渲染和延迟渲染的解决方法:
// 1. 先进行延迟渲染
// ... 延迟渲染代码 ...// 2. 复制深度缓冲
glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST);// 3. 使用正向渲染渲染特殊效果(如透明物体)
shaderForward.use();
// ... 渲染透明物体 ...
整个执行流程可以理解为:我们先进行延迟渲染,然后我们将G-Buffer的缓冲内容复制到正常渲染流程中涉及的缓冲如深度缓冲(这个其实最重要因为延迟渲染主要缺少的就是深度信息,有了深度信息就能有后续的其他几何信息)、颜色缓冲等,然后针对延迟渲染无法渲染的对象去渲染(如透明对象)。
效果如图:
然后在LearnOpenGL中,我们还学习到了一个新的内容:光体积(Light Volume)。
通常情况下,当我们渲染一个复杂光照场景下的片段着色器时,我们会计算场景中每一个光源的贡献,不管它们离这个片段有多远。很大一部分的光源根本就不会到达这个片段,所以为什么我们还要浪费这么多光照运算呢?
隐藏在光体积背后的想法就是计算光源的半径,或是体积,也就是光能够到达片段的范围。由于大部分光源都使用了某种形式的衰减(Attenuation),我们可以用它来计算光源能够到达的最大路程,或者说是半径。我们接下来只需要对那些在一个或多个光体积内的片段进行繁重的光照运算就行了。这可以给我们省下来很可观的计算量,因为我们现在只在需要的情况下计算光照。
总结来说就是,我们先去计算每一个光源能辐射到的半径,然后延迟渲染时不是要挨个光源计算在G-BUFFER中的几何信息的光照效果吗,我们只去计算在光照半径内的几何信息的光照效果即可。更具体的内容大家可以自行下去了解,主要难点就是如何去计算光源的半径。
SSAO
SSAO(Screen-Space Ambient Occlusion),中文名是屏幕空间环境光遮蔽,是一种对环境光照的模拟。之前的学习中我们都知道环境光是最难被模拟的一种光照,因为所有其他物体无论反射多少次的光照都可以被归类为环境光照,且在现实生活中光照是可以无限弹射的,而我们的计算机的计算能力有限。因此在我们之前学习的光照模型,我们都是非常粗暴地把环境光照直接用一个常数来表示,而现在如果我们想让视觉效果更上一层楼的话,去对环境光照进行更高层次的模拟是一个不错的方向。
在现实中,光线会以任意方向散射,它的强度是会一直改变的,所以间接被照到的那部分场景也应该有变化的强度,而不是一成不变的环境光。其中一种间接光照的模拟叫做环境光遮蔽(Ambient Occlusion),它的原理是通过将褶皱、孔洞和非常靠近的墙面变暗的方法近似模拟出间接光照。这些区域很大程度上是被周围的几何体遮蔽的,光线会很难流失,所以这些地方看起来会更暗一些。站起来看一看你房间的拐角或者是褶皱,是不是这些地方会看起来有一点暗?
环境光遮蔽这一技术会带来很大的性能开销,因为它还需要考虑周围的几何体。我们可以对空间中每一点发射大量光线来确定其遮蔽量,但是这在实时运算中会很快变成大问题。在2007年,Crytek公司发布了一款叫做屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)的技术,并用在了他们的看家作孤岛危机上。这一技术使用了屏幕空间场景的深度而不是真实的几何体数据来确定遮蔽量。这一做法相对于真正的环境光遮蔽不但速度快,而且还能获得很好的效果,使得它成为近似实时环境光遮蔽的标准。
SSAO背后的原理很简单:对于铺屏四边形(Screen-filled Quad)上的每一个片段,我们都会根据周边深度值计算一个遮蔽因子(Occlusion Factor)。这个遮蔽因子之后会被用来减少或者抵消片段的环境光照分量。遮蔽因子是通过采集片段周围球型核心(Kernel)的多个深度样本,并和当前片段深度值对比而得到的。高于片段深度值样本的个数就是我们想要的遮蔽因子。
看起来似乎非常复杂,但是其实本质上就是:当我们开启SSAO功能之后,我们的屏幕上会多一层四边形,也就是铺屏四边形,这个四边形会检查每一个在屏幕里的像素:准确地说是以每个像素为球心扩展出一个球,然后我们在这个球内容去采样后检查采样点和像素的深度进行比较,比如这个球内采样了一百个点,六十个的深度值小于该像素深度而四十个大于,那么这个像素的遮蔽因子就是0.4,也就是他只会收获到正常亮度的百分之六十。
最开始的时候确实是一个以像素为球心的球进行采样,但是大家发现这样会有一些问题:
说白了就是以球体来采样的话,一定会采样到本来深度就小于当前像素的区域,那样即使实际上没有几何体遮蔽的部分也会亮度降低,所以采取法向半球来采样会更合理一些。
这里就需要提一下关于随机性的重要性:
现在我们来看具体的实现:
// 1. 创建G-Buffer
unsigned int gBuffer;
glGenFramebuffers(1, &gBuffer);
// 存储位置、法线和颜色信息// 2. 创建SSAO帧缓冲
unsigned int ssaoFBO, ssaoBlurFBO;
glGenFramebuffers(1, &ssaoFBO);
glGenFramebuffers(1, &ssaoBlurFBO);
SSAO可以基于延迟渲染实现。
std::vector<glm::vec3> ssaoKernel;
for (unsigned int i = 0; i < 64; ++i)
{glm::vec3 sample(randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, randomFloats(generator));sample = glm::normalize(sample);sample *= randomFloats(generator);float scale = float(i) / 64.0f;scale = ourLerp(0.1f, 1.0f, scale * scale);sample *= scale;ssaoKernel.push_back(sample);
}
根据像素位置和法线方向生成法线半球。
std::vector<glm::vec3> ssaoNoise;
for (unsigned int i = 0; i < 16; i++)
{glm::vec3 noise(randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, 0.0f);ssaoNoise.push_back(noise);
}
生成噪声纹理以引入随机性,从而达到消除SSAO中固定采样模式的问题。
void main()
{// 获取输入数据vec3 fragPos = texture(gPosition, TexCoords).xyz;vec3 normal = normalize(texture(gNormal, TexCoords).rgb);vec3 randomVec = normalize(texture(texNoise, TexCoords * noiseScale).xyz);// 创建TBN矩阵vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));vec3 bitangent = cross(normal, tangent);mat3 TBN = mat3(tangent, bitangent, normal);// 计算遮蔽float occlusion = 0.0;for(int i = 0; i < kernelSize; ++i){// 获取采样位置vec3 samplePos = TBN * samples[i];samplePos = fragPos + samplePos * radius;// 投影采样位置vec4 offset = vec4(samplePos, 1.0);offset = projection * offset;offset.xyz /= offset.w;offset.xyz = offset.xyz * 0.5 + 0.5;// 获取采样深度float sampleDepth = texture(gPosition, offset.xy).z;// 范围检查和累积float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth));occlusion += (sampleDepth >= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck;}// 计算最终遮蔽值occlusion = 1.0 - (occlusion / kernelSize);FragColor = occlusion;
}
整个SSAO计算流程,效果如下:
可以看到环境光的效果不再像之前一样哪里都是一样亮了,更加真实。
相关文章:

LearnOpenGL-笔记-其十二
今天我们来将LearnOpenGL的高级光照部分彻底完结: Bloom 泛光是一个非常常见的用于改善图像质量的手段,其主要做法就是将某个高亮度区域的亮度向四周发善以实现该区域更亮的视觉效果(因为显示器的亮度范围有限,需要通过泛光来体…...
【C++】C++面向对象设计的核心思想之一: 接口抽象、解耦和可扩展性
1. 什么是虚函数? 虚函数(virtual)是C里实现“多态”的关键机制。 在基类中声明虚函数,在子类中可以**覆盖(override)**它们。通过基类指针/引用操作时,自动调用实际对象(子类&…...

Namespace 命名空间的使用
名字空间:划分更多的逻辑空间,有效避免名字冲突的问题 1.什么是命名空间 名字命名空间 namespace 名字空间名 {...} // 名字空间 n1 域 namespace n1 {// 全局变量int g_money 0;void save(int money){g_money money;}void pay(int money){g_money - m…...

mac 下安装Rust Toolchain(Nightly)
你可以用 Homebrew 安装 rustup,这是推荐的管理 Rust toolchain的 brew install rustup-init安装 Rust(包含 rustup) rustup-init安装过程中会让你选择安装那个,直接回车选择默认的即可 安装完成后,cargo, rustc, r…...
PHP中文网文章内容提取免费API接口教程
接口简介: 提取PHP中文网指定文章内容。本接口仅做内容提取,未经作者授权请勿转载。 请求地址: https://cn.apihz.cn/api/caiji/phpzww.php 请求方式: POST或GET。 请求参数: 【名称】【参数】【必填】【说明】 【用…...
【Java笔记】Spring IoC DI
目录 Spring IoC & DI1. IoC1.1 Bean的存储1.1.1 类注解1.1.2 方法注解 Bean1.1.3 重命名1.1.4 Spring扫描路径 2. DI Spring IoC & DI Spring两个核心思想:IoC & AOP Spring相当于一个容器,IoC就是把对象存放在Spring容器中,让…...

学习STC51单片机22(芯片为STC89C52RCRC)
记住这个AT指令千万不要去脑子记,要用手册查 每日一言 努力不是为了感动谁,而是为了不辜负自己的野心。 硬件:ESP8266 wife模块 蓝牙,ESP-01s,Zigbee,NB-lot等通信模块都是基于AT指令的设计 老样子 我们用…...
ubuntu20.04.5--arm64版上使用node集成java
ubuntu20.04.5arm上使用node集成java #ssh,可选 sudo apt update sudo apt install openssh-server sudo systemctl status ssh sudo systemctl enable ssh sudo systemctl enable --now ssh #防火墙相关,可选 sudo ufw allow ssh sudo ufw allow 22…...

Linux --UDP套接字实现简单的网络聊天室
一、Server端的实现 1.1、服务端的初始化 ①、创建套接字: 创建套接字接口: #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol); //1. 这是一个创建套接字的接…...
嵌入式学习笔记 - keil安装目录下的头文件自动包含问题
Keil MDK/MDK-ARM(ARM编译器)默认情况下会自动包含其安装目录下的标准头文件路径(如CMSIS库、设备头文件等)。具体机制如下: 默认自动包含: 新建工程或使用设备数据库选择芯片型号后,Keil会…...

word批量导出visio图
具体步骤 修改word格式打开VBA窗口插入代码运行代码 修改word格式 将word文档修改为docm格式 打开VBA窗口 打开开发工具VisualBasic项,如果没有右键在自定义功能区添加 插入代码 插入 -> 模块,代码如下: Sub ExportAllVisioDiagrams()D…...
把数据库做得能扩展:Aurora DSQL 的故事
把数据库做得能扩展:Aurora DSQL 的故事 我们在 AWS re:Invent 上发布了 Aurora DSQL,这是一个全新方式构建关系型数据库的尝试。它不是单纯的技术升级,而是一段从零开始、反复试错、不断学习的工程旅程。 我们为什么做 Aurora DSQL&#x…...
全面解析:npm 命令、package.json 结构与 Vite 详解
全面解析:npm 命令、package.json 结构与 Vite 详解 一、npm run dev 和 npm run build 命令解析 1. npm run dev 作用:启动开发服务器,用于本地开发原理: 启动 Vite 开发服务器提供实时热更新(HMR)功能…...

【本地部署】 Deepseek+Dify创建工作流
文章目录 DeepseekDify 简介流程1、下载Docker2、Dify下载3、使用浏览器打开 Deepseek Deepseek 是一款功能强大的 AI 语言模型工具,具备出色的理解与生成能力。它可以处理各种自然语言任务,无论是文本创作、问答,还是数据分析与解释&#x…...
Rust 配置解析`serde` + `toml`
🦀 Rust 配置解析:彻底搞懂 TOML、Option、Vec、derive 背后的原理 📌 目录 什么是 TOML 文件?为什么要用 serde toml crate?结构体上 #[derive(...)] 是什么?配置中数组 [] 和表数组 [[...]] 怎么用&…...
linux进程用户态内存泄露问题从进程角度跟踪举例
我们习惯性的会看下那个进程在泄漏内存,我这里使用一个test_malloc的测试进程,该进程每2秒钟会分配一个10000字节的空间,并作简单赋值(注意:如果仅malloc而不使用,编译器会优化,实际测试时将看不…...

数据结构-图的应用,实现环形校验和拓扑排序
文章目录 一、如何理解“图”?1.什么是图?2.无向图和有向图3.无权图和有权图 二、JGraphT-图论数据结构和算法的 Java 库1.引入Maven依赖2.环形校验2.1 什么是循环依赖 ?2.2 单元测试代码2.3 情况1:自己依赖自己2.4 情况2…...
交换机 路由器
在计算机网络中,S 和 R 常常分别代表以下设备: S:Switch(交换机)R:Router(路由器) 简要说明: Switch(交换机,S) 交换机工作在数据链…...

某乎x-zse-96 破解(补环境版本)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、总体概述二、请求分析分析请求流程三、逆向分析总结一、总体概述 本文主要实现某乎x-zse-96 破解(补环境版本),相关的链接: https://www.zhihu.com/search?type=content&q=%25E7%258…...

VSCode+Cline 安装配置及使用说明
安装配置 打开VSCode,点击左侧Extension图标,在弹出页面中,检索Cline,选择Cline进行安装。 安装完毕,在左侧会出现一个图标,点击图标 选择【Use your own API key】,在出来的界面中选择大模型&…...
Java中Redis面试题集锦(含过期策略详解)
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Java中Redis面试题集锦(含过期策…...

Maven 安装与配置指南(适用于 Windows、Linux 和 macOS)
Apache Maven 是一款广泛应用于 Java 项目的项目管理和构建工具。 本文提供在 Windows、Linux 和 macOS 系统上安装与配置 Maven 的详细步骤,旨在帮助开发者快速搭建高效的构建环境。 一、前置条件:安装 Java Development Kit (JDK) Maven 依赖于 Java …...

android 媒体框架之MediaCodec
一、MediaCodec 整体架构与设计思想 MediaCodec 是 Android 底层多媒体框架的核心组件,负责高效处理音视频编解码任务。其架构采用 生产者-消费者模型,通过双缓冲区队列(输入/输出)实现异步数据处理: 输入缓冲区队列…...

堆与堆排序及 Top-K 问题解析:从原理到实践
一、堆的本质与核心特性 堆是一种基于完全二叉树的数据结构,其核心特性为父节点与子节点的数值关系,分为大堆和小堆两类: 大堆:每个父节点的值均大于或等于其子节点的值,堆顶元素为最大值。如: 小堆:每个…...
Linux中检查当前用户是不是root
Linux中检查当前用户是不是root 检查当前用户是否为root用户。如果是root用户,输出“当前用户是root”;否则,输出“当前用户不是root”。 创建一个 aaa.sh脚本文件 写入如下内容 #!/bin/bash# 检查当前用户的UID是否为0(root用…...

软件锁:守护隐私,安心无忧
数字化时代,手机已成为我们生活中不可或缺的一部分,它不仅存储着我们的个人信息、照片、聊天记录等重要数据,还承载着我们的社交、娱乐和工作等多种功能。然而,这也意味着手机上的隐私信息面临着诸多泄露风险。无论是家人、朋友还…...

无人机桥梁3D建模、巡检、检测的航线规划
无人机桥梁3D建模、巡检、检测的航线规划 无人机在3D建模、巡检和检测任务中的航线规划存在显著差异,主要体现在飞行高度、航线模式、精度要求和传感器配置等方面。以下是三者的详细对比分析: 1. 核心目标差异 任务类型主要目标典型应用场景3D建模 生成…...
项目:贪吃蛇实现
头文件 snake.h #include<stdio.h> #include<stdlib.h> #include<windows.h> #include<locale.h> #include<stdbool.h> #include<time.h>#define POS_X 24#define POS_Y 5 #define BODY L● #define FOOD L★ #define KEY_PRESS(VK) ((…...

【Java基础05】面向对象01
文章目录 1. 设计对象并使用1.1 类与对象1.2 封装1.2.1 private关键字1.2.2 this关键字成员变量和局部变量的区别 1.2.3 构造方法1.2.4 标准JavaBean类 1.3 对象内存图 本文部分参考这篇博客 1. 设计对象并使用 1.1 类与对象 public class 类名{1、成员变量(代表属性,一般是名…...

设计模式:观察者模式 - 实战
一、观察者模式场景 1.1 什么是观察者模式? 观察者模式(Observer Pattern)观察者模式是一种行为型设计模式,用于定义一种一对多的依赖关系,当对象的状态发生变化时,所有依赖于它的对象都会自动收到通知并更…...