Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序

news2024/11/14 5:49:20

OpenGL 学习教程
Android OpenGL ES 学习(一) – 基本概念
Android OpenGL ES 学习(二) – 图形渲染管线和GLSL
Android OpenGL ES 学习(三) – 绘制平面图形
Android OpenGL ES 学习(四) – 正交投屏
Android OpenGL ES 学习(五) – 渐变色
代码工程地址: https://github.com/LillteZheng/OpenGLDemo.git

上一篇 Android OpenGL ES 学习(五) – 渐变色 ,我们已经完成了 三角形的渐变色处理。

这一章,我们学习 GL3.0 特有的 OpenGL 缓存对象,VBO ,VAO 和 EBO。这一章稍微有点吃理解,建议多看多想。

一. VBO

顶点缓冲对象:Vertex Buffer Object,VBO
为什么有这个对象?从Android OpenGL ES 学习(二) – 图形渲染管线和GLSL 这章知道,图形渲染管线的顶点数据是在 CPU 上的,我们需要调用 GL 的指令,把这些数据加载到 GPU 中,每绘制一个顶点,都需要加载一次。
当数据小的时候可以忽略不计,但如果有非常大的数据,频繁的在 CPU 和 GPU 之间传递呢,比如渲染图片,视频。

再比如,我们的渐变色三角形,就18个顶点数据,我们会把它作为输入发送给图形渲染管线的顶点着色器,它会在 CPU 上创建内存,并存储这些顶点数据,然后再把它传给 CPU 。
但如果不止 18 点呢,这里有上万点呢,这无疑是很好性能的。所以,GL在 3.0 可以,引入 VBO 这个顶点缓存对象,它会在 GPU 内存(显存) 中存储大量顶点,好处就是我们可以一次性把大批数据发送给显存,而不是每个顶点发送一次;

从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。

了解原理,看看怎么使用。

1.1 使用 VBO

VBO 的使用非常简单,步骤如下:

  1. 创建缓存区:glGenBuffers
  2. 绑定缓存区到上下文: glBindBuffer
  3. 将顶点数据存在缓冲区: glBindData
  4. 指定如何解析顶点属性数组:glVertexAttribPointer
  5. 绘制:glDrawArrays

创建:

//创建缓存区
val vbo = IntArray(1)
GLES30.glGenBuffers(1,vbo,0)
//绑定缓存区到上下文
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
//将顶点数据存在缓冲区
GLES30.glBufferData(
    GLES30.GL_ARRAY_BUFFER,
    vertexData.capacity() * 4,
    vertexData,
    GLES30.GL_STATIC_DRAW)

主要看 glBufferData 这个方法,它是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数,参数如下:

  1. buffer 对象类型: 有GL_ARRAY_BUFFER,GL_ELEMENT_ARRAY_BUFFER,GL_SHADER_STORAGE_BUFFER 等等。
  2. 传输数据大小((以字节为单位)): 填写 buffer 大小,由于是 float类型,乘以4
  3. 实际数据
  4. 告诉显卡如果管理给定的数据,它有三种形式
    • GL_STATIC_DRAW :数据不会或几乎不会改变
    • GL_DYNAMIC_DRAW:数据会被改变很多。
    • GL_STREAM_DRAW :数据每次绘制时都会改变。

因为三角形的数据不会改变,每次都一样,所以使用 GL_STATIC_DRAW。

使用数据

//绘制位置,注意这里,我们不再填入 vertexData,而是填入数据偏移地址
GLES30.glVertexAttribPointer(
    0, 3, GLES30.GL_FLOAT,
    false, 24, 0
)
GLES30.glEnableVertexAttribArray(0)

//绘制颜色,颜色地址偏移量从3开始,前面3个为位置
vertexData.position(3)
GLES30.glVertexAttribPointer(
    1, 3, GLES30.GL_FLOAT,
    false, 24, 12 //需要指定颜色的地址 3 * 4
)
GLES30.glEnableVertexAttribArray(1)

