【Shader与ShaderToy】画一个圆

写在前面

        今天准备试着在Unity和在ShaderToy的网站上分别搞一个圆,看一下两者具体有什么区别。

        关于Unity与shadertoy运行前的一些准备工作会略过,直接上代码与显示的效果。

Unity部分

        用Unity来+VS来编写shader是本人比较喜欢的,直观快捷,而且还能直接在面板上改变参数设置效果,比网页不知道高了多少去了!下面贴上shader的代码

Shader "Custom/circle" {
	Properties {
		//xy表示圆心在屏幕中的uv值,z为半径,w为圆边缘的平滑值
		_parameters("circleParameter",Vector)=(0.5,0.5,10,0)
		_Color("circleColor",COLOR)=(1,1,1,1)
	}
	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

		float4 _parameters;
		float4 _Color;
		float4 _backgroundColor;

		struct v2f{
			float4 pos:SV_POSITION;
			float4 srcPos:TEXCOORD0;
		};

		v2f vert(appdata_base v){
			v2f o;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
			o.srcPos=ComputeScreenPos(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 cicle(vec2 pos,vec2 center,float radius,float3 col,float antialias){
			//求出点到圆心距离,如果为正则在圆外 负在圆内 我们需要对圆内的点进行上色 即对负值进行处理
			float d=length(pos-center)-radius;
			//判断d的大小 如果小于0则返回0 如果大于antialias返回1 返回值在0-1之间
			//smoothstep(a,b,t) 判断t t<a返回0,t>b返回1,t在a-b之间反差值返回0-1 
			float t=smoothstep(0,antialias,d);
			//返回颜色值 在圆外的设置alpha=0透明 
			return vec4(col,1.0-t);

		}
		vec4 main(vec2 fragCoord){
			vec2 pos=fragCoord;
			//给背景一个动态的颜色
			vec3 temp = 0.5 + 0.5*cos(iGlobalTime+pos.xyx/_ScreenParams.y+vec3(0,2,4));
			//获取背景的颜色
			vec4 layer1=vec4(temp,1.0);
			//获取圆
			vec4 layer2=cicle(pos,_parameters.xy*iResolution.xy,_parameters.z,_Color.rgb,_parameters.w);
			//插值处理,使边界更模糊化,layer2中的_parameters.w值越大越模糊
			return mix(layer1,layer2,layer2.a);
		}



		ENDCG
		}
	}
	FallBack "Diffuse"
}

        代码看似很长很长的一段,但其实就是套上了shadertoy转shader的模板,模板的说明可以点击这里查看,关键的代码就是画圆的circle函数。shadertoy大概的原理就是画一个一个的图层,然后用各种效果混合上去,跟PS画图差不多,这知道为什么效果越精彩的shadertoy网页上看起来就越卡,都不知道叠了多少个图层了。

        为了让Unity的效果与shadertoy那边一样,还特地加了默认的那个可变色背景上去,代码如下

//给背景一个动态的颜色
vec3 temp = 0.5 + 0.5*cos(iGlobalTime+pos.xyx/_ScreenParams.y+vec3(0,2,4));

        利用余弦函数并配合一个不断变化的时间值iGlobalTime,让背景能够不断改变颜色。Unity上的运行效果如下

ShaderToy部分

        本来以为shadertoy上会很快搞定,毕竟Unity是为了兼容shadertoy才写了一堆定义的代码,没想到找语法的错误都能找个半小时,而且还是同一个错误,仿佛回到了刚学编程的时代啊。。。报错的内容如下

        

        别被这些红框框给吓到了,两个报错都是同一个原因,参数类型不对。shadertoy上,只要你定义的是float型,那参数一定要是float型,不然就直接no matching overloaded function found。报错的时候没意识到这个问题,我还以为明明就是个普通的内置函数,怎么就突然报没有重载的方法了,为次我还去看了其他人写的代码,都还没发现问题所在,最后万念俱灰也不知为何将后面的return返回值的1.0-t改成了1-t,红通通的报错内容才打醒了我,原来是参数类型不对!!!!smoothstep方法一定要浮点型!!不会帮你自己转换!!!就算你是0也好,也得写成0.0!!!

        下面是错误的shadertoy代码

float t=smoothstep(0,antialias,d);

        下面是正确的shadertoy代码

float t=smoothstep(0.0,antialias,d);

        真是相当的。。。严谨啊。。。下面贴上shadertoy的代码

//输入参数(当前点位置,中心点位置,点的半径,颜色,与背景过渡的平滑值)
vec4 cicle(vec2 pos,vec2 center,float radius,vec3 col,float antialias){
    //求圆心距离
    float d=length(pos-center)-radius;
    //smoothstep(a,b,t)函数 t<a return a, t>b return b
    float t=smoothstep(0.0,antialias,d);
    return vec4(col,1.0-t);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    //获取点的位置
    //iResolution为屏幕的分辨率
    //fragCoord为当前点的位置 原点是左下角
    //返回的uv是以屏幕中心为原点
    vec2 uv =(2.0*fragCoord.xy-iResolution.xy) /iResolution.y;
    //中心点
    vec2 point1 = vec2(0,0);
    //圆的颜色
    vec3 color=vec3(1,0,0);
    // layer1 cos函数
    vec3 temp = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
    vec4 layer1= vec4(temp,1);
    //layer2 平滑的圆
    vec4 layer2 = cicle(uv,point1,0.8,color,0.03);
    // 输出像素
    fragColor = mix(layer1,layer2,layer2.a);
}

       

        很简单的一个效果,基本跟unity的区别,但浏览器上写着色顺真的十分的不方便!!webGL真的相当容易崩掉!!而且输入的参数也不怎么好改。。。再一次体会到unity的便利性啊。。

总结

        在此说下unity下的shader与shadertoy代码上的一些区别。

        1.shadertoy上的字符类型不会自动强转。如果你定义的函数中有个参数是float型的,调用时使用int型就肯定报错;0这个数值是整型,0.0才是符点型。(unity上试了下类型强转没有问题)

        2.两种shader的输入位置不一样。

        unity里shader的片段函数frag()代码中输入的是顶点的投影坐标,需要对坐标进行归一化处理再与屏幕分辨率相乘获得对应的坐标点

//(iParam.srcPos.xy/iParam.srcPos.w)获取归一化的点 范围在(0-1)
//与屏幕分辨率相乘获得实际的像素坐标,坐标原点在正中心
vec2 fragCoord=((iParam.srcPos.xy/iParam.srcPos.w)*_ScreenParams.xy);

        shadertoy中main方法输入的是顶点对应的像素位置,原点是屏幕的左下角,需要转换成原点在中心,这种将原本范围是(0,a)转成(-b,b)的操作,原本坐标的位置就会变成y=bx-b (0<x<a)。但这样操作数值会放大许多倍,所以需要进行统一缩放,将得到的位置坐标再除以屏幕的一个轴。

//获取点的位置
//iResolution为屏幕的分辨率
//fragCoord为当前点的位置 原点是左下角
//返回的uv是以屏幕中心为原点 并且除以分辨率的一个轴进行缩放
vec2 uv =(2.0*fragCoord.xy-iResolution.xy) /iResolution.y;

PS:将理解的内容描述出来也不是件容易的事情啊。。。

猜你喜欢

转载自blog.csdn.net/ssssssilver/article/details/81129441