OpenGL ES渲染管线与着色器
转自:http://blog.csdn.net/kesalin/article/details/8223649
[OpenGL ES 02]OpenGL ES渲染管线与着色器
罗朝辉 (http://blog.csdn.net/kesalin)
前言
一,渲染管线
再回到上图,这张图就是 OpenGL ES 的“架构图”,学习OpenGL ES 就是学习这张图中的每一个部分,在这里先粗略地介绍一下。
Vertex Shader:顶点着色器通过可编程的方式实现对顶点的操作,如进行坐标空间转换,计算 per-vertex color以及纹理坐标;
Fragment Shader:片元着色器通过可编程的方式实现对片元的操作。在这一阶段它接受光栅化处理之后的fragment,color,深度值,模版值作为输入。
Per-Fragment Operation:在这一阶段对片元着色器输出的每一个片元进行一系列测试与处理,从而决定最终用于渲染的像素。这一系列处理过程如下:
Pixel ownership test:该测试决定像素在 framebuffer 中的位置是不是为当前 OpenGL ES 所有。也就是说测试某个像素是否对用户可见或者被重叠窗口所阻挡;
Scissor Test:剪裁测试,判断像素是否在由 glScissor 定义的剪裁矩形内,不在该剪裁区域内的像素就会被剪裁掉;
Stencil Test:模版测试,将模版缓存中的值与一个参考值进行比较,从而进行相应的处理;
Depth Test:深度测试,比较下一个片段与帧缓冲区中的片段的深度,从而决定哪一个像素在前面,哪一个像素被遮挡;
Blending:混合,混合是将片段的颜色和帧缓冲区中已有的颜色值进行混合,并将混合所得的新值写入帧缓冲;
Dithering:抖动,抖动是使用有限的色彩让你看到比实际图象更多色彩的显示方式,以缓解表示颜色的值的精度不够大而导致的颜色剧变的问题。
二,顶点着色器
Samplers:一种特殊的 uniform,用于呈现纹理。sampler 可用于顶点着色器和片元着色器。
Shader program:由 main 申明的一段程序源码,描述在顶点上执行的操作:如坐标变换,计算光照公式来产生 per-vertex 颜色或计算纹理坐标。
在顶点着色器阶段至少应输出位置信息-即内建变量:gl_Position,其它两个可选的变量为:gl_FrontFacing 和 gl_PointSize。
三,片元着色器
Shader program:由 main 申明的一段程序源码,描述在片元上执行的操作。
在顶点着色器阶段只有唯一的 varying 输出变量-即内建变量:gl_FragColor。
四,顶点着色与片元着色在编程上的差异
着色语言定了三种级别的精度:lowp, mediump, highp。我们可以在 glsl 脚本文件的开头定义默认的精度。如下代码定义在 float 类型默认使用 highp 级别的精度
precision highp float;
在顶点着色阶段,如果没有用户自定义的默认精度,那么 int 和 float 都默认为 highp 级别;而在片元着色阶段,如果没有用户自定义的默认精度,那么就真的没有默认精度了,我们必须在每个变量前放置精度描述符。此外,OpenGL ES 2.0 标准也没有强制要求所有实现在片元阶段都支持 highp 精度的。我们可以通过查看是否定义 GL_FRAGMENT_PRECISION_HIGH 来判断具体实现是否在片元着色器阶段支持 highp 精度,从而编写出可移植的代码。当然,通常我们不需要在片元着色器阶段使用 highp 级别的精度,推荐的做法是先使用 mediump 级别的精度,只有在效果不够好的情况下再考虑 highp 精度。
2,attribute 修饰符只可用于顶点着色。这个前面已经说过了。
3,或由于精度的不同,或因为编译优化的原因,在顶点着色和片元着色阶段同样的计算可能会得到不同的结果,这会导致一些问题(z-fighting)。因此 glsl 引入了 invariant 修饰符来修饰在两个着色阶段的同一变量,确保同样的计算会得到相同的值。
五,使用顶点着色器与片元着色器
好了,理论知识讲得足够多了,下面我们来看看如何在代码中添加顶点着色器与片元着色器。我们在前一篇文章《[OpenGL ES 01]OpenGL ES之初体验》代码的基础上进行编码。在前面提到可编程管线通过用 shader 语言编写脚本文件实现的,这些脚本文件相当于 C 源码,有源码就需要编译链接,因此需要对应的编译器与链接器,shader 对象与 program 对象就相当于编译器与链接器。shader 对象载入源码,然后编译成 object 形式(就像C源码编译成 .obj文件)。经过编译的 shader 就可以装配到 program 对象中,每个 program对象必须装配两个 shader 对象:一个顶点 shader,一个片元 shader,然后 program 对象被连接成“可执行文件”,这样就可以在 render 中是由该“可执行文件”了。
1,创建,装载和编译 shader
首先,我们向工程中添加新的类 GLESUtils,让它继承自 NSObject。修改 GLESUtils.h 为:
#import <Foundation/Foundation.h>
include <OpenGLES/ES2/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;
@end
修改 GLESUtils.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; }
</span><span style="color:rgb(0,0,255);">return</span><span> [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; }
</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Load the shader source</span>
<span style="color:rgb(0,0,255);">const</span> <span style="color:rgb(0,0,255);">char</span> * shaderStringUTF8 =<span> [shaderString UTF8String];
glShaderSource(shader, </span><span style="color:rgb(128,0,128);">1</span>, &<span>shaderStringUTF8, NULL);</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Compile the shader</span>
glCompileShader(shader);
</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Check the compile status</span>
GLint compiled = <span style="color:rgb(128,0,128);">0</span><span>;
glGetShaderiv(shader, GL_COMPILE_STATUS, </span>&<span>compiled);</span><span style="color:rgb(0,0,255);">if</span> (!<span>compiled) {GLint infoLen </span>= <span style="color:rgb(128,0,128);">0</span><span>;glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, </span>&<span>infoLen );</span><span style="color:rgb(0,0,255);">if</span> (infoLen > <span style="color:rgb(128,0,128);">1</span><span>) {</span><span style="color:rgb(0,0,255);">char</span> * infoLog = malloc(<span style="color:rgb(0,0,255);">sizeof</span>(<span style="color:rgb(0,0,255);">char</span>) *<span> infoLen);glGetShaderInfoLog (shader, infoLen, NULL, infoLog);NSLog(</span><span style="color:rgb(128,0,0);">@"</span><span style="color:rgb(128,0,0);">Error compiling shader:\n%s\n</span><span style="color:rgb(128,0,0);">"</span><span>, infoLog ); free(infoLog);}glDeleteShader(shader);</span><span style="color:rgb(0,0,255);">return</span> <span style="color:rgb(128,0,128);">0</span><span>;
}</span><span style="color:rgb(0,0,255);">return</span><span> shader;
}@end
辅助类 GLESUtils 中有两个类方法用来跟进 shader 脚本字符串或 shader 脚本文件创建 shader,然后装载它,编译它。下面详细介绍每个步骤。
1),创建/删除 shader
函数 glCreateShader 用来创建 shader,参数 GLenum type 表示我们要处理的 shader 类型,它可以是 GL_VERTEX_SHADER 或 GL_FRAGMENT_SHADER,分别表示顶点 shader 或 片元 shader。它返回一个句柄指向创建好的 shader 对象。
函数 glDeleteShader 用来销毁 shader,参数为 glCreateShader 返回的 shader 对象句柄。
2),装载 shader
函数 glShaderSource 用来给指定 shader 提供 shader 源码。第一个参数是 shader 对象的句柄;第二个参数表示 shader 源码字符串的个数;第三个参数是 shader 源码字符串数组;第四个参数一个 int 数组,表示每个源码字符串应该取用的长度,如果该参数为 NULL,表示假定源码字符串是 \0 结尾的,读取该字符串的内容指定 \0 为止作为源码,如果该参数不是 NULL,则读取每个源码字符串中前 length(与每个字符串对应的 length)长度个字符作为源码。
3),编译 shader
函数 glCompileShader 用来编译指定的 shader 对象,这将编译存储在 shader 对象中的源码。我们可以通过函数 glGetShaderiv 来查询 shader 对象的信息,如本例中查询编译情况,此外还可以查询 GL_DELETE_STATUS,GL_INFO_LOG_STATUS,GL_SHADER_SOURCE_LENGTH 和 GL_SHADER_TYPE。在这里我们查询编译情况,如果返回 0,表示编译出错了,错误信息会写入 info 日志中,我们可以查询该 info 日志,从而获得错误信息。
2,编写着色脚本
GLESUtils 提供的接口让我们可以使用两种方式:脚本字符串或脚本文件来提供 shader 源码,通常使用脚本文件方式有更大的灵活性。(Cocos2D 源码中倒是提供了不少脚本字符串应对一些常见的情况,有兴趣的同学可以查看下)。在这里,我们使用脚本文件方式。
1),添加顶点着色脚本
右击 Supporting Files 目录,New File->Other->Empty,输入名称:VertexShader.glsl,去除 target Tutorial02 中的勾选。后缀glsl 表示 GL Shader Language。
编辑其内容如下:
attribute vec4 vPosition; void main(void) { gl_Position = vPosition; }
然后选择 Tutorial02,在 Build Phases -> Copy Bundle Sources 中添加 VertexShader.glsl。
顶点着色脚本的源码很简单,如果你仔细阅读了前面的介绍,就一目了然。 attribute 属性 vPosition 表示从应用程序输入的类型为 vec4 的位置信息,输出内建 vary 变量 vPosition。留意:这里使用了默认的精度。
2),添加片元着色脚本
用于添加顶点着色脚本同样的方式添加名为 FragmentShader.glsl 的文件,编辑其内容如下:
precision mediump float;void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }
不用忘记在 Build Phases -> Copy Bundle Sources 中添加 FragmentShader.glsl。
片元着色脚本源码也很简单,前面说过片元着色要么自己定义默认精度,要么在每个变量前添加精度描述符,在这里自定义 float 的精度为 mediump。然后为内建输出变量 gl_FragColor 指定为红色。
3,创建 program,装配 shader,链接 program,使用 program
1),创建 program
在 OpenGLView.h 的 OpenGLView 类声明中添加两个成员:
GLuint _programHandle; GLuint _positionSlot;
然后依然在 OpenGLView.m 中的匿名 category 中添加成员方法:
- (void)setupProgram;
在 - (void)render 方法前,添加其实现:
- (void)setupProgram { // Load shaders // NSString * vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader" ofType:@"glsl"]; NSString * fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader" ofType:@"glsl"]; GLuint vertexShader = [GLESUtils loadShader:GL_VERTEX_SHADER withFilepath:vertexShaderPath]; GLuint fragmentShader = [GLESUtils loadShader:GL_FRAGMENT_SHADER withFilepath:fragmentShaderPath];
</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Create program, attach shaders.</span>
_programHandle =<span> glCreateProgram();
</span><span style="color:rgb(0,0,255);">if</span> (!<span>_programHandle) {NSLog(</span><span style="color:rgb(128,0,0);">@"</span><span style="color:rgb(128,0,0);">Failed to create program.</span><span style="color:rgb(128,0,0);">"</span><span>);</span><span style="color:rgb(0,0,255);">return</span><span>;
}glAttachShader(_programHandle, vertexShader);
glAttachShader(_programHandle, fragmentShader);</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Link program
</span><span style="color:rgb(0,128,0);">//
glLinkProgram(_programHandle);
</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Check the link status</span>
GLint linked; glGetProgramiv(_programHandle, GL_LINK_STATUS, &linked ); if (!linked) { GLint infoLen = 0; glGetProgramiv (_programHandle, GL_INFO_LOG_LENGTH, &infoLen );
</span><span style="color:rgb(0,0,255);">if</span> (infoLen > <span style="color:rgb(128,0,128);">1</span><span>){</span><span style="color:rgb(0,0,255);">char</span> * infoLog = malloc(<span style="color:rgb(0,0,255);">sizeof</span>(<span style="color:rgb(0,0,255);">char</span>) *<span> infoLen);glGetProgramInfoLog (_programHandle, infoLen, NULL, infoLog );NSLog(</span><span style="color:rgb(128,0,0);">@"</span><span style="color:rgb(128,0,0);">Error linking program:\n%s\n</span><span style="color:rgb(128,0,0);">"</span><span>, infoLog ); free (infoLog );}glDeleteProgram(_programHandle);_programHandle </span>= <span style="color:rgb(128,0,128);">0</span><span>;</span><span style="color:rgb(0,0,255);">return</span><span>;
}glUseProgram(_programHandle);</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Get attribute slot from program
</span><span style="color:rgb(0,128,0);">//
_positionSlot = glGetAttribLocation(_programHandle, “vPosition”); }
有了前面的介绍,上面的代码很容易理解。首先我们是由 GLESUtils 提供的辅助方法从前面创建的脚本中创建,装载和编译顶点 shader 和片元 shader;然后我们创建 program,将顶点 shader 和片元 shader 装配到 program 对象中,再使用 glLinkProgram 将装配的 shader 链接起来,这样两个 shader 就可以合作干活了。注意:链接过程会对 shader 进行可链接性检查,也就是前面说到同名变量必须同名同型以及变量个数不能超出范围等检查。我们如何检查 shader 编译情况一样,对 program 的链接情况进行检查。如果一切正确,那我们就可以调用 glUseProgram 激活 program 对象从而在 render 中使用它。通过调用 glGetAttribLocation 我们获取到 shader 中定义的变量 vPosition 在 program 的槽位,通过该槽位我们就可以对 vPosition 进行操作。
4,使用示例
在 - (void)layoutSubviews 中调用 render 方法之前,插入对 setupProgram 的调用:
[self setupProgram];
[self render];</span></pre></div><p style="margin:14px auto;font-family:verdana, Arial, Helvetica, sans-serif;font-size:14px;line-height:21px;">然后改写 render 方法:</p><div class="cnblogs_code" style="background-color:rgb(245,245,245);font-family:'Courier New';border:1px solid rgb(204,204,204);padding:5px;overflow:auto;margin:5px 0px;"><pre style="font-family:'Courier New';margin:5px 8px;padding:5px;">- (<span style="color:rgb(0,0,255);">void</span><span>)render
{ glClearColor(0, 1.0, 0, 1.0); glClear(GL_COLOR_BUFFER_BIT);
</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Setup viewport
</span><span style="color:rgb(0,128,0);">//
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
GLfloat vertices[] </span>=<span> {</span><span style="color:rgb(128,0,128);">0.0f</span>, <span style="color:rgb(128,0,128);">0.5f</span>, <span style="color:rgb(128,0,128);">0.0f</span><span>, </span>-<span style="color:rgb(128,0,128);">0.5f</span>, -<span style="color:rgb(128,0,128);">0.5f</span>, <span style="color:rgb(128,0,128);">0.0f</span><span>,</span><span style="color:rgb(128,0,128);">0.5f</span>, -<span style="color:rgb(128,0,128);">0.5f</span>, <span style="color:rgb(128,0,128);">0.0f</span><span> };</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Load the vertex data
</span><span style="color:rgb(0,128,0);">//
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices ); glEnableVertexAttribArray(_positionSlot);
</span><span style="color:rgb(0,128,0);">//</span><span style="color:rgb(0,128,0);"> Draw triangle
</span><span style="color:rgb(0,128,0);">//
glDrawArrays(GL_TRIANGLES, 0, 3);
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
在新增的代码中,第一句 glViewport 表示渲染 surface 将在屏幕上的哪个区域呈现出来,然后我们创建一个三角形顶点数组,通过 glVertexAttribPointer 将三角形顶点数据装载到 OpenGL ES 中并与 vPositon 关联起来,最后通过 glDrawArrays 将三角形图元渲染出来。
5,编译运行
编译运行,将看到一个红色的三角形显示在屏幕中央。知道为什么是红色的么?那是因为 program 也链接了片元着色器,在片元着色脚本文件中,我们指定 gl_FragColor 的值为红色 vec4(1.0, 0.0, 0.0, 1.0)。
六,总结
本文源码可以在这里获得:https://github.com/kesalin/OpenGLES/tree/master/Tutorial02
七,Refference
OpenGL ES 2.0 Programming Guide
OpenGL ES Programming Guide for iOS
OpenGL ES渲染管线与着色器相关推荐
- [OpenGL ES 02]OpenGL ES渲染管线与着色器
http://blog.csdn.net/kesalin/article/details/8223649 罗朝辉 (http://blog.csdn.net/kesalin) 本文遵循"署名 ...
- Opengl ES系列学习--顶点着色器
本节我们继续来看一下<OPENGL ES 3.0编程指南 原书第2版(中文版)>书中第8章的内容,PDF下载地址:OPENGL ES 3.0编程指南 原书第2版(中文版),代码下载地址:O ...
- 可编程渲染管线与着色器语言
Programming pipeline & shading language 大家好,今天想给大家介绍一下可编程渲染管线和着色器语言的相关基础知识,使想上手SHADER编程的童鞋们可以快速揭 ...
- OpenGL播放yuv数据流(着色器SHADER)-IOS(一)
OpenGL播放yuv数据流(着色器SHADER)-IOS(一) 和windows平台的类似,只是用object-c编写的,着色器语言shader,rgb转yuv有些不同,具体看代码注释. //.h ...
- OpenGL shader class自定义着色器的实例
OpenGL shader class自定义着色器 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #in ...
- OpenGL Compute Shader计算着色器的实例
OpenGL Compute Shader计算着色器 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 // #define USE_GL3W #include &l ...
- OpenGL播放yuv数据流(着色器SHADER)-windows(一)
OpenGL播放yuv数据流(着色器SHADER)-windows(一) 在写这篇文章之前首先要感谢老雷,http://blog.csdn.net/leixiaohua1020/article/det ...
- OpenGL渲染管线,着色器,光栅化等概念理解
卧槽,前些日子看这几个概念就十分想吐槽,这么难理解的概念窃以为纯属翻译的不够接地气. ---- 首先,光栅化(Rasterize/rasteriztion). 这个词儿Adobe官方翻译成栅格化或者像 ...
- OpenGL(三)——着色器
目录 一.前言 二.Shader 2 Shader 2.1 顶点着色器 2.2 片段着色器 三.APP 2 Shader 四.顶点颜色属性 五.着色器类C++ 一.前言 着色器Shader是运行在GP ...
最新文章
- oracle 年龄计算 岁 月 天
- python performance measure 01
- SQL查询 的一些原则
- 086_访问html元素
- jQuery操作元素属性
- Windows 10企业批量部署实战之自动化rules设置
- 我应该用哪种虚拟机?(一)
- linux 信号_Linux信号量(1)-SYSTEM V
- git命令超实用集齐
- 机器学习之网格搜索(GridSearch)及参数说明,实例演示
- WPF Chart控件
- 小米pro 笔记本 计算机,小米笔记本Pro 15
- 从零开始写一个武侠冒险游戏-4-第一次整合
- 自我评价中专计算机600作文,中专生毕业的自我评价(精选5篇)
- Java 类对象基础知识--科普
- 访问者模式的java语言_Java 设计模式系列(二三)访问者模式(Vistor)
- 阿里在职5年,一个女测试工师的坎坷之路
- python爬虫餐饮行业数据分析统计服_Python数据分析实战,简单快速制作餐饮行业商业化报告...
- web.xml根标签web-app规范
- 【Android】APP嵌入百度地图骑行导航一直初始化引擎失败解决办法
热门文章
- 从零开始学Pytorch(十七)之样式分格
- 小数加分数怎样计算讲解_反渗透阻垢剂的加药量怎样计算?
- VS恢复调试时出现的确认对话框
- thinkphp v5.0.11漏洞_thinkphp 5.0 代码执行漏洞
- c语言参数传入函数赋值后传出来,c语言第10次实验内容函数2邹显春.ppt
- 汇编语言ax=0c58ch,第4章89C5汇编语言程序设计.ppt
- 华为p50 pro 鸿蒙,华为P50Pro确认!1英寸大底相机+首发鸿蒙:这才是华为最强实力...
- edittext 无法输入内容_掌握其中1个Excel小技巧,你就不用再担心会重复录入内容了。...
- iso22000食品安全管理体系_食品安全管理体系ISO22000:2018澄清了两个层次PDCA的区别,具体怎么样的...
- android 透明主题 crash,Android 8.0 的填坑(透明的activity崩溃)