当前位置: 首页 > news >正文

OpenGL ES 03 加载3张图片并做混合处理

OpenGL ES 02 加载3张图片并做混合处理

  • 什么是纹理单元
  • 纹理单元的作用
    • 使用纹理单元的步骤
    • 详细解释
    • 加载图片并绑定到到GPU纹理单元
    • 采样器的设置
      • 1.设置采样器变量的纹理单元编号,目的是为了告诉纹理采样器,从哪个纹理单元采集数据
      • 2.如果你没有显式地设置采样器变量的纹理单元编号,OpenGL ES 可能会默认将所有采样器变量绑定到纹理单元 0。
      • 3.这意味着所有的采样器变量(caodi, huangtu, noise)都会从纹理单元 0 中采样数据
  • 顶点着色器(fragment.glsl)
  • 片段着色器(fragment.glsl)
  • Shader类封装
  • OpenGL ES 页面整体代码
        • `setupLayer` 方法
        • `setupContext` 方法
        • `setupRenderBuffers` 方法
        • `setupFrameBuffer` 方法
        • `prepareShader` 方法
        • `prepareVAOAndVBO` 方法
        • `loadImageToGPUTexture` 方法
        • `renderLayer` 方法
    • 总结
    • 素材
  • 最终效果
    • 请添加图片描述

什么是纹理单元

纹理单元(Texture Unit)是 OpenGL 和 OpenGL ES 中的一个概念,用于管理和绑定多个纹理对象,以便在着色器中进行纹理采样操作。纹理单元允许你在一个渲染过程中使用多个纹理,而无需频繁地绑定和解绑纹理对象。

纹理单元的作用

  1. 管理多个纹理

    • 纹理单元允许你同时绑定多个纹理对象。每个纹理单元都有一个唯一的编号(索引),你可以通过这个编号来引用特定的纹理单元。
    • 这使得在一个渲染过程中可以使用多个纹理,而无需频繁地绑定和解绑纹理对象。
  2. 在着色器中进行纹理采样

    • 在着色器中,你可以使用采样器(如 sampler2D)从绑定到特定纹理单元的纹理对象中采样颜色数据。
    • 通过将采样器变量与纹理单元绑定,着色器可以从不同的纹理单元中获取数据,从而实现复杂的纹理效果。

使用纹理单元的步骤

加载Image图片,并绑定到对应的纹理单元

func loadImageToGPUTexture(from path: String, index: Int) {guard let image = UIImage(named: path)?.cgImage else {fatalError("Failed to load image at path: \(path)")}let width = image.widthlet height = image.heightlet colorSpace = CGColorSpaceCreateDeviceRGB()let rawData = calloc(height * width * 4, MemoryLayout<GLubyte>.size)let bytesPerPixel = 4let bytesPerRow = bytesPerPixel * widthlet bitsPerComponent = 8let context = CGContext(data: rawData,width: width,height: height,bitsPerComponent: bitsPerComponent,bytesPerRow: bytesPerRow,space: colorSpace,bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)context?.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))var texture: GLuint = 0glGenTextures(1, &texture)glActiveTexture(GLenum(GL_TEXTURE0 + Int32(index)))glBindTexture(GLenum(GL_TEXTURE_2D), texture)glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), rawData)free(rawData)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_NEAREST)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_REPEAT)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_REPEAT)}

这个方法 loadImageToGPUTexture(from:index:) 的作用是将指定路径的图像加载到 GPU 的纹理单元中,以便在 OpenGL ES 中使用。以下是对该方法的详细解释,包括每个步骤的作用和流程。

详细解释

  1. 加载图像

    guard let image = UIImage(named: path)?.cgImage else {fatalError("Failed to load image at path: \(path)")
    }
    
    • 使用 UIImage(named:) 加载指定路径的图像,并将其转换为 CGImage
    • 如果图像加载失败,程序将终止并输出错误信息。
  2. 获取图像宽度和高度

    let width = image.width
    let height = image.height
    
    • 获取图像的宽度和高度,以便后续使用。
  3. 创建颜色空间和原始数据缓冲区

    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let rawData = calloc(height * width * 4, MemoryLayout<GLubyte>.size)
    let bytesPerPixel = 4
    let bytesPerRow = bytesPerPixel * width
    let bitsPerComponent = 8
    
    • 创建一个 RGB 颜色空间。
    • 分配一个缓冲区 rawData 用于存储图像的原始像素数据。缓冲区大小为图像的宽度 * 高度 * 每像素字节数(4 字节,RGBA)。
  4. 创建 CGContext 并绘制图像

    let context = CGContext(data: rawData,width: width,height: height,bitsPerComponent: bitsPerComponent,bytesPerRow: bytesPerRow,space: colorSpace,bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)context?.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
    
    • 创建一个 CGContext,用于将图像绘制到缓冲区 rawData 中。
    • 使用 context?.draw(image, in:) 将图像绘制到缓冲区中。
  5. 生成和绑定纹理

    var texture: GLuint = 0
    glGenTextures(1, &texture)
    glActiveTexture(GLenum(GL_TEXTURE0 + Int32(index)))
    glBindTexture(GLenum(GL_TEXTURE_2D), texture)
    
    • 使用 glGenTextures 生成一个纹理对象,并将其 ID 存储在 texture 变量中。
    • 使用 glActiveTexture 激活指定的纹理单元(GL_TEXTURE0 + index)。
    • 使用 glBindTexture 将生成的纹理对象绑定到当前激活的纹理单元。
  6. 上传纹理数据到 GPU

    glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), rawData)
    
    • 使用 glTexImage2D 将缓冲区 rawData 中的图像数据上传到 GPU 的纹理对象中。
    • 参数说明:
      • GL_TEXTURE_2D:目标纹理类型。
      • 0:纹理的级别(通常为 0)。
      • GL_RGBA:纹理内部格式。
      • widthheight:纹理的宽度和高度。
      • 0:边框宽度(必须为 0)。
      • GL_RGBA:像素数据的格式。
      • GL_UNSIGNED_BYTE:像素数据的类型。
      • rawData:指向像素数据的指针。
  7. 释放原始数据缓冲区

    free(rawData)
    
    • 释放之前分配的缓冲区 rawData,以避免内存泄漏。
  8. 设置纹理参数

    glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)
    glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_NEAREST)
    glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_REPEAT)
    glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_REPEAT)
    
    • 使用 glTexParameteri 设置纹理参数:
      • GL_TEXTURE_MAG_FILTER:设置纹理放大时的过滤方式(GL_LINEAR 为线性过滤)。
      • GL_TEXTURE_MIN_FILTER:设置纹理缩小时的过滤方式(GL_NEAREST 为邻近过滤)。
      • GL_TEXTURE_WRAP_SGL_TEXTURE_WRAP_T:设置纹理在 S 和 T 方向上的环绕方式(GL_REPEAT 为重复)。

