前言:編程
上篇案例:利用GLKit實現旋轉立方體中,使用了蘋果提供的GLKit框架,它提供的函數和類,包括提供加載紋理、數學計算、經常使用着色器、視圖、視圖控制器等,大大減小了工做量。可是GLKit也存在必定缺陷,着色器代碼沒法修改,着色器提供的屬性變量有上限等。xcode
OpenGL ES命令須要
渲染上下文
、繪製表面
才能完成圖形圖像的繪製,可是,OpenGL ES API並無提供如何建立渲染上下文,或者上下文如何鏈接到原生窗口系統的方法。 EGL是Khronos渲染API和原生窗口系統之間的接口.可是iOS並不支持EGL,咱們在iOS須要使用蘋果封裝的EAGL。緩存
狀態機
圖形硬件的趨勢是在那些變得極其複雜的領域,用可編程能力來代替固定功能,這樣的兩個領域就是頂點處理和片元處理。使用GLSL,頂點處理和片元處理的固定功能階段由於有了可編程階段而獲得補充。markdown
GLSL源自於C語言,一樣包含了豐富的類型,包括向量和矩陣app
經常使用vec2,vec3,vec4浮點向量框架
類型 | 描述 |
---|---|
vec2,vec3,vec4 | 2份量,3份量,4份量浮點向量 |
ivec2,ivec3,ivec4 | 2份量、3份量、4份量整型向量 |
uvec2,uvec3,uvec4 | 2份量、3份量、4份量⽆符號整型向量 |
bvec2,bvec3,bvec4 | 2份量、3份量、4份量bool型向量 |
經常使用mt3,mt4函數
類型 | 描述 |
---|---|
mat2,mat2x2 | 兩行兩列 |
mat3,mat3x3 | 三行三列 |
mat4,mat4x4 | 四行四列 |
mat2x3 | 三行兩列 |
mat2x4 | 四行兩列 |
mat3x2 | 三行兩列 |
mat3x4 | 三行四列 |
mat4x2 | 兩行四列 |
mat4x3 | 三行四列 |
修飾符表示,是經過何種方式輸入數據。post
從app代碼傳遞到頂點/片元着色器(vertex,fragment)中所用到的變量性能
glUniform..
進行傳遞,通常會經過uniform傳遞視圖矩陣、投影矩陣,投影視圖矩陣等只能從客戶端傳遞到頂點着色器,而且只能夠在頂點着色器中進行使用的數據ui
glVertex..
進行傳遞,例如以前的glVertexAttribPointer
當須要將頂點着色器的數據傳遞到片元着色器時,兩個着色器中如出一轍的數據變量就須要它來修飾
在iOS中,經過新建一個空白的類來編寫着色器源碼,一般用.vsh
和.fsh
後綴分別表明頂點着色器和片元着色器,這樣的後綴只是開發者用來區分,而Xcode實際並無這樣後綴的文件。
// 經過attribute傳遞的4份量浮點數據,表示頂點數據
attribute vec4 position;
// 經過attribute傳遞的2份量浮點數據,表示紋理座標
/*
紋理座標數據,是經過代碼傳遞給vertex後,由vertex橋接給fragment
*/
attribute vec2 textCoordinate;
// 經過varying傳遞低精度的2份量浮點數據,表示紋理座標
varying lowp vec2 varyTextCoord;
void main()
{
// 數據橋接
varyTextCoord = textCoordinate;
// vertex計算以後的頂點數據須要賦值給GLSL的內建變量`gl_Position`
gl_Position = position;
}
複製代碼
//定義精度,不然可能會報錯
precsion highp float;
//紋理座標 必須與頂點着色器中如出一轍,經過這個參數獲取傳遞過來的值
varying lowp vec2 varyTextCoord;
//紋理 sampler採樣器,
uniform sampler2D colorMap;
void main(){
//拿到紋理對應座標下的紋素。紋素是紋理對應像素點的顏色值
lowp vec4 temp = texture2D(colorMap, varyTextCoord);
//紋理結果賦值給內建變量:gl_FragColor
gl_FragColor = temp;
}
複製代碼
textrue2D(紋理,紋理座標)
計算// 要建立的類型type:GL_VERTEX_SHADER / GL_FRAGMENT_SHADER
GLuint glCreateShader(GLenum type)
複製代碼
/*
1.shader:要編譯的着色器對象
2.count:源碼字符串數量
3.string:源碼內容
4.length:通常NULL,意味字符串是NULL終止的
*/
glShaderSource(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length)
複製代碼
在xcode建立空文件,自定義編寫着色器代碼,本質是一個字符串。
void glCompileShader(GLuint shader);
複製代碼
GLUint glCreateProgram()
複製代碼
void glAttachShader(GLuint program,GLuint shader);
複製代碼
glLinkProgram(GLuint program)
複製代碼
glUseProgram(GLuint program)
複製代碼
以上API,基本表現出了,獲取連接後着色器對象的通常過程:
大體能夠分爲六個步驟
CAEAGLLayer
,繼承CALayer以上步驟爲渲染作的準備工做,重點在於渲染的步驟,其中也能夠繼續分四步
+(Class)layerClass
{
return [CAEAGLLayer class];
}
//1.設置圖層
- (void)setupLayer{
// 1 建立特殊圖層
self.myEagLayer = (CAEAGLLayer *)self.layer;
// 2.設置scale ,由屏幕的scale來決定
[self setContentScaleFactor: [[UIScreen mainScreen]scale]];
//3.設置描述屬性,這裏設置不維持渲染內容以及顏色格式爲RGBA8
/*
kEAGLDrawablePropertyRetainedBacking 表示繪圖表面顯示後,是否保留其內容。
kEAGLDrawablePropertyColorFormat 可繪製表面的內部顏色緩存區格式,這個key對應的值是一個NSString指定特定顏色緩存區對象。默認是kEAGLColorFormatRGBA8;
kEAGLColorFormatRGBA8:32位RGBA的顏色,4*8=32位
kEAGLColorFormatRGB565:16位RGB的顏色,
kEAGLColorFormatSRGBA8:sRGB表明了標準的紅、綠、藍,即CRT顯示器、LCD顯示器、投影機、打印機以及其餘設備中色彩再現所使用的三個基本色素。sRGB的色彩空間基於獨立的色彩座標,可使色彩在不一樣的設備使用傳輸中對應於同一個色彩座標體系,而不受這些設備各自具備的不一樣色彩座標的影響。
*/
self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@YES,kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
}
複製代碼
其中作layer強轉時,須要重寫類方法layerClass
// 2.設置圖形上下文
- (void)setupContext{
// 初始化2.0
EAGLContext *context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (context == nil) {
NSLog(@"建立上下文失敗");
return;
}
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"設置當前上下文失敗");
return;
}
self.myContext = context;
}
複製代碼
// 3.清空緩衝區
- (void)deleteRenderAndFrameBuffer{
// 幀緩衝區 是 渲染緩衝區的管理者 FBO:frame buffer object
// 先清空渲染緩衝區 , 而後幀緩衝區,未免有遺留髒數據,先作清空,再使用
glDeleteRenderbuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
glDeleteFramebuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
}
複製代碼
備註:如下會對幀緩衝區,渲染緩衝區作擴展
由於幀緩衝區管理着渲染緩衝區,因此要先建立設置渲染緩衝區,而後在建立設置幀緩衝區時,創建二者間的管理關係
gen 申請分配緩衝區
->bind 綁定緩衝區類型GL_RENDERBUFFER
->renderbufferStorage 將緩衝區綁定到上下文
// 4.開闢渲染緩衝區
- (void)setupRenderBuffer{
//1.定義bufferid
GLuint buffer;
// 申請緩衝區,獲得bufferid存在buffer
glGenRenderbuffers(1, &buffer);
self.myColorRenderBuffer = buffer;
// 綁定緩衝區
glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
// 緩衝區綁定到上下文
[self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}
複製代碼
設置幀緩衝區時,須要和渲染緩衝區進行綁定
// 5.幀緩衝區
- (void)setupFrameBuffer{
GLuint buffer;
glGenFramebuffers(1, &buffer);
self.myColorFrameBuffer = buffer;
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
// !!! 渲染緩衝區和 幀緩衝區 綁定一塊兒
/*
glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
target:GL_FRAMEBUFFER
attachment:渲染緩衝區附着到幀緩衝區的哪裏,GL_COLOR_ATTACHMENT0
renderbuffertarget:
*/
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorFrameBuffer);
}
複製代碼
到此爲止,準備工做告一段落,正式渲染工做中,也須要對開發者本身利用GLSL語言編寫的着色器源碼進行處理、以及頂點紋理數據的處理等.
glClearColor(0.3, 0.45, 0.5, 1);
glClear(GL_COLOR_BUFFER_BIT);
// 視口
CGFloat scale = [[UIScreen mainScreen]scale];
glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);
複製代碼
利用上邊的頂點着色器源碼和片元着色器源碼,在編寫GLSL着色器源碼時除了注意不一樣的修飾符表明的傳輸方式,也要注意不要在源碼中留下注釋,以避免出現問題定位不許,畢竟它們只是個字符串
如上記錄的:着色器與程序的建立、編譯、連接API
,咱們須要對編寫的着色器源碼進行一樣的處理,最終獲得的程序來使用.
其中用到的API都在上邊作過了詳細的介紹
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{
// 源碼讀取路徑,轉換爲NSString
NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
// EAGL使用的是C語言函數,將OC的content字符串 轉換爲 C語言的字符串
const GLchar *source = (GLchar *)[content UTF8String];
// 1.建立對應類型的着色器
*shader = glCreateShader(type);
// 2.將着色器的源碼 附着在着色器對象
glShaderSource(*shader, 1, &source, NULL);
// 3.編譯
glCompileShader(*shader);
}
複製代碼
這個函數方法,經過咱們傳入的shader對象、着色器type,源碼位置,最終獲得一個附着有着色源碼的對象
- (GLuint)loaderShaders:(NSString *)vert withFrag:(NSString *)frag{
// 1.定義頂點/片元着色器
GLuint verShader,fragShader;
// 2.定義一個program程序
GLuint program = glCreateProgram();
// 3.編譯着色器
[self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
[self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
// 4.編譯好的着色器對象 附着到程序
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
// 5.已經附着的shader對象能夠刪掉
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
複製代碼
GLint linkStatus;
glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkStatus);
複製代碼
linkStatus
成功的話,咱們就能夠調用glUseProgram
來使用這個着色器程序
一樣的,咱們須要將頂點、紋理數據從內容中copy到緩衝區裏
// 準備頂點/紋理數據
//前3個是頂點座標,後2個是紋理座標
GLfloat attrArr[] =
{
0.5f, -0.5f, -1.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -1.0f, 0.0f, 1.0f,
0.5f, -0.5f, -1.0f, 1.0f, 0.0f,
};
// 將內存中的數據 copy 到緩衝區
// 頂點數據處理
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
// 打開頂點數據通道
// 從program得到頂點數據通道id position
GLuint position = glGetAttribLocation(self.myProgram, "position");
glEnableVertexAttribArray(position);
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
// 打開紋理數據通道,傳輸紋理座標數據
GLuint textCoordinate = glGetAttribLocation(self.myProgram, "textCoordinate");
glEnableVertexAttribArray(textCoordinate);
glVertexAttribPointer(textCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL+3);
複製代碼
其中有一點和GLKit有所不一樣,在GLKit中,蘋果已經寫好了着色器源碼,當咱們須要打開Attrib屬性通道時,能夠直接調用glEnableVertexAttribArray(GLKVertexAttribPosition);
其中的GLKVertexAttribPosition
,其實就是上面代碼中GLuint position = glGetAttribLocation(self.myProgram, "position");
的結果
接下來還有一處與GLKit不一樣的地方:紋理加載
.在GLKit中咱們是使用GLKTextureLoader
加載的圖片紋理,那咱們如今自定義的話須要另外本身寫紋理加載的部分,這部分其實和OpenGL沒有關係,它使用的是iOS中Core Graphics
//從圖片中加載紋理
- (GLuint)setupTexture:(NSString *)fileName {
//一、將 UIImage 轉換爲 CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
//判斷圖片是否獲取成功
if (!spriteImage) {
NSLog(@"Failed to load image %@", fileName);
exit(1);
}
//二、讀取圖片的大小,寬和高
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
//3.獲取圖片字節數 寬*高*4(RGBA)
GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
//4.建立上下文
/*
參數1:data,指向要渲染的繪製圖像的內存地址
參數2:width,bitmap的寬度,單位爲像素
參數3:height,bitmap的高度,單位爲像素
參數4:bitPerComponent,內存中像素的每一個組件的位數,好比32位RGBA,就設置爲8
參數5:bytesPerRow,bitmap的沒一行的內存所佔的比特數
參數6:colorSpace,bitmap上使用的顏色空間 kCGImageAlphaPremultipliedLast:RGBA
*/
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
//五、在CGContextRef上--> 將圖片繪製出來
/*
CGContextDrawImage 使用的是Core Graphics框架,座標系與UIKit 不同。UIKit框架的原點在屏幕的左上角,Core Graphics框架的原點在屏幕的左下角。
CGContextDrawImage
參數1:繪圖上下文
參數2:rect座標
參數3:繪製的圖片
*/
CGRect rect = CGRectMake(0, 0, width, height);
//6.使用默認方式繪製
CGContextDrawImage(spriteContext, rect, spriteImage);
//七、畫圖完畢就釋放上下文
CGContextRelease(spriteContext);
//八、綁定紋理到默認的紋理ID(
glBindTexture(GL_TEXTURE_2D, 0);
//9.設置紋理屬性
/*
參數1:紋理維度
參數2:線性過濾、爲s,t座標設置模式
參數3:wrapMode,環繞模式
*/
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
float fw = width, fh = height;
//10.載入紋理2D數據
/*
參數1:紋理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
參數2:加載的層次,通常設置爲0
參數3:紋理的顏色值GL_RGBA
參數4:寬
參數5:高
參數6:border,邊界寬度
參數7:format
參數8:type
參數9:紋理數據
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
//11.釋放spriteData
free(spriteData);
return 0;
}
複製代碼
紋理數據加載成功後,咱們還須要將紋理數據,經過uniform傳遞給片元着色器,uniform修飾的數據,不須要打開通道開關
glUniform1i(glGetUniformLocation(self.myProgram,"colorMap"), 0);
複製代碼
//繪圖
glDrawArrays(GL_TRIANGLES, 0, 6);
//從渲染緩存區顯示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
複製代碼
ps:爲何圖片是反的?