Schreiben Sie vor
Nachdem ich einige Artikel über das Zeichnen von Linien und Punkten mit Shadertoy gelesen hatte, wollte ich mich plötzlich zu einem fünfzackigen Stern machen, um meine Hände zu üben. Aber wenn ich darüber nachdenke, war es immer noch voller "Unebenheiten", als ich anfing. Nachdem ich ein Wochenende lang geworfen hatte, war es nur eine klare Idee, aber der Code war durcheinander. Am Ende habe ich ehrlich verschiedene Formeln und Materialien im Internet nachgeschlagen. Während des Prozesses stellte ich fest, dass ich es vergessen hatte Die Formel, um den Kreis zu finden. Ich kann nicht anders, als das Gefühl zu haben, dass meine Mathematik wirklich zurückgekehrt ist. Der Lehrer. Das einzige, was mich erleichtert, ist, dass das Letzte, was herauskommt, der Effekt, der auf Shadertoy erzielt wird, ist, dass die Adresse hier ist .
Das Zeichnen von Punkten im Shader erfolgt mit der Logik des Zeichnens von Kreisen . Die spezifische Implementierung kann auf den vorherigen Artikel verweisen, und der Implementierungscode des Liniensegments basiert auf dem großen Gott der Shadertoy-Website. Die Adresse ist hier . Die Linie () Methode ist umständlicher zu verstehen. Ich habe in ganz Baidu nach dem Prinzip dieser Strichzeichnung gesucht, aber schließlich habe ich die Antwort in einer Spielgruppe erhalten. Was für ein Meister ist auf dem Markt versteckt. . . Die spezifische Prinzipbeschreibung wird im folgenden Implementierungscode kommentiert.
Jeder im Shader gezeichnete Kreis und jede Linie nimmt eine Ebene ein, und schließlich müssen diese Ebenen gemäß der von uns gewünschten Logik überlagert werden, sodass der am Ende angezeigte Berechnungsbetrag für einen solchen Effekt sehr groß ist, und ich habe ihn nicht erwartet Diese Phase Ein guter Weg, um den Leistungsverlust dieser Berechnungen zu optimieren, kann nur in der zukünftigen Studie herausgefunden werden. . .
Grundsatzerklärung
Lassen Sie uns über das Prinzip der Realisierung dieses fünfzackigen Sterns sprechen.
Grundsätzlich kennen Sie (wahrscheinlich) ein gewisses Maß an Mathematik. Die fünf Punkte eines regulären fünfzackigen Sterns befinden sich alle auf demselben Kreis, und jeder benachbarte Punkt ist 72 ° voneinander entfernt. Ein Punkt ist also der Mittelpunkt des Kreises, und die Koordinatenposition der fünf Punkte wird unter Verwendung der Formel für berechnet Finden der Punkte auf dem Kreis. Der fünfzackige Stern hat fünf Seiten. Solange der aktuelle Punkt und der getrennte Punkt getrennt gezeichnet werden, kann ein fünfzackiger Stern gebildet werden. Das letzte Bild ist intuitiver
Das Prinzip ist bekannt. Der nächste Schritt besteht darin, die Methode im Shader zu implementieren. Der erste Schritt ist die Methode zum Zeichnen von Punkten, die eigentlich die Methode zum Zeichnen von Kreisen ist. Der Code lautet wie folgt
//画点
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);
}
Das Prinzip, das ich bereits im vorherigen Artikel erwähnt habe , sind zwei Ebenen zum Zeichnen eines Kreises, von denen eine die Farbe des äußeren Randes des Kreises behandelt und die andere zum Zeichnen der Farbe des Kreises verwendet wird.
Als nächstes wird die Methode zum Zeichnen von Liniensegmenten beschrieben. Ich habe die Strichzeichnungsmethode von Candycat schon einmal gesehen . Sie verwendet die Steigung zum Zeichnen und fügt zuerst das Code-Prinzip ein
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);
}
Dies dient zum Zeichnen einer Linie, indem die Steigung zweier Punkte ermittelt und anschließend anhand der Abstandsformel die Entfernung vom Punkt zur Linie beurteilt wird. Es ist sehr leicht zu verstehen, aber es gibt ein Problem, dass Sie nur eine Gerade zeichnen können Linie! ! Kein Liniensegment! ! Wenn Sie damit Linien zeichnen, wird folgender Effekt erzielt
Solange der Punkt auf dem Bildschirm mit der Neigung übereinstimmt, wird er gezeichnet, sodass er für unseren Effekt nicht geeignet ist. Schließlich habe ich auf der Shadertoy-Website eine sehr leistungsfähige Methode zum Zeichnen von Liniensegmenten gefunden. Diese Zeichnungslinie verwendet einen Vektor, um den Abstand vom Punkt zur Linie zu berechnen. Das Prinzip der Implementierung kann sich auf die Einleitung dieses Artikels beziehen persönliches Verständnis ist beigefügt.
Das berechnete Ergebnis entspricht dem Operationsteil des folgenden Codes
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);
Das dir0 im Code entspricht b, das dir1 im Code entspricht a und der letzte erforderliche Abstand h entspricht der Länge des Vektors d.
Die endgültige integrierte Methode zum Zeichnen von Liniensegmenten ist wie folgt
//画线
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);
}
Der schwierigste Teil wurde gelöst, und der Rest ist einfach. Lassen Sie mich darüber sprechen, welche fünf Punkte zu ziehen sind. Der Ursprung der Koordinaten kann als Mittelpunkt genommen werden. Die fünf Punkte des fünfzackigen Sterns verlaufen durch den Mittelpunkt. Die Verarbeitungslogik lautet wie folgt: Unter der Annahme, dass der Grad des ersten Punktes a ist, ist der nächste Punkt a + 72 ° und der nächste Die Punkte sind + 72 ° + 72 ° usw. und schließlich werden diese Punkte in einem Array gespeichert. Natürlich können Sie die Ebene direkt zeichnen, ohne das Array zu speichern. Es besteht jedoch das Problem, dass die Linie auf der dahinter liegenden Ebene gezeichnet wird und oben auf dem Punkt angezeigt wird.
Es ist wichtig zu erwähnen, dass die entsprechenden Eingabeparameter der Methoden cos () und sin () Bogenmaß sind! ! Es ist Bogenmaß! ! Kein Abschluss! ! !
degree[i+1]=vec2(cos(d),sin(d);
Das obige Schreiben ist falsch und das Ergebnis wird sehr seltsam sein.
Die richtige Schreibweise sollte so sein
degree[i+1]=vec2(cos(d*pi/180.0),sin((d*pi)/180.0));
In diesem Fall können Sie neue Effekte erzielen, indem Sie die Formel leicht ändern. Wenn Sie beispielsweise den Sin-Teil um 0,5 skalieren, erhalten Sie einen skalierten fünfzackigen Stern.
Wenn der Parameter d der Laufzeit zugeordnet ist, kann der fünfzackige Stern ~ gedreht werden
Schließlich ist der vollständige Code von Shadertoy und Unity beigefügt.
ShaderToy-Abschnitt
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);
}
}
Einheitsteil
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"
}
um zusammenzufassen
Der mathematische Teil ist wirklich anstrengend. . . Ich sollte im College wirklich nicht faul in Mathe sein.
Nachdem ich einige der von Daniel hergestellten Shadertoys durchgesehen hatte, spürte ich wieder den Charme des Shaders und verstand gleichzeitig den Mangel an Wissen. Nicht viel zu sagen, komm schon!