OpenGL--GLSL基础

Shader简介

首先简单介绍一下Shader是什么,Shader意为着色器,只是整个图形渲染管线流程中的某几个单元,也可以理解为图形渲染管线中的几处会被执行的代码,Shader主要包括:顶点Shader,片段Shader以及几何Shader这3种。

那么顶点,片段和几何分别是什么呢?顶点很好理解,如要绘制一张图片,一般是一个由四个顶点组成的图形。就是我们读书那会几何学里的顶点一个意思。

片段简单点理解就是要绘制这张图片的每一个像素。(这种叫法一般指在渲染管线的着色阶段的像素,片段和像素是有区别的,如在重叠的情况下,一个像素上可以渲染多个片段)

几何指的是要绘制的形状,同样的4个点,可以组成两个三角形、一个四边形或者两条线。

我们通常说的图形渲染其实就是一个固定流水线流程,即一张图片从无到有怎么显示到显示器上的一个过程。Shader就是这个过程中我们程序员唯一能动态操作改变的部分。操作的桥梁就是语言了,DirectX中是HLSL,OpenGL中使用的是GLSL语言来编写Shader脚本,可以写出各种炫酷的效果~

图形渲染管线

下面我们来大致讲讲图形渲染管线流程的具体的几个步骤:
1.准备好顶点数据和顶点连通性数据。顶点数据包括顶点的位置、颜色、纹理坐标、法线等数据,而顶点连通性数据则是描述顶点之间如何连接组合的数据,如这边两个顶点是连成一条线,这边三个顶点组成一个三角形。

