前面的文章都是繪制實實在在的圖形的娶靡,在OpenGL中牧牢,我們還可以使用紋理圖片來渲染圖形,使用圖片可以讓描繪出來的物體更加真實也可以讓我們的開發(fā)更加簡單姿锭。
資料:http://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/06%20Textures/ 塔鳍。
接下來我們直接開始代碼書寫:
1.開始之前,我們把工具類GLESUtils優(yōu)化一下呻此,使之能直接返回我們需要的program轮纫。用了這么久,希望你自己也能封裝焚鲜。
修改.h
#import <Foundation/Foundation.h>
#include <OpenGLES/ES3/gl.h>
@interface GLESUtils : NSObject
// Create a shader object, load the shader source string, and compile the shader.
//
+(GLuint)loadShader:(GLenum)type withString:(NSString *)shaderString;
+(GLuint)loadShader:(GLenum)type withFilepath:(NSString *)shaderFilepath;
//直接返回program
+(GLuint)loadProgram:(NSString *)vertexShaderFilepath withFragmentShaderFilepath:(NSString *)fragmentShaderFilepath;
@end
修改.m
#import "GLESUtils.h"
@implementation GLESUtils
+(GLuint)loadShader:(GLenum)type withFilepath:(NSString *)shaderFilepath
{
NSError* error;
NSString* shaderString = [NSString stringWithContentsOfFile:shaderFilepath
encoding:NSUTF8StringEncoding
error:&error];
if (!shaderString) {
NSLog(@"Error: loading shader file: %@ %@", shaderFilepath, error.localizedDescription);
return 0;
}
return [self loadShader:type withString:shaderString];
}
+(GLuint)loadShader:(GLenum)type withString:(NSString *)shaderString
{
// Create the shader object
GLuint shader = glCreateShader(type);
if (shader == 0) {
NSLog(@"Error: failed to create shader.");
return 0;
}
// Load the shader source
const char * shaderStringUTF8 = [shaderString UTF8String];
glShaderSource(shader, 1, &shaderStringUTF8, NULL);
// Compile the shader
glCompileShader(shader);
// Check the compile status
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLen = 0;
glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );
if (infoLen > 1) {
char * infoLog = malloc(sizeof(char) * infoLen);
glGetShaderInfoLog (shader, infoLen, NULL, infoLog);
NSLog(@"Error compiling shader:\n%s\n", infoLog );
free(infoLog);
}
glDeleteShader(shader);
return 0;
}
return shader;
}
+(GLuint)loadProgram:(NSString *)vertexShaderFilepath withFragmentShaderFilepath:(NSString *)fragmentShaderFilepath
{
// Load the vertex/fragment shaders
GLuint vertexShader = [self loadShader:GL_VERTEX_SHADER
withFilepath:vertexShaderFilepath];
if (vertexShader == 0)
return 0;
GLuint fragmentShader = [self loadShader:GL_FRAGMENT_SHADER
withFilepath:fragmentShaderFilepath];
if (fragmentShader == 0) {
glDeleteShader(vertexShader);
return 0;
}
// Create the program object
GLuint programHandle = glCreateProgram();
if (programHandle == 0)
return 0;
glAttachShader(programHandle, vertexShader);
glAttachShader(programHandle, fragmentShader);
// Link the program
glLinkProgram(programHandle);
// Check the link status
Glint linked;
glGetProgramiv(programHandle, GL_LINK_STATUS, &linked);
if (!linked) {
GLint infoLen = 0;
glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1){
char * infoLog = malloc(sizeof(char) * infoLen);
glGetProgramInfoLog(programHandle, infoLen, NULL, infoLog);
NSLog(@"Error linking program:\n%s\n", infoLog);
free(infoLog);
}
glDeleteProgram(programHandle );
return 0;
}
// Free up no longer needed shader resources
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return programHandle;
}
2.我們的項目需要返璞歸真掌唾,重新寫一個
1).創(chuàng)建項目
2).新建MyGLView(實現(xiàn)layerClass放前、initWithFrame、setupLayer郑兴、setupContext犀斋、setupRenderBuffer、setupFrameBuffer情连、setupProgram叽粹、render這些函數(shù))
做完,.m應該是這樣的:
#import "MyGLView.h"
#import "GLESUtils.h"
#import <OpenGLES/ES3/gl.h>
@interface MyGLView ()
{
CAEAGLLayer *_eaglLayer; //OpenGL內容只會在此類layer上描繪
EAGLContext *_context; //OpenGL渲染上下文
GLuint _renderBuffer; //
GLuint _frameBuffer; //
GLuint _programHandle;
GLuint _positionSlot; //頂點槽位
GLuint _colorSlot; //顏色槽位
}
@end
@implementation MyGLView
+(Class)layerClass{
//OpenGL內容只會在此類layer上描繪
return [CAEAGLLayer class];
}
-(instancetype)initWithFrame:(CGRect)frame{
if (self==[super initWithFrame:frame]) {
[self setupLayer];
[self setupContext];
[self setupRenderBuffer];
[self setupFrameBuffer];
[self setupProgram]; //配置program
[self render];
}
return self;
}
- (void)setupLayer
{
_eaglLayer = (CAEAGLLayer*) self.layer;
// CALayer 默認是透明的却舀,必須將它設為不透明才能讓其可見,性能最好
_eaglLayer.opaque = YES;
// 設置描繪屬性虫几,在這里設置不維持渲染內容以及顏色格式為 RGBA8
_eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}
- (void)setupContext {
// 指定 OpenGLES 渲染API的版本,在這里我們使用OpenGLES 3.0挽拔,由于3.0兼容2.0并且功能更強辆脸,為何不用更好的呢
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES3;
_context = [[EAGLContext alloc] initWithAPI:api];
if (!_context) {
NSLog(@"Failed to initialize OpenGLES 3.0 context");
}
// 設置為當前上下文
[EAGLContext setCurrentContext:_context];
}
-(void)setupRenderBuffer{
glGenRenderbuffers(1, &_renderBuffer); //生成和綁定render buffer的API函數(shù)
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
//為其分配空間
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}
-(void)setupFrameBuffer{
glGenFramebuffers(1, &_frameBuffer); //生成和綁定frame buffer的API函數(shù)
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
//將renderbuffer跟framebuffer進行綁定
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
}
- (void)setupProgram
{
// Load shaders
//
NSString * vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader"
ofType:@"gals"];
NSString * fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader"
ofType:@"gals"];
_programHandle = [GLESUtils loadProgram:vertexShaderPath withFragmentShaderFilepath:fragmentShaderPath];
glUseProgram(_programHandle);
// Get attribute slot from program
//
_positionSlot = glGetAttribLocation(_programHandle, "vPosition");
}
-(void)render
{
//設置清屏顏色,默認是黑色,如果你的運行結果是黑色螃诅,問題就可能在這兒
glClearColor(0.3, 0.5, 0.8, 1.0);
/*
glClear指定清除的buffer
共可設置三個選項GL_COLOR_BUFFER_BIT啡氢,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT
也可組合如:glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
這里我們只用了color buffer,所以只需清除GL_COLOR_BUFFER_BIT
*/
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST); //添加
// Setup viewport
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
[_context presentRenderbuffer:_renderBuffer];
}
@end
3).創(chuàng)建頂點和片元著色器腳本文件术裸。
由于要使用紋理,我們頂點著色器腳本VertexShader.glsl需改寫為:
attribute vec4 vPosition;
attribute vec2 TexCoordIn;
varying vec2 TexCoordOut;
void main(void)
{
gl_Position = vPosition;
TexCoordOut = TexCoordIn;
}
其中TexCoordIn為傳入進來的紋理坐標倘是,TexCoordOut為要傳入到片元著色器中的紋理坐標。也就是把傳入進來的紋理坐標TexCoordIn傳入到片元著色器以供處理袭艺。
FragmentShader.glsl改為:
uniform sampler2D ourTexture;
varying lowp vec2 TexCoordOut;
void main()
{
gl_FragColor = texture2D(ourTexture, TexCoordOut);
}
以上varying的使用就是保證TexCoordOut的唯一性搀崭,有前面的基礎應該很容易理解
uniform sampler2D ourTexture; //這句代碼的意義是鏈接的采樣紋理常量
gl_FragColor = texture2D(ourTexture, TexCoordOut); //這句代碼表示以紋理坐標TexCoordOut來采樣ourTexture當做像素的顏色
4).使用紋理
根據(jù)我們glsl腳本,我們在項目中需要新定義兩個新變量:
GLuint _texCoordSlot; //紋理坐標槽位
GLuint _ourTextureSlot; //紋理對象槽位
在MyGLView.m里
@interface MyGLView ()
{
CAEAGLLayer *_eaglLayer; //OpenGL內容只會在此類layer上描繪
EAGLContext *_context; //OpenGL渲染上下文
GLuint _renderBuffer; //
GLuint _frameBuffer; //
GLuint _programHandle;
GLuint _positionSlot; //頂點槽位
GLuint _texCoordSlot; //紋理坐標槽位
GLuint _ourTextureSlot; //紋理對象槽位
}
然后我們在設置著色器程序- (void)setupProgram()方法里獲取槽位值:
- (void)setupProgram
{
// Load shaders
//
NSString * vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader"
ofType:@"gals"];
NSString * fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader"
ofType:@"gals"];
_programHandle = [GLESUtils loadProgram:vertexShaderPath withFragmentShaderFilepath:fragmentShaderPath];
glUseProgram(_programHandle);
// Get attribute slot from program
//
_positionSlot = glGetAttribLocation(_programHandle, "vPosition");
glEnableVertexAttribArray(_positionSlot);
//獲取紋理相關槽位,請注意glGetAttribLocation和glGetUniformLocation的區(qū)別
_texCoordSlot = glGetAttribLocation(_programHandle, "TexCoordIn");
glEnableVertexAttribArray(_texCoordSlot);
_ourTextureSlot = glGetUniformLocation(_programHandle, "ourTexture");
}
5).紋理工具類TextureManager
到目前為止猾编,我們嗨沒有真正使用到紋理瘤睹,要真正使用紋理,我們需要紋理圖片和把紋理圖片轉成紋理對象的方法答倡。
關于紋理圖片的話轰传,不用說啦,隨便找一張:
關于把紋理圖片轉成紋理對象的方法瘪撇,我們封裝成一個專門的類TextureManager來干這種事:
新建TextureManager類繼承NSObject获茬,在.h里
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface TextureManager : NSObject
/*
* 通過UIImage的方式獲取紋理對象
*/
+ (GLuint)getTextureImage:(UIImage *)image;
@end
在.m中實現(xiàn)對應功能,代碼中給出了相應解釋:
#import "TextureManager.h"
#import <OpenGLES/ES3/gl.h>
@implementation TextureManager
/*
* 通過UIImage的方式獲取紋理對象
*/
+ (GLuint)getTextureImage:(UIImage *)image {
// 獲取UIImage并轉換成CGImage
CGImageRef spriteImage = image.CGImage;
if(!spriteImage) {
return 0;
}
// 獲取圖片的大小
GLsizei width = (GLsizei)CGImageGetWidth(spriteImage);
GLsizei height = (GLsizei)CGImageGetHeight(spriteImage);
// 分配內存,并初始化該內存空間為零, 因為一個像素有4個通道(RGBA)所以乘4
GLubyte * spriteData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
/*
* 創(chuàng)建位圖上下文
*/
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,
CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
// 在上下文中繪制圖片
CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
// 釋放上下文
CGContextRelease(spriteContext);
// 創(chuàng)建紋理對象并且綁定, 紋理對象用無符號整數(shù)表示, 這個紋理對象相當于我們在C語言文件操作里面的句柄
GLuint texName;
glGenTextures(1, &texName);
glBindTexture(GL_TEXTURE_2D, texName);
//設置紋理循環(huán)模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//設置紋理過濾模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加載圖像數(shù)據(jù), 并上傳紋理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
// 解綁紋理對象(在本文這里解不解綁都一樣设江,因為后面還是要綁定)
glBindTexture(GL_TEXTURE_2D, 0);
// 釋放分配的內存空間
free(spriteData);
return texName;
}
@end
這里面值得注意的是紋理的創(chuàng)建過程和紋理的循環(huán)和過濾模式,對紋理循環(huán)和過濾模式不清晰請繼續(xù)回到 http://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/06%20Textures/
這里也稍稍提一下多級漸遠紋理,上面鏈接中對多級漸遠紋理的解釋很詳細攘轩,我們在iOS端使用的時候最簡單的只需要調用
glGenerateMipmap(GL_TEXTURE_2D);
然后設置紋理縮小時的過濾模式為多級漸遠紋理過濾就行:
舉例:
//設置紋理過濾模式
glGenerateMipmap(GL_TEXTURE_2D); //自動生成多級漸遠紋理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); //多級漸遠紋理過濾模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加載圖像數(shù)據(jù), 并上傳紋理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
注意:一個常見的錯誤是叉存,將放大過濾的選項設置為多級漸遠紋理過濾選項之一。這樣沒有任何效果度帮,因為多級漸遠紋理主要是使用在紋理被縮小的情況下的:紋理放大不會使用多級漸遠紋理歼捏。
6).渲染紋理
圖片咱有了稿存,轉紋理對象方法也有了,接下來咱們開始渲染紋理瞳秽。
首先在.m里新增一個紋理對象變量
GLuint _textureID; //紋理對象
然后我們在- (void)setupProgram()方法最后獲取紋理對象
//獲取紋理對象
_textureID = [TextureManager getTextureImage:[UIImage imageNamed:@"timg.jpg"]];
最后就是render了瓣履,render前我們先構造紋理的坐標數(shù)據(jù):
//4個頂點(分別表示xyz軸)
GLfloat vertices[] = {
// x y z
-0.5, -0.5, 0, //左下
0.5, -0.5, 0, //右下
-0.5, 0.5, 0, //左上
0.5, 0.5, 0, //右上
};
//4個頂點對應紋理坐標
GLfloat textureCoord[] = {
0, 0,
1, 0,
0, 1,
1, 1,
};
整個render方法如下:
-(void)render
{
//設置清屏顏色,默認是黑色,如果你的運行結果是黑色练俐,問題就可能在這兒
glClearColor(0.3, 0.5, 0.8, 1.0);
/*
glClear指定清除的buffer
共可設置三個選項GL_COLOR_BUFFER_BIT袖迎,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT
也可組合如:glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
這里我們只用了color buffer,所以只需清除GL_COLOR_BUFFER_BIT
*/
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST); //添加
// Setup viewport
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
//4個頂點(分別表示xyz軸)
GLfloat vertices[] = {
// x y z
-0.5, -0.5, 0, //左下
0.5, -0.5, 0, //右下
-0.5, 0.5, 0, //左上
0.5, 0.5, 0, //右上
};
//4個頂點對應紋理坐標
GLfloat textureCoord[] = {
0, 0,
1, 0,
0, 1,
1, 1,
};
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(_texCoordSlot, 2, GL_FLOAT, GL_FALSE, 0, textureCoord);
//使用紋理單元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _textureID);
glUniform1i(_ourTextureSlot, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[_context presentRenderbuffer:_renderBuffer];
}
所有做完我們運行查看一下結果:
唉腺晾,這個圖片怎么反了把嘧丁?
原因就是紋理坐標的原點在左下角悯蝉,x,y正方向為往右往上
而我們openGL坐標系原點在屏幕中心归形,x正方向與紋理x正方向相同,但y正方向與紋理相反鼻由,這樣的話暇榴,解決辦法可以修改我們頂點數(shù)據(jù)對應的紋理坐標,也可以在VertexShader.glsl文件里把:TexCoordOut = TexCoordIn;改為TexCoordOut = vec2(TexCoordIn.x, 1.0-TexCoordIn.y);
attribute vec4 vPosition;
attribute vec2 TexCoordIn;
varying vec2 TexCoordOut;
void main(void)
{
gl_Position = vPosition;
// TexCoordOut = TexCoordIn;
TexCoordOut = vec2(TexCoordIn.x, 1.0-TexCoordIn.y);
}
這個時候圖片方向就對了蕉世,我們運行一下:
所有教程代碼在此 : https://github.com/qingmomo/iOS-OpenGLES-