OpenGL Frame Buffer Object (FBO)

Overview

在 OpenGL 渲染管线中,几何数据和纹理被转换并通过多次测试,然后最终作为 2D 像素渲染到屏幕上。OpenGL 管线的最终渲染目的地称为帧缓冲区。帧缓冲区是 OpenGL 使用的二维数组或存储的集合:颜色缓冲区、深度缓冲区、模板缓冲区和累积缓冲区。默认情况下,OpenGL 使用帧缓冲区作为渲染目标,完全由窗口系统创建和管理。此默认帧缓冲区称为窗口系统提供的帧缓冲区(window-system-provided-framebuffer)。

OpenGL 扩展 GL_ARB_FRAMEBUFFER_OBJECT 提供了一个接口来创建额外的不可显示的帧缓冲区对象 (FBO)。这个帧缓冲区被称为应用程序创建的帧缓冲区(application-created-framebuffer),以区别于默认的窗口系统提供的帧缓冲区。通过使用帧缓冲区对象 (FBO),OpenGL 应用程序可以将渲染输出重定向到应用程序创建的帧缓冲区对象 (FBO),而不是传统的窗口系统提供的帧缓冲区。而且,它完全由 OpenGL 控制。

类似于窗口系统提供的帧缓冲区,FBO 包含一组渲染目标:颜色、深度和模板缓冲区。(FBO 中未定义累积缓冲区。)FBO 中的这些逻辑缓冲区称为可附加帧缓冲区的图像,它们是可以附加到帧缓冲区对象的二维像素阵列。

有两种类型的可附加帧缓冲区的图像:纹理图像和渲染缓冲图像。如果纹理对象的图像附加到帧缓冲区,OpenGL 将执行“渲染到纹理”。如果渲染缓冲区对象的图像附加到帧缓冲区,则 OpenGL 将执行“离屏渲染”。

顺便说一下,renderbuffer 对象是 GL_ARB_framebuffer_object 扩展中定义的一种新型存储对象。它在渲染过程中用作单个 2D 图像的渲染目标。

下图显示了帧缓冲对象、纹理对象和渲染缓冲对象之间的连接。可以通过附加点将多个纹理对象或渲染缓冲区对象附加到帧缓冲区对象。

帧缓冲对象中有多个颜色附着点 (GL_COLOR_ATTACHMENT0,…, GL_COLOR_ATTACHMENTn)、一个深度附着点 (GL_DEPTH_ATTACHMENT) 和一个模板附着点 (GL_STENCIL_ATTACHMENT)。颜色附着点的数量取决于具体实现,但每个 FBO 必须至少有一个颜色附着点。您可以使用 GL_MAX_COLOR_ATTACHMENTS 查询最大颜色附着点数,这是显卡支持的。FBO 具有多个颜色附加点的原因是允许将颜色缓冲区同时渲染到多个目的地。这种“多渲染目标”(MRT)可以通过 GL_ARB_draw_buffers 扩展来实现。帧缓冲区对象本身没有任何图像存储(数组),但是,它只有多个连接点。

帧缓冲对象(FBO)提供了高效的切换机制:从 FBO 分离先前的可附加帧缓冲区的图像,并将新的可附加帧缓冲区的图像附加到 FBO。切换可附加帧缓冲区的图像比在 FBO 之间切换要快得多。FBO 提供 glFramebufferTexture2D() 来切换 2D 纹理对象,以及glFramebufferRenderbuffer()来切换渲染缓冲区对象。

Creating Frame Buffer Object (FBO)

创建帧缓冲对象类似于生成顶点缓冲对象 (VBO)。

glGenFramebuffers()

void glGenFramebuffers(GLsizei n, GLuint* ids)void glDeleteFramebuffers(GLsizei n, const GLuint* ids)

glGenFramebuffers() 需要 2 个参数;第一个是要创建的帧缓冲区的数量,第二个参数是指向 GLuint变量或存储单个 ID 或多个 ID 的数组的指针。它返回未使用的帧缓冲区对象的 ID。ID 0 表示默认帧缓冲区,即窗口系统提供的帧缓冲区。

并且,当不再使用 FBO 时,可以通过调用 glDeleteFramebuffers()来删除它。

