앞에 쓰기
지난 이틀 동안 shadertoy에 대한 원형 진행률 표시 줄 ( 원래 주소 ) 의 효과를 보았습니다 . 꽤 좋은 것 같습니다. 원칙을 배우고 싶습니다. 원래 효과는 이렇게
본문
셰이더 토이를 만지기 전에 셰이더를 사용하여 위의 효과를 얻으려면 최소한 두 개의 그림, 즉 완전한 링 그래프와 마스킹을위한 반투명 이미지를 사용해야했습니다. 반투명 이미지의 알파 채널의 그래디언트를 사용하여 링 이미지를 마스크하고 표시합니다. 달성 된 효과는 다음과 같습니다.
마스크를 사용하여 코드를 만드는 것은 매우 간단하지만 표시되는 그래디언트 효과는 분명히 위와 같지 않으며 UI의 제작 수준에 따라 크게 좌우됩니다. 덧붙여서 코드도 게시했습니다.
Shader "Custom/AlphaMask" {
Properties
{
_MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}//原图
_MaskTex ("Mask (A)", 2D) = "white" {}//遮罩图
_Progress ("Progress", Range(0,1)) = 0.5//遮罩的百分比
}
Category
{
Lighting Off
ZWrite Off
Cull back
Fog { Mode Off }
Tags {"Queue"="Transparent" "IgnoreProjector"="True"}
Blend SrcAlpha OneMinusSrcAlpha//设置成半透明
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
sampler2D _MaskTex;
float _Progress;
struct appdata
{
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (appdata v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);//矩阵转换获得顶点
o.uv = v.texcoord.xy;//获取uv值
return o;
}
half4 frag(v2f i) : COLOR
{
fixed4 c = tex2D(_MainTex, i.uv);//获取原图像素上的颜色取样
fixed ca = tex2D(_MaskTex, i.uv).a;//获取遮罩图上颜色的alpha值
c.a *= ca >= _Progress ? 0: 1;//判断遮罩图上的alpha值是否大于_Progress的设置,如果小于则为0,透明不显示;如果大于则显示
return c;//返回像素颜色值
}
ENDCG
}
}
}
Fallback "Transparent/VertexLit"
}
마스크 된 이미지의 알파 값도 아래 그림과 같이 각도에 따라 변경됩니다.
오늘 저는 shadertoy를 사용하여이 효과를 얻는 것이 훨씬 더 좋을 것이라고 이야기하고 싶습니다. 텍스처가 필요하지 않을뿐만 아니라 색상을 수정하고 진행률 표시 줄의 시작 위치,주기 기간 등을 설정할 수도 있습니다. 먼저 원본 코드를 붙여 넣으십시오.
// static values
const float PI=3.14159265358979323846;
const float TAU = 6.28318530717958647692;
const float STEP_LENGTH = 0.01;
const float ANGLE_OFFSET = PI*0.5; // angle of dial
const vec4 color1 = vec4(1.0, 0.0, 0.0, 1.0);
const vec4 color2 = vec4(1.0, 1.0, 0.0, 1.0);
const float duration = 3.0; // duration of dial
// Get the color value based on where in the circle the uv is
vec4 getGradientValue(in vec2 uv)
{
vec2 dist = vec2(1.0, 0.0) - vec2(-1.0, 0.0);
float val = dot( uv - vec2(-1,0), dist ) / dot( dist, dist );
clamp( val, 0.0, 1.0 );
vec4 color = mix( color1, color2, val );
// clamp depending on higher alpha value
if( color1.a >= color2.a )
color.a = clamp( color.a, color2.a, color1.a );
else
color.a = clamp( color.a, color1.a, color2.a );
return color;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
float progress = mod(iTime, duration) / duration;
float innerRadius = 0.5;
float outerRadius = 0.65;
float startAngle = 0.0;
float endAngle = progress* TAU;
vec2 uv = (2.0*fragCoord.xy - iResolution.xy)/iResolution.y;
float d = length( uv );
vec4 ioColor = getGradientValue(uv);
// Perform adaptive anti-aliasing.
float w = fwidth( d ) * 1.0;
float c = smoothstep( outerRadius + w, outerRadius - w, d );
c -= smoothstep( innerRadius + w, innerRadius - w, d );
// set color for the area within inner and outer radius
fragColor = vec4(ioColor.rgb * vec3(c), 1.0);
// limit to active progress
float angle = (atan(uv.y,uv.x)) + ANGLE_OFFSET;
if( angle < 0.0 ) angle += PI * 2.0;
if( angle > endAngle){
float a = smoothstep( 0.75, -w*2.0, abs(endAngle - angle) );
//float a = smoothstep( 0.0, -w*2.0, abs(endAngle - angle) );
fragColor *= a;
}
if(angle - w*2.0 < startAngle ){
float a = smoothstep( -w*2.0, w*2.0, (abs(startAngle - angle)) );
fragColor *= a;
}
/*
// round butt stuff
float lineWidth = (outerRadius - innerRadius) * 0.5;
float midRadius = innerRadius + lineWidth;
// distance from pt at end angle
vec2 endAnglePos = vec2( cos(endAngle-ANGLE_OFFSET), sin(endAngle-ANGLE_OFFSET)) * vec2(midRadius);
float dist = length( uv - endAnglePos );
float buttAlpha = smoothstep( lineWidth + w, lineWidth - w, dist );
fragColor = mix(fragColor, ioColor, buttAlpha );
// distance from pt at start angle
vec2 startAnglePos = vec2( cos(startAngle-ANGLE_OFFSET), sin(startAngle-ANGLE_OFFSET)) * vec2(midRadius);
dist = length( uv - startAnglePos );
buttAlpha = smoothstep( lineWidth + w, lineWidth - w, dist );
fragColor = mix(fragColor, ioColor, buttAlpha );
*/
}
댓글은 모두 영어로되어있어 불편 해 보이네요 이해 후 코드를 최적화하고 중국어 댓글을 추가했습니다.
vec4 _Color1=vec4(1.0,0.0,1.0,1.0);
vec4 _Color2=vec4(0.0,1.0,1.0,1.0);
//循环时间
float _Duration=5.0;
//开始位置
float _AngleOffset=0.0;
//平滑区间
float _Antialias=1.0;
float pi=3.14;
// 颜色混合后转出 uv控制alpha值
vec4 getGradientValue(in vec2 uv)
{
vec2 dist = vec2(1.0, 1.0) - vec2(-1.0, 0.0);
float val = dot( uv - vec2(-1,0), dist ) / dot( dist, dist );
//限制val的范围为0到1
val= clamp( val, 0.0, 1.0 );
//将两个颜色混合输出
vec4 color = mix( _Color1, _Color2, val );
return color;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv =(2.0* fragCoord-iResolution.xy)/iResolution.y;
//获取当前的进度
float progress = ( mod(iTime,_Duration))/_Duration;
//设置内外圆半径
float innerRadius = 0.5;
float outerRadius = 0.65;
//获得当前弧度
float endAngle = progress*pi*2.0;
//uv的长度 原点与圆相同
float d = length(uv);
//获取颜色
vec4 ioColor = getGradientValue(uv);
//抗锯齿处理 fwidth(a)=abs(ddx(a))+abs(ddy(a))
float w = fwidth( d ) * 1.0;
//uv与外半径相比 大于半径返回1 小于半径返回0
float c = smoothstep( outerRadius + w, outerRadius - w, d );
//smoothstep函数中 与内半径相比 大于半径返回1 小于返回0
c-=smoothstep( innerRadius + w, innerRadius - w, d );//最后得到的结果:如果uv在两个半径的环内,返回1,否则都返回0
//混合颜色
fragColor = vec4(ioColor.xyz * vec3(c,c,c),1.0);
// 进度条设置
//获取当前uv相对原点的弧度
float angle = (atan(uv.y,uv.x)) + _AngleOffset;
//保证弧度>0
if( angle < 0.0 ) angle += pi * 2.0;
if( angle > endAngle){
//让当前进度平滑显示
float a = smoothstep(_Antialias,w*2.0,abs(endAngle - angle) );
fragColor *= a;
}
}
코드에서 실제 실행 시간 iTime은 현재 진행 상황을 제어하는 데 사용됩니다. 코드에서 _Duration은 진행 라운드의 시간이고 (mod (iTime, _Duration)) / _ Duration 연산은 현재 진행 상황을 가져 오는 데 사용됩니다. 진행. 작성자가 현재 uv의 길이를 원형 진행률 막대 내부 및 외부의 두 반경과 비교하여 얻은 값을 출력 색상과 혼합하여 얻은 값을 사용한다는 것을 이해하기가 더 어렵습니다. 이는 선형 보간 fwidth ()의 일부 작업도 포함합니다. . 여기에 자세히 설명하십시오.
먼저 fwidth () 메서드에 대해 이야기하겠습니다. fwidth (a)는 x 및 y 방향에서 편도 함수의 절대 값의 합을 반환합니다.
即 fwidth (a) = abs (ddx (ax)) + abs (ddy (ay))
fwidth (a)는 현재 픽셀과 다음 픽셀 사이의 차이를 반환하며, 이는 직선의 선형 차이로 이해 될 수 있습니다.
여기서 코드는 먼저 원 중심에 대한 uv의 길이를 구한 다음 d = length (uv) 거리에 대해 w = fwidth (d) 차이 연산을 수행합니다.이 w는 거리로 이해 될 수 있습니다. 현재 픽셀 거리와 다음 픽셀 간의 차이입니다.
그런 다음 smoothstep () 메서드가 있고 smoothstep 메서드는 값을 0-1로 강제로 되돌립니다.
즉, smoothstep (min, max, x), x <min, x = 0 일 때; x> max, x = 1 일 때; min <x <max 일 때 x의 범위는
흥미로운 점은 작성자가 사용하는 smoothstep () 메서드에서 최소값과 최대 값이 반대로되어 있다는 것입니다.
float c = smoothstep( outerRadius + w, outerRadius - w, d );
c-=smoothstep( innerRadius + w, innerRadius - w, d );
outerRadius + w> outerRadius-w。
min과 max의 위치를 하나의 단위로 교환하여 찾을 수 있으며, 이와 같이 처리 된 smoothstep ()의 결과도 반대로됩니다. 즉, x <min이 1을 반환하고 x> max가 0을 반환 할 때입니다.
따라서 uv 포인트가 outerRadius 안에 있으면 c는 1을 반환하고 outerRadius 밖에 있으면 0을 반환합니다.
마찬가지로 smoothstep (innerRadius + w, innerRadius-w, d)의 경우 uv가 innerRadius 내부에 있으면 1을 반환하고 innerRadius 외부에 있으면 0을 반환합니다.
즉, 둘을 빼면 w (uv의 길이)가 innerRadius와 outerRadius 사이에있을 때만 c가 1을 반환하고 마지막으로 출력 색상과 곱해지며 1이면 색상 결과가 반환됩니다. 0을 반환하면 색상을 곱하면 검은 색 결과 만 반환됩니다. (생각했을 때 거의 죽일 뻔했다)
fragColor = vec4(ioColor.xyz * vec3(c,c,c),1.0);
원 그리기의 어려움이 해결되었고, 마지막으로 진행률 표시 줄의 효과가 해결되었습니다. 그들 중
float angle = (atan2(uv.y,uv.x)) + _AngleOffset;
atan2 (uv.y, uv.x) 메서드는 현재 uv의 라디안 (각도 아님)과 오프셋 라디안 (각도 아님)을 나타내는 _AngleOffset을 반환합니다.
그리고 endAngle은 현재 시간의 호에 해당합니다.
float progress = (iGlobalTime%_Duration)/_Duration;
float endAngle = progress*pi*2.0;
(iGlobalTime % _Duration) / _ Duration을 사용하여 현재 기간의 백분율을 얻은 다음 progress * pi * 2.0을 곱하여 현재 라디안을 얻습니다. 이 경우. ...에 대한
float a = smoothstep(_Antialias,w*2.0,abs(endAngle - angle) );
현재 uv 포인트가 해당 기간 내에 있다고 판단되면 a는 부드럽게 0 ~ 1로 돌아가고, 그렇지 않으면 표시하지 않고 0으로 돌아갑니다.
위는 shadertoy 코드의 분석입니다.
덧붙여서 쉐이더 부분의 코드를 붙여
Shader "Custom/countdown" {
Properties {
_Color1("Color1",COLOR)=(1,1,1,1)
_Color2("Color2",COLOR)=(1,1,1,1)
//循环一次的时间
_Duration("Duration",float)=3
//开始位置
_AngleOffset("AngleOffset",float)=1.72
//平滑区间
_Antialias ("Antialias Factor", float) = 1
}
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 _Color1;
float4 _Color2;
float _Duration;
float _AngleOffset;
float _Antialias;
struct v2f{
float4 pos:SV_POSITION;
float4 srcPos:TEXCOORD0;
};
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{
vec2 fragCoord=((iParam.srcPos.xy/iParam.srcPos.w)*_ScreenParams.xy);
return main(fragCoord);
}
// 颜色混合后转出
vec4 getGradientValue(in vec2 uv)
{
vec2 dist = vec2(1.0, 1.0) - vec2(-1.0, 0.0);
float val = dot( uv - vec2(-1,0), dist ) / dot( dist, dist );
//限制val的范围为0到1
val= clamp( val, 0.0, 1.0 );
//将两个颜色混合输出
vec4 color = mix( _Color1, _Color2, val );
return color;
}
vec4 main(vec2 fragCoord){
vec2 uv = fragCoord/iResolution.y;
//设置转一圈的周期
float progress = (iGlobalTime%_Duration)/_Duration;
//设置内外圆半径
float innerRadius = 0.5;
float outerRadius = 0.65;
//获得当前弧度
float endAngle = progress*pi*2.0;
//uv的长度 原点与圆相同
float d = length(uv);
//获取颜色
vec4 ioColor = getGradientValue(uv);
//抗锯齿处理 fwidth(a)=abs(ddx(a))+abs(ddy(a))
float w = fwidth( d ) * 1.0;
//uv与外半径相比 大于半径返回1 小于半径返回0
float c = smoothstep( outerRadius + w, outerRadius - w, d );
//smoothstep函数中 与内半径相比 大于半径返回1 小于返回0
c-=smoothstep( innerRadius + w, innerRadius - w, d );//最后得到的结果:如果uv在两个半径的环内,返回1,否则都返回0
//混合颜色
vec4 fragColor = vec4(ioColor.xyz * vec3(c,c,c),1.0);
// 进度条设置
//获取当前uv相对原点的弧度
float angle = (atan2(uv.y,uv.x)) + _AngleOffset;
//保证弧度>0
if( angle < 0.0 ) angle += pi * 2.0;
if( angle > endAngle){
//对当前进度平滑处理
float a = smoothstep(_Antialias,w*2.0,abs(endAngle - angle) );
fragColor *= a;
}
return fragColor;
}
ENDCG
}
}
FallBack "Diffuse"
}
요약하자면
shadertoy를 배우는 것은 대학에서 수학을 공부하는 시대로 거슬러 올라갑니다. 특정 수학적 문제가 발생하면 해결하기가 매우 어렵습니다. 바이두는 찾기가 쉽지 않고 주변에 이것을 이해하는 사람이 없습니다. 결과적으로 작은 문제가 며칠 동안 갇힐 수 있지만 영감이 떠오르거나 문제가 해결되면 기쁨 감정을 표현하기가 어려우 니 너무 고통스럽고 행복하세요. . .