OpenGL ES 帧缓冲对象介绍和使用示例

一、介绍

1. 帧缓冲对象

默认情况下,OpenGL渲染的目标是屏幕,但如果你不想直接渲染到屏幕上,还需要对渲染结果做某些后期处理、渲染到纹理、阴影映射等操作,便可以使用帧缓冲对象,实现离屏渲染。

帧缓冲对象(Frame Buffer Object,FBO)是一个概念容器,它可以包含颜色缓冲区、深度缓冲区、模板缓冲区等,形成一个完整的渲染目标。

通过使用帧缓冲对象,可以实现离屏渲染、多重渲染目标(MRT)等高级渲染技术,而不必直接渲染到屏幕。

2. 相关概念

以下是帧缓冲对象的一些基本概念:

  • 颜色缓冲区(Color Buffer):存储渲染的颜色信息。一个帧缓冲对象可以包含多个颜色缓冲区,用于实现多重渲染目标。
  • 深度缓冲区(Depth Buffer): 存储每个像素的深度信息,用于实现深度测试,确保正确的渲染顺序。
  • 模板缓冲区(Stencil Buffer): 存储模板测试的结果,用于实现各种复杂的渲染效果。
  • 渲染缓冲区(Renderbuffer): 实际上是OpenGL ES 3.0中用于存储颜色、深度和模板信息的内存对象。它可以附加到帧缓冲对象的不同附着点。

3. 渲染缓冲对象

渲染缓冲对象(Render Buffer Object,RBO)是一种特殊类型的对象,用于存储图形渲染过程中的像素数据,优点是它的存储格式是优化过的,性能更好,适合渲染而不是读取,所以不可以直接读取。

用途一般是作为帧缓冲对象的附着点,提供一个高效的存储区域,用于存储渲染结果。 渲染缓冲对象通常用于只写入不读取的场景,而纹理可以作为可读取的附着点。于是渲染缓冲对象经常作为深度和模板附件来使用,因为大多数时候并不需要从深度和模板缓冲中读取数据。

4. 关系图


图源:OPENGL ES 3.0 编程指南 原书第2版

二、使用

帧缓冲对象的使用通常包括以下步骤:

  1. 创建帧缓冲对象: 使用 glGenFramebuffers 函数创建一个帧缓冲对象,其值是一个非零的无符号整数。
  2. 绑定帧缓冲对象: 使用 glBindFramebuffer 函数将帧缓冲对象绑定为当前渲染目标。
  3. 创建和附着附加点: 创建颜色缓冲区和深度缓冲区,然后将它们附着到帧缓冲对象的附着点上。
  4. 渲染: 执行渲染操作,渲染结果将存储在帧缓冲对象中。
  5. 解绑帧缓冲对象: 使用 glBindFramebuffer(GL_FRAMEBUFFER, 0) 将帧缓冲对象解绑,将渲染目标切换回屏幕,数字0即表示屏幕。
  6. 删除帧缓冲对象: 使用 glDeleteFramebuffers 函数创建一个帧缓冲对象。

代码示例:

#include <GLES3/gl3.h>

// 定义帧缓冲对象和渲染缓冲对象的标识符
GLuint framebuffer;
GLuint renderbuffer;

// 定义纹理对象的标识符
GLuint texture;

// 图像的宽度和高度
int screenWidth = 1080;
int screenHeight = 1440;

void init()
{
    // 创建并绑定帧缓冲对象
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    // 创建并绑定渲染缓冲对象用于深度和模板信息
    glGenRenderbuffers(1, &renderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);

    // 分配存储空间
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenWidth, screenHeight);

    // 将渲染缓冲对象附着到帧缓冲的深度和模板附着点
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);

    // 创建并绑定纹理对象用于颜色附着点
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    // 分配存储空间
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screenWidth, screenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // 将纹理对象附着到帧缓冲的颜色附着点
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

    // 检查帧缓冲完整性
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        // 处理错误
        // ...
    }

    // 解绑帧缓冲对象
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void render()
{
    // 绑定帧缓冲对象
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    // 渲染操作,将渲染结果存储在帧缓冲对象中

    // 解绑帧缓冲对象
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // 使用帧缓冲对象的渲染结果进行后期处理或直接显示在屏幕上
}

