上一篇:(四)着色器和程序:https://www.jianshu.com/p/b1b4656cef9e
着色器是OpenGL ES 3.0 API的一个基础核心概念。每个OpenGL ES 3.0程序都需要且仅需要一个顶点着色器和一个片段着色器。
OpenGL ES着色语言基础知识
着色器的语法和C编程语言很相似,但是在版本规范和原生数据类型上有重大区别。
着色器版本规范
首先第一行必须定义着色器使用的OpenGL版本,我们使用OpenGL ES 3.0:
#version 300 es
变量和变量类型
计算机图形学中,两个基本数据类型组成了变换的基础:向量和矩阵。
着色器语言中存在基于标量、向量和矩阵的数据类型,如下图:
变量构造器
OpenGL ES着色语言在类型转换方面非常严格,不允许隐含类型转换。每种内建变量类型都有一组相关的构造器。
向量和矩阵分量
向量分量提取:
vec3 tmp = vec3(0.0, 1.0, 2.0);
// tmp.x = 0.0
// tmp.xyzx = {0.0, 1.0, 2.0 0.0}
矩阵分量提取:
mat3 tmp = mat3{ 0.0, 1.0, 2.0, // 第一列
3.0, 4.0, 5.0, // 第二列
6.0, 7.0, 8.0} // 第三列
// tmp[0] = {0.0, 1.0, 2.0}
// tmp[1][1] = 4.0
常量
可以将任何基本类型声明为常数变量。
const float pi =3.14159;
结构体
与C语言结构体类似:
// 定义
struct fogStruct {
vec4 color;
float start;
float end;
} fogVar;
// 赋值
fogVar = fogStruct(vec4(0.0, 1.0, 0.0, 0.0), // color
0.5, // start
2.0); // end
数组
与C语言数组类似:
// 浮点数数组
float floatArray[4];
// 向量数组
vec4 vecArray[2];
// -------------------
float a[4] = float[](1.0, 2.0, 3.0, 4.0);
float b[4] = float[4](1.0, 2.0, 3.0, 4.0);
vec2 c[2] = vec2[2](vec2(1.0), vec2(1.0));
运算符
着色器运算符和C语言的运算符一致:
函数
着色语言限定符有三种:in,inout和out:
函数定义示例,基本漫射光线的简单函数:
vec4 diffuse(vec3 normal,
vec3 light,
vec4 baseColor) {
return baseColor * dot(normal, light);
}
注意:OpenGL ES著色语言的函数不能递归。
内建函数
下面计算基本反射照明的着色器代码:
float nDotL = dot(normal, light);
float rDotV = dot(viewDir, (2.0 * normal) * nDotL - light);
float specular = specularColor * pow(rDotV, specularPower);
上述dot和pow都是OpenGL的内建函数。
控制流语句
主要包含判断逻辑和循环逻辑:
判断:
if(color.a < 0.25) {
color *= color.a;
} else {
color = vec4(0.0);
}
循环:
while(){
}
// =========
do{
} while();
统一变量
统一变量(uniform)是OpenGL ES着色语言中的变量类型限定符之一。
uniform mat4 viewProjMatrix;
统一变量的命名空间在顶点着色器和片段着色器中都是共享的。即,如果顶点着色器和片段着色器都链接到一个程序对象,它们就会共享同一组变量。
统一变量通常保存在硬件中,这个区域被称为“常量存储”,其大小固定,所以统一变量个数受限。可以通过函数:
gl_MaxVertexUniformVectors() // 获取最大顶点着色器数目
gl_MaxFragmentUniformVectors() // 获取最大片元顶点着色器数目
统一变量块
统一变量块比统一变量的优势:
1、统一变量缓冲区对象可以多个程序共享,但只需要设置一次;
2、统一变量缓冲区对象一般可以存储更大量的统一变量;
3、统一变量缓冲区对象之间切换比一次单加载一个统一变量更高效。
uniform TransformBlock {
mat4 matViewProj;
mat3 matNormal;
mat3 matTexGen;
};
统一变量块的内存布局限定符有shared、packed、std140、row_major和column_major:
默认方式是(shared, column_major):
layout(shared, column_major) uniform;
顶点和片段着色器输入/输出
顶点输入变量用于指定顶点着色器种每个顶点的输入,用in关键字指定,它们通常存储位置、法线、纹理和颜色这样的数据。
顶点着色器样板:
#version 300 es
uniform mat4 u_matViewProjection;
layout(location = 0) in vec4 a_position; // 顶点位置
layout(location = 1) in vec3 a_color; // 顶点颜色
out vec3 v_color;
void main(void) {
gl_Position = u_matViewProjection * a_position;
v_color = a_color;
}
具有匹配的输出/输入声明的顶点和片段着色器:
// 顶点着色器
#version 300 es
uniform mat4 u_matViewProjection;
// 顶点着色器输入
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec3 a_color;
// 顶点着色器输出
out vec3 v_color;
void main(void) {
gl_Position = u_matViewProjection * a_position;
v_color = a_color;
}
// ==============================
// 片元着色器
#version 300 es
precision mediump float;
// 来自顶点着色器的输入
in vec3 v_color;
// 片元着色器输出
layout(location = 0) out vec4 o_fragColor;
void main() {
o_fragColor = vec4(v_color, 1.0);
}
插值限定符
OpenGL ES 3.0主要包含2种插值器:
平滑着色:smooth
平面着色:flat
上述两种插值器还可以用质心采样:centroid来修饰。
预处理器和指令
预处理器:
类似C++预处理器,可以使用如下指令定义宏和条件测试:
#define
#undef
#if
#ifdef
#ifndef
#else
#elif
#endif
扩展行为指令:
extension指令用于启用和设置扩展行为。下述示例即希望预处理器在
NVIDIA阴影采样器立方体扩展不受支持时产生警告:
#extension GL_NV_shadow_samplers_cube : enable
统一变量和插值器打包
底层硬件种可用于每个变量存储的资源是固定的。我们采用打包规则来节省程序所需内存空间。
打包规则规定了插值器和统一变量映射到物理存储空间的方式,它基于物理存储空间被组织为一个存储位置4列和1行的网格的概念。举下面的统一变量块为例说明:
uniform mat3 m;
uniform float f[6];
uniform vec3 v;
未打包情况:
打包情况:
因为GPU通常会按照向量位置索引对常量存储进行所以。打包必须使数组跨越行边界。
了解打包很重要,这样才能编写再任何OpenGL ES 3.0实现上都不超过最小允许存储的可移植着色器。
精度限定符
着色器变量可以声明为低、中和高。
// highp 高精度
// mediump 中精度
// lowp 低精度
highp vec4 position;
也可以用精度限定符指定某种类型的精度:
precision highp float; // 所有float都是高精度 32bit
precision mediump float; // 所有float都是中精度 16bit
precision lowp float; // 所有float都是低精度 10bit
不变性
OpenGL ES着色器语言种引入了invariant关键字可以用于任何可变的顶点着色器输出,可以避免编译器可能进行导致指令重新排序的优化。
一旦某个输出变量声明了不变性,编译器便保证相同的计算和着色器输入条件下结果相同。
警告:慎用invariant,它回导致性能下降。
小结
上一篇:(四)着色器和程序:https://www.jianshu.com/p/b1b4656cef9e