概述
一、基础图形管线
渲染管线(rendering pipeline),它是一系列数据处理过程,并且将应用程序的数据转换到最终渲染的图像。下图是 OpenGL 4.3 版本的管线。
分步骤拆解
- 调用API填充顶点数组(要绘制的图形顶点信息)
- 传递到顶点着色器,将坐标信息转换为OpenGL 内部坐标信息
- 有了图形的坐标信息后,OpenGL 将坐标信息装配为基本的几何图形
- OpenGL 通过默认的几何着色器对上一步的图形进行处理
- 对上一步的几何图形进行光栅化,所谓的光栅化其实是把线性的几何图形映射到一个一个的像素上
- 通过片段着色器给每一个像素赋予颜色
- 最后将图像传递到帧缓冲(Framebuffer)中提供给屏幕进行刷新操作。
OpenGL 中的 图元 只不过是顶点的集合以预定义的方式结合在一起罢了。
OpenGL渲染管线简化流程图:
上面图片引用自:OpenGL学习(三)-- OpenGL 基础渲染 - 掘金
客户端-服务器
管线上半部分是客户端,下半部分是服务器.就 OpenGL 而言,客户端是存储在 CPU 存储器中的,驱动程序将渲染命令与数据组合起来发给服务器执行。
服务器和客户端在功能上是异步的。
客户端不断的将数据和命令组合在一起送入缓冲区,缓冲区再发送到服务器执行。
Opengl定义:
OpenGL 着色语言(OpenGL Shading Language)是用来在OpenGL中着色编程的语言,也即开发人员写的短小的自定义程序, 它们在图形卡的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程型。
比如:视图转换、投影转换等。
GLSL(GL Shading Language)的着色器代码分成2个部分:
Vertex Shader(顶点着色器)和Fragment(片断着色器),有时还会有Geometry Shader(几何着色器).
notes: opengl 在GPU运行
输入顶点数据==>化成三角形==>填充小方块==>片元着色器着色
notes:以载入图片为例:传入顶点坐标,opengl根据坐标,依次画三角形,进而画出图片.
顶点坐标确定图片位置(不需要给出整个图片的所有坐标)
每个三角形在光栅化的时候变成马赛克, 片元着色器取图片的颜色,比如高斯滤波等操作着色
着色器定义和实例
- 顶点着色器:
是一个可编程单元,执行顶点变换、纹理坐标变换、光照、材质等顶点的相关操作,每顶点执行一次.在图像处理中,有 4 个顶点:每一个顶点代表图像的一个角。顶点着色器设置顶点的位置,并且把位置和纹理坐标这样的参数发送到片段着色器。
下面是GPUImage中一个顶点着色器实例分析:
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
gl_position = position;
textureCoordinate = inputTextureCoordinate.xy;
}
attribute:是只能在顶点着色器中使用的变量.来表示一些顶点的数据,如:顶点坐标,法线,纹理坐标,顶点颜色等。
varying变量:是vertex和fragment shader之间做数据传递用的。一般vertex shader修改varying变量的值,然后fragment shader使用该varying变量的值。因此varying变量在vertex和fragment shader二者之间的声明必须是一致的。
uniforms变量(一致变量):用来将数据值从应用程序传递到顶点着色器或者片元着色器
attribute vec4 position:
position变量是我们在程序中传给Shader的顶点数据的位置,是一个矩阵,规定了图像4个点的位置,并且可以在shader中经过矩阵进行平移、旋转等再次变换。
在GPUImage中:
1) GLSurfaceView的大小、PreviewSize的大小实现计算出矩阵
2) 通过glGetAttribLocation获取id,再通过glVertexAttribPointer将矩阵传入。新的顶点位置通过在顶点着色器中写入gl_Position传递到渲染管线的后继阶段继续处理。
3) 结合后面绘制过程中的glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);首先选取第三个点,与前两个点绘制成一个三角形,再选取最后一个点,与第二、第三个点绘制成三角形,最终绘制成多边形区域.
notes: opengl 只能画点,线,三角形
attribute vec2 inputTextureCoordinate;
inputTextureCoordinate是纹理坐标,纹理坐标定义了图像的哪一部分将被映射到多边形。如图所示,下图是OpenGL纹理坐标系统,左下角为原点,
传入此坐标,代表输出图像不会经过变换,在GPUImage中,因为输出图像与应用方向关系,需要将图像旋转90度,即坐标为
<span style="font-size:10px;"> public static final float TEXTURE_ROTATED_90[] = {
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 0.0f,
};</span>
notes:没看懂why是旋转90度????
varying vec2 textureCoordinate
因为顶点着色器负责和片段着色器交流,所以我们需要创建一个变量和它共享相关的信息。在图像处理中,片段着色器需要的唯一相关信息就是顶点着色器现在正在处理哪个像素。
gl_Position = position;
gl_Position是用来传输投影坐标系内顶点坐标的内建变量,GPUImage在Java层已经变换过,在这里不需要经过任何变换。
textureCoordinate = inputTextureCoordinate.xy;
取出这个顶点中纹理坐标的 X 和 Y 的位置(仅需要这两个属性),然后赋值给一个将要和片段着色器通信的变量。到此,顶点着色器建立完毕。
- 片段着色器:
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
片段着色器和顶点着色器会成对出现。
片段着色器扮演着显示的角色。我们的滤镜处理大部分都在片段着色器中进行。上段代码是一个无滤镜效果的片段着色器。
varying highp vec2 textureCoordinate;
对应顶点着色器中变量名相同的变量,片段着色器作用在每一个像素上,我们需要一个方法来确定我们当前在分析哪一个像素/片段。它需要存储像素的 X 和 Y 坐标。我们接收到的是当前在顶点着色器被设置好的纹理坐标。
uniform sampler2D inputImageTexture;
uniforms变量(一致变量): 用来将数据值从应用程其序传递到顶点着色器或者片元着色器。该变量有点类似C语言中的常量(const),即该变量的值不能被shader程序修改。sampler2D对应2D纹理,在GPUImage中,与onPreviewFrame中经过变换过的RGB数据绑定。GPU将从该纹理中取出点进行处理。
gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
这是我们碰到的第一个 GLSL 特有的方法:texture2D,顾名思义,创建一个 2D 的纹理。它采用我们之前声明过的属性作为参数来决定被处理的像素的颜色。这个颜色然后被设置给另外一个内建变量,gl_FragColor。因为片段着色器的唯一目的就是确定一个像素的颜色,gl_FragColor 本质上就是我们片段着色器的返回语句。一旦这个片段的颜色被设置,接下来片段着色器就不需要再做其他任何事情了,所以你在这之后写任何的语句,都不会被执行。
notes: textureCoordinate=>inputImageTexture=>gl_FragColor
到此为止,我们的Shader就写完了。
补充:每一个Shader程序都有一个main函数
vec2:包含了2个浮点数的向量;vec3:包含了3个浮点数的向量;vec4:包含了4个浮点数的向量
sampler1D:1D纹理着色器;sampler2D:2D纹理着色器;sampler3D:3D纹理着色器
mat2:2*2维矩阵;mat3:3*3维矩阵 ;mat4:4*4维矩阵
上文代码中还使用到了OpenGL的几个全局变量:
gl_Position:原始的顶点数据在Vertex Shader中经过平移、旋转、缩放等数学变换后,生成新的顶点位置(一个四维 (vec4) 变量,包含顶点的 x、y、z 和 w 值)。新的顶点位置通过在Vertex Shader中写入gl_Position传递到渲染管线的后继阶段继续处理。
gl_FragColor:Fragment Shader的输出,它是一个四维变量(或称为 vec4)。gl_FragColor 表示在经过着色器代码处理后,正在呈现的像素的 R、G、B、A 值。
Vertex Shader是作用于每一个顶点的,如果Vertex有三个点,那么Vertex Shader会被执行三次。Fragment Shader是作用于每个像素的,一个像素运行一次。从源代码中可以看出,像素的转换在Fragment Shader中完成。
在网上看到两张图可以很好地说明Vertex Shader和Fragment Shader的作用:
Vertex Shader(顶点着色器):主要是传入相应的Attribute变量、Uniforms变量、采样器以及临时变量,最后生成Varying变量,以及gl_Posizion等变量。
Fragment Shade(片元着色器):可以执行纹理的访问、颜色的汇总、雾化等操作,最后生成gl_FragColor变量。
有高手总结如下:“vsh负责搞定像素位置,填写gl_Posizion;fsh负责搞定像素外观,填写 gl_FragColor"
在实际程序例如GPUImage中,操作顺序如下
5) 初始化Shader
初始化Shader的步骤比较多,主要可以分为3步:创建Shader,创建Program,初始化Texture。
(1) 创建一个Shader对象
1)编写Vertex Shader和Fragment Shader源码。
2)创建两个shader 实例 。
3)给Shader实例指定源码。
4)在线编译shaer源码。
(2) 创建一个Program对象
1)创建program。
2)绑定shader到program。
3)链接program。
4)使用porgram。
(3) 初始化Texture。可以分为以下步骤。
1)定义定点数组
2)设置顶点数组
3)初始化纹理
1.创建shader
1)编写Vertex Shader和Fragment Shader源码。
2)创建两个shader 实例:GLuint glCreateShader(GLenum type);
3)给Shader实例指定源码。 glShaderSource
4)在线编译shaer源码 void glCompileShader(GLuint shader)
public static int loadShader(final String strSource, final int iType) {
int[] compiled = new int[1];
int iShader = GLES20.glCreateShader(iType);
GLES20.glShaderSource(iShader, strSource);
GLES20.glCompileShader(iShader);
GLES20.glGetShaderiv(iShader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.d("Load Shader Failed", "Compilationn" + GLES20.glGetShaderInfoLog(iShader));
return 0;
}
return iShader;
}
2.创建program
在OpenGL ES中,每个program对象有且仅有一个Vertex Shader对象和一个Fragment Shader对象连接到它.
Shader类似于C编译器。Program类似于C链接器。glLinkProgram操作产生最后的可执行程序,它包含最后可以在硬件上执行的硬件指令。
1)创建program : GLuint glCreateProgram(void)
2)绑定shader到program : void glAttachShader(GLuint program, GLuint shader)。每个program必须绑定一个Vertex Shader 和一个Fragment Shader。
3)链接program : void glLinkProgram(GLuint program)
4)使用porgram : void glUseProgram(GLuint program)
3. 获取纹理坐标、顶点坐标、纹理等对应id
通过glGetAttribLocation和glGetUniformLocation获取对应的id
mGLAttribPosition = GLES20.glGetAttribLocation(mGLProgId, "position");
mGLUniformTexture = GLES20.glGetUniformLocation(mGLProgId, "inputImageTexture");
mGLAttribTextureCoordinate = GLES20.glGetAttribLocation(mGLProgId,
"inputTextureCoordinate");
4.绘制
1)首先设置背景颜色和绘制创建绘制区域、清理当前缓冲区
2)使用program(glUseProgram),传递两个矩阵
3)通过glGenTextures(GLsizei n,GLuint *textures)产生你要操作的纹理对象的id,
然后通过glBindTexture绑定并获取纹理id,告诉OpenGL下面对纹理的任何操作都是对它所绑定的纹理对象的.
比如:glBindTexture(GL_TEXTURE_2D,1)告诉OpenGL下面代码中对2D纹理的任何设置都是针对索引为1的纹理的。
通过glTexParameteri设置一些属性。
最后通过glTexImage2D根据指定参数,包括RGB数据,生成2D纹理。当第二帧绘制的时候,则不需要重新绑定纹理,使用glTexSubImage2D更新现有纹理即可。
public static int loadProgram(final String strVSource, final String strFSource) {
int iVShader,iFShader,iProgId;
int[] link = new int[1];
iVShader = loadShader(strVSource, GLES20.GL_VERTEX_SHADER);
iFShader = loadShader(strFSource, GLES20.GL_FRAGMENT_SHADER);
iProgId = GLES20.glCreateProgram();
GLES20.glAttachShader(iProgId, iVShader);
GLES20.glAttachShader(iProgId, iFShader);
GLES20.glLinkProgram(iProgId);
GLES20.glGetProgramiv(iProgId, GLES20.GL_LINK_STATUS, link, 0);
...
}
public static int loadTexture(final IntBuffer data, final Size size, final int usedTexId) {
int textures[] = new int[1];
if (usedTexId == NO_TEXTURE) {
GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, size.width, size.height,0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data);
} else {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, usedTexId);
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, size.width,
size.height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data);
textures[0] = usedTexId;
}
return textures[0];
}
4)然后使用函数glActiveTexture()来指定要对其进行设置的纹理单元,这里为GL_TEXTURE0,使用glBindTexture再次绑定,通过glUniform1i复制,最后glDrawArrays绘制。
补充1:
VBO和FBO 这两个都是Buffer Object,说白了就是一块存储区域。
VBO(顶点缓冲对像)(一次性cpu--gpu)是用来加快渲染的速度,主要思想是在显卡中分配一块显存空间,然后一次性将CPU中的数据传输过来,这样显示的时候就会直接输出到显示器,这样做的动机是因为CPU到GPU传输的瓶颈。
FBO(帧缓冲对像)(一次性gpu--屏幕)是用来改变当前帧缓存的输出位置,默认的帧缓存会直接输出到显示器,而有的时候不需要输出到显示器,在后台渲染就可以,这个技术叫离屏渲染,渲染完之后再用该纹理进行操作,最终再以某种形式输出到屏幕,其实类似于以前用的双缓冲显示技术。
vbo,fbo使用:https://www.jianshu.com/p/a310a1a16c68
vbo 创建
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glDrawArrays(GL_TRIANGLES, 0, 3);
1) 使用glGenBuffers函数和一个缓冲ID生成一个VBO对象,
2) 使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
3) 调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中
4) glDrawArrays来进行渲染顶点数据
FBO
1、创建FBO
GLES20.glGenBuffers(1, fbos, 0);
2、绑定FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbos[0]);
3、设置FBO分配内存大小,第一个参数表示二级纹理,第三个参数表示颜色模式,四五俩个参数代表屏幕宽高,可以与实际不一致,其他自行查阅
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, 720, 1280, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
4、把纹理绑定到FBO
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureid, 0);
5、检查FBO绑定是否成功
GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE)
6、解绑FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
补充2:坐标系用于3D,比如:投影等
OpenGL学习(五)-- 裁剪与混合 - 掘金
//拉伸的原理是多画4个顶点,修改载入图片的原理就是修改坐标点
应用实例: 图片根据顶点坐标拉伸:OpenGL 13 - 案例:纹理图片拉伸与保存 - 张张_z - 博客园
旋转:OpenGL ES入门:滤镜篇 - 漩涡、马赛克 | 码农网
Android OpenGL ES - 反相、曝光、对比度、饱和度、色调滤镜 - SegmentFault 思否
glsl 内置数学函数 - c_dragon - 博客园
OpenGL ES入门09-GLSL实现常见特效 - 简书
【Android 音视频开发打怪升级:OpenGL渲染视频画面篇】三、OpenGL渲染多视频,实现画中画 - 简书
补充:显示
如果使用“双缓冲”方式,使用glutSwapBuffers()绘制.如果使用“单缓冲”方式的话,使用glFlush()绘制.
glutSwapBuffers()的功能是交换两个缓冲区指针,表现的形式即是把画面呈现到屏幕上。
简单解释一下双缓冲技术。当我们进行复杂的绘图操作时,画面便可能有明显的闪烁。这是由于绘制的东西没有同时出现在屏幕上而导致的。使用双缓冲可以解决这个问题。
所谓双缓冲技术, 是指使用两个缓冲区: 前台缓冲和后台缓冲。前台缓冲即我们看到的屏幕,后台缓冲则在内存当中,对我们来说是不可见的。每次的所有绘图操作不是在屏幕上直接绘制,而是在后台缓冲中进行, 当绘制完成时,再把绘制的最终结果显示到屏幕上
实例:最简单的视音频播放示例6:OpenGL播放YUV420P(通过Texture,使用Shader)_雷霄骅的博客-CSDN博客_texture yuv
最后
以上就是笑点低电脑为你收集整理的Opengl基础一、基础图形管线的全部内容,希望文章能够帮你解决Opengl基础一、基础图形管线所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复