一、说明
我们除了对GLSL语言本身的理解,对真正的宏观管线认知是越早越好的事情。理解着色器的能力,以及使用局限性能够迫使我们修正设计思路。也许我们胸中有无限多的想象,一旦面对着色器的无能为力,精妙想向也就灰飞叶湮灭。有时需要引进其它的环节,但又不能确定是否用着色器本身就能完成?种种困局需要我们扎实理解渲染管线和着色器原理,然后才能决定采取何种技术路线。
二、管线原理
2.1 渲染管线总览
也称渲染管线,因为 OpenGL ES在渲染处理过程中会顺序执行一系列操作,这一系列相关的处理阶段就被称为OpenGL ES 渲染管线。OpenGL ES 渲染过程就如流水线作业一样,这样的实现极大地提高了渲染的效率。如图就是 OpenGL ES 的管线图,学习OpenGL ES 就是学习这张图中的每一个部分。
图中阴影部分的 Vertex Shader(顶点着色器) 、geography Shader(几何着色器)、和 Fragment Shader(片元着色器) 是可编程管线 。它们三个阶段是用户可编程的。这种编程类似于回调函数,在一个GPU的处理流程中,将用户的“私货”夹带进去。每个可编程阶段的编程语言是GLSL类C语言。
因为渲染管线是定制好的流水线作业,用户用“回调函数”的方法改变作业计划,这种改变是极大受约束的编程,为了提早了解这种约束的细节,这里先简介数据流程,和各个着色器的角色。
2.2 渲染管线的着色器
2.2.1 数据描述对象
被渲染的事物有两种,一种叫独立对象,一种叫点缀对象,这两种对象的区别如下:
- 独立物:在游戏画面中必须详细交待细节的物体。
- 点缀物:游戏画面中,数量庞大的,细节不重要的,有时无足轻重的对象。
这其实很容易理解,如果有漫天的繁星,那它们就是点缀物,如果针对一个星体比如火星去研究,那就是独立物。点缀物数据简单细节少,独立物数据复杂。
因此,数据buffer的设计,以及buffer内数据的刷新是有区别的。另外,对几何着色器的需求也是不同的。
2.2.2 顶点着色器的输入和输出
顶点着色器:输入是顶点,输出是图元。
几何着色器:输入图元输出图元。
片段着色器:输入图元,输出像素集合
如图;
1)顶点着色器
2)几何着色器
3)片段着色器
三、Vertex Shader(顶点着色器)
-
如图,顶点着色器分为输入和输出两部分,负责的功能是把输入的数据进行矩阵变换位置,计算光照公式生成逐顶点颜⾊,⽣成/变换纹理坐标.并且把位置和纹理坐标这样的参数发送到片段着色器.
-
输入参数介绍:
1.着色器程序(Shader Program,图中没有画出):由 main 申明的一段程序源码或可执行文件,描述在顶点上执行的操作:如坐标变换、计算光照公式产生每个顶点颜色、计算纹理坐标。2.属性(Attribute):由 vertext array 提供的顶点数据,如空间位置,法向量,纹理坐标以及顶点颜色,属性可以理解为针对每一个顶点的输入数据。属性只在顶点着色器中才有,片元着色器中没有属性。
3.统一值(Uniforms): Uniforms保存由应用程序传递给着色器的只读常量数据。在顶点着色器中,这些数据通常是变换矩阵,光照参数,颜色等。由 uniform 修饰符修饰的变量属于全局变量,该全局性对顶点着色器与片元着色器均可见,也就是说,这两个着色器如果被连接到同一个应用程序中,它们共享同一份 uniform 全局变量集。因此如果在这两个着色器中都声明了同名的 uniform 变量,要保证这对同名变量完全相同:同名+同类型,因为它们实际是同一个变量。
4.采样器(Samplers): 一种特殊的 uniform,用于呈现纹理。sampler 可用于顶点着色器和片元着色器。
-
输出参数介绍:
1.可变变量(Varying):varying 变量用于存储顶点着色器的输出数据,也存储片元着色器的输入数据。varying 变量会在光栅化处理阶段被线性插值。顶点着色器如果声明了 varying 变量,它必须被传递到片元着色器中才能进一步传递到下一阶段,因此顶点着色器中声明的 varying 变量都应在片元着色器中重新声明为同名同类型的 varying 变量。-
gl_Position:在顶点着色器阶段至少应输出位置信息-即内建变量
-
gl_FrontFacing:为back-face culling stage阶段生成的变量,无论精选是否被禁用,该变量都会生成。
-
gl_PointSize:点大小。
-
-
示例代码
// 位置属性
attribute vec4 position;
// 坐标属性
attribute vec2 textCoordinate;
// 旋转角度
uniform mat4 rotateMatrix;
// 输出变量
varying lowp vec2 varyTextCoord;
// 着色器程序(Shader Program)
void main()
{
// 赋值坐标属性到输出变量
varyTextCoord = textCoordinate;
// 位置乘以旋转矩阵
vec4 vPos = position;
vPos = vPos * rotateMatrix;
// 赋值位置到内建变量gl_Position上,作为输出信息(必须)
gl_Position = vPos;
}
四、Fragment Shader(片元着色器)
片元着色器
片元着色器的作用是处理由光栅化阶段生成的每个片元,最终计算出每个像素的最终颜色。归根结底,实际上就是数据的集合。这个数据集合包含每一个像素的各个颜色分量和像素透明度的值。
-
输入参数介绍:
1.着色器程序(Shader program): 由 main 申明的一段程序源码,描述在片元上执行的操作。2.可变变量(Varyings): 顶点着色器阶段输出的 varying 变量在光栅化阶段被线性插值计算之后输出到片元着色器中作为它的输入,即上图中的 gl_FragCoord,gl_FrontFacing 和 gl_PointCoord。
3.统一值(Uniforms): 用于片元着色器的常量,如雾化参数,纹理参数等。
4.采样器(Samples): 一种特殊的 uniform,用于呈现纹理。
-
输出参数介绍:
1.gl_FragColor: 在顶点着色器阶段只有唯一的 varying 输出变量-即内建变量gl_FragColor。 -
示例代码
// 纹理坐标
varying lowp vec2 varyTextCoord;
// 采样器
uniform sampler2D colorMp;
// 着色器程序(Shader program)
void main()
{
// 读取纹素(纹理的颜色)放到输出变量gl_FragColor上
gl_FragColor = texture2D(colorMap,varyTextCoord);
}
五、几何着色器
在顶点和片段着色器之间有一个可选的着色器,叫做几何着色器(Geometry Shader)。几何着色器以一个或多个表示为一个单独基本图形(primitive)的顶点作为输入,比如可以是一个点或者三角形。几何着色器在将这些顶点发送到下一个着色阶段之前,可以将这些顶点转变为它认为合适的内容。几何着色器有意思的地方在于它可以把(一个或多个)顶点转变为完全不同的基本图形(primitive),从而生成比原来多得多的顶点。
我们直接用一个例子深入了解一下:
#version 330 core
layout (points) in;
layout (line_strip, max_vertices = 2) out;
void main() {
gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
EmitVertex();
gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);
EmitVertex();
EndPrimitive();
}
每个几何着色器开始位置我们需要声明输入的基本图形(primitive)类型,这个输入是我们从顶点着色器中接收到的。我们在in关键字前面声明一个layout标识符。这个输入layout修饰符可以从一个顶点着色器接收以下基本图形值:
这是我们能够给渲染函数的几乎所有的基本图形。如果我们选择以GL_TRIANGLES绘制顶点,我们要把输入修饰符设置为triangles。括号里的数字代表一个基本图形所能包含的最少的顶点数。
当我们需要指定一个几何着色器所输出的基本图形类型时,我们就在out关键字前面加一个layout修饰符。和输入layout标识符一样,输出的layout标识符也可以接受以下基本图形值:
- points
- line_strip
- triangle_strip
使用这3个输出修饰符我们可以从输入的基本图形创建任何我们想要的形状。为了生成一个三角形,我们定义一个triangle_strip作为输出,然后输出3个顶点。
几何着色器同时希望我们设置一个它能输出的顶点数量的最大值(如果你超出了这个数值,OpenGL就会忽略剩下的顶点),我们可以在out关键字的layout标识符上做这件事。在这个特殊的情况中,我们将使用最大值为2个顶点,来输出一个line_strip。
这种情况,你会奇怪什么是线条:一个线条是把多个点链接起来表示出一个连续的线,它最少有两个点来组成。每个后一个点在前一个新渲染的点后面渲染,你可以看看下面的图,其中包含5个顶点:
上面的着色器,我们只能输出一个线段,因为顶点的最大值设置为2。
为生成更有意义的结果,我们需要某种方式从前一个着色阶段获得输出。GLSL为我们提供了一个内建变量,它叫做gl_in
,它的内部看起来可能像这样:
in gl_Vertex
{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[];
} gl_in[];
这里它被声明为一个接口块(interface block,前面的教程已经讨论过),它包含几个有意思的变量,其中最有意思的是gl_Position
,它包含着和我们设置的顶点着色器的输出相似的向量。
要注意的是,它被声明为一个数组,因为大多数渲染基本图形由一个以上顶点组成,几何着色器接收一个基本图形的所有顶点作为它的输入。
更详细的几何着色器内容,请看专题讲述:xxxxxxxxxxx
六、GPU的并行模式
以三个顶点输入去解释GPU的并行操作方式,CPU传入了3个顶点到GPU,GPU将这三个顶点,传递给三个顶点着色器。
这里要意识到,顶点着色器开始,就是并行处理了。GPU是很强大的SIMD架构(单指令流多数据流)。
如果我们自定义了一段顶点着色器代码,则三个顶点着色器会同时运行这段代码。(后面的片段着色器代码,就是N个顶点同时运行)
顶点着色器进行处理,传递给图元装配。
图元装配阶段,进行了顶点扩充,变成N个点,N看作三角形面积所在的点。
之后N个点依次传给 几何着色器->光栅化->片段着色器,最后经过测试与混合后,输出到屏幕。
可以自定义编程的,有顶点着色器、几何着色器、片段着色器(有的地方也叫像素着色器),顺带提一下,还有另外三种:曲面控制着色器、曲面评估着色器 和 计算着色器。
一般我们的关注点,都会在片段着色器上。
七、结论
就渲染本身需要从大背景下思考认知和设计,我们注重着色器,关键要了解它们的性能;设计一种渲染,哪些环节需要在哪些着色器去处理,这是设计人员的本职任务。除了对着色器进行关注,同时需要对buffer的认知和设计,以及对CPU的流水性能都有一个深刻理解,才能恰到好处地处理渲染作品。谢谢阅读!
标签:输出,变量,OpenGL,片元,顶点,gl,着色器 From: https://blog.csdn.net/gongdiwudu/article/details/136695591