GLSL着色器踩坑与实践经验
搞游戏开发不可避免要接触Shader,GLSL是OpenGL的着色器语言。记录一下基础语法和踩过的坑。
基础类型
| 类型 |
说明 |
示例 |
| void |
空类型 |
void main() |
| bool |
布尔 |
true, false |
| int |
整数 |
42, -7 |
| float |
浮点数 |
3.14, -0.5 |
| vec2/3/4 |
浮点向量 |
vec3(1.0, 2.0, 3.0) |
| mat2/3/4 |
浮点矩阵 |
mat3(1.0) |
| sampler2D |
2D纹理 |
uniform sampler2D texture |
注意:GLSL没有double类型,只有float。
向量分量访问
GLSL向量可以用多种方式访问分量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| vec4 v = vec4(1.0, 2.0, 3.0, 1.0);
float x = v.x;
float r = v.r;
float s = v.s;
float x2 = v[0];
vec3 xyz = v.xyz; vec3 rgb = v.rgb; vec3 v3 = v.zyx;
|
向量重组很灵活,可以随意组合:
1 2
| vec4 v2 = v.xyzx; vec2 xy = v.xy;
|
结构体和数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct Light { float intensity; vec3 position; vec4 color; };
Light light1 = Light(3.0, vec3(1.0, 2.0, 3.0), vec4(1.0));
float weights[3] = float[3](0.5, 0.3, 0.2); vec3 positions[4];
Light lights[4];
|
运算符
GLSL运算符和C语言类似,支持逐分量运算:
| 优先级 |
运算符 |
说明 |
| 1 |
() |
括号 |
| 2 |
[] . |
数组、属性 |
| 4 |
* / |
乘除 |
| 5 |
+ - |
加减 |
| 6 |
< > <= >= |
关系运算 |
| 7 |
== != |
相等判断 |
| 8-10 |
&& ^^ || |
逻辑运算 |
| 12 |
= += -= |
赋值 |
逐分量运算
1 2 3 4 5 6 7 8 9 10 11
| vec3 a = vec3(1.0, 2.0, 3.0); float s = 10.0; vec3 b = s * a;
vec3 c = a + b; vec3 d = a * b;
vec2 v = vec2(10.0, 20.0); mat2 m = mat2(1.0, 2.0, 3.0, 4.0); vec2 w = m * v;
|
类型转换
GLSL不支持隐式转换,必须显式转换:
1 2 3 4 5 6 7 8 9 10 11
| float f = 2.0; int i = int(f);
int i2 = 3; float f2 = float(i2);
bool t = true; float f3 = float(t);
vec3 v = vec3(1.0, 2.0, 3.0); ivec3 iv = ivec3(v);
|
变量限定符
| 限定符 |
说明 |
位置 |
| (默认) |
局部变量 |
函数内 |
| const |
常量 |
任意 |
| attribute |
顶点属性 |
顶点着色器 |
| uniform |
统一变量 |
全局 |
| varying |
插值变量 |
顶点/片元着色器 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| attribute vec4 a_position; attribute vec2 a_texCoord; attribute vec3 a_normal;
uniform mat4 u_mvpMatrix; uniform vec4 u_color; uniform sampler2D u_texture;
varying vec2 v_texCoord; varying vec3 v_normal; varying float v_diffuse;
|
参数限定符
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void func(in float value) { value = 10.0; }
void func(out float value) { value = 10.0; }
void func(inout float value) { value = value * 2.0; }
|
精度限定
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| highp float position; highp vec4 vertex;
mediump vec4 color; mediump vec2 texCoord;
lowp float alpha; lowp vec3 normal;
precision mediump float;
|
精度选择建议:
| 类型 |
精度 |
说明 |
| 位置、矩阵 |
highp |
需要精确计算 |
| 颜色、纹理坐标 |
mediump |
平衡精度和性能 |
| 法线、光照强度 |
lowp |
对精度要求低 |
坑:精度不够会导致渲染出现条纹或闪烁。
内置函数
通用函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| float abs(float x); float sign(float x); float floor(float x); float ceil(float x); float fract(float x); float mod(float x, float y);
float min(float x, float y); float max(float x, float y); float clamp(float x, float min, float max);
float mix(float x, float y, float a); float step(float edge, float x); float smoothstep(float e0, float e1, float x);
|
三角函数
1 2 3 4 5 6 7 8 9 10
| float radians(float degrees); float degrees(float radians);
float sin(float angle); float cos(float angle); float tan(float angle);
float asin(float x); float acos(float x); float atan(float y, float x);
|
指数函数
1 2 3 4 5 6 7
| float pow(float x, float y); float exp(float x); float log(float x); float exp2(float x); float log2(float x); float sqrt(float x); float inversesqrt(float x);
|
几何函数
1 2 3 4 5 6 7 8
| float length(vec3 v); float distance(vec3 p0, vec3 p1); float dot(vec3 v0, vec3 v1); vec3 cross(vec3 v0, vec3 v1); vec3 normalize(vec3 v);
vec3 reflect(vec3 I, vec3 N); vec3 refract(vec3 I, vec3 N, float eta);
|
纹理采样
1 2 3 4 5 6
| vec4 texture2D(sampler2D sampler, vec2 coord); vec4 texture2D(sampler2D sampler, vec2 coord, float bias);
vec4 textureCube(samplerCube sampler, vec3 coord);
|
流控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| if (condition) { } else if (otherCondition) { } else { }
float value = condition ? a : b;
if (alpha < 0.01) { discard; }
for (int i = 0; i < 10; i++) { }
while (count < 10) { count++; }
|
注意:循环变量必须是编译时常量或已知值,有些GPU不支持动态循环。
完整着色器示例
顶点着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #version 100
attribute vec4 a_position; attribute vec3 a_normal; attribute vec2 a_texCoord;
uniform mat4 u_mvpMatrix; uniform mat3 u_normalMatrix; uniform vec3 u_lightDir;
varying vec2 v_texCoord; varying float v_diffuse;
void main() { gl_Position = u_mvpMatrix * a_position;
vec3 normal = normalize(u_normalMatrix * a_normal);
v_diffuse = max(dot(u_lightDir, normal), 0.0);
v_texCoord = a_texCoord; }
|
片元着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #version 100
precision mediump float;
uniform sampler2D u_texture; uniform vec4 u_ambient;
varying vec2 v_texCoord; varying float v_diffuse;
void main() { vec4 texColor = texture2D(u_texture, v_texCoord);
vec3 color = texColor.rgb * (v_diffuse + u_ambient.rgb);
gl_FragColor = vec4(color, texColor.a); }
|
流程:
1 2 3
| 顶点数据(attribute) -> 矩阵变换(uniform) -> 光照计算 -> varying | varying <- 纹理采样(uniform) <- 片元着色(gl_FragColor)
|
预编译指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #version 100
#define PI 3.14159 #define MAX_LIGHTS 4
#ifdef GL_ES precision mediump float; #endif
#undef PI
|
内置宏
| 宏 |
说明 |
__LINE__ |
当前行号 |
__VERSION__ |
GLSL版本号 |
GL_ES |
OpenGL ES环境为1 |
GL_FRAGMENT_PRECISION_HIGH |
片元着色器是否支持高精度 |
1 2 3 4 5 6 7 8
| #ifdef GL_ES #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif #endif
|
踩坑记录
| 问题 |
原因 |
解决 |
| 黑屏 |
编译错误 |
检查日志 |
| 颜色异常 |
精度问题 |
调整精度修饰符 |
| 纹理错乱 |
坐标问题 |
检查纹理坐标范围0-1 |
| 性能差 |
计算复杂 |
简化算法,避免动态分支 |
| 条纹/闪烁 |
精度不够 |
提高精度到highp |
学习资源
| 网站 |
网址 |
特点 |
| ShaderToy |
shadertoy.com |
最知名的在线Shader编辑器 |
| GLSLSandbox |
glslsandbox.com |
简洁的GLSL沙盒 |
| The Book of Shaders |
thebookofshaders.com |
系统学习资料 |
GLSL入门不难,但要写出高效好看的Shader需要大量练习。建议多逛ShaderToy看别人怎么写,从模仿开始。注意精度问题和GPU差异,移动端和桌面端表现可能不一样。