GLSL-WebGL shader language syntax detailed explanation

GLSL syntax

GLSL is a strongly typed language, and every sentence must have a semicolon. Its syntax is quite similar to typescript.
The comment syntax of GLSL is the same as JS, and the variable name rules are also the same as JS. Keywords, reserved words, and cannot start with gl_, webgl_ or webgl . The operators are basically the same as JS, ++ – += && || and ternary operators are all supported.
There are three main data value types in GLSL, floating point, integer, and boolean. Note that floating point numbers must have a decimal point. Type conversion can be done directly using float, int and bool functions. For example, the integer 1 is converted into a floating point number 1.0 as follows.

float f = float(1);

In addition, the syntax for defining variables in GLSL is <type> <variable name>, and the value assigned to the variable should be consistent with the type of the defined variable. If the above code writes float f = 1;, an error will be reported directly, because 1 is Integer, but adding a decimal point will turn it into a floating point number, float f = 1.; If you write it this way, no error will be reported.
Languages ​​such as JavaScript and C are usually executed on the CPU, while shader languages ​​are usually executed on the GPU. The execution environments are different, so the syntax design will naturally be somewhat different.

basic type:
type illustrate
void Empty type, that is, does not return any value
bool Boolean type true, false
int signed integer signed integer
float signed floating point number floating scalar
thing2, thing3, thing4 n-component floating point vector
bvec2, bvec3, bvec4 n-dimensional Boolean vector Boolean vector
ivec2, ivec3, ivec4 n-dimensional integer vector signed integer vector
mat2, mat3, mat4 2x2, 3x3, 4x4 floating point matrix float matrix
sampler2D 2D texturea 2D texture
samplerCube cube mapped texture
Basic structures and arrays:
type illustrate
structure struct type-name{} is similar to the structure in C language
array float foo[3] glsl only supports 1-dimensional arrays, and arrays can be members of structures
Component access of vectors:

Vectors (vec2, vec3, vec4) in glsl often have special meanings. For example, they may represent a spatial coordinate (x, y, z, w), or a color (r, g, b, a), and then Or represents a texture coordinate (s, t, p, q), so glsl provides some more user-friendly component access methods.

vector.xyzw where xyzw can be combined in any way

vector.rgba where rgba can be combined in any way

vector.stpq where rgba can be combined in any way

vec4 v = vec4(1.0,2.0,3.0,1.0);
float x = v.x; //1.0
float x1 = v.r; //1.0
float x2 = v[0]; //1.0
vec3 xyz = v.xyz; // vec3(1.0,2.0,3.0)
vec3 xyz1 = vec(v[0],v[1],v[2]); // vec3(1.0,2.0,3.0)
vec3 rgb = v.rgb; // vec3(1.0,2.0,3.0)
vec2 xyzw = v.xyzw; // vec4(1.0,2.0,3.0,1.0);
vec2 rgba = v.rgba; // vec4(1.0,2.0,3.0,1.0);
Operator:
Priority (the smaller, the higher) operator illustrate
1 () Grouping:a*(b+c)
2 [] () . ++ - - Array subscript [], method parameter fun(arg1, arg2, arg3), attribute access ab, auto-increment/decrement suffix a++ a- -
3 ++ – + - ! Auto-increment/decrement prefix ++a --a, positive and negative sign (usually the positive sign is not written) a, -a, negation! false
4 * / Mathematical operations of multiplication and division
5 + - Addition and subtraction math operations
6 < > <= >= Relational operators
7 == != equality operator
8 && logical AND
9 ^^ Logical exclusive OR (its usefulness is basically equal to !=)
10 Logical OR. Can't type, the symbol is double vertical bars
11 ? : ternary operator
12 = += -= *= /= Assignment and compound assignment
13 , sequential allocation operation
Operations between basic types:

In glsl, there is no implicit type conversion. In principle, glsl requires that the types of the left and right sides (l-value) and (r-value) of any expression must be consistent, which means that the following expressions are wrong:

