关于openGL的一些学习记录

414 阅读8分钟

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 特性和最佳实践