普通视图

发现新文章,点击刷新页面。
今天 — 2025年7月7日首页

关于openGL的一些学习记录

2025年7月7日 09:51

OpenGL 学习总结

目录

  1. 基础概念

  2. 渲染管线

  3. 着色器编程

  4. 纹理与采样

  5. iOS OpenGL ES

  6. 实际应用

  7. 性能优化

  8. 常见问题


基础概念

什么是 OpenGL?

OpenGL(Open Graphics Library)是一个跨语言、跨平台的图形渲染 API,用于渲染 2D 和 3D 图形。它提供了一套硬件无关的接口,让开发者能够利用 GPU 进行高效的图形渲染。

核心特点

  • 硬件抽象层:屏蔽不同 GPU 的差异

  • 状态机:通过设置状态来控制渲染行为

  • 立即模式 vs 保留模式:现代 OpenGL 使用保留模式

  • 可编程管线:通过着色器程序控制渲染过程

坐标系系统


// OpenGL 使用右手坐标系

// X轴:向右为正

// Y轴:向上为正  

// Z轴:向外为正(屏幕外)

  


// 顶点坐标通常在 [-1, 1] 范围内

let vertices: [Float] = [

    -1.0, -1.0, 0.0// 左下

     1.0, -1.0, 0.0// 右下

     0.01.0, 0.0   // 顶部

]


渲染管线

1. 顶点着色器阶段


// 顶点着色器

attribute vec4 position;

attribute vec2 texCoord;

varying vec2 vTexCoord;

  


void main() {

    gl_Position = position;

    vTexCoord = texCoord;

}

2. 图元装配

  • 将顶点连接成图元(点、线、三角形)

  • 进行视锥体裁剪

  • 背面剔除

3. 光栅化

  • 将图元转换为像素

  • 插值计算片段的属性

4. 片段着色器阶段


// 片段着色器

precision mediump float;

varying vec2 vTexCoord;

uniform sampler2D uTexture;

  


void main() {

    vec4 color = texture2D(uTexture, vTexCoord);

    gl_FragColor = color;

}

5. 逐片段操作

  • 深度测试

  • 模板测试

  • 混合


着色器编程

顶点着色器


// 基础顶点着色器

attribute vec4 aPosition;

attribute vec2 aTexCoord;

uniform mat4 uModelViewProjectionMatrix;

  


varying vec2 vTexCoord;

  


void main() {

    gl_Position = uModelViewProjectionMatrix * aPosition;

    vTexCoord = aTexCoord;

}

片段着色器


// 基础片段着色器

precision mediump float;

  


varying vec2 vTexCoord;

uniform sampler2D uTexture;

uniform float uAlpha;

  


void main() {

    vec4 color = texture2D(uTexture, vTexCoord);

    gl_FragColor = vec4(color.rgb, color.a * uAlpha);

}

常用内置变量

  • gl_Position:顶点位置(顶点着色器输出)

  • gl_FragColor:片段颜色(片段着色器输出)

  • gl_PointSize:点大小

  • gl_FragCoord:片段坐标

数据类型


// 标量类型

float, int, bool

  


// 向量类型

vec2, vec3, vec4

ivec2, ivec3, ivec4

bvec2, bvec3, bvec4

  


// 矩阵类型

mat2, mat3, mat4

  


// 采样器类型

sampler2D, samplerCube


纹理与采样

纹理创建


func createTexture(from image: UIImage) -> GLuint {

    guard let cgImage = image.cgImage else { return 0 }

    

    var textureName: GLuint = 0

    glGenTextures(1, &textureName)

    glBindTexture(GLenum(GL_TEXTURE_2D), textureName)

    

    // 设置纹理参数

    glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)

    glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)

    glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE)

    glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE)

    

    // 上传纹理数据

    let width = cgImage.width

    let height = cgImage.height

    let colorSpace = CGColorSpaceCreateDeviceRGB()

    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)

    

    let context = CGContext(data: nil,

                           width: width,

                           height: height,

                           bitsPerComponent: 8,

                           bytesPerRow: width * 4,

                           space: colorSpace,

                           bitmapInfo: bitmapInfo.rawValue)!

    

    context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

    

    let data = context.data!

    glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), data)

    

    return textureName

}