加载图片并绑定到到GPU纹理单元

  // 加载图片到GPU纹理单元0loadImageToGPUTexture(from: "caodi", index: 0)// 加载图片到GPU纹理单元1loadImageToGPUTexture(from: "huangtu", index: 1)// 加载图片到GPU纹理单元2loadImageToGPUTexture(from: "noise", index: 2)

采样器的设置

1.设置采样器变量的纹理单元编号,目的是为了告诉纹理采样器,从哪个纹理单元采集数据

2.如果你没有显式地设置采样器变量的纹理单元编号,OpenGL ES 可能会默认将所有采样器变量绑定到纹理单元 0。

3.这意味着所有的采样器变量(caodi, huangtu, noise)都会从纹理单元 0 中采样数据

        shader.setInt(name: "caodi", value: 0)shader.setInt(name: "huangtu", value: 1)shader.setInt(name: "noise", value: 2)

顶点着色器(fragment.glsl)

#version 300 es
// 接收顶点数据
layout (location = 0) in vec3 aPos; // 这里的location对应的是顶点属性的索引
layout (location = 1) in vec2 aUV; // 这里的location对应的是顶点属性的索引
out vec2 uv;void main()
{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);uv = aUV;
}

片段着色器(fragment.glsl)

#version 300 esprecision mediump float;out vec4 FragColor;
in vec2 uv;
// 纹理单元采样器, 必须要跟纹理单元对应上
uniform sampler2D caodi;
// 纹理单元采样器, 必须要跟纹理单元对应上
uniform sampler2D huangtu;
// 纹理单元采样, 必须要跟纹理单元对应上
uniform sampler2D noise;void main()
{// 从一张纹理图片中采样uv对应位置的颜色vec4 caodiColor = texture(caodi, uv);vec4 huangtuColor = texture(huangtu, uv);vec4 noiceColor = texture(noise, uv);// 将三张纹理图片的颜色混合vec4 finalColor = mix(caodiColor, huangtuColor, noiceColor.r);FragColor = vec4(finalColor.rgb, 1.0);
}

Shader类封装

定义了一个 Shader 类,用于加载、编译和链接 OpenGL 着色器程序,并提供了设置 uniform 变量和使用着色器程序的方法