三、应用实例

1. 保存纹理数据

利用FBO绑定纹理到颜色附着上,便可以方便的把纹理数据读取出来了。

代码示例:

void textureToBuffer(int textureId, int x, int y, int width, int height, unsigned char *buffer) {
    // 创建FBO
    GLuint fbo[1];
    glGenFramebuffers(1, fbo);
    // 绑定
    glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0);
    // 读取数据
    glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);  // 这里的format和type需要和纹理一致
    // 解绑和释放
    glBindFramebuffer(GL_FRAMEBUFFER, 0);  // unbind
    glDeleteFramebuffers(1, fbo);
}

2. 渲染结果还需后处理

当需要多次渲染(渲染结果还需后处理)才能完成的任务时,此时无需次次都渲染到屏幕上,而是可以选择进行一次或多次离屏渲染,最终再渲染到屏幕上。

下面看一个图像虚化的例子来感受一下。(关于虚化方法这里不做过多解释,可以查看其他文章,如:opencv图像卷积操作和常用的图像滤波函数)

方案一:单次滤波完成虚化

使用 BoxFilter 的 fragment shader 文件如下:

#version 300 es
precision mediump float;
layout (location = 2) uniform sampler2D s_texture;
layout (location = 3) uniform int u_kernelSize;

in vec2 v_texCoor;
out vec4 fragColor

vec4 boxFilter() {
    if (u_kernelSize <= 1) {
        return texture(s_texture, v_texCoor);
    }
    ivec2 texSize = textureSize(s_texture, 0);
    float xStep = 1.0 / float(texSize.x);
    float yStep = 1.0 / float(texSize.y);
    vec4 sum = vec4(0.0);
    int num = 0;
    // 复杂度:N^2
    for (int i = -u_kernelSize; i <= u_kernelSize; i++) {
        for (int j = -u_kernelSize; j <= u_kernelSize; j++) {
            float x = v_texCoor.x + float(i) * xStep;
            float y = v_texCoor.y + float(j) * yStep;
            sum += texture(s_texture, vec2(x, y));
            num++;
        }
    }
    return sum / float(num);
}

void main() {
    fragColor = boxFilter();
}

我们通过对周围像素采样,并求平均值的方式获取当前点虚化后的值。由于实现上是嵌套的两层for循环,随着 u_kernelSize 的增大,其耗时将急剧增大。

方案二:横纵方向两次滤波完成虚化

之前我们是对 N*N 的区域进行采样并求平均值,其时间复杂度为 N^2。但如果我们先对横向进行一次采样求平均值,再对输出的结再进行一次纵向的采样求平均值,这样只需要 N*2 即可以达到相同的效果。在高通6225机器上测试 KernelSize = 7 时,耗时从44ms降低到9ms。

优化后的 fragment shader 文件如下:

#version 300 es
precision mediump float;
layout (location = 2) uniform sampler2D s_texture;
layout (location = 3) uniform int u_kernelSize;
layout (location = 4) uniform int u_boxFilterType;

in vec2 v_texCoor;
out vec4 fragColor

vec4 boxFilterHorizontal() {
    if (u_kernelSize <= 1) {
        return texture(s_texture, v_texCoor);
    }
    ivec2 texSize = textureSize(s_texture, 0);
    float xStep = 1.0 / float(texSize.x);
    vec4 sum = vec4(0.0);
    int num = 0;
    // 复杂度:N
    for (int i = -u_kernelSize; i <= u_kernelSize; i++) {
        float x = v_texCoor.x + float(i) * xStep;
        sum += texture(s_texture, vec2(x, v_texCoor.y));
        num++;
    }
    return sum / float(num);
}