glBindFramebuffer()

创建 FBO 后,必须先对其进行绑定,然后才能使用它。

void glBindFramebuffer(GLenum target, GLuint id)

第一个参数target应该是GL_FRAMEBUFFER,第二个参数是一个framebuffer对象的ID。一旦绑定了 FBO,所有 OpenGL 操作都会影响当前绑定的帧缓冲区对象。对象 ID 0 是为默认的窗口系统提供的帧缓冲区保留的。因此,为了解除当前帧缓冲区(FBO)的绑定,请在 glBindFramebuffer()中使用 ID 0。

Renderbuffer Object

此外,为离屏渲染新引入了渲染缓冲区对象。它允许将场景直接渲染到渲染缓冲区对象,而不是渲染到纹理对象。Renderbuffer 只是一个包含可渲染内部格式的单个图像的数据存储对象。它用于存储没有相应纹理格式的 OpenGL 逻辑缓冲区,例如模板或深度缓冲区。

glGenRenderbuffers()

void glGenRenderbuffers(GLsizei n, GLuint* ids)void glDeleteRenderbuffers(GLsizei n, const Gluint* ids)

创建渲染缓冲区后,它将返回非零正整数。 ID 0 是为 OpenGL 保留的

glBindRenderbuffer()

void glBindRenderbuffer(GLenum target, GLuint id)

与其他 OpenGL 对象一样,您必须在引用它之前绑定当前的渲染缓冲区对象。对于渲染缓冲区对象,目标参数应该是 GL_RENDERBUFFER。

glRenderbufferStorage()

void glRenderbufferStorage(GLenum  target,GLenum  internalFormat,GLsizei width,GLsizei height)

当一个渲染缓冲区对象被创建时,它没有任何数据存储,所以我们必须为它分配一个内存空间。这可以通过使用 glRenderbufferStorage() 来完成。第一个参数必须是 GL_RENDERBUFFER。第二个参数是颜色渲染方式(GL_RGB、GL_RGBA 等)。深度可渲染(GL_DEPTH_COMPONENT)或模板可渲染格式(GL_STENCIL_INDEX)。

宽度和高度是以像素为单位的渲染缓冲区图像的尺寸。宽度和高度应小于 GL_MAX_RENDERBUFFER_SIZE,否则会产生 GL_INVALID_VALUE 错误。

glGetRenderbufferParameteriv()

void glGetRenderbufferParameteriv(GLenum target,GLenum param,GLint* value)

您还可以获得当前绑定的渲染缓冲区对象的各种参数。 target 应该是 GL_RENDERBUFFER,第二个参数是参数名称。最后一个是指向存储返回值的整数变量的指针。

渲染缓冲区参数的可用名称是:

GL_RENDERBUFFER_WIDTH
GL_RENDERBUFFER_HEIGHT
GL_RENDERBUFFER_INTERNAL_FORMAT
GL_RENDERBUFFER_RED_SIZE
GL_RENDERBUFFER_GREEN_SIZE
GL_RENDERBUFFER_BLUE_SIZE
GL_RENDERBUFFER_ALPHA_SIZE
GL_RENDERBUFFER_DEPTH_SIZE
GL_RENDERBUFFER_STENCIL_SIZE

Attaching images to FBO

FBO 本身没有任何图像存储(缓冲区)。相反,我们必须将可附加帧缓冲区的图像(纹理或渲染缓冲区对象)附加到 FBO。这种机制允许 FBO 快速切换(分离和附加)FBO 中可附加帧缓冲区的图像。与在 FBO 之间切换相比,切换可附加帧缓冲区的图像要快得多。而且,它节省了不必要的数据副本和内存消耗。例如,一个纹理可以附加到多个 FBO,其图像存储可以由多个 FBO 共享。

Attaching a 2D texture image to FBO

glFramebufferTexture2D(GLenum target,GLenum attachmentPoint,GLenum textureTarget,GLuint textureId,GLint  level)