int a = 2.0; // 错误,右侧 value为float 而 左侧 value 为int.
int a = 1.0 + 2;
float a = 2;
float a = 2.0 + 1;
bool a = 0; 
vec3 a = vec3(1.0, 2.0, 3.0) * 2;

Other possible situations
1. float and int:

Float and float, int and int can be directly operated, but float and int cannot. They need to perform an explicit conversion. That is, either convert float to int: int(1.0), or convert int to float: float
( 1) , the following expressions are correct:

int a = int(2.0);
float a = float(2);
int a = int(2.0)*2 + 1;
float a = float(2)*6.0+2.3;

2. float and vec (vector) mat (matrix):

These types of vec and mat are actually composites of float. When they are operated with float, they are actually operated with float on each component. This is the so-called component-by-component operation. Most of glsl involves vec and mat
. The operations of are all component-by-component operations, but not all. Special cases will be discussed below. The component-
by-component operations are linear, which means that the result of the operation of vec and float is still vec. The relationship between
int and vec, mat is It is not operable, because each component in vec and mat is of type float. It cannot be calculated component-by-component with int. The following enumerates
several cases of operations between float and vec, mat.

vec3 a = vec3(1.0, 2.0, 3.0);
mat3 m = mat3(1.0);
float s = 10.0;
vec3 b = s * a; // vec3(10.0, 20.0, 30.0)
vec3 c = a * s; // vec3(10.0, 20.0, 30.0)
mat3 m2 = s * m; // = mat3(10.0)
mat3 m3 = m * s; // = mat3(10.0)

3. vec (vector) and vec (vector):

The operation between two vectors must first ensure that the order of the operands is the same. Otherwise, it cannot be calculated. For example: vec3*vec2 vec4+vec3, etc. are not possible. Their calculation method is that the components of the two operands at the same position are
respectively The essence of the operation is still component-by-component, which
may be slightly different from the component-by-component operation of the float type mentioned above. The same thing is that the result of the operation between vec and vec is still vec, and the order remains unchanged.

vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(0.1, 0.2, 0.3);
vec3 c = a + b; // vec3(1.1, 2.2, 3.3)
vec3 d = a * b; // vec3(0.1, 0.4, 0.9)

Insert image description here
4. vec (vector) and mat (matrix):

It is necessary to ensure that the order of the operands is the same, and there are only multiplication operations between vec and mat.
Their calculation method is the same as matrix multiplication in linear algebra, not component-by-component operations.

vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2.,  3., 4.);
vec2 w = m * v;  //  vec2(1. * 10. + 3. * 20., 2. * 10. + 4. * 20.)
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2.,  3., 4.);
vec2 w = v * m;  //  vec2(1. * 10. + 2. * 20., 3. * 10. + 4. * 20.)

The multiplication rules of vectors and matrices are as follows:
Insert image description here
5. mat(matrix) and mat(matrix):

It is necessary to ensure that the order of the operands is the same.
In the operations of mat and mat, except for the multiplication, which is matrix multiplication in linear algebra, the rest of the operations are component-by-component operations. Simply put, only multiplication is special, and the rest are the same as vec Similar to vec operation.

mat2 a = mat2(1., 2.,  3., 4.);
mat2 b = mat2(10., 20.,  30., 40.);
mat2 c = a * b;  // mat2(1.*10.+3.*20.,2.*10.+4.*20.,1.* 30.+3.*40.,2.* 30.+4.*40.);
mat2 d = a+b;  // mat2(1.+10.,2.+20.,3.+30.,4.+40);

The rules for matrix multiplication are as follows:
Insert image description here

Variable qualifier:
modifier illustrate
none (The default can be omitted) local variables, readable and writable, the input parameters of functions are of this type
const Declare the parameters of a variable or function as read-only types
attribute Can only exist in the vertex shader. It is generally used to save vertex or normal data. It can read data in the data buffer.
uniform The shader cannot change the uniform variable at runtime. It is generally used to place the transformation matrix, material, lighting parameters, etc. passed to the shader by the program.
varying Mainly responsible for passing variables between vertex and fragment

