[Shader and ShaderToy] draw a five-pointed star

Write in front

        After reading a few articles about drawing lines and points with shadertoy, I suddenly wanted to make myself a five-pointed star to practice my hands. But thinking about it, it was still full of "bumps" when I started. After tossing for a weekend, it was just a clear idea, but the code was in a mess. In the end, I honestly looked up various formulas and materials on the Internet. In the middle of the process, I found that I had forgotten the formula to find the circle. I can’t help feeling that my mathematics has really been returned. The teacher. The only thing I feel relieved is that the last thing is out, the effect achieved on shadertoy is this, the address is here .

        Draw points in the shader is done with the logic of drawing circles . The specific implementation can refer to the previous article ; and the implementation code of the line segment is based on the great god of the shadertoy website. The address is here . The line() method is more cumbersome to understand directly. , I searched all over Baidu for the principle of this line drawing, but I finally got the answer in a game group. What a master is hidden in the market. . . The specific principle description will be annotated in the following implementation code.

        Each circle and line drawn in the shader occupies a layer, and finally these layers need to be superimposed according to the logic we want, so the calculation amount of such an effect displayed in the end is very large, and I did not expect it at this stage A good way to optimize the performance loss of these calculations can only hope to find out in the future study. . .

Principle explanation 

        Let's talk about the principle of realizing this five-pointed star.

        Basically you will know a certain amount of mathematics (probably). The five points of a regular five-pointed star are all on the same circle, and each adjacent point is 72° apart, so one point is the center of the circle, and the coordinates of the five points are calculated using the formula for finding the points on the circle. The five-pointed star has five sides. As long as the current point and the separated point are drawn separately, a five-pointed star can be formed. The last picture is more intuitive

        The principle is known. The next step is to implement the method in the shader. The first is the method of drawing points, which is actually the method of drawing circles. The code is as follows

//画点
vec4 circle(vec2 pos, vec2 center, float radius, vec4 color) {
    //求点是否在圆的半径内
    float d = length(pos - center) - radius;
    //fwidth(x) ==abs(ddx(x)) + abs(ddy(x)),对点求偏导,这种处理能让数据变平滑
    float w = fwidth(0.5*d) * 2.0;
    //图层0 画圆外边框
    vec4 layer0 = vec4(_OutlineColor.rgb, 1.0-smoothstep(-w, w, d - _Antialias));
    //图层1 画内圆
    vec4 layer1 = vec4(color.rgb, 1.0-smoothstep(0.0, w, d));
    //混合两个图层并返回
    return mix(layer0, layer1, layer1.a);
}

        The principle I have already mentioned in the previous article , here are two layers to draw a circle, one of which handles the color of the outer border of the circle, and the other is used to draw the color of the circle.

        Next is the method of drawing line segments. I saw the line drawing method of candycat before , it uses the slope to draw, first paste the code principle

vec4 DrawLines(vec2 pos,vec2 point1,vec2 point2,float width,float3 color,float antialias){
	//斜率
	float k=(point1.y-point2.y)/(point1.x-point2.x);
	//y=kx+b 常量b=y-kx
	float b=point1.y-k*point1.x;
	//求点到直线的距离
	// b=(kx-y+b)/sqrt(k*k+1*1)
	float d=abs(k*pos.x-pos.y+b)/sqrt(k*k+1);
	//Width/2 是因为要求两端的距离 antialias为平滑处理的范围
	float t=smoothstep(width/2.0,width/2.0+antialias,d);
	return vec4(color,1.0-t);
}

        This is to draw a line by finding the slope of two points, and then using the distance formula to judge the distance from the point to the line. It is very easy to understand, but there is a problem that you can only draw a straight line! ! Not a line segment! ! If you use it to draw lines, the effect achieved is like this

        As long as the point on the screen matches the slope, it will be drawn, so it is not suitable for our effect. Finally, I found a very powerful method of drawing line segments on the shadertoy website. This drawing line uses a vector to calculate the distance from the point to the line. The principle of implementation can refer to the introduction of this article . At the end, a personal understanding is also attached.

        The calculated result is the same as the operation part of the following code

    vec2 dir0 = point2 - point1;
    vec2 dir1 = pos - point1;
    //dot()方法返回两个向量的点积 如果向量垂直返回0,平行返回1 相反返回-1
    //clamp()方法限制返回0到1 截出线段,不然会返回直线
    //这公式返回点到线上的距离
    float h = clamp(dot(dir1, dir0)/dot(dir0, dir0), 0.0, 1.0);
    //判断点是否在线的两边范围内
    float d = (length(dir1 - dir0 * h) - width * 0.5);

        The dir0 in the code corresponds to b, the dir1 in the code corresponds to a, and the last required distance h corresponds to the length of the vector d.

       The final integrated method of drawing line segments is like this