import UIKitclass Shader: NSObject {var shaderProgram: GLuint = 0private func loadShaderSource(from file: String) -> String? {guard let path = Bundle.main.path(forResource: file, ofType: "glsl") else {print("Failed to find shader file: \(file)")return nil}do {let source = try String(contentsOfFile: path, encoding: .utf8)return source} catch {print("Failed to load shader file: \(file), error: \(error)")return nil}}func setInt(name: String, value: Int) {// 通过location设置uniform的值glUniform1i(glGetUniformLocation(shaderProgram, name), GLint(value))}func begin() {glUseProgram(shaderProgram)}func compileShader(vert: String, frag: String) {// 读取着色器源代码guard let vertexSource = loadShaderSource(from: vert),let fragmentSource = loadShaderSource(from: frag)else {return}// 打印着色器源代码print("Vertex Shader Source:\n\(vertexSource)")print("Fragment Shader Source:\n\(fragmentSource)")// 创建着色器程序let vertexShader = glCreateShader(GLenum(GL_VERTEX_SHADER))let fragmentShader = glCreateShader(GLenum(GL_FRAGMENT_SHADER))// 将着色器源码附加到着色器对象上vertexSource.withCString { ptr invar p: UnsafePointer<GLchar>? = UnsafePointer<GLchar>(ptr)glShaderSource(vertexShader, 1, &p, nil)}fragmentSource.withCString { ptr invar p: UnsafePointer<GLchar>? = UnsafePointer<GLchar>(ptr)glShaderSource(fragmentShader, 1, &p, nil)}// 编译顶点着色器glCompileShader(vertexShader)// 检查编译错误var status: GLint = 0glGetShaderiv(vertexShader, GLenum(GL_COMPILE_STATUS), &status)if status == GL_FALSE {var logLength: GLint = 0glGetShaderiv(vertexShader, GLenum(GL_INFO_LOG_LENGTH), &logLength)// Allocate buffer with an extra byte for the null terminatorlet bufferLength = Int(logLength) + 1var log = [GLchar](repeating: 0, count: bufferLength)// Get the shader info logglGetShaderInfoLog(vertexShader, logLength, nil, &log)// Convert the buffer to a Swift stringif let logString = String(validatingUTF8: log) {print("编译 顶点着色器 error: \(logString)")} else {print("编译 顶点着色器 error: Failed to retrieve log.")}return}// 编译片元着色器glCompileShader(fragmentShader)// 检查编译错误glGetShaderiv(fragmentShader, GLenum(GL_COMPILE_STATUS), &status)if status == GL_FALSE {var logLength: GLint = 0glGetShaderiv(fragmentShader, GLenum(GL_INFO_LOG_LENGTH), &logLength)// Allocate buffer with an extra byte for the null terminatorlet bufferLength = Int(logLength) + 1var log = [GLchar](repeating: 0, count: bufferLength)// Get the shader info logglGetShaderInfoLog(fragmentShader, logLength, nil, &log)// Convert the buffer to a Swift stringif let logString = String(validatingUTF8: log) {print("编译 片元着色器 error: \(logString)")} else {print("编译 片元着色器 error: Failed to retrieve log.")}return}// 创建程序对象并链接着色器shaderProgram = glCreateProgram()glAttachShader(shaderProgram, vertexShader)glAttachShader(shaderProgram, fragmentShader)glLinkProgram(shaderProgram)var linkStatus: GLint = 0// 获取链接状态glGetProgramiv(shaderProgram, GLenum(GL_LINK_STATUS), &linkStatus)if linkStatus == GL_FALSE {NSLog("link error")let message = UnsafeMutablePointer<GLchar>.allocate(capacity: 512)glGetProgramInfoLog(shaderProgram, GLsizei(MemoryLayout<GLchar>.size * 512), nil, message)let str = String(utf8String: message)print("error = \(str ?? "没获取到错误信息")")return} else {NSLog("link success")}// 删除着色器对象,因为它们已经链接到程序对象中glDeleteShader(vertexShader)glDeleteShader(fragmentShader)}
}

OpenGL ES 页面整体代码

//
//  ViewController.swift
//  OpenGLESLoadImage
//
//  Created by anker on 2024/12/17.
//import GLKit
import UIKit/**渲染一个图片纹理*/
class ViewController: UIViewController {var eaglLayer: CAEAGLLayer!var myContext: EAGLContext!var shader = Shader()var vao = GLuint()var renderBuffer = GLuint()var frameBuffer = GLuint()var fbo = GLuint()var fboTexture = GLuint()override func viewDidLoad() {super.viewDidLoad()// 设置渲染显示区域setupLayer()// 初始化上下文setupContext()// 设置帧缓冲区setupRenderBuffers()setupFrameBuffer()// 准备着色器prepareShader()prepareVAOAndVBO()// 加载图片到GPU纹理单元0loadImageToGPUTexture(from: "caodi", index: 0)// 加载图片到GPU纹理单元1loadImageToGPUTexture(from: "huangtu", index: 1)// 加载图片到GPU纹理单元2loadImageToGPUTexture(from: "noise", index: 2)renderLayer()}func setupLayer() {eaglLayer = CAEAGLLayer()eaglLayer.frame = view.frameeaglLayer.isOpaque = trueview.layer.addSublayer(eaglLayer)}func setupContext() {if let context = EAGLContext(api: .openGLES3) {EAGLContext.setCurrent(context)myContext = contextprint("Create context success")} else {print("Create context failed!")}}// 生成和绑定渲染缓冲区对象,为渲染缓冲区分配存储空间,生成和绑定帧缓冲区对象,并将渲染缓冲区附加到帧缓冲区的颜色附件点func setupRenderBuffers() {// 生成一个渲染缓冲区对象,并将其ID存储在,colorRenderBuffer变量中。glGenRenderbuffers(1, &renderBuffer)//将生成的渲染缓冲区对象绑定到当前的 OpenGL ES 渲染缓冲区目标,使其成为当前的渲染缓冲区。之后的渲染操作将会使用这个缓冲区。glBindRenderbuffer(GLenum(GL_RENDERBUFFER), renderBuffer)}func setupFrameBuffer() {// 生成一个帧缓冲区对象,并将其 ID 存储在 frameBuffer 变量中。帧缓冲区对象是一个用于管理多个渲染缓冲区和纹理附件的对象。glGenFramebuffers(1, &frameBuffer)// 生成的帧缓冲区对象绑定到当前的 OpenGL ES 帧缓冲区目标,使其成为当前的帧缓冲区。之后的渲染操作将会使用这个帧缓冲区。glBindFramebuffer(GLenum(GL_FRAMEBUFFER), frameBuffer)// 将之前生成并绑定的渲染缓冲区对象附加到当前帧缓冲区的颜色附件点。颜色附件点是帧缓冲区的一部分,用于存储颜色信息。glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), renderBuffer)// 方法为当前绑定的渲染缓冲区分配存储空间,并将其与 CAEAGLLayer 关联。myContext.renderbufferStorage(Int(GL_RENDERBUFFER), from: eaglLayer)}func prepareShader() {shader.compileShader(vert: "vertex", frag: "fragment")}func prepareVAOAndVBO() {let positions: [GLfloat] = [-1, -1, 0.0, // 左下角1, -1, 0.0, // 右下角-1, 1, 0.0, // 左上角1, 1, 0.0 // 左上角]let colors: [GLfloat] = [1.0, 0.2, 0.2, 1.0,0.5, 1.0, 0.2, 1.0,0.5, 0.5, 1.0, 1.0]// uvs 数据 2D纹理坐标,坐标系的左下角是(0, 0),右上角是(1, 1), 代表图片纹理的取值区域let uvs: [GLfloat] = [0.0, 0.0,1.0, 0.0,0.0, 1.0,1.0, 1.0]let indices: [GLubyte] = [0, 1, 2, 3]var positionVBO = GLuint()glGenBuffers(1, &positionVBO)glBindBuffer(GLenum(GL_ARRAY_BUFFER), positionVBO)glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * positions.count, positions, GLenum(GL_STATIC_DRAW))var uvVBO = GLuint()glGenBuffers(1, &uvVBO)glBindBuffer(GLenum(GL_ARRAY_BUFFER), uvVBO)glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * uvs.count, uvs, GLenum(GL_STATIC_DRAW))var ebo = GLuint()glGenBuffers(1, &ebo)glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), ebo)glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER), MemoryLayout<GLubyte>.size * indices.count, indices, GLenum(GL_STATIC_DRAW))glGenVertexArrays(1, &vao)glBindVertexArray(vao)glBindBuffer(GLenum(GL_ARRAY_BUFFER), positionVBO)glVertexAttribPointer(0, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 3), nil)glEnableVertexAttribArray(0)// uvglBindBuffer(GLenum(GL_ARRAY_BUFFER), uvVBO)glVertexAttribPointer(1, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 2), nil)glEnableVertexAttribArray(1)glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), ebo)glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)glBindVertexArray(0)}func loadImageToGPUTexture(from path: String, index: Int) {guard let image = UIImage(named: path)?.cgImage else {fatalError("Failed to load image at path: \(path)")}let width = image.widthlet height = image.heightlet colorSpace = CGColorSpaceCreateDeviceRGB()let rawData = calloc(height * width * 4, MemoryLayout<GLubyte>.size)let bytesPerPixel = 4let bytesPerRow = bytesPerPixel * widthlet bitsPerComponent = 8let context = CGContext(data: rawData,width: width,height: height,bitsPerComponent: bitsPerComponent,bytesPerRow: bytesPerRow,space: colorSpace,bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)context?.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))var texture: GLuint = 0glGenTextures(1, &texture)glActiveTexture(GLenum(GL_TEXTURE0 + Int32(index)))glBindTexture(GLenum(GL_TEXTURE_2D), texture)glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), rawData)free(rawData)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_NEAREST)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_REPEAT)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_REPEAT)}func renderLayer() {glClearColor(0.0, 0.0, 0.0, 1.0)glClear(GLbitfield(GL_COLOR_BUFFER_BIT))glViewport(GLint(0), GLint(0), GLsizei(view.frame.size.width), GLsizei(view.frame.size.height))shader.begin()//解绑之前的帧缓冲区,恢复默认帧缓冲区glBindFramebuffer(GLenum(GL_FRAMEBUFFER), 0)//指定的帧缓冲区(Frame Buffer)绑定到当前的 OpenGL ES 渲染上下文中,使frameBuffer帧缓冲区其成为当前的渲染目标glBindFramebuffer(GLenum(GL_FRAMEBUFFER), frameBuffer)//设置采样器变量的纹理单元编号,目的是为了告诉纹理采样器,从哪个纹理单元采集数据//如果你没有显式地设置采样器变量的纹理单元编号,OpenGL ES 可能会默认将所有采样器变量绑定到纹理单元 0。//这意味着所有的采样器变量(caodi, huangtu, noise)都会从纹理单元 0 中采样数据shader.setInt(name: "caodi", value: 0)shader.setInt(name: "huangtu", value: 1)shader.setInt(name: "noise", value: 2)// 绘制一个全屏四边形,将FBO纹理渲染到屏幕上// 你需要设置适当的顶点和纹理坐标// 这里假设你已经有一个VAO和VBO来绘制全屏四边形glBindVertexArray(0)glBindVertexArray(vao)glDrawElements(GLenum(GL_TRIANGLE_STRIP), 4, GLenum(GL_UNSIGNED_BYTE), nil)// 将渲染缓冲区的内容呈现到屏幕上myContext.presentRenderbuffer(Int(GL_RENDERBUFFER))}
}

这个代码展示了如何在 iOS 上使用 OpenGL ES 3.0 渲染一个带有纹理的四边形。以下是对代码的详细分析,包括每个步骤的作用和流程。

setupLayer 方法
func setupLayer() {eaglLayer = CAEAGLLayer()eaglLayer.frame = view.frameeaglLayer.isOpaque = trueview.layer.addSublayer(eaglLayer)
}

setupLayer 方法创建一个 CAEAGLLayer,用于显示 OpenGL ES 渲染结果,并将其添加到视图的图层中。

setupContext 方法
func setupContext() {if let context = EAGLContext(api: .openGLES3) {EAGLContext.setCurrent(context)myContext = contextprint("Create context success")} else {print("Create context failed!")}
}

setupContext 方法创建一个 OpenGL ES 3.0 渲染上下文,并将其设置为当前上下文。

setupRenderBuffers 方法

生成和绑定渲染缓冲区对象,为渲染缓冲区分配存储空间,生成和绑定帧缓冲区对象,并将渲染缓冲区附加到帧缓冲区的颜色附件点

func setupRenderBuffers() {glGenRenderbuffers(1, &renderBuffer)glBindRenderbuffer(GLenum(GL_RENDERBUFFER), renderBuffer)
}

setupRenderBuffers 方法生成并绑定一个渲染缓冲区对象。

setupFrameBuffer 方法
func setupFrameBuffer() {glGenFramebuffers(1, &frameBuffer)glBindFramebuffer(GLenum(GL_FRAMEBUFFER), frameBuffer)glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), renderBuffer)myContext.renderbufferStorage(Int(GL_RENDERBUFFER), from: eaglLayer)
}