glFramebufferTexture2D()是将2D纹理图像附加到FBO。第一个参数必须是GL_FRAMEBUFFER,第二个参数是连接纹理图像的附着点。FBO 有多个颜色连接点(GL_COLOR_ATTACHMENT0、...、GL_COLOR_ATTACHMENTn)、GL_DEPTH_ATTACHMENTGL_STENCIL_ATTACHMENT。在大多数情况下,第三个参数“textureTarget”是 GL_TEXTURE_2D。第四个参数是纹理对象的标识符。最后一个参数是要附加的纹理的 mipmap 级别。

如果textureId 参数设置为0,则纹理图像将从FBO 中分离。如果纹理对象在仍然附加到 FBO 的情况下被删除,则纹理图像将自动从当前绑定的 FBO 中分离,但不会脱离任何其他未绑定的 FBO。

Attaching a Renderbuffer image to FBO

void glFramebufferRenderbuffer(GLenum target,GLenum attachmentPoint,GLenum renderbufferTarget,GLuint renderbufferId)

可以通过调用 glFramebufferRenderbuffer()附加渲染缓冲区图像。第一个和第二个参数与 glFramebufferTexture2D() 相同。第三个参数必须是GL_RENDERBUFFER,最后一个参数是renderbuffer对象的ID。

如果 renderbufferId 参数设置为 0,则渲染缓冲区图像将从 FBO 中的附着点分离。如果一个渲染缓冲区对象在它仍然附加在 FBO 中时被删除,那么它会自动从绑定的 FBO 中分离。但是,它不会与任何其他非绑定 FBO 分离。

FBO with MSAA (Multi Sample Anti Aliasing)


当您渲染到 FBO 时,即使您为窗口系统提供的帧缓冲区正确创建了具有多重采样属性 (SAMPLEBUFFERS_ARB) 的 OpenGL 渲染上下文,也不会自动启用抗锯齿。

为了激活多采样抗锯齿模式以渲染到 FBO,您需要准备多采样图像并将其附加到 FBO 的颜色和/或深度附加点。

FBO 扩展提供 glRenderbufferStorageMultisample() 为多采样抗锯齿渲染模式创建渲染缓冲区图像。

void glRenderbufferStorageMultisample(GLenum  target,GLsizei samples,GLenum  internalFormat,GLsizei width,GLsizei height)

它在 glRenderbufferStorage()之上添加了新参数 samples,这是抗锯齿渲染模式的多重采样数。如果为 0,则不启用 MSAA 模式,而是调用 glRenderbufferStorage()。您可以在 glGetIntegerv() 中使用 GL_MAX_SAMPLES 标记查询最大样本数。

下面的代码是创建一个带有多样本颜色缓冲和深度缓冲图像的 FBO。请注意,如果多个图像附加到 FBO,则所有图像必须具有相同数量的多重样本。否则,FBO 状态不完整。

// create a 4x MSAA renderbuffer object for colorbuffer
int msaa = 4;
GLuint rboColorId;
glGenRenderbuffers(1, &rboColorId);
glBindRenderbuffer(GL_RENDERBUFFER, rboColorId);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa, GL_RGB8, width, height);// create a 4x MSAA renderbuffer object for depthbuffer
GLuint rboDepthId;
glGenRenderbuffers(1, &rboDepthId);
glBindRenderbuffer(GL_RENDERBUFFER, rboDepthId);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa, GL_DEPTH_COMPONENT, width, height);// create a 4x MSAA framebuffer object
GLuint fboMsaaId;
glGenFramebuffers(1, &fboMsaaId);
glBindFramebuffer(GL_FRAMEBUFFER, fboMsaaId);// attach colorbuffer image to FBO
glFramebufferRenderbuffer(GL_FRAMEBUFFER,       // 1. fbo target: GL_FRAMEBUFFERGL_COLOR_ATTACHMENT0, // 2. color attachment pointGL_RENDERBUFFER,      // 3. rbo target: GL_RENDERBUFFERrboColorId);          // 4. rbo ID// attach depthbuffer image to FBO
glFramebufferRenderbuffer(GL_FRAMEBUFFER,       // 1. fbo target: GL_FRAMEBUFFERGL_DEPTH_ATTACHMENT,  // 2. depth attachment pointGL_RENDERBUFFER,      // 3. rbo target: GL_RENDERBUFFERrboDepthId);          // 4. rbo ID// check FBO status
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(status != GL_FRAMEBUFFER_COMPLETE)fboUsed = false;

重要的是要知道 glRenderbufferStorageMultisample()仅启用对 FBO 的 MSAA 渲染。但是,您不能直接使用来自 MSAA FBO 的结果。如果需要将结果传输到纹理或其他非多重采样帧缓冲区,则必须使用 glBlitFramebuffer() 将结果转换(下采样)为单样本图像。

void glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, // source rectangleGLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, // destination rectGLbitfield mask,GLenum filter)

glBlitFramebuffer()将一个矩形图像从源 (GL_READ_BUFFER) 复制到目标帧缓冲区 (GL_DRAW_BUFFER)。“mask”参数用于指定复制哪些缓冲区,GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BIT 和/或 GL_STENCIL_BUFFER_BIT。最后一个参数“filter”用于指定源矩形和目标矩形尺寸不同时的插值模式。它是 GL_NEARESTGL_LINEAR

下面的代码是将一个多采样图像从一个 FBO 传输到另一个非多采样 FBO。它需要额外的 FBO 才能获得 MSAA 渲染的结果。


// copy rendered image from MSAA (multi-sample) to normal (single-sample)
// NOTE: The multi samples at a pixel in read buffer will be converted
// to a single sample at the target pixel in draw buffer.
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboMsaaId); // src FBO (multi-sample)
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboId);     // dst FBO (single-sample)glBlitFramebuffer(0, 0, width, height,             // src rect0, 0, width, height,             // dst rectGL_COLOR_BUFFER_BIT,             // buffer maskGL_LINEAR);                      // scale filter