//画线
vec4 line(vec2 pos, vec2 point1, vec2 point2, float width) {
    //分别求出点二到点一以及当前点到点一的向量
    vec2 dir0 = point2 - point1;
    vec2 dir1 = pos - point1;
    //dot()方法返回两个向量的点积 如果向量垂直返回0,平行返回1 相反返回-1
    //clamp()方法限制返回0到1 截出线段,不然会返回直线
    //这公式返回点到线上的距离
    float h = clamp(dot(dir1, dir0)/dot(dir0, dir0), 0.0, 1.0);
    //判断点是否在线的两边范围内
    float d = (length(dir1 - dir0 * h) - width * 0.5);
    //平滑处理
    float w = fwidth(0.5*d) * 2.0;
    //画线的外边
    vec4 layer0 = vec4(_OutlineColor.rgb, 1.-smoothstep(-w, w, d - _Antialias));
    //画线
    vec4 layer1 = vec4(_FrontColor.rgb, 1.-smoothstep(-w, w, d));
   	//混合两个图层
    return mix(layer0, layer1, layer1.a);
}

        The hardest part has been solved, and the rest is easy. Let me talk about which five points to draw. The origin of the coordinates can be taken as the center. The five points of the five-pointed star pass through the center. The processing logic is as follows: assuming the degree of the first point is a, the next point is a+72°, and the next one The points are a+72°+72°, and so on, and finally these points are saved in an array. Of course, you can draw the layer directly without saving the array, but there is a problem that the line is drawn on the layer behind, and it will be displayed on the top of the point.

        It is important to mention here that the corresponding input parameters of the cos() and sin() methods are radians! ! It's radians! ! Not a degree! ! !

