———————————————————— 前序 ———————————————————
AndroidLearnOpenGL是本博主自己实现的LearnOpenGL练习集合:
Github地址:GitHub - wangyongyao1989/AndroidLearnOpenGL: OpenGL基础及运用
系列文章:
1、LearnOpenGL之入门基础
2、LearnOpenGL之3D显示
3、LearnOpenGL之摄像机
4、LearnOpenGL之光照
5、LearnOpenGL之3D模型加载
6、LearnOpenGL之文字渲染
7、LearnOpenGL之高级OpenGL(1)
8、LearnOpenGL之高级OpenGL(2)
9、LearnOpenGL之骨骼动画
10、LearnOpenGL之Shader编程用算法绘画
——————————————————————————————————————————
—————————————————— 效果展示 ————————————————————
GLSL着色器编程算法绘画
————————————————
一、Shader(着色器)及GLSL概览:
如果你曾经有用计算机绘图的经验,你就知道在这个过程中你需要画一个圆,然后一个长方形,一条线,一些三角形……直到画出你想要的图像。这个过程很像用手写一封信或一本书 —— 都是一系列的指令,需要你一件一件完成。
Shaders 也是一系列的指令,但是这些指令会对屏幕上的每个像素同时下达。也就是说,你的代码必须根据像素在屏幕上的不同位置执行不同的操作。就像活字印刷,你的程序就像一个 function(函数),输入位置信息,输出颜色信息,当它编译完之后会以相当快的速度运行。
shaders并行处理:
想象你的 CPU 是一个大的工业管道,然后每一个任务都是通过这个管道的某些东西 —— 就像一个生产流水线那样。有些任务要比别的大,也就是说要花费更多时间和精力去处理。我们就称它要求更强的处理能力。由于计算机自身的架构,这些任务需要串行;即一次一个地依序完成。现代计算机通常有一组四个处理器,就像这个管道一样运行,一个接一个地处理这些任务,从而使计算机流畅运行。每个管道通常被称为线程。
另一个 GPU 的魔法是特殊数学函数可通过硬件加速。非常复杂的数学操作可以直接被微芯片解决,而无须通过软件。这就表示可以有更快的三角和矩阵运算 —— 和电流一样快。
GLSL:
GLSL 代表 openGL Shading Language,openGL 着色语言。
Uniforms:
我们知道了 GPU 如何处理并行线程,每个线程负责给完整图像的一部分配置颜色。尽管每个线程和其他线程之间不能有数据交换,但我们能从 CPU 给每个线程输入数据。因为显卡的架构,所有线程的输入值必须统一(uniform),而且必须设为只读。也就是说,每条线程接收相同的数据,并且是不可改变的数据。
这些输入值叫做 uniform
(统一值),它们的数据类型通常为:float
, vec2
, vec3
, vec4
, mat2
, mat3
, mat4
, sampler2D
and samplerCube
。uniform 值需要数值类型前后一致。且在 shader 的开头,在设定精度之后,就对其进行定义。
GLSL 还有更多惊喜。GPU 的硬件加速支持我们使用角度,三角函数和指数函数。这里有一些这些函数的介绍:sin(), cos(), tan(), asin(), acos(), atan(), pow(), exp(), log(), sqrt(), abs(), sign(), floor(), ceil(), fract(), mod(), min(), max() 和 clamp()。
gl_FragCoord:
就像 GLSL 有个默认输出值 vec4 gl_FragColor
一样,它也有一个默认输入值( vec4 gl_FragCoord
)。gl_FragCoord
存储了活动线程正在处理的像素或屏幕碎片的坐标。有了它我们就知道了屏幕上的哪一个线程正在运转。为什么我们不叫 gl_FragCoord
uniform (统一值)呢?因为每个像素的坐标都不同,所以我们把它叫做 varying(变化值)。
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
void main() {
vec2 st = gl_FragCoord.xy/u_resolution;
gl_FragColor = vec4(st.x,st.y,0.0,1.0);
}
运行shader:
可以在 editor.thebookofshaders.com/ 上将其作为独立的 Web 应用运行。
GLSL插件安装:
以Android Studio为例在Plugins应用市场中搜索glsl:
安装插件后glsl文件高亮显示及函数的智能补全及跳转应用等功能:
跳转至builtin.glsl里面有着色器的标准函数:
builtin内置标准函数的解析网址为:Built-in Variable (GLSL) - OpenGL Wiki
GLSL ES参考手册
OpenGL ES着色器语言GLSL ES参考文档
二、Shader的造型函数:
smoothstep函数:
smoothstep
函数是GLSL中用于实现平滑插值的函数之一,它可以在指定起止边界的范围内,将一个给定的值进行平滑插值。smoothstep
函数在图形渲染中常常用于平滑过渡,例如在着色器计算过程中,将颜色值进行平滑渐变,从而实现渐变效果。
smoothstep (edge0, edge1, x)
- edge0:float类型的起始边界;
- edge1:float类型的终止边界;
- x:float类型的输入值;
- 返回值:返回一个float类型的值,这个值的范围在[0, 1]之间,代表了输入值经过平滑插值后的结果。
代码实现:
float smoothstep (float edge0, float edge1, float x) {
// Scale, and clamp x to 0..1 range
x = clamp((x - edge0) / (edge1 - edge0));
return x * x * (3.0f - 2.0f * x);
}
float clamp(float x, float lowerlimit = 0.0f,
float upperlimit = 1.0f) {
if (x < lowerlimit) return lowerlimit;
if (x > upperlimit) return upperlimit;
return x;
}
下面这个例子实现从黑色到白色的渐变,并画一条红线的着色器代码:
#version 320 es
precision mediump float;
out vec4 FragColor; //out代表输出至下一个着色器
uniform vec2 u_resolution; //视窗的分辨率
uniform float u_time; //传入的时间变量
uniform float uu;
// Plot a line on Y using a value between 0.0-1.0
float plot(vec2 st) {
//返回一个float类型的值,这个值的范围在[0, 1]之间,
//代表了输入值经过平滑插值后的结果。
return smoothstep(0.02f, 0.0f, abs(st.y - st.x));
}
void main() {
//1.传入每个像素的xy除视窗的分辨率
vec2 st = gl_FragCoord.xy / u_resolution;
//2.获取st的x分量,加载uniform中的u_time,
//得到跟随时间的变换的渐变色
vec3 color = vec3(st.x,st.x,abs(sin(u_time)));
// Plot a line
float pct = plot(st);
//3.【(1.0f - pct) * color】表示除直线外的渐变颜色,
//【pct * vec3(1.0f,0.0f,0.0f)】表示附着直线的颜色。
color = (1.0f - pct) * color + pct * vec3(1.0f,0.0f,0.0f);
FragColor = vec4(color , 1.0f);
}
step()函数:
step()插值函数需要输入两个参数。第一个是极限或阈值,第二个是我们想要检测或通过的值。对任何小于阈值的值,返回 0.0
,大于阈值,则返回 1.0
。
#version 320 es
precision mediump float;
out vec4 FragColor; //out代表输出至下一个着色器
uniform vec2 u_resolution; //视窗的分辨率
uniform float u_time; //传入的时间变量
float plot(vec2 st, float pct){
return smoothstep( pct-0.02, pct, st.y) -
smoothstep( pct, pct+0.02, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution;
//插值函数需要输入两个参数。第一个是极限或阈值,
//第二个是我们想要检测或通过的值。对任何小于阈值的值,
//返回0.0,大于阈值,则返回1.0。
float y = step(0.5,st.x);
vec3 color = vec3(y);
float pct = plot(st,y);
color = (1.0-pct)*color+pct*vec3(1.0,0.0,0.0);
FragColor = vec4(color , 1.0f);
}
其他有用的函数:
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
float plot(vec2 st, float pct){
return smoothstep( pct-0.02f, pct, st.y) -
smoothstep( pct, pct+0.02f, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
// y = mod(x,0.5); // 返回 x 对 0.5 取模的值
// y = fract(x); // 仅仅返回数的小数部分
y = ceil(x); // 向正无穷取整
// y = floor(x); // 向负无穷取整
// y = sign(x); // 提取 x 的正负号
// y = abs(x); // 返回 x 的绝对值
// y = clamp(x,0.0,1.0); // 把 x 的值限制在 0.0 到 1.0
// y = min(0.0,x); // 返回 x 和 0.0 中的较小值
// y = max(0.0,x); // 返回 x 和 0.0 中的较大值
// Plot a line
float pct = plot(st,y);
vec3 color = pct * vec3(1.0f,0.0f,0.0f);
FragColor = vec4(color , 1.0f);
}
造型函数进阶:
更加复杂的造型函数的文档:Shaping Functions
- 多项式造型函数:用于在 [0...1] 范围内塑造、补间和缓和信号的多项式函数。如下例子:
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
float blinnWyvillCosineApproximation (float x){
float x2 = x*x;
float x4 = x2*x2;
float x6 = x4*x2;
float fa = (4.0/9.0);
float fb = (17.0/9.0);
float fc = (22.0/9.0);
float y = fa*x6 - fb*x4 + fc*x2;
return y;
}
float doubleCubicSeat (float x, float a, float b){
float epsilon = 0.00001;
float min_param_a = 0.0 + epsilon;
float max_param_a = 1.0 - epsilon;
float min_param_b = 0.0;
float max_param_b = 1.0;
a = min(max_param_a, max(min_param_a, a));
b = min(max_param_b, max(min_param_b, b));
float y = 0.00000f;
if (x <= a){
y = b - (b * pow(1.0-x/a, 3.0));
} else {
y = b + (1.0-b)*pow((x-a)/(1.0-a), 3.0);
}
return y;
}
float plot(vec2 st, float pct){
return smoothstep(pct-0.02f, pct, st.y) -
smoothstep(pct, pct+0.02f, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
// y = blinnWyvillCosineApproximation(x);
y = doubleCubicSeat(x, 5.0f, 5.0f);
// Plot a line
float pct = plot(st, y);
vec3 color = pct * vec3(1.0f, 0.0f, 0.0f);
FragColor = vec4(color, 1.0f);
}
- 指数造型函数:指数整形函数的实现中,控制参数a允许设计者将函数从缓出形式改变为缓入形式。如下例子:
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
float exponentialEasing (float x, float a){
float epsilon = 0.00001;
float min_param_a = 0.0 + epsilon;
float max_param_a = 1.0 - epsilon;
a = max(min_param_a, min(max_param_a, a));
if (a < 0.5){
// emphasis
a = 2.0*(a);
float y = pow(x, a);
return y;
} else {
// de-emphasis
a = 2.0*(a-0.5);
float y = pow(x, 1.0/(1.0-a));
return y;
}
}
float plot(vec2 st, float pct){
return smoothstep(pct-0.02f, pct, st.y) -
smoothstep(pct, pct+0.02f, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
y = exponentialEasing(st.x, 0.3);
// Plot a line
float pct = plot(st, y);
vec3 color = pct * vec3(1.0f, 0.0f, 0.0f);
FragColor = vec4(color, 1.0f);
}
- 椭圆造型函数:圆弧提供了一种快速且易于编码的方法来缓入或缓出单位正方形。然而,由于使用平方根,该函数的计算效率会降低。
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
float doubleCircleSigmoid (float x, float a){
float min_param_a = 0.0;
float max_param_a = 1.0;
a = max(min_param_a, min(max_param_a, a));
float y = 0.0;
if (x<=a){
y = a - sqrt(a*a - x*x);
} else {
y = a + sqrt(pow((1.0-a), 2.0) - pow((x-1.0), 2.0));
}
return y;
}
float plot(vec2 st, float pct){
return smoothstep(pct-0.02f, pct, st.y) -
smoothstep(pct, pct+0.02f, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
y = doubleCircleSigmoid(st.x, 0.3);
// Plot a line
float pct = plot(st, y);
vec3 color = pct * vec3(1.0f, 0.0f, 0.0f);
FragColor = vec4(color, 1.0f);
}
- 贝塞尔造型函数:此函数定义一个二阶(二次)贝塞尔曲线,该曲线在单位正方形中具有单个用户指定的样条控制点(位于坐标 a,b)。此函数保证具有与双线性插值器相同的进入和退出斜率。换句话说,此曲线允许用户精确指定其在单位正方形中端点的变化率。
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
float quadraticBezier (float x, float a, float b){
// adapted from BEZMATH.PS (1993)
// by Don Lancaster, SYNERGETICS Inc.
// http://www.tinaja.com/text/bezmath.html
float epsilon = 0.00001;
a = max(0.0, min(1.0, a));
b = max(0.0, min(1.0, b));
if (a == 0.5){
a += epsilon;
}
// solve t from x (an inverse operation)
float om2a = 1.0 - 2.0*a;
float t = (sqrt(a*a + om2a*x) - a)/om2a;
float y = (1.0-2.0*b)*(t*t) + (2.0*b)*t;
return y;
}
float plot(vec2 st, float pct){
return smoothstep(pct-0.02f, pct, st.y) -
smoothstep(pct, pct+0.02f, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
y = quadraticBezier(st.x, 0.001, 5.0);
// Plot a line
float pct = plot(st, y);
vec3 color = pct * vec3(1.0f, 0.0f, 0.0f);
FragColor = vec4(color, 1.0f);
}
- Impulse造型函数:
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
// Function from Iñigo Quiles
// www.iquilezles.org/www/articles/functions/functions.htm
float impulse( float k, float x ){
float h = k*x;
return h*exp(1.0-h);
}
float plot(vec2 st, float pct){
return smoothstep( pct-0.02, pct, st.y) -
smoothstep( pct, pct+0.02, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
y = impulse(12.0,x);
// Plot a line
float pct = plot(st, y);
vec3 color = pct * vec3(1.0f, 0.0f, 0.0f);
FragColor = vec4(color, 1.0f);
}
- CubicPulse造型函数:
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
// Function from Iñigo Quiles
// www.iquilezles.org/www/articles/functions/functions.htm
float cubicPulse( float c, float w, float x ){
x = abs(x - c);
if( x>w ) return 0.0;
x /= w;
return 1.0 - x*x*(3.0-2.0*x);
}
float plot(vec2 st, float pct){
return smoothstep( pct-0.02, pct, st.y) -
smoothstep( pct, pct+0.02, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
y = cubicPulse(0.5,0.2,st.x);
// Plot a line
float pct = plot(st, y);
vec3 color = pct * vec3(1.0f, 0.0f, 0.0f);
FragColor = vec4(color, 1.0f);
}
- ExpStep造型函数:
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
// Function from Iñigo Quiles
// www.iquilezles.org/www/articles/functions/functions.htm
float expStep(float x, float k, float n){
return exp(-k*pow(x, n));
}
float plot(vec2 st, float pct){
return smoothstep(pct-0.02, pct, st.y) -
smoothstep(pct, pct+0.02, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
y = expStep(st.x, 10., 1.0);
// Plot a line
float pct = plot(st, y);
vec3 color = pct * vec3(1.0f, 0.0f, 0.0f);
FragColor = vec4(color, 1.0f);
}
- Parabola造型函数:
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
// Function from Iñigo Quiles
// www.iquilezles.org/www/articles/functions/functions.htm
float parabola(float x, float k){
return pow(4.0*x*(1.0-x), k);
}
float plot(vec2 st, float pct){
return smoothstep(pct-0.02, pct, st.y) -
smoothstep(pct, pct+0.02, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
y = parabola(st.x, 1.0);
// Plot a line
float pct = plot(st, y);
vec3 color = pct * vec3(1.0f, 0.0f, 0.0f);
FragColor = vec4(color, 1.0f);
}
- Pcurve造型函数:
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;
uniform vec2 u_resolution;
// Function from Iñigo Quiles
// www.iquilezles.org/www/articles/functions/functions.htm
float pcurve(float x, float a, float b){
float k = pow(a+b, a+b) / (pow(a, a)*pow(b, b));
return k * pow(x, a) * pow(1.0-x, b);
}
float plot(vec2 st, float pct){
return smoothstep(pct-0.02, pct, st.y) -
smoothstep(pct, pct+0.02, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float y;
float x = st.x;
y = pcurve(st.x, 3.0, 1.0);
// Plot a line
float pct = plot(st, y);
vec3 color = pct * vec3(1.0f, 0.0f, 0.0f);
FragColor = vec4(color, 1.0f);
}
三、Shader的颜色:
GLSL中向量类型的另一大特点是可以用你需要的任意顺序简单地投射和混合(变量)值。这种能力被(形象地)称为:鸡尾酒。
混合颜色:
在GLSL中,有个十分有用的函数:mix() 这个函数让你以百分比混合两个值。
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
vec3 colorA = vec3(0.149,0.141,0.912);
vec3 colorB = vec3(1.000,0.833,0.224);
void main() {
vec3 color = vec3(0.0);
float pct = abs(sin(u_time));
// Mix uses pct (a value from 0-1) to
// mix the two colors
color = mix(colorA, colorB, pct);
FragColor = vec4(color, 1.0f);
}
缓动函数:
Robert Penner 开发了一些列流行的计算机动画塑形函数,被称为缓动函数。
#version 320 es
precision mediump float;
#define PI 3.141592653589793f;
#define HALF_PI 1.5707963267948966f;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
// Robert Penner's easing functions in GLSL
// https://github.com/stackgl/glsl-easings
float linear(float t) {
return t;
}
float exponentialIn(float t) {
return t == 0.0 ? t : pow(2.0, 10.0 * (t - 1.0));
}
float exponentialOut(float t) {
return t == 1.0 ? t : 1.0 - pow(2.0, -10.0 * t);
}
float exponentialInOut(float t) {
return t == 0.0 || t == 1.0
? t
: t < 0.5
? +0.5 * pow(2.0, (20.0 * t) - 10.0)
: -0.5 * pow(2.0, 10.0 - (t * 20.0)) + 1.0;
}
float sineIn(float t) {
float result = (t - 1.0) * HALF_PI;
return sin(result) + 1.0f;
}
float sineOut(float t) {
float result = t * HALF_PI;
return sin(result);
}
float qinticIn(float t) {
return pow(t, 5.0);
}
float qinticOut(float t) {
return 1.0 - (pow(t - 1.0, 5.0));
}
float qinticInOut(float t) {
return t < 0.5
? +16.0 * pow(t, 5.0)
: -0.5 * pow(2.0 * t - 2.0, 5.0) + 1.0;
}
float quarticIn(float t) {
return pow(t, 4.0);
}
float quarticOut(float t) {
return pow(t - 1.0, 3.0) * (1.0 - t) + 1.0;
}
float quarticInOut(float t) {
return t < 0.5
? +8.0 * pow(t, 4.0)
: -8.0 * pow(t - 1.0, 4.0) + 1.0;
}
float quadraticInOut(float t) {
float p = 2.0 * t * t;
return t < 0.5 ? p : -p + (4.0 * t) - 1.0;
}
float quadraticIn(float t) {
return t * t;
}
float quadraticOut(float t) {
return -t * (t - 2.0);
}
float cubicIn(float t) {
return t * t * t;
}
float cubicOut(float t) {
float f = t - 1.0;
return f * f * f + 1.0;
}
float cubicInOut(float t) {
return t < 0.5
? 4.0 * t * t * t
: 0.5 * pow(2.0 * t - 2.0, 3.0) + 1.0;
}
float elasticIn(float t) {
float result = 13.0 * t * HALF_PI;
return sin(result) * pow(2.0, 10.0 * (t - 1.0));
}
float elasticOut(float t) {
float result = -13.0 * (t + 1.0) * HALF_PI;
return sin(result) * pow(2.0, -10.0 * t) + 1.0;
}
float elasticInOut(float t) {
float result1 = 13.0 * HALF_PI;
float result2 = -13.0 * HALF_PI;
return t < 0.5
? 0.5 * sin(result1 * 2.0 * t) * pow(2.0, 10.0 * (2.0 * t - 1.0))
: 0.5 * sin(result2 * ((2.0 * t - 1.0) + 1.0)) * pow(2.0, -10.0 * (2.0 * t - 1.0)) + 1.0;
}
float circularIn(float t) {
return 1.0 - sqrt(1.0 - t * t);
}
float circularOut(float t) {
return sqrt((2.0 - t) * t);
}
float circularInOut(float t) {
return t < 0.5
? 0.5 * (1.0 - sqrt(1.0 - 4.0 * t * t))
: 0.5 * (sqrt((3.0 - 2.0 * t) * (2.0 * t - 1.0)) + 1.0);
}
float bounceOut(float t) {
const float a = 4.0 / 11.0;
const float b = 8.0 / 11.0;
const float c = 9.0 / 10.0;
const float ca = 4356.0 / 361.0;
const float cb = 35442.0 / 1805.0;
const float cc = 16061.0 / 1805.0;
float t2 = t * t;
return t < a
? 7.5625 * t2
: t < b
? 9.075 * t2 - 9.9 * t + 3.4
: t < c
? ca * t2 - cb * t + cc
: 10.8 * t * t - 20.52 * t + 10.72;
}
float bounceIn(float t) {
return 1.0 - bounceOut(1.0 - t);
}
float bounceInOut(float t) {
return t < 0.5
? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0))
: 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5;
}
float backIn(float t) {
float result = t * PI;
return pow(t, 3.0) - t * sin(result);
}
float backOut(float t) {
float f = 1.0 - t;
float result = f * PI;
return 1.0 - (pow(f, 3.0) - f * sin(result));
}
float backInOut(float t) {
float f = t < 0.5 ? 2.0 * t : 1.0 - (2.0 * t - 1.0);
float result = f * PI;
float g = pow(f, 3.0) - f * sin(result);
return t < 0.5
? 0.5 * g
: 0.5 * (1.0 - g) + 0.5;
}
void main() {
vec3 colorA = vec3(0.149, 0.141, 0.912);
vec3 colorB = vec3(1.000, 0.833, 0.224);
float t = sin(u_time);
float pct;
// pct = cubicInOut(abs(fract(t)*2.0-1.0));
pct = elasticIn(t);
vec3 color = mix(colorA, colorB, pct);
FragColor = vec4(color, 1.0f);
}
渐变:
我们可以输入两个互相匹配的变量类型而不仅仅是单独的 float
变量,在我们这个例子中用的是 vec3
。这样我们便获得了混合颜色单独通道 .r
,.g
和 .b
的能力。
#version 320 es
precision mediump float;
#define PI 3.141592653589793f;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
vec3 colorA = vec3(0.149, 0.141, 0.912);
vec3 colorB = vec3(1.000, 0.833, 0.224);
float plot (vec2 st, float pct){
return smoothstep(pct-0.01, pct, st.y) -
smoothstep(pct, pct+0.01, st.y);
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(0.0);
vec3 pct = vec3(st.x);
pct.r = smoothstep(0.0, 1.0, st.x);
float result = st.x * PI;
pct.g = sin(result);
pct.b = pow(st.x, 0.5);
color = mix(colorA, colorB, pct);
// Plot transition lines for each channel
color = mix(color, vec3(1.0, 0.0, 0.0), plot(st, pct.r));
color = mix(color, vec3(0.0, 1.0, 0.0), plot(st, pct.g));
color = mix(color, vec3(0.0, 0.0, 1.0), plot(st, pct.b));
FragColor = vec4(color, 1.0f);
}
HSB:
不能脱离色彩空间来谈论颜色。正如所知,除了rgb值,有其他不同的方法去描述定义颜色。
HSB代表色相,饱和度和亮度(或称为值)。这更符合直觉也更有利于组织颜色。将x坐标(位置)映射到Hue值并将y坐标映射到明度,我们就得到了五彩的可见光光谱。这样的色彩空间分布实现起来非常方便,比起RGB,用HSB来拾取颜色更直观。
- H(Hue):表示颜色的类型(例如红色,绿色或者黄色).取值范围为0—360.其中每一个值代表一种颜色.
- S(Saturation):颜色的饱和度从0到1有时候也称为纯度.(0表示灰度图,1表示纯的颜色)
- B(Brightness or Value):颜色的明亮程度从0到1(0表示黑色,1表示特定饱和度的颜色)
下面是rgb2hsv()
和 hsv2rgb()
函数的例子:
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
vec3 rgb2hsb(in vec3 c){
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz),
vec4(c.gb, K.xy),
step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r),
vec4(c.r, p.yzx),
step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)),
d / (q.x + e),
q.x);
}
// Function from Iñigo Quiles
// https://www.shadertoy.com/view/MsS3Wc
vec3 hsb2rgb(in vec3 c){
vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0, 4.0, 2.0),
6.0)-3.0)-1.0,
0.0,
1.0);
rgb = rgb*rgb*(3.0-2.0*rgb);
return c.z * mix(vec3(1.0), rgb, c.y);
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(0.0);
color = hsb2rgb(vec3(st.x, 1.0, st.y));
FragColor = vec4(color, 1.0f);
}
四、形状:
正方形:
下面是一个画一个正方形的代码,思路为:step()函数会让每一个小于0.1的像素变成黑色(vec(0.0)并将其余的变成白色(vec3(1.0))。left
乘 bottom
效果相当于逻辑 AND —— 当 x y 都为 1.0 时乘积才能是 1.0。这样做的效果就是画了两条黑线,一个在画布的底边另一个在左边。同理top和right也是这样。
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
vec3 color = vec3(0.0);
// bottom-left
vec2 bl = step(vec2(0.1), st);
float pct = bl.x * bl.y;
// top-right
vec2 tr = step(vec2(0.1), 1.0-st);
pct *= tr.x * tr.y;
color = vec3(pct);
FragColor = vec4(color, 1.0f);
}
圆:
圆的实现有几种方法:
- 像素到中心的距离:
- 从像素到中心的矢量长度;
- 从像素到中心的向量的平方根;
distance()这个函数其实内部调用length()函数,计算不同两点的距离(在此例中是像素坐标和画布中心的距离)。length()函数内部只不过是用平方根sqrt()计算斜边的方程。
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
vec3 color = vec3(0.0);
// bottom-left
vec2 bl = step(vec2(0.1), st);
float pct = bl.x * bl.y;
// top-right
vec2 tr = step(vec2(0.1), 1.0-st);
pct *= tr.x * tr.y;
color = vec3(pct);
FragColor = vec4(color, 1.0f);
}
距离场:
我们可也可以从另外的角度思考上面的例子:把它当做海拔地图(等高线图)——越黑的地方意味着海拔越高。想象下,你就在圆锥的顶端,那么这里的渐变就和圆锥的等高线图有些相似。到圆锥的水平距离是一个常数0.5。这个距离值在每个方向上都是相等的。通过选择从那里截取这个圆锥,你就会得到或大或小的圆纹面。
其实我们是通过“空间距离”来重新解释什么是图形。这种技巧被称之为“距离场”,从字体轮廓到3D图形被广泛应用。
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
float circle(in vec2 _st, in float _radius){
vec2 dist = _st-vec2(0.5, 0.3);
return 1.-smoothstep(_radius-(_radius*0.01),
_radius+(_radius*0.01),
dot(dist, dist)*5.0);
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
//获取屏幕分辨率的长宽比
float rateXY = u_resolution.x / u_resolution.y;
vec2 st1 = vec2(st.x * rateXY, st.y);
vec3 color = vec3(circle(st1, 0.2));
FragColor = vec4(color, 1.0f);
}
距离场景2:
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
st.x *= u_resolution.x/u_resolution.y;
vec3 color = vec3(0.0);
float d = 0.0;
// Remap the space to -1. to 1.
st = st *2.0-1.0;
// Make the distance field
d = length(abs(st)-0.3);
// d = length( min(abs(st)-.3,0.) );
// d = length( max(abs(st)-.3,0.) );
// Visualize the distance field
color = vec3(fract(d*10.0));
// Drawing with the distance field
// color = vec3(step(0.3, d));
// color = vec3(step(0.3,d) * step(d,0.4));
FragColor = vec4(color, 1.0f);
}
极坐标图形:
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(0.0);
vec2 pos = vec2(0.5)-st;
float r = length(pos)*2.0;
float a = atan(pos.y, pos.x);
float f = cos(a*3.0);
// f = abs(cos(a*3.));
// f = abs(cos(a*2.5))*.5+.3;
// f = abs(cos(a*12.)*sin(a*3.))*.8+.1;
// f = smoothstep(-.5,1., cos(a*10.))*0.2+0.5;
color = vec3(1.0-smoothstep(f, f+0.02, r));
// Drawing with the distance field
// color = vec3(step(0.3, d));
// color = vec3(step(0.3,d) * step(d,0.4));
FragColor = vec4(color, 1.0f);
}
五、Matrices(二维矩阵):
如上图形 - 而如何移动它们的技巧则是借助移动它们自身的参考坐标系。我们只需要给 st
变量加上一个包含每个片段的位置的向量。这样就移动了整个坐标系。
平移:
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
float box(in vec2 _st, in vec2 _size){
_size = vec2(0.5) - _size*0.5;
vec2 uv = smoothstep(_size,
_size+vec2(0.001),
_st);
uv *= smoothstep(_size,
_size+vec2(0.001),
vec2(1.0)-_st);
return uv.x*uv.y;
}
float crossT(in vec2 _st, float _size){
return box(_st, vec2(_size, _size/4.0)) +
box(_st, vec2(_size/4.0, _size));
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
vec3 color = vec3(0.0);
// 为了移动十字架,我们移动了空间
vec2 translate = vec2(cos(u_time), sin(u_time));
st += translate*0.35;
// 在背景上显示空间的坐标
color = vec3(st.x,st.y,0.0);
// 在视窗上添加形状
color += vec3(crossT(st, 0.15));
FragColor = vec4(color, 1.0f);
}
矩阵:
要移动物体,我们同样需要移动整个空间(坐标)系统。为此我们将使用一个矩阵。矩阵是一个通过行和列定义的一组数。用矩阵乘以一个向量是用一组精确的规则定义的,这样做是为了以一组特定的方式来改变向量的值。
GLSL本身支持2维,3维和4维方阵(mm矩阵):mat2、mat3、mat4。GLSL同样支持矩阵相乘 (```)和特殊矩阵函数([
matrixCompMult()```](../glossary/?search=matrixCompMult))。
- 矩阵平移:基于矩阵的特性,我们便有可能构造一个矩阵来产生特定的作用。比如我们可以用一个矩阵来平移一个向量。
- 矩阵旋转: 可以用矩阵来旋转坐标系统。
下面构成2维旋转的矩阵的代码,将二维向量绕 vec2(0.0)
点旋转。
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
- 矩阵缩放:如果你用过3D建模软件或者 Processing中的 pushmatrix 和 popmatrix 函数,你会知道矩阵也可以被用来缩放物体的大小。
根据上面的公式,我们知道如何构造一个2D缩放矩阵:
mat2 scale(vec2 _scale){
return mat2(_scale.x,0.0,
0.0,_scale.y);
}
矩阵旋转:
#version 320 es
precision mediump float;
#define PI 3.14159265359;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
mat2 rotate2d(float _angle){
return mat2(cos(_angle), -sin(_angle),
sin(_angle), cos(_angle));
}
float box(in vec2 _st, in vec2 _size){
_size = vec2(0.5) - _size*0.5;
vec2 uv = smoothstep(_size,
_size+vec2(0.001),
_st);
uv *= smoothstep(_size,
_size+vec2(0.001),
vec2(1.0)-_st);
return uv.x*uv.y;
}
float crossR(in vec2 _st, float _size){
return box(_st, vec2(_size, _size/4.)) +
box(_st, vec2(_size/4., _size));
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
vec3 color = vec3(0.0);
// 将空间从中心移动到vec2(0.0)
st -= vec2(0.5);
// 旋转空间
float angle = sin(u_time)*PI;
st = rotate2d(angle) * st;
// 将其移回原位
st += vec2(0.5);
// 在背景上显示空间的坐标
color = vec3(st.x,st.y,0.0);
// 在视窗上添加形状
color += vec3(crossR(st, 0.4));
FragColor = vec4(color, 1.0f);
}
矩阵缩放:
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
mat2 scale(vec2 _scale){
return mat2(_scale.x, 0.0,
0.0, _scale.y);
}
float box(in vec2 _st, in vec2 _size){
_size = vec2(0.5) - _size*0.5;
vec2 uv = smoothstep(_size,
_size+vec2(0.001),
_st);
uv *= smoothstep(_size,
_size+vec2(0.001),
vec2(1.0)-_st);
return uv.x*uv.y;
}
float crossS(in vec2 _st, float _size){
return box(_st, vec2(_size, _size/4.0)) +
box(_st, vec2(_size/4.0, _size));
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
vec3 color = vec3(0.0);
st -= vec2(0.5);
st = scale(vec2(sin(u_time)+1.0)) * st;
st += vec2(0.5);
// 在背景上显示空间的坐标
color = vec3(st.x, st.y, 0.0);
// 在视窗上添加形状
color += vec3(crossS(st, 0.2));
FragColor = vec4(color, 1.0f);
}
YUV三维矩阵转换:
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
// YUV to RGB matrix
mat3 yuv2rgb = mat3(1.0, 0.0, 1.13983,
1.0, -0.39465, -0.58060,
1.0, 2.03211, 0.0);
// RGB to YUV matrix
mat3 rgb2yuv = mat3(0.2126, 0.7152, 0.0722,
-0.09991, -0.33609, 0.43600,
0.615, -0.5586, -0.05639);
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
vec3 color = vec3(0.0);
// UV值从-1到1
// 因此,我们需要重新映射st(0.0到1.0)
st -= 0.5;// becomes -0.5 to 0.5
st *= 2.0;// becomes -1.0 to 1.0
// 我们将st作为三维向量的y&z值传递给正确乘以3x3矩阵
color = yuv2rgb * vec3(0.5, st.x, st.y);
FragColor = vec4(color, 1.0f);
}
六、图案:
我们的策略依然基于乘以空间坐标(0到1之间),这样我们的画的在0到1之间的图形就会重复地形成网格。
图案内部应用标量重复:
fract()函数,它返回一个数的分数部分,本质上是除1的余数。换句话说fract()返回小数点后的数。 我们单位化的坐标系变量 (st
) 已经是 0.0 到 1.0 之间的了。
#version 320 es
precision mediump float;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
float circle(in vec2 _st, in float _radius){
vec2 l = _st-vec2(0.5);
return 1.0-smoothstep(_radius-(_radius*0.01),
_radius+(_radius*0.01),
dot(l, l)*4.0);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
st.x *= u_resolution.x / u_resolution.y;
vec3 color = vec3(0.0);
st *= 5.0;// Scale up the space by 3
st = fract(st);// Wrap around 1.0
// Now we have 9 spaces that go from 0-1
color = vec3(st, 0.0);
color = vec3(circle(st, 0.5));
FragColor = vec4(color, 1.0f);
}
图案内部应用矩阵:
#version 320 es
precision mediump float;
#define PI 3.14159265358979323846f;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
vec2 rotate2D(vec2 _st, float _angle){
_st -= 0.5;
_st = mat2(cos(_angle), -sin(_angle),
sin(_angle), cos(_angle)) * _st;
_st += 0.5;
return _st;
}
vec2 tile(vec2 _st, float _zoom){
_st *= _zoom;
return fract(_st);
}
float box(vec2 _st, vec2 _size, float _smoothEdges){
_size = vec2(0.5)-_size*0.5;
vec2 aa = vec2(_smoothEdges*0.5);
vec2 uv = smoothstep(_size, _size+aa, _st);
uv *= smoothstep(_size, _size+aa, vec2(1.0)-_st);
return uv.x*uv.y;
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
st.x *= u_resolution.x / u_resolution.y;
vec3 color = vec3(0.0);
// Divide the space in 4
st = tile(st, 4.0);
// Use a matrix to rotate the space 45 degrees
float angle = 0.25f * PI;
st = rotate2D(st, angle);
// Draw a square
color = vec3(box(st, vec2(0.7), 0.01));
// color = vec3(st,0.0);
FragColor = vec4(color, 1.0f);
}
图案偏移:
#version 320 es
precision mediump float;
#define PI 3.14159265358979323846f;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
vec2 brickTile(vec2 _st, float _zoom){
_st *= _zoom;
// Here is where the offset is happening
_st.x += step(1., mod(_st.y,2.0)) * 0.5;
return fract(_st);
}
float box(vec2 _st, vec2 _size){
_size = vec2(0.5)-_size*0.5;
vec2 uv = smoothstep(_size,_size+vec2(1e-4),_st);
uv *= smoothstep(_size,_size+vec2(1e-4),vec2(1.0)-_st);
return uv.x*uv.y;
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
st.x *= u_resolution.x / u_resolution.y;
vec3 color = vec3(0.0);
// Modern metric brick of 215mm x 102.5mm x 65mm
// http://www.jaharrison.me.uk/Brickwork/Sizes.html
// st /= vec2(2.15,0.65)/1.5;
// Apply the brick tiling
st = brickTile(st,5.0);
color = vec3(box(st,vec2(0.9)));
// Uncomment to see the space coordinates
color = vec3(st,0.0);
FragColor = vec4(color, 1.0f);
}
Truchet瓷砖:
目前我们学了如何区分奇数行/列或偶数行/列,(类似的),(我们也)可能再用(这个技巧)根据位置来设计元素。Truchet瓷砖,即一个单一设计元素可以以四种不同的方式呈现,仔细观察 rotateTilePattern()
函数, 它把坐标空间细分成四个单元并赋予每一个旋转值。
#version 320 es
precision mediump float;
#define PI 3.14159265358979323846f;
out vec4 FragColor;//out代表输出至下一个着色器
uniform vec2 u_resolution;//视窗的分辨率
uniform float u_time;//传入的时间变量
vec2 rotate2D (vec2 _st, float _angle) {
_st -= 0.5;
_st = mat2(cos(_angle), -sin(_angle),
sin(_angle), cos(_angle)) * _st;
_st += 0.5;
return _st;
}
vec2 tile (vec2 _st, float _zoom) {
_st *= _zoom;
return fract(_st);
}
vec2 rotateTilePattern(vec2 _st){
// Scale the coordinate system by 2x2
_st *= 2.0;
// Give each cell an index number
// according to its position
float index = 0.0;
index += step(1., mod(_st.x, 2.0));
index += step(1., mod(_st.y, 2.0))*2.0;
// |
// 2 | 3
// |
//--------------
// |
// 0 | 1
// |
// Make each cell between 0.0 - 1.0
_st = fract(_st);
// Rotate each cell according to the index
if (index == 1.0){
// Rotate cell 1 by 90 degrees
float angle = 0.5* PI;
_st = rotate2D(_st, angle);
} else if (index == 2.0){
// Rotate cell 2 by -90 degrees
float angle = - 0.5 * PI;
_st = rotate2D(_st, angle);
} else if (index == 3.0){
// Rotate cell 3 by 180 degrees
float angle = PI;
_st = rotate2D(_st, angle);
}
return _st;
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
st.x *= u_resolution.x / u_resolution.y;
vec3 color = vec3(0.0);
st = tile(st, 3.0);
st = rotateTilePattern(st);
// Make more interesting combinations
// st = tile(st, 2.0);
float angle2D1 = - u_time *0.25 *PI;
st = rotate2D(st, angle2D1);
st = rotateTilePattern(st*2.0);
float angle2D2 = u_time*0.25 * PI;
st = rotate2D(st, angle2D2);
// step(st.x,st.y) just makes a b&w triangles
// but you can use whatever design you want.
// Uncomment to see the space coordinates
color = vec3(step(st.x, st.y));
FragColor = vec4(color, 1.0f);
}
参考资料:
The Book of Shader
thebookofshader的github地址:thebookofshader
GitHub - McNopper/OpenGL: OpenGL 3 and 4 with GLSL