//解绑数据,因为我们不需要动态更新
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)

注意绘制索引时 glVertexAttribPointer,我们不再填入顶点内存数据,因为我们已经把数据关联到 VBO 上了,所以填入数据偏移地址。
由于位置是首位,所以偏移地址是0,颜色是 3 * 4.

绘制
绘制也比较简单,直接使用 vbo 就可以了。

GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0,3)

效果如下,熟悉的三角形:
在这里插入图片描述

二. VAO

顶点数组对象:Vertex Array Object,VAO
前面说道 VBO 会把顶点数据存到 GPU 显存中,但怎么使用呢?并没有人管理,GL 并不知道怎么去拿这些数据。
比如,我们有这个一个场景,要画两个三角形,构成一个矩形,按照我们上面的做法,也需要两个 VBO 。

顶点数据:

//第一个三角形
 private val POINT_COLOR_DATA = floatArrayOf(
     // positions         // colors
     0.5f, 0.5f, 0.0f,   1.0f, 0.5f, 0.5f,// 右上角
     0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 1.0f,// 右下角
     -0.5f, 0.5f, 0.0f,  0.0f, 0.5f, 1.0f,// 左上角
 )
 //第二个三角形
 private val POINT_COLOR_DATA2 = floatArrayOf(
     // positions         // colors
     0.5f, -0.5f, 0.0f,  1.0f, 0.5f, 0.5f,// 右下角
     -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f,// 左下角
     -0.5f, 0.5f, 0.0f,   0.0f, 0.5f, 1.0f,// 左上角
 )

创建两个vbo

  val vbo = IntArray(2)
private fun useVaoVbo(){
   
    //绑定第一个 vbo
    val vertexData = BufferUtil.createFloatBuffer(POINT_COLOR_DATA)
    GLES30.glGenBuffers(2,vbo,0)
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
    GLES30.glBufferData(
        GLES30.GL_ARRAY_BUFFER,
        vertexData.capacity() * 4,
        vertexData,
        GLES30.GL_STATIC_DRAW)
    //绘制位置
    GLES30.glVertexAttribPointer(
        0, 3, GLES30.GL_FLOAT,
        false, 24, 0
    )
    GLES30.glEnableVertexAttribArray(0)

    //绘制颜色,颜色地址偏移量从3开始,前面3个为位置
    vertexData.position(3)
    GLES30.glVertexAttribPointer(
        1, 3, GLES30.GL_FLOAT,
        false, 24, 12 //需要指定颜色的地址 3 * 4
    )
    GLES30.glEnableVertexAttribArray(1)
    //解绑数据
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)
   


    //绑定第二个 vbo
    val vertexData2 = BufferUtil.createFloatBuffer(POINT_COLOR_DATA2)
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[1])
    GLES30.glBufferData(
        GLES30.GL_ARRAY_BUFFER,
        vertexData2.capacity() * 4,
        vertexData2,
        GLES30.GL_STATIC_DRAW)
    //绘制位置
    GLES30.glVertexAttribPointer(
        0, 3, GLES30.GL_FLOAT,
        false, 24, 0
    )
    GLES30.glEnableVertexAttribArray(0)

    //绘制颜色,颜色地址偏移量从3开始,前面3个为位置
    vertexData2.position(3)
    GLES30.glVertexAttribPointer(
        1, 3, GLES30.GL_FLOAT,
        false, 24, 12 //需要指定颜色的地址 3 * 4
    )
    GLES30.glEnableVertexAttribArray(1)

    //解绑数据
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)
    
}

绘制:

//绘制第一个三角形,右上角
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0,3)

在这里插入图片描述
咦,我们的第一个顶点数据的数据为:

//第一个三角形
private val POINT_COLOR_DATA = floatArrayOf(
    // positions         // colors
    0.5f, 0.5f, 0.0f,   1.0f, 0.5f, 0.5f,// 右上角
    0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 1.0f,// 右下角
    -0.5f, 0.5f, 0.0f,  0.0f, 0.5f, 1.0f,// 左上角
)

应该是下图才对,
在这里插入图片描述
怎么变成第二个顶点的三角形形状了呢?
因为前面我们创建了两个 VBO ,使用 glBufferData 后,VBO[0] 就发送给显卡内存了,同理 VBO[1] 也发给过去,但是GPU 在拿这些数据的时候,并不知道这些数据的地址,就拿了最新的,所以才会显示 VBO[1] 的数据。
怎么处理呢?

回到刚才的 VAO ,它会记录 Buffer Object 中缓存顶点属性的缓存对象的配置信息,相当于我们可以通过 VAO 准确告诉 GPU 缓存对象的地址。如下图:
在这里插入图片描述
它的创建跟 VBO 差不多,只不过方法不一样:

  1. 创建 VAO: glGenVertexArrays
  2. 绑定 VAO : glBindVertexArray ,需要注意,这里绑定了 VAO 后,再绑定 VBO,这样他们才能关联起来。
  3. 解绑数据:GLES30.glBindVertexArray(0)

这样,我们修改一下上面的代码:

private val vao = IntArray(2)
private fun useVaoVbo(){
     val vbo = IntArray(2)
    val vertexData = BufferUtil.createFloatBuffer(POINT_COLOR_DATA)
    //创建 VAO
    GLES30.glGenVertexArrays(2,vao,0)
    // //创建 VBO
    GLES30.glGenBuffers(2,vbo,0)
    //绑定 VAO ,之后再绑定 VBO
    GLES30.glBindVertexArray(vao[0])
    //绑定VBO
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
    GLES30.glBufferData(
        GLES30.GL_ARRAY_BUFFER,
        vertexData.capacity() * 4,
        vertexData,
        GLES30.GL_STATIC_DRAW)
    //绘制位置
    GLES30.glVertexAttribPointer(
        0, 3, GLES30.GL_FLOAT,
        false, 24, 0
    )
    GLES30.glEnableVertexAttribArray(0)

    //绘制颜色,颜色地址偏移量从3开始,前面3个为位置
    vertexData.position(3)
    GLES30.glVertexAttribPointer(
        1, 3, GLES30.GL_FLOAT,
        false, 24, 12 //需要指定颜色的地址 3 * 4
    )
    GLES30.glEnableVertexAttribArray(1)
    //解绑数据
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)
    GLES30.glBindVertexArray(0)


    //绑定第二个 vbo
    val vertexData2 = BufferUtil.createFloatBuffer(POINT_COLOR_DATA2)

   // GLES30.glBindVertexArray(vao[1])
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[1])
    GLES30.glBufferData(
        GLES30.GL_ARRAY_BUFFER,
        vertexData2.capacity() * 4,
        vertexData2,
        GLES30.GL_STATIC_DRAW)
    //绘制位置
    GLES30.glVertexAttribPointer(
        0, 3, GLES30.GL_FLOAT,
        false, 24, 0
    )
    GLES30.glEnableVertexAttribArray(0)

    //绘制颜色,颜色地址偏移量从3开始,前面3个为位置
    vertexData2.position(3)
    GLES30.glVertexAttribPointer(
        1, 3, GLES30.GL_FLOAT,
        false, 24, 12 //需要指定颜色的地址 3 * 4
    )
    GLES30.glEnableVertexAttribArray(1)

    //解绑数据
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)
    GLES30.glBindVertexArray(0)
}

绘制,使用 vao

//绘制第一个三角形
GLES30.glBindVertexArray(vao[0])
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0,3)

可以看到,我们第一个三角形可以被正确识别了
在这里插入图片描述

三. EBO / IBO

元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO

什么叫元素缓冲对象呢,你可以简单理解成 顶点数据复用,举个栗子:
上面我们要画一个矩形,需要用到两个三角形,顶点如下:

//第一个三角形
private val POINT_COLOR_DATA = floatArrayOf(
    // positions         // colors
    0.5f, 0.5f, 0.0f,   1.0f, 0.5f, 0.5f,// 右上角
    0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 1.0f,// 右下角
    -0.5f, 0.5f, 0.0f,  0.0f, 0.5f, 1.0f,// 左上角
)
//第二个三角形
private val POINT_COLOR_DATA2 = floatArrayOf(
    // positions         // colors
    0.5f, -0.5f, 0.0f,  1.0f, 0.5f, 0.5f,// 右下角
    -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f,// 左下角
    -0.5f, 0.5f, 0.0f,   0.0f, 0.5f, 1.0f,// 左上角
)

但注意到,对角线的数据,是相同的,有几个顶点叠加了,一个矩形只有4个角而不是6个,这样就产生50%的额外开销。

当有成千上百个三角形时,就会产生一大堆浪费。

一个比较好的解决方案,就是存储不同的顶点,并按照顺序去绘制,这样,我们只需要4个顶点,就能绘制矩形了。而 EBO 就是这样一个缓冲对象,它会存储 GL 用来决定要绘制哪些顶点的索引。
所以,我们先定义不重复的顶点:

private val POINT_RECT_DATA2 = floatArrayOf(
     // 矩形4个顶点
     0.5f, 0.5f, 0.0f,   1.0f, 0.5f, 0.5f,// 右上角
     0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 1.0f,// 右下角

     -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f,// 左下角
     -0.5f, 0.5f, 0.0f,   0.0f, 0.5f, 1.0f,// 左上角
 )

需要按顺序的数据:

private val indeices = intArrayOf(
    // 注意索引从0开始!
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形

    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
)

它的创建也跟 VAO 特别像

  1. 创建对象:glGenBuffers
  2. 绑定对象到上下文:glBindBuffer
  3. 绑定顺序的数据:glBufferData ,根据前面的解释,这个对象参数,选择 GL_ELEMENT_ARRAY_BUFFER

代码如下:

//创建 ebo
GLES30.glGenBuffers(1,ebo,0)
//绑定 ebo 到上下文
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER,ebo[0])
//昂丁
GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER,
    indexData.capacity() * 4,
    indexData,
    GLES30.GL_STATIC_DRAW
)

跟 VBO 和 VAO 结合后的代码:

private fun useVaoVboAndEbo(){
val vertexData = BufferUtil.createFloatBuffer(POINT_RECT_DATA2)
val indexData = BufferUtil.createIntBuffer(indeices)


//使用 vbo,vao 优化数据传递
//创建 VAO
GLES30.glGenVertexArrays(1,vao,0)
// //创建 VBO
GLES30.glGenBuffers(1,vbo,0)
//绑定 VAO ,之后再绑定 VBO
GLES30.glBindVertexArray(vao[0])
//绑定VBO
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,vbo[0])
GLES30.glBufferData(
    GLES30.GL_ARRAY_BUFFER,
    vertexData.capacity() * 4,
    vertexData,
    GLES30.GL_STATIC_DRAW)
//创建 ebo
GLES30.glGenBuffers(1,ebo,0)
//绑定 ebo 到上下文
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER,ebo[0])
//昂丁
GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER,
    indexData.capacity() * 4,
    indexData,
    GLES30.GL_STATIC_DRAW
)

//绘制位置
GLES30.glVertexAttribPointer(
    0, 3, GLES30.GL_FLOAT,
    false, 24, 0
)
GLES30.glEnableVertexAttribArray(0)

//绘制颜色,颜色地址偏移量从3开始,前面3个为位置
vertexData.position(3)
GLES30.glVertexAttribPointer(
    1, 3, GLES30.GL_FLOAT,
    false, 24, 12 //需要指定颜色的地址 3 * 4
)
GLES30.glEnableVertexAttribArray(1)

GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)

GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0)
GLES30.glBindVertexArray(0)
//注意顺序,ebo 要在 vao 之后
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER,0)
}

关于 EBO 为啥要再 VAO 之后,可以参考 https://www.zhihu.com/question/39082624

绘制:

GLES30.glBindVertexArray(vao[0])
GLES30.glDrawElements(GLES30.GL_TRIANGLE_STRIP,6,GLES30.GL_UNSIGNED_INT,0)

注意这里把 glDrawArrays 替换成了 glDrawElements ,表示我们要从索引缓存中渲染三角形。它的参数如下:

  1. 绘制类型,这里也是三角形 GLES30.GL_TRIANGLE_STRIP
  2. 顶点个数:6 个,实际是两个三角形
  3. 索引的类型,这里是GL_UNSIGNED_INT
  4. 最后是偏移量,这里填0即可

glDrawElements函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER目标的EBO中获取其索引。但前面说道,VAO 可以关联 buffer object 的索引,所以,统一使用 VAO 去关联即可。
如下图:
在这里插入图片描述
可以看到,VAO 实际上关联了 VAO 和 EBO 。
效果:
在这里插入图片描述
参考:
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
https://www.zhihu.com/question/39082624
https://juejin.cn/post/7149775557398364167

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

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

相关文章

【ARIMA时序预测】基于ARIMA实现时间序列数据预测附matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。 🍎个人主页:Matlab科研工作室 🍊个人信条:格物致知。 更多Matlab仿真内容点击👇 智能优化算法 …

Unity常用的三种拖拽方法(内置方法 + 接口 + Event Trigger组件)

目录 内置方法OnMouseDrag【对象含有Collider组件】 配对小游戏 Event Trigger组件 接口 窗口小案例 内置方法OnMouseDrag【对象含有Collider组件】 OnMOuseOver()检测鼠标是否进入到这个2D贴图 当鼠标进入或离开2D贴图,会相应的放大、缩小 private void OnMo…

[附源码]计算机毕业设计springboot校园快递柜存取件系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

山外山通过注册:拟募资12亿 大健康与华盖信诚是股东

雷递网 雷建平 12月2日重庆山外山血液净化技术股份有限公司(简称:“山外山”)日前通过注册,准备在科创板上市。山外山计划募资12.47亿元,其中,8.63亿用于血液净化设备及高值耗材产业化项目,1.64…

【Python基础系列】Part2. 列表

二、列表 1.列表介绍 定义:列表是由一系列按照一定顺序排列的元素组成。 Python中用[]表示列表,用,分割元素。 number ["one", "two", "three"] print(number)# [one, two, three]列表中的元素可以是不同类型 numbe…

netsh interface portproxy端口转发,从本地端口到本地端口不起作用的解决办法

开启IP V6 你虽然可能用不到IPV6,但是有些系统是需要用到IPV6的dll来做端口转发的. 如图,确保你联网的连接已经开启 IPV6 检查IP Helper服务 打开任务管理器 点击 服务 查看iphlpsvc是否启动状态,点击右键如果显示的是停止,就是已经启动了. 如果显示"启动服务"则…

drools规则引擎并发结果不准确问题记录

思路 首先,drools的整体思路比较简单,一个是加载,一个是执行! 加载:把一个比较复杂的关系运算想办法放到drools里面! 执行:让drools去计算这个复杂的运算,最终我们只需要取结果就好&…

广域网技术——SR-MPLS技术基础理论讲解

目录 SR-MPLS基础概念 使用Segment Routeing MPLS技术的优点 Segment Routeing MPLS的基本原理 SRGB Segment ID Bind SID 粘连标签 OSPF对于SR-MPLS的扩展 OSPF对邻接SID做了细分 10类LSA定义的TLV类型 10类LSA定义的TLV的报文格式 ISIS对SR-MPLS的扩展…

