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

embedded/2024/12/21 8:09:12/

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,并可以在渲染过程中使用。

素材

请添加图片描述

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

最终效果

请添加图片描述


http://www.ppmy.cn/embedded/147480.html

相关文章

Ubuntu22.04安装CH340/CH341驱动

陈拓 2024/12/20-2024/12/20 1. 我的系统 硬件系统架构 arch 操作系统版本 lsb_release -a 2. CH340G&#xff0c;USB-串口转换器 3. Ubuntu22.04安装CH340驱动 3.1 用lsusb查看USB 插上CH340之前 插上CH340之后 输出中包含ID 1a86:7523 QinHeng Electronics CH340 serial…

网络安全工具及其使用方法:保护数字安全的第一道防线

目录 一、防火墙&#xff08;Firewall&#xff09; 功能 使用方法 二、密码管理器 功能 使用方法 三、漏洞扫描器 功能 使用方法 四、杀毒软件 功能 使用方法 五、网络流量分析工具 功能 使用方法 六、数据加密工具 功能 使用方法 结语 Welcome to Code Bloc…

​Python 标识符是啥?​

Python 的标识符就是我们写代码时用来给变量、函数、类等取名字的东西。 你写的 my_variable 是个标识符&#xff0c; 定义的 add_numbers 函数名也是个标识符&#xff0c; 甚至你写的 Cat 类名&#xff0c;也是标识符。 一句话总结&#xff1a;标识符就是代码里给“东西”起…

内容与资讯API优质清单

作为开发者&#xff0c;拥有一套API合集是必不可少的。这个开发者必备的API合集汇集了各种实用的API资源&#xff0c;为你的开发工作提供了强大的支持&#xff01;无论你是在构建网站、开发应用还是进行数据分析&#xff0c;这个合集都能满足你的需求。你可以通过这些免费API获…

meta-llama/Llama-3.2-1B 微调记录

踩坑&#xff1a; 1.刚开始部署在自己的windows电脑上&#xff0c;semgrep不支持windows &#xff0c;然后就换了linux服务器 2.服务器没有梯子&#xff0c;huggingface无法访问&#xff0c;模型数据集无法下载 解决方法&#xff1a; 使用huggingface镜像网站下载模型&#xf…

vue 上传组件 vxe-upload 实现拖拽调整顺序

vue 上传组件 vxe-upload 实现拖拽调整顺序&#xff0c;通过设置 drag-sort 参数就可以启用拖拽排序功能 官网&#xff1a;https://vxeui.com/ 图片拖拽排序 <template><div><vxe-upload v-model"imgList" mode"image" multiple drag-sor…

每天40分玩转Django:实操博客应用

实操博客应用 一、内容概述 模块重要程度主要内容项目初始化⭐⭐⭐⭐创建项目和应用模型设计⭐⭐⭐⭐⭐文章、评论、用户模型视图实现⭐⭐⭐⭐⭐增删改查功能模板开发⭐⭐⭐⭐页面布局和样式用户认证⭐⭐⭐⭐⭐用户登录和权限 二、项目结构 blog_project/ ├── blog/ │ …

NOTEBOOK_11 汽车电子设备分享(工作经验)

汽车电子设备分享 摘要 本文主要列出汽车电子应用的一些实验设备和生产设备&#xff0c;部分会给予一定推荐。目录 摘要一、通用工具&#xff1a;二、测量与测试仪器2.1测量仪器2.2无线通讯测量仪器2.3元器件测试仪2.4安规测试仪2.5电源供应器2.6电磁兼容测试设备2.7可靠性环境…