文章目录
- 相关网址
- 前言
- 基本实现画一个圆步骤
- 1.初步-根据长度绘画圆
- 2.初步-根据1-length(uv)绘画圆
- 3.正式-绘画白色形状的圆
- (1)if逻辑代码实现
- (2)用shader的step函数
- 完善圆-实现绘画手环圆
- 1.if逻辑代码实现
- 2.用step函数代替if
- 用smoothstep代替step函数
- 1.smoothstep函数
- 2.使用smoothstep
- 3.完善shader代码
- 用smoothstep的小Bug
- 1.Bug说明
- 2.解决方法
- 方法一
- 方法二
- 测试smoothstep(edg0, edg1, distance)
相关网址
Cherno原视频网址:https://www.youtube.com/watch?v=xf7Y988cPRk
shader网站:https://www.shadertoy.com/
GLSL函数解释网址:https://docs.gl/
此文是记录对第一个网址学习的笔记和思考的过程。
鄙人作为shader初学者,文中应该会有错误,欢迎指正。
前言
-
基本思路
用Quad顶点包围一个范围作为画布(Canvas)用glsl控制画出一个圆内alpha为1,圆外的范围的alpha为0。
-
如何控制在圆内的alpha为1,圆外的alpha为0
用当前坐标点距离原点的长度、step函数来控制
基本实现画一个圆步骤
1.初步-根据长度绘画圆
- shader和效果图
-
说明
-
uv相当于在xy坐标系的坐标,xy坐标系的原点在中心
-
length(uv) = sqrt(u*u+v*v) 即长度
-
原点的坐标是(0, 0),distance是0,所以是黑色
假设x点(0, 0.5), distance是0.5,所以是灰色
所以形成
- 靠近原点的颜色是黑色
- 远离原点的颜色是灰色
-
2.初步-根据1-length(uv)绘画圆
-
shader+效果图
-
说明
distance= 1-length(uv);
原点的坐标是(0, 0),length(uv)是0,被1减去后,是白色1
假设边缘点x(0, 1), length(uv)是1,被1减去后,是黑色0
所以形成翻转颜色的效果
-
原先靠近圆心是黑色0,远离圆心逐渐递增到白色1
-
现在靠近圆心是白色1,远离圆心逐渐递减到黑色0
-
3.正式-绘画白色形状的圆
(1)if逻辑代码实现
- shader代码+效果图
-
说明
if(distance > 0.0){ distance = 1.0; }
表示原先大于0.0的颜色都白色,即灰色都是白色
(2)用shader的step函数
- shader代码+效果图
-
说明
step(edge0, x); x<edge0 返回0,x>edge0 返回1
step函数作用同
if(distance > 0.0){ distance = 1.0; }
step(0, x);函数图像
从线性变成阶梯型
完善圆-实现绘画手环圆
1.if逻辑代码实现
- shader+效果
-
说明
-
原本靠近圆心的distance为1,颜色为白色1,远离圆心的distance逐渐从1降为0,颜色从白色1到黑色0。
但经历下面代码后成上图所示
if(distance > 0.1){ col = vec3(0.0); }
-
被白色包围的黑色范围形成说明
以圆心为出发点,length(uv)在(0, 0.9)范围内的distance为(1, 0.1),满足if条件,使颜色为黑色0。
-
白色范围
以圆心为出发点,length(uv)在(0.9, 1)范围内的distance为(0.1, 0),不满足if条件,使颜色依旧是白色0。
-
白色外面的黑色范围形成说明
以圆心为出发点,length(uv)在(1, 1.1)范围内的distance为(0, -0.1),虽然不满足if条件不受第13行if代码块的影响,但是受第12行代码step函数的影响,早已为黑色。
所以白色范围length为(0.9, 1)
-
2.用step函数代替if
- shader+效果
-
说明
-
col *= vec3(1.0 - step(0.02, distance))的作用相当于
if(distance > 0.1){ col = vec3(0.0); }
-
设第13行*以的中间值c2如下
c2 = vec3(1 - step(0.02, distance));// step(0.02, distance)使得大于0.02的为1,小于0.02的为0
-
c2的值
以圆心为出发点,length(uv)在(0, 0.98)范围内的distance为(1, 0.02),step(0.02, distance)后为1,被1减去后,为黑色0
以圆心为出发点,length(uv)在(0.98, 1)范围内的distance为(0.02, 0),step(0.02, distance)后为0,被1减去后,为白色1
-
col的值-第11行的代码,并执行第13行的*代码
vec3 col = vec3(step(0.0, distance)); col *= c2;
以圆心为出发点,length(uv)在(0, 0.98)范围内的distance为(1, 0.02)的col是白色1,与上述同范围的c2黑色0相乘为0黑色。
以圆心为出发点,length(uv)在(0.98, 1)范围内的distance为(0.02, 0)的col是白色1,与上述同范围的c2白色1相乘为1白色。
所以白色的范围length是(0.98, 1),所以很小
-
用smoothstep代替step函数
1.smoothstep函数
-
说明
https://docs.gl/sl4/smoothstep
// 当edge0 < edge1时,当x < edg0时,返回0,当x > edg1时,返回1,当x在edg0和edg1之间时,返回x。 // 当edge1 < edge0时,当x < edg1时,返回1,当x > edg0时,返回0,当x在edg0和edg1之间时,返回x。这个有点难理解,是我在下面情况测试出来的,没有官方说明,也许不对 // 当edge0 = edge1 ,smoothstep退化成step(0除外) smoothstep(edge0, edge1, x);
等同于
genType t; t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); return (3.0 - 2.0 * t) * t * t;
自己测试smoothstep的结论,测试过程在后面
// 当edge1 < edge0时,当x < edg1时,返回1,当x > edg0时,返回0,当x在edg0和edg1之间时,返回x。 // 当edge0 = edge1 ,smoothstep退化成step // 但当edge0=edge1=0,smoothstep(edge0,edge1,x);无论x是什么都返回0! smoothstep(edge0, edge1, x);
-
表示的阶梯型函数图形
如图所示,在e0 e1范围更平滑,不像step那么突兀
2.使用smoothstep
-
代替后的shader代码+效果图
// 其中fade = 0.005,即smoothstep的第二个参数 第16行替换成smoothstep函数是给外圈边缘平滑过渡的 第17行替换成smoothstep函数是给内圈边缘平滑过渡的
3.完善shader代码
-
当前代码的不足
由于之前第17行代码有点冗余,所以可以简短一点,写完如下,效果依旧保持良好不变
即
vec3(smoothstep(thickness, thickness - fade, distance)); 等价于 vec3(1.0 - smoothstep(thickness - fade, thickness, distance));
-
第17行代码说明
-
简要说明
原先是递增,改后是递减
-
翻转图示
原先小于0.095为0,大于0.1是1=>现变成小于0.095为1,大于0.1为0
-
结合代码和效果图具体说明
- 原先的效果-递增
c2 = vec3(1 - smoothstep(0.1 - 0.005, 0.1, distance)); // distance < 0.095 为0,distance > 0.1为1 c2 = vec3(1 - smoothstep(0.095, 0.1, distance));
length(uv)在(0, 0.9)范围内的distance为(1, 0.1),smoothstep后为1,被1减去后,为黑色0
length(uv)在(0.9, 0.905)范围内的distance为(0.1, 0.095),smoothstep后为不变,被1减去后,为平缓的白色(0.9, 0.905)之间
length(uv)在(0.905, 1)范围内的distance为(0.095, 0),smoothstep后为0,被1减去后,为白色1
- 改后的效果-递减
说明当edge0 > edge1时函数的意义发生变化,这个变化上面介绍smoothstep时也说了,只不过并没有官方说明,也没问过人,不知是否正确,如有错,欢迎指正。
c2 = vec3(smoothstep(0.1, 0.1 - 0.005, distance)) // distance < 0.095 为1,distance > 0.1为0, c2 = vec3(smoothstep(0.1, 0.095, distance))
length(uv)在(0, 0.9)范围内的distance为(1, 0.1),>0.1,smoothstep后为黑色0
length(uv)在(0.9, 0.905)范围内的distance为(0.1, 0.095),smoothstep后为不变,被1减去后,为平缓的白色(0.9, 0.905)之间
length(uv)在(0.905, 1)范围内的distance为(0.095, 0),<0.095,smoothstep后为白色1
所以结果不变。
-
-
验证:
这段有点不好说明,可以跳过,是用OpenGL的glsl测试smoothstep函数
-
glsl关键代码
.... // fragment阶段 void main() { float distance = 1.0 - length(Input.LocalPosition);// distance = 1-圆心的距离 float circle = smoothstep(0.5, Input.Fade, distance); o_Color = vec4(circle, circle, circle, 1); }
可见,Input.Fade是变量,可以调节它是否小于0.5还是大于0.5,来验证上述是否正确
-
当Fade=0.51大于0.5时
float circle = smoothstep(0.5, 0.51, distance);
-
圆内白色:
distance在(1, 0.51)范围,>(大于)0.51,smoothstep后circle是白色1。
-
圆外黑色:
distance在(0.5, 0)范围,<(小于)0.5,smoothstep后circle是黑色0。
-
-
当Fade=0.483小于0.5时
float circle = smoothstep(0.5, 0.483, distance);
-
圆内黑色:
distance在(1, 0.5)范围,>(大于)0.5,smoothstep后circle是黑色0。
-
圆外白色:
distance在(0.483, 0)范围,<(小于)0.483,smoothstep后circle是白色1。
-
-
用smoothstep的小Bug
1.Bug说明
-
当thickness=1时,会导致中间有一个很小很小的黑色洞
-
解释——待实际验证
由smoothstep的减法导致的
// distance < 0.995 为1,distance > 1为0 c2 = vec3(smoothstep(1, 0.995, distance))
注意:由图像所示,不止>1,接近1的位置也是0
length(uv)在(0, 0.002)范围内的distance为(1, 0.998),在(0.995,1)之间,但是0.998靠近1,smoothstep后为接近黑色0,所以原点呈现黑色。
tips:这是我自己猜的,有待验证和询问别人,还是那句话,欢迎指正。
2.解决方法
方法一
初始化thickness += fade,可以解决洞的问题
-
解释——待实际验证
// distance < 1 为1,distance > 1.005为0 c2 = vec3(smoothstep(1.005, 1, distance))
length(uv)在(0, 0.002)范围内的distance为(1, 0.998),不在(1.005,1)之间,而是<1,smoothstep后为白色1。
tips:欢迎指正
方法二
推荐,代码更好看吧
-
解决方式
将thickness+fade代码写入smoothstep中
-
解释——同上
// distance < 1 为1,distance > 1.005为0 c2 = vec3(smoothstep(1.005, 1, distance))
length(uv)在(0, 0.002)范围内的distance为(1, 0.998),不在(1.005,1)之间,而是<1,smoothstep后为白色1。
tips:如有错,欢迎指正
-
新问题
会有新问题,即thickness=0时,fade=0.005时,有个0.005的厚度薄圆轮廓(如上两个箭头所指)
-
解决方法
这问题迫不得已,cherno没说解决方案。
我自己测试,当thickness=0时,只需调整fade为0就不会有薄圆了
-
说明为何有效
1.先说薄圆轮廓产生原因
设fade= 0.1, thickness = 0
float distance = 1.0 - length(uv); vec3 col = vec3(smoothstep(0, 0.1, distance)); // distance < 0 为1,distance > 0.1为0 col *= vec3(smoothstep(0.1, 0, distance));
-
uv为圆内黑色范围的点
length(uv)在(0, 0.9)范围
1.distance > 0.1 2.col = 1 3.col = 1 * 0 = 0;// * 0因为distance > 0.1为0
所以呈现黑色
-
uv为圆上薄圆轮廓范围的点
length(uv)在(0.9, 1)范围
1.0 < distance < 0.1 2.0 < col < 0.1 3.col = (0,0.1) * (0, 0.1) = (0, 1);
所以呈现薄圆
2.将fade=0时,薄圆消失
设fade= 0, thickness = 0
float distance = 1.0 - length(uv); vec3 col = vec3(smoothstep(0, 0, distance)); col *= vec3(smoothstep(0, 0, distance));
不管distance是多少,smoothstep(0, 0, distance)返回0,所以col是黑色,与背景融合在一起,所以轮廓消失。
-
测试smoothstep(edg0, edg1, distance)
-
edge0=edge1=0
void main()// 部分glsl的fragment代码 { float distance = 1.0 - length(Input.LocalPosition);// distance = 1-圆心的距离 float circle = smoothstep(0, 0, distance); o_Color = vec4(circle, circle, circle, 1); }
结果
说明smoothstep(0, 0, distance);返回0
-
edge0=edge1=0.2
void main()// 部分glsl的fragment代码 { float distance = 1.0 - length(Input.LocalPosition);// distance = 1-圆心的距离 float circle = smoothstep(0.2, 0.2, distance); o_Color = vec4(circle, circle, circle, 1); }
白色范围因distance>0.2所以smoothstep(0.2, 0.2, distance)返回1
黑色范围因distance<0.2所以smoothstep(0.2, 0.2, distance)返回0
-
edge0=edge1=0.8
void main()// 部分glsl的fragment代码 { float distance = 1.0 - length(Input.LocalPosition);// distance = 1-圆心的距离 float circle = smoothstep(0.8, 0.8, distance); o_Color = vec4(circle, circle, circle, 1); }
白色范围因distance>0.8所以smoothstep(0.8, 0.8, distance)返回1
黑色范围因distance<0.8所以smoothstep(0.8, 0.8, distance)返回0
结论
smoothstep(0, 0, distance);返回0
smoothstep(0.2, 0.2, distance);退化成step(0.2, distance);distance大于0.2返回1,小于0.2返回0
smoothstep(0.8, 0.8, distance);退化成step(0.8, distance);distance大于0.8返回1,小于0.8返回0