degree[i+1]=vec2(cos(d),sin(d);

        The above writing is wrong, and the result will be very strange.

        The correct way of writing should be like this

degree[i+1]=vec2(cos(d*pi/180.0),sin((d*pi)/180.0));

        In this case, you can get new effects by slightly modifying the formula. For example, if you scale the sin part by 0.5, you can get a scaled five-pointed star.

        If the parameter d is associated with the running time, the five-pointed star can be rotated~

        Finally, the complete code of Shadertoy and Unity is attached.

ShaderToy section

        

vec4 _OutlineColor = vec4(1.0,1.0,1.0,1.0);
vec4 _FrontColor = vec4(1,0,0,1.0);

float pi=3.14159;

float _Antialias=0.01;


//画点
vec4 circle(vec2 pos, vec2 center, float radius, vec4 color) {
    //求点是否在圆的半径内
    float d = length(pos - center) - radius;
    //fwidth(x) ==abs(ddx(x)) + abs(ddy(x)),对点求偏导,这种处理能让数据变平滑
    float w = fwidth(0.5*d) * 2.0;
    //图层0 画圆外边框
    vec4 layer0 = vec4(_OutlineColor.rgb, 1.0-smoothstep(-w, w, d - _Antialias));
    //图层1 画内圆
    vec4 layer1 = vec4(color.rgb, 1.0-smoothstep(0.0, w, d));
    //混合两个图层并返回
    return mix(layer0, layer1, layer1.a);
}
//画线
vec4 line(vec2 pos, vec2 point1, vec2 point2, float width) {
    //分别求出点二到点一以及当前点到点一的向量
    vec2 dir0 = point2 - point1;
    vec2 dir1 = pos - point1;
    //dot()方法返回两个向量的点积 如果向量垂直返回0,平行返回1 相反返回-1
    //clamp()方法限制返回0到1 截出线段,不然会返回直线
    //这公式返回点到线上的距离
    float h = clamp(dot(dir1, dir0)/dot(dir0, dir0), 0.0, 1.0);
    //判断点是否在线的两边范围内
    float d = (length(dir1 - dir0 * h) - width * 0.5);
    //平滑处理
    float w = fwidth(0.5*d) * 2.0;
    //画线的外边
    vec4 layer0 = vec4(_OutlineColor.rgb, 1.-smoothstep(-w, w, d - _Antialias));
    //画线
    vec4 layer1 = vec4(_FrontColor.rgb, 1.-smoothstep(-w, w, d));
   	//混合两个图层
    return mix(layer0, layer1, layer1.a);
}

//根据index来保存图层的颜色值
void setlayer(inout vec4 layer[5],int index,vec4 val){
	if(index==0)
        layer[0]=val;
    if(index==1)
        layer[1]=val;
    	if(index==2)
        layer[2]=val;
    if(index==3)
        layer[3]=val;
    if(index==4)
        layer[4]=val;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.y;
    //动态背景颜色
    vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
    fragColor=vec4(col,1.0);
    //点的图层
    vec4 layers[5];
    float d=iTime*10.0;
    //保存五个点 从1开始
    vec2 degree[6];
    //for循环创建五个点
    for(int i=0;i<=4;i++)
    {
        //保存点
        //坐标上圆边上的点的坐标(cos(r),sin(r)) r为弧度
        degree[i+1]=vec2(cos(d*pi/180.0),sin((d*pi)/180.0));
        //绘制点
        setlayer(layers,i,circle(uv,degree[i+1],0.06,_FrontColor));
        //圆上的五角星,每个点相隔72度
        d+=72.0;
    } 
    //for循环画五条线
    for(int i=1;i<6;i++){
		vec2 point1=vec2(0.0,0.0);
        //判断连线的位置 即当前点的隔一个点
		if(i<=2)
		{
			point1=degree[i+3];
		}
		else
		{
			point1=degree[i-2];
		}
        //画线
		vec4 temp=line(uv,degree[i],point1,0.02);
        //混合线的图层
		fragColor=mix(fragColor,temp,temp.a);

	}
    //混合点的图层
   for (int i = 4; i >= 0; i--) {
        fragColor = mix(fragColor, layers[i], layers[i].a);
    }

}

Unity part

Shader "Custom/pentagram" {
	Properties {
		//xy表示圆心在屏幕中的uv值,z为半径,w为圆边缘的平滑值
		_OutlineColor("circleParameter",COLOR)=(0.5,0.5,10,0)
		_FrontColor("circleColor",COLOR)=(1,1,1,1)
		_Antialias("_Antialias",Range(0,1))=0.01
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		Pass{
		CGPROGRAM
		#include "UnityCG.cginc"
		#pragma fragmentoption ARB_precision_hint_fastest   
		#pragma target 3.0
		#pragma vertex vert
		#pragma fragment frag

		#define vec2 float2
		#define vec3 float3
		#define vec4 float4
		#define mat2 float2
		#define mat3 float3
		#define mat4 float4
		#define iGlobalTime _Time.y
		#define mod fmod
		#define mix lerp
		#define fract frac
		#define Texture2D tex2D
		#define iResolution _ScreenParams
		#define pi 3.1415926

		float4 _OutlineColor;
		float4 _FrontColor;
		float _Antialias;

		struct v2f{
			float4 pos:SV_POSITION;
			float4 srcPos:TEXCOORD0;
		};

		//画点
		vec4 circle(vec2 pos, vec2 center, float radius, vec4 color) {
			float d = length(pos - center) - radius;
			float w = fwidth(0.5*d) * 2.0;
			vec4 layer0 = vec4(_OutlineColor.rgb, 1.-smoothstep(-w, w, d - _Antialias));
			vec4 layer1 = vec4(color.rgb, 1.-smoothstep(0., w, d));
			return mix(layer0, layer1, layer1.a);
		}
		//画线
		vec4 lines(vec2 pos, vec2 point1, vec2 point2, float width) {
			vec2 dir0 = point2 - point1;
			vec2 dir1 = pos - point1;
			float h = clamp(dot(dir0, dir1)/dot(dir0, dir0), 0.0, 1.0);
			float d = (length(dir1 - dir0 * h) - width * 0.5);
			float w = fwidth(0.5*d) * 2.0;
			vec4 layer0 = vec4(_OutlineColor.rgb, 1.-smoothstep(-w, w, d - _Antialias));
			vec4 layer1 = vec4(_FrontColor.rgb, 1.-smoothstep(-w, w, d));
    
			return mix(layer0, layer1, layer1.a);
		}

		void setlayer(inout vec4 layer[5],int index,vec4 val){
			if(index==0)
				layer[0]=val;
			if(index==1)
				layer[1]=val;
    			if(index==2)
				layer[2]=val;
			if(index==3)
				layer[3]=val;
			if(index==4)
				layer[4]=val;
		}

		v2f vert(appdata_base v){
			v2f o;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
			o.srcPos=ComputeScreenPos(o.pos);
			o.srcPos=o.pos;
			return o;
		}
		vec4 main(vec2 fragCoord);
		float4 frag(v2f iParam):COLOR{
			//获取uv对应的当前分辨率下的点   uv范围(0-1) 与分辨率相乘
			vec2 fragCoord=((iParam.srcPos.xy/iParam.srcPos.w)*_ScreenParams.xy);
			return main(fragCoord);
		}
		vec4 main(vec2 fragCoord){
			//vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.y;
			vec2 uv=fragCoord/iResolution.y;
			vec3 col = 0.5 + 0.5*cos(iGlobalTime+uv.xyx+vec3(0,2,4));
			vec4 layers[5];
			float d=iGlobalTime*20.0;
			vec2 degree[6];
			for(int i=0;i<=4;i++)
			{
				degree[i+1]=vec2(cos(d*pi/180.0),sin((d*pi)/180.0));
				setlayer(layers,i,circle(uv,degree[i+1],0.06,_FrontColor));
				d+=72.0;
			} 
   
			vec4 fragColor=vec4(col,1.0);



			for(int i=1;i<6;i++){
			vec2 point1=vec2(0.0,0.0);
				if(i<=2)
				{
					point1=degree[i+3];
				}
				else
				{
				    point1=degree[i-2];
				}
			vec4 temp=lines(uv,degree[i],point1,0.02);
			fragColor=mix(fragColor,temp,temp.a);

			}
			for (int i = 4; i >= 0; i--) {
				fragColor = mix(fragColor, layers[i], layers[i].a);
			}
			return fragColor;
		}



		ENDCG
		}
	}
	FallBack "Diffuse"
}

to sum up

        The math part is really tiring. . . I really shouldn’t be lazy in math in college.

        After browsing some of the shadertoy made by Daniel, I once again felt the charm of the shader, and at the same time deeply understood the lack of knowledge. Not much to say, come on!

Guess you like

Origin blog.csdn.net/ssssssilver/article/details/81166233