纹理坐标


// 纹理坐标 (0,0) 在左下角,(1,1) 在右上角

let texCoords: [Float] = [

    0.0, 0.0// 左下

    1.0, 0.0// 右下

    0.0, 1.0// 左上

    1.0, 1.0   // 右上

]

纹理过滤


// 最近邻过滤

glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_NEAREST)

glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_NEAREST)

  


// 线性过滤

glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)

glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)

  


// 多级纹理过滤

glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR_MIPMAP_LINEAR)

glGenerateMipmap(GLenum(GL_TEXTURE_2D))


iOS OpenGL ES

初始化


import GLKit

  


class OpenGLView: GLKView {

    private var context: EAGLContext!

    private var program: GLuint = 0

    

    override init(frame: CGRect) {

        // 创建 OpenGL ES 2.0 上下文

        context = EAGLContext(api: .openGLES2)!

        super.init(frame: frame, context: context)

        

        // 设置代理

        self.delegate = self

        

        // 设置像素格式

        self.drawableColorFormat = .RGBA8888

        self.drawableDepthFormat = .format24

        

        // 设置内容缩放因子

        self.contentScaleFactor = UIScreen.main.scale

        

        // 设置当前上下文

        EAGLContext.setCurrent(context)

        

        // 初始化 OpenGL 状态

        setupOpenGL()

    }

    

    private func setupOpenGL() {

        // 启用深度测试

        glEnable(GLenum(GL_DEPTH_TEST))

        

        // 设置清除颜色

        glClearColor(0.0, 0.0, 0.0, 1.0)

        

        // 创建着色器程序

        program = createShaderProgram()

    }

}

渲染循环


extension OpenGLView: GLKViewDelegate {

    func glkView(_ view: GLKView, drawIn rect: CGRect) {

        // 清除缓冲区

        glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))

        

        // 使用着色器程序

        glUseProgram(program)

        

        // 设置顶点数据

        setupVertexData()

        

        // 设置纹理

        setupTexture()

        

        // 绘制

        glDrawArrays(GLenum(GL_TRIANGLES), 0, 6)

    }

}

着色器编译


func createShaderProgram() -> GLuint {

    let vertexShaderSource = """

    attribute vec4 position;

    attribute vec2 texCoord;

    varying vec2 vTexCoord;

    

    void main() {

        gl_Position = position;

        vTexCoord = texCoord;

    }

    """

    

    let fragmentShaderSource = """

    precision mediump float;

    varying vec2 vTexCoord;

    uniform sampler2D uTexture;

    

    void main() {

        gl_FragColor = texture2D(uTexture, vTexCoord);

    }

    """

    

    // 编译顶点着色器

    let vertexShader = compileShader(type: GLenum(GL_VERTEX_SHADER), source: vertexShaderSource)

    

    // 编译片段着色器

    let fragmentShader = compileShader(type: GLenum(GL_FRAGMENT_SHADER), source: fragmentShaderSource)

    

    // 创建程序

    let program = glCreateProgram()

    glAttachShader(program, vertexShader)

    glAttachShader(program, fragmentShader)

    glLinkProgram(program)

    

    // 检查链接状态

    var linkStatus: GLint = 0

    glGetProgramiv(program, GLenum(GL_LINK_STATUS), &linkStatus)

    if linkStatus == GL_FALSE {

        print("Program link failed")

        glDeleteProgram(program)

        return 0

    }

    

    // 清理着色器

    glDeleteShader(vertexShader)

    glDeleteShader(fragmentShader)

    

    return program

}

  


func compileShader(type: GLenum, source: String) -> GLuint {

    let shader = glCreateShader(type)

    var cSource = (source as NSString).utf8String

    var length = GLint(source.utf8.count)

    glShaderSource(shader, 1, &cSource, &length)

    glCompileShader(shader)

    

    // 检查编译状态

    var compileStatus: GLint = 0

    glGetShaderiv(shader, GLenum(GL_COMPILE_STATUS), &compileStatus)

    if compileStatus == GL_FALSE {

        var infoLength: GLint = 0

        glGetShaderiv(shader, GLenum(GL_INFO_LOG_LENGTH), &infoLength)

        var infoLog = [GLchar](repeating: 0, count: Int(infoLength))

        glGetShaderInfoLog(shader, infoLength, nil, &infoLog)

        print("Shader compilation failed: \(String(cString: infoLog))")

        glDeleteShader(shader)

        return 0

    }

    

    return shader

}


