提示
教程例子都可以到下面网址进行运行,不需要另外安装软件环境:
官方提供在线编写shader工具:https://thebookofshaders.com/edit.php
glslsandbox网站:http://glslsandbox.com/
shadertoy网站:https://www.shadertoy.com/
本文提到的关键字
关键字 | 描述 | 图像 |
---|---|---|
mix(a,b,float x) | 混合ab颜色,根据x的值,x~0偏向a,x~1偏向b |
|
色彩变量
以x,y,z定义颜色是不是有些奇怪?正因如此,我们有其他方法访问这些变量——以不同的名字。.x, .y, .z也可以被写作.r, .g, .b 和 .s, .t, .p。(.s, .t, .p通常被用做后面章节提到的贴图空间坐标)你也可以通过使用索引位置[0], [1] 和 [2]来访问向量
vec3 red = vec3(1.0,0.0,0.0);
red.x = 1.0;
red.y = 0.0;
red.z = 0.0;
下面的代码展示了所有访问相同数据的方式:
vec4 vector;
vector[0] = vector.r = vector.x = vector.s;
vector[1] = vector.g = vector.y = vector.t;
vector[2] = vector.b = vector.z = vector.p;
vector[3] = vector.a = vector.w = vector.q;
鸡尾酒
GLSL中向量类型的另一大特点是可以用你需要的任意顺序简单地投射和混合(变量)值。这种能力被(形象地)称为:鸡尾酒。
vec3 yellow, magenta, green;
// Making Yellow
yellow.rg = vec2(1.0); // 同时设置红绿空间
yellow[2] = 0.0; // 设置蓝色值为0
// Making Magenta
magenta = yellow.rbg; // 调换了蓝绿空间
// Making Green
green.rgb = yellow.bgb; // 将蓝色空间覆盖到红色空间
混合颜色
在黄蓝之间渐变过渡
#ifdef GL_ES
precision mediump float;
#endif
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);
gl_FragColor = vec4(color,1.0);
}
绘制渐变
#ifdef GL_ES
precision mediump float;
#endif
#define PI 3.14159265359
uniform vec2 u_resolution;
uniform vec2 u_mouse;
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);
// pct.g = sin(st.x*PI);
// 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));
gl_FragColor = vec4(color,1.0);
}
在05讲中的代码,稍微改一下,之前混合颜色是通过公式(1-ps)a+psb,也就是用mix去简化了它的写法mix(a,b,ps)
色相
不是出卖色相的色相,是HSB(色相,饱和度和亮度)
将x坐标(位置)映射到Hue值并将y坐标映射到明度,我们就得到了五彩的可见光光谱。这样的色彩空间分布实现起来非常方便,比起RGB,用HSB来拾取颜色更直观。
我们不能脱离色彩空间来谈论颜色。正如你所知,除了rgb值,有其他不同的方法去描述定义颜色。HSB代表色相,饱和度和亮度(或称为值)。这更符合直觉也更有利于组织颜色。稍微花些时间阅读下面的
rgb2hsv()
和hsv2rgb()
函数。
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);
}
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);
}
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform float u_time;
void main(){
vec2 st = gl_FragCoord.xy/u_resolution;
vec3 color = vec3(0.0);
// We map x (0.0 - 1.0) to the hue (0.0 - 1.0)
// And the y (0.0 - 1.0) to the brightness
color = hsb2rgb(vec3(st.x,1.0,st.y));
gl_FragColor = vec4(color,1.0);
}
极坐标下的HSB
HSB原本是在极坐标下产生的(以半径和角度定义)而并非在笛卡尔坐标系(基于xy定义)下。将HSB映射到极坐标我们需要取得角度和到像素屏中点的距离。由此我们运用 length() 函数和 atan(y,x) 函数(在GLSL中通常用atan(y,x))。
当用到矢量和三角学函数时,vec2, vec3 和 vec4被当做向量对待,即使有时候他们代表颜色。我们开始把颜色和向量同等的对待,事实上你会慢慢发现这种理念的灵活性有着相当强大的用途。
一旦我们得到角度和长度,我们需要单位化这些值:0.0到1.0。在27行, atan(y,x) 会返回一个介于-PI到PI的弧度值(-3.14 to 3.14),所以我们要将这个返回值除以
TWO_PI
(在code顶部定义了)来得到一个-0.5到0.5的值。这样一来,用简单的加法就可以把这个返回值最终映射到0.0到1.0。半径会返回一个最大值0.5(因为我们计算的是到视口中心的距离,而视口中心的范围已经被映射到0.0到1.0),所以我们需要把这个值乘以二来得到一个0到1.0的映射。
#ifdef GL_ES
precision mediump float;
#endif
#define TWO_PI 6.28318530718
uniform vec2 u_resolution;
uniform float u_time;
// 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;
vec3 color = vec3(0.0);
// Use polar coordinates instead of cartesian
vec2 toCenter = vec2(0.5)-st;
float angle = atan(toCenter.y,toCenter.x);
float radius = length(toCenter)*2.0;
// Map the angle (-PI to PI) to the Hue (from 0 to 1)
// and the Saturation to the radius
color = hsb2rgb(vec3((angle/TWO_PI)+0.5,radius,1.0));
gl_FragColor = vec4(color,1.0);
}
如果你仔细观察用来拾色的色轮(见下图),你会发现它用一种根据RYB色彩空间的色谱。例如,红色的对面应该是绿色,但在我们的例子里是青色。你能找到一种修复的方式来让它看起来和下图一样么?[提示:这是用塑形函数的好机会!]
函数和变量
int newFunction(in vec4 aVec4, // read-only
out vec3 aVec3, // write-only
inout int aInt); // read-write
在进入下一章之前让我们停下脚步回顾下。复习下之前例子的函数。你会注意到变量类型之前有个限定符
in
,在这个 qualifier (限定符)例子中它特指这个变量是只读的。在之后的例子中我们会看到可以定义一个out
或者inout
变量。最后这个inout
,再概念上类似于参照输入一个变量,这意味着我们有可能修改一个传入的变量。
或许你还不相信我们可以用所有这些元素来画一些炫酷的东西。下一章我们会学习如何结合所有这些技巧通过融合 (blending) 空间来创造几何形状。没错。。。融合(blending) 空间。
https://thebookofshaders.com/06/?lan=ch