zoukankan      html  css  js  c++  java
  • OpenGL 七

    纹理基初知识

    一、纹理

    1)无论是 tga 文件还是 png/jpg 文件,最终图片文件都是要归结到位图文件去处理的。

    纹理文件   --> TGA文件     --> OpenGL --> 位图

    iOS开发中 --> OpenGL ES --> png/jpg --> 位图

    2)原始图像数据:

    图像存储空间 = 图像高度 * 图像宽度 * 每个像素的字节数

    二、相关函数

    1)

    // 改变像素存储⽅式

    void glPixelStorei(GLenum pname,GLint param);

    // 恢复像素存储⽅式

    void glPixelStoref(GLenum pname,GLfloat param);

    // 举例 :

    // 参数1: GL_UNPACK_ALIGNMENT 指定 OpenGL 如何从数据缓存区中解包图像数据

    // 参数2: 表示参数 GL_UNPACK_ALIGNMENT 设置的值

    // GL_UNPACK_ALIGNMENT 指内存中每个像素⾏起点的排列请求,

    // 允许设置为1:byte排列列、2:排列为偶数byte的行、4:字word排列、8:行从双字节边界开始

    glPixelStorei(GL_UNPACK_ALIGNMENT,1);

    2)从颜色缓存区 内容作为像素图 直接读取

    // 参数1:x, 矩形左下角的窗⼝坐标

    // 参数2:y, 矩形左下角的窗口坐标

    // 参数3:width, 矩形的宽,以像素为单位 

    // 参数4:height, 矩形的高,以像素为单位

    // 参数5:format, OpenGL 的像素格式,参考下面表格: OpenGL像素表格 

    // 参数6:type, 解释参数 pixels 指向的数据,告诉OpenGL 使用缓存区中的什么数据类型来存储颜⾊分量,像素数据的数据类型,参考表格:像素数据的像素类型

    // 参数7:pixels, 指向图形数据的指针

    void glReadPixels(GLint x,GLint y,GLSizei width,GLSizei height, GLenum format, GLenum type,const void * pixels);

    glReadBuffer(mode);// 指定读取的缓存 

    glWriteBuffer(mode);// 指定写入的缓存

     

    3)载入纹理

    void glTexImage1D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLint border,GLenum format,GLenum type,void *data);

    void glTexImage2D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLsizei height,GLint border,GLenum format,GLenum type,void * data);

    void glTexImage3D(GLenum target,GLint level,GLint internalformat,GLSizei width,GLsizei height,GLsizei depth,GLint border,GLenum format,GLenum type,void *data);

    /*参数们:

    * target:`GL_TEXTURE_1D`、`GL_TEXTURE_2D`、`GL_TEXTURE_3D`

    * Level:指定所加载的 mip 贴图层次。一般我们都把这个参数设置为0

    * internalformat:每个纹理单元中存储多少颜色成分

    * width、height、depth 参数:指加载纹理的宽度、⾼度、深度。注意: 这些值必须是 2的整数次⽅ --> 这是因为 OpenGL 旧版本上的遗留下的⼀个要求,当然现在已经可以支持不是 2 的整数次方,但是开发者们还是习惯使⽤以2的整数次⽅去设置这些参数。

    * border 参数:允许为纹理贴图指定一个边界宽度

    * format、type、data 参数:与我们在讲 glDrawPixels 函数对应的参数相同*/

    4)更新纹理

    void glTexSubImage1D(GLenum target, GLint level, GLint xOffset, GLsizei width, GLenum format, GLenum type, const GLvoid *data);

    void glTexSubImage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *data);

    void glTexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, Glenum type, const GLvoid * data);

    5)插⼊替换纹理

    void glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsize width); 

    void glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yOffset, GLint x, GLint y,GLsizei width,GLsizei height);

    void glCopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yOffset, GLint zOffset, GLint x,GLint y, GLsizei width, GLsizei height);

    6)使⽤颜⾊缓存区加载数据,形成新的纹理使⽤

    void glCopyTexImage1D(GLenum target,GLint level,GLenum internalformt,GLint x,GLint y,GLsizei width,GLint border);

    void glCopyTexImage2D(GLenum target,GLint level,GLenum internalformt,GLint x,GLint y,GLsizei width,GLsizei height,GLint border);

    x,y 在颜⾊缓存区中指定了开始读取纹理数据的位置; 缓存区里的数据,是源缓存区通过 glReadBuffer 设置的。 

    注意:不存在 glCopyTextImage3D ,因为我们⽆法从 2D 颜⾊缓存区中获取体积数据。

     

    7)纹理对象

    // 使⽤函数分配纹理对象 

    // 指定纹理对象的数量 和 指针 (指针指向⼀个⽆符号整形数组,由纹理对象标识符填充) 

    void glGenTextures(GLsizei n,GLuint * textTures);

    // 绑定纹理状态 

    // 参数target: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 

    // 参数texture: 需要绑定的纹理对象

    void glBindTexture(GLenum target,GLunit texture); 

    // 删除绑定纹理对象

    // 纹理对象 以及 纹理对象指针 —> 指针指向⼀个⽆符号整形数组,由纹理对象标识符填充 

    void glDeleteTextures(GLsizei n,GLuint *textures); 

    // 测试纹理对象是否有效 

    // 如果 texture 是⼀个已经分配空间的纹理对象,那么这个函数会返回 GL_TRUE, 否则会返回 GL_FALSE。

    GLboolean glIsTexture(GLuint texture); 

     

    8)设置纹理参数

    glTexParameterf(GLenum target,GLenum pname,GLFloat param);

    glTexParameteri(GLenum target,GLenum pname,GLint param);

    glTexParameterfv(GLenum target,GLenum pname,GLFloat *param);

    glTexParameteriv(GLenum target,GLenum pname,GLint *param);

    /* 参数1:target, 指定这些参数将要应⽤在哪个纹理模式上,⽐如GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D。 

    参数2:pname,指定需要设置哪个纹理参数

    参数3:param,设定特定的纹理参数的值*/

    8.1)设置过滤方式
    下图,临近过滤取值:+ 号所在位置的色值;
       线性过滤取值:+ 号所在位置的 附近3个挨着的颜色的混合值。

    2种纹理过滤方式的比较:纹理放大缩小时,设置是使⽤线性过滤还是临近过滤

    临近过滤-图像锯齿;线性过滤-平滑柔焦  --> 此效果是指图像放大 or 缩小到一定程度的效果,正常图片的显示不会有问题的。

    纹理参数设置函数

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 纹理放大时,使⽤线性过滤

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);  // 纹理缩小时,使⽤线性过滤

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);// 纹理放大时,使⽤临近过滤

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // 纹理缩小时,使⽤临近过滤

       

    8.2)设置环绕方式

    设置环绕方式 API

    /*

    参数1:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 

    参数2:GL_TEXTURE_WRAP_S、GL_TEXTURE_T、GL_TEXTURE_R,针对 s、t、r 坐标  --  s -> x 

    参数3:GL_REPEAT、GL_CLAMP、GL_CLAMP_TO_EDGE、GL_CLAMP_TO_BORDER 

    GL_REPEAT:OpenGL 在纹理理坐标超过1.0的⽅方向上对纹理理进⾏行行重复; 

    GL_CLAMP:所需的纹理单元取⾃纹理边界或 TEXTURE_BORDER_COLOR; 

    GL_CLAMP_TO_EDGE 环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后一⾏或者最后一列来进⾏采样。 

    GL_CLAMP_TO_BORDER:在纹理坐标在 0.0到1.0范围之外的 只使⽤边界纹理单元。边界纹理单元是作为围绕基本图像的 额外的行和列,并与基本纹理图像⼀起加载的。

    */ 

    glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE); 

    glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_T,GL_CLAMP_TO_EDGE);

    二、相关表格

    1)OpenGL像素表格:

    2)像素数据的像素类型:

    UNSIGNED_BYTE_3_3_2 

     

    UNSIGNED_BYTE_2_3_3_REV 

    指定分量 RGBA 的排列顺序根据 format 参数确定。分量按照分量高位到低位排列。 

    三、纹理坐标

    顶点坐标:x、y、z

    纹理坐标:s、t、r  --> 其实就是对应的 x、y、z

    纹理坐标范围是 0~1 之间,更多地是描述一种映射关系。纹理坐标默认左下角为原点 (0,0)。图示:

    立方体:

     

    四、案例 

    效果:

    1)三角锥底部(2个三角形)的坐标纹理对应关系:

     

    其他剩余三角形面同理。

    主要代码:

      1 /// 绘制金字塔
      2 void MakePyramid(GLBatch& pyramidBatch)
      3 {
      4     /*1、通过pyramidBatch组建三角形批次
      5       参数1:类型
      6       参数2:顶点数
      7       参数3:这个批次中将会应用1个纹理
      8       注意:如果不写这个参数,默认为0。
      9      */
     10     pyramidBatch.Begin(GL_TRIANGLES, 18, 1);
     11     
     12     /***前情导入
     13      
     14      2)设置纹理坐标
     15      void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);
     16      参数1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0
     17      参数2:s:对应顶点坐标中的x坐标
     18      参数3:t:对应顶点坐标中的y
     19      (s,t,r,q对应顶点坐标的x,y,z,w)
     20      
     21      pyramidBatch.MultiTexCoord2f(0,s,t);
     22      
     23      3)void Vertex3f(GLfloat x, GLfloat y, GLfloat z);
     24       void Vertex3fv(M3DVector3f vVertex);
     25      向三角形批次类添加顶点数据(x,y,z);
     26       pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);
     27     
     28      */
     29     
     30     //塔顶
     31     M3DVector3f vApex = { 0.0f, 1.0f, 0.0f };
     32     M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f };
     33     M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };
     34     M3DVector3f vBackLeft = { -1.0f,  -1.0f, -1.0f };
     35     M3DVector3f vBackRight = { 1.0f,  -1.0f, -1.0f };
     36     
     37     //金字塔底部
     38     // 底部的四边形 = 三角形X + 三角形Y
     39     // 三角形X = (vBackLeft,vBackRight,vFrontRight)
     40     // vBackLeft
     41     pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
     42     pyramidBatch.Vertex3fv(vBackLeft);
     43     
     44     // vBackRight
     45     pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
     46     pyramidBatch.Vertex3fv(vBackRight);
     47     
     48     // vFrontRight
     49     pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
     50     pyramidBatch.Vertex3fv(vFrontRight);
     51     
     52     
     53     // 三角形Y =(vFrontLeft,vBackLeft,vFrontRight)
     54     // vFrontLeft
     55     pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
     56     pyramidBatch.Vertex3fv(vFrontLeft);
     57     
     58     //vBackLeft
     59     pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
     60     pyramidBatch.Vertex3fv(vBackLeft);
     61     
     62     //vFrontRight
     63     pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
     64     pyramidBatch.Vertex3fv(vFrontRight);
     65 
     66     
     67     // 金字塔前面
     68     //三角形:(Apex,vFrontLeft,vFrontRight)
     69     pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
     70     pyramidBatch.Vertex3fv(vApex);
     71 
     72     pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
     73     pyramidBatch.Vertex3fv(vFrontLeft);
     74 
     75     pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
     76     pyramidBatch.Vertex3fv(vFrontRight);
     77     
     78     //金字塔左边
     79     //三角形:(vApex, vBackLeft, vFrontLeft)
     80     pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
     81     pyramidBatch.Vertex3fv(vApex);
     82     
     83     pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
     84     pyramidBatch.Vertex3fv(vBackLeft);
     85     
     86     pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
     87     pyramidBatch.Vertex3fv(vFrontLeft);
     88     
     89     //金字塔右边
     90     //三角形:(vApex, vFrontRight, vBackRight)
     91     pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
     92     pyramidBatch.Vertex3fv(vApex);
     93     
     94     pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
     95     pyramidBatch.Vertex3fv(vFrontRight);
     96 
     97     pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
     98     pyramidBatch.Vertex3fv(vBackRight);
     99     
    100     //金字塔后边
    101     //三角形:(vApex, vBackRight, vBackLeft)
    102     pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    103     pyramidBatch.Vertex3fv(vApex);
    104     
    105     pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    106     pyramidBatch.Vertex3fv(vBackRight);
    107     
    108     pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    109     pyramidBatch.Vertex3fv(vBackLeft);
    110     
    111     //结束批次设置
    112     pyramidBatch.End();
    113 }
    114 
    115 /// 将TGA文件加载为2D纹理。
    116 //参数1:纹理文件名称
    117 //参数2&参数3:需要缩小&放大的过滤器模式设置
    118 //参数4:纹理坐标 环绕模式
    119 bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
    120 {
    121     GLbyte *pBits;
    122     int nWidth, nHeight, nComponents;
    123     GLenum eFormat;
    124     
    125     //1、读纹理位,读取像素 --> 将图片读取为 位图
    126     //参数1:纹理文件名称
    127     //参数2:文件宽度地址
    128     //参数3:文件高度地址
    129     //参数4:文件组件地址
    130     //参数5:文件格式地址
    131     //返回值:pBits,指向图像数据的指针
    132     pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    133     
    134     if(pBits == NULL)// 读取图片数据失败直接返回 false
    135         return false;
    136     
    137     //2、设置纹理参数
    138     //参数1:纹理维度
    139     //参数2:为 S/T 坐标设置模式
    140     //参数3:wrapMode,环绕模式
    141     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    142     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    143     
    144     // z设置过滤方式:纹理放大缩小时怎么显示
    145     //参数1:纹理维度
    146     //参数2:过滤的放大缩小
    147     //参数3: 缩小/放大过滤方式.
    148     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    149     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    150     
    151 
    152     //3.载入纹理
    153     //参数1:纹理维度
    154     //参数2:mip贴图层次
    155     //参数3:纹理单元存储的颜色成分(从读取像素图是获得)
    156     //参数4:加载纹理宽
    157     //参数5:加载纹理高
    158     //参数6:border
    159     //参数7、8:像素数据的数据类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数)
    160     //参数9:指向纹理图像数据的指针
    161     glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
    162                  eFormat, GL_UNSIGNED_BYTE, pBits);
    163     
    164     //使用完毕释放pBits
    165     free(pBits);
    166     
    167     //只有minFilter 等于以下四种模式,才可以生成Mip贴图
    168     //GL_NEAREST_MIPMAP_NEAREST具有非常好的性能,并且闪烁现象非常弱
    169     //GL_LINEAR_MIPMAP_NEAREST常常用于对游戏进行加速,它使用了高质量的线性过滤器
    170     //GL_LINEAR_MIPMAP_LINEAR 和GL_NEAREST_MIPMAP_LINEAR 过滤器在Mip层之间执行了一些额外的插值,以消除他们之间的过滤痕迹。
    171     //GL_LINEAR_MIPMAP_LINEAR 三线性Mip贴图。纹理过滤的黄金准则,具有最高的精度。
    172     if(minFilter == GL_LINEAR_MIPMAP_LINEAR ||
    173        minFilter == GL_LINEAR_MIPMAP_NEAREST ||
    174        minFilter == GL_NEAREST_MIPMAP_LINEAR ||
    175        minFilter == GL_NEAREST_MIPMAP_NEAREST)
    176     //4.纹理生成所有的Mip层
    177     //参数:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
    178     glGenerateMipmap(GL_TEXTURE_2D);
    179  
    180     return true;
    181 }
    182 
    183 // 初始化
    184 void SetupRC()
    185 {
    186     //1.
    187     glClearColor(0.7f, 0.7f, 0.7f, 1.0f );
    188     shaderManager.InitializeStockShaders();
    189     
    190     //2.
    191     glEnable(GL_DEPTH_TEST);
    192     
    193     //3.
    194     //分配纹理对象 参数1:纹理对象个数,参数2:纹理对象指针
    195     glGenTextures(1, &textureID);
    196     
    197     //绑定纹理状态 参数1:纹理状态2D 参数2:纹理对象
    198     glBindTexture(GL_TEXTURE_2D, textureID);
    199     
    200     //将TGA文件加载为2D纹理 --> LoadTGATexture:自定义方法
    201     //参数1:纹理文件名称
    202     //参数2&参数3:需要缩小&放大的过滤器
    203     //参数4:纹理坐标环绕模式
    204     LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
    205     
    206     //4.创造金字塔 pyramidBatch --> MakePyramid:自定义方法
    207     MakePyramid(pyramidBatch);
    208     
    209     //5.
    210     /**相机frame MoveForward(平移)
    211     参数1:Z,深度(屏幕到图形的Z轴距离)
    212      */
    213     cameraFrame.MoveForward(-10);
    214 }
    215 
    216 // 渲染绘制
    217 void RenderScene(void)
    218 {
    219     //1.颜色值&光源位置
    220     static GLfloat vLightPos [] = { 1.0f, 1.0f, 0.0f };
    221     static GLfloat vWhite [] = { 1.0f, 1.0f, 1.0f, 1.0f };
    222     
    223     //2.清理缓存区
    224     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    225     
    226     //3.当前模型视频压栈
    227     modelViewMatrix.PushMatrix();
    228     
    229     //添加照相机矩阵
    230     M3DMatrix44f mCamera;
    231     //从camraFrame中获取一个4*4的矩阵
    232     cameraFrame.GetCameraMatrix(mCamera);
    233     //矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部 将照相机矩阵 与 当前模型矩阵相乘 压入栈顶
    234     modelViewMatrix.MultMatrix(mCamera);
    235     
    236     //创建mObjectFrame矩阵
    237     M3DMatrix44f mObjectFrame;
    238     //从objectFrame中获取矩阵,objectFrame保存的是特殊键位的变换矩阵
    239     objectFrame.GetMatrix(mObjectFrame);
    240     //矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部 将世界变换矩阵 与 当前模型矩阵相乘 压入栈顶
    241     modelViewMatrix.MultMatrix(mObjectFrame);
    242     
    243     //4.绑定纹理,因为我们的项目中只有一个纹理。如果有多个纹理。绑定纹理很重要
    244     glBindTexture(GL_TEXTURE_2D, textureID);
    245     
    246     //5.纹理替换矩阵着色器
    247      /*
    248      参数1:GLT_SHADER_TEXTURE_REPLACE(着色器标签)
    249      参数2:模型视图投影矩阵
    250      参数3:纹理层
    251      */    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);
    252     
    253     //pyramidBatch 绘制
    254     pyramidBatch.Draw();
    255     
    256     //模型视图出栈,恢复矩阵(push一次就要pop一次)
    257     modelViewMatrix.PopMatrix();
    258     
    259     //6.交换缓存区
    260     glutSwapBuffers();
    261 }

    2)Mip 贴图

    当不同大小图的绘制时,缩放规则:每次缩放 1/2  直至最小的图为 1:1 的纹理单元为止,会过滤(比例缩放)成多少份取决于图片的大小 --> 提高渲染性能和显示质量

    2.1)设置 Mip 贴图

    // 设置Mip贴图最基层 

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);

    // 设置Mip贴图最大层

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_BASE_LEVEL,0);

    --> 参数0: 第一层是0,第二层 1 ... ... 以此类推。

    2.2)mip 贴图的过滤方式

    上面代码 204 行:LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);// GL_LINEAR_MIPMAP_NEAREST --> 选择最邻近mip层,并进行线性过滤。

    表格中后 4 种方式方式才可生成 mip 贴图。代码示例:上面的‘主要代码’167行处。

  • 相关阅读:
    简单算法题20200815
    求图的连通子图的个数并保存每个子图的节点python
    java遍历树,并得到每条根到叶子节点的路径
    volatile 对于n=n+1,无效
    java重载(overload)和重写(override)
    对象的上转型对象
    (阿里巴巴笔试题)直线上安装水塔,水塔到直线上其它点的距离之和最小
    选择排序、树形排序、堆排序的java代码实现
    linux里面那些奇奇怪怪但是还没有解决的问题
    Linux使用free命令buff/cache过高
  • 原文地址:https://www.cnblogs.com/zhangzhang-y/p/13360369.html
Copyright © 2011-2022 走看看