setupFrameBuffer 方法生成并绑定一个帧缓冲区对象,并将渲染缓冲区附加到帧缓冲区的颜色附件点。

prepareShader 方法
func prepareShader() {shader.compileShader(vert: "vertex", frag: "fragment")
}

prepareShader 方法编译和链接顶点着色器和片段着色器。

prepareVAOAndVBO 方法
func prepareVAOAndVBO() {let positions: [GLfloat] = [-1, -1, 0.0, // 左下角1, -1, 0.0, // 右下角-1, 1, 0.0, // 左上角1, 1, 0.0 // 左上角]let colors: [GLfloat] = [1.0, 0.2, 0.2, 1.0,0.5, 1.0, 0.2, 1.0,0.5, 0.5, 1.0, 1.0]let uvs: [GLfloat] = [0.0, 0.0,1.0, 0.0,0.0, 1.0,1.0, 1.0]let indices: [GLubyte] = [0, 1, 2, 3]var positionVBO = GLuint()glGenBuffers(1, &positionVBO)glBindBuffer(GLenum(GL_ARRAY_BUFFER), positionVBO)glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * positions.count, positions, GLenum(GL_STATIC_DRAW))var uvVBO = GLuint()glGenBuffers(1, &uvVBO)glBindBuffer(GLenum(GL_ARRAY_BUFFER), uvVBO)glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * uvs.count, uvs, GLenum(GL_STATIC_DRAW))var ebo = GLuint()glGenBuffers(1, &ebo)glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), ebo)glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER), MemoryLayout<GLubyte>.size * indices.count, indices, GLenum(GL_STATIC_DRAW))glGenVertexArrays(1, &vao)glBindVertexArray(vao)glBindBuffer(GLenum(GL_ARRAY_BUFFER), positionVBO)glVertexAttribPointer(0, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 3), nil)glEnableVertexAttribArray(0)glBindBuffer(GLenum(GL_ARRAY_BUFFER), uvVBO)glVertexAttribPointer(1, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 2), nil)glEnableVertexAttribArray(1)glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), ebo)glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)glBindVertexArray(0)
}

prepareVAOAndVBO 方法创建并初始化顶点数组对象(VAO)、顶点缓冲对象(VBO)和元素缓冲对象(EBO),并将顶点数据、颜色数据和纹理坐标数据传输到 GPU。

loadImageToGPUTexture 方法
func loadImageToGPUTexture(from path: String, index: Int) {guard let image = UIImage(named: path)?.cgImage else {fatalError("Failed to load image at path: \(path)")}let width = image.widthlet height = image.heightlet colorSpace = CGColorSpaceCreateDeviceRGB()let rawData = calloc(height * width * 4, MemoryLayout<GLubyte>.size)let bytesPerPixel = 4let bytesPerRow = bytesPerPixel * widthlet bitsPerComponent = 8let context = CGContext(data: rawData,width: width,height: height,bitsPerComponent: bitsPerComponent,bytesPerRow: bytesPerRow,space: colorSpace,bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)context?.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))var texture: GLuint = 0glGenTextures(1, &texture)glActiveTexture(GLenum(GL_TEXTURE0 + Int32(index)))glBindTexture(GLenum(GL_TEXTURE_2D), texture)glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), rawData)free(rawData)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_NEAREST)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_REPEAT)glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_REPEAT)
}

