Color(颜色)
前面章节
我们目前为止还未涉及到GLSL的向量类型。在我们深入向量之前,学习更多关于变量和色彩主题是一个了解向量类型的好方法。
若你熟悉面向对象的编程范式(或者说编程思维模式),你一定注意到我们以一种类C的 struct的方式访问向量数据的内部分量。
vec3 red = vec3(1.0,0.0,0.0);
red.x = 1.0;
red.y = 0.0;
red.z = 0.0;
以x,y,z定义颜色是不是有些奇怪?正因如此,我们有其他方法访问这些变量——以不同的名字。.x, .y, .z也可以被写作.r, .g, .b 和 .s, .t, .p。(.s, .t, .p通常被用做后面章节提到的贴图空间坐标)你也可以通过使用索引位置[0], [1] 和 [2]来访问向量.
下面的代码展示了所有访问相同数据的方式:
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); // Assigning 1. to red and green channels
yellow[2] = 0.0; // Assigning 0. to blue channel
// Making Magenta
magenta = yellow.rbg; // Assign the channels with green and blue swapped
// Making Green
green.rgb = yellow.bgb; // Assign the blue channel of Yellow (0) to red and blue channels
混合颜色
现在你了解到如何定义颜色,是时候将先前所学的整合一下了!在GLSL中,有个十分有用的函数:mix(),这个函数让你以百分比混合两个值。猜下百分比的取值范围?没错,0到1!完美!学了这么久的基本功,是时候来用一用了!
T mix(T x,T y, T a) T mix(T x,T y, float a) 取 x,y 的线性混合,x*(1-a)+y*a
看下下面的这句代码color = mix(colorA, colorB, pct),这里展示了我们如果是用随时间变化的sin绝对值来混合 colorA 和 colorB。
#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);
}
cocos creator effect shader 代码
//
vec3 colorA = vec3(0.149,0.141,0.912);
vec3 colorB = vec3(1.000,0.833,0.224);
void main () {
vec4 o = vec4(1, 1, 1, 1);
#if USE_TEXTURE
CCTexture(texture, v_uv0, o);
#endif
o *= v_color;
ALPHA_TEST(o);
vec3 color = vec3(0.0);
float pct = abs(sin(cc_time.x));
color = mix(colorA, colorB, pct);
gl_FragColor = vec4(color,1.0);
}
试着来 show 一下你所学到的:
给颜色赋予一个有趣的过渡。想想某种特定的感情。哪种颜色更具代表性?他如何产生?又如何褪去?再想想另外的一种感情以及对应的颜色。然后改变上诉代码中的代表这种情感的开始颜色和结束颜色。Robert Penner 开发了一些列流行的计算机动画塑形函数,被称为缓动函数。你可以研究这些例子并得到启发,但最好你还是自己写一个自己的缓动函数。
玩玩渐变
mix() 函数有更多的用处。我们可以输入两个互相匹配的变量类型而不仅仅是单独的 float 变量,在我们这个例子中用的是 vec3。这样我们便获得了混合颜色单独通道 .r,.g 和 .b的能力。
试试下面的例子。正如前面一个例子,我们用一条线来可视化根据单位化x坐标的过渡。现在所有通道都按照同样的线性变换过渡。
现在试试取消25行的注释,看看会发生什么。然后再试试取消26行和27行。记住直线代表了colorA 和 colorB每个通道的混合比例。
#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);
}
对于注释的三句代码,可以取消注释看看分别有什么效果,下面给出在cocos上的代码
//
vec3 playGradients()
{
vec3 colorA = vec3(0.149,0.141,0.912);
vec3 colorB = vec3(1.000,0.833,0.224);
vec2 st=v_uv0.xy/vec2(1.0,1.0);
vec3 pct = vec3(st.x);
// float pct = abs(sin(cc_time.x));
// Mix uses pct (a value from 0-1) to
// mix the two colors,以百分比混合两个值(0-1)
vec3 color = mix(colorA, colorB, pct);
pct.r = smoothstep(0.0,1.0, st.x);
//r和b的都是前面学过的效果。这里看下g这两个公式
//其实都是根据三角函数sin(x)去变化而成的,这句是不会动的,只是改变了其频率
//pct.g = abs(sin(st.x*6.14157726));
//y=A*sin(W*x+t)+y0。其中A代表振幅,也就是上下的幅度。W代表周期,T=(2*PI)/W
//t代表初相位,这里我们看到的效果会移动就是因为初相位加上了cc_time,y0 代表Y轴初始偏移量
pct.g = 0.5*sin(st.x*3.14*2.0+cc_time.x*2.0)+0.5;
pct.b = pow(st.x,0.5);
//color = mix(colorA, colorB, pct);
// Plot transition lines for each channel
//下面的每句代表代表着每一天线,R,G,B
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));
return color;
}
cocos 代码,这里我把效果的代码封装成到了一个方法里面,你可以直接把他放到main方法上面去,然后在main方法里调用,把color赋值上去color=playGradients()。
这里最重要的就是这个三角函数y=Asin(Wx+t)+y0。在shader中三角函数是用的很频繁的一个公式,所以一定要好好理解公式里每个值所代表的意义,都有什么作用。这里我们可以改变某一个值看其变化,就可以知道每个变量所对应的效果是怎样的。
color=playGradients();
gl_FragColor = vec4(color,1.0);
最后把color赋值给gl_FragColor的效果如下:
HSB
我们不能脱离色彩空间来谈论颜色。正如你所知,除了rgb值,有其他不同的方法去描述定义颜色。
HSB 代表色相,饱和度和亮度(或称为值)。这更符合直觉也更有利于组织颜色。稍微花些时间阅读下面的 rgb2hsv() 和 hsv2rgb() 函数。
对于颜色值,RGB 可能是我们接触最多的颜色模型,图像中的任何颜色都是由红色(R)、绿色(G)、蓝色(B) 这三个通道合成的,这三种颜色可以组合成几乎所有的颜色。
然而,它并不直观,比如我随便说一个rgb值,你能猜到他是什么颜色吗?几乎不可能,所以,后面引入了HSV、HSL等颜色模型。 HSV 相对于 RGB 来说 是一种更加直观的颜色模型,HSV更加符合我们人类视觉。
HSL和HSV 概念:
HSL 即色相、饱和度、亮度(英语:Hue, Saturation, Lightness)。
HSV 即色相、饱和度、明度(英语:Hue, Saturation, Value),又称HSB,其中B即英语:Brightness。
色相(H) 是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。
饱和度(S) 是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。
明度(V),亮度(L), 取0-100%
关于具体的这两个概念可以看下这个: HSV 和 RGB 的相互转换
将x坐标(位置)映射到Hue值并将y坐标映射到明度,我们就得到了五彩的可见光光谱。这样的色彩空间分布实现起来非常方便,比起RGB,用HSB来拾取颜色更直观。
#ifdef GL_ES
precision mediump float;
#endif
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;
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
关于极坐标的一些基础可以参考下面两个文章:
极坐标与笛卡尔坐标
极坐标Polar Coordinates
HSB原本是在极坐标下产生的(以半径和角度定义)而并非在笛卡尔坐标系(基于xy定义)下。将HSB映射到极坐标我们需要取得角度和到像素屏中点的距离。由此我们运用 length() 函数和 atan(y,x) 函数(在GLSL中通常用atan(y,x))。
当用到矢量和三角学函数时,vec2, vec3 和 vec4被当做向量对待,即使有时候他们代表颜色。我们开始把颜色和向量同等的对待,事实上你会慢慢发现这种理念的灵活性有着相当强大的用途。
注意:如果你想了解,除length()以外的诸多几何函数,例如:distance(), dot(), cross, normalize(), faceforward(), reflect() 和 refract()。 GLSL也有与向量相关的函数:lessThan(), lessThanEqual(), greaterThan(), greaterThanEqual(), equal() and notEqual()。
一旦我们得到角度和长度,我们需要单位化这些值: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的映射。
正如你所见,这里我们的游戏都是关于变换和映射到一个0到1这样我们乐于处理的值。
#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);
}
完整源码:
源码上我加了个mixType以便可以在编译器上根据1,2,3来方便切换到每个效果
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
CCEffect %{
techniques:
- passes:
- vert: vs
frag: fs
blendState:
targets:
- blend: true
rasterizerState:
cullMode: none
properties:
texture: { value: white }
alphaThreshold: { value: 0.5 }
mixType: { value: 1.0 }
}%
CCProgram vs %{
precision highp float;
#include <cc-global>
#include <cc-local>
in vec3 a_position;
in vec4 a_color;
out vec4 v_color;
#if USE_TEXTURE
in vec2 a_uv0;
out vec2 v_uv0;
#endif
void main () {
vec4 pos = vec4(a_position, 1);
#if CC_USE_MODEL
pos = cc_matViewProj * cc_matWorld * pos;
#else
pos = cc_matViewProj * pos;
#endif
#if USE_TEXTURE
v_uv0 = a_uv0;
#endif
v_color = a_color;
gl_Position = pos;
}
}%
CCProgram fs %{
precision highp float;
#include <alpha-test>
#include <texture>
#include <cc-global>
#include <cc-local>
in vec4 v_color;
#if USE_TEXTURE
in vec2 v_uv0;
uniform sampler2D texture;
#endif
uniform ColorMixType{
float mixType;
};
vec3 sunA=vec3(1.0,0.832,0.149);
vec3 sunB=vec3(0.988,0.686,0.003);
vec3 sunC=vec3(0.905,0.525,0.141);
vec3 colorA = vec3(0.149,0.141,0.912);
vec3 colorB = vec3(1.000,0.833,0.224);
vec2 center=vec2(0.5,0.5);
float radius=0.1;
float PI=3.1425726;
float plot(vec2 st,float pct)
{
return smoothstep(pct-0.02,pct,st.y)-smoothstep(pct,pct+0.02,st.y);
}
vec3 playGradients()
{
vec3 colorA = vec3(0.149,0.141,0.912);
vec3 colorB = vec3(1.000,0.833,0.224);
vec2 st=v_uv0.xy/vec2(1.0,1.0);
vec3 pct = vec3(st.x);
// float pct = abs(sin(cc_time.x));
// Mix uses pct (a value from 0-1) to
// mix the two colors,以百分比混合两个值(0-1)
vec3 color = mix(colorA, colorB, pct);
pct.r = smoothstep(0.0,1.0, st.x);
//r和b的都是前面学过的效果。这里看下g这两个公式
//其实都是根据三角函数sin(x)去变化而成的,这句是不会动的,只是改变了其频率
//pct.g = abs(sin(st.x*6.14157726));
//A*sin(W*x+B)+C。其中A代表振幅,也就是上下的幅度。W代表周期,T=(2*PI)/W
//B代表初相位,这里我们看到的效果会移动就是因为初相位加上了cc_time,C 代表Y轴初始偏移量
pct.g = 0.5*sin(st.x*3.14*2.0+cc_time.x*2.0)+0.5;
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));
return color;
}
float linePlot(float a,float b){
float dis=distance(center,v_uv0);
float temp=dis/radius;
return smoothstep(a,b,temp);
}
vec3 hsbToRgb(vec3 _vector){
vec3 _mod=mod(_vector.x*6.0+vec3(0.0,4.0,2.0),6.0);
vec3 rgb = clamp(abs(_mod-3.0)-1.0,0.0,1.0 );
rgb=rgb*rgb*(3.0-2.0*rgb);
return _vector.z*mix(vec3(1.0),rgb,_vector.y);
}
vec3 rgbToHsb(vec3 _vector){
vec4 k=vec4(0.0,-1.0/3.0,2.0/3.0,-1.0);
vec4 p=mix(vec4(_vector.bg,k.wz),vec4(_vector.gb,k.xy),step(_vector.b,_vector.g));
vec4 q=mix(vec4(p.xyw,_vector.r),vec4(_vector.r,p.yzx),step(p.x,_vector.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);
}
mat2 rotate2d(float angle)
{
return mat2(cos(angle),-sin(angle),sin(angle),cos(angle));
}
vec3 polarColor(vec2 st)
{
/* st-=vec2(.5);
st=rotate2d(sin(cc_time.x)*PI)*st;
st+=vec2(.5); */
vec2 center=vec2(.5)-st;
float angle=atan(center.y,center.x);
float radius=length(center)*2.0;
vec3 color=hsbToRgb(vec3(angle/(2.*PI)+.5,radius,1.));
return color;
}
void main () {
vec4 o = vec4(1, 1, 1, 1);
#if USE_TEXTURE
CCTexture(texture, v_uv0, o);
#endif
o *= v_color;
ALPHA_TEST(o);
vec2 st=v_uv0;
vec3 color = vec3(0.0);
float pct=0.;
if(mixType==1.)
{
pct=abs(sin(cc_time.x));
color=mix(colorA,colorB,pct);
}
else if(mixType==2.)
{
color=playGradients();
}
else if(mixType==3.)
{
//st+=vec2(cos(3.*cc_time.x),0.);
color=hsbToRgb(vec3(st.x,1.0,st.y));
}
else if(mixType==4.)
{
color=polarColor(v_uv0);
}
gl_FragColor =vec4(color,1.);
}
}%