[Shader e ShaderToy] desenham uma estrela de cinco pontas

Escreva na frente

        Depois de ler alguns artigos sobre como desenhar linhas e pontos com o shadertoy, de repente eu queria fazer para mim uma estrela de cinco pontas para praticar minhas mãos. Mas pensando bem, ainda estava cheio de "solavancos" quando comecei. Após um fim de semana, era apenas uma ideia clara, mas o código estava uma bagunça. No final, eu honestamente pesquisei várias fórmulas e materiais na Internet. No meio do processo, descobri que havia esquecido a fórmula para encontrar o círculo. Não posso deixar de sentir que minha matemática realmente foi devolvida. O professor. A única coisa que me sinto aliviada é que a última coisa saiu, o efeito obtido no shadertoy é isso, o endereço está aqui .

        O desenho de pontos no shader é feito com a lógica de desenhar círculos . A implementação específica pode se referir ao artigo anterior ; e o código de implementação do segmento de linha é baseado no grande deus do site shadertoy. O endereço está aqui . A linha () método é mais complicado de entender diretamente., Eu pesquisei em todo o Baidu o princípio desse desenho de linha, mas finalmente consegui a resposta em um grupo de jogos. Que mestre está escondido no mercado. . . A descrição do princípio específico será anotada no código de implementação a seguir.

        Cada círculo e linha desenhada no shader ocupa uma camada e, finalmente, essas camadas precisam ser sobrepostas de acordo com a lógica que queremos, então a quantidade de cálculo de tal efeito exibido no final é muito grande, e eu não esperava isso em esta fase Uma boa maneira de otimizar a perda de desempenho desses cálculos só posso esperar descobrir no estudo futuro. . .

Explicação do princípio 

        Vamos falar sobre o princípio de realização desta estrela de cinco pontas.

        Basicamente, você saberá uma certa quantidade de matemática (provavelmente). Os cinco pontos de uma estrela regular de cinco pontas estão todos no mesmo círculo, e cada ponto adjacente está separado de 72 °, então um ponto é o centro do círculo, e a posição coordenada dos cinco pontos é calculada usando a fórmula para encontrando os pontos no círculo. A estrela de cinco pontas tem cinco lados. Desde que o ponto atual e o ponto separado sejam desenhados separadamente, uma estrela de cinco pontas pode ser formada. A última foto é mais intuitiva

        O princípio é conhecido. A próxima etapa é implementar o método no sombreador. A primeira é o método de desenhar pontos, que na verdade é o método de desenhar círculos. O código é o seguinte

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

        O princípio que já mencionei no artigo anterior , aqui estão duas camadas para desenhar um círculo, uma das quais trata a cor da borda externa do círculo, e a outra é usada para desenhar a cor do círculo.

        A seguir está o método de desenho de segmentos de linha. Eu vi o método de desenho de linha do candycat antes , ele usa a inclinação para desenhar, primeiro cole o princípio do código

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

        Isso é para desenhar uma linha encontrando a inclinação de dois pontos e, em seguida, usando a fórmula da distância para julgar a distância do ponto à linha. É muito fácil de entender, mas há um problema que você só pode desenhar uma reta linha! ! Não é um segmento de linha! ! Se você usá-lo para desenhar linhas, o efeito obtido é assim

        Contanto que o ponto na tela corresponda à inclinação, ele será desenhado, portanto, não é adequado para o nosso efeito. Por fim, descobri um método muito poderoso de desenhar segmentos de linha no site do shadertoy. Esta linha de desenho usa um vetor para calcular a distância do ponto à linha. O princípio de implementação pode se referir à introdução deste artigo . Finalmente, um a compreensão pessoal está ligada.

        O resultado calculado é o mesmo que a parte da operação do código a seguir

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

        O dir0 no código corresponde a b, o dir1 no código corresponde a a, e a última distância necessária h corresponde ao comprimento do vetor d.

       O método integrado final de desenhar segmentos de linha é assim

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

        A parte mais difícil foi resolvida e o resto é fácil. Deixe-me falar sobre quais cinco pontos desenhar. A origem das coordenadas pode ser considerada como o centro. Os cinco pontos da estrela de cinco pontas passam pelo centro. A lógica de processamento é a seguinte: assumindo que o grau do primeiro ponto é a, o próximo ponto é a + 72 °, e o próximo Os pontos são a + 72 ° + 72 °, e assim por diante, e finalmente esses pontos são salvos em uma matriz. Obviamente, você pode desenhar a camada diretamente sem salvar a matriz, mas há um problema que a linha é desenhada na camada atrás e será exibida na parte superior do ponto.

        É importante mencionar aqui que os parâmetros de entrada correspondentes dos métodos cos () e sin () são radianos! ! São radianos! ! Não é um diploma! ! !

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

        A escrita acima está errada e o resultado será muito estranho.

        A maneira correta de escrever deve ser assim

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

        Nesse caso, você pode obter novos efeitos modificando ligeiramente a fórmula. Por exemplo, se você dimensionar a parte do pecado em 0,5, poderá obter uma estrela de cinco pontas dimensionada.

        Se o parâmetro d estiver associado ao tempo de execução, a estrela de cinco pontas pode ser girada ~

        Finalmente, o código completo de Shadertoy e Unity está anexado.

Seção 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);
    }

}

Parte da unidade

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

Resumindo

        A parte matemática é muito cansativa. . . Eu realmente não deveria ser preguiçoso em matemática na faculdade.

        Depois de folhear um pouco do shadertoy feito por Daniel, mais uma vez senti o charme do shader e, ao mesmo tempo, entendi profundamente a falta de conhecimento. Não há muito a dizer, vamos!

Acho que você gosta

Origin blog.csdn.net/ssssssilver/article/details/81166233
Recomendado
Clasificación