[Shader and ShaderToy] 다섯개 별 그리기

앞에 쓰기

        셰이더 토이로 선과 점을 그리는 방법에 대한 몇 가지 기사를 읽은 후 갑자기 손을 연습 할 수있는 다섯 개의 별을 만들고 싶었습니다. 그러나 그것에 대해 생각했을 때 내가 시작했을 때 그것은 여전히 ​​"범프"로 가득 차 있었다. 주말에 던지 더니 분명한 생각 이었지만 코드가 엉망이 됐고 결국 인터넷에서 다양한 공식과 자료를 솔직히 찾아 봤는데 그 과정에서 잊었다는 사실을 알게되었습니다. 원을 찾기위한 공식입니다. 제 수학이 정말로 돌아 왔다는 느낌을받을 수 없습니다. 선생님. 내가 안심할 수있는 유일한 것은 마지막 것이 나왔다는 것입니다. shadertoy에 대한 효과는 이것 입니다. 주소는 여기 입니다.

        셰이더에서 포인트 그리기는 원 그리기 로직 으로 수행됩니다. 특정 구현은 이전 기사를 참조 할 수 있으며 선분의 구현 코드는 shadertoy 웹 사이트의 위대한 신을 기반으로합니다. 주소는 여기 입니다. () 방법은 직접 이해하기가 더 번거 롭습니다.,이 선화의 원리를 찾기 위해 바이두 곳곳을 검색했지만 마침내 게임 그룹에서 답을 얻었습니다. . . 구체적인 원칙 설명은 다음 구현 코드에 주석으로 표시됩니다.

        셰이더에 그려진 각 원과 선은 하나의 레이어를 차지하고 마지막으로 이러한 레이어는 우리가 원하는 논리에 따라 중첩되어야하므로 마지막에 표시되는 이러한 효과의 계산량은 매우 큽니다. 이 단계 이러한 계산의 성능 손실을 최적화하는 좋은 방법은 향후 연구에서만 확인할 수 있습니다. . .

원리 설명 

        이 다섯개 별을 실현하는 원리에 대해 이야기합시다.

        기본적으로 당신은 (아마도) 어느 정도의 수학을 알게 될 것입니다. 일반 5 개 별의 5 개 점은 모두 같은 원에 있고 인접한 각 점은 72 ° 떨어져 있으므로 한 점은 원의 중심이며 5 개 점의 좌표 위치는 다음 공식을 사용하여 계산됩니다. 원에서 점을 찾는 것. 오 각별은 5 개의 변을 가지고 있으며, 현재 지점과 분리 된 지점을 따로 그리면 오 각별을 만들 수 있습니다. 마지막 그림이 더 직관적입니다.

        원리는 알려져 있으며 다음 단계는 셰이더에서 방법을 구현하는 것이고, 첫 번째는 점을 그리는 방법으로 실제로는 원을 그리는 방법입니다. 코드는 다음과 같습니다.

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

이전 기사         에서 이미 언급 한 원칙 , 여기에 원을 그리는 두 개의 레이어가 있는데, 그중 하나는 원의 바깥 쪽 테두리 색상을 처리하고 다른 하나는 원의 색상을 그리는 데 사용됩니다.

        다음은 선분을 그리는 방법입니다. 나는 전에 candycat 의 선 그리기 방법을 보았고 , 기울기를 사용하여 그리기, 먼저 코드 원칙을 붙여 넣습니다.

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

        두 점의 기울기를 찾아 선을 그린 다음 거리 공식을 이용하여 점에서 선까지의 거리를 판단하는 것입니다. 이해하기는 매우 쉽지만 직선 만 그릴 수있는 문제가 있습니다. 선! ! 선분이 아닙니다! ! 선을 그리는 데 사용하면 다음과 같은 효과가 나타납니다.

        화면의 점이 경사면과 일치하면 그려 지므로 효과에 적합하지 않습니다. 마지막으로 shadertoy 웹 사이트에서 매우 강력한 선분 그리기 방법을 발견했습니다.이 그리기 선은 벡터를 사용하여 점에서 선까지의 거리를 계산합니다. 구현 원리 이 기사 의 소개를 참조 할 수 있습니다 . 개인적인 이해가 첨부되어 있습니다.

        계산 된 결과는 다음 코드의 연산 부분과 동일합니다.

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

        코드의 dir0은 b에 해당하고, 코드의 dir1은 a에 해당하며, 마지막으로 필요한 거리 h는 벡터 d의 길이에 해당합니다.

       선분을 그리는 최종 통합 방법은 다음과 같습니다.

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

        가장 어려운 부분이 해결되었고 나머지는 쉽습니다. 어떤 점을 그릴 지 이야기하겠습니다. 좌표의 원점을 중심으로 할 수 있습니다 .5 개 별의 5 개 점이 중심을 통과합니다. 처리 논리는 다음과 같습니다 : 첫 번째 점의 정도를 a라고 가정하면 다음 점은 a + 72입니다. ° 및 다음 포인트 포인트는 a + 72 ° + 72 ° 등이며 마지막으로이 포인트는 배열에 저장됩니다. 물론 배열을 저장하지 않고 직접 레이어를 그릴 수는 있지만, 뒤에있는 레이어에 선이 그려져 포인트 상단에 표시되는 문제가 있습니다.

        cos () 및 sin () 메서드의 해당 입력 매개 변수가 라디안이라는 것을 여기서 언급하는 것이 중요합니다! ! 라디안입니다! ! 학위가 아닙니다! ! !

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

        위의 내용이 잘못되어 결과가 매우 이상 할 것입니다.

        올바른 쓰기 방식은 다음과 같아야합니다

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

        이 경우 수식을 약간 수정하여 새로운 효과를 얻을 수 있으며, 예를 들어 sin 부분을 0.5로 스케일하면 5 점 별을 얻을 수 있습니다.

        매개 변수 d가 실행 시간과 관련이 있으면 다섯개 별을 회전 할 수 있습니다 ~

        마지막으로 Shadertoy와 Unity의 전체 코드가 첨부됩니다.

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

}

단결 부분

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

요약하자면

        수학 부분은 정말 피곤합니다. . . 대학에서 수학을 정말 게으 르면 안 돼요.

        다니엘이 만든 셰이더 토이를 살펴본 후 다시 한 번 셰이더의 매력을 느꼈고 동시에 지식의 부족을 깊이 이해했습니다. 할 말이 많지 않습니다.

추천

출처blog.csdn.net/ssssssilver/article/details/81166233