Unity Shader基础篇-常用函数的使用与案例
发表于2018-06-12
一、前言
本篇文章和大家讲解下Cg的常用函数的使用案例,帮助巩固Cg语言的基础知识。这些函数都可以在Cg的教程里The Cg Tutorial找到示例代码和函数意义。本文讲解几个常用的函数,分别有:
1、Step(a,x):如果x<a返回0;如果x>或=a返回1
2、Clamp(x,a,a):如果x<a返回a;如果x>b返回b;如果在a和b之间就返回x
3、smoothstep(min,max,x):返回的值为–2*(( x – min )/( max – min ))3 +3*(( x – min )/( max – min ))2
4、lerp(a,b,f):线性插值函数,返回值为(1-f)*a+b*f
5、三角函数sin、cos
二、常用函数的使用实例
1、Step函数:在Unity中的Shader代码
Shader "Unlit/Chapter5-Step" { Properties { _background("背景色",Color)=(0,0,0,0) } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always CGINCLUDE ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" float4 _background; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.uv.y = 1 - o.uv.y; return o; } // Functions fixed4 frag(v2f i) : SV_Target { float2 r = 2.0*(i.uv - 0.5); //_ScreenParams是Unity内置的变量 float aspectRatio = _ScreenParams.x / _ScreenParams.y; r.x *= aspectRatio; fixed3 pixel = _background.xyz; float edge, variable, ret; //将屏幕划分成五个部分 //第一部分 if (r.x < -0.6*aspectRatio) { variable = r.y; edge = 0.2; if (variable > edge) { ret = 1.0; } else { ret = 0; } } else if (r.x < -0.2*aspectRatio) { variable = r.y; edge = -0.2; //step(a,x):如果x<a结果返回0,反之返回1 ret = step(edge, variable); } else if (r.x < 0.2*aspectRatio) { ret = 1.0 - step(0.5, r.y); } else if (r.x < 0.6*aspectRatio) { ret = 0.3 + 0.5*step(-0.4, r.y); } else { ret = step(-0.3, r.y) * (1.0 - step(0.2, r.y)); } pixel = fixed3(ret, ret, ret); return fixed4(pixel, 1.0); } ENDCG } } }
得到的效果图如图所示:
2、Clamp:在Unity中的代码如下:
Shader "Unlit/Chapter5-Clamp" { Properties { _background("背景色",Color) = (0,0,0,0) } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always // 追加 CGINCLUDE //定义宏 #define PI 3.14159 ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" float4 _background; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.uv.y = 1 - o.uv.y; return o; } // Functions fixed4 frag(v2f i) : SV_Target { float2 r = 2.0*(i.uv - 0.5); //_ScreenParams是Unity内置的变量 float aspectRatio = _ScreenParams.x / _ScreenParams.y; r.x *= aspectRatio; fixed3 pixel = _background.xyz; float edge, variable, ret; //第一部分 if (i.uv.x < 0.25) { // part1 ret = i.uv.y; } else if (i.uv.x < 0.5) { // part2 float minVal = 0.3; float maxVal = 0.6; variable = i.uv.y; if (variable < minVal) { ret = minVal; } if (variable > minVal && variable < maxVal) { ret = variable; } if (variable > maxVal) { ret = maxVal; } } else if (i.uv.x < 0.75) { // part3 float minVal = 0.6; float maxVal = 0.8; variable = i.uv.y; //clam(x,a,b):x如果小于a返回a,如果大于b返回b,在a~b范围内返回x ret = clamp(variable, minVal, maxVal); } else { // part4 float y = cos(5.0 * 2.0 * PI *i.uv.y); y = (y + 1.0)*0.5; // map [-1,1] to [0,1] ret = clamp(y, 0.2, 0.8); } pixel = fixed3(ret, ret, ret); return fixed4(pixel, 1.0); } ENDCG } } }
效果图如图所示:
说明:对比1和2的图会明显发现,在使用Clamp处理的时候,边缘的颜色会有渐变的效果。
3、smoothstep:这个脚本只给出片段着色器部分,其他部分同2,代码如下:
fixed4 frag(v2f i) : SV_Target { fixed3 pixel = _background.xyz; float edge, variable, ret; if (i.uv.x < 1.0 / 5.0) { // part1 edge = 0.5; ret = step(edge, i.uv.y); } else if (i.uv.x < 2.0 / 5.0) { // part2 float edge0 = 0.45; float edge1 = 0.55; float t = (i.uv.y - edge0) / (edge1 - edge0); float t1 = clamp(t, 0.0, 1.0); ret = t1; } else if (i.uv.x < 3.0 / 5.0) { // part3 float edge0 = 0.45; float edge1 = 0.55; float t = clamp((i.uv.y - edge0) / (edge1 - edge0), 0.0, 1.0); float t1 = 3.0*t*t - 2.0*t*t*t; ret = t1; } else if (i.uv.x < 4.0 / 5.0) { // part4 //smoothstep(min,max,x):x=-2*((x-min)/(max-min))^3+3*((x-min)/(max-min))^2,当x=min时返回0,当x=max时返回1 ret = smoothstep(0.45, 0.55, i.uv.y); } else if (i.uv.x < 5.0 / 5.0) { float edge0 = 0.45; float edge1 = 0.55; float t = clamp((i.uv.y - edge0) / (edge1 - edge0), 0.0, 1.0); float t1 = t*t*t*(t*(t*6.0 - 15.0) + 10.0); ret = t1; } pixel = fixed3(ret, ret, ret); return fixed4(pixel, 1.0); }
得到的效果图如上图所示,这个效果要对比之前的两个,可以发现有明显的过渡效果。当然不是说这个函数就比之前的函数好用,各有千秋,只是在本篇文章中特有的安排而已。
4、lerp函数,Unity中的Shader代码如下:
Shader "Unlit/Chapter5-Lerp" { Properties { _background("背景色",Color) = (0,0,0,0) _col1("颜色1",Color)=(0,0,0,0) _col2("颜色2",Color)=(0,0,0,0) } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always CGINCLUDE //定义宏 #define PI 3.14159 ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" float4 _background; float4 _col1; float4 _col2; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.uv.y = 1 - o.uv.y; return o; } // Functions fixed4 frag(v2f i) : SV_Target { fixed3 pixel = _background.xyz; fixed3 ret; if (i.uv.x < 1.0 / 5.0) { // part1 float x0 = 0.2; float x1 = 0.7; float m = 0.1; float val = x0 * (1.0 - m) + x1*m; ret = fixed3(val, val, val); } else if (i.uv.x < 2.0 / 5.0) { // part2 float x0 = 0.2; float x1 = 0.7; float m = i.uv.y; float val = x0*(1.0 - m) + x1*m; ret = fixed3(val, val, val); } else if (i.uv.x < 3.0 / 5.0) { // part3 float x0 = 0.2; float x1 = 0.7; float m = i.uv.y; //lerp(a,b,f)返回(1-f)*a+b*f float val = lerp(x0, x1, m); ret = fixed3(val, val, val); } else if (i.uv.x < 4.0 / 5.0) { // part4 float m = i.uv.y; ret = lerp(_col1, _col2, m); } else if (i.uv.x < 5.0 / 5.0) { float m = smoothstep(0.5, 0.6, i.uv.y); ret = lerp(_col1, _col2, m); } pixel = ret; return fixed4(pixel, 1.0); } ENDCG } } }
效果图如图所示:
这个Shader中对lerp函数和smoothstep函数做了对比,其中第三个区域是使用lerp函数的效果,第四个区域是先进行了smoothstep处理在进行lerp处理的效果。
5、lerp函数与直接的颜色加减进行对比:完整的Shader代码如下:
Shader "Unlit/Chapter5-ColorAdd&Substr" { Properties { } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always // 追加 CGINCLUDE // 添加画圆盘的方法 float disk(float2 r, float2 center, float radius) { float distanceFromCenter = length(r - center); float outsideOfDisk = smoothstep(radius - 0.005, radius + 0.005, distanceFromCenter); float insideOfDisk = 1.0 - outsideOfDisk; return insideOfDisk; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.uv.y = 1 - o.uv.y; return o; } // ANTI-ALIASING WITH SMOOTHSTEP fixed4 frag(v2f i) : SV_Target { float2 r = 2.0 * (i.uv - 0.5); float aspectRatio = _ScreenParams.x / _ScreenParams.y; r.x *= aspectRatio; fixed3 black = float3(0.0, 0.0, 0.0); // black fixed3 white = float3(1.0, 1.0, 1.0); fixed3 gray = float3(0.3, 0.3, 0.3); fixed3 col1 = float3(0.216, 0.471, 0.698); // blue fixed3 col2 = float3(1.00, 0.329, 0.298); // red fixed3 col3 = float3(0.867, 0.910, 0.247); // yellow fixed3 ret; fixed3 pixel; float d; //三个部分的画法各有优势 //第一部分背景是灰色,简单的覆盖叠加 if (i.uv.x < 1.0 / 3.0) { // part1 ret = gray; d = disk(r, float2(-1.1, 0.3), 0.4); ret = lerp(ret, col1, d); d = disk(r, float2(-1.3, 0.0), 0.4); ret = lerp(ret, col2, d); d = disk(r, float2(-1.05, -0.3), 0.4); ret = lerp(ret, col3, d); } //第二部分背景是黑色,通过颜色相加来实现 else if (i.uv.x < 2.0 / 3.0) { // part2 // Color addition ret = black; ret += disk(r, float2(0.1, 0.3), 0.4) * col1; ret += disk(r, float2(-0.1, 0.0), 0.4) * col2; ret += disk(r, float2(0.15, -0.3), 0.4) * col3; } //第三部分背景是白色,通过相减来实现颜色的显示 else if (i.uv.x < 3.0 / 3.0) { // part3 // Color substraction ret = white; ret -= disk(r, float2(1.1, 0.3), 0.4) * col1; ret -= disk(r, float2(1.05, 0.0), 0.4) * col2; ret -= disk(r, float2(1.35, -0.25), 0.4) * col3; } pixel = ret; return fixed4(pixel, 1.0); } ENDCG } } }
得到的效果图如图所示:第一部分通过lerp函数来处理颜色重叠的部分,第二、三部分之间通过颜色的加减来处理颜色重叠的部分。
6、三角函数,sin、cos函数:利用正弦余弦函数来做图形的旋转,效果图如图所示:
蓝色的网格和蓝色的矩形、圆盘都是固定的,旋转的是红色的网格以及网格上的矩阵和圆盘,它的shader代码如下:
Shader "Unlit/Chapter6-Rotation" { Properties { _RotateAngle("旋转的角度",Range(0,360))=36 } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always // CGINCLUDE #define PI 3.14159 // 使用函数来创建网格,返回的值再乘以颜色及得到网格图形 float coordinateGrid(float2 r) { float3 axisCol = float3(0.0, 0.0, 1.0); float3 gridCol = float3(0.5, 0.5, 0.5); float ret = 0.0; // 画线 const float tickWidth = 0.1; for (float i = -2.0; i<2.0; i += tickWidth) { ret += 1.0 - smoothstep(0.0, 0.008, abs(r.x - i)); ret += 1.0 - smoothstep(0.0, 0.008, abs(r.y - i)); } // 画坐标轴 ret += 1.0 - smoothstep(0.001, 0.015, abs(r.x)); ret += 1.0 - smoothstep(0.001, 0.015, abs(r.y)); return ret; } // 在圆盘里面的都返回1 float disk(float2 r, float2 center, float radius) { return 1.0 - smoothstep(radius - 0.005, radius + 0.005, length(r - center)); } // 在长方形里面的都返回1 float rectangle(float2 r, float2 bottomLeft, float2 topRight) { float ret; float d = 0.005; ret = smoothstep(bottomLeft.x - d, bottomLeft.x + d, r.x); ret *= smoothstep(bottomLeft.y - d, bottomLeft.y + d, r.y); ret *= 1.0 - smoothstep(topRight.y - d, topRight.y + d, r.y); ret *= 1.0 - smoothstep(topRight.x - d, topRight.x + d, r.x); return ret; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float _RotateAngle; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.uv.y = 1 - o.uv.y; return o; } //坐标变换:旋转 fixed4 frag(v2f i) : SV_Target { float2 r = 2.0 * (i.uv - 0.5); float aspectRatio = _ScreenParams.x / _ScreenParams.y; r.x *= aspectRatio; fixed3 bgCol = float3(1.0, 1.0, 1.0); // white fixed3 col1 = float3(0.216, 0.471, 0.698); // blue fixed3 col2 = float3(1.00, 0.329, 0.298); // red fixed3 col3 = float3(0.867, 0.910, 0.247); // yellow fixed3 ret; float2 q; float angle; //angle = 0.2*PI; // 旋转36度 angle = _RotateAngle / PI; q.x = cos(angle)*r.x + sin(angle)*r.y; q.y = -sin(angle)*r.x + cos(angle)*r.y; ret = bgCol; // 画出这两个坐标系 //底色浅一点为固定轴 ret = lerp(ret, col1, coordinateGrid(r)*0.4); //要旋转的坐标轴 ret = lerp(ret, col2, coordinateGrid(q)); // 画出各种图形 //在固定坐标系的图形 ret = lerp(ret, col1, disk(r, float2(1.0, 0.0), 0.2)); ret = lerp(ret, col1, rectangle(r, float2(-0.8, 0.2), float2(-0.5, 0.4))); //在可旋转坐标系的图形 ret = lerp(ret, col2, disk(q, float2(1.0, 0.0), 0.2)); ret = lerp(ret, col2, rectangle(q, float2(-0.8, 0.2),float2(-0.5, 0.4))); fixed3 pixel = ret; return fixed4(pixel, 1.0); } ENDCG } } }
7、使用lerp函数来进行缩放,效果图所示:同样,蓝色部分是固定的,红色部分是缩放的的对象。
缩放的Shader的代码如下:
Shader "Unlit/Chapter6-Scale" { Properties { _ScaleValue("缩放因子",Range(0.1,10))=1 } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always CGINCLUDE #define PI 3.14159 // 使用函数来创建网格,返回的值再乘以颜色及得到网格图形 float coordinateGrid(float2 r) { float3 axisCol = float3(0.0, 0.0, 1.0); float3 gridCol = float3(0.5, 0.5, 0.5); float ret = 0.0; // 画网格 const float tickWidth = 0.1; for (float i = -2.0; i<2.0; i += tickWidth) { ret += 1.0 - smoothstep(0.0, 0.008, abs(r.x - i)); ret += 1.0 - smoothstep(0.0, 0.008, abs(r.y - i)); } // 画坐标轴 ret += 1.0 - smoothstep(0.001, 0.015, abs(r.x)); ret += 1.0 - smoothstep(0.001, 0.015, abs(r.y)); return ret; } // 在圆盘内的返回1 float disk(float2 r, float2 center, float radius) { return 1.0 - smoothstep(radius - 0.005, radius + 0.005, length(r - center)); } // 在长方形内的返回1 float rectangle(float2 r, float2 bottomLeft, float2 topRight) { float ret; float d = 0.005; ret = smoothstep(bottomLeft.x - d, bottomLeft.x + d, r.x); ret *= smoothstep(bottomLeft.y - d, bottomLeft.y + d, r.y); ret *= 1.0 - smoothstep(topRight.y - d, topRight.y + d, r.y); ret *= 1.0 - smoothstep(topRight.x - d, topRight.x + d, r.x); return ret; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float _ScaleValue; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.uv.y = 1 - o.uv.y; return o; } //坐标转换:缩放 fixed4 frag(v2f i) : SV_Target { float2 r = 2.0 * (i.uv - 0.5); float aspectRatio = _ScreenParams.x / _ScreenParams.y; r.x *= aspectRatio; fixed3 bgCol = float3(1.0, 1.0, 1.0); // white fixed3 col1 = float3(0.216, 0.471, 0.698); // blue fixed3 col2 = float3(1.00, 0.329, 0.298); // red fixed3 col3 = float3(0.867, 0.910, 0.247); // yellow fixed3 ret; ret = bgCol; // 固定的坐标系 ret = lerp(ret, col1, coordinateGrid(r) / 2.0); // 缩放 float2 q = _ScaleValue*r; ret = lerp(ret, col2, coordinateGrid(q)); // 画各个图形 //在原始坐标系中画 ret = lerp(ret, col1, disk(r, float2(0.0, 0.0), 0.1)); ret = lerp(ret, col1, rectangle(r, float2(-0.5, 0.0),float2(-0.2, 0.2))); //在可缩放坐标系中画 ret = lerp(ret, col2, disk(q, float2(0.0, 0.0), 0.1)); //大 ret = lerp(ret, col2, rectangle(q, float2(-0.5, 0.0),float2(-0.2, 0.2))); //大 fixed3 pixel = ret; return fixed4(pixel, 1.0); } ENDCG } } }
8、平移以及旋转和平移的组合,效果如图所示,这个Shader部分分了两个部分对旋转和平移进行组合使用,分别是
先旋转在平移和先平移再旋转,Shader的代码如下:
Shader "Unlit/Chapter6-Transform" { Properties { _RotationAngle("旋转角",Range(0,360))=0 _LRotatedTranslatedX("左半部分X方向平移",Range(0,1))=0 _LRotatedTranslatedY("左半部分Y方向平移",Range(0,1))=0 _RRotatedTranslatedX("右半部分X方向平移",Range(0,1)) = 0 _RRotatedTranslatedY("右半部分Y方向平移",Range(0,1)) = 0 } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always CGINCLUDE #define PI 3.1415926 // 通过函数来画网格 float coordinateGrid(float2 r) { float3 axisCol = float3(0.0, 0.0, 1.0); float3 gridCol = float3(0.5, 0.5, 0.5); float ret = 0.0; // 画网线 const float tickWidth = 0.1; for (float i = -2.0; i<2.0; i += tickWidth) { ret += 1.0 - smoothstep(0.0, 0.008, abs(r.x - i)); ret += 1.0 - smoothstep(0.0, 0.008, abs(r.y - i)); } // 画坐标轴 ret += 1.0 - smoothstep(0.001, 0.015, abs(r.x)); ret += 1.0 - smoothstep(0.001, 0.015, abs(r.y)); return ret; } // 圆内的返回1 float disk(float2 r, float2 center, float radius) { return 1.0 - smoothstep(radius - 0.005, radius + 0.005, length(r - center)); } // 在长方形内返回1 float rectangle(float2 r, float2 bottomLeft, float2 topRight) { float ret; float d = 0.005; ret = smoothstep(bottomLeft.x - d, bottomLeft.x + d, r.x); ret *= smoothstep(bottomLeft.y - d, bottomLeft.y + d, r.y); ret *= 1.0 - smoothstep(topRight.y - d, topRight.y + d, r.y); ret *= 1.0 - smoothstep(topRight.x - d, topRight.x + d, r.x); return ret; } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float _RotationAngle; uniform float _LRotatedTranslatedX; uniform float _LRotatedTranslatedY; uniform float _RRotatedTranslatedX; uniform float _RRotatedTranslatedY; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.uv.y = 1 - o.uv.y; return o; } //坐标旋转和平移 fixed4 frag(v2f i) : SV_Target { float2 r = 2.0 * (i.uv - 0.5); float aspectRatio = _ScreenParams.x / _ScreenParams.y; r.x *= aspectRatio; fixed3 bgCol = float3(1.0, 1.0, 1.0); // white fixed3 col1 = float3(0.216, 0.471, 0.698); // blue fixed3 col2 = float3(1.00, 0.329, 0.298); // red fixed3 col3 = float3(0.867, 0.910, 0.247); // yellow fixed3 ret; ret = bgCol; float angle = _RotationAngle/PI; float2x2 rotationMatrix = float2x2(cos(angle), -sin(angle), sin(angle), cos(angle)); //分两部分进行转换 //左半部分 if (i.uv.x < 1.0 / 2.0) { r = r - float2(-aspectRatio / 2.0, 0); float2 rotated = mul(rotationMatrix, r); float2 rotatedTranslated = rotated - float2(_LRotatedTranslatedX, _LRotatedTranslatedY); //原始坐标 ret = lerp(ret, col1, coordinateGrid(r) * 0.3); //旋转坐标 ret = lerp(ret, col2, coordinateGrid(rotated)*0.3); //旋转再平移 ret = lerp(ret, col3, coordinateGrid(rotatedTranslated)*0.3); //原始坐标的图形 ret = lerp(ret, col1, rectangle(r, float2(-0.1, -0.2), float2(0.1, 0.2))); //旋转之后的坐标图形 ret = lerp(ret, col2, rectangle(rotated, float2(-0.1, -0.2), float2(0.1, 0.2))); //旋转再平移之后 ret = lerp(ret, col3, rectangle(rotatedTranslated, float2(-0.1, -0.2), float2(0.1, 0.2))); } //右半部分 else if (i.uv.x < 2.0 / 2.0) { r = r - float2(aspectRatio / 2.0, 0); //平移 float2 translated = r - float2(_RRotatedTranslatedX, _RRotatedTranslatedY); //平移再旋转 float2 translatedRotated = mul(rotationMatrix, translated); //原始坐标 ret = lerp(ret, col1, coordinateGrid(r) * 0.3); //平移 ret = lerp(ret, col2, coordinateGrid(translated)*0.3); //平移之后再旋转 ret = lerp(ret, col3, coordinateGrid(translatedRotated)*0.3); ret = lerp(ret, col1, rectangle(r, float2(-0.1, -0.2), float2(0.1, 0.2))); ret = lerp(ret, col2, rectangle(translated, float2(-0.1, -0.2), float2(0.1, 0.2))); ret = lerp(ret, col3, rectangle(translatedRotated, float2(-0.1, -0.2), float2(0.1, 0.2))); } fixed3 pixel = ret; return fixed4(pixel, 1.0); } ENDCG } } }
三、放两个大招—各种动画效果
1、效果如图所示:从左到右分别是五个不同的动画效果,使用到的函数都是前面讲解的函数加上之后补充的旋转、缩放和平移效果。
Shader代码如下:
Shader "Unlit/Chapter6-Animations" { Properties { _SpeedY("第一部分的速度",Range(0,3)) = 1 _Amplitude("第二部分的振幅",Range(0,1)) = 0.8 _RSpeedX("圆周运动X方向的速度",Range(0,10)) = 5 _RSpeedY("圆周运动Y方向的速度",Range(0,10)) = 5 _RAmplitudeY("圆周运动Y方向的幅度",Range(0,1)) = 0.1 _RAmplitudeX("圆周运动X方向的幅度",Range(0,1)) = 0.1 _ChainAnimSpeed("链条运动的速度",Range(0,10)) = 5 _ChainAnimRotaSpeed("链条运动的旋转速度",Range(0,10)) = 3 _JumpSpeed("跳跃运动的速度",Range(0,10))=2 } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always CGINCLUDE #define PI 3.1415926 // 通过函数来画网格 float coordinateGrid(float2 r) { float3 axisCol = float3(0.0, 0.0, 1.0); float3 gridCol = float3(0.5, 0.5, 0.5); float ret = 0.0; // 画网线 const float tickWidth = 0.1; for (float i = -2.0; i<2.0; i += tickWidth) { ret += 1.0 - smoothstep(0.0, 0.008, abs(r.x - i)); ret += 1.0 - smoothstep(0.0, 0.008, abs(r.y - i)); } // 画坐标轴 ret += 1.0 - smoothstep(0.001, 0.015, abs(r.x)); ret += 1.0 - smoothstep(0.001, 0.015, abs(r.y)); return ret; } // 圆内的返回1 float disk(float2 r, float2 center, float radius) { return 1.0 - smoothstep(radius - 0.005, radius + 0.005, length(r - center)); } // 在长方形内返回1 float rectangle(float2 r, float2 bottomLeft, float2 topRight) { float ret; float d = 0.005; ret = smoothstep(bottomLeft.x - d, bottomLeft.x + d, r.x); ret *= smoothstep(bottomLeft.y - d, bottomLeft.y + d, r.y); ret *= 1.0 - smoothstep(topRight.y - d, topRight.y + d, r.y); ret *= 1.0 - smoothstep(topRight.x - d, topRight.x + d, r.x); return ret; } float mod(float a, float b) { return a - b*floor(a / b); } ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float _Amplitude; uniform float _SpeedY; uniform float _RSpeedX; uniform float _RSpeedY; uniform float _RAmplitudeY; uniform float _RAmplitudeX; uniform float _ChainAnimSpeed; uniform float _ChainAnimRotaSpeed; uniform float _JumpSpeed; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.uv.y = 1 - o.uv.y; return o; } //动画,使用到了Unity内置的变量_Time 四维向量(t/20, t, t*2, t*3),_Time.y=t; fixed4 frag(v2f i) : SV_Target { float2 r = 2.0 * (i.uv - 0.5); float aspectRatio = _ScreenParams.x / _ScreenParams.y; r.x *= aspectRatio; fixed3 bgCol = float3(1.0, 1.0, 1.0); // white fixed3 col1 = float3(0.216, 0.471, 0.698); // blue fixed3 col2 = float3(1.00, 0.329, 0.298); // red fixed3 col3 = float3(0.867, 0.910, 0.247); // yellow fixed3 ret; ret = bgCol; //第一部分,循环向上运动 if (i.uv.x < 1.0 / 5.0) { float2 q = r + float2(aspectRatio*4.0 / 5.0, 0); ret = fixed3(0.3, 0.3, 0.3); //unity内置的时间向量 float y = _SpeedY*_Time.y; //使得y在-1到1之间 y = mod(y,2.0) - 1.0; ret = lerp(ret, col1, disk(q, float2(0.0, y), 0.1)); } //第二部分,循环来回并缩放运动 else if (i.uv.x < 2.0 / 5.0) { float2 q = r + float2(aspectRatio*2.0 / 5.0, 0); ret = fixed3(0.4, 0.4, 0.4); //添加振幅 float y = _Amplitude * sin(0.5*_Time.y* 2.0 * PI); float radius = 0.15 + 0.05 * sin(_Time.y * 8.0); ret = lerp(ret, col1, disk(q, float2(0.0, y), radius)); } //第三部分,圆周运动并变换颜色 else if (i.uv.x < 3.0 / 5.0) { float2 q = r + float2(aspectRatio * 0 / 5.0, 0); ret = float3(0.5, 0.5, 0.5); float x = _RAmplitudeX*cos(_Time.y*_RSpeedX); float y = _RAmplitudeY*sin(_Time.y*_RSpeedY); float radius = 0.2 + 0.1*sin(_Time.y*2.0); fixed3 color = lerp(col1, col2, sin(_Time.y)*0.5 + 0.5); ret = lerp(ret, color, rectangle(q, float2(x - 0.1, y - 0.1), float2(x + 0.1, y + 0.1))); } //第四部分,链条运动 else if (i.uv.x < 4.0 / 5.0) { float2 q = r + float2(-aspectRatio*2.0 / 5.0, 0); ret = float3(0.4, 0.4, 0.4); for (float i = -1.0; i<1.0; i += 0.2) { float x = 0.2 * cos(_Time.y*_ChainAnimSpeed + i*PI); float y = i; float2 s = q - float2(x, y); float angle = _Time.y * _ChainAnimRotaSpeed + i; float2x2 rot = float2x2(cos(angle), -sin(angle), sin(angle), cos(angle)); s = mul(rot, s); ret = lerp(ret, col1, rectangle(s, float2(-0.06, -0.06), float2(0.06, 0.06))); } } //第五部分,跳跃运动 else if (i.uv.x < 5.0 / 5.0) { float2 q = r + float2(-aspectRatio*4.0 / 5.0, 0); ret = float3(0.3, 0.3, 0.3); float speed = _JumpSpeed; float t = _Time.y * speed; float stopEveryAngle = PI / 2.0; float stopRatio = 0.5; //floor(x):返回小于等于t的最大整数 frac(x):返回x的小数部分 float t1 = (floor(t) + smoothstep(0.0, 1.0 - stopRatio, frac(t)))*stopEveryAngle; float x = -0.2*cos(t1); float y = 0.3 * sin(t1); float dx = 0.1 + 0.03 * sin(t*10.0); float dy = 0.1 + 0.03 * sin(t*10.0 + PI); ret = lerp(ret, col1, rectangle(q, float2(x - dx, y - dy), float2(x + dx, y + dy))); } fixed3 pixel = ret; return fixed4(pixel, 1.0); } ENDCG } } }
代码中使用到了“_Time”变量,这个是Unity内置的四维向量,(t/20,t,t*2,t*3)因此“_Time.y=t”即获得系统的渲染的单位时间。
2、等离子流动效果,效果图如图所示:
Shader代码如下:
Shader "Unlit/Chapter6-Plasma" { Properties { _WaveSpeed("波浪速度",Range(0,10))=8 _ColorValue1("混合颜色1",Range(0,360))=180 _ColorValue2("混合颜色2",Range(0,360)) = 180 } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always CGINCLUDE #define PI 3.1415926 ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float _WaveSpeed; uniform float _ColorValue1; uniform float _ColorValue2; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; o.uv.y = 1 - o.uv.y; return o; } //等离子效果 fixed4 frag(v2f i) : SV_Target { float2 r = 2.0 * (i.uv - 0.5); float aspectRatio = _ScreenParams.x / _ScreenParams.y; r.x *= aspectRatio; float t = _Time.y*_WaveSpeed; r = r*8.0; float v1 = sin(r.x + t); float v2 = sin(r.y + t); float v3 = sin(r.x + r.y + t); float v4 = sin(sqrt(r.x*r.x + r.y*r.y) + 1.7*t); float v = v1 + v2 + v3 + v4; fixed3 ret; //第一部分垂直波浪 if (i.uv.x < 1.0 / 10.0) { ret = float3(v1, v1, v1) } //第二部分水平波浪 else if (i.uv.x < 2.0 / 10.0) { ret = float3(v2, v2, v2); } //第三部分对角线波浪 else if (i.uv.x < 3.0 / 10.0) { ret = float3(v3, v3, v3); } //第四部分圆环波浪 else if (i.uv.x < 4.0 / 10.0) { ret = float3(v4, v4, v4); } //第五部分所有波浪的综合 else if (i.uv.x < 5.0 / 10.0) { ret = float3(v, v, v); } //第六部分通过正、余弦函数添加周期性渐变 else if (i.uv.x < 6.0 / 10.0) { ret = float3(sin(2.0 * v), sin(2.0 * v), sin(2.0 * v)); } //第七部分混合各种颜色 else if (i.uv.x < 10.0 / 10.0) { ret = float3(sin(v), sin(v + _ColorValue1/PI), sin(v + _ColorValue2/PI)); } ret = 0.5 + 0.5 * ret; fixed3 pixel = ret; return fixed4(pixel, 1.0); } ENDCG } } }
四、总结
1、Cg函数虽简单,使用得当也是逼格暴涨,对于想学好Shader童鞋来讲,还是要多从Cg语言基础着手,通过实例练习,不仅可以巩固基础知识,而且也可以在做的过程中添加学习的信心和兴趣。
2、正弦、余弦函数配合时间变量的使用能做到非常不错的动画效果,最好的效果还是要从根本上来讲还是数学,当今世界是学好数学和英语走遍天下都不怕了。
来自:凯尔八阿哥专栏https://blog.csdn.net/zhangxiao13627093203/article/details/53163098