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

news2024/12/21 7:45:48

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.width
        let height = image.height

        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let rawData = calloc(height * width * 4, MemoryLayout<GLubyte>.size)
        let bytesPerPixel = 4
        let bytesPerRow = bytesPerPixel * width
        let bitsPerComponent = 8

        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))

        var texture: GLuint = 0
        glGenTextures(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纹理单元0
  loadImageToGPUTexture(from: "caodi", index: 0)
  // 加载图片到GPU纹理单元1
  loadImageToGPUTexture(from: "huangtu", index: 1)
  // 加载图片到GPU纹理单元2
  loadImageToGPUTexture(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 es

precision 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 UIKit

class Shader: NSObject {
    var shaderProgram: GLuint = 0
    
    private 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 in
            var p: UnsafePointer<GLchar>? = UnsafePointer<GLchar>(ptr)
            glShaderSource(vertexShader, 1, &p, nil)
        }
        fragmentSource.withCString { ptr in
            var p: UnsafePointer<GLchar>? = UnsafePointer<GLchar>(ptr)
            glShaderSource(fragmentShader, 1, &p, nil)
        }
        
        // 编译顶点着色器
        glCompileShader(vertexShader)
        // 检查编译错误
        var status: GLint = 0
        glGetShaderiv(vertexShader, GLenum(GL_COMPILE_STATUS), &status)
        if status == GL_FALSE {
            var logLength: GLint = 0
            glGetShaderiv(vertexShader, GLenum(GL_INFO_LOG_LENGTH), &logLength)
            
            // Allocate buffer with an extra byte for the null terminator
            let bufferLength = Int(logLength) + 1
            var log = [GLchar](repeating: 0, count: bufferLength)
            
            // Get the shader info log
            glGetShaderInfoLog(vertexShader, logLength, nil, &log)
            
            // Convert the buffer to a Swift string
            if 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 = 0
            glGetShaderiv(fragmentShader, GLenum(GL_INFO_LOG_LENGTH), &logLength)
            
            // Allocate buffer with an extra byte for the null terminator
            let bufferLength = Int(logLength) + 1
            var log = [GLchar](repeating: 0, count: bufferLength)
            
            // Get the shader info log
            glGetShaderInfoLog(fragmentShader, logLength, nil, &log)
            
            // Convert the buffer to a Swift string
            if 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纹理单元0
        loadImageToGPUTexture(from: "caodi", index: 0)
        // 加载图片到GPU纹理单元1
        loadImageToGPUTexture(from: "huangtu", index: 1)
        // 加载图片到GPU纹理单元2
        loadImageToGPUTexture(from: "noise", index: 2)
        renderLayer()
    }
    
    func setupLayer() {
        eaglLayer = CAEAGLLayer()
        eaglLayer.frame = view.frame
        eaglLayer.isOpaque = true
        view.layer.addSublayer(eaglLayer)
    }
    
    func setupContext() {
        if let context = EAGLContext(api: .openGLES3) {
            EAGLContext.setCurrent(context)
            myContext = context
            print("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)
        
        // uv
        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)
    }

    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.width
        let height = image.height

        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let rawData = calloc(height * width * 4, MemoryLayout<GLubyte>.size)
        let bytesPerPixel = 4
        let bytesPerRow = bytesPerPixel * width
        let bitsPerComponent = 8

        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))

        var texture: GLuint = 0
        glGenTextures(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.frame
    eaglLayer.isOpaque = true
    view.layer.addSublayer(eaglLayer)
}

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

setupContext 方法
func setupContext() {
    if let context = EAGLContext(api: .openGLES3) {
        EAGLContext.setCurrent(context)
        myContext = context
        print("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.width
    let height = image.height

    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let rawData = calloc(height * width * 4, MemoryLayout<GLubyte>.size)
    let bytesPerPixel = 4
    let bytesPerRow = bytesPerPixel * width
    let bitsPerComponent = 8

    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))

    var texture: GLuint = 0
    glGenTextures(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.coloradmin.cn/o/2263144.html

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

相关文章

临床外显子组测序分析中的那些坑(上)

大规模并行测序技术或下一代测序已成为基因诊断和研究的标准技术&#xff0c;尤其是外显子组和基因组测序现在已经在世界范围内广泛应用于患者的分子诊断。在过去几年中&#xff0c;许多实验室都在努力应对基于全新技术建立基因检测工作流程的挑战。测序技术中持续引入新的仪器…

【Tomcat】第六站(最后一站啦!):数据的返回

1. 引言 前端资源比如html页面&#xff0c;进行返回。截止到目前我们写的项目架构不支持前端页面&#xff08;静态资源 &#xff09;。 2. 数据的返回 2.1 准备 为了能够写前端页面&#xff0c;新建一个项目。选择Maven项目&#xff0c;下一步&#xff0c;下一步。 加载完…

ai论文生成器:分享8款AI一键生成论文的写作软件

在撰写毕业论文的过程中&#xff0c;高效利用各类软件工具可以极大地提升写作效率与质量。以下是八个免费的神器软件工具&#xff0c;它们各自在论文撰写、文献管理、语法校对、数据可视化等方面发挥着重要作用。希望这些推荐能帮助你顺利完成毕业论文的写作。 千笔AI论文&…

【Ubuntu 20.04】notepad++的安装与汉化

一&#xff0c;简介 本文介绍如何在Ubuntu虚拟机中安装notepad&#xff0c;供参考。 二&#xff0c;操作步骤 2.1 notepad的安装 sudo snap install notepad-plus-plus

Cherno C++学习笔记 P45 拷贝与拷贝构造函数

在这一篇文章当中&#xff0c;我们会学习一下C当中的复制以及复制构造函数&#xff0c;当然&#xff0c;还会用一个相对比较完整的代码类作为例子。这也是我们第一次写一个相对比较长的和完整的代码。 对于C来说&#xff0c;理解复制是非常重要的&#xff0c;因为整个C变成可以…

uniapp入门 01创建项目模版

0安装 hbuilder x 标准版 1.创建模版工程 2.创建官方 案例工程 index.uvuewen 文件解析 <!-- 模版 标签 --> <template><view></view></template><!-- 脚本 --> <script>export default {data() {return {}},onLoad() {},methods:…

kubeadm安装K8s高可用集群之集群初始化及master/node节点加入calico网络插件安装

系列文章目录 1.kubeadm安装K8s高可用集群之基础环境配置 2.kubeadm安装K8s集群之高可用组件keepalivednginx及kubeadm部署 3.kubeadm安装K8s高可用集群之集群初始化及master/node节点加入集群calico网络插件安装 kubeadm安装K8s高可用集群之集群初始化及master/node节点加入ca…

【NLP】序列到序列(seq2seq)建模工具fairseq使用详解

文章目录 一、fairseq简介二、安装方式2.1 pip安装2.2 源码安装 三、fairseq命令工具3.1 fairseq-preprocess3.2 fairseq-train3.3 fairseq-generate3.4 fairseq-interactivate3.5 fairseq-score3.6 fairseq-eval-lm 4. 常见报错报错1 参考资料 一、fairseq简介 fairseq 是 Fa…

28、论文阅读:基于像素分布重映射和多先验Retinex变分模型的水下图像增强

A Pixel Distribution Remapping and Multi-Prior Retinex Variational Model for Underwater Image Enhancement 摘要介绍相关工作基于模型的水下图像增强方法&#xff1a;无模型水下图像增强方法&#xff1a;基于深度学习的水下图像增强方法&#xff1a; 论文方法概述像素分布…

ArkTs组件的学习

一. AlphabetIndexer 可以与容器组件联动用于按逻辑结构快速定位容器显示区域的组件 参数名类型必填说明arrayValueArray<string>是字母索引字符串数组&#xff0c;不可设置为空selectednumber是初始选中项索引值若超出索引值范围则取默认值0 class Lxr{tImg:Resource…

Python读取Excel批量写入到PPT生成词卡

一、问题的提出 有网友想把Excel表中的三列数据&#xff0c;分别是&#xff1a;单词、音标和释义分别写入到PPT当中&#xff0c;每一张PPT写一个单词的内容。这种批量操作是python的强项&#xff0c;尤其是在办公领域&#xff0c;它能较好地解放双手&#xff0c;读取Excel表后…

百度面试手撕 go context channel部分学习

题目 手撕 对无序的切片查询指定数 使用context进行子协程的销毁 并且进行超时处理。 全局变量定义 var (startLoc int64(0) // --- 未处理切片数据起始位置endLoc int64(0) // --- 切片数据右边界 避免越界offset int64(0) // --- 根据切片和协程数量 在主线程 动态设…

Otsu 二值化算法:原理、实现与应用

摘要&#xff1a; 本文深入探讨了 Otsu 二值化算法&#xff0c;详细阐述其原理&#xff0c;包括类间方差的计算与阈值确定机制。分别给出了该算法在 C#、Python 和 C 中的实现代码示例&#xff0c;并对代码进行了详细注释与分析。此外&#xff0c;还探讨了 Otsu 二值化算法在图…

uniApp使用腾讯地图提示未添加maps模块

uniApp使用腾讯地图&#xff0c;打包提示未添加maps模块解决方案 这是报错信息&#xff0c;在标准基座运行的时候是没问题的&#xff0c;但是打包后会提示未添加&#xff0c;可以通过在mainfest里面把地图插件上腾讯地图的key更换高德地图的key&#xff0c;定位服务可以继续用腾…

Deepin/Linux clash TUN模式不起作用,因网关导致的问题的解决方案。

网关导致的问题的解决方案 查看路由 ip route寻找默认路由 默认路由应当为Mihomo default dev Mihomo scope link 如果不是&#xff0c;则 sudo ip route add default dev Mihomo在clash TUN开关状态发生变化时&#xff0c;Mihomo网卡会消失&#xff0c;所以提示找不到网卡…

scala中正则表达式的使用

正则表达式&#xff1a; 基本概念 在 Scala 中&#xff0c;正则表达式是用于处理文本模式匹配的强大工具。它通过java.util.regex.Pattern和java.util.regex.Matcher这两个 Java 类来实现&#xff08;因为 Scala 运行在 Java 虚拟机上&#xff0c;可以无缝使用 Java 类库&…

apache应用(客户机地址限制、用户授权限制、日志分割、AWStats日志分析)

目录 一、 客户机地址限制 二、 用户授权限制 三、 日志分割 使用rotatelogs分割工具 使用第三方工具cronolog 四、 AWStats日志分析 具体的apache软件安装可以阅读我之前的文章apache安装https://blog.csdn.net/m0_68472908/article/details/139348739?spm1001.2014.300…

护士资格实践题库(含解析)

1.患者女&#xff0c;30岁。诊断类风湿关节炎入院&#xff0c;经使用药物治疗后患者关节疼痛减轻&#xff0c;但出现体重增加、满月脸、向心性肥胖。提示存在何种药物的副作用&#xff08; &#xff09; A.泼尼松 B.环磷酰胺 C.硫唑嘌呤 D.吲哚美辛 E.阿司匹林 【答案】…

网络安全概论——防火墙原理与设计

一、防火墙概述 防火墙是一种装置&#xff0c;它是由软件/硬件设备组合而成&#xff0c;通常处于企业的内部局域网与 Internet 之间&#xff0c;限制 Internet 用户对内部网络的访问以及管理内部用户访问 Internet 的权限。换言之&#xff0c;一个防火墙在一个被认为是安全和可…

接口测试-Fidder及jmeter使用

一、接口测试的基础 1.接口的含义 也叫做API&#xff0c;是一组定义、程序及协议的集合&#xff0c;提供访问一组例程的能力&#xff0c;无需访问源码获理解内部工作细节 2.接口的分类 代码内部的接口&#xff0c;程序模块间的接口&#xff0c;对于程序接口测试&#xff0c;需…