loadImageToGPUTexture 方法将指定路径的图像加载到 GPU 的纹理单元中。具体步骤包括:

  1. 加载图像并获取其宽度和高度。
  2. 创建颜色空间和原始数据缓冲区。
  3. 创建 CGContext 并将图像绘制到缓冲区中。
  4. 生成并绑定纹理对象。
  5. 将图像数据上传到 GPU 的纹理对象中。
  6. 释放原始数据缓冲区。
  7. 设置纹理参数。
renderLayer 方法
func renderLayer() {glClearColor(0.0, 0.0, 0.0, 1.0)glClear(GLbitfield(GL_COLOR_BUFFER_BIT))glViewport(GLint(0), GLint(0), GLsizei(view.frame.size.width), GLsizei(view.frame.size.height))shader.begin()// 解绑之前的帧缓冲区,恢复默认帧缓冲区glBindFramebuffer(GLenum(GL_FRAMEBUFFER), 0)// 指定的帧缓冲区(Frame Buffer)绑定到当前的 OpenGL ES 渲染上下文中,使 frameBuffer 帧缓冲区其成为当前的渲染目标glBindFramebuffer(GLenum(GL_FRAMEBUFFER), frameBuffer)// 设置采样器变量的纹理单元编号,目的是为了告诉纹理采样器,从哪个纹理单元采集数据shader.setInt(name: "caodi", value: 0)shader.setInt(name: "huangtu", value: 1)shader.setInt(name: "noise", value: 2)// 绘制一个全屏四边形,将 FBO 纹理渲染到屏幕上glBindVertexArray(0)glBindVertexArray(vao)glDrawElements(GLenum(GL_TRIANGLE_STRIP), 4, GLenum(GL_UNSIGNED_BYTE), nil)// 将渲染缓冲区的内容呈现到屏幕上myContext.presentRenderbuffer(Int(GL_RENDERBUFFER))
}

renderLayer 方法负责执行渲染操作并将结果显示到屏幕上。具体步骤包括:

  1. 清除颜色缓冲区。
  2. 设置视口。
  3. 启用着色器程序。
  4. 解绑之前的帧缓冲区,恢复默认帧缓冲区。
  5. 绑定帧缓冲区。
  6. 设置采样器变量的纹理单元编号。
  7. 绑定 VAO 并绘制全屏四边形。
  8. 将渲染缓冲区的内容呈现到屏幕上。

总结

这个代码展示了如何在 iOS 上使用 OpenGL ES 3.0 渲染一个带有纹理的四边形。具体步骤包括初始化 OpenGL ES 渲染环境、加载图像到 GPU 纹理单元、设置顶点数组对象和顶点缓冲对象、编译和链接着色器程序,以及执行渲染操作并将结果显示到屏幕上。通过这些步骤,图像数据被成功上传到 GPU,并可以在渲染过程中使用。

素材

请添加图片描述

请添加图片描述
请添加图片描述

最终效果

请添加图片描述

相关文章:

OpenGL ES 03 加载3张图片并做混合处理

OpenGL ES 02 加载3张图片并做混合处理 什么是纹理单元纹理单元的作用使用纹理单元的步骤详细解释加载图片并绑定到到GPU纹理单元采样器的设置1.设置采样器变量的纹理单元编号&#xff0c;目的是为了告诉纹理采样器&#xff0c;从哪个纹理单元采集数据2.如果你没有显式地设置采…...

c++数据结构算法复习基础--13--基数算法

基数排序 - 桶排序 时间复杂度 O(n*d) – d为数据的长度 每次比较一位&#xff08;个位、十位。。。&#xff09;&#xff0c;所以取值范围就为0-9。 根据该特点&#xff0c;设计桶的概念 – 0号桶、1号桶… 1、思想 1&#xff09;找出最长的数字&#xff0c;确定要处理的…...

基于YOLOv5的行人与帽子检测与识别说明文档

基于YOLOv5的行人与帽子检测与识别说明文档 1. 任务的内容和目标 1.1 任务目标 在计算机视觉领域&#xff0c;头盔检测至关重要&#xff0c;主要用于判定图像或视频里的人是否佩戴头盔。于工业生产、建筑工地、交通出行&#xff08;如摩托车与自行车骑行&#xff09;等高危场…...

Mybatis——(2)

2.2 Mybatis 工具类&#xff08;了解&#xff09; 为了简化MyBatis的开发&#xff0c;可将MyBatis进一步封装。 import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apa…...

QT笔记- QSystemTrayIcon系统托盘功能完整示例

1. 创建托盘对象 // 创建托盘图标QSystemTrayIcon * trayIcon new QSystemTrayIcon(this);QIcon icon("://icon/test.png");trayIcon->setIcon(icon);trayIcon->show();trayIcon->connect(trayIcon, &QSystemTrayIcon::activated,this, &MainWindo…...

