先唠嗑
吾今日见天高气爽,百里无云,大喜。平日事儿以ShaderToy为趣之见阅,LeetCode 配之。虽常入其闻之,但未尝知其所以也。友曰,君何其惑所极其精力也?趣之非普众所受,遂答之。
ShaderToy - 着色器玩具
作为一个时常在ShaderToy潜水的“巨佬”,在看到社区上各位巨巨佬的作品时,总不禁感叹,图形的世界真奇妙。只有你想不到的,没有他们搞不定的。特别是上面的各种效果,你能想象,不用模型,只用纯数学的方式,就能实现惊艳的效果。对于痴迷其中的我,完全是不能自拔。为了带更多的人入坑,我决定,分享一下我经常玩的shader撸图函数,还有后处理效果。让更多的小伙伴为图形学的社区活力增添一丝丝浪花。
准备工作
我希望你们充分并了解webgl 的基本知识,还有glsl 的语法基础,还有 高中的发霉的数学,好好拿洗洁精擦擦。盘顺了再来玩这个盘。可在WebGL基本基础翻阅。
巨佬们的展示
光线追踪
赛博朋克风
图形噪声
正式开始
有向距离场 SDF 的概念
对于一些基本的图形造型,例如圆形,矩形,间隔面,抑或是相对复杂的几何面的组合图形。其实本质上都是SDF 即距离场的实现,在这里,我们主要讨论的是平面2D的距离场。所以我们的造型函数一半有且只有连个维度分量的计算,至于涉及到更高维度的变换,既有可能是高维在拍平到二维的高维过渡变换。
基本图形
矩形推导思路
假如一个篮球场 100米 * 100米,里面有一个30 * 30 的正方形区域,起始点为(10,10),我们可以简单理解为下面这个图例。
假设你是区域内其中任意一个像素点,你应该如何判断自己是否在这个白色区域中间呢? 对于 聪明的我们来说,我们假设那任意一点坐标为 ,整个区域为,如果定义白色的区域就有下面的理解
对于 任意的,
这样好像就可以了,我们把10 转换下单位,有了这样的代码跟效果。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
uv.x /= iResolution.y/iResolution.x;
uv.y = 1. -uv.y;
if(uv.x >= 0.1 && uv.y >= 0.1 && uv.x <= 0.4 && uv.y <= 0.4){
fragColor = vec4(1.,1.,1.,1.);
}else{
fragColor = vec4(0.,0.,0.,1.0);
}
// Output to screen
}
但是从GPU的角度来说,这段代码还可以继续优化。由于GPU并不擅长逻辑判断运算。我们看看能不能不用判断就可以实现这种效果呢? 答案当然是可以的,我们这边介绍glsl 第一个造型函数 step
step() 函数
step()函数的定义,如果x < edge,返回0.0,否则返回1.0
genType step (genType edge, genType x),genType step (float edge, genType x)
我们使用第一个重载的函数 定义左边的边界
float left = step(0.1,uv.x);
定义右边的边界
float right = step(uv.x,0.4);
定义上边的边界
float top = step(0.1,uv.y);
定义底边的边界
float bottom = step(uv.y,0.4);
然后组合起来
float pixelValue = top + right + left + bottom;
```glsl
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
uv.x /= iResolution.y/iResolution.x;
uv.y = 1. -uv.y;
float left = step(0.1,uv.x);
float right = step(uv.x,0.4);
float top = step(0.1,uv.y);
float bottom = step(uv.y,0.4);
float pixelValue = top * right * left * bottom;
fragColor = vec4(vec3(pixelValue),1.0);
// Output to screen
}
当然了,这样也是可以的。
圆形推导思路
同样道理,高中我们就已经知道了圆的解析式 ,所以我们可以直接写出这样的代码
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
uv.x /= iResolution.y/iResolution.x;
uv.y = 1. -uv.y;
// 圆心(0.4,0.4)
uv -= vec2(.40);
if(uv.x * uv.x + uv.y * uv.y <= .05){
fragColor = vec4(vec3(1.),1.0);
}else{
fragColor = vec4(vec3(0.),1.0);
}
// Output to screen
}
或许你已经看到了if ,我们通过距离场的思想来理解圆,你会发现凡是任意一点离圆心的距离小于等于半径的话,该点就在圆里面。
distance() 函数
float distance (genType p0, genType p1)
计算向量p0,p1之间的距离
glsl 内置了 distance() 函数来帮助我们解决这类问题 所以就有了下面的代码
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
uv.x /= iResolution.y/iResolution.x;
uv.y = 1. -uv.y;
// 圆心(0.3,0.3) r = 0.2
float result = step(distance(uv,vec2(.3,.3)),.2);
fragColor = vec4(vec3(result ),1.0);
// Output to screen
}
组合封装
我们把上面矩形跟圆形的代码封装成一个函数的话,我们就可以组合一些图形显示了
float Circle(vec2 uv,vec2 center,float r){
float result = step(distance(uv,center),r);
return result;
}
float Rect(vec2 uv,vec2 startPoint, float w,float h){
float left = step(startPoint.x,uv.x);
float right = step(uv.x,startPoint.x + w);
float top = step(startPoint.y ,uv.y);
float bottom = step(uv.y,startPoint.y + h);
float pixelValue = top * right * left * bottom;
return pixelValue;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
uv.x /= iResolution.y/iResolution.x;
uv.y = 1. -uv.y;
float circleA = Circle(uv,vec2(.2,.2),.1);
float circleB = Circle(uv,vec2(.7,.5),.2);
float circleC = Circle(uv,vec2(.2,.5),.05);
float rectA = Rect(uv,vec2(.2,.2),.2,0.2);
float rectB = Rect(uv,vec2(.31,.5),.1,0.2);
float rectC = Rect(uv,vec2(.2,.5),.1,0.2);
float result = circleA + circleB + circleC;
result += rectA + rectB + rectC;
fragColor = vec4(vec3(result),1.0);
// Output to screen
}
颜色遮罩
我们最后直接根据uv坐标作为颜色值给他加个颜色遮罩
fragColor = vec4(vec3(result)* vec3(uv,1.0),1.0);
再把shadertoy内置时间变量加上
fragColor = vec4(vec3(result)*cos(iTime+uv.xyx+vec3(0,2,4)),1.0);
完美~~
To Be Continued
今天的分享就到这里,下一期我们介绍更好玩的一些造型函数跟造型方法