这个程序是学习github上的代码,我重新写了遍(这个学习方法挺有效),做了注解,并在此文与大家分享调试中遇到的注意事项,最后几个点花了我一个上午。
>>>框架
GLContext,
GLBaseViewController,
GLProgram(也有人命名为shaderProgram,这边统一GL打头,好看些
GLDrawProtocol
GLContext.swft
import Foundation
import OpenGLES
import GLKit
class GLContext {
enum GLContextError: Error {
case notSupportES3
}
var context: EAGLContext?
fileprivate(set) var maxVertexAttrib: Int = 9
fileprivate(set) var maxTextureUnits: Int = 0
fileprivate(set) var maxTextureSize: Int = 0
init() throws {
context = EAGLContext(api: .openGLES3)
if context == nil {
throw GLContextError.notSupportES3
}
checkEnviroment()
}
func activate(){
EAGLContext.setCurrent(context)
}
func deactivate(){
if EAGLContext.current() == context {
EAGLContext.setCurrent(nil)
}
}
func checkEnviroment(){
//指针是GLint, 编号是GLuint
//unsafeMutablePointer 是可变指针,类似于inout,所以要&
//unsafePointer不可变,所以不要&
var n: GLint = 0
//attribute 修饰的变量数目有限,比如最大0个,glGetIntegerv查询
glGetIntegerv(GLenum(GL_MAX_VERTEX_ATTRIBS), &n)
self.maxVertexAttrib = Int(n)
glGetIntegerv(GLenum(GL_MAX_TEXTURE_UNITS), &n)
self.maxTextureUnits = Int(n)
glGetIntegerv(GLenum(GL_MAX_TEXTURE_SIZE), &n)
self.maxTextureSize = Int(n)
outputEnviroment()
}
func outputEnviroment(){
var description = ""
description += "\n"
description += "\n-Max VertexAttrib: \(self.maxVertexAttrib)"
description += "\n-Max TextureUnits: \(self.maxTextureUnits)"
description += "\n-Max TextureSize: \(self.maxTextureSize)"
print(description)
}
}
GLBaseViewController.swift
import UIKit
import GLKit
class GLBaseViewController: GLKViewController {
var ctx: GLContext!
override func viewDidLoad() {
super.viewDidLoad()
do {
ctx = try GLContext()
} catch GLContext.GLContextError.notSupportES3 {
print(GLNote.failed.rawValue, "not support OpenglES3")
} catch {
print(GLNote.failed.rawValue, "context")
}
let view = self.view as! GLKView
view.context = ctx.context!
ctx.activate()
view.drawableColorFormat = .RGBA8888
view.drawableDepthFormat = .format24
view.drawableMultisample = .multisample4X
self.preferredFramesPerSecond = 60
self.pauseOnWillResignActive = true
self.resumeOnDidBecomeActive = true
//glEnable should be placed here, in glkView will occur error
glEnable(GLenum(GL_DEPTH_TEST))
//GL_DEPTH_TEST: check if there are pixels in front of the current pixel, if exists, then current pixel will not be drew. In other words, opengl always draw the uppermost pixel.
//GL_BLEND: use blend map, you need close GL_DEPTH_TEST, open GL_BLEND, set glColor4f, glBlendFunc
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
GLProgram.swift
//
// GLProgram.swift
// stNightGirl
//
// Created by sj on 05/05/2018.
// Copyright © 2018 sj. All rights reserved.
//
import Foundation
import OpenGLES
enum GLNote: String {
case failed = "[OpenGL Failure]"
}
class GLProgram {
//着色器文件
var vertexFile: String
var fragmentFile: String
var program: GLuint = 0 //起始编号为0
var vertexShader: GLuint = 0
var fragmentShader: GLuint = 0
init(vertexFileName: String, fragmentFileName: String){
self.vertexFile = vertexFileName
self.fragmentFile = fragmentFileName
}
deinit {
glDeleteProgram(program)
}
//MARK: program 4
func use(){
if program != 0 {
glUseProgram(program)
}
}
func validate() -> Bool {
glValidateProgram(program)
var status: GLint = 0
glGetProgramiv(program, GLenum(GL_VALIDATE_STATUS), &status)
if status == 0 {
outputProgramError()
return false
}
return true
}
fileprivate func linkProgram() -> Bool {
program = glCreateProgram()
glAttachShader(program, vertexShader)
glAttachShader(program, fragmentShader)
glLinkProgram(program)
var status: GLint = 0
glGetProgramiv(program, GLenum(GL_LINK_STATUS), &status)
if status == 0 {
outputProgramError()
glDeleteProgram(program)
return false
}
glDetachShader(program, vertexShader)
glDetachShader(program, fragmentShader)
glDeleteShader(vertexShader)
glDeleteShader(fragmentShader)
return true
}
fileprivate func outputProgramError(){
var logLength: GLint = 0
glGetProgramiv(program, GLenum(GL_INFO_LOG_LENGTH), &logLength)
if logLength > 0{
var logChar: [GLchar] = [GLchar](repeating: 0, count: Int(logLength))
glGetProgramInfoLog(program, GLsizei(logLength), &logLength, &logChar)
let logString = String(cString: logChar, encoding: String.Encoding.utf8)
print("Error in Program: \(logString)")
}
}
func getUniformLocation(name: String) -> Int32 {
return glGetUniformLocation(program, name.cString(using: .utf8))
}
//MARK: shader 3
fileprivate func compileShader() -> Bool {
if !compileShader(shader: &vertexShader, type: GLenum(GL_VERTEX_SHADER), fileName: self.vertexFile) {
print(GLNote.failed.rawValue, "compile vertex shader")
return false
}
if !compileShader(shader: &fragmentShader, type: GLenum(GL_FRAGMENT_SHADER), fileName: fragmentFile){
print(GLNote.failed.rawValue, "compile fragment shader")
return false
}
return true
}
fileprivate func compileShader(shader: inout GLuint, type: GLenum, fileName: String) -> Bool {
// 1
shader = glCreateShader(type)
if shader == 0 {
print(GLNote.failed.rawValue, "create shader fail, check context")
}
// 2
var source: UnsafePointer<Int8>
do {
let url = Bundle.main.url(forResource: fileName, withExtension: nil)
source = try NSString(contentsOf: url!, encoding: String.Encoding.utf8.rawValue).utf8String!
var sourceChar: UnsafePointer<GLchar>? = UnsafePointer<GLchar>(source)
glShaderSource(shader, 1, &sourceChar, nil)
} catch {
print(GLNote.failed.rawValue, "read shader src \(fileName)", error)
}
// 3
glCompileShader(shader)
// 4
var status: GLint = 0
glGetShaderiv(shader, GLenum(GL_COMPILE_STATUS), &status) //1 success, 0 fail
if status == 0 {
outputShaderError(shader: shader, fileName: fileName)
glDeleteShader(shader)
return false
}
return true
}
fileprivate func outputShaderError(shader: GLuint, fileName: String){
var logLength: GLint = 0
glGetShaderiv(shader , GLenum(GL_INFO_LOG_LENGTH), &logLength)
if logLength > 0 {
let logChar: UnsafeMutablePointer<GLchar> = UnsafeMutablePointer<GLchar>.allocate(capacity: Int(logLength))
logChar.initialize(to: 0)
glGetShaderInfoLog(shader, GLsizei(logLength), &logLength, logChar)
let logString = String(cString: logChar, encoding: String.Encoding.utf8)
print(GLNote.failed.rawValue, "shader\(fileName), \(String(describing: logString))")
logChar.deinitialize()
logChar.deallocate(capacity: Int(logLength))
}
}
@discardableResult
func loadShaders() -> Bool {
if !compileShader() {
return false
}
if !linkProgram() {
return false
}
return true
}
}
GLDrawProtocol.swift
import Foundation
protocol GLDrawProtocol {
var shaderProgram: GLProgram { get set }
func loadShader()
func bindVertexData()
func draw()
}
以上能成功读shader,朋友们直接拿去用即可,有什么可以email我。
下面是测试文件,Triangle
Triangle.swift
import Foundation
import GLKit
class Triangle: GLDrawProtocol {
var shaderProgram: GLProgram
var vertexArray: GLuint = 0
var vertexBuffer: GLuint = 0
var indexBuffer: GLuint = 0
var vertices: [GLfloat] = [ //accept GLFloat & Float
0.5, -0.5, 0,
0.5, 0.5, 0,
-0.5, 0.5, 0,
-0.5, -0.5, 0 ]
var indices: [GLuint] = [ //must be GLuint, Int no
0, 1, 2,
0, 2, 3 ]
init() {
shaderProgram = GLProgram(vertexFileName: "tri.vsh", fragmentFileName: "tri.fsh")
}
func loadShader() {
shaderProgram.loadShaders()
}
func bindVertexData() {
//产生vao,顶点数组对象,每个顶点有坐标/法线/颜色/纹理坐标信息
//@param: 1st 数量,2nd 编号数组
/*
//多个vao
var uu: [GLuint] = [GLuint](repeating: 0, count:19)
glGenVertexArrays(19, &uu)
//单个vao
var pp: GLuint = 0
glGenVertexArrays(1, &pp)
*/
// 1 vao
glGenVertexArrays(1, &vertexArray)
glBindVertexArray(vertexArray)
// 2 vbo
glGenBuffers(1, &vertexBuffer)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)
let verticesBufferSize = vertices.count * MemoryLayout<GLfloat>.size
glBufferData(GLenum(GL_ARRAY_BUFFER), verticesBufferSize, vertices, GLenum(GL_STATIC_DRAW)) //static,数据不变;dynamic,变;stream,每帧变
// 3 veo
glGenBuffers(1, &indexBuffer)
glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), indexBuffer) //designate buffer type
let indexBufferSize = indices.count * MemoryLayout<GLuint>.size
glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER), indexBufferSize, indices, GLenum(GL_STATIC_DRAW))
// 4 enable position in shader
let stride = 3 * MemoryLayout<GLfloat>.size
//@param indx: number in shader, see tri.vsh -> layout (location = 0) in position
//@param size: must be 1, 2, 3, or 4. The initial value is 4.
glVertexAttribPointer(0, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(stride), UnsafeRawPointer(bitPattern: 0))
glEnableVertexAttribArray(0)
// 5
glBindVertexArray(0) //release vao
}
func draw() {
// 1 clear, in parent glkView
// 2 have turn program or uniform will occur error
shaderProgram.use()
// 3 animate
let offset: Int32 = shaderProgram.getUniformLocation(name: "positionOffset")
let pos = generatePosition()
glUniform3f(offset, pos.x, pos.y, 0)
let customColor = shaderProgram.getUniformLocation(name: "customColor")
let color = generateColors()
glUniform4f(customColor, color.red, color.green, color.blue, 1.0)
// 4
glBindVertexArray(vertexArray)
glDrawElements(GLenum(GL_TRIANGLES), 6, GLenum(GL_UNSIGNED_INT), UnsafeRawPointer(bitPattern: 0)) //according to your index order
glBindVertexArray(0)
}
}
extension Triangle {
fileprivate func generateColors() -> (red: GLfloat, green: GLfloat, blue: GLfloat) {
let t = CFAbsoluteTimeGetCurrent()
let red = cos(t)/2 + 0.5
let green = sin(t)/2 + 0.5
let blue = cos(t)*sin(t)
return (GLfloat(red), GLfloat(green), GLfloat(blue))
}
fileprivate func generatePosition() -> (x: GLfloat, y: GLfloat) {
let t = CFAbsoluteTimeGetCurrent()
let x = cos(t)/2 //圆的参数方程,半径为屏幕一半
let y = sin(t)/2
return (GLfloat(x), GLfloat(y))
}
}
tri.vsh
#version 300 core
precision highp float;
layout (location = 0) in vec3 position;
uniform vec3 positionOffset;
void main() {
gl_Position = vec4(position + positionOffset, 1);
}
tri.fsh
#version 300 core
precision highp float;
uniform vec4 customColor;
out vec4 color;
void main() {
//color = vec4(1.0, 1.0, 1.0, 1.0);
color = customColor;
}
Line.swift
import Foundation
import GLKit
class Line: GLDrawProtocol {
var shaderProgram: GLProgram
var vertexArray: GLuint = 0
var vertexBuffer: GLuint = 0
//method 1: pure array
var v1: [GLfloat] = [
-0.5, 0.5, 0,
0.5, -0.5, 0
]
//method 2: position
struct Point {
var x: GLfloat
var y: GLfloat
var z: GLfloat
}
var v2: [Point] = [
Point(x: -0.5, y: 0.5, z: 0),
Point(x: 0.5, y: -0.5, z: 0)
]
//method 3: multi attributes
struct Vertex {
var position = [GLfloat](repeating: 0, count: 3)
//var texCoord = [Int](repeating: 0, count: 2)
}
var v3: [Vertex] = [
Vertex(position: [-0.5, 0.5, 0]),
Vertex(position: [0.5, -0.5, 0])
]
init() {
shaderProgram = GLProgram(vertexFileName: "line.vsh", fragmentFileName: "line.fsh")
}
func loadShader() {
shaderProgram.loadShaders()
}
func bindVertexData() {
// 1 vao
glGenVertexArrays(1, &vertexArray)
glBindVertexArray(vertexArray)
// 2 vbo
glGenBuffers(1, &vertexBuffer)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)
//m1
//let bs1 = v1.count * MemoryLayout<GLfloat>.size
//glBufferData(GLenum(GL_ARRAY_BUFFER), GLsizeiptr(bs1), v1, GLenum(GL_STATIC_DRAW))
//m2
let bs2 = v2.count * MemoryLayout<Point>.size
glBufferData(GLenum(GL_ARRAY_BUFFER), bs2, v2, GLenum(GL_STATIC_DRAW))
//m3 - no
//let bs3 = v3.count * MemoryLayout<Vertex>.size
//glBufferData(GLenum(GL_ARRAY_BUFFER), GLsizeiptr(bs3), v3, GLenum(GL_STATIC_DRAW))
// 3
//m1
//glVertexAttribPointer(0, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 3), UnsafeRawPointer(bitPattern: 0))
//m2
glVertexAttribPointer(0, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<Point>.size), UnsafeRawPointer(bitPattern: 0))
//m3 - no
//glVertexAttribPointer(0, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<Vertex>.size), UnsafeRawPointer(bitPattern: 0))
glEnableVertexAttribArray(0)
glBindVertexArray(0)
}
func draw() {
glLineWidth(10)
shaderProgram.use()
glBindVertexArray(vertexArray)
glDrawArrays(GLenum(GL_LINES), 0, 2)
glBindVertexArray(0)
//GL_POINTS
//GL_LINES
//GL_LINE_LOOP
//GL_LINE_STRIP
//顶点为3的倍数,剩下的1或2个点不显示
//GL_TRIANGLES
//v1, v2, v3 -> v4, v5, v6
//GL_TRIANGLE_STRIP,
//2)偶数, T = [n-1, n-2, n]
//3)奇数, T = [n-2, n-1, n]
//GL_TRIANGLE_FAN 共享定顶点
}
}
line.vsh
#version 300 core
precision highp float;
layout (location = 0) in vec3 position;
void main(){
gl_Position = vec4(position, 1.0);
}
line.fsh
#version 300 core
precision highp float;
out vec4 color;
void main(){
color = vec4(0.0f, 0.0f, 1.0f, 1.0f);
}
TriangleViewController.swift
import UIKit
import GLKit
class TriangleViewController: GLBaseViewController {
var triangle: Triangle!
var line: Line!
override func viewDidLoad() {
super.viewDidLoad()
triangle = Triangle()
triangle.loadShader()
triangle.bindVertexData()
line = Line()
line.loadShader()
line.bindVertexData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func glkView(_ view: GLKView, drawIn rect: CGRect) {
glClearColor(0.5, 0.8, 0.9, 1)
glClear(GLbitfield(GL_COLOR_BUFFER_BIT) | GLbitfield(GL_DEPTH_BUFFER_BIT))
//opengl draw order is like stack
//first triangle, then line
//so line is infront of triangle
line.draw()
triangle.draw()
}
}