const:

Similar to the C language, variables modified by the const qualifier are immutable after initialization. In addition to local variables, function parameters can also use the const modifier. However, it should be noted that structure variables can be modified with const, but fields in the structure cannot.

const variables must be initialized when declared const vec3 v3 = vec3(0.,0.,0.)
Local variables can only use const qualifiers.
Function parameters can only use const qualifiers.

struct light {
  vec4 color;
  vec3 pos; 
  // const vec3 pos1; // 结构中的字段不可用const修饰会报错.
};
const light lgt = light(vec4(1.0), vec3(0.0)); // 结构变量可以用const修饰

attribute:

The attribute variable is global and read-only. It can only be used in the vertex shader and can only be combined with floating point numbers, vectors or matrix variables.
Generally, attribute variables are used to place model vertices, normals, colors, textures, etc. passed by the program. Data can access the data buffer
(remember the function __gl.vertexAttribPointer__)

attribute vec4 a_Position;

uniform:

uniform变量是全局且只读的,在整个shader执行完毕前其值不会改变,他可以和任意基本类型变量组合,
一般我们使用uniform变量来放置外部程序传递来的环境数据(如点光源位置,模型的变换矩阵等等)
这些数据在运行中显然是不需要被改变的.

uniform vec4 lightPosition;

varying:

varying类型变量是 vertex shader 与 fragment shader 之间的信使,一般我们在 vertex shader 中修改它然后在fragment shader使用它,但不能在fragment shader中修改它.

// 顶点着色器
varying vec4 v_Color;
void main(){
  v_Color = vec4(1.,1.,1.,1);
}
// 片元着色器
varying vec4 v_Color;
void main() {
  gl_FragColor = v_Color;
}
glsl的函数:

glsl允许在程序的最外部声明函数.函数不能嵌套,不能递归调用,且必须声明返回值类型(无返回值时声明为void) 在其他方面glsl函数与c函数非常类似.

vec4 getPosition(){ 
  vec4 v4 = vec4(0.,0.,0.,1.);
  return v4;
}

void doubleSize(inout float size){
  size= size*2.0  ;
}
void main() {
  float psize= 10.0;
  doubleSize(psize);
  gl_Position = getPosition();
  gl_PointSize = psize;
}
构造函数:

glsl中变量可以在声明的时候初始化,float pSize = 10.0 也可以先声明然后等需要的时候在进行赋值.
聚合类型对象如(向量,矩阵,数组,结构) 需要使用其构造函数来进行初始化. vec4 color = vec4(0.0, 1.0, 0.0, 1.0);

// 一般类型
float pSize = 10.0;
float pSize1;
pSize1 = 10.0;
// 复合类型
vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
vec4 color1;
color1 = vec4(0.0, 1.0, 0.0, 1.0);
// 结构
struct light {
  float intensity;
  vec3 position;
};
light lightVar = light(3.0, vec3(1.0, 2.0, 3.0)); // 数组
const float c[3] = float[3](5.0, 7.2, 1.1);

类型转换:

glsl可以使用构造函数进行显式类型转换,各值如下:

bool t= true;
bool f = false;
int a = int(t); // true转换为1或1.0
int a1 = int(f);  // false转换为0或0.0 
float b = float(t);
float b1 = float(f);
bool c = bool(0); // 0或0.0转换为false
bool c1 = bool(1); // 非0转换为true
bool d = bool(0.0);
bool d1 = bool(1.0);
精度限定:

glsl在进行光栅化着色的时候,会产生大量的浮点数运算,这些运算可能是当前设备所不能承受的,所以glsl提供了3种浮点数精度,我们可以根据不同的设备来使用合适的精度.
在变量前面加上 highp mediump lowp 即可完成对该变量的精度声明.

lowp float color;
varying mediump vec2 Coord;
lowp ivec2 foo(lowp mat3);
highp mat4 m;

我们一般在片元着色器(fragment shader)最开始的地方加上 precision mediump float; 便设定了默认的精度.这样所有没有显式表明精度的变量都会按照设定好的默认精度来处理.

