前に書く
シェーダートイで線や点を描くことについての記事をいくつか読んだ後、私は突然、自分の手を練習するために五芒星になりたいと思いました。でも考えてみると、始めた時はまだ「でこぼこ」でいっぱいでした。週末に投げた後、それは明確な考えでしたが、コードが混乱していました。結局、インターネットでさまざまな数式や資料を正直に調べました。その過程で、忘れていたことがわかりました。円を見つけるための公式。私の数学が本当に戻ってきたと感じずにはいられません。先生。私が安心した唯一のことは、最後のものが出ているということです、shadertoyで達成された効果はこれです、アドレスはここにあります。
シェーダーでの点の描画は、円を描画するロジックで行われます。特定の実装は前の記事を参照できます。線分の実装コードは、シェーダーのウェブサイトの偉大な神に基づいています。アドレスはここにあります。線()方法は直接理解するのが面倒です。、この線画の原理をバイドゥ中を探しましたが、ついにゲームグループで答えを得ました。マスターが市場に隠されているもの。。。具体的な原則の説明は、次の実装コードで注釈が付けられます。
シェーダーに描かれた円や線はそれぞれレイヤーを占めており、最終的には必要なロジックに従ってこれらのレイヤーを重ね合わせる必要があるため、最終的に表示されるこのような効果の計算量は非常に多く、この段階これらの計算のパフォーマンス損失を最適化するための良い方法は、将来の研究でのみ見つけることを期待できます。。。
原理説明
この五芒星を実現する原理についてお話しましょう。
基本的に、あなたはある程度の数学を知っているでしょう(おそらく)。通常の五芒星の5点はすべて同じ円上にあり、隣接する各点は72°離れているため、1点が円の中心になり、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);
}
前回の記事ですでに述べ た原理ですが、ここでは円を描くための2つのレイヤーがあり、1つは円の外側の境界線の色を処理し、もう1つは円の色を描くために使用されます。
次は線分の描き方です。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);
}
これは、2点の傾きを求め、距離の公式を使って点から線までの距離を判断することで線を引くことです。非常にわかりやすいですが、直線しか引けないという問題があります。ライン!!線分ではありません!!線を引くとこんな効果が出ます
画面上の点が傾きと一致している限り描画されるため、効果には適していません。最後に、shadertoyのWebサイトで、非常に強力な線分の描画方法を見つけました。この描画線は、ベクトルを使用して点から線までの距離を計算します。実装の原則は、この記事の紹介を参照できます。最後に、個人的な理解も添付されています。
計算結果は、次のコードの操作部分と同じです。
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));
この場合、式を少し変更することで新しい効果を得ることができます。たとえば、罪の部分を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"
}
総括する
数学の部分は本当に疲れます。。。私は大学で数学を怠ってはいけません。
ダニエルが作ったシェーダーをいくつか見てみると、シェーダーの魅力を改めて感じ、知識不足を深く理解しました。言うことはあまりありません、さあ!