实际应用

图片渲染


class ImageRenderer {

    private var program: GLuint = 0

    private var vertexBuffer: GLuint = 0

    private var texture: GLuint = 0

    

    func setup() {

        // 创建顶点缓冲区

        let vertices: [Float] = [

            // 位置        // 纹理坐标

            -1.0, -1.0, 0.00.0, 0.0,

             1.0, -1.0, 0.01.0, 0.0,

            -1.01.0, 0.00.0, 1.0,

             1.0, -1.0, 0.01.0, 0.0,

             1.01.0, 0.01.0, 1.0,

            -1.01.0, 0.00.0, 1.0

        ]

        

        glGenBuffers(1, &vertexBuffer)

        glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)

        glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<Float>.size * vertices.count, vertices, GLenum(GL_STATIC_DRAW))

    }

    

    func render() {

        glUseProgram(program)

        

        // 设置顶点属性

        glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)

        

        let positionLocation = glGetAttribLocation(program, "position")

        let texCoordLocation = glGetAttribLocation(program, "texCoord")

        

        glEnableVertexAttribArray(GLuint(positionLocation))

        glEnableVertexAttribArray(GLuint(texCoordLocation))

        

        let stride = GLsizei(MemoryLayout<Float>.size * 5)

        glVertexAttribPointer(GLuint(positionLocation), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), stride, nil)

        glVertexAttribPointer(GLuint(texCoordLocation), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), stride, UnsafeRawPointer(bitPattern: 3 * MemoryLayout<Float>.size))

        

        // 设置纹理

        glActiveTexture(GLenum(GL_TEXTURE0))

        glBindTexture(GLenum(GL_TEXTURE_2D), texture)

        glUniform1i(glGetUniformLocation(program, "uTexture"), 0)

        

        // 绘制

        glDrawArrays(GLenum(GL_TRIANGLES), 0, 6)

        

        glDisableVertexAttribArray(GLuint(positionLocation))

        glDisableVertexAttribArray(GLuint(texCoordLocation))

    }

}

滤镜效果


// 灰度滤镜

void main() {

    vec4 color = texture2D(uTexture, vTexCoord);

    float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));

    gl_FragColor = vec4(vec3(gray), color.a);

}

  


// 反色滤镜

void main() {

    vec4 color = texture2D(uTexture, vTexCoord);

    gl_FragColor = vec4(1.0 - color.rgb, color.a);

}

  


// 模糊滤镜

void main() {

    vec2 texelSize = 1.0 / textureSize(uTexture, 0);

    vec4 sum = vec4(0.0);

    

    for(int i = -2; i <= 2; i++) {

        for(int j = -2; j <= 2; j++) {

            vec2 offset = vec2(float(i), float(j)) * texelSize;

            sum += texture2D(uTexture, vTexCoord + offset);

        }

    }

    

    gl_FragColor = sum / 25.0;

}


性能优化

1. 批处理


// 合并多个绘制调用

func batchRender(objects: [GameObject]) {

    // 按材质分组

    let groupedObjects = Dictionary(grouping: objects) { $0.material }

    

    for (material, objects) in groupedObjects {

        // 绑定材质

        bindMaterial(material)

        

        // 批量绘制

        for object in objects {

            updateTransform(object.transform)

            drawObject(object)

        }

    }

}

2. 顶点缓冲区优化


// 使用 VBO 存储顶点数据

func createVertexBuffer() {

    let vertices: [Float] = [/* 顶点数据 */]

    

    glGenBuffers(1, &vertexBuffer)

    glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)

    glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<Float>.size * vertices.count, vertices, GLenum(GL_STATIC_DRAW))

}

3. 纹理优化