如何确定精度:

变量的精度首先是由精度限定符决定的,如果没有精度限定符,则要寻找其右侧表达式中,已经确定精度的变量,一旦找到,那么整个表达式都将在该精度下运行.如果找到多个,
则选择精度较高的那种,如果一个都找不到,则使用默认或更大的精度类型.

uniform highp float h1;
highp float h2 = 2.3 * 4.7; //运算过程和结果都 是高精度
mediump float m;
m = 3.7 * h1 * h2; //运算过程 是高精度
h2 = m * h1; //运算过程 是高精度
m = h2 – h1; //运算过程 是高精度
h2 = m + m; //运算过程和结果都 是中等精度
void f(highp float p); // 形参 p 是高精度
f(3.3); //传入的 3.3是高精度
invariant关键字:

由于shader在编译时会进行一些内部优化,可能会导致同样的运算在不同shader里结果不一定精确相等.这会引起一些问题,尤其是vertx shader向fragmeng shader传值的时候.
所以我们需要使用invariant 关键字来显式要求计算结果必须精确一致. 当然我们也可使用 #pragma STDGL invariant(all)来命令所有输出变量必须精确一致,
但这样会限制编译器优化程度,降低性能.

#pragma STDGL invariant(all) //所有输出变量为 invariant
invariant varying texCoord; //varying在传递数据的时候声明为invariant

限定符的顺序:

当需要用到多个限定符的时候要遵循以下顺序:
1.在一般变量中: invariant > storage > precision
2.在参数中: storage > parameter > precision

我们来举例说明:

invariant varying lowp float color; // invariant > storage > precision
void doubleSize(const in lowp float s){ //storage > parameter > precision
    float s1=s;
}
预编译指令:

以 # 开头的是预编译指令,常用的有:
#define #undef #if #ifdef #ifndef #else
#elif #endif #error #pragma #extension #version #line
比如 #version 100 他的意思是规定当前shader使用 GLSL ES 1.00标准进行编译,如果使用这条预编译指令,则他必须出现在程序的最开始位置.

内置的宏:

LINE : 当前源码中的行号.

VERSION : 一个整数,指示当前的glsl版本 比如 100 ps: 100 = v1.00

GL_ES : 如果当前是在 OPGL ES 环境中运行则 GL_ES 被设置成1,一般用来检查当前环境是不是 OPENGL ES.

GL_FRAGMENT_PRECISION_HIGH : 如果当前系统glsl的片元着色器支持高浮点精度,则设置为1.一般用于检查着色器精度.

实例:

1.如何通过判断系统环境,来选择合适的精度:

#ifdef GL_ES //
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif

2.自定义宏:

#define NUM 100
#if NUM==100
#endif
内置的特殊变量

glsl程序使用一些特殊的内置变量与硬件进行沟通.他们大致分成两种
一种是 input类型,他负责向硬件(渲染管线)发送数据.
另一种是output类型,负责向程序回传数据,以便编程时需要.

在 vertex Shader 中:
output 类型的内置变量:

变量 说明 单位
highp vec4 gl_Position; gl_Position 放置顶点坐标信息 vec4
mediump float gl_PointSize; gl_PointSize 需要绘制点的大小,(只在gl.POINTS模式下有效) float

在 fragment Shader 中:
input 类型的内置变量:

变量 说明 单位
mediump vec4 gl_FragCoord; 片元在framebuffer画面的相对位置 vec4
bool gl_FrontFacing; 标志当前图元是不是正面图元的一部分 bool
mediump vec2 gl_PointCoord; 经过插值计算后的纹理坐标,点的范围是0.0到1.0 vec2

output 类型的内置变量:

变量 说明 单位
mediump vec4 gl_FragColor; 设置当前片点的颜色 vec4 RGBA color
mediump vec4 gl_FragData[n] 设置当前片点的颜色,使用glDrawBuffers数据数组 vec4 RGBA color
流控制

