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 中的一个概念,用于管理和绑定多个纹理对象,以便在着色器中进行纹理采样操作。纹理单元允许你在一个渲染过程中使用多个纹理,而无需频繁地绑定和解绑纹理对象。
纹理单元的作用
-
管理多个纹理:
- 纹理单元允许你同时绑定多个纹理对象。每个纹理单元都有一个唯一的编号(索引),你可以通过这个编号来引用特定的纹理单元。
- 这使得在一个渲染过程中可以使用多个纹理,而无需频繁地绑定和解绑纹理对象。
-
在着色器中进行纹理采样:
- 在着色器中,你可以使用采样器(如
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 中使用。以下是对该方法的详细解释,包括每个步骤的作用和流程。
详细解释
-
加载图像:
guard let image = UIImage(named: path)?.cgImage else {fatalError("Failed to load image at path: \(path)") }
- 使用
UIImage(named:)
加载指定路径的图像,并将其转换为CGImage
。 - 如果图像加载失败,程序将终止并输出错误信息。
- 使用
-
获取图像宽度和高度:
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
- 创建一个 RGB 颜色空间。
- 分配一个缓冲区
rawData
用于存储图像的原始像素数据。缓冲区大小为图像的宽度 * 高度 * 每像素字节数(4 字节,RGBA)。
-
创建 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:)
将图像绘制到缓冲区中。
- 创建一个
-
生成和绑定纹理:
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
将生成的纹理对象绑定到当前激活的纹理单元。
- 使用
-
上传纹理数据到 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
:纹理内部格式。width
和height
:纹理的宽度和高度。0
:边框宽度(必须为 0)。GL_RGBA
:像素数据的格式。GL_UNSIGNED_BYTE
:像素数据的类型。rawData
:指向像素数据的指针。
- 使用
-
释放原始数据缓冲区:
free(rawData)
- 释放之前分配的缓冲区
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)
- 使用
glTexParameteri
设置纹理参数:GL_TEXTURE_MAG_FILTER
:设置纹理放大时的过滤方式(GL_LINEAR
为线性过滤)。GL_TEXTURE_MIN_FILTER
:设置纹理缩小时的过滤方式(GL_NEAREST
为邻近过滤)。GL_TEXTURE_WRAP_S
和GL_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 的纹理单元中。具体步骤包括:
- 加载图像并获取其宽度和高度。
- 创建颜色空间和原始数据缓冲区。
- 创建
CGContext
并将图像绘制到缓冲区中。 - 生成并绑定纹理对象。
- 将图像数据上传到 GPU 的纹理对象中。
- 释放原始数据缓冲区。
- 设置纹理参数。
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
方法负责执行渲染操作并将结果显示到屏幕上。具体步骤包括:
- 清除颜色缓冲区。
- 设置视口。
- 启用着色器程序。
- 解绑之前的帧缓冲区,恢复默认帧缓冲区。
- 绑定帧缓冲区。
- 设置采样器变量的纹理单元编号。
- 绑定 VAO 并绘制全屏四边形。
- 将渲染缓冲区的内容呈现到屏幕上。
总结
这个代码展示了如何在 iOS 上使用 OpenGL ES 3.0 渲染一个带有纹理的四边形。具体步骤包括初始化 OpenGL ES 渲染环境、加载图像到 GPU 纹理单元、设置顶点数组对象和顶点缓冲对象、编译和链接着色器程序,以及执行渲染操作并将结果显示到屏幕上。通过这些步骤,图像数据被成功上传到 GPU,并可以在渲染过程中使用。
素材
最终效果
相关文章:

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

深度学习-74-大语言模型LLM之基于API与llama.cpp启动的模型进行交互
文章目录 1 大模型量化方法1.1 GPTQ(后训练量化)1.2 GGUF(支持CPU)1.3 AWQ(后训练量化)2 llama.cpp2.1 功能2.1.1 Chat(聊天)2.1.2 Completion(补全)2.2 运行开源LLM2.2.1 下载安装llama.cpp2.2.2 下载gguf格式的模型2.2.3 运行大模型3 API访问3.1 调用补全3.2 调用聊天3.3 提取…...

PyTorch 2.0 中设置默认使用 GPU 的方法
PyTorch 2.0 中设置默认使用 GPU 的方法 在 PyTorch 2.0 中,默认情况下仍然是使用 CPU 进行计算,除非明确指定使用 GPU。torch.set_default_device 是 PyTorch 2.0 引入的新功能,用于设置默认设备,使得所有后续张量和模块在没有明…...