Checking FBO Status

将可附加图像(纹理和渲染缓冲区)附加到 FBO 后,在执行 FBO 操作之前,您必须使用 glCheckFramebufferStatus() 验证 FBO 状态是完整还是不完整。

如果 FBO 未完成,则任何绘图和读取命令(glBegin()glCopyTexImage2D() 等)都将失败。

GLenum glCheckFramebufferStatus(GLenum target)

glCheckFramebufferStatus() 在当前绑定的 FBO 上验证其所有附加的图像和帧缓冲区参数。并且,不能在 glBegin()/glEnd() 对中调用此函数。目标参数应该是 GL_FRAMEBUFFER。检查 FBO 后返回非零值。如果满足所有要求和规则,则返回 GL_FRAMEBUFFER_COMPLETE。否则,它返回一个相关的错误值,该值说明违反了什么规则。

FBO完整性的规则是:

  • 可附加帧缓冲区的图像的宽度和高度不得为零。
  • 如果图像附加到颜色连接点,则图像必须具有可显色的内部格式。 (GL_RGBAGL_DEPTH_COMPONENTGL_LUMINANCE 等)
  • 如果图像附加到 GL_DEPTH_ATTACHMENT,则图像必须具有可深度渲染的内部格式。 (GL_DEPTH_COMPONENTGL_DEPTH_COMPONENT24 等)
  • 如果图像附加到 GL_STENCIL_ATTACHMENT,则图像必须具有模板可渲染的内部格式。 (GL_STENCIL_INDEXGL_STENCIL_INDEX8 等)
  • FBO 必须至少附有一张图片。
  • 附加到 FBO 的所有图像必须具有相同的宽度和高度。
  • 附加到颜色附加点的所有图像必须具有相同的内部格式。

即使满足上述所有条件,您的 OpenGL 驱动程序也可能不支持某些内部格式和参数的组合。如果 OpenGL 驱动程序不支持特定实现,则 glCheckFramebufferStatus() 返回 GL_FRAMEBUFFER_UNSUPPORTED

Example: Render To Texture

有时,您需要动态生成动态纹理。最常见的例子是生成镜像/反射效果、动态立方体/环境贴图和阴影贴图。动态纹理可以通过将场景渲染为纹理来完成。渲染到纹理的传统方法是照常将场景绘制到帧缓冲区,然后使用 glCopyTexSubImage2D() 将帧缓冲区图像复制到纹理。

使用 FBO,我们可以将场景直接渲染到纹理上,因此我们根本不必使用窗口系统提供的帧缓冲区。此外,我们可以消除额外的数据副本(从帧缓冲区到纹理)。

