魔法水晶Shader全解析:从呼吸到发光

发布时间:2026/8/6 7:21:48
魔法水晶Shader全解析:从呼吸到发光 引子从简单到进阶向前一大步嗨小朋友上次我们看了一个简单的Shader**——只会做两件事贴图染色你可能觉得这也太简单了吧——游戏里那些炫酷的火焰、流动的水面、发光的宝剑——是怎么做出来的答案是——它们的Shader比我们上次看的复杂得多**但别怕——“复杂不代表看不懂”它只是用了更多小技巧**每个小技巧——都能拆开慢慢看拆开之后——你会发现哦原来是这样啊今天——我们要看一份真正的进阶Shader——一个会呼吸、发光、摆动的魔法水晶Shader**看完这个——你就能理解游戏里那些炫酷特效的原理**了一、先看看今天的主角今天的Shader要做出这样的效果 会摆动——像水晶在轻轻晃动✨ 会发光——边缘有一圈光晕 会呼吸——亮度一亮一暗地变化 有花纹——表面有滚动的能量纹路听起来很酷对不对——这就是一个魔法水晶Shader完整代码在这里先别怕我们会一点点拆开看Shader MyShader/MagicCrystal { Properties { _MainTex (主贴图, 2D) white {} _NoiseTex (能量纹路, 2D) white {} _BaseColor (基础颜色, Color) (0.2, 0.5, 1.0, 1) _RimColor (边缘光颜色, Color) (0.5, 0.8, 1.0, 1) _RimPower (边缘光强度, Range(0.1, 10)) 3.0 _WaveSpeed (摆动速度, Range(0, 5)) 1.0 _WaveStrength (摆动幅度, Range(0, 0.5)) 0.1 _ScrollSpeed (纹路滚动速度, Range(0, 5)) 1.0 _PulseSpeed (呼吸速度, Range(0, 5)) 2.0 } SubShader { Tags { RenderType Transparent Queue Transparent } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Cull Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc sampler2D _MainTex; sampler2D _NoiseTex; float4 _BaseColor; float4 _RimColor; float _RimPower; float _WaveSpeed; float _WaveStrength; float _ScrollSpeed; float _PulseSpeed; struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 viewDir : TEXCOORD2; }; v2f vert(appdata v) { v2f o; // 顶点摆动 float wave sin(_Time.y * _WaveSpeed v.vertex.y * 3) * _WaveStrength; v.vertex.x wave; o.pos UnityObjectToClipPos(v.vertex); o.uv v.uv; o.worldNormal UnityObjectToWorldNormal(v.normal); float3 worldPos mul(unity_ObjectToWorld, v.vertex).xyz; o.viewDir normalize(_WorldSpaceCameraPos - worldPos); return o; } fixed4 frag(v2f i) : SV_Target { // 主贴图 fixed4 mainCol tex2D(_MainTex, i.uv); // 滚动的能量纹路 float2 scrollUV i.uv float2(_Time.y * _ScrollSpeed, 0); fixed4 noiseCol tex2D(_NoiseTex, scrollUV); // 边缘光 float rim 1 - saturate(dot(i.worldNormal, i.viewDir)); rim pow(rim, _RimPower); // 呼吸 float pulse (sin(_Time.y * _PulseSpeed) 1) * 0.5; pulse lerp(0.5, 1.0, pulse); // 合成最终颜色 fixed3 finalColor mainCol.rgb * _BaseColor.rgb; finalColor noiseCol.rgb * 0.3; finalColor _RimColor.rgb * rim; finalColor * pulse; return fixed4(finalColor, mainCol.a * _BaseColor.a); } ENDCG } } FallBack Diffuse }**看到这一大堆——你可能又开始晕了——别慌深呼吸——我们一块一块拆开看——每一块都不难二、Properties这次的材料清单更丰富了Properties { _MainTex (主贴图, 2D) white {} _NoiseTex (能量纹路, 2D) white {} _BaseColor (基础颜色, Color) (0.2, 0.5, 1.0, 1) _RimColor (边缘光颜色, Color) (0.5, 0.8, 1.0, 1) _RimPower (边缘光强度, Range(0.1, 10)) 3.0 _WaveSpeed (摆动速度, Range(0, 5)) 1.0 _WaveStrength (摆动幅度, Range(0, 0.5)) 0.1 _ScrollSpeed (纹路滚动速度, Range(0, 5)) 1.0 _PulseSpeed (呼吸速度, Range(0, 5)) 2.0 }**上次只有2个材料——这次有9个打个比方上次做菜——只放盐、糖这次做菜——放盐、糖、辣椒、酱油、醋、姜、蒜、料酒、花椒——做一道大餐**看看每个材料_MainTex——主贴图水晶本身的样子_NoiseTex——能量纹路会滚动的花纹_BaseColor——基础颜色水晶是什么色的_RimColor——边缘光颜色边上光晕什么色_RimPower——边缘光有多亮_WaveSpeed——摆得多快_WaveStrength——摆得多厉害_ScrollSpeed——纹路滚多快_PulseSpeed——呼吸多快看到Range(0.1, 10)了吗这个意思是可以在0.1到10之间调**在Unity里——会显示成一个滑块——美术姐姐可以拖着调这就是Shader的贴心之处——把所有可以调的东西都摆到Inspector里**——美术不用改代码就能调效果三、SubShader的新东西让物体半透明SubShader { Tags { RenderType Transparent Queue Transparent } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Cull Off // ... }**这里有几个新东西——都和透明有关新东西1RenderType Transparent上次是Opaque不透明——这次是Transparent透明告诉Unity**我是透明物体——请按透明物体的方式对待我。就像贴标签上次的标签是实心物体**这次的标签是半透明水晶**新东西2Queue Transparent这是渲染队列——决定先画谁后画谁打个比方画画的时候——要先画背景再画前面的东西不然前面的物体会被背景盖住Queue——就是画的顺序Transparent队列——排在后面——**保证先把不透明物体画完——再画透明物体。为什么要这样——因为透明物体要混合背景色——必须先有背景才行新东西3Blend SrcAlpha OneMinusSrcAlpha这就是透明混合的魔法**看起来很吓人——别慌——它的意思是“把我的颜色 和 后面的背景 混合起来”具体怎么混合SrcAlpha——我的透明度是多少比如0.5就是半透明OneMinusSrcAlpha——1减去我的透明度0.5就是背景占一半混合公式最终颜色 我的颜色 × 0.5 背景颜色 × 0.5结果就是我一半、背景一半——看起来半透明这就是玻璃、水、透明水晶的效果新东西4ZWrite Off“ZWrite是深度写入”——**关掉它——让别的物体能透过我被看见。打个比方ZWrite On——“我很霸道我挡住谁谁就看不见”ZWrite Off——“我很客气你们随便被看见”透明物体一般都要ZWrite Off——不然透明后面的物体会被莫名挡住。新东西5Cull Off“Cull是剔除”——“要不要剔除背面”**。普通物体——只画正面Cull Back——背面看不到反正。透明水晶——**能看到里面——内外都要画Cull Off这三个设置Blend ZWrite Off Cull Off——就是半透明物体的标配**四、顶点魔法师升级让物体摆动v2f vert(appdata v) { v2f o; // 顶点摆动 float wave sin(_Time.y * _WaveSpeed v.vertex.y * 3) * _WaveStrength; v.vertex.x wave; o.pos UnityObjectToClipPos(v.vertex); o.uv v.uv; o.worldNormal UnityObjectToWorldNormal(v.normal); float3 worldPos mul(unity_ObjectToWorld, v.vertex).xyz; o.viewDir normalize(_WorldSpaceCameraPos - worldPos); return o; }**这次的顶点魔法师——比上次多了几个新技能新技能1摆动魔法float wave sin(_Time.y * _WaveSpeed v.vertex.y * 3) * _WaveStrength; v.vertex.x wave;**这两行——就让水晶摆动起来了让我们拆开看_Time.y是什么——Unity送给你的时间变量**游戏一直在跑——时间一直在增加_Time.y就是现在过了多少秒**sin(...)是什么——数学里的正弦函数它的值——在-1到1之间来回摇摆**像一个来回荡的秋千**时间不断变化——sin的值不断在-1到1之间跳打个比方想象一个钟摆**——**左右左右不停摆**左边最远——值是-1**中间——值是0**右边最远——值是1**sin就是这个钟摆v.vertex.y * 3是什么——“位置越高摆得越不一样”让每个位置的顶点——摆动稍微不一样不然整个物体就像僵尸一样一起动**——很假* _WaveStrength——“摆多厉害由你决定”**_WaveStrength 0.1——摆一点点**_WaveStrength 0.5——摆很大v.vertex.x wave;——把这个摆动值加到x坐标上每一帧——顶点位置稍微不一样看起来就像——水晶在左右摆动这就是顶点动画的魔法——通过修改顶点位置让物体动起来很多游戏里的旗帜、草、水面、果冻**——都是用这个原理做的新技能2算出世界法线o.worldNormal UnityObjectToWorldNormal(v.normal);法线是什么——它是每个顶点朝哪个方向**打个比方想象你在墙上贴一张贴纸贴纸的正面朝哪个方向朝离开墙的方向——这就是法线球的每一个点——都有一个法线——朝远离球心的方向。为什么要算这个——用来做边缘光**下面会用到UnityObjectToWorldNormal(...)——**Unity帮我们写好的函数——把模型自己坐标系的法线变成游戏世界里的法线。新技能3算出看的方向float3 worldPos mul(unity_ObjectToWorld, v.vertex).xyz; o.viewDir normalize(_WorldSpaceCameraPos - worldPos);“viewDir是视线方向”——“摄像机在看这个点的方向”打个比方**你的眼睛——**摄像机**一个物体上的点**从物体指向你眼睛——**就是视线方向这两行的意思**worldPos——**这个顶点在游戏世界里的位置**_WorldSpaceCameraPos——**摄像机的位置Unity送的**两者相减——**就是看向摄像机的方向为什么要算这个——还是为了边缘光**五、片段魔法师升级颜色的大合成fixed4 frag(v2f i) : SV_Target { // 主贴图 fixed4 mainCol tex2D(_MainTex, i.uv); // 滚动的能量纹路 float2 scrollUV i.uv float2(_Time.y * _ScrollSpeed, 0); fixed4 noiseCol tex2D(_NoiseTex, scrollUV); // 边缘光 float rim 1 - saturate(dot(i.worldNormal, i.viewDir)); rim pow(rim, _RimPower); // 呼吸 float pulse (sin(_Time.y * _PulseSpeed) 1) * 0.5; pulse lerp(0.5, 1.0, pulse); // 合成最终颜色 fixed3 finalColor mainCol.rgb * _BaseColor.rgb; finalColor noiseCol.rgb * 0.3; finalColor _RimColor.rgb * rim; finalColor * pulse; return fixed4(finalColor, mainCol.a * _BaseColor.a); }这次片段魔法师——做了四件事——每一件都很酷事情1取主贴图颜色老朋友fixed4 mainCol tex2D(_MainTex, i.uv);**这个我们上次学过——从贴图上取颜色。事情2滚动的能量纹路新魔法float2 scrollUV i.uv float2(_Time.y * _ScrollSpeed, 0); fixed4 noiseCol tex2D(_NoiseTex, scrollUV);让贴图动起来的魔法核心i.uv float2(_Time.y * _ScrollSpeed, 0)原来的uv——是固定的位置加上时间×速度——位置一直在移动打个比方你在窗户外——**看外面的风景**窗户不动——**风景不动**窗户往右移——**风景就往左滚过你的视线Shader里uv不变——贴图不动uv随时间加一个值——贴图滚动结果能量纹路一直在物体表面流动——像水晶里能量在涌动这个技巧——在游戏里用得超级多传送带的效果能量护盾的花纹瀑布的水流火焰的动态事情3边缘光最酷的魔法float rim 1 - saturate(dot(i.worldNormal, i.viewDir)); rim pow(rim, _RimPower);**这两行——做出了边缘发光的效果——就是那种物体边上有一圈光晕的效果让我们拆开看dot(i.worldNormal, i.viewDir)是什么**dot——**数学里的点积不用管公式只要知道它的意义它算的是两个方向有多像**具体意义法线朝着摄像机——dot值≈1法线垂直摄像机——dot值≈0这就是边缘法线背对摄像机——dot值≈-11 - dot——反过来中间的地方法线朝摄像机——rim值≈0不发光边缘的地方法线垂直摄像机——rim值≈1发光这就是边缘光的原理打个比方**想象一个球**正中间——**你看到的是球的正面——很清楚**球的边上——**几乎是侧着的——**你只能勉强看到**“越侧的地方”——**就越是边缘——就越亮——这就是边缘光pow(rim, _RimPower)——让边缘光更集中**_RimPower 1——**光很散**_RimPower 5——**光只在很窄的边缘**_RimPower 10——**光只在最边最边**很多游戏里发光的水晶、护盾、幽灵——都是用这个原理事情4呼吸效果可爱的魔法float pulse (sin(_Time.y * _PulseSpeed) 1) * 0.5; pulse lerp(0.5, 1.0, pulse);又见到我们的老朋友sin了——这次让物体一亮一暗地呼吸**。sin(_Time.y * _PulseSpeed)值在-1到1之间跳1变成0到2之间* 0.5变成0到1之间lerp(0.5, 1.0, pulse)lerp是在两个数之间平滑过渡让pulse从0到1——变成0.5到1保证物体永远不会完全变黑**——最暗也是一半亮结果亮度在一半亮到完全亮之间来回变化——像呼吸**大合成把所有效果混在一起fixed3 finalColor mainCol.rgb * _BaseColor.rgb; finalColor noiseCol.rgb * 0.3; finalColor _RimColor.rgb * rim; finalColor * pulse;**四行代码——做四件事第一行主贴图 × 基础颜色——“给贴图染色”第二行加上0.3的能量纹路——“加入流动的花纹”第三行加上边缘光——“边缘发光”第四行乘以呼吸值——“一亮一暗”打个做菜的比方先炒主菜**主贴图×基础颜色加点调料**能量纹路撒点装饰**边缘光最后大火收汁**呼吸变亮暗四步下来——一份炫酷的魔法水晶就做好了**最后一行输出return fixed4(finalColor, mainCol.a * _BaseColor.a);**fixed4(...)——把颜色打包成4个数**前面finalColor——**红绿蓝三个值**后面mainCol.a * _BaseColor.a——**透明度“透明度也要乘”——因为要让物体半透明**六、完整流程回顾一个像素的魔法之旅**让我们跟着一个水晶边缘的像素——看它是怎么被画出来的Step 1顶点魔法师算它的位置**加上sin摆动——它稍微偏了一点算出屏幕位置算出法线、视线方向Step 2片段魔法师上场从主贴图取颜色——得到淡蓝色和_BaseColor相乘——变成深蓝色Step 3取能量纹路**uv加上时间——采样滚动的位置得到一个能量花纹的颜色**加到基础色上——变成有花纹的深蓝色Step 4算边缘光**这是边缘像素——dot值≈0**1-dot ≈ 1——rim值很大加上边缘光颜色——变成发光的深蓝色Step 5呼吸**假设当前sin0.5——**pulse≈0.75**颜色乘以0.75——稍微暗一点**下一帧——**pulse可能1——又亮一点Step 6输出加上透明度和背景混合屏幕上出现——一个摆动、发光、呼吸的魔法水晶的边缘像素七、这个Shader的精妙之处看完这个例子——能提炼几个Shader的核心技巧技巧1用_Time让物体动起来**只要有_Time.y——任何东西都能动**顶点位置动——**摆动、飘扬**uv动——**滚动、流动**颜色动——**呼吸、闪烁技巧2用sin做周期性变化sin是来回摇摆的魔法摆动呼吸闪烁波浪**几乎所有周期性动画——都能用sin做技巧3用dot(法线, 视线)做边缘光这是边缘检测的经典技巧——**几乎所有护盾幽灵能量物体卡通描边——都会用到这个原理技巧4颜色层层叠加复杂的效果——都是简单效果的叠加基础色 × 主贴图加上滚动纹路加上边缘光乘以呼吸**一层一层——堆出复杂效果结语一份魔法配方的进阶之旅从上次的简单Shader只会贴图染色到这次的魔法水晶会摆动、会呼吸、会发光、会流动——我们做了一次从新手到进阶的完整旅程多学了透明混合**——让物体半透明多学了顶点动画**——让物体摆动多学了uv滚动**——让贴图流动多学了边缘光**——让物体发光多学了呼吸变化**——让物体呼吸多学了多层合成**——堆出复杂效果它像是一份进阶菜谱**上次的菜谱——“白饭配盐”**这次的菜谱——“海鲜大餐”**原料多了几倍步骤多了几倍但每一步都能理解**它有Properties里丰富的可调参数——让美术能调出各种风格的水晶。它有渲染状态的透明配置——让物体像水晶一样半透明。它有顶点着色器的摆动魔法——让物体动起来。它有片段着色器的四种效果合成——滚动边缘光呼吸染色。它有sin、dot、pow这些数学工具——让复杂效果变简单。下次当你看到游戏里的炫酷特效——请记得**那看起来神奇的画面——背后是一堆Properties让美术能调一些顶点动画让物体动一些uv滚动让贴图流一些sin/dot运算让效果酷一些颜色叠加让画面丰富每一个炫酷效果——归根到底就是我们今天讲的这些基础技巧的组合。每一份复杂Shader——都可以拆成几十个小技巧慢慢理解。每一位曾经看代码就晕的学习者——只要肯拆开慢慢看——都能像今天这样看懂了。每一个_Time.y——都是时间的魔法棒——让物体动起来。每一个sin(...)——都是摇摆的秋千——让效果周期变化。每一个dot(法线, 视线)——都是边缘的检测器——让物体发光。每一次叠加——都在堆积炫酷——从简单到复杂。这——就是Shader进阶的秘密不是神秘的黑魔法而是几个基础技巧的组合**不是天才才能写而是愿意慢慢学的人都能掌握**不是看代码就头疼的东西而是拆开就能理解的清晰配方**在这份魔法配方的背后是Unity为你准备的丰富工具箱**_Time、sin、pow、dot……是数学与美学的巧妙结合**是简单技巧堆积成复杂效果的智慧路径**是每一位创作者都能实现的炫酷梦想**这——就是Shader的进阶之旅——不是越来越难——而是越来越好玩——每多学一个技巧就多一份创造的能力——是每一位怀着好奇心的学习者都能一步步走完的魔法之路。每一位读到这里的小朋友——都已经掌握了游戏Shader的很多核心秘密了每一次你多看一个Shader——都会发现哦这也是我学过的技巧**每一位持续探索的学习者——总有一天能写出属于自己的炫酷Shader**这——就是Unity送给每一位小小学习者的、最鼓舞人心的进阶礼物。 ✨‍♂️加油——你已经在Shader的路上走得比想象中远了