ElasticSearch08-分析器详解

零、文章目录 ElasticSearch08-分析器详解 1、分析器原理 Elasticsearch的分词器&#xff08;Analyzer&#xff09;是全文搜索的核心组件&#xff0c;它负责将文本转换为一系列单词&#xff08;term/token&#xff09;的过程&#xff0c;也叫分词。 &#xff08;1&#xff…...

指针的深入讲解

本章重点&#xff1a; 字符指针数组指针指针数组数组传参和指针传参函数指针函数指针数组指向函数指针数组的指针回调函数 我们在指针的初阶的时候主要讲了&#xff1a; 1.指针就是变量&#xff0c;用来存放地址&#xff0c;地址唯一标识一块内存空间 2.指针的大小是固定4个…...

王佩丰24节Excel学习笔记——第十二讲:match + index

【以 Excel2010 系列学习&#xff0c;用 Office LTSC 专业增强版 2021 实践】 【本章小技巧】 vlookup与match&#xff0c;index 相结合使用match,index 结合&#xff0c;快速取得引用的值扩展功能&#xff0c;使用match/index函数&#xff0c;结合照相机工具获取照片 一、回顾…...

概率论得学习和整理28:用EXCEL画折线图,X轴数据也被当成曲线的解决办法

目录 1 折线图和散点图&#xff0c;对数据的处理差别 1.1 EXCEL画图的一些默认设置 1.2 多于2列的数据&#xff0c;也是如此 2 如果我们非要以第1列数据为X轴&#xff0c;做一个折线图呢&#xff1f;也能 2.1 首先&#xff0c;把第1列&#xff0c;想当成X轴的数据&#xf…...

387. 字符串中的第一个唯一字符

1&#xff0c;题目 给定一个字符串 s &#xff0c;找到 它的第一个不重复的字符&#xff0c;并返回它的索引 。如果不存在&#xff0c;则返回 -1 。 2&#xff0c;代码 class Solution { public:int firstUniqChar(string s) {//记数排序int coutArr[26] {0};//统计字符出现…...

Oracle RAC最佳实践-优化私网连接

在 Oracle RAC 环境中&#xff0c;私网&#xff08;Interconnect&#xff09; 是节点之间通信和数据传输的关键部分。一直有个误解&#xff0c;认为私网&#xff08;心跳网&#xff09;只要能通随便什么交换机都可以,甚至有直连的&#xff0c;实际上私网的性能至关重要&#xf…...

[bug] StarRocks borker load意向之外的bug

意向之外&#xff0c;又清理之中 背景&#xff1a; StarRocks各方面碾压相同类型的数据库&#xff0c;最近我们要从生成HIVE导历史数据&#xff08;ORC格式&#xff09;到StarRocks&#xff0c;前期小测一下&#xff0c;在测试是没问题&#xff0c;上生产先导2个月的数据&…...

游戏AI实现-寻路算法(Dijkstra)

戴克斯特拉算法&#xff08;英语&#xff1a;Dijkstras algorithm&#xff09;&#xff0c;又称迪杰斯特拉算法、Dijkstra算法&#xff0c;是由荷兰计算机科学家艾兹赫尔戴克斯特拉在1956年发现的算法。 算法过程&#xff1a; 1.首先设置开始节点的成本值为0&#xff0c;并将…...

9 OOM和JVM退出。OOM后JVM一定会退出吗?

首先我们把两个概念讲清楚 OOM是线程在申请堆内存&#xff0c;发现堆内存空间不足时候抛出的异常。 JVM退出的条件如下&#xff1a; java虚拟机在没有守护线程的时候会退出。守护线程是启动JVM的线程&#xff0c;服务于用户线程。 我们简单说下守护线程的功能: 1.日志的记录…...

Linux 端口操作

安装netstat yum -y install net-tools 检测端口占用 netstat -npl | grep "端口" 安装lsof lsof yum -y install lsof 检测端口占用 lsof -i :端口号 安装nc yum -y install nc 查看对方端口是否开放 nc -vz 对方ip 对方端口 安装telnet telnet yum -y in…...

【USB-HID】“自动化键盘“ - 模拟键盘输入

目录 【USB-HID】"自动化键盘" - 模拟键盘输入1. 前言2. 模拟键盘2.1 STM32CubeMX 配置2.2 修改代码配置2.3 发送按键信息 3. 接收主机Setup数据3.1 获取PC下发的数据 4. 总结 【USB-HID】“自动化键盘” - 模拟键盘输入 1. 前言 对于模拟键盘的实现&#xff0c;网…...

基于Spring Boot+Vue 的高校运动会管理系统

目录 1 绪论1.1研究背景1.2 研究意义1.3 相关开发技术简介1.3.1 Vue.js1.3.2 Spring Boot1.3.3 MySQL 2 系统分析2.1 需求分析2.1.1 功能需求2.1.2 非功能需求 2.2 系统可行性分析2.2.1 经济可行性2.2.2 技术可行性2.2.3 操作可行性 3 系统概要设计系统功能描述业务流程分析 4 …...

Linux应用程序中终止进程的几种方法

目录 1、正常退出进程的方法 1.1、exit(int status) 函数 1.2、_exit(int status) 函数 1.3、_Exit(int status) 函数 2、异常退出进程的方法 3、何时使用这些方法&#xff1f; 在 Linux 应用程序中&#xff0c;终止进程的方式有多种&#xff0c;通常取决于进程是否需要进…...

电脑文档损坏:原因剖析和修复方法

在使用电脑的过程中&#xff0c;许多用户可能会遇到文档突然提示损坏、无法打开的情况。这种情况的发生往往让人感到困惑&#xff0c;特别是当并未进行任何明显错误操作时。以下是一些常见的原因以及应对方法。 一、文档损坏的常见原因 1、非人为的异常操作&#xff1a; 在编…...

了解ARM的千兆以太网——RK3588

1. 简介 本文并不重点讲解调试内容&#xff0c;重点了解以太网在ARM设计中的框架以及在设备树以及驱动的一个整体框架。了解作为一个驱动开发人员当拿到一款未开发过的ARM板卡应该怎么去把网卡配置使用起来。 2. 基础知识介绍 在嵌入式ARM中实现以太网的解决方案通常有以下两种…...

