ShaderToy学习笔记 03.多个形状和旋转
1. 正方形和旋转
1.1. 正方形
要绘制一个正方形,我们需要定义一个点到正方形边界的距离函数。对于中心在原点的正方形,其数学表达式为:
对于一个点 p(x,y) 到正方形边界的距离函数可以表示为:
d = max(|x|, |y|) - r
其中:
- |x| 和 |y| 分别表示点到原点的x轴和y轴距离的绝对值
- r 为正方形的半边长
- d 为点到正方形边界的有向距离:
- d < 0 表示点在正方形内部
- d = 0 表示点在正方形边界上
- d > 0 表示点在正方形外部
我们可以使用 desmos 来验证一下。
运行结果如下图所示:
#define PIXW (1./iResolution.y)vec3 sdfSquare(vec2 uv, float r,vec2 offset)
{uv=uv-offset;float d=max(abs(uv.x),abs(uv.y))-r;return d>0.?vec3(0.):vec3(abs(sin(iTime*0.3)),abs(cos(iTime*0.3)),abs(sin(iTime*0.3)));
}void mainImage( out vec4 fragColor, in vec2 fragCoord )
{// Normalized pixel coordinates (from -1 to 1)vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.xx;float r=0.3;vec3 c=sdfSquare(uv,0.3,vec2(0,0));// Output to screenfragColor = vec4(vec3(c),1.0);
}
1.2. 旋转
1.2.1. 2.1 旋转矩阵
1.2.2. 二维空间的旋转矩阵
从向量角度看:
点A可以理解为向量A1+A2,其中 A1 为(x,0),A2 为(0,y)。
将坐标轴旋转θ角度后,点A’的坐标为(x’,y’)。
将坐标轴以逆时针旋转θ角度后,点A’的坐标为(x’,y’)。
点A’可以理解为向量A1’+A2’,其中
A1' = (x*cos(θ),x*sin(θ))
A2' = (-y*sin(θ),y*cos(θ))
**注意:从图中也可以看出A2’的x坐标为 -y*sin(θ) **
A’=A1’+A2’ ,所以
x' = x * cos(θ) - y * sin(θ)
y' = x * sin(θ) + y * cos(θ)
在二维空间中,旋转矩阵可以表示为:
R ( θ ) = ( cos θ − sin θ sin θ cos θ ) R(\theta) = \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix} R(θ)=(cosθsinθ−sinθcosθ)
对于任意点 A(x, y),旋转后的坐标 A’(x’, y’) 可以通过矩阵乘法得到:
( x ′ y ′ ) = ( cos θ − sin θ sin θ cos θ ) ( x y ) \begin{pmatrix} x' \\ y' \end{pmatrix} = \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} (x′y′)=(cosθsinθ−sinθcosθ)(xy)
展开后得到旋转公式:
x' = x * cos(θ) - y * sin(θ)
y' = x * sin(θ) + y * cos(θ)
在 desmos 来验证一下
思路:
- 定义一个旋转矩阵 R,它是一个 2x2 的矩阵,其中包含了旋转角度的余弦和正弦值。
运行结果如下图所示:
#define PIXW (1./iResolution.y)#define PI 3.1415926535897932384626433832795
vec2 rotate(vec2 uv ,float theta)
{mat2 m=mat2(cos(theta),sin(theta),-sin(theta),cos(theta));return m*uv;
}
vec3 sdfSquare(vec2 uv, float r,vec2 offset)
{uv=uv-offset;uv=rotate(uv,PI/4.);float d=max(abs(uv.x),abs(uv.y))-r;return d>0.?vec3(0.):vec3(abs(sin(iTime*0.3)),abs(cos(iTime*0.3)),abs(sin(iTime*0.3)));
}void mainImage( out vec4 fragColor, in vec2 fragCoord )
{// Normalized pixel coordinates (from -1 to 1)vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.xx;float r=0.3;vec3 c=sdfSquare(uv,0.3,vec2(0,0));// Output to screenfragColor = vec4(vec3(c),1.0);
}
1.2.3. 三维空间的旋转矩阵
详见 https://blog.csdn.net/weixin_44539328/article/details/147030682?spm=1011.2415.3001.5331 # 1. 多个2D形状和混合
1.1. mix函数
mix函数可以将两个颜色混合在一起,它的语法如下:
函数 | 说明 | 数学公式 | 示例 |
---|---|---|---|
mix(x,y,a) | 在x和y之间按a进行线性插值。a的范围在[0,1]之间,当a=0时返回x,当a=1时返回y | f(x,y,a) = x * (1-a) + y * a | mix(0.0, 1.0, 0.5) = 0.5 |
1.1.1. 用mix函数混合颜色
#define PIXW (1./iResolution.y)
vec3 getBackgroundColor(vec2 uv)
{return mix(vec3(1,0,1),vec3(0,1,0),uv.y);
}void mainImage( out vec4 fragColor, in vec2 fragCoord )
{// Normalized pixel coordinates (from -1 to 1)vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.xx;float r=0.3;vec3 c=getBackgroundColor(uv);// Output to screenfragColor = vec4(vec3(c),1.0);
}
1.2. SDF 介绍
SDF在shader中指的是"Signed Distance Function"(有符号距离函数)。这是一个非常重要的概念,让我详细解释一下:
-
基本概念:
- SDF 是一个返回点到形状表面距离的函数
- 返回值的符号表示点是在形状内部(负值)还是外部(正值)
- 表面上的点返回值为0
-
SDF 的特点:
- 可以精确描述几何形状
- 计算效率高
- 便于实现形状的混合和变形
- 可以轻松实现平滑过渡和边缘效果
1.3. 用SDF实现多个2D形状
1.3.1. 用SDF实现一个圆
核心代码
vec3 c=getBackgroundColor(uv);float circle_d=sdfCircle(uv,r,vec2(0,0));c=mix(vec3(1,0,0),c,step(0.,circle_d));
完整代码
#define PIXW (1./iResolution.y)
float sdfCircle(vec2 p, float r,vec2 offset)
{return length(p-offset)-r;
}vec3 getBackgroundColor(vec2 uv)
{
//uv.y [-1,1]
//y: [0,1] float y=(uv.y+1.)/2.; return mix(vec3(1,0,1),vec3(0,1,1),y);
}void mainImage( out vec4 fragColor, in vec2 fragCoord )
{// Normalized pixel coordinates (from -1 to 1)vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.xx;float r=0.3;vec3 c=getBackgroundColor(uv);float circle_d=sdfCircle(uv,r,vec2(0,0));c=mix(vec3(1,0,0),c,step(0.,circle_d));// Output to screenfragColor = vec4(vec3(c),1.0);
}
1.3.2. 用SDF实现一个矩形 和 圆形
核心代码
vec3 c=getBackgroundColor(uv);float circle_d=sdfCircle(uv,r,vec2(0,0));float rect_d=sdfRect(uv,vec2(0.3,0.3),vec2(0,0));c=mix(vec3(1,0,0),c,step(0.,circle_d));c=mix(vec3(0,1,0),c,step(0.,rect_d));
采用mix函数,实现形状的混合,但当两个形状相交时,最后画的图形会覆盖前面的图形。上图中圆形和矩形相交部份显示的是矩形的颜色。
完整代码
#define PIXW (1./iResolution.y)
float sdfCircle(vec2 p, float r,vec2 offset)
{return length(p-offset)-r;
}
float sdfSquare(vec2 p, float r,vec2 offset)
{return max(abs(p.x-offset.x)-r,abs(p.y-offset.y)-r);
}
vec3 getBackgroundColor(vec2 uv)
{
//uv.y [-1,1]
//y: [0,1] float y=(uv.y+1.)/2.; return mix(vec3(1,0,1),vec3(0,1,1),y);
}void mainImage( out vec4 fragColor, in vec2 fragCoord )
{// Normalized pixel coordinates (from -1 to 1)vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.xx;float r=0.3;vec3 c=getBackgroundColor(uv);float circle_d=sdfCircle(uv,r,vec2(-0.5,0));float square_d=sdfSquare(uv,r,vec2(-0.1,0));c=mix(vec3(1,0,0),c,step(0.,circle_d));c=mix(vec3(0,1,0),c,step(0.,square_d));// Output to screenfragColor = vec4(vec3(c),1.0);
}