vec4 boxFilterVertical() {
    if (u_kernelSize <= 1) {
        return texture(s_texture, v_texCoor);
    }
    ivec2 texSize = textureSize(s_texture, 0);
    float yStep = 1.0 / float(texSize.y);
    vec4 sum = vec4(0.0);
    int num = 0;
    // 复杂度:N
    for (int i = -u_kernelSize; i <= u_kernelSize; i++) {
        float y = v_texCoor.y + float(i) * yStep;
        if (y < 0.0 || y > 1.0) {
            continue;
        }
        sum += texture(s_texture, vec2(v_texCoor.x, y));
        num++;
    }
    return sum / float(num);
}

void main() {
    if (u_boxFilterType == 1) {
        fragColor = boxFilter();
    } else if (u_boxFilterType == 2) {
        fragColor = boxFilterHorizontal();
    } else if (u_boxFilterType == 3){
        fragColor = boxFilterVertical();
    } else {
        fragColor = texture(s_texture, v_texCoor); // origin
    }
}

如上我们通过指定 u_boxFilterType 的值来实现使用不同的函数来完成滤波(当然你也可以写几个不同的 fragment shader 来实现)。

部分调用代码如下(完整代码链接在文章末尾):

public void drawBitmapUseFBO() {
    if (mGLProgramBlur <= 0) {
        Log.e(TAG, "mGLProgram not create!");
        return;
    }
    GLES30.glFinish();
    long startTime = System.currentTimeMillis();

    GLES30.glUseProgram(mGLProgramBlur); // 指定使用的program
    GLES30.glEnable(GLES30.GL_CULL_FACE); // 启动剔除
    // init vertex
    GLES30.glEnableVertexAttribArray(0);
    GLES30.glEnableVertexAttribArray(1);
    GLES30.glVertexAttribPointer(0, VERTEX_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, mVertexBuffer);
    GLES30.glVertexAttribPointer(1, VERTEX_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, mTextureBitmapBuffer);

    // 先进行boxFilterHorizontal,渲染到FBO上,绑定颜色附着的纹理
    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextures[0]);
    GLES30.glUniform1i(2, 0);
    GLES30.glUniform1i(3, mKernelSize);  // set u_kernelSize
    GLES30.glUniform1i(4, 2);  // set u_boxFilterType to boxFilterHorizontal
    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBO[0]);
    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mTextures[1], 0);
    GLES30.glDrawElements(GLES30.GL_TRIANGLE_FAN, VERTEX_ORDER.length, GLES30.GL_UNSIGNED_BYTE, mDrawListBuffer);

    // 再进行boxFilterVertical,渲染到屏幕
    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);  // 解绑,即重新绑定回屏幕
    // GLES30.glVertexAttribPointer(0, VERTEX_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, mVertexBuffer);
    // 这次不需要再对纹理进行上下翻转了,重新设置下纹理坐标的值
    GLES30.glVertexAttribPointer(1, VERTEX_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, mTextureRotation0Buffer);
    GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextures[1]);
    GLES30.glUniform1i(2, 1);
    GLES30.glUniform1i(3, mKernelSize);  // set u_kernelSize
    GLES30.glUniform1i(4, 3);  // set u_boxFilterType to boxFilterVertical
    GLES30.glDrawElements(GLES30.GL_TRIANGLE_FAN, VERTEX_ORDER.length, GLES30.GL_UNSIGNED_BYTE, mDrawListBuffer);
    GLES30.glDisableVertexAttribArray(0);
    GLES30.glDisableVertexAttribArray(1);

    GLES30.glFinish();
    long endTime = System.currentTimeMillis();
    Log.i(TAG, "drawBitmapUseFBO time(ms): " + (endTime - startTime));
}

先进行 boxFilterHorizontal,渲染到FBO上,再进行 boxFilterVertical,渲染到屏幕。

3. 多重渲染目标(MRT)