【Nginx-4】Nginx负载均衡策略详解

在现代Web应用中&#xff0c;随着用户访问量的增加&#xff0c;单台服务器往往难以承受巨大的流量压力。为了解决这一问题&#xff0c;负载均衡技术应运而生。Nginx作为一款高性能的Web服务器和反向代理服务器&#xff0c;提供了多种负载均衡策略&#xff0c;能够有效地将请求分…...

低级计算机网络知识总结

1 应用层 1.1 HTTP(TCP) 浏览器访问WWW服务器过程&#xff1a;首先进行域名解析&#xff0c;然后通过TCP向服务器发送连接请求 HTTP本身是无连接&#xff0c;无状态的。无状态特性使服务器能够支持大量的并发HTTP请求。实际应用中&#xff0c;通常使用Cookie加数据库跟踪用户…...

linux sysrq的使用举例

在menuconfig中选择m和 *的区别&#xff1a; *: 模块驱动编译到内核中&#xff0c;启动时自动加载 M:标识作为内核模块编译 空格:表示该功能不编译到内核中&#xff0c;即新的内核将不支持该功能。 m&#xff1a;模块会被编译&#xff0c;但是不会被编译到内核中&#xff0c;只…...

数字IC后端设计实现篇之TSMC 12nm TCD cell(Dummy TCD Cell)应该怎么加?

TSMC 12nm A72项目我们需要按照foundary的要求提前在floorplan阶段加好TCD Cell。这个cell是用来做工艺校准的。这个dummy TCD Cell也可以等后续Calibre 插dummy自动插。但咱们项目要求提前在floorplan阶段就先预先规划好位置。 TSCM12nm 1P9M的metal stack结构图如下图所示。…...

Oracle 适配 OpenGauss 数据库差异语法汇总

背景 国产化进程中&#xff0c;需要将某项目的数据库从 Oracle 转为 OpenGauss &#xff0c;项目初期也是规划了适配不同数据库的&#xff0c;MyBatis 配置加载路径设计的是根据数据库类型加载指定文件夹的 xml 文件。 后面由于固定了数据库类型为 Oracle 后&#xff0c;只写…...

【记录】Django解决与VUE跨域问题

1 梗概 这里记录Django与VUE的跨域问题解决方法&#xff0c;主要修改内容是在 Django 中。当然其他的前端项目 Django 也可以这样处理。 2 安装辅助包 pip install django-cors-headers3 配置 settings.py INSTALLED_APPS [ # ... corsheaders, # ... ] 为了响应…...

Yolov10本地部署,torch找不到GPU问题解决

在本地部署跑Yolov10的模型.具体分为以下几步,也是踩了一些坑: 1.YoloV10 代码拉取 2.安装CUDA 1.查看CUDA支持版本 2.下载安装CUDA 3.下载CUDNN 3.创建python虚拟环境 Anaconda下载安装 虚拟环境安装配置 4.运行 1.yoloV10代码拉取 源码地址: GitHub - THU-MIG/yolov10: YOLO…...

el-upload 上传文件 入参格式为form-data格式,入参字段为code、name、type、file(文件)的形式,如何实现?

el-upload 是 Element UI 中用于文件上传的组件。如果你需要上传文件并将其封装为 form-data 格式&#xff0c;并且包含字段如 code、name、type 和 file&#xff0c;你可以通过自定义 before-upload 或 action 进行处理。 1. el-upload 的基本用法 Element UI 的 el-upload …...

VUE组件插槽使用示例,弹窗样式

在Vue.js中&#xff0c;插槽&#xff08;slots&#xff09;是一种非常强大的功能&#xff0c;它允许你在父组件中向子组件传递内容。插槽主要有三种类型&#xff1a;默认插槽、具名插槽和作用域插槽。下面是一些示例来展示如何使用这些插槽。 默认插槽 默认插槽是最简单的插槽…...

ARM嵌入式学习--第八天(PWM)

PWM -PWM介绍 PWM&#xff08;pulse Width Modulation&#xff09;简称脉宽调制&#xff0c;是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术&#xff0c;广泛应用在测量&#xff0c;通信&#xff0c;工控等方面 PWM的频率 是指在1秒钟内&#xff0c;信号从…...

新能源汽车大屏可视化第三次数据存储

任务&#xff1a; 将数据存放到temp.csv 链接&#xff1a; 1.排行页面 https://www.dongchedi.com/sales 2.参数页面 https://www.dongchedi.com/auto/params-carIds-x-9824 完善打印&#xff1a; 1. [{‘series_id’: 5952, ‘series_name’: ‘海鸥’, ‘image’: ‘https://…...

linux 替换yum源镜像

1. 备份源镜像 sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak 2. 下载国内镜像阿里云 如果没有wget可以用curl 代替 sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo 清华大学 sudo wget -…...

SAP:如何修改已释放的请求

SAP:如何修改已释放的请求 QQ出了一个新功能&#xff0c;把10年前的旧日志推给自己。这个10年前的日志&#xff0c;是用户反映在SE10中把请求释放后发现漏了内容&#xff0c;想修改已释放的请求。经调查写了一个小程序&#xff0c;实现用户的需求。 *&-------------------…...

js的?. 和??和||有什么区别

let a 0; let b null; let c Hello;console.log(a ?? default); // 0 console.log(b ?? default); // "default" console.log(c ?? default); // "Hello"console.log(a || default); // "default" (因为 0 是假值) console.log(b |…...

clickhouse 分布式表创建、增加、更新、删除、查询