// 使用纹理图集

func createTextureAtlas() {

    // 将多个小纹理合并到一个大纹理中

    // 减少纹理切换次数

}

  


// 使用压缩纹理

func loadCompressedTexture() {

    // 使用 PVRTC 或 ASTC 格式

    // 减少内存占用和带宽

}

4. 着色器优化


// 避免分支语句

// 不好的做法

if (condition) {

    color = texture2D(tex1, coord);

} else {

    color = texture2D(tex2, coord);

}

  


// 好的做法

color = mix(texture2D(tex1, coord), texture2D(tex2, coord), condition ? 1.0 : 0.0);

  


// 使用内置函数

// 不好的做法

float length = sqrt(x * x + y * y);

  


// 好的做法

float length = length(vec2(x, y));


常见问题

1. 纹理显示问题

问题:纹理显示为黑色或白色

原因

  • 纹理数据格式不匹配

  • 纹理坐标错误

  • 采样器设置问题

解决


// 检查纹理格式

glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, width, height, 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), data)

  


// 检查纹理坐标

let texCoords: [Float] = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]

  


// 设置正确的采样器

glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)

glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)

2. 深度测试问题

问题:物体渲染顺序错误

解决


// 启用深度测试

glEnable(GLenum(GL_DEPTH_TEST))

glDepthFunc(GLenum(GL_LESS))

  


// 清除深度缓冲区

glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))

3. 内存泄漏

问题:OpenGL 资源未正确释放

解决


deinit {

    // 释放纹理

    if texture != 0 {

        glDeleteTextures(1, &texture)

    }

    

    // 释放缓冲区

    if vertexBuffer != 0 {

        glDeleteBuffers(1, &vertexBuffer)

    }

    

    // 释放着色器程序

    if program != 0 {

        glDeleteProgram(program)

    }

}

4. 性能问题

问题:渲染性能低下

解决

  • 减少绘制调用次数

  • 使用批处理

  • 优化着色器

  • 使用 LOD(细节层次)

  • 启用背面剔除


调试技巧

1. 着色器调试


// 在片段着色器中输出调试信息

void main() {

    vec4 color = texture2D(uTexture, vTexCoord);

    

    // 输出红色通道作为调试

    gl_FragColor = vec4(color.r, 0.0, 0.0, 1.0);

}

2. 状态检查


func checkOpenGLState() {

    // 检查帧缓冲区状态

    let status = glCheckFramebufferStatus(GLenum(GL_FRAMEBUFFER))

    if status != GLenum(GL_FRAMEBUFFER_COMPLETE) {

        print("Framebuffer not complete: \(status)")

    }

    

    // 检查着色器编译状态

    var compileStatus: GLint = 0

    glGetShaderiv(shader, GLenum(GL_COMPILE_STATUS), &compileStatus)

    if compileStatus == GL_FALSE {

        // 获取错误信息

        var infoLength: GLint = 0

        glGetShaderiv(shader, GLenum(GL_INFO_LOG_LENGTH), &infoLength)

        var infoLog = [GLchar](repeating: 0, count: Int(infoLength))

        glGetShaderInfoLog(shader, infoLength, nil, &infoLog)

        print("Shader compilation failed: \(String(cString: infoLog))")

    }

}

3. 性能分析


// 使用 Instruments 进行性能分析

// 关注以下指标:

// - GPU 使用率

// - 绘制调用次数

// - 纹理内存使用

// - 顶点处理数量


总结

OpenGL 是一个强大的图形渲染 API,掌握它需要:

  1. 理解渲染管线:从顶点到像素的完整流程

  2. 掌握着色器编程:GLSL 语言和 GPU 编程

  3. 熟悉纹理系统:纹理创建、采样和过滤

  4. 学会性能优化:批处理、内存管理、算法优化

  5. 掌握调试技巧:状态检查、错误处理、性能分析

通过持续学习和实践,OpenGL 将成为你图形编程的强大工具。记住:

  • 从简单开始,逐步增加复杂度

  • 重视性能优化

  • 养成良好的调试习惯

  • 关注最新的 OpenGL 特性和最佳实践

❌
❌