今天给各位分享深入探讨OpenGLES:自定义顶点与片元着色器的编译与连接技巧(第二部分)的知识,其中也会对进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
具体思路
二.编译链接前,我们先来了解一些基本的OpenGLES的函数参数的说明
2.1编译/链接流程说明
1) Shader和程序对象
2)创建并编译着色器
3)创建并链接程序
4) 获取和设置统一变量
5) 获取和设置属性
6)Shader编译器和程序二进制代码
使用着色器进行渲染需要创建2 个基本对象: 着色器对象和程序对象。获取链接的着色器对象的一般过程包括6个步骤:
1.创建顶点着色器对象和段着色器对象
2. 将源代码链接到每个着色器对象
3.编译shader对象
4. 创建程序对象
5. 将编译好的着色器对象连接到程序对象
6. 链接器对象
2.2函数及参数说明
创建着色器:GLuint glCreateShader(GLenum类型)参数样式一般使用GL_VERTEX_SHADER(顶点着色器)或GL_FRAGMENT_SHADER(片段着色器)返回着色器句柄
删除着色器:glDeleteShader(GLuint着色器)参数:设置刚刚创建的顶点/片段句柄
void glShaderSource(GLuint 着色器,GLSizei 计数,const GLChar * const *string,const GLint
*length) 将shader源码附加到shader对象上(上一篇文章我写了两个顶点/片段着色器.vsh和.fsh文件,这里需要将写好的源码附加到shader上(设备上)参数说明:
1)参数1:shader,要编译的shader对象*shader
2)参数2:numOfStrings,传递的源代码字符串数量为1。特别说明:该参数大多数情况下为1。
3)参数3:字符串,着色器程序的源代码(真正的着色器程序源代码)
4) lenOfStrings, length, 一个数组,其中包含每个字符串的长度,或者NULL,表示该字符串以NULL 结尾
对应的程序代码:
-(void)compileShader:(GLuint*)shadertype:(GLenum)typefile:(NSString*)file{
//1.读取文件路径字符串,并根据传入的顶点/片段着色器源代码地址将其转换为C语言字符串。
NSString* 内容=[NSString stringWithContentsOfFile:文件编码:NSUTF8StringEncoding error:nil];
const GLchar* 源=(GLchar*)[内容UTF8String];
//2.创建着色器(基于类型)
*着色器=glCreateShader(类型);
glShaderSource(*着色器,1,源,NULL);
//4.将shader源代码编译为目标代码
glCompileShader(*着色器);
}
OpenGLES无法像iOS那样设置断电并输出信息。如果想查看刚刚创建编译的着色器,可以通过:void glGetShaderiv(GLuint Shader, GLenum pname, GLint *params) 查看链接状态。我将在下面详细解释如何执行此操作。使用void glGetShaderInfolog(GLuint Shader, GLSizei maxLength, GLSizei *length, GLChar *infoLog) 获取输出日志。具体参数如下:
(1) void glGetShaderiv(GLuint 着色器, GLenum pname, GLint *params);
着色器——需要编译的着色器对象的句柄
pname—获取的信息参数可以是GL_COMPILE_STATUS/GL_DELETE_STATUS/GL_INFO_LOG_LENGTH/GL_SHADER_SOURCE_LENGTH/GL_SHADER_TYPE
params——指向查询结果的整数存储位置的指针
(2) void glGetShaderInfolog(GLuint 着色器, GLSizei maxLength, GLSizei *length, GLChar *infoLog);
Shader—需要获取信息日志的Shader对象的句柄
maxLength——保存信息日志的缓存区大小
length——写入的信息日志的长度(减去空终止符);如果不需要知道长度,该参数可以为Null
infoLog——指向保存信息日志的字符缓冲区的指针
2.3创建与链接程序
(1) 创建程序
GLUint glCreateProgram() 描述: 创建程序对象并返回值: 返回执行新程序对象的句柄。同样,只要创建了,就会为创建的程序删除void glDeleteProgram(GLuint program)参数。
void glAttachShader( GLuint program , GLuint Shader ) 描述: 将刚刚创建的顶点/片段着色器附加到创建的程序中。当然,如果连接了,就会断开连接。 void glDetachShader(GLuint 程序);向创建的程序发送指令,断开附件
(2) 链接器
glLinkProgram(GLuint program) 参数是刚刚创建的程序
检查链接状态:void glGetProgramiv (GLuint program,GLenum pname, GLint *params)
参数说明:program:需要获取该信息的程序对象句柄
pname:参数获取信息,可以是:
GL_ACTIVE_ATTRIBUTES
GL_ACTIVE_ATTRIBUTES_MAX_LENGTH
GL_ACTIVE_UNIFORM_BLOCK
GL_ACTIVE_UNIFORM_BLOCK_MAX_LENGTH
GL_ACTIVE_UNIFROMS
GL_ACTIVE_UNIFORM_MAX_LENGTH
GL_ATTACHED_SHADERS
GL_DELETE_STATUS
GL_INFO_LOG_LENGTH
GL_LINK_STATUS
GL_PROGRAM_BINARY_RETRIEVABLE_HINT
GL_TRANSFORM_FEEDBACK_BUFFER_MODE
GL_TRANSFORM_FEEDBACK_VARYINGS
GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH
GL_VALIDATE_STATUS
params: 指向查询结果整数存储位置的指针
对应程序中的代码为:
glLinkProgram(self.myPrograme);
GLintlink 状态;
//获取链接状态
glGetProgramiv(self.myPrograme,GL_LINK_STATUS, linkStatus);
if(linkStatus==GL_FALSE) {
GLchar 消息[512];注意:因为我们不知道错误信息的长度,所以在设置数组时可以尽量让它尽可能大。第512章可以修改。
glGetProgramInfoLog(self.myPrograme,sizeof(消息),0,消息[0]);
NSString*messageString=[NSStringstringWithUTF8String:message];
NSLog(@"程序链接错误:%@",messageString);
返回;
}
(3)使用程序
void glUseProgram(GLuint program) 说明:创建并链接后,使用程序
三.FrameBuffffer (帧缓冲区)和RenderBuffffer(渲染缓冲区)说明以及他两的关系
首先我们看一张图:
FrameBuffffer Objects、RenderBuffffer Objects和Textures的解释来自苹果官网:
renderbuffffer 对象是应用程序分配的2D 图像缓冲区。 renderbuffffer 可用于分配和存储颜色、深度或模板值。它还可以用作帧缓冲中的颜色、深度或模板附件。渲染缓冲区是由类似屏幕的窗口系统提供的可绘制表面。例如,pBuffer。一个renderbuffffer,那么它就不能像GL纹理那样直接使用。
一个frameBuffffer对象(通常称为FBO)。是收集颜色、深度和模板缓冲区的连接点。描述属性的状态,例如颜色、深度以及模板缓冲区的大小和格式,都与FBO(帧缓冲区对象)相关联。并且纹理名称和renderBuffffer对象也与FBO相关联。各种2D 图形可以附加到帧缓冲对象的颜色附加点。它们包含存储在渲染缓冲区对象、2D 纹理或立方体贴图中的颜色值。或者3D 纹理中的mip 级2D 切片。同样,包含当前深度值的各种2D 图形可以附加到FBO 的深度附加点。唯一可以附加到FBO 模板附加点的二维图像是存储模板值的渲染缓冲对象。
四.设置RenderBufferFrameBuffer
(一)设置RenderBuffer
1. 定义缓存ID
GLuint 缓冲区;
2、申请缓存区标志
glGenRenderbuffers(1, 缓冲区);
3. 使用属性保存
self.myColorRenderBuffer=缓冲区;
4.将标识符绑定到GL_RENDERBUFFER
glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
5.Context将drawable对象的CAEAGLLayer的存储绑定到OpenGL ES renderBuffer对象
[来自Drawable:self.myEagLayer的self.myContext renderbufferStorage:GL_RENDERBUFFER];
(二)设置FrameBuffer
1.定义缓存ID
GLuint 缓冲区;
2、申请缓存区标志
//glGenRenderbuffers(1, 缓冲区);
//glGenFramebuffers(1, 缓冲区);
glGenBuffers(1, buffer);//也可以指定对应的缓冲区标志,如上面两行
3.
self.myColorFrameBuffer=缓冲区;
4.
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
/*生成帧缓冲区后,需要将renderbuffer绑定到帧缓冲区。
调用glFramebufferRenderbuffer函数绑定对应的附着点,以便后续的绘制工作。
*/
5、通过glFramebufferRenderbuffer函数将渲染缓冲区myColorRenderBuffer绑定到GL_COLOR_ATTACHMENT0。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
注意:这里不会将Context与帧缓冲区联系起来,因为帧缓冲区的控制是由GPU控制,当我们用于图像图形显示的时候,是从渲染缓冲区拿的,我们只需要开辟帧缓冲区,把它和渲染缓冲区一起绑定到GL_COLOR_ATTACHMENT0就行了,表示帧缓冲区和渲染缓冲区对应的关系,可以看上面苹果爸爸的解释,你会看到最后一行代码和设置渲染缓冲区的是不一样的,渲染缓冲区是把Context将可绘制对象drawable object"s CAEAGLLayer的存储绑定到OpenGL ES renderBuffer对象
五.使用固GLKView定着色器和自定义着色器的函数使用不同点(开辟和绑定我们就不说了,代码一样,直接讲不通的地方)加载纹理的不同点
1.设置顶点坐标的不同点
在GLKView固定着色器里面,我们设置顶点坐标数据:
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
在自定义顶点着色器中,我们将顶点坐标数据设置为:
将顶点数据从CPU 内存复制到GPU
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
通过myPrograme将顶点数据传递到顶点着色器程序的位置
1.glGetAttribLocation,用于获取顶点属性的入口。
2.告诉OpenGL ES,通过glEnableVertexAttribArray,
3.最终的数据是通过glVertexAttribPointer传递的。
(1)注意:第二个参数字符串必须与输入变量:sharv.vsh中的位置一致
GLuint 位置=glGetAttribLocation(self.myPrograme, "位置");
(2).设置适当的格式从缓冲区读取数据
glEnableVertexAttribArray(位置);
(3).设置读取方式
参数1:index,顶点数据的索引
参数2:size,每个顶点属性的分量个数,1、2、3、4。默认初始值为4。
参数3:type,数据中各个组成部分的类型。常用的有GL_FLOAT、GL_BYTE、GL_SHORT。默认初始值为GL_FLOAT
参数4:归一化,定点数据值是归一化还是直接转换为固定值。 (GL_FALSE)
参数5:stride,连续顶点属性之间的偏移量,默认为0;
参数6:指定一个指向数组中第一个顶点属性的第一个分量的指针。默认值为0
glVertexAttribPointer(位置,3,GL_FLOAT,GL_FALSE,sizeof(GLfloat) *5,NULL);
2.设置纹理坐标的不同点
在GLKView固定着色器中,我们设置纹理坐标数据:
纹理坐标数据
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
在自定义顶点着色器中,我们将纹理坐标数据设置为:
(1).glGetAttribLocation,用于获取顶点属性的入口。
//注意:第二个参数字符串必须与sharv.vsh中的输入变量:textCooperative一致
GLuint textCoor=glGetAttribLocation(self.myPrograme, "textCooperative");
(2).设置适当的格式从缓冲区读取数据
glEnableVertexAttribArray(textCoor);
(3).设置读取方式(见参数说明)
glVertexAttribPointer(textCoor,2,GL_FLOAT,GL_FALSE,sizeof(GLfloat)*5, (float*)NULL+3);
3.加载纹理的不同点
在GLKView固定着色器中,我们设置加载纹理:
1.获取纹理图像路径
NSString *filePath=[[NSBundle mainBundle]pathForResource:@"timg" ofType:@"jpg"];
2.设置纹理参数
纹理坐标原点是左下角,但图片显示原点应该是左上角。
NSDictionary *选项=[NSDictionary DictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo=[GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
3.使用Apple的GLKit提供的GLKBaseEffect来完成shader工作(顶点/片段)
cEffect=[[GLKBaseEffect alloc]init];
cEffect.texture2d0.enabled=GL_TRUE;
cEffect.texture2d0.name=textureInfo.name;
在自定义着色器里面,我们设置加载纹理:
//从图像加载纹理
- (GLuint)setupTexture:(NSString*)文件名{
//1.将UIImage 转换为CGImageRef
计算机图形图像处理
mageRef spriteImage = [UIImage imageNamed:fileName].CGImage; //判断图片是否获取成功 if(!spriteImage) { NSLog(@"Failed to load image %@", fileName); exit(1); } //2、读取图片的大小,宽和高 size_twidth =CGImageGetWidth(spriteImage); size_theight =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); //5、在CGContextRef上-->将图片绘制出来 /* CGContextDrawImage 使用的是Core Graphics框架,坐标系与UIKit 不一样。UIKit框架的原点在屏幕的左上角,Core Graphics框架的原点在屏幕的左下角。 CGContextDrawImage 参数1:绘图上下文 参数2:rect坐标 参数3:绘制的图片 */ CGRectrect =CGRectMake(0,0, width, height); //6.使用默认方式绘制 CGContextDrawImage(spriteContext, rect, spriteImage); //7、画图完毕就释放上下文 CGContextRelease(spriteContext); //8、绑定纹理到默认的纹理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); floatfw = 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); return0; } 效果下一篇,我们讲纹理的翻转 最后附上demo:链接: https://pan.baidu.com/s/1-6RJ0toD6WUkoozX0Fu1Cw 提取码: fweb关于深入探讨OpenGLES:自定义顶点与片元着色器的编译与连接技巧(第二部分)到此分享完毕,希望能帮助到您。
【深入探讨OpenGLES:自定义顶点与片元着色器的编译与连接技巧(第二部分)】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
终于到了深度学习部分了!一直在想怎么应用于游戏开发。
有8位网友表示赞同!
这段时间接触OpenGL就卡在了着色器这块,希望能学到一些实际的例子。
有11位网友表示赞同!
之前做过简单点的着色器代码,但是没深入了解过编译和链接的过程,这个应该很有帮助。
有5位网友表示赞同!
想学习自定义顶点/片元着色器主要是为了做一些特殊的渲染效果。
有5位网友表示赞同!
感觉这篇文章终于揭开了OpenGL的奥秘!可以让我更好地理解游戏开发了。
有15位网友表示赞同!
最近在学3D建模,不知道如何应用着色器,看来这个文章正好能解决我的疑惑。
有10位网友表示赞同!
自定义着色器才能实现更加个性化的渲染效果,期待学习。
有18位网友表示赞同!
OpenGLES的文章讲解得非常详细,应该能一步步学会编写自定义着色器。
有13位网友表示赞同!
想做一些手机游戏,需要了解OpenGL和着色器的知识。
有14位网友表示赞同!
这篇教程看起来很有用,可以让我更好地理解OpenGL的原理。
有9位网友表示赞同!
学习OpenGL的难度其实挺大的,希望这篇文章能帮我入门!
有20位网友表示赞同!
之前看过一些OPENGL的入门教程,但感觉还不够深入,这个文章希望能解答我的问题。
有7位网友表示赞同!
OpenGLES的应用场景很多,掌握着色器的知识很重要。
有12位网友表示赞同!
我想要学习OpenGL开发移动游戏,这篇文章应该很有用!
有6位网友表示赞同!
期待看到一些实际代码示例,更好地理解如何编译和连接着色器。
有19位网友表示赞同!
我一直对GPU渲染感兴趣,这个文章能帮助我更深入地了解相关知识!
有6位网友表示赞同!
希望学习完这篇教程能够自己写出一些有趣的着色器效果。
有10位网友表示赞同!
想制作3D动画,需要对OpenGL和着色器有一定的理解。
有13位网友表示赞同!
学习OpenGLES可以让游戏开发更加灵活多彩,期待学习。
有7位网友表示赞同!