海安县城乡建设局网站,适合设计师的网站编辑软件,wordpress使用攻略,在国外服务器上做网站项目如何赚钱OpenGL ES 02 加载3张图片并做混合处理 什么是纹理单元纹理单元的作用使用纹理单元的步骤详细解释加载图片并绑定到到GPU纹理单元采样器的设置1.设置采样器变量的纹理单元编号#xff0c;目的是为了告诉纹理采样器#xff0c;从哪个纹理单元采集数据2.如果你没有显式地设置采… OpenGL ES 02 加载3张图片并做混合处理 什么是纹理单元纹理单元的作用使用纹理单元的步骤详细解释加载图片并绑定到到GPU纹理单元采样器的设置1.设置采样器变量的纹理单元编号目的是为了告诉纹理采样器从哪个纹理单元采集数据2.如果你没有显式地设置采样器变量的纹理单元编号OpenGL ES 可能会默认将所有采样器变量绑定到纹理单元 0。3.这意味着所有的采样器变量caodi, huangtu, noise都会从纹理单元 0 中采样数据 顶点着色器fragment.glsl片段着色器fragment.glslShader类封装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, MemoryLayoutGLubyte.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, MemoryLayoutGLubyte.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: UnsafePointerGLchar? UnsafePointerGLchar(ptr)glShaderSource(vertexShader, 1, p, nil)}fragmentSource.withCString { ptr invar p: UnsafePointerGLchar? UnsafePointerGLchar(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 UnsafeMutablePointerGLchar.allocate(capacity: 512)glGetProgramInfoLog(shaderProgram, GLsizei(MemoryLayoutGLchar.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), MemoryLayoutGLfloat.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), MemoryLayoutGLfloat.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), MemoryLayoutGLubyte.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(MemoryLayoutGLfloat.size * 3), nil)glEnableVertexAttribArray(0)// uvglBindBuffer(GLenum(GL_ARRAY_BUFFER), uvVBO)glVertexAttribPointer(1, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayoutGLfloat.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, MemoryLayoutGLubyte.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), MemoryLayoutGLfloat.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), MemoryLayoutGLfloat.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), MemoryLayoutGLubyte.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(MemoryLayoutGLfloat.size * 3), nil)glEnableVertexAttribArray(0)glBindBuffer(GLenum(GL_ARRAY_BUFFER), uvVBO)glVertexAttribPointer(1, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayoutGLfloat.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, MemoryLayoutGLubyte.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并可以在渲染过程中使用。
素材 最终效果