[Shader et ShaderToy] dessinent une étoile à cinq branches

Ecrire devant

        Après avoir lu quelques articles sur le dessin de lignes et de points avec shadertoy, j'ai soudain eu envie de me faire une étoile à cinq branches pour pratiquer mes mains. Mais en y repensant, c'était encore plein de "bosses" quand j'ai commencé. Après avoir joué pendant un week-end, c'était juste une idée claire, mais le code était en désordre. En fin de compte, j'ai honnêtement cherché diverses formules et documents sur Internet. Au milieu du processus, j'ai constaté que j'avais oublié la formule pour trouver le cercle Je ne peux pas m'empêcher de sentir que mes mathématiques sont vraiment revenues. La seule chose qui me soulage, c'est que la dernière chose est sortie, l'effet obtenu sur shadertoy est le suivant, l'adresse est ici .

        Dessiner des points dans le shader se fait avec la logique du dessin de cercles . L'implémentation spécifique peut faire référence à l' article précédent ; et le code d'implémentation du segment de ligne est basé sur le grand dieu du site shadertoy. L'adresse est ici . La ligne () méthode est plus difficile à comprendre directement., J'ai cherché partout dans Baidu le principe de ce dessin au trait, mais j'ai finalement obtenu la réponse dans un groupe de jeu. Quel maître est caché sur le marché. . . La description de principe spécifique sera annotée dans le code d'implémentation suivant.

        Chaque cercle et chaque ligne dessinés dans le shader occupe un calque, et enfin ces calques doivent être superposés selon la logique que nous voulons, donc la quantité de calcul d'un tel effet affiché à la fin est très grande, et je ne m'y attendais pas à cette étape Un bon moyen d'optimiser la perte de performance de ces calculs ne peut qu'espérer le découvrir dans la future étude. . .

Explication du principe 

        Parlons du principe de réalisation de cette étoile à cinq branches.

        Fondamentalement, vous connaîtrez un certain nombre de mathématiques (probablement). Les cinq points d'une étoile régulière à cinq branches sont tous sur le même cercle, et chaque point adjacent est espacé de 72 °, donc un point est le centre du cercle, et la position des coordonnées des cinq points est calculée à l'aide de la formule pour trouver les points sur le cercle. L'étoile à cinq branches a cinq côtés. Tant que le point courant et le point séparé sont dessinés séparément, une étoile à cinq branches peut être formée. La dernière image est plus intuitive

        Le principe est connu. L'étape suivante consiste à implémenter la méthode dans le shader. La première est la méthode de dessin de points, qui est en fait la méthode de dessin de cercles. Le code est le suivant

//画点
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);
}

        Le principe que j'ai déjà mentionné dans l' article précédent , voici deux calques pour dessiner un cercle, dont l'un gère la couleur de la bordure extérieure du cercle et l'autre sert à dessiner la couleur du cercle.

        Vient ensuite la méthode de dessin des segments de ligne. J'ai vu la méthode de dessin au trait de candycat avant , elle utilise la pente pour dessiner, collez d'abord le principe du code

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);
}

        Il s'agit de tracer une ligne en trouvant la pente de deux points, puis en utilisant la formule de distance pour juger de la distance entre le point et la ligne. C'est très facile à comprendre, mais il y a un problème que vous ne pouvez tracer qu'une droite ligne! ! Pas un segment de ligne! ! Si vous l'utilisez pour dessiner des lignes, l'effet obtenu est le suivant

        Tant que le point à l'écran correspond à la pente, il sera dessiné, il ne convient donc pas à notre effet. Enfin, j'ai trouvé une méthode très puissante pour dessiner des segments de ligne sur le site shadertoy. Cette ligne de dessin utilise un vecteur pour calculer la distance entre le point et la ligne. Le principe de mise en œuvre peut se référer à l'introduction de cet article . Enfin, un la compréhension personnelle est attachée.

        Le résultat calculé est le même que la partie opération du code suivant

    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);

        Le dir0 dans le code correspond à b, le dir1 dans le code correspond à a, et la dernière distance requise h correspond à la longueur du vecteur d.

       La dernière méthode intégrée de dessin de segments de ligne est comme ceci

//画线
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);
}

        La partie la plus difficile a été résolue et le reste est facile. Permettez-moi de parler des cinq points à tirer. L'origine des coordonnées peut être prise comme centre. Les cinq points de l'étoile à cinq branches passent par le centre. La logique de traitement est la suivante: en supposant que le degré du premier point est a, le point suivant est a + 72 °, et le suivant Les points sont à + 72 ° + 72 °, et ainsi de suite, et enfin ces points sont enregistrés dans un tableau. Bien sûr, vous pouvez dessiner le calque directement sans enregistrer le tableau, mais il y a un problème que la ligne est dessinée sur le calque derrière, et elle sera affichée en haut du point.

        Il est important de mentionner ici que les paramètres d'entrée correspondants des méthodes cos () et sin () sont des radians! ! Ce sont des radians! ! Pas un diplôme! ! !

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

        L'écriture ci-dessus est erronée et le résultat sera très étrange.

        La manière correcte d'écrire devrait être comme ça

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

        Dans ce cas, vous pouvez obtenir de nouveaux effets en modifiant légèrement la formule. Par exemple, si vous mettez à l'échelle la partie péché de 0,5, vous pouvez obtenir une étoile à cinq branches mise à l'échelle.

        Si le paramètre d est associé au temps de course, l'étoile à cinq branches peut être tournée ~

        Enfin, le code complet de Shadertoy et Unity est joint.

Section ShaderToy

        

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);
    }

}

Partie Unity

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"
}

Pour résumer

        La partie mathématique est vraiment fatigante. . . Je ne devrais vraiment pas être paresseux en mathématiques à l’université.

        Après avoir parcouru certains des shadertoy réalisés par Daniel, j'ai de nouveau ressenti le charme du shader et en même temps compris profondément le manque de connaissances. Pas grand chose à dire, allez!

Je suppose que tu aimes

Origine blog.csdn.net/ssssssilver/article/details/81166233
conseillé
Classement