通常情况下,片段着色器只有一个输出颜色。如果想要同时输出多个颜色,则可以使用多重渲染目标来实现,通过多重渲染目标,片段着色器输出多个颜色(用于保存RGBA颜色、法线、深度或纹理坐标)。MRT 用于多种高级渲染算法中,例如延迟着色和快速环境遮蔽估算(SSAO)。

使用示例:

fragment shader 中指定多个输出颜色

#version 300 es
precision mediump float;
layout (location = 2) uniform sampler2D s_texture;

in vec2 v_texCoor;
out vec4 fragColor1;  // 输出到第一个颜色附着点
out vec4 fragColor2;  // 输出到第二个颜色附着点
out vec4 fragColor3;  // 输出到第三个颜色附着点

void main() {
    vec4 color = texture(s_texture, v_texCoor); // origin
    fragColor1 = vec4(1.0) - color; // inverted
    fragColor2 = mix(color, vec4(1.0, 0.0, 0.0, 1.0), 0.5); // mix with color red
    fragColor3 = mix(color, vec4(0.0, 0.0, 1.0, 1.0), 0.5); // mix with color blur
}

部分调用代码如下(完整代码链接在文章末尾):

public void drawBitmapUseMRT(int targetIndex) {
    if (mGlProgramMRT <= 0) {
        Log.e(TAG, "mGLProgram not create!");
        return;
    }
    GLES30.glFinish();
    long startTime = System.currentTimeMillis();

    Log.d(TAG, "drawBitmapUseMRT mGLProgram: " + mGlProgramMRT);
    GLES30.glUseProgram(mGlProgramMRT); // 指定使用的program
    GLES30.glEnable(GLES30.GL_CULL_FACE); // 启动剔除
    // init vertex
    GLES30.glEnableVertexAttribArray(0);
    GLES30.glEnableVertexAttribArray(1);
    GLES30.glVertexAttribPointer(0, VERTEX_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, mVertexBuffer);
    GLES30.glVertexAttribPointer(1, VERTEX_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, mTextureBitmapBuffer);
    // bind texture
    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextures[0]);
    GLES30.glUniform1i(2, 0);

    // bind FBO and color attachment
    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBO[0]);
    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mTextures[1], 0);
    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT1, GLES30.GL_TEXTURE_2D, mTextures[2], 0);
    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT2, GLES30.GL_TEXTURE_2D, mTextures[3], 0);
    // 假设有两个颜色附着点,分别对应 GL_COLOR_ATTACHMENT0 和 GL_COLOR_ATTACHMENT1
    int drawBuffers[] = { GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_COLOR_ATTACHMENT1, GLES30.GL_COLOR_ATTACHMENT2 };
    // 将颜色附着点指定给帧缓冲对象
    GLES30.glDrawBuffers(drawBuffers.length, drawBuffers, 0);
    GLES30.glDrawElements(GLES30.GL_TRIANGLE_FAN, VERTEX_ORDER.length, GLES30.GL_UNSIGNED_BYTE, mDrawListBuffer);

    GLES30.glFinish();
    long endTime = System.currentTimeMillis();
    Log.i(TAG, "drawBitmapUseMRT time(ms): " + (endTime - startTime));
}

四、示例代码地址

https://github.com/afei-cn/OpenGLSample/tree/master/fbodemo

其中shader代码在 fbodemo/src/main/assets 文件夹下,调用代码在 fbodemo/src/main/java/com/afei/fbodemo/JavaDrawer.java 中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/226130.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jmeter接口测试

前言&#xff1a; 本文主要针对http接口进行测试&#xff0c;使用Jmeter工具实现。 Jmter工具设计之初是用于做性能测试的&#xff0c;它在实现对各种接口的调用方面已经做的比较成熟&#xff0c;因此&#xff0c;本次直接使用Jmeter工具来完成对Http接口的测试。 1.介绍什么是…

《即时消息系统-IM核心技术》