详解设计模式:模版方法模式

模板方法模式(Template Method Pattern)也被称为模板模式(Template Pattern),是在 GoF 23 种设计模式中定义了的行为型模式。 模板方法模式 定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使…

若依框架RuoYi项目运行启动教程【傻瓜式教程】

启动若依项目 1.官网下载代码 若依官网 若依在线文档 首先去官网下载代码 链接到码云下载,要么用git下载要么压缩包下载。 然后再IDEA打开项目 想要运行就要搭建好环境 2.搭建若依环境 按照文档要求配置环境 JDK > 1.8 (推荐1.8版本) Mysql > 5.7.0 (推…

Stable Diffusion 2.0 来了

Stable Diffusion 一经发布,就立刻在业界掀起巨大的波浪。我个人后知后觉,直到 Stable Diffusion V1.4 版本发布,才接触 Stable Diffusion (之前使用的是 Disco Diffusion)。这段时间,SD 团队也没闲着,很快就发布了 V2…

【华为上机真题 2022】停车场车辆统计

🎈 作者:Linux猿 🎈 简介:CSDN博客专家🏆,华为云享专家🏆,Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我,关注我,有问题私聊! &…

【Python】推荐三个好玩的图像处理库

1. 引言 Python是一门高级语言,它可以实现很多功能。Python强大的原因是什么?某种程度上,在于它所拥有的现成的库,使其在编程的各个方向上都易于使用。在本文中,我将向大家展示一些Python库,这些库非常有用…

node.js的模块化

目录 一、模块化的概念 1.什么是模块化 2.编程领域中的模块化 二、node.js中模块的分类 三、require() 加载模块 四. 模块作用域 五、module对象 六、module.exports对象 七、exports对象 八、CommonJS规定: 九、关于包(第三方模块) 十、解决…

阿里P8高级专家,耗时多年整理SpringBoot指南文档

前言 相信程序员们已经看过甚至动手操作过很多的springboot项目,在项目操作中需要各种插件的支持,其实,可能还有很多大家不知道的但是很方便的操作,小编今天就给大家把这份PDF分享出来,绝对是你以前没有见到过的。 1、…

springboot读取yml文件中的list列表、数组、map集合和对象

前言 springboot配置文件yml类型简单的风格,十分受大家的欢迎,支持字符string类型,支持列表list类型,支持集合map类型,支持数组array类型,支持类对象类型,下面我们来实战下这些形式的配置如何取…

聚观早报 | 国美电器被申请破产清算;首款太阳能汽车投入生产

今日要闻:网传国美电器被申请破产清算;全球首款太阳能汽车投入生产;苹果头显配套系统已改名为xrOS;马斯克计划植入脑机接口设备;特斯拉即将推出自动驾驶出租车网传国美电器被申请破产清算 12 月 2 日消息,据…

网站都变成灰色,有哪些方法可以快速实现?

有些时候我们需要把网站页面变成黑白色或灰色,特别是对于一些需要悼念的日子,以及一些影响力很大的伟人逝世或纪念日的时候,都会让网站的全部网页变成灰色(黑白色),以表示我们对逝者或者英雄的缅怀和悼念。…

在校大学生如何申请软著,手把手教会你(内有免费模板)

目录 一.前言 二.以学校为单位全流程申请(以我的学校为例) 1.问问导员谁负责管软著申请这块的,联系他,问需要什么。 2.为了防止学生买软著转头申请 3.按以下要求准备材料 4.没问题就发给老师,一般要破费一下 5.…

View基础知识-位置大小和滑动

前言 这篇文章可以作为基础看看,但是有时候基础就是细节,不一定所有人都记得,所以基础也要记录一下。都熟悉的话也可以看看其他系列文章: View事件分发机制(源码分析篇) Android一步一步追踪View的工作原…