2.将第一步获取的顶点数据进行处理,包括顶点位置变换,顶点光照计算,生成纹理坐标等操作,处理完后输出到下一步。(顶点着色器负责阶段

3.根据上面提供的顶点数据和顶点连通性数据,将点连成线,线连成面。把零零散散的顶点组装成一个一个的图元,并且将图元进行栅格化,即处理成一个一个的像素格子,并且决定了每个像素格子的位置,这就是我们说的片段。
被确定的除了片段的位置之外,还可能包含片段的颜色,OpenGL会根据图元各个顶点的颜色,纹理坐标进行插值,得出每个片段的颜色插值。这个阶段还会对不在视口范围中的内容进行裁剪背面剔除操作也会在这个阶段执行。(几何着色器负责阶段,这个我们一般不会用到,因为一般不需要我们再去实现一套新的图元组装规则)

4.对第三步输出的片段进行处理,包括对颜色处理,或者丢弃片段,雾化也是在这个阶段执行的。(片段着色器负责阶段

5.为即将呈现到屏幕上的内容进行最后的处理,即对每个片段进行一系列的测试

  • 裁剪测试,激活GL_SCISSOR_TEST时开启,判断是否在设定的裁剪窗口中,如是则通过,否则不渲染当前片段。
  • Alpha测试,激活GL_ALPHA_TEST时开启,判断当前片段的透明度是否符合glAlphaFunc设定的条件(如透明度大于0.5),如是则通过,否则不渲染当前片段。
  • 模版测试,激活GL_STENCIL_TEST时开启,判断当前片段模版值与模版缓冲区中对应的模版值是否符合glStencilFunc()函数设定的条件,如是则通过,否则不渲染当前片段。
  • 深度测试,激活GL_DEPTH_TEST时开启,用于判断片段的前后遮挡关系,判断当前片段的深度值和深度缓冲区中对应的值是否符合glDepthFunc()函数设定的条件,如是则通过,否则不渲染当前片段。

最后,如果测试都通过了的话,会根据当前的混合模式将片段更新到帧缓冲区里对应的像素中,当屏幕刷新时,帧缓冲区的内容会被渲染到屏幕上。这就是我们看到一个图像如何显示到屏幕上的整个过程。

GLSL基础语法

下面我们看看怎么编写Shader脚本,GLSL语法类似C语言,顶点着色器和片段着色器都是使用GLSL编写的。
一:数据类型和变量
1.基础类型
GLSL支持以下三种基本数据类型:float、int、bool
需要特别注意的是,假设有一个float变量,在着色器中将其赋值为1,在一些显卡上可能会崩溃!因为1是整数不是浮点数,所以需要将1.0赋给浮点型变量。可以这样使用基本类型变量:

int a, b;
float c = 3.0;
bool d = true;

2.向量类型
上面3种基本数据类型分别对应3种向量:
vec2、vec3、vec4:浮点型向量,可以存储2~4个浮点数。
ivec2、ivec3、ivec4:整数向量。
bvec2、bvec3、bvec4:布尔型向量。
向量的操作是很灵活的,可以使用下标来访问向量的分量,也可以使用x、y、z、w字段来访问向量的成员,使用r、g、b、a字段可以访问颜色向量,使用s、t、p、q字段可以访问纹理坐标向量。如果要访问向量的x、y、z3个字段,还可以连续使用字段。下面来代码演示下:

vec2 a = vec2(1.0, 2.0);
vec3 b = vec3(3.0, 4.0, 5.0);
vec3 color = b.rgb;    //使用rgb来操作b的三个分量
float c = a[0];        //使用下标的方式来访问a的第一个变量
vec3 d = vec3(a, c);   //使用一个vec2和一个float初始化vec3
float e = float(d);    //d的x分量被赋值给了e

3.矩阵类型
GLSL提供了2✖️2,3✖️3,4✖️4共3种矩阵类型,分别使用mat2,mat3,mat4表示。此外还支持2~4✖️2~4的任意矩阵,如mat3✖️2、mat4✖️3等。用法和向量类似。

4.采样器
GLSL提供了一组采样器,这是一种用于访问纹理的特殊类型,在读取纹理值时会用到,主要有下面几种采样器。

  • sampler1D:一维纹理采样器
  • sampler2D:二维纹理采样器
  • sampler3D:三维纹理采样器
  • samplerCube:立方体映射纹理采样器
  • sampler1DShadow:一维阴影映射采样器
  • sampler2DShadow:二维阴影映射采样器

5.数组和结构体
在GLSL中,可以像C语言一样声明和访问数组,但不能在声明时直接初始化数组,数组下标从0开始,如:

int a[3];
a[0] = 1;

GLSL还可以定义结构体,定义和初始化如下:

struct xxx
{
    vec3 pos;
    vec3 color;
};

xxx st1;
xxx st2 = xxx(vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 0.0));
st2.pos = vec3(0.0, 1.0, 0.0);

二:操作符

=<+-*/&&||.等等这些基本和c语言操作符一致,不作过多介绍。

三:变量修饰符
变量修饰符放在变量类型之前,用于修饰变量,GLSL常用的有以下变量修饰符。

  • const:编译时期的常量,不可被修改
  • attribute:属性变量
  • uniform:统一变量
  • varying:易变变量

属性变量和统一变量都是只读的全局变量(不能定义在函数内),都是由OpenGL应用程序设置的变量,即值由底层操作,我们只需定义使用。
属性变量是针对每个顶点的数据,所以只能在顶点着色器中定义,一般会将顶点的颜色、位置、法线等顶点数据作为属性变量传递给顶点着色器。
统一变量是针对整个图元的数据,既可以在顶点着色器中使用,也可以在片段着色器中使用。我们把统一变量作为中间媒介,通过统一变量传递应用程序中的变量给着色器使用

易变变量是着色器中最神奇的变量,也最不容易理解。主要用于存储插值数据,必须同时在顶点着色器和片段着色器中定义同一个易变变量,在顶点着色器中写入易变变量的值,然后在片段着色器中获取易变变量的值(片段着色器中,易变变量是只读的)。
在顶点着色器中写入顶点的插值,然后到了插值计算阶段会根据这些顶点的插值会进行插值运算,计算出顶点之间片段的值。简单的讲就是对数据进行插值计算。

四:语句与函数
GLSL语句和函数基本和c语言一致,支持if-else,for,while,do-while语句,同时支持break和continue关键字。在片段着色器中,还可以使用discard语句来丢弃当前片段。
着色器程序由函数组成,每个着色器都需要有一个main()函数,和c一样这是程序的入口。
函数返回值可以是任意类型,但不能是数组。函数参数有3种修饰符,分别是in,out,inout,分别表示输入、输出、既输入又输出。没有指定默认为in。函数还允许重载。

bool areyouhappy(in float money)
{
    if(money < 1000000000.0)
        return false;
    else
        return true;
}

五:Shader简单示例
简单的颜色Shader脚本:

//顶点Shader
//输入顶点和颜色数据
attribute vec4 vVertex;
attribute vec4 vColor;
//输出颜色插值到vVaryingColor中
varying vec4  vVaryingColor;
void main()
{
    vVaryingColor = vColor;
    gl_Position = vVertex;
}
//片段Shader
//拿到经过插值后的vVaryingColor,将它设置到最终输出的gl_FragColor变量中
varying vec4 vVaryingColor;
void main()
{
    gl_FragColor = vVaryingColor;
}

顶点着色器最主要的职责就是计算位置,所以顶点着色器至少需要写入一个变量gl_Position,这个是GLSL内置的位置变量,需要将变换后的顶点坐标赋予该变量。

片段着色器主要职责是计算片段颜色,所以需要将片段最终颜色写入内置的gl_FragColor变量中(或者将该片段抛弃)。

猜你喜欢

转载自blog.csdn.net/u010223072/article/details/80470998