glsl的流控制和c语言非常相似,这里不必再做过多说明,唯一不同的是片段着色器中有一种特殊的控制流discard.使用discard会退出片段着色器,不执行后面的片段着色操作。片段也不会写入帧缓冲区。

void main() {
	vec4 diffuseColor = texture2D( map, vUv );
	gl_FragColor = vec4( diffuseColor.xyz * HSLtoRGB(vec3(vScale/5.0, 1.0, 0.5)), diffuseColor.w );
	if ( diffuseColor.w < 0.5 ) discard;
}
内置函数库

glsl提供了非常丰富的函数库,供我们使用,这些功能都是非常有用且会经常用到的. 这些函数按功能区分大改可以分成7类:

1、通用函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.

方法 说明
T abs(T x) 返回x的绝对值
T sign(T x) 比较x与0的值,大于,等于,小于 分别返回 1.0 ,0.0,-1.0
T floor(T x) 返回<=x的最大整数
T ceil(T x) 返回>=等于x的最小整数
T fract(T x) 获取x的小数部分
T mod(T x, T y),T mod(T x, float y) 取x,y的余数
T min(T x, T y),T min(T x, float y) 取x,y的最小值
T max(T x, T y),T max(T x, float y) 取x,y的最大值
T clamp(T x, T minVal, T maxVal),T clamp(T x, float minVal,float maxVal) min(max(x, minVal), maxVal),返回值被限定在 minVal,maxVal之间
T mix(T x, T y, T a),T mix(T x, T y, float a) 取x,y的线性混合,x*(1-a)+y*a
T step(T edge, T x),T step(float edge, T x) 如果 x<edge 返回 0.0 否则返回1.0
T smoothstep(T edge0, T edge1, T x),T smoothstep(float edge0,float edge1, T x) 如果x<edge0 返回 0.0 如果x>edge1返回1.0, 否则返回Hermite插值

2、角度&三角函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.

方法 说明
T radians(T degrees) 角度转弧度
T radians(T degrees) 角度转弧度
T degrees(T radians) 弧度转角度
T sin(T angle) 正弦函数,角度是弧度
T cos(T angle) 余弦函数,角度是弧度
T tan(T angle) 正切函数,角度是弧度
T asin(T x) 反正弦函数,返回值是弧度
T acos(T x) 反余弦函数,返回值是弧度
T atan(T y, T x),T atan(T y_over_x) 反正切函数,返回值是弧度

3、指数函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.

方法 说明
T pow(T x, T y) 返回x的y次幂 xy
T exp(T x) 返回x的自然指数幂 ex
T log(T x) 返回x的自然对数 ln
T exp2(T x) 返回2的x次幂 2x
T log2(T x) 返回2为底的对数 log2
T sqrt(T x) 开根号 √x
T inversesqrt(T x) 先开根号,在取倒数,就是 1/√x

4、几何函数:
下文中的 类型 T可以是 float, vec2, vec3, vec4,且可以逐分量操作.

方法 说明
float length(T x) 返回矢量x的长度
float distance(T p0, T p1) 返回p0 p1两点的距离
float dot(T x, T y) 返回x y的点积
vec3 cross(vec3 x, vec3 y) 返回x y的叉积
T normalize(T x) 对x进行归一化,保持向量方向不变但长度变为1
T faceforward(T N, T I, T Nref) 根据 矢量 N 与Nref 调整法向量
T reflect(T I, T N) 返回 I - 2 * dot(N,I) * N, 结果是入射矢量 I 关于法向量N的 镜面反射矢量
T refract(T I, T N, float eta) 返回入射矢量I关于法向量N的折射矢量,折射率为eta

5、矩阵函数:
mat可以为任意类型矩阵.

方法 说明
mat matrixCompMult(mat x, mat y) 将矩阵 x 和 y的元素逐分量相乘

6、向量函数:

下文中的 类型 T可以是 vec2, vec3, vec4, 且可以逐分量操作.

bvec指的是由bool类型组成的一个向量:

vec3 v3= vec3(0.,0.,0.);
vec3 v3_1= vec3(1.,1.,1.);
bvec3 aa= lessThan(v3,v3_1); //bvec3(true,true,true)