如何在 Ubuntu 22.04 服务器上安装 Jenkins
简介 Jenkins 是一个非常流行的免费自动化工具,每个人都应该了解它。DevOps 工程师使用它来自动化代码构建、测试和部署。本文将重点介绍如何在新的 Ubuntu LTS 版本,即 Ubuntu 22.04 中安装 Jenkins。 但在此之前,让我们快速讨论一下 Jenk…...

【一篇搞定配置】如何在Ubuntu上配置单机/伪分布式Hadoop
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀各种软件安装与配置_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1.…...

利用Map集合设计程序,存储城市和对应等级相关信息
package testmap;import java.util.HashMap; import java.util.Scanner; import java.util.Set;public class TestHashMap6 {public static void main(String[] args) {//1.创建一个Map集合:存储键值对HashMap<String, String> map new HashMap<>();/…...

【自动驾驶】单目摄像头实现自动驾驶3D目标检测
🍑个人主页:Jupiter. 🚀 所属专栏:传知代码 欢迎大家点赞收藏评论😊 目录 概述算法介绍演示效果图像推理视频推理 核心代码算法处理过程使用方式环境搭建下载权重文件pytorch 推理(自动选择CPU或GPU&#x…...

21 go语言(golang) - gin框架安装及使用(二)
四、组成 前面的文章中,我们介绍了其中一部分组成,接下来继续学习: Router(路由器) Gin 使用基于树结构的路由机制来处理 HTTP 请求。它支持动态路由参数、分组路由以及中间件。路由器负责将请求路径映射到相应的处理…...

Intel(R) Iris(R) Xe Graphics安装Anaconda、Pytorch(CPU版本)
一、Intel(R) Iris(R) Xe Graphics安装Anaconda 下载网址:https://repo.anaconda.com/archive/ 双击Anaconda3-2024.10-1-Windows-x86_64,一直下一步,选择安装的路径位置,一直下一步就安装完成了。打开Anaconda PowerShell Promp…...

【Unity3D】实现可视化链式结构数据(节点数据)
关键词:UnityEditor、可视化节点编辑、Unity编辑器自定义窗口工具 使用Newtonsoft.Json、UnityEditor相关接口实现 主要代码: Handles.DrawBezier(起点,终点,起点切线向量,终点切线向量,颜色,n…...

Three.js推荐-可以和Three.js结合的动画库
在 Three.js 中,3D 模型、相机、光照等对象的变换(如位置、旋转、缩放)通常需要通过动画进行控制,以实现更加生动和富有表现力的效果。然而,Three.js 本身并没有内置的强大动画管理系统,尽管可以通过关键帧…...

增强现实(AR)和虚拟现实(VR)的应用
增强现实(AR)和虚拟现实(VR)是近年来迅速发展的技术,广泛应用于多个行业,提供沉浸式的体验和增强的信息交互。以下是AR和VR的定义及其在不同领域的具体应用。 相关学点: 2025年大数据、通信技术…...

告别机器人味:如何让ChatGPT写出有灵魂的内容
目录 ChatGPT的一些AI味道小问题 1.提供编辑指南 2.提供样本 3.思维链大纲 4.融入自己的想法 5.去除重复增加多样性 6.删除废话 ChatGPT的一些AI味道小问题 大多数宝子们再使用ChatGPT进行写作时,发现我们的老朋友ChatGPT在各类写作上还有点“机器人味”太重…...

【Threejs】从零开始(六)--GUI调试开发3D效果
请先完成前置步骤再进行下面操作:【Threejs】从零开始(一)--创建threejs应用-CSDN博客 一.GUI界面概述 GUI(Graphical User Interface)指的是图形化用户界面,广泛用在各种程序的上位机,能够通过…...

Cocos Creator 试玩广告开发
之前主要是使用Unity,这次刚好项目是试玩游戏的开发,所以临时学了Cocos来开发。所以这篇文章,更加关注从Unity转到Cocos开发的经历以及试玩的基本开发。 首先,我是没有使用过Cocos的,也没有接触过Ts语言,对于Ts的开发开…...

快速解决oracle 11g中exp无法导出空表的问题
在一些生产系统中,有些时候我们为了进行oracle数据库部分数据的备份和迁移,会使用exp进行数据的导出。但在实际导出的时候,我们发现导出的时候,发现很多空表未进行导出。今天我们给出一个快速解决该问题的办法。 一、问题复现 我…...

selenium 报错 invalid argument: invalid locator
环境: Python3.12.2 selenium4.0 报错信息: invalid argument: invalid locator 错误分析: selenium语法错误,find_element方法少写By.XPATH参数 错误语法如下: driver.find_element(//div[id"myid"]) 解决办…...

Flink2.0未来趋势中需要注意的一些问题
手机打字,篇幅不长,主要讲一下FFA中关于Flink2.0的未来趋势,直接看重点。 Flink Forward Asia 2024主会场有一场关于Flink2.0的演讲,很精彩,官方也发布了一些关于Flink2.0的展望和要解决的问题。 1.0时代和2.0时代避免…...

机械鹦鹉与真正的智能:大语言模型推理能力的迷思
编者按: 大语言模型真的具备推理能力吗?我们是否误解了"智能"的本质,将模式匹配误认为是真正的推理? 本文深入探讨了大语言模型(LLMs)是否真正具备推理能力这一前沿科学问题,作者的核…...

本地电脑使用命令行上传文件至远程服务器
将本地文件上传到远程服务器,在本地电脑中cmd使用该命令: scp C:/Users/"你的用户名"/Desktop/environment.yml ws:~/environment.yml 其中,C:/Users/“你的用户名”/Desktop/environment.yml是本地文件的路径, ~/en…...

【系统】Windows11更新解决办法,一键暂停
最近的windows更新整的我是措不及防,干啥都要关注一下更新的问题,有的时候还关不掉,我的强迫症就来了,非得关了你不可! 经过了九九八十一难的研究之后,终于找到了一个算是比较靠谱的暂停更新的方法&#x…...

34. Three.js案例-创建球体与模糊阴影
34. Three.js案例-创建球体与模糊阴影 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染 3D 场景的核心类。它负责将场景中的对象绘制到画布上。 构造器 new THREE.WebGLRenderer(parameters)参数类型描述parametersObject可选参数对象,包…...

Qt同步读取串口
头文件 #include "InsScpi.h" #include <QObject> #include <QSerialPort>class TestSerial : public QObject {Q_OBJECT public:explicit TestSerial(QObject *parent nullptr);//打开设备bool openDevice(const QString &portName);//关闭设备…...

如何用上AI视频工具Sora,基于ChatGPT升级Plus使用指南
没有GPT,可以参考这个教程:详情移步至底部参考原文查看哦~ 1.准备工作 详情移步至底部参考原文查看哦~ 详情移步至底部参考原文查看哦~ 4.Sora使用 详情移步至底部参考原文查看哦 参考文章:【包教包会】如何用上AI视频工具Soraÿ…...

对象的状态变化处理与工厂模式实现
一、引言 在 C 编程中,有效地处理对象的状态变化以及合理运用设计模式可以极大地提高代码的可维护性、可扩展性和可读性。本文将深入探讨 C 如何处理对象的状态变化以及如何实现工厂模式。 二、C 中对象的状态变化处理 使用成员变量表示状态 class GameCharacte…...

关于IP代理API,我应该了解哪些功能特性?以及如何安全有效地使用它来隐藏我的网络位置?
IP代理API是一种服务,允许用户通过访问经过中间服务器的网络连接来改变其公开的互联网协议地址(IP),从而达到隐藏真实地理位置的效果。以下是您在选择和使用IP代理API时应关注的一些功能和安全性考虑: 匿名度ÿ…...

在Linux上将 `.sh` 脚本、`.jar` 包或其他脚本文件添加到开机自启动
在Linux上将 .sh 脚本、.jar 包或其他脚本文件添加到开机自启动 在Linux环境中,有时需要将一些程序、脚本或应用程序设置为开机时自动启动。这对于那些需要在系统启动时启动的服务或应用非常有用。本文将介绍如何将 .sh 脚本、.jar 包或其他脚本文件添加到Linux系统…...

[Maven]构建项目与高级特性
有关于安装配置可以看我的另一篇文章:Maven下载安装配置与简介。 构建项目的生命周期和常用命令 这一节的内容熟记即可,要用了认得出来即可。 在Maven出现之前,项目构建的生命周期就已经存在。对项目进行清理、编译、测试、部署等一系列工作…...

【系统架构设计师】真题论文: 论数据分片技术及其应用(包括解题思路和素材)
更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 真题题目(2020年 试题1)解题思路论文素材参考Hash 分片原理一致性 Hash 分片原理按照数据范围(Range Based)分片原理项目采用的分片方式的实现过程和效果真题题目(2020年 试题1) 数据分片就是按照一定的规则…...

【bWAPP】XSS跨站脚本攻击实战
别低头,皇冠会掉;别流泪,贱人会笑。 0x01、XSS - Reflected (GET) Low 输入的内容直接输出到页面中: 后台服务端没有对输入的参数进行过滤, 构造一个注入xss payload即可: <script>alert(1)</script> 成功弹窗 Medium 审查…...