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) {// 创建FBOGLuint 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);  // unbindglDeleteFramebuffers(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 fragColorvec4 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^2for (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 fragColorvec4 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;// 复杂度:Nfor (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;// 复杂度:Nfor (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); // 指定使用的programGLES30.glEnable(GLES30.GL_CULL_FACE); // 启动剔除// init vertexGLES30.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_kernelSizeGLES30.glUniform1i(4, 2);  // set u_boxFilterType to boxFilterHorizontalGLES30.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_kernelSizeGLES30.glUniform1i(4, 3);  // set u_boxFilterType to boxFilterVerticalGLES30.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); // originfragColor1 = vec4(1.0) - color; // invertedfragColor2 = mix(color, vec4(1.0, 0.0, 0.0, 1.0), 0.5); // mix with color redfragColor3 = 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); // 指定使用的programGLES30.glEnable(GLES30.GL_CULL_FACE); // 启动剔除// init vertexGLES30.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 textureGLES30.glActiveTexture(GLES30.GL_TEXTURE0);GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextures[0]);GLES30.glUniform1i(2, 0);// bind FBO and color attachmentGLES30.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_ATTACHMENT1int 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 中。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JSON字符串转泛型对象

JSON字符串转泛型对象 以下问题只仅限于博主自身遇到&#xff0c;不代表绝对出现问题 相关类展示&#xff1a; 参数基类 public class BaseParams { }基类 public abstract class AbstractPush<Params extends BaseParams> {protected abstract void execute(Params…

怎么选护眼灯?考公必备护眼灯推荐

随着现在的近视问题受到越来越广泛的关注&#xff0c;各种护眼产品的需求量也一直在快速增加&#xff0c;其中最受广大家长、学生党欢迎的还是护眼台灯。因为专业的护眼台灯不仅可以补充我们夜晚工作、学习时不足的光亮&#xff0c;避免眼睛长时间处于昏暗的环境导致疲劳。 而…

018 OpenCV 人脸检测

目录 一、环境 二、分类器原理 2.1、概述 2.2、工作原理 三、人脸检测代码 一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、分类器原理 CascadeClassifier是OpenCV&#xff08;开源计算机视觉库&#xff09;中的一个强大的类…

day01十五模拟第二期

2 2——608#include <stdio.h> #include <math.h>int main(int argc, const char * argv[]) {int res1;for(int i1;i<2023;i){res(res*2)%1000;}printf("%d",res );return 0; }3 4-----4169 #include <stdio.h> #include<string.h> #inc…

C语言——指针(五)

&#x1f4dd;前言&#xff1a; 上篇文章C语言——指针&#xff08;四&#xff09;更加深入的介绍了不同类型指针的特点&#xff0c;这篇文章主要想记录一下函数与指针的结合运用以及const和assert关于指针的用法&#xff1a; 1&#xff0c;函数与指针 2&#xff0c;const 3&am…

3.PyTorch——常用神经网络层

import numpy as np import pandas as pd import torch as t from PIL import Image from torchvision.transforms import ToTensor, ToPILImaget.__version__2.1.13.1 图像相关层 图像相关层主要包括卷积层&#xff08;Conv&#xff09;、池化层&#xff08;Pool&#xff09;…

node.js和浏览器之间的区别

node.js是什么 Node.js是一种基于Chrome V8引擎的JavaScript运行环境&#xff0c;可以在服务器端运行JavaScript代码 Node.js 在浏览器之外运行 V8 JavaScript 引擎。 这使得 Node.js 非常高效。 浏览器如何运行js代码 nodejs运行环境 在浏览器中&#xff0c;大部分时间你所…

Linux系统调试课:USB 常用调试方法

文章目录 一、USB调试工具有哪些二、USB相关节点2.1、USB枚举成功标志2.2、USB speed查询2.3、USB 查询PID、VID沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要 介绍 USB 常用调试方法。 一、USB调试工具有哪些

Sanic:一个极速Python Web框架

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Sanic&#xff1a;一个极速Python Web框架&#xff0c;全文3500字&#xff0c;阅读大约12分钟。 随着 Web 应用的日益复杂&#xff0c;选择一个高性能的 Web 框架变得尤为…

扫描器的使用

漏扫器 注意事项 扫描器会给客户的业务造成影响。比如&#xff0c;如果存在sql注入漏洞&#xff08;重大的漏洞&#xff09;的话&#xff0c;会给客户的数据库插入脏数据&#xff0c;后果很严重 主机漏扫 针对IP地址和网段的漏洞扫描&#xff0c;例如&#xff1a;22端口弱口…

LCM-LoRA:a universal stable-diffusion acceleration module

Consistency is All You Need - wrong.wang什么都不用做生成却快了十倍其实也并非完全不可能https://wrong.wang/blog/20231111-consistency-is-all-you-need/ 1.Stable diffusion实在预训练VAE空间训练diffusion model的结果。 2.consistency decoder是用consistency model技…

ISIS默认路由下发的各种机制

作者简介&#xff1a;大家好&#xff0c;我是Asshebaby&#xff0c;热爱网工&#xff0c;有网络方面不懂的可以加我一起探讨 :1125069544 个人主页&#xff1a;Asshebaby博客 当前专栏&#xff1a; 网络HCIP内容 特色专栏&#xff1a; 常见的项目配置 本文内容&am…

017 OpenCV 向量机SVM

目录 一、环境 二、SVM原理 三、完整代码 一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、SVM原理 OpenCV中的向量机&#xff08;SVM&#xff09;是一种监督学习算法&#xff0c;用于分类和回归分析。它通过找到一个最优的超平…

振弦采集仪在岩土工程中的探索与应用

振弦采集仪在岩土工程中的探索与应用 振弦采集仪是一种常用的测量仪器&#xff0c;在岩土工程中具有重要的应用价值。它主要利用振弦原理&#xff0c;通过测量振动信号的特征参数来分析地下土体的力学特性以及工程中的变形情况。 振弦采集仪早期主要用于建筑物、桥梁、堤坝等…

手机拍照的图片,如何传到电脑上?

手机受性能和屏幕限制&#xff0c;其应用功能也多少会因此而受到影响&#xff0c;比如在金鸣识别的电脑客户端&#xff0c;用户可一次性提交100张的图片进行识别&#xff0c;而在移动端&#xff0c;则最多只能一次三张&#xff0c;如何破这个“局”呢&#xff1f; 一、有扫描仪…

RT-DETR优化:Backbone改进 | UniRepLKNet,通用感知大内核卷积网络,RepLK改进版本 | 2023.11

🚀🚀🚀本文改进: UniRepLKNet,通用感知大内核卷积网络,ImageNet-22K预训练,精度和速度SOTA,ImageNet达到88%, COCO达到56.4 box AP,ADE20K达到55.6 mIoU 🚀🚀🚀RT-DETR改进创新专栏:http://t.csdnimg.cn/vuQTz 学姐带你学习YOLOv8,从入门到创新,轻轻松松…

Linux centos8安装JDK1.8、tomcat

一、安装jdk 1.如果之前安装过jdk&#xff0c;先卸载掉旧的 rpm -qa | grep -i jdk 2.检查yum中有没有java1.8的包 yum list java-1.8* 3.yum安装jdk yum install java-1.8.0-openjdk* -y 4.验证 二、安装tomcat Index of /tomcat 可以在这里选择你想要安装的tomcat版本…

Centos7部署Graylog5.2日志系统

Graylog5.2部署 Graylog 5.2适配MongoDB 5.x~6.x&#xff0c;MongoDB5.0要求CPU支持AVX指令集。 主机说明localhost部署Graylog&#xff0c;需要安装mongodb-org-6.0、 Elasticsearch7.10.2 参考&#xff1a; https://blog.csdn.net/qixiaolinlin/article/details/129966703 …

洛谷(md版)

小知识点 1.printf()一行一个双引号“” 2.double->%lf 3.例题 ​​​​​​​​​​​​​​ ​​​4. 这两者不一样 上行&#xff1a;先转化成了浮点数&#xff0c;再运算 下行&#xff1a;先运算的整数&#xff0c;得到结果&#xff0c;再转化成浮点数 no1 no / (…

BiseNet实现遥感影像地物分类

遥感地物分类通过对遥感图像中的地物进行准确识别和分类&#xff0c;为资源管理、环境保护、城市规划、灾害监测等领域提供重要信息&#xff0c;有助于实现精细化管理和科学决策&#xff0c;提升社会治理和经济发展水平。深度学习遥感地物分类在提高分类精度、自动化程度、处理…