该演示程序执行渲染到纹理操作,有/没有 FBO,并比较性能差异。除了性能提升之外,使用 FBO 还有另一个优势。如果在传统的渲染到纹理模式(没有 FBO)下纹理分辨率大于渲染窗口的大小,那么窗口区域外的区域将被裁剪。但是,FBO 不会遇到这种裁剪问题。您可以创建大于显示窗口的帧缓冲区可渲染图像。

以下代码是在开始渲染循环之前设置 FBO 和可附加帧缓冲区的图像。不仅纹理图像附加到 FBO,而且渲染缓冲区图像也附加到 FBO 的深度附加点。我们实际上并没有使用这个深度缓冲区,但是,FBO 本身需要它来进行深度测试。如果我们不将此深度可渲染图像附加到 FBO,则渲染输出将因缺少深度测试而损坏。如果在 FBO 渲染期间还需要模板测试,则应将额外的渲染缓冲区图像附加到 GL_STENCIL_ATTACHMENT

...
// create a texture object
GLuint textureId;
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);// create a renderbuffer object to store depth info
GLuint rboId;
glGenRenderbuffers(1, &rboId);
glBindRenderbuffer(GL_RENDERBUFFER, rboId);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);// create a framebuffer object
GLuint fboId;
glGenFramebuffers(1, &fboId);
glBindFramebuffer(GL_FRAMEBUFFER, fboId);// attach the texture to FBO color attachment point
glFramebufferTexture2D(GL_FRAMEBUFFER,        // 1. fbo target: GL_FRAMEBUFFERGL_COLOR_ATTACHMENT0,  // 2. attachment pointGL_TEXTURE_2D,         // 3. tex target: GL_TEXTURE_2DtextureId,             // 4. tex ID0);                    // 5. mipmap level: 0(base)// attach the renderbuffer to depth attachment point
glFramebufferRenderbuffer(GL_FRAMEBUFFER,      // 1. fbo target: GL_FRAMEBUFFERGL_DEPTH_ATTACHMENT, // 2. attachment pointGL_RENDERBUFFER,     // 3. rbo target: GL_RENDERBUFFERrboId);              // 4. rbo ID// check FBO status
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(status != GL_FRAMEBUFFER_COMPLETE)fboUsed = false;// switch back to window-system-provided framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
...

渲染到纹理的渲染过程与普通绘图几乎相同。我们只需要将渲染目标从窗口系统提供的切换到不可显示的、应用程序创建的帧缓冲区 (FBO)。

...
// set rendering destination to FBO
glBindFramebuffer(GL_FRAMEBUFFER, fboId);// clear buffers
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// draw a scene to a texture directly
draw();// unbind FBO
glBindFramebuffer(GL_FRAMEBUFFER, 0);// trigger mipmaps generation explicitly
// NOTE: If GL_GENERATE_MIPMAP is set to GL_TRUE, then glCopyTexSubImage2D()
// triggers mipmap generation automatically. However, the texture attached
// onto a FBO should generate mipmaps manually via glGenerateMipmap().
glBindTexture(GL_TEXTURE_2D, textureId);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
...

glGenerateMipmap()也作为 FBO 扩展的一部分包含在内,以便在修改基本级别纹理图像后显式生成 mipmap。如果 GL_GENERATE_MIPMAP 设置为 GL_TRUE,则glTex{Sub}Image2D()glCopyTex{Sub}Image2D()会触发自动 mipmap 生成(在 OpenGL 1.4 或更高版本中)。但是,FBO 操作不会在修改基础级别纹理时自动生成其 mipmap,因为 FBO 不会调用 glCopyTex{Sub}Image2D() 来修改纹理。因此,必须显式调用 glGenerateMipmap() 以生成 mipmap。

OpenGL FBO学习相关推荐

  1. OpenGL 开始学习指南

    近期需要做一个涌潮的预报与仿真模拟,为了使模型更具有真实感,且逼真,使用起来更灵活.感觉还是得从基础的OpenGL学习.鉴于Direct3D技术存在的众多不确定性,且评论不太好的原因,决定用OpenG ...

  2. opengl基础学习专题 (二) 点直线和多边形

    题外话 随着学习的增长,越来越觉得自己很水.关于上一篇博文中推荐用一个 学习opengl的 基于VS2015的 simplec框架.存在 一些问题. 1.这个框架基于VS 的Debug 模式下,没有考 ...

  3. OpenGL开发学习指南二(glfw+glad)

    在上一篇文章中博主介绍了freeglut+glew的环境配置,本文介绍glfw+glad的环境配置 本系列教程将使用本文的opengl开发库 开发工具 VS2017 glfw源码:源码地址 CMake ...

  4. 【eoe特刊】第二十七期 OpenGL ES学习及项目解析

    经过一个月征稿.编辑,新的一版特刊终于出炉了. 本次特刊的制作,改变以往的制作方式,完全取自网友的独自的风格. 在只有一个主题的前提下,完全是通过社区的热心的网友,根据自己的想法,自行设计,自由发挥, ...

  5. OpenGL入门学习[二] 绘制简单的几何图形

    OpenGL入门学习[二] 本次课程所要讲的是绘制简单的几何图形,在实际绘制之前,让我们先熟悉一些概念. 一.点.直线和多边形 我们知道数学(具体的说,是几何学)中有点.直线和多边形的概念,但这些概念 ...

  6. 最全面的openGL 入门学习

    自己在找openGL学习资料的时候,找到此篇openGL入门学习(虽然不是移动开发,但给我提供了非常好的思路),所以转一下让更多人知道,本文来自http://www.cppblog.com/doing ...

  7. Android OpenGL ES 学习(十一) –渲染YUV视频以及视频抖音特效

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  8. OpenGL入门学习 (转)

    OpenGL入门学习 (转) 说起编程作图,大概还有很多人想起TC的#include <graphics.h>吧? 但是各位是否想过,那些画面绚丽的PC游戏是如何编写出来的?就靠TC那可怜 ...

  9. OpenGL入门学习[三]

    OpenGL入门学习[三] http://xiaxveliang.blog.163.com/blog/static/2970803420126246501930/ OpenGL入门学习[十一] 我们在 ...

  10. 【待完善】OpenGL入门学习

    严正声明:本文转载自网络,但具体出处未知.如果有读者了解,请联系我更正. 为了阅读方便,我对文字格式进行了修改,并填补了缺少的图片. 我尊重每位作者的权益,如果本文存在侵权行为,请联系我删除并道歉. ...

最新文章

  1. 快速排序 python菜鸟教程-C 排序算法
  2. .net运行项目的几种形式
  3. c语言 字符串 正序再倒序_新特性解读 | MySQL 8.0 索引特性3 -倒序索引
  4. PHP(一)——概述及服务器配置
  5. IBatisNet + Castle 开发相关文章
  6. leetcode刷题:求旋转有序数组的最小值
  7. [翻译 EF Core in Action 2.3] 理解EF Core数据库查询
  8. c语言写报告抽象数据类型,C语言抽象数据类型ADT
  9. vue学习笔记-9-tab选项卡小案例
  10. 聚类-KMeans算法(图解算法原理)
  11. @inherited 注解详解
  12. IOI2008 island
  13. python中del的用法
  14. Markdown基操
  15. Mac使用ssh密钥登录Linux
  16. RocketMQ消息消费方式 推拉模式
  17. S-SDLC(Secure Software Development Lifecycle) 安全编码规范
  18. ios申请企业开发者账号的代理_苹果企业开发者账号如何申请?
  19. 《数据时代 2025》报告-2017年版
  20. Mac查看OpenGL版本

热门文章

  1. php 获取提成的公式,拿提成的工资怎么算的有公式吗_工资提成计算公式
  2. win11安装Pandoc
  3. 怎样关闭百度云开机启动服务器,怎样取消百度网盘的启动打开?百度网盘关闭启动自启的办法...
  4. popupwindow拦截点击物理返回键
  5. Kaggle Titanic 数据分析
  6. JHU计算机专业学费,约翰霍普金斯大学学费多少 贵不贵
  7. mysql32位的能装在64位的电脑上吗_32位电脑能装64位系统吗|怎么看32位电脑可不可以装64位系统-系统城...
  8. Axure下载安装汉化
  9. 禁止浏览器查看源代码
  10. 计算机磁盘扩展,win7如何对硬盘进行扩展分区