前に書く
過去2日間、円形のプログレスバー(元のアドレス)がshadertoyに与える影響を確認しました。かなり良いと思います。原理を学びたいと思います。元の効果はこんな感じ
テキスト
シェーダーに触れる前に、シェーダーを使用して上記の効果を実現するには、完全なリンググラフとマスキング用の半透明の画像の少なくとも2つの画像を使用する必要がありました。半透明画像のアルファチャンネルのグラデーションを使用して、リング画像をマスクして表示します。達成される効果は次のようになります。
マスクを使ってコードを作成するのは非常に簡単ですが、表示されるグラデーション効果は明らかに上記ほど良くなく、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"
}
下の図に示すように、マスクされた画像のアルファ値も角度に応じて変化します。
今日は、この効果を達成するためにシェーダートイを使用することについて話したいと思います。テクスチャが不要なだけでなく、色の変更、プログレスバーの開始位置、サイクル期間などを設定することもできます。最初に元のコードを貼り付けます
// 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の長さを円形プログレスバーの内側と外側の2つの半径と比較して得られた値を使用して出力色と混合することを理解するのはさらに困難です。これには、線形補間fwidth()の操作も含まれます。 。ここで詳細に説明してください。
まず、fwidth()メソッドについて説明します。fwidth(a)は、x方向とy方向の偏導関数の絶対値の合計を返します。
即幅(a)= abs(ddx(ax))+ abs(ddy(ay))
fwidth(a)は、現在のピクセルと次のピクセルの間のaの差を返します。これは、直線の線形差として理解できます。
ここで、コードは最初に円の中心に対するuvの長さd = length(uv)を取得し、次に距離dに対して差分演算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()メソッドでは、minとmaxが逆になっていることです。
float c = smoothstep( outerRadius + w, outerRadius - w, d );
c-=smoothstep( innerRadius + w, innerRadius - w, d );
アウターラディウス+ w>アウターラディウス-w。
minとmaxの位置を1で交換することで見つけることができます。このように処理されたsmoothstep()の結果も逆になります。つまり、x <minが1を返す場合、x> maxが0を返す場合です。
したがって、uvポイントがouterRadiusの内側にある場合、cは1を返し、outerRadiusの外側にある場合、cは0を返します。
同様に、smoothstep(innerRadius + w、innerRadius-w、d)の場合、uvがinnerRadiusの内側にある場合は1を返し、innerRadiusの外側にある場合は0を返します。
つまり、2を引くと、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) );
現在の紫外線点が対応する時間内であると判断された場合、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"
}
総括する
シェーダートイを学ぶことは、大学で数学を勉強していた時代にさかのぼります。ある数学的問題に遭遇すると、解決が非常に難しくなります。百度は見つけにくく、周りにそれを理解している人がいないため、小さな問題が数日間続くことがありますが、インスピレーションが点滅するか、問題が解決する、喜び彼の気持ちを表現するのは難しいので、とても痛くて幸せです。。。