方法 说明
bvec lessThan(T x, T y) 逐分量比较x < y,将结果写入bvec对应位置
bvec lessThanEqual(T x, T y) 逐分量比较 x <= y,将结果写入bvec对应位置
bvec greaterThan(T x, T y) 逐分量比较 x > y,将结果写入bvec对应位置
bvec greaterThanEqual(T x, T y) 逐分量比较 x >= y,将结果写入bvec对应位置
bvec equal(T x, T y),bvec equal(bvec x, bvec y) 逐分量比较 x == y,将结果写入bvec对应位置
bvec notEqual(T x, T y),bvec notEqual(bvec x, bvec y) 逐分量比较 x!= y,将结果写入bvec对应位置
bool any(bvec x) 如果x的任意一个分量是true,则结果为true
bool all(bvec x) 如果x的所有分量是true,则结果为true
bvec not(bvec x) bool矢量的逐分量取反

7、纹理查询函数:

图像纹理有两种 一种是平面2d纹理,另一种是盒纹理,针对不同的纹理类型有不同访问方法.

纹理查询的最终目的是从sampler中提取指定坐标的颜色信息. 函数中带有Cube字样的是指 需要传入盒状纹理. 带有Proj字样的是指带投影的版本.

以下函数只在vertex shader中可用:

vec4 texture2DLod(sampler2D sampler, vec2 coord, float lod);
vec4 texture2DProjLod(sampler2D sampler, vec3 coord, float lod);
vec4 texture2DProjLod(sampler2D sampler, vec4 coord, float lod);
vec4 textureCubeLod(samplerCube sampler, vec3 coord, float lod);

以下函数只在fragment shader中可用:

vec4 texture2D(sampler2D sampler, vec2 coord, float bias);
vec4 texture2DProj(sampler2D sampler, vec3 coord, float bias);
vec4 texture2DProj(sampler2D sampler, vec4 coord, float bias);
vec4 textureCube(samplerCube sampler, vec3 coord, float bias);

Available in both vertex shader and fragment shader:

vec4 texture2D(sampler2D sampler, vec2 coord);
vec4 texture2DProj(sampler2D sampler, vec3 coord);
vec4 texture2DProj(sampler2D sampler, vec4 coord);
vec4 textureCube(samplerCube sampler, vec3 coord);

Official shader example:

If you can understand the following shader at a glance, it means that you have basically mastered the glsl language.

Vertex Shader:

uniform mat4 mvp_matrix; //透视矩阵 * 视图矩阵 * 模型变换矩阵
uniform mat3 normal_matrix; //法线变换矩阵(用于物体变换后法线跟着变换)
uniform vec3 ec_light_dir; //光照方向
attribute vec4 a_vertex; // 顶点坐标
attribute vec3 a_normal; //顶点法线
attribute vec2 a_texcoord; //纹理坐标
varying float v_diffuse; //法线与入射光的夹角
varying vec2 v_texcoord; //2d纹理坐标
void main(void) {
	// 归一化法线
	vec3 ec_normal = normalize(normal_matrix * a_normal);
	// v_diffuse 是法线与光照的夹角.根据向量点乘法则,当两向量长度为1是 乘积即cosθ值
	v_diffuse = max(dot(ec_light_dir, ec_normal), 0.0);
	v_texcoord = a_texcoord;
	gl_Position = mvp_matrix * a_vertex;
}

Fragment Shader:

precision mediump float;
uniform sampler2D t_reflectance;
uniform vec4 i_ambient;
varying float v_diffuse;
varying vec2 v_texcoord;
void main (void) {
	vec4 color = texture2D(t_reflectance, v_texcoord);
	// 这里分解开来是 color*vec3(1,1,1)*v_diffuse + color*i_ambient
	// 色*光*夹角cos + 色*环境光
	gl_FragColor = color*(vec4(v_diffuse) + i_ambient);
}

Guess you like

Origin blog.csdn.net/weixin_44384273/article/details/132856402