创建分布式表 --先创建本地表 设置自动过期时间3天 CREATE TABLE IF NOT EXISTS ck_database.ck_databaseon cluster default(cluster name) (table_id String COMMENT id,item_id String COMMENT 业务id,desc Int64 COMMENT 描述,time DateTime DEFAULT now() COMMENT 数据…...

推送本地仓库到远程git仓库

目录 推送本地仓库到远程git仓库1.1修改本地仓库用户名1.2 push 命令1.3远程分支查看 推送本地仓库到远程git仓库 删除之前的仓库中的所有内容&#xff0c;从新建库&#xff0c;同时创建一个 A.txt 文件 清空原有的远程仓库内容&#xff0c;重新创建一个新的仓库&#xff0c;…...

LSTM长短期记忆网络

LSTM&#xff08;长短期记忆网络&#xff09;数学原理 LSTM&#xff08;Long Short-Term Memory&#xff09;是一种特殊的递归神经网络&#xff08;RNN&#xff09;&#xff0c;解决了标准RNN中存在的梯度消失&#xff08;Vanishing Gradient&#xff09; 和**梯度爆炸&#x…...

ABAP SQL 取日期+时间最新的一条数据

我们在系统对接的时候&#xff0c;外部系统可能会推送多个数据给到我们。 我们 SAP 系统的表数据中日期和时间是作为主键的&#xff0c;那么如果通过 ABAP SQL 取到最新日期的最新时间呢。 解决方案&#xff1a; 方式 1&#xff1a;SELECT MAX 可以通过两个 SELECT MAX 来取…...

SAST静态应用安全测试常见的编码规则

行业优先级难易度标准标准名称数量 军工12易GJB 5369:2005GJB_5369&#xff08;国家军用标准航天型号软件C语言可靠性编程规范&#xff09;138军工行业最早的C语言编码标准&#xff0c;强制性4易GJB 8114:2013GJB_8114&#xff08;国家军用标准C/C语言可靠性编程规范&#xff…...

AI相关专业名词汇总解释

1.SFT Supervised fine-tuning&#xff0c;“有监督微调”意味着使用有标签的数据来调整一个已预训练好的语言模型&#xff08;LLM&#xff09;&#xff0c;使其更适应某一特定任务。通常LLM的预训练是无监督的&#xff0c;但微调过程往往是有监督的。 详解&#xff1a;https:/…...

【C语言】指针数组和数组指针

前言 指针数组和数组指针是C语言中经常混淆的两个概念&#xff0c;虽然他们的名字相似&#xff0c;但其含义却完全不同。 指针数组 指针数组本质是一个数组&#xff0c;特点是数组中的元素均为指针&#xff0c;其定义形式为&#xff1a; 数据类型 *指针名[长度] 例如 int *…...

联邦学习中:公共物品属性的一般定义

在经济学和相关领域中,公共物品属性具有特定的含义,在论文中与联邦学习数据交易等情境相关联时,其意义如下: 公共物品属性的一般定义 非排他性 公共物品一旦被提供,很难或不可能排除其他人使用。例如,路灯照亮了街道,一个人使用路灯照明并不会阻止其他人同时使用,无法…...

前端的Python应用指南(一):快速构建 Web 服务器 - Flask vs Node.js 对比

随着前端开发技术的不断发展&#xff0c;前端开发者的技术栈也在不断扩展。如今&#xff0c;前端开发者不仅要掌握 HTML、CSS、JavaScript&#xff0c;还要掌握后端技术&#xff0c;成为全栈开发者。而在后端技术的选择上&#xff0c;Python 和 Node.js 是两种非常流行的选择。…...

典型案例 | 旧PC新蜕变!东北师范大学依托麒麟信安云“旧物焕新生”

东北师范大学始建于1946年&#xff0c;坐落于吉林省长春市&#xff0c;是中国共产党在东北地区创建的第一所综合性大学。作为国家“双一流”建设高校&#xff0c;学校高度重视教学改革和科技创新&#xff0c;校园信息化建设工作始终走在前列。基于麒麟信安云&#xff0c;东北师…...

【UE5】pmx导入UE5,套动作。(防止“气球人”现象。

参考视频&#xff1a;UE5Animation 16: MMD模型與動作導入 (繁中自動字幕) 问题所在&#xff1a; 做法记录&#xff08;自用&#xff09; 1.导入pmx&#xff0c;删除这两个。 2.转换给blender&#xff0c;清理节点。 3.导出时&#xff0c;内嵌贴图&#xff0c;选“复制”。 …...

ROS+PX4+Gazebo仿真环境配置全流程解析

上一期文章介绍了我们即将发布的仿真平台&#xff0c;并提到后续需要在Ubuntu系统上进行PX4软件在环仿真。本期文章将为大家详细介绍如何配置Ubuntu环境以及安装ROS和PX4仿真环境。具体配置包括&#xff1a;Ubuntu 20.04 ROS Noetic PX4 Python3。 需要注意的是&#xff0c…...

STM32F103单片机HAL库串口通信卡死问题解决方法

在上篇文章 STM32F103单片机使用STM32CubeMX创建IAR串口工程 中分享了使用cubeMX直接生成串口代码的方法&#xff0c;在测试的过程中无意间发现&#xff0c;串口会出现卡死的问题。 当串口一次性发送十几个数据的时候&#xff0c;串口感觉像卡死了一样&#xff0c;不再接收数据…...

基于微信小程序的电影院订票选座系统ssm+论文源码调试讲解

第2章 开发环境与技术 本章节对开发基于微信小程序的电影院订票选座系统需要搭建的开发环境&#xff0c;还有基于微信小程序的电影院订票选座系统开发中使用的编程技术等进行阐述。 2.1 Java语言 Java语言是当今为止依然在编程语言行业具有生命力的常青树之一。Java语言最原始…...

解决新安装CentOS 7系统mirrorlist.centos.org can‘t resolve问题

原因 mirrorlist.centos.org yum源用不了 解决办法就是 # cd /etc/yum.repos.d/ # mv CentOS-Base.repo CentOS-Base.repo_bak # vim CentOS-Base.repoCentOS系统操作 # mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/*.repo_bak # curl -o /etc/yum.repos.d/CentOS-Linux-Ba…...

分布式系统架构3:服务容错

这是小卷对分布式系统架构学习的第3篇文章&#xff0c;虽然知道大家都不喜欢看纯技术文章&#xff0c;写了也没多少阅读量&#xff0c;但是个人要成长的话&#xff0c;还是需要往深一点的技术上去探索的 1.为什么需要容错 分布式系统的本质是不可靠的&#xff0c;一个大的服务…...