整体思路
一、GLSL和iOS
本片主要使用编译链接自定义的shader(顶点着色器/片元着色器)。用简单的GLSL语言来实现顶点、片元着色器,并对图形进行简单的变换。
OpenGL ES只是用来做渲染的,所iOS要提供一个载体,就是CAEAGLLayer,创建的方法是,通过重写UIView的类属性(OC中是类方法)返回CAEAGLLayer.self。它是一个对core animation的封装,它能满足所有的OpenGLES的方法访问。
CAEAGLLayer:在制定该图层关联的视图作为渲染器的目标图形上下文之前,可以使用drawableProperties属性更改呈现属性。此属性允许您配置呈现表面的颜色格式以及表面是否保留其内容。 因为OpenGL ES渲染的效果是要提交到用户使用的核心动画上,所以使用在该layer上的任何效果和动画都会影响渲染的3D效果,为了时性能最佳你应该做一下操作:设置图层为不透明,设置图层边界以匹配显示的尺寸,确保图层没有做变换。
尽量避免在CAEAGLLayer添加其layer。如果必须要添加其他非OpenGL内容,那么如果将透明的2D内容置于GL内容之上,并确保OpenGL内容是不透明的且没有转换过,那么性能还是可以接受的。当在竖屏上绘制横向内容时,应该自己旋转内容,而不是使用CAEAGLLayer转换来旋转它。
二、GLSL实现图片渲染的思路
创建图层:重写layerClass,将YDWView返回的图层从CALayer替换成CAEAGLLayer,并设置描述属性;
创建上下文:上下文主要是用于保存OpenGL ES中的状态,是一个状态机,不论是GLKIt还是GLSL,都是需要context的;
清空缓存区:buffer分为 frameBuffer 和 renderBuffer 两个大类,都需要清空;
设置RenderBuffer:渲染缓存区
设置FrameBuffer:帧缓存区
开始绘制:读取顶点/片元着色器的程序,并加载shader,编译顶点着色程序/片元着色器程序,然后链接程序,设置顶点、纹理坐标,并处理顶点坐标和纹理;
析构函数中释放buffer。
三、GLSL着色语言
准备工作
一、ViewController和View
新建一个view继承UIView,命名为YDWView;
在ViewController中声明一个var myView : YDWView!属性。在viewDidLoad加载myView:
override func viewDidLoad ( ) {
super . viewDidLoad ( )
self . myView = self . view as? YDWView
}
在YDWView中定义部分需要用到的全局变量(在iOS和tvOS上绘制OpenGL ES内容的图层,继承CALayer),并重载layoutSubviews和重写layerClass方法(重写layerClass,将YDWView返回的图层从CALayer替换成CAEAGLLayer):
var myEagLayer : CAEAGLLayer!
var myContext : EAGLContext!
var myColorRenderBuffer : GLuint!
var myColorFrameBuffer : GLuint!
var myPrograme : GLuint!
override func layoutSubviews ( ) {
}
override class var layerClass: AnyClass {
return CAEAGLLayer. self
}
二、自定义着色器的vsh和fsh文件创建
attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main ( ) {
varyTextCoord = textCoordinate;
gl_Position = position;
}
创建shaderv.fsh文件(片元着色器):将gl_FragColor = texture2D(colorMap, varyTextCoord);改为gl_FragColor = texture2D(colorMap, vec2(varyTextCoord.x,1.0-varyTextCoord.y));翻转纹理;
precision highp float ;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main ( ) {
gl_FragColor = texture2D ( colorMap, vec2 ( varyTextCoord. x, 1.0 - varyTextCoord. y) ) ;
}
渲染流程
一、创建图层
private func setupLayer ( ) {
self . myEagLayer = self . layer as? CAEAGLLayer
self . contentScaleFactor = UIScreen. main. scale
self . myEagLayer. drawableProperties = [ kEAGLDrawablePropertyRetainedBacking : false,
kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8]
}
二、创建上下文
创建图形上下文,指定OpenGL ES 渲染API版本;
判断是否创建成功和设置图形上下文是否成功;
将局部context,变成全局的context;
private func setupContext ( ) {
if let context = EAGLContext. init ( api: . openGLES2) {
EAGLContext. setCurrent ( context)
self . myContext = context
} else {
print ( "Failed" )
}
}
三、清空缓存区
private func clearRenderAndFrameBuffer ( ) {
glDeleteBuffers ( 1 , & myColorRenderBuffer)
self . myColorRenderBuffer = 0
glDeleteBuffers ( 1 , & myColorFrameBuffer)
self . myColorFrameBuffer = 0
}
四、 设置RenderBuffer(渲染缓存区)
private func setupRenderBuffer ( ) {
glGenBuffers ( 1 , & self . myColorRenderBuffer)
glBindRenderbuffer ( GLenum ( GL_RENDERBUFFER) , self . myColorRenderBuffer)
self . myContext. renderbufferStorage ( Int ( GL_RENDERBUFFER) , from: self . myEagLayer)
}
五、设置FrameBuffer(帧缓存区)
private func setupFrameBuffer ( ) {
glGenBuffers ( 1 , & self . myColorFrameBuffer)
glBindFramebuffer ( GLenum ( GL_FRAMEBUFFER) , self . myColorFrameBuffer)
glFramebufferRenderbuffer ( GLenum ( GL_FRAMEBUFFER) , GLenum ( GL_COLOR_ATTACHMENT0) , GLenum ( GL_RENDERBUFFER) , self . myColorRenderBuffer)
}
生成帧缓存区之后,则需要将renderbuffer跟framebuffer进行绑定,调用glFramebufferRenderbuffer函数进行绑定到对应的附着点上,绘制才能起作用。
六、开始绘制
glClearColor ( 0.4 , 0.4 , 0.4 , 1.0 )
glClear ( GLbitfield ( GL_COLOR_BUFFER_BIT) )
let scale = UIScreen. main. scale
glViewport ( GLint ( self . frame. origin. x * scale) , GLint ( self . frame. origin. y * scale) , GLsizei ( self . frame. size. width * scale) , GLsizei ( self . frame. size. height * scale) )
let vertFile : String = Bundle. main. path ( forResource: "shader" , ofType: "vsh" ) ? ? ""
let fragFile : String = Bundle. main. path ( forResource: "shaderf" , ofType: "fsh" ) ? ? ""
let ( sucess, program) = self . loadShaders ( vert: vertFile, frag: fragFile)
if ! sucess! {
return
}
self . myProgram = program
private func loadShaders ( vert: String, frag: String) -> ( Bool? , GLuint? ) {
let program : GLuint = glCreateProgram ( )
guard let verShader : GLuint = self . compileShader ( type: GLenum ( GL_VERTEX_SHADER) , filePath: vert) else {
return ( false, program)
}
glAttachShader ( program, verShader)
glDeleteShader ( verShader)
guard let fragShader : GLuint = self . compileShader ( type: GLenum ( GL_FRAGMENT_SHADER) , filePath: frag) else {
return ( false, program)
}
glAttachShader ( program, fragShader)
glDeleteShader ( fragShader)
glLinkProgram ( program)
var status : GLint = 0
glGetProgramiv ( program, GLenum ( GL_LINK_STATUS) , & status)
if status == GLenum ( GL_FALSE) {
print ( "Link Error" )
let message = UnsafeMutablePointer< GLchar> . allocate ( capacity: 512 )
glGetProgramInfoLog ( program, GLsizei ( MemoryLayout< GLchar> . size * 512 ) , nil, message)
let string = String. init ( utf8String: message)
print ( "program link error \(string!)" )
return ( false, program)
} else {
print ( "link sucess!" )
glUseProgram ( program)
return ( true, program)
}
}
private func compileShader ( type: GLenum, filePath: String) -> GLuint? {
let verShader: GLuint = glCreateShader ( type)
guard let shaderString = try? String. init ( contentsOfFile: filePath, encoding: String. Encoding. utf8) else {
return nil
}
shaderString. withCString { ( pointer) in
var pon: UnsafePointer< GLchar> ? = pointer
glShaderSource ( verShader, 1 , & pon, nil)
}
glCompileShader ( verShader)
return verShader
}
let vertexs : [ GLfloat] = [
0.5 , - 0.5 , - 1.0 , 1.0 , 0.0 ,
- 0.5 , 0.5 , - 1.0 , 0.0 , 1.0 ,
- 0.5 , - 0.5 , - 1.0 , 0.0 , 0.0 ,
0.5 , 0.5 , - 1.0 , 1.0 , 1.0 ,
- 0.5 , 0.5 , - 1.0 , 0.0 , 1.0 ,
0.5 , - 0.5 , - 1.0 , 1.0 , 0.0 ,
]
var verbuffer = GLuint ( )
glGenBuffers ( 1 , & verbuffer)
glBindBuffer ( GLenum ( GL_ARRAY_BUFFER) , verbuffer)
glBufferData ( GLenum ( GL_ARRAY_BUFFER) , MemoryLayout< GLfloat> . size * 30 , vertexs, GLenum ( GL_DYNAMIC_DRAW) )
let position = glGetAttribLocation ( self . myProgram, "position" )
glEnableVertexAttribArray ( GLuint ( position) )
glVertexAttribPointer ( GLuint ( position) , 3 , GLenum ( GL_FLOAT) , GLboolean ( GL_FALSE) , GLsizei ( MemoryLayout< GLfloat> . size * 5 ) , UnsafeRawPointer ( bitPattern: 0 ) )
let texture = glGetAttribLocation ( myProgram, "textCoordinate" )
glEnableVertexAttribArray ( GLuint ( texture) )
glVertexAttribPointer ( GLuint ( texture) , 2 , GLenum ( GL_FLOAT) , GLboolean ( GL_FALSE) , GLsizei ( MemoryLayout< GLfloat> . size * 5 ) , UnsafeRawPointer ( bitPattern: MemoryLayout< GLfloat> . size * 3 ) )
setUpTextureImage ( imageName: "yiyi" )
glUniform1i ( glGetUniformLocation ( self . myProgram, "colorMap" ) , 0 )
glDrawArrays ( GLenum ( GL_TRIANGLES) , 0 , 6 )
self . myContext. presentRenderbuffer ( Int ( GL_RENDERBUFFER) )
private func setUpTextureImage ( imageName: String) {
guard let image = UIImage. init ( named: imageName) ? . cgImage else {
return
}
let width = image. width
let height = image. height
let spriteData: UnsafeMutablePointer = UnsafeMutablePointer< GLubyte> . allocate ( capacity: MemoryLayout< GLubyte> . size * width * height * 4 )
UIGraphicsBeginImageContext ( CGSize. init ( width: width, height: height) )
let spriteContext = CGContext ( data: spriteData, width: width, height: height, bitsPerComponent: 8 , bytesPerRow: image. width * 4 , space: image. colorSpace! , bitmapInfo: image. bitmapInfo. rawValue)
spriteContext? . draw ( image, in : CGRect ( x: 0 , y: 0 , width: width, height: height) )
UIGraphicsEndImageContext ( )
glBindTexture ( GLenum ( GL_TEXTURE_2D) , 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)
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)
glTexImage2D ( GLenum ( GL_TEXTURE_2D) , 0 , GL_RGBA, GLsizei ( image. width) , GLsizei ( image. height) , 0 , GLenum ( GL_RGBA) , GLenum ( GL_UNSIGNED_BYTE) , spriteData)
free ( spriteData)
}
deinit {
glDeleteBuffers ( 1 , & myColorRenderBuffer)
glDeleteBuffers ( 1 , & myColorFrameBuffer)
}