【Shader与ShaderToy 】画一个五角星
写在前面
看了几篇关于用shadertoy画线与画点的文章之后,突然想自己做一个五角星的效果来练练手。但是想归想,动起手来还是充满了“坎坷”。折腾了一个周末只是思路清晰,但代码却一塌糊涂,最后还是老老实实上网查阅各种公式资料,中途竟发现自己连求圆上点的公式都忘了,不禁感慨自己的数学真的已经还给老师了。唯一觉得欣慰的是最后东西是弄出来了,在shadertoy上实现的效果是这样的,地址在这里。
在shader中画点是用画圆的逻辑来做的,具体实现可以参照上一篇;而线段的实现代码是参考shadertoy网站的大神的,地址在这里的line()方法,直接理解起来比较繁琐,这画线的原理我搜遍百度都没查到究竟,最后破罐破摔竟然在一个游戏群里问到了答案,真是高手隐于市啊。。。具体原理说明会在后面的实现代码里注释。
shader里每画一个圆一条线都会占用到一个图层,最后需要将这些图层按我们想要的逻辑叠加起来,所以最后显示的这么一个效果的计算量是十分大的,现阶段也没想到好的办法来优化这些计算上性能的损耗,只能希望在之后的学习中摸索出来了。。。
原理说明
下面说下实现这个五角星的原理。
有一定的数学基本都会知道(大概吧)。正五角星的五个点都同一个圆上,每个相邻的点都相差72°,那么个以一个点为圆心,用求圆上点的公式算出五个点的坐标位置。而五角星有五条边,只要分别让当前的点与相隔的一个点画上线,那么就可以组成一个五角星。上个图直观一点吧
原理知道了接下来是在shader实现的方法.首先是画点的方法,其实也就是画圆的方法,代码如下所示
//画点
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-kxfloat 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);
}
最难的部分已经解决了,剩下的部分就轻松了。再来说说要画哪五个点。可以以坐标原点为圆心,五角星的五个点都是经过圆心的,那么处理的逻辑是这样的:假设第一个点的度数为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可以得到缩放的五角星
将参数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);}}
Unity部分
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 200Pass{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.1415926float4 _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"
}
总结
数学的部分真的很累人。。。大学的时候真不应该在数学上偷懒啊。
在浏览过一些大牛制作的shadertoy后,再一次感受到了shader的魅力,同时也深刻了解到自己知识面的不足。不多说了,加油吧!
【Shader与ShaderToy 】画一个五角星相关推荐
- python turtle库画一个五角星 【Python初学 绘制五角星】
一.常用命令 import turtle #导入turtle库 import time #时间模块 turtle.forward() #向前移动 turtle.right() #顺时针旋转 turtl ...
- 五角星具有“胜利”的含义。被很多国家的军队作为军官(尤其是高级军官)的军衔标志使用。也常常运用在旗帜上。我们的国旗上就有五角星。请你也画一个五角星吧。
五角星具有"胜利"的含义.被很多国家的军队作为军官(尤其是高级军官)的军衔标志使用.也常常运用在旗帜上.我们的国旗上就有五角星.请你也画一个五角星吧. [输入] 无 [输出] * ...
- 案例四、1.使用Canvas画一个五角星
在使用Cnavas元素画一个五角星时,最难的应该是五角星角的坐标. 首先在Canvas元素中,y轴是向下为正. 由于五角星有五个角,圆为360度,所以角与角之间的距离应该为72度. 此时的五角星的十个 ...
- 无聊画一个五角星玩玩
无聊,画一个五角星玩玩 code https://github.com/char0xface/five_pointed_star EXE 链接:https://pan.baidu.com/s ...
- python同切圆_画一组同切圆 画一组同心圆 画一个五角星 画一个黄色实心五角星 turtle.up() turtle.goto(0,-100) turtle.down() ...
1.画一组同切圆 >>> import turtle >>> turtle.circle(10) >>> turtle.circle(15) &g ...
- c语言课程设计台球厅系统,c语言画图用c语言画一个五角星和一个两周期 – 手机爱问...
2013-11-23 怎么画出一个正五角星 尺规作图: 1.画一条水平线,通过此线上的任意点做一个圆 2.将圆规的一腿放在圆与直线的其一交点上,通过上述圆的圆心画半圆,并与之交两点.连接这两点做垂直线 ...
- python画一个五角星
用python 画一个可爱的五角星,这是效果. 参考代码: from turtle import *color('red','yellow')screensize(800, 600, "bl ...
- OpenGL二 - 画一个五角星 pentagram
使用数学计算出10个五角星的坐标,然后画出图形. 效果如下: 一直想使用Polygen画是实心的,但是Polygen对于凹的地方支持不好,会出问题,所以就使用直线画了. 数学计算可以参考一个百度提问: ...
- canvas笔记-画一个五角星(含算法)
算法如下: 上面的图是正三角形: 说下算法,关于上面那些x,y是怎么算出来的. 这里大圆的5个顶点,每个顶点占用的角度为360/5 = 72度,左边那个18度是通过90 - 72 = 18度. 大圆半 ...
最新文章
- 为 ASP.NET Datagrid 创建自定义列
- IO测试工具之fio详解
- mysql mongo关联查询语句_MongoDB 集合间关联查询后通过$filter进行筛选
- Apache PDFbox快速开发指南
- 针织物染色常见的6大问题
- 【PyCharm】10个省时间的 PyCharm 技巧
- 简繁体在线切换JS插件
- 微信打飞机思路总结 蓝懿教育
- Ubuntu下用snap7与西门子通信
- 解决NintendoSwitch安装SXPro后开机长期蓝屏问题
- 如何用matlab绘制心形线,心形线的matlab程序
- 字节跳动2019春招算法题
- java中四大层次结构
- CAS 单点登录使用详解
- word文档中的水印怎样去除?这三个方法教大家快速搞定!
- error: cannot use promoted field in struct literal of type [duplicate]
- Dynamics 365 设置Postman environment For WebAPI
- Unity3D插件 AnyPortrait 2D骨骼动画制作
- 手把手教你做20道菜全套教程
- excel下拉菜单vba_Excel 2007的经典菜单
热门文章
- Flutter 2.2 更新详解
- Incomplete Multimodal Learning(不完整多模态学习)
- 使用Navicat导入execl到mysql数据库中日期值显示0000-00-00的问题解决
- 【SIGN】函数使用技巧
- 东方财富、同花顺、大智慧、通达信的Level2行情接口哪个好?
- ADS 常见问题及解决方法
- Visual Studio(VS)的各个版本下载及安装
- 微服务之服务监控篇 ActuatorAdmin
- 数据库表和表字段的命名规范
- 论文翻译-Scene Text Detection and Recognition: The Deep Learning Era