以前也写过openGL 简单的理论知识,可是真要看openGL 相关人写的代码的时候,发现自己还是太菜,因此,想写个完整的一系列博客来记录下学习的点滴.
创建第一个工程
工程主要代码
#import
#import
NS_ASSUME_NONNULL_BEGIN
@interface FirstViewController : GLKViewController
@end
NS_ASSUME_NONNULL_END
#import "FirstViewController.h"
#import "OpenGLUtilsHeader.h"
@interface FirstViewController ()
@property (nonatomic ,strong) EAGLContext * eagcontext;
@property (nonatomic ,assign) GLuint program;
@property (nonatomic ,strong) Shader * shader ;
@property (nonatomic ,assign) GLint vertexcolor;
@property (nonatomic ,strong) Vertex * vertex ;
@end
@implementation FirstViewController
-(void)createEagContext{
self.eagcontext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.eagcontext];
}
-(void)configure{
GLKView *view = (GLKView*)self.view;
view.context = self.eagcontext;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
}
-(void)createShader{
self.shader = [Shader new];
[self.shader compileLinkSuccessShaderName:@"Shader" completeBlock:^(GLuint program) {
glBindAttribLocation(program, 0, "position"); // 0代表枚举位置
_vertexcolor = glGetUniformLocation(program, "color");
}];
}
-(void)loadVertex{
self.vertex = [Vertex new];
[self.vertex allocVertexNum:3 andEachVertexNum:2];
GLfloat vertex[2];
vertex[0]=1;
vertex[1]=0;
[self.vertex setVertex:vertex index:0];
vertex[0]=0;
vertex[1]=1;
[self.vertex setVertex:vertex index:1];
vertex[0]=0;
vertex[1]=0;
[self.vertex setVertex:vertex index:2];
[self.vertex bindBufferWithUsage:GL_STATIC_DRAW];
[self.vertex enableVertexInVertexAttrib:GLKVertexAttribPosition numberOfCoordinates:2 attribOffset:0];
}
-(void)viewDidLoad{
[super viewDidLoad];
[self createEagContext];
[self configure];
[self createShader];
[self loadVertex];
}
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
static NSInteger count = 0;
// 清除颜色缓冲区
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
count ++;
if (count > 50 ) {
count = 0;
// 根据颜色索引值,设置颜色数据,就是刚才我们从着色器程序中获取的颜色索引值
glUniform4f(_vertexcolor, arc4random_uniform(255)/225.0, arc4random_uniform(255)/225.0, arc4random_uniform(255)/225.0, 1);
}
// 使用着色器程序
glUseProgram(self.shader.program);
// 绘制
[self.vertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:3];
}
@end
//Shader.fsh
varying lowp vec4 colorVarying;
void main()
{
gl_FragColor = colorVarying; // 设置片段着色器中的颜色
}
//Shader.vsh
attribute vec4 position; // 属性,如果绑定了这个属性,
uniform vec4 color; // 需要从程序中传入的值,一会我们会不断改变这个值
varying lowp vec4 colorVarying; // 这个值需要和片段着色器的声明相同
void main()
{
gl_Position = position; // 设置顶点位置
colorVarying = color; // 设置顶点颜色
}
//OpenGLUtilsHeader.h
#ifndef OpenGLUtilsHeader_h
#define OpenGLUtilsHeader_h
#import "Shader.h"
#import "Vertex.h"
#endif /* OpenGLUtilsHeader_h */
#import
#import
NS_ASSUME_NONNULL_BEGIN
@interface Shader : NSObject
@property (nonatomic ,readonly ) GLuint program;
-(BOOL)compileLinkSuccessShaderName:(NSString *)shader completeBlock:(void(^)(GLuint program))completeBlock;
@end
NS_ASSUME_NONNULL_END
#import "Shader.h"
@interface Shader ()
@property (nonatomic ,readwrite) GLuint program;
@end
@implementation Shader
- (instancetype)init
{
self = [super init];
if (self) {
self.program = glCreateProgram();
}
return self;
}
-(BOOL)compileLinkSuccessShaderName:(NSString *)shader completeBlock:(void(^)(GLuint program))completeBlock
{
NSURL *vertShaderURL, *fragShaderURL;
GLuint vertShader, fragShader;
GLuint luint =self.program;
vertShaderURL = [[NSBundle mainBundle] URLForResource:shader withExtension:@"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER URL:vertShaderURL]) {
NSLog(@"Failed to compile vertex shader");
return NO;
}
// Create and compile fragment shader.
fragShaderURL = [[NSBundle mainBundle] URLForResource:shader withExtension:@"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER URL:fragShaderURL]) {
NSLog(@"Failed to compile fragment shader");
return NO;
}
// Attach vertex shader to program.
glAttachShader(luint, vertShader);
// Attach fragment shader to program.
glAttachShader(luint, fragShader);
if (![self linkProgram:luint]) {
NSLog(@"Failed to link program: %d", luint);
if (vertShader) {
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader) {
glDeleteShader(fragShader);
fragShader = 0;
}
if (luint) {
glDeleteProgram(luint);
luint = 0;
}
return NO;
}
completeBlock(luint);
glUseProgram(luint);
return YES;
}
- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type URL:(NSURL *)URL
{
NSError *error;
NSString *sourceString = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:&error];
if (sourceString == nil) {
NSLog(@"Failed to load vertex shader: %@", [error localizedDescription]);
return NO;
}
GLint status;
const GLchar *source;
source = (GLchar *)[sourceString UTF8String];
*shader = glCreateShader(type);
glShaderSource(*shader, 1, &source, NULL);
glCompileShader(*shader);
#if defined(DEBUG)
GLint logLength;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log);
NSLog(@"Shader compile log:\n%s", log);
free(log);
}
#endif
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status == 0) {
glDeleteShader(*shader);
return NO;
}
return YES;
}
-(BOOL)linkProgram:(GLuint)prog
{
GLint status;
glLinkProgram(prog);
#if defined(DEBUG)
GLint logLength;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
NSLog(@"Program link log:\n%s", log);
free(log);
}
#endif
glGetProgramiv(prog, GL_LINK_STATUS, &status);
if (status == 0) {
return NO;
}
return YES;
}
- (BOOL)validateProgram:(GLuint)prog
{
GLint logLength, status;
glValidateProgram(prog);
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
NSLog(@"Program validate log:\n%s", log);
free(log);
}
glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
if (status == 0) {
return NO;
}
return YES;
}
@end
#import
#import
NS_ASSUME_NONNULL_BEGIN
@interface Vertex : NSObject
-(void)allocVertexNum:(GLsizei)vertexNum andEachVertexNum:(GLsizei)eachVertexNum;
-(void)setVertex:(CGFloat *)vertex index:(NSInteger)index;
-(void)releaseVertex;
-(void)bindBufferWithUsage: (GLenum) usage;
-(void)enableVertexInVertexAttrib:(GLuint)index numberOfCoordinates:(GLint)count attribOffset:(GLsizeiptr)offset;
-(void)drawVertexWithMode:(GLenum)mode startVertexIndex:(GLint)first
numberOfVertices:(GLsizei)count;
@end
NS_ASSUME_NONNULL_END
#import "Vertex.h"
@interface Vertex()
@property (nonatomic ,assign) GLfloat *vertex; ;
@property (nonatomic ,assign) GLsizei vertexNum ;
@property (nonatomic ,assign) GLsizei eachVertexNum ;
@property (nonatomic, assign) GLuint vertexBuffers;
@property (nonatomic ,assign) GLenum usage ;
@end
@implementation Vertex
- (instancetype)init
{
self = [super init];
if (self) {
[self _customInit];
}
return self;
}
-(void)_customInit{
glGenBuffers(1, &_vertexBuffers);
}
-(NSInteger)getAllocSpaceByteNum{
return self.getVertexWidth*self.vertexNum;
}
-(GLsizei)getVertexWidth{
return sizeof(GLfloat) * self.eachVertexNum;
}
-(void)allocVertexNum:(GLsizei)vertexNum andEachVertexNum:(GLsizei)eachVertexNum{
[self releaseVertex];
self.vertexNum = vertexNum;
self.eachVertexNum = eachVertexNum;
self.vertex =(GLfloat*)malloc(self.getAllocSpaceByteNum);
memset( self.vertex, 0, self.getAllocSpaceByteNum);
}
-(void)setVertex:(GLfloat *)vertex index:(NSInteger)index{
if (self.vertex) {
NSInteger offset = index * self.eachVertexNum;
for (NSInteger i = 0; i=
((first + count) *sizeof(GLfloat) * self.eachVertexNum),
@"Attempt to draw more vertex data than available.");
glDrawArrays(mode, first, count);
}
- (void)dealloc
{
[self releaseVertex];
}
@end
工程运行结果
工程源码地址
github
工程解释
在第一个工程里面,我们只是绘制了一个三角形,并且不停的变化三角形的颜色.
这里我们看看我们的第一个工程里面都干了些啥事情
声明OpenGL VC
我们首先创建了GLKViewController 类型的vc.该类会定时刷新屏幕回调函数是-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect
创建上下文 和配置view
我们必须给openGL 创建OpenGL使用的上下文EAGLContext .并且设置为当前上下文.并且给当前view 使用当前上下文
这里其实就是配置OpenGL 环境而已
配置shader
shader 是OpenGL 绘制所必须依赖的.
这里我们需要知道shader 需要经过编译连接过程.和编译型语言很相似.
顶点设置
接着我们再设置顶点相关信息,我们配置了三个顶点.
绘制
我们根据shader 和顶点 进行再屏幕绘制.
简单工程可学习知识点
顶点
顶点是啥?
顶点就是坐标位置,不管你是画直线,三角形,正方体,球体,以及3D游戏人物等,都需要顶点来确定其形状。
如何绘制顶点
涉及到的api如下
- glGenBuffers (GLsizei n, GLuint* buffers)
生成buffer指针
- glBindBuffer (GLenum target, GLuint buffer)
绑定buffer指针指向的内存区域
- glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage)
给内存区域赋值
- glEnableVertexAttribArray (GLuint index)
使能顶点属性
- glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
设置顶点该属性使用的是内存中顶点的那些值
- glDrawArrays (GLenum mode, GLint first, GLsizei count)
绘制顶点
具体模型如下
glBindBuffer
绑定的buffer 是GL_ARRAY_BUFFER. 这里还有其他的buffer GL_ELEMENT_ARRAY_BUFFER
,GL_ARRAY_BUFFER_BINDING
,GL_ELEMENT_ARRAY_BUFFER_BINDING
.这里不探讨几种buffer.等积累到一定知识再来讨论这几种buffer.
glBufferData
写入数据的时候,我们有个参数是GL_STATIC_DRAW
该参数还有以下几个值GL_STREAM_DRAW
,GL_DYNAMIC_DRAW
这些值什么意思呢?
"Static”意味着数据不会被改变(一次修改,多次使用),"dynamic”意味着数据可以被频繁修改(多次修改,多次使用),"stream”意味着数据每帧都不同(一次修改,一次使用)。"Draw”意味着数据将会被送往GPU进行绘制,"read”意味着数据会被用户的应用读取,"copy”意味着数据会被用于绘制和读取。注意在使用VBO时,只有draw是有效的,而copy和read主要将会在像素缓冲区(PBO)和帧缓冲区(FBO)中发挥作用。
因此这里我们需要根据顶点的数据特点选择特定的样式. 我们的demo中由于只是简单的绘制三角形,没有必要进行更改就选择了GL_STATIC_DRAW 模式.
在demo中我们传入的顶点属性是 GLKVertexAttribPosition
,顶点属性还有以下值GLKVertexAttribNormal
,GLKVertexAttribColor
,GLKVertexAttribTexCoord0
,GLKVertexAttribTexCoord1
GLKVertexAttribPosition 代表顶点在屏幕上的具体位置
GLKVertexAttribNormal 代表法向量
GLKVertexAttribColor 顶点的颜色
GLKVertexAttribTexCoord0 0位置贴图
GLKVertexAttribTexCoord1 1 位置贴图
具体的效果,在后面的章节研究.
glDrawArrays
中我们传入了GL_TRIANGLE_FAN
,该值代码点连线的方式. 还有以下其他方式GL_POINTS
,GL_LINE_LOOP
,GL_LINE_STRIP
, GL_TRIANGLES
,GL_TRIANGLE_FAN
.这些模式具体的作用在后面的章节具体细讲.
shader 的介绍
shader 分为顶点着色器 和片段着色器.每种着色器都需要经过编译和连接两个步骤
主要api
+glCreateProgram (void)
生成program .其实就是一个指针地址,代表着色器
- glCreateShader (GLenum type)
创建shader 值是 GL_VERTEX_SHADER 或者GL_FRAGMENT_SHADER
- glShaderSource (GLuint shader, GLsizei count, const GLchar* const string, const GLint length)
给shader 赋值
- glCompileShader (GLuint shader)
编译shader
- glDeleteShader (GLuint shader)
要是shader 编译不成功需要删除shader
- glAttachShader (GLuint program, GLuint shader)
绑定shader 到指定的标志位上.
- glLinkProgram (GLuint program)
连接
- glUseProgram (GLuint program)
使用着色器
具体图如下
这里不探讨着色器的具体写法.后面有专有章节学习.