Escribir al frente
Después de leer algunos artículos sobre cómo dibujar líneas y puntos con el juguete de sombra, de repente quise convertirme en una estrella de cinco puntas para practicar mis manos. Pero pensándolo bien, todavía estaba lleno de "baches" cuando comencé. Después de lanzar durante un fin de semana, era solo una idea clara, pero el código estaba desordenado. Al final, honestamente busqué varias fórmulas y materiales en Internet. En medio del proceso, descubrí que había olvidado la fórmula para encontrar el círculo. No puedo evitar sentir que mis matemáticas realmente han sido devueltas. El profesor. Lo único que me siento aliviado es que lo último que salió, el efecto logrado en Shadertoy es este, la dirección está aquí .
Dibujar puntos en el sombreador se hace con la lógica de dibujar círculos . La implementación específica puede referirse al artículo anterior ; y el código de implementación del segmento de línea se basa en el gran dios del sitio web Shaderrtoy. La dirección está aquí . La línea El método () es más engorroso de entender directamente. Busqué por todo Baidu el principio de este dibujo lineal, pero finalmente obtuve la respuesta en un grupo de juegos. Qué maestro se esconde en el mercado. . . La descripción del principio específico se anotará en el siguiente código de implementación.
Cada círculo y línea dibujados en el sombreador ocupa una capa, y finalmente estas capas deben superponerse de acuerdo con la lógica que queremos, por lo que la cantidad de cálculo de tal efecto que se muestra al final es muy grande, y no lo esperaba en esta etapa Una buena manera de optimizar la pérdida de rendimiento de estos cálculos solo puede esperar descubrir en el futuro estudio. . .
Explicación del principio
Hablemos del principio de realizar esta estrella de cinco puntas.
Básicamente conocerás una cierta cantidad de matemáticas (probablemente). Los cinco puntos de una estrella regular de cinco puntas están todos en el mismo círculo, y cada punto adyacente está separado por 72 °, por lo que un punto es el centro del círculo, y la posición de las coordenadas de los cinco puntos se calcula usando la fórmula para encontrar los puntos en el círculo. La estrella de cinco puntas tiene cinco lados. Siempre que el punto actual y el punto separado se dibujen por separado, se puede formar una estrella de cinco puntas. La última imagen es más intuitiva
El principio es conocido. El siguiente paso es implementar el método en el sombreador. El primero es el método de dibujar puntos, que en realidad es el método de dibujar círculos. El código es el siguiente
//画点
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);
}
El principio que ya mencioné en el artículo anterior , aquí hay dos capas para dibujar un círculo, una de las cuales maneja el color del borde exterior del círculo, y la otra se usa para dibujar el color del círculo.
El siguiente es el método de dibujar segmentos de línea. Vi el método de dibujo lineal de candycat antes , usa la pendiente para dibujar, primero pega el principio del código
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);
}
Esto es para dibujar una línea encontrando la pendiente de dos puntos y luego usando la fórmula de la distancia para juzgar la distancia desde el punto a la línea. Es muy fácil de entender, pero hay un problema de que solo puedes dibujar una recta. ¡línea! ! ¡No es un segmento de línea! ! Si lo usas para dibujar líneas, el efecto logrado es así
Siempre que el punto de la pantalla coincida con la pendiente, se dibujará, por lo que no es adecuado para nuestro efecto. Finalmente, encontré un método muy poderoso para dibujar segmentos de línea en el sitio web Shadertoy. Esta línea de dibujo usa un vector para calcular la distancia desde el punto a la línea. El principio de implementación puede referirse a la introducción de este artículo . Finalmente, un Se adjunta comprensión personal.
El resultado calculado es el mismo que la parte de operación del siguiente código
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);
El dir0 en el código corresponde ab, el dir1 en el código corresponde a a, y la última distancia requerida h corresponde a la longitud del vector d.
El método integrado final para dibujar segmentos de línea es así
//画线
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);
}
La parte más difícil se ha resuelto y el resto es fácil. Permítanme hablar sobre qué cinco puntos sacar. El origen de las coordenadas se puede tomar como centro. Los cinco puntos de la estrella de cinco puntas pasan por el centro. La lógica de procesamiento es la siguiente: asumiendo que el grado del primer punto es a, el siguiente punto es a + 72 °, y el siguiente Los puntos son a + 72 ° + 72 °, y así sucesivamente, y finalmente estos puntos se guardan en un arreglo. Por supuesto, puede dibujar la capa directamente sin guardar la matriz, pero existe el problema de que la línea se dibuja en la capa posterior y se mostrará en la parte superior del punto.
¡Es importante mencionar aquí que los parámetros de entrada correspondientes de los métodos cos () y sin () son radianes! ! ¡Son radianes! ! ¡Ni un título! ! !
degree[i+1]=vec2(cos(d),sin(d);
La escritura anterior es incorrecta y el resultado será muy extraño.
La forma correcta de escribir debería ser así
degree[i+1]=vec2(cos(d*pi/180.0),sin((d*pi)/180.0));
En este caso, puede obtener nuevos efectos modificando ligeramente la fórmula. Por ejemplo, si escala la parte del pecado en 0.5, puede obtener una estrella de cinco puntas escalada.
Si el parámetro d está asociado con el tiempo de ejecución, la estrella de cinco puntas se puede girar ~
Finalmente, se adjunta el código completo de Shadertoy y Unity.
Sección 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);
}
}
Parte de la unidad
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"
}
para resumir
La parte de matemáticas es realmente agotadora. . . Realmente no debería ser un vago en matemáticas en la universidad.
Después de examinar algunos de los juguetes de sombra hechos por Daniel, una vez más sentí el encanto del sombreador y, al mismo tiempo, comprendí profundamente la falta de conocimiento. ¡No hay mucho que decir, vamos!