IM 核心概念 用户&#xff1a;系统的使用者 消息&#xff1a;是指用户之间的沟通内容。通常在 IM 系统中&#xff0c;消息会有以下几类&#xff1a;文本消息、表情消息、图片消息、视频消息、文件消息等等 会话&#xff1a;通常指两个用户之间因聊天而建立起的关联 群&…

聊一聊Java中的枚举和泛型(两种强大的编程特性)

聊一聊Java中的枚举和泛型&#xff08;两种强大的编程特性&#xff09; 保持热爱&#xff0c;奔赴山海。。。。。。 Java中的枚举 在Java中&#xff0c;枚举&#xff08;Enum&#xff09;是一种特殊的数据类型&#xff0c;用于定义包含固定常量集合的数据类型。枚举类型在Jav…

猫咪瘦弱的原因是什么?适合给消瘦猫咪长肉吃的猫罐头分享

很多小猫咪吃得很多&#xff0c;但是还是很瘦&#xff0c;这让很多猫主人感到困惑&#xff0c;猫咪瘦弱的原因是什么呢&#xff1f;铲屎那么多年&#xff0c;还是有点子养猫知识在身上的。那么&#xff0c;小猫咪瘦弱的原因是什么呢&#xff1f;让我们看看是不是这些原因导致的…

LinuxBasicsForHackers笔记 -- 进程管理

进程是一个正在运行和使用资源的程序。 Linux 内核是操作系统的内核&#xff0c;几乎控制着一切&#xff0c;在创建进程时&#xff0c;它会按顺序为每个进程分配一个唯一的进程 ID (PID)。 查看进程 ps – 用于在命令行查看哪些进程处于活动状态。单独使用 ps 命令并不能真正…

使用Notepad++编辑器,安装compare比较差异插件

概述 是一款非常有特色的编辑器&#xff0c;Notepad是开源软件&#xff0c;Notepad中文版可以免费使用。 操作步骤&#xff1a; 1、在工具栏 ->“插件”选项。 2、勾选Compare选项&#xff0c;点击右上角“安装”即可。 3、 确认安装插件 4、下载插件 5、插件已安装 6、打…

微前端介绍

目录 微前端概念 微前端特性 场景演示 微前端方案 iframe 方案 qiankun 方案 micro-app 方案 EMP 方案 无界微前端 方案 无界方案 成本低 速度快 原生隔离 功能强大 总结 前言&#xff1a;微前端已经是一个非常成熟的领域了&#xff0c;但开发者不管采用哪个现…

JAVA网络编程——BIO、NIO、AIO深度解析

I/O 一直是很多Java同学难以理解的一个知识点&#xff0c;这篇帖子将会从底层原理上带你理解I/O&#xff0c;让你看清I/O相关问题的本质。 1、I/O的概念 I/O 的全称是Input/Output。虽常谈及I/O&#xff0c;但想必你也一时不能给出一个完整的定义。搜索了谷哥欠&#xff0c;发…

MySQl int(1)、int(20) 的区别到底在哪里

MySQl int(1)、int(20) 的区别到底在哪里 常思一二&#xff0c;便得自然… int(1)数据类型介绍 在MySQL中&#xff0c;INT(1) 是一种定义整数类型的数据字段&#xff0c;其中的数字表示显示宽度而不是存储范围。具体说&#xff0c;INT(1) 中的数字 1 表示显示宽度&#xff0…

数字人知识库:Awesome-Talking-Head-Synthesis

数字人知识库&#xff1a;Awesome-Talking-Head-Synthesis 文章目录 数字人知识库&#xff1a;Awesome-Talking-Head-SynthesisDatasetsSurveyAudio-drivenText-drivenNeRF & 3DMetricsTools & SoftwareSlides & Presentations Gihub&#xff1a;https://github.co…

从 ByteHouse 网关,看如何进一步提升 OLAP 引擎性能

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 随着数字化转型的加速&#xff0c;企业面临着海量数据收集、处理和分析挑战。ClickHouse因其分析速度快、高性能的特点&#xff0c;被开发者广泛使用。 作为连接客户…

OPC UA客户端工具UaExpert使用

OPC UA客户端工具UaExpert使用 官方下载地址: https://www.unified-automation.com/downloads.html UaExpert 是一个全功能的 OPC UA 客户端&#xff0c;能够支持多个 OPC UA 配置文件和功能。 安装UaExpert 官方下载最新安装包: uaexpert-bin-win32-x86-vs2008sp1-v1.5.1-…

一文搞懂系列——你真的了解如何生成动态库了吗?

引言 动态库的编译&#xff0c;这有什么难度&#xff0c;这不是手到擒来的事情吗&#xff1f;无非不就是&#xff1a; gcc -FPIC -shared -o libxxx.so *.o *.c 我若是提出这些需求场景&#xff0c;阁下又如何应对呢&#xff1f; 动态库A依赖其他部分提供的能力。但是却不…

网络层(1)——概述

一、概述 网络层毫无疑问是最复杂的一层&#xff0c;涉及到大量的协议与结构的内容。在如今主流的设计中&#xff0c;大家都会把网络层分成两个部分&#xff1a;数据平面、控制平面。其中数据平面指的是网络层中每台路由器的功能&#xff0c;它决定了到达路由器端口输入链路之一…

马蹄集 oj赛(双周赛第十六次)

目录 ​圣诞树上的星星 军团大战 堆煤球 武力对决 小码哥教数学 小码哥玩字母独 跳跳棋 激光扫描游戏 数数游戏 小狗巴克 魔塔密码 地狱尖兵 3D眩晕 圣诞树上的星星 难度:青铜 时间限制: 1秒占用内存:64M 小码哥在过圣诞节! 小码哥家里有很多个星星 *&#xff0c…

热烈祝贺许战海老师成为北京湖南商会特聘专家!

在北京的初冬时节&#xff0c;一股商业的暖流在世纪华天大酒店的湖南厅中涌动。2023年12月3日下午&#xff0c;这里迎来了一场盛大的聚会——北京湖南企业商会成立20周年的预热活动之一&#xff1a;“湘商大讲堂”。这不仅是一次庆祝&#xff0c;更是一次对未来的展望&#xff…

在Pwn中,为什么时长需要栈对齐?

Index 介绍知识要点正文 介绍 在 Pwn 的学习中&#xff0c;对于初学者常常会遇到这个问题&#xff1a; 找到了溢出点&#xff0c;并且知道如何溢出&#xff0c;但是不知道为什么自己的Payload并没有成功&#xff0c;Pwntools报错EOF&#xff1a; 今天趁着有时间&#xff0c;来…

C++ 指针进阶

目录 一、字符指针 二、指针数组 三、数组指针 数组指针的定义 &数组名 与 数组名 数组指针的使用 四、数组参数 一维数组传参 二维数组传参 五、指针参数 一级指针传参 二级指针传参 六、函数指针 七、函数指针数组 八、指向函数指针数组的指针 九、回调函…

HBase 使用JDK21

HBase 使用JDK21 启动zookeeper和hadoop 创建软件目录 mkdir -p /opt/soft cd /opt/soft下载软件 wget https://dlcdn.apache.org/hbase/2.5.6/hbase-2.5.6-hadoop3-bin.tar.gz解压 hbase tar -zxvf hbase-2.5.6-hadoop3-bin.tar.gz修改 hbase 目录名称 mv hbase-2.5.6-had…

圣诞将至—C语言圣诞树代码来啦

文章目录 圣诞将至—C实现语言圣诞树源码 圣诞将至—C实现语言圣诞树 圣诞树 源码 #define _CRT_SECURE_NO_WARNINGS#include <stdio.h> #include <math.h> #include <stdlib.h> #include <windows.h> #include <time.h> #define PI 3.14159265…