앞에 쓰기
셰이더 토이로 선과 점을 그리는 방법에 대한 몇 가지 기사를 읽은 후 갑자기 손을 연습 할 수있는 다섯 개의 별을 만들고 싶었습니다. 그러나 그것에 대해 생각했을 때 내가 시작했을 때 그것은 여전히 "범프"로 가득 차 있었다. 주말에 던지 더니 분명한 생각 이었지만 코드가 엉망이 됐고 결국 인터넷에서 다양한 공식과 자료를 솔직히 찾아 봤는데 그 과정에서 잊었다는 사실을 알게되었습니다. 원을 찾기위한 공식입니다. 제 수학이 정말로 돌아 왔다는 느낌을받을 수 없습니다. 선생님. 내가 안심할 수있는 유일한 것은 마지막 것이 나왔다는 것입니다. shadertoy에 대한 효과는 이것 입니다. 주소는 여기 입니다.
셰이더에서 포인트 그리기는 원 그리기 로직 으로 수행됩니다. 특정 구현은 이전 기사를 참조 할 수 있으며 선분의 구현 코드는 shadertoy 웹 사이트의 위대한 신을 기반으로합니다. 주소는 여기 입니다. () 방법은 직접 이해하기가 더 번거 롭습니다.,이 선화의 원리를 찾기 위해 바이두 곳곳을 검색했지만 마침내 게임 그룹에서 답을 얻었습니다. . . 구체적인 원칙 설명은 다음 구현 코드에 주석으로 표시됩니다.
셰이더에 그려진 각 원과 선은 하나의 레이어를 차지하고 마지막으로 이러한 레이어는 우리가 원하는 논리에 따라 중첩되어야하므로 마지막에 표시되는 이러한 효과의 계산량은 매우 큽니다. 이 단계 이러한 계산의 성능 손실을 최적화하는 좋은 방법은 향후 연구에서만 확인할 수 있습니다. . .
원리 설명
이 다섯개 별을 실현하는 원리에 대해 이야기합시다.
기본적으로 당신은 (아마도) 어느 정도의 수학을 알게 될 것입니다. 일반 5 개 별의 5 개 점은 모두 같은 원에 있고 인접한 각 점은 72 ° 떨어져 있으므로 한 점은 원의 중심이며 5 개 점의 좌표 위치는 다음 공식을 사용하여 계산됩니다. 원에서 점을 찾는 것. 오 각별은 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);
}
이전 기사 에서 이미 언급 한 원칙 , 여기에 원을 그리는 두 개의 레이어가 있는데, 그중 하나는 원의 바깥 쪽 테두리 색상을 처리하고 다른 하나는 원의 색상을 그리는 데 사용됩니다.
다음은 선분을 그리는 방법입니다. 나는 전에 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);
}
두 점의 기울기를 찾아 선을 그린 다음 거리 공식을 이용하여 점에서 선까지의 거리를 판단하는 것입니다. 이해하기는 매우 쉽지만 직선 만 그릴 수있는 문제가 있습니다. 선! ! 선분이 아닙니다! ! 선을 그리는 데 사용하면 다음과 같은 효과가 나타납니다.
화면의 점이 경사면과 일치하면 그려 지므로 효과에 적합하지 않습니다. 마지막으로 shadertoy 웹 사이트에서 매우 강력한 선분 그리기 방법을 발견했습니다.이 그리기 선은 벡터를 사용하여 점에서 선까지의 거리를 계산합니다. 구현 원리 는 이 기사 의 소개를 참조 할 수 있습니다 . 개인적인 이해가 첨부되어 있습니다.
계산 된 결과는 다음 코드의 연산 부분과 동일합니다.
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));
이 경우 수식을 약간 수정하여 새로운 효과를 얻을 수 있으며, 예를 들어 sin 부분을 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"
}
요약하자면
수학 부분은 정말 피곤합니다. . . 대학에서 수학을 정말 게으 르면 안 돼요.
다니엘이 만든 셰이더 토이를 살펴본 후 다시 한 번 셰이더의 매력을 느꼈고 동시에 지식의 부족을 깊이 이해했습니다. 할 말이 많지 않습니다.