參考網頁
http://blog.csdn.net/kesalin/article/details/8223649
http://blog.csdn.net/kesalin/article/details/7168967
實作紀錄網頁
http://kirenenko-tw.blogspot.tw/2013/04/opengl_11.html
1. 首先使用 initWithCoder 初始化 Storyboard 上的元件(OpenGLView.m)
initWithCoder 是物件既有的一個初始化方法函式,正常來說,你不會在使用程式碼動態產生物件時去呼叫它產生新的物件,而是在使用
Storyboard 設計介面時,直接將 Storyboard 上的元件與類別中的 initWithCoder
方法做連結,使用類似靜態的方式,讓應用程式在一開始執行時就將介面上的元件直接定義成該類別。
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setupLayer]; // 初始設定 一個 CAEAGLLayer
[self setupContext]; // 初始設定一個 rendering context
[self setupProgram]; // 載入shader的執行程式碼 ,最重要的主程式
[self setupProjection]; //設定投射矩陣
[self resetTransform]; // 回歸初始值
}
return self;
}
OpenGL ES是Core Animation的客户,要使用OpenGL ES需要创建一个UIView,这个UIView由一个特殊的core animation layer支持,这个特殊的layer是一个CAEAGLLayer对象。CAEAGLLayer是OpenGLES和core animation联系的桥梁。当应用程序渲染完一帧后,CAEAGLLayer的内容被呈现并且和其他view的数据组合。
參考網頁 http://hi.baidu.com/wwssttt/item/e161f725e2ba9941469962d3
- (void)setupLayer
{
_eaglLayer = (CAEAGLLayer*) self.layer;
// CALayer 默认是透明的,必须将它设为不透明才能让其可见
_eaglLayer.opaque = YES;
// 设置描绘属性,在这里设置不维持渲染内容以及颜色格式为 RGBA8
_eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}
EAGLContext : 定义了rendering context。应用程序创建和初始化一个EAGLContext,设定他为当前的OpenGL命令的目标。OpenGL命令通常存放在一个 context所维护的队列里面,并在之后被执行。EAGLContext也提供了方法将图像呈现给Core Animation。
- (void)setupContext {
// 指定 OpenGL 渲染 API 的版本,在这里我们使用 OpenGL ES 2.0
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
_context = [[EAGLContext alloc] initWithAPI:api];
if (!_context) {
NSLog(@" >> Error: Failed to initialize OpenGLES 2.0 context");
exit(1);
}
// 设置为当前上下文
if (![EAGLContext setCurrentContext:_context]) {
_context = nil;
NSLog(@" >> Error: Failed to set current OpenGL context");
exit(1);
}
}
在前面提到可编程管线通过用 shader 语言编写脚本文件实现的,这些脚本文件相当于 C 源码,有源码就需要编译链接,因此需要对应的编译器与链接器,shader 对象与 program 对象就相当于编译器与链接器。shader 对象载入源码,然后编译成 object 形式(就像C源码编译成 .obj文件)。经过编译的 shader 就可以装配到 program 对象中,每个 program对象必须装配两个 shader 对象:一个顶点 shader,一个片元 shader,然后 program 对象被连接成“可执行文件”,这样就可以在 render 中是由该“可执行文件”了。
參考網頁 http://content.gpwiki.org/index.php/OpenGL:Tutorials:Loading_and_using_GLSL_shaders#Creating_the_Program
- (void)setupProgram
{
// Load shaders
//
NSString * vertexShaderPath;
NSString * fragmentShaderPath;
vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader"
ofType:@"glsl"];
fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader"
ofType:@"glsl"];
//從 加入 shader,此處的loadProgramg需自己處理
_programHandle = [GLESUtils loadProgram:vertexShaderPath
withFragmentShaderFilepath:fragmentShaderPath];
if (_programHandle == 0) {
NSLog(@" >> Error: Failed to setup program.");
return;
}
glUseProgram(_programHandle);
// Get the attribute position slot from program
// 通过调用 glGetAttribLocation 我们获取到 shader 中定义的变量 vPosition 在 program 的槽位,通过该槽位我们就可以对 vPosition 进行操作。
// Returns the location of an attribute variable
_positionSlot = glGetAttribLocation(_programHandle, "vPosition");
// Get the uniform model-view matrix slot from program
// Returns the location of a uniform variable
_modelViewSlot = glGetUniformLocation(_programHandle, "modelView");
// Get the uniform projection matrix slot from program
//
_projectionSlot = glGetUniformLocation(_programHandle, "projection");
}
其中會用到兩隻檔案
VertexShader.glsl 及 FragmentShader.glsl
VertexShader.glsl
uniform mat4 projection;
uniform mat4 modelView;
attribute vec4 vPosition;
void main(void)
{
gl_Position = projection * modelView * vPosition;
}
FragmentShader.glsl
precision mediump float;
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
此處要說明一下
Uniforms and Attributes
Uniforms 是一个program 中统一分配的,vertext 和fragment中同名的Uniform必须同类型。对应于不经常变化的变量。
Attributes 变化率高的变量。主要用来定义输入的每次点属性。
Uniforms and Attributes 在shader中通过location 和 name 来对应的。
通过GLint glGetUniformLocation(GLuint program,const char* name).根据一个Uniform的名称获取其location. 通过 glUniform***系列函数可以给一个location 设置一个Uniform的值。
參考網頁 http://blog.csdn.net/beelike/article/details/5774288
對於 loadProgram,可將其放在外部檔案,此例放在GLESUtils.m
+(GLuint)loadProgram:(NSString *)vertexShaderFilepath withFragmentShaderFilepath:(NSString *)fragmentShaderFilepath
{
// Load the vertex/fragment shaders, and compile,此處的 loadShader也需要自己處理
// shader type => GL_VERTEX_SHADER
GLuint vertexShader = [self loadShader:GL_VERTEX_SHADER
withFilepath:vertexShaderFilepath];
if (vertexShader == 0)
return 0;
// shader type => GL_FRAGMENT_SHADER
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;
}
+(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;
}
基本shader創建的步驟,但是省略了檢查錯誤的步驟,實作上卻是必要的。
1. glCreateShader创建着色器对象
2. glShaderSource为着色器加载源代码,讀入檔案
3. glCompileShader编译每个着色器
4. glCreateProgram创建程序对象
5. glAttachShader 把着色器对象连接到程序对象
6. glLinkProgram 链接程序对象,生成可执行程序
7. glUseProgram安装可执行程序替换OpengGL固定功能流水线处理模块
設定投影矩陣
此處可參考網頁 http://kirenenko-tw.blogspot.tw/2013/05/opengl_16.html,瞭解整個投影的需求。
-(void)setupProjection
{
// Generate a perspective matrix with a 60 degree FOV
//
float aspect = self.frame.size.width / self.frame.size.height; // 目前的畫面是投射近距離的畫面,要觀察遠距離的畫面。
ksMatrixLoadIdentity(&_projectionMatrix);
// 使用一個單元矩陣來產生一個投射的矩陣
ksPerspective(&_projectionMatrix, 60.0, aspect, 1.0f, 20.0f);
// Load projection matrix
glUniformMatrix4fv(_projectionSlot, 1, GL_FALSE, (GLfloat*)&_projectionMatrix.m[0][0]);
///glUniformMatrix4fv函数用我们得到的变换矩阵来更新着色器程序里的uniform变量矩阵modelview。第一个参数是要修改的uniform变量的索引位置,第二个参数是要修改的矩阵数据,这里是1个。第三个参数是第四个参数是按行主序还是列主序指定的,这里我们用GL_FALSE(0),表明是列主序指定的,矩陣不需要轉置。最后一个参数是我们前面设定的变换矩阵。
}
- (void)resetTransform
{
if (_displayLink != nil) {
[_displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
_displayLink = nil;
}
self.posX = 0.0;
self.posY = 0.0;
self.posZ = -5.5;
self.rotateX = 0.0;
self.rotateY = 0.0;
self.scaleZ = 1.0;
[self updateTransform];
}
// 重新計算所有modelView的對應矩陣
- (void)updateTransform
{
// Generate a model view matrix to rotate/translate/scale
//
ksMatrixLoadIdentity(&_modelViewMatrix);
// Translate away from the viewer
//
ksMatrixTranslate(&_modelViewMatrix, self.posX, self.posY, self.posZ);
// Rotate the triangle
//
ksMatrixRotate(&_modelViewMatrix, self.rotateX, 1.0, 0.0, 0.0); // rotate X
ksMatrixRotate(&_modelViewMatrix, self.rotateY, 0.0, 1.0, 0.0); // rotate Y
// Scale the triangle
ksMatrixScale(&_modelViewMatrix, 1.0, 1.0, self.scaleZ);
// Load the model-view matrix,將modelViewMatrix載入到_modelViewSlot中
glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);
}
2. 一旦有所需要,就開始繪圖,此處包含在render內
- (void)render
{
if (_context == nil)
return;
glClearColor(0, 1.0, 0, 1.0); // specify clear values for the color buffers
glClear(GL_COLOR_BUFFER_BIT); // clear buffers to preset values, The three masks are GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, and GL_STENCIL_BUFFER_BIT.
// Setup viewport
//
glViewport(0, 0, self.frame.size.width, self.frame.size.height); // glViewport 表示渲染 surface 将在屏幕上的哪个区域呈现出来
[self drawCube]; // 劃出方形磚塊
[_context presentRenderbuffer:GL_RENDERBUFFER]; // Displays a renderbuffer’s contents on screen.
}
3. 使用layoutSubviews來繪製畫面,這是一個類似重畫的機制,
可參考 http://kirenenko-tw.blogspot.tw/2013/04/layoutsubviews.html- (void)layoutSubviews
{
[EAGLContext setCurrentContext:_context]; // The graphics-context object to set as the current one. This must be an instance of a concrete subclass of NSGraphicsContext.
glUseProgram(_programHandle); // Installs a program object as part of current rendering state
[self destoryBuffers];
[self setupBuffers];
// 任何目標的轉移與縮放,基本上都只用到下面兩個函數呼叫
[self updateTransform];
[self render];
}
- (void)destoryBuffers
{
glDeleteRenderbuffers(1, &_colorRenderBuffer);
_colorRenderBuffer = 0;
glDeleteFramebuffers(1, &_frameBuffer);
_frameBuffer = 0;
}
- (void)setupBuffers {
glGenRenderbuffers(1, &_colorRenderBuffer); // Specifies an array in which the generated renderbuffer object names are stored.
// 设置为当前 renderbuffer
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
// bind a renderbuffer to a renderbuffer target
// 为 color renderbuffer 分配存储空间
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
// Binds a drawable object’s storage to an OpenGL ES renderbuffer object.
glGenFramebuffers(1, &_frameBuffer); //
// Specifies an array in which the generated framebuffer object names are stored.
// 设置为当前 framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
//bind a framebuffer to a framebuffer target
// 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, _colorRenderBuffer);
//attach a renderbuffer as a logical buffer to the currently bound framebuffer object
}
整個程序到此,可以繪出一個3D的Model。如果有任何Model上的變化,只需要用到下面兩行就可以了。
// 任何目標的轉移與縮放,基本上都只用到下面兩個函數呼叫
[self updateTransform];
[self render];