当前位置: 首页 > news >正文

从“消融”到“流动岩浆”:用Unity Shader的Tilling和Offset玩转动态纹理(URP/HDRP都适用)

从“消融”到“流动岩浆”用Unity Shader的Tilling和Offset玩转动态纹理URP/HDRP都适用在游戏开发中纹理的动态效果往往能带来令人惊艳的视觉体验。想象一下炽热的岩浆在火山表面缓缓流动清澈的水面随着微风泛起涟漪或是能量在武器上汇聚时产生的炫目光效——这些效果都可以通过巧妙地操控纹理的Tilling和Offset参数来实现。本文将带你深入探索如何通过脚本动态控制_ST向量的xyTilling和zwOffset值创造出令人惊叹的动态纹理效果。1. 理解Tilling和Offset的核心原理在Unity Shader中每个纹理采样都依赖于UV坐标。Tilling和Offset本质上是对UV坐标的数学变换Tilling缩放度控制纹理在单位空间内的重复次数。数值越大纹理显得越小、越密集。Offset偏移度控制纹理在UV空间中的平移量。通过改变这个值可以让纹理产生滑动效果。数学表达式可以简化为newUV originalUV * Tilling Offset1.1 性能优化关键点在实现动态效果时性能是需要重点考虑的因素// 错误做法在片元着色器中计算变换后的UV fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv * _MainTex_ST.xy _MainTex_ST.zw); return col; } // 正确做法在顶点着色器中预先计算 v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); // 使用Unity内置宏 return o; }提示Unity提供了TRANSFORM_TEX宏来简化Tilling和Offset的计算这个宏会自动处理_ST向量的xy和zw分量。2. 动态Offset创造流动效果通过随时间改变Offset值我们可以模拟各种流动效果。下面是一个完整的岩浆流动实现方案。2.1 C#脚本控制Offsetusing UnityEngine; [ExecuteAlways] public class LavaFlowController : MonoBehaviour { [SerializeField] private Material targetMaterial; [SerializeField] private Vector2 flowSpeed new Vector2(0.1f, 0); [SerializeField] private string textureProperty _MainTex; private Vector2 currentOffset; private int textureSTId; void Start() { textureSTId Shader.PropertyToID(textureProperty _ST); currentOffset targetMaterial.GetVector(textureSTId).zw; } void Update() { currentOffset flowSpeed * Time.deltaTime; // 确保Offset在[0,1]范围内循环 currentOffset.x Mathf.Repeat(currentOffset.x, 1); currentOffset.y Mathf.Repeat(currentOffset.y, 1); Vector4 st targetMaterial.GetVector(textureSTId); st.z currentOffset.x; st.w currentOffset.y; targetMaterial.SetVector(textureSTId, st); } }2.2 进阶技巧多纹理混合为了创造更真实的流动效果可以结合两张不同速度的噪声纹理Shader Custom/LavaFlow { Properties { _MainTex (Base Texture, 2D) white {} _NoiseTex1 (Noise Texture 1, 2D) gray {} _NoiseTex2 (Noise Texture 2, 2D) gray {} _FlowSpeed1 (Flow Speed 1, Vector) (0.1, 0, 0, 0) _FlowSpeed2 (Flow Speed 2, Vector) (-0.05, 0.03, 0, 0) } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag sampler2D _MainTex, _NoiseTex1, _NoiseTex2; float4 _MainTex_ST, _NoiseTex1_ST, _NoiseTex2_ST; float2 _FlowSpeed1, _FlowSpeed2; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float2 noiseUV1 : TEXCOORD1; float2 noiseUV2 : TEXCOORD2; }; v2f vert (appdata_full v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.texcoord, _MainTex); o.noiseUV1 TRANSFORM_TEX(v.texcoord, _NoiseTex1); o.noiseUV2 TRANSFORM_TEX(v.texcoord, _NoiseTex2); return o; } fixed4 frag (v2f i) : SV_Target { // 动态调整噪声纹理UV float2 flowUV1 i.noiseUV1 _FlowSpeed1 * _Time.y; float2 flowUV2 i.noiseUV2 _FlowSpeed2 * _Time.y; fixed4 noise1 tex2D(_NoiseTex1, flowUV1); fixed4 noise2 tex2D(_NoiseTex2, flowUV2); // 混合噪声和基础纹理 fixed4 base tex2D(_MainTex, i.uv); float noiseMix (noise1.r noise2.g) * 0.5; // 添加热变形效果 float2 distortedUV i.uv (noiseMix - 0.5) * 0.02; fixed4 final tex2D(_MainTex, distortedUV); // 增强颜色 final.rgb * 1.0 noiseMix * 0.5; return final; } ENDCG } } }3. 动态Tilling能量汇聚效果动态调整Tilling参数可以创造出物体表面能量汇聚或扩散的视觉效果非常适合用于技能特效或环境互动。3.1 基础实现方案public class EnergyConvergence : MonoBehaviour { [SerializeField] private Material targetMaterial; [SerializeField] private float minTilling 0.5f; [SerializeField] private float maxTilling 2.0f; [SerializeField] private float pulseSpeed 1.0f; [SerializeField] private string textureProperty _MainTex; private int textureSTId; private float currentTilling; void Start() { textureSTId Shader.PropertyToID(textureProperty _ST); currentTilling minTilling; } void Update() { // 使用PingPong函数在min和max之间来回变化 currentTilling Mathf.PingPong(Time.time * pulseSpeed, maxTilling - minTilling) minTilling; Vector4 st targetMaterial.GetVector(textureSTId); st.x currentTilling; st.y currentTilling; targetMaterial.SetVector(textureSTId, st); } }3.2 进阶效果方向性能量流动Shader Custom/EnergyFlow { Properties { _MainTex (Energy Texture, 2D) white {} _NoiseTex (Noise Texture, 2D) gray {} _FlowDirection (Flow Direction, Vector) (1, 0, 0, 0) _FlowSpeed (Flow Speed, Float) 0.5 _PulseFrequency (Pulse Frequency, Float) 1.0 } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag sampler2D _MainTex, _NoiseTex; float4 _MainTex_ST, _NoiseTex_ST; float2 _FlowDirection; float _FlowSpeed, _PulseFrequency; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float2 noiseUV : TEXCOORD1; }; v2f vert (appdata_full v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.texcoord, _MainTex); o.noiseUV TRANSFORM_TEX(v.texcoord, _NoiseTex); return o; } fixed4 frag (v2f i) : SV_Target { // 动态调整Tilling float pulse sin(_Time.y * _PulseFrequency) * 0.5 0.5; float2 dynamicTilling lerp(float2(0.5, 0.5), float2(2.0, 2.0), pulse); // 动态Offset float2 flowOffset _FlowDirection * _FlowSpeed * _Time.y; // 应用变换 float2 finalUV i.uv * dynamicTilling flowOffset; // 采样噪声纹理增加细节 fixed4 noise tex2D(_NoiseTex, i.noiseUV); finalUV (noise.rg - 0.5) * 0.1; fixed4 col tex2D(_MainTex, finalUV); // 根据Tilling值调整亮度 col.rgb * 1.0 (1.0 - pulse) * 2.0; return col; } ENDCG } } }4. URP/HDRP中的特殊注意事项在不同渲染管线中使用Tilling和Offset时需要注意一些关键差异点。4.1 URP中的实现URP Shader需要包含以下核心代码结构Shader Universal Render Pipeline/Custom/TextureAnimation { Properties { _BaseMap(Base Texture, 2D) white {} _BaseColor(Base Color, Color) (1,1,1,1) } SubShader { Tags { RenderTypeOpaque RenderPipelineUniversalPipeline } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionHCS : SV_POSITION; float2 uv : TEXCOORD0; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); float4 _BaseMap_ST; half4 _BaseColor; Varyings vert(Attributes IN) { Varyings OUT; OUT.positionHCS TransformObjectToHClip(IN.positionOS.xyz); OUT.uv TRANSFORM_TEX(IN.uv, _BaseMap); return OUT; } half4 frag(Varyings IN) : SV_Target { half4 color SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv); color * _BaseColor; return color; } ENDHLSL } } }4.2 HDRP中的实现差异HDRP Shader的结构更为复杂需要注意材质面板的设置Shader HDRP/Custom/TextureAnimation { Properties { [MainTexture] _BaseColorMap(Base Color Map, 2D) white {} [MainColor] _BaseColor(Base Color, Color) (1,1,1,1) } SubShader { Tags { RenderPipelineHDRenderPipeline } Pass { Name ForwardOnly Tags { LightModeForwardOnly } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl struct Attributes { float3 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; }; TEXTURE2D(_BaseColorMap); SAMPLER(sampler_BaseColorMap); float4 _BaseColorMap_ST; float4 _BaseColor; Varyings vert(Attributes IN) { Varyings OUT; OUT.positionCS TransformWorldToHClip(TransformObjectToWorld(IN.positionOS)); OUT.uv TRANSFORM_TEX(IN.uv, _BaseColorMap); return OUT; } float4 frag(Varyings IN) : SV_Target { float4 color SAMPLE_TEXTURE2D(_BaseColorMap, sampler_BaseColorMap, IN.uv); color * _BaseColor; return color; } ENDHLSL } } }注意在HDRP中纹理属性的命名约定通常使用Map后缀如_BaseColorMap这与URP和内置管线的命名习惯不同。5. 实战案例结合Tilling和Offset创造复杂效果让我们通过一个完整的案例展示如何结合动态Tilling和Offset创造出高级视觉效果。5.1 魔法护盾效果这个效果结合了动态Offset创造能量流动动态Tilling模拟能量波动边缘发光增强视觉冲击Shader Custom/MagicShield { Properties { _MainTex (Base Texture, 2D) white {} _NoiseTex (Noise Texture, 2D) gray {} _FlowSpeed (Flow Speed, Float) 0.5 _PulseSpeed (Pulse Speed, Float) 1.0 _EdgeColor (Edge Color, Color) (0,1,1,1) _EdgeWidth (Edge Width, Range(0, 0.5)) 0.1 } SubShader { Tags { RenderTypeTransparent QueueTransparent } Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag sampler2D _MainTex, _NoiseTex; float4 _MainTex_ST, _NoiseTex_ST; float _FlowSpeed, _PulseSpeed; float4 _EdgeColor; float _EdgeWidth; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float2 noiseUV : TEXCOORD1; float3 worldPos : TEXCOORD2; float3 viewDir : TEXCOORD3; }; v2f vert (appdata_full v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.texcoord, _MainTex); o.noiseUV TRANSFORM_TEX(v.texcoord, _NoiseTex); o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; o.viewDir normalize(UnityWorldSpaceViewDir(o.worldPos)); return o; } fixed4 frag (v2f i) : SV_Target { // 动态Offset - 能量流动 float2 flowOffset float2(_Time.y * _FlowSpeed, 0); // 动态Tilling - 能量波动 float pulse sin(_Time.y * _PulseSpeed) * 0.5 1.0; float2 dynamicTilling float2(pulse, pulse); // 应用变换 float2 finalUV i.uv * dynamicTilling flowOffset; // 采样噪声纹理增加细节 fixed4 noise tex2D(_NoiseTex, i.noiseUV flowOffset * 0.5); finalUV (noise.rg - 0.5) * 0.05; fixed4 col tex2D(_MainTex, finalUV); // 边缘发光效果 float fresnel 1.0 - saturate(dot(normalize(i.viewDir), float3(0,1,0))); float edge smoothstep(0.5 - _EdgeWidth, 0.5 _EdgeWidth, fresnel); col.rgb lerp(col.rgb, _EdgeColor.rgb, edge * _EdgeColor.a); // 根据噪声增加透明度变化 col.a * noise.b * 0.5 0.5; return col; } ENDCG } } }5.2 配套C#控制器using UnityEngine; [ExecuteAlways] public class MagicShieldController : MonoBehaviour { [SerializeField] private Material shieldMaterial; [SerializeField] private float minTilling 0.8f; [SerializeField] private float maxTilling 1.2f; [SerializeField] private float pulseSpeed 1.0f; [SerializeField] private Vector2 flowSpeed new Vector2(0.3f, 0); private Vector2 currentOffset; private float currentTilling; private int mainTexSTId; private int noiseTexSTId; void Start() { mainTexSTId Shader.PropertyToID(_MainTex_ST); noiseTexSTId Shader.PropertyToID(_NoiseTex_ST); currentOffset Vector2.zero; currentTilling minTilling; } void Update() { // 更新Offset currentOffset flowSpeed * Time.deltaTime; currentOffset.x Mathf.Repeat(currentOffset.x, 1); currentOffset.y Mathf.Repeat(currentOffset.y, 1); // 更新Tilling currentTilling Mathf.PingPong(Time.time * pulseSpeed, maxTilling - minTilling) minTilling; // 应用变化到主纹理 Vector4 mainST shieldMaterial.GetVector(mainTexSTId); mainST.x currentTilling; mainST.y currentTilling; mainST.z currentOffset.x; mainST.w currentOffset.y; shieldMaterial.SetVector(mainTexSTId, mainST); // 噪声纹理使用半速Offset Vector4 noiseST shieldMaterial.GetVector(noiseTexSTId); noiseST.z currentOffset.x * 0.5f; noiseST.w currentOffset.y * 0.5f; shieldMaterial.SetVector(noiseTexSTId, noiseST); } }
http://www.gsyq.cn/news/1381471.html

相关文章:

  • 在OpenClaw项目中接入Taotoken作为Agent执行后端
  • 别再手动算UV了!Unity Shader中TRANSFORM_TEX宏的隐藏用法与性能优化实战
  • IDC官宣!低代码增速42.3%,AI原生+私有化成2026技术主流
  • 【C++】零基础入门 · 第 5 节:函数基础
  • 零基础3分钟免费获取百度文库文档:浏览器控制台脚本实战指南
  • 微博相册批量下载终极指南:轻松获取高清图片收藏
  • UE5动画拖尾粒子实战:用材质和通知轨道,为角色动作添加酷炫特效(附完整蓝图)
  • 告别卡顿!用Addressable动态加载优化后的TMP字体,实现UI秒开
  • 别再手动找点了!用OpenCV的stereoRectify函数,5分钟搞定双目相机立体校正
  • 备战2026求职季:5款实用AI面试工具推荐与深度横评
  • 风扇控制软件终极指南:如何用FanControl彻底解决电脑噪音与散热问题
  • 铜仁中医学类院校怎么选?2026年中医药教育升学完全指南 - 优质企业观察收录
  • 2026年江苏省SCMP培训选哪家?众智商学院课程特色与真实评价 - 众智商学院课程中心
  • Linux CPU 容量感知:capacity_of 与异构计算调度
  • 毕节卫生类学校怎么选?2026年医卫中职升学完全指南 - 优质企业观察收录
  • Linux平台终极Jellyfin客户端:如何用Tsukimi打造专业级媒体中心体验?
  • Unity项目实战:用TriLib 2.x插件动态加载外部FBX/OBJ模型(含贴图自动读取)
  • 【升级 v 2.7.5 版本】Windows 端 Open Claw 本地部署图文详解
  • 利用模型广场为智能网站选择最合适的AI引擎
  • 2026天津黄金回收市场白皮书:个人旧金资产处置攻略 - 合扬奢侈品交易中心
  • Unity新手避坑指南:NavMesh烘焙参数(Agent Radius/Height)到底怎么设?附场景实测
  • 从《王者荣耀》野怪巡逻到RTS单位集结:拆解Unity Navigation系统在实战中的4种高级用法
  • Unity资源管理优化:YooAsset实现加载提速50%与零冗余部署
  • Android权限管理框架深度解析:XXPermissions架构设计与Android 16适配最佳实践
  • 福满多黄金回收|2026年5月金价高位震荡,吉林黄金变现全攻略 - 润富黄金珠宝行
  • Sora 2 GIF导出成功率从61%→99.8%:基于1072次A/B测试的7项关键参数调优矩阵(附可复用YAML配置模板)
  • AI采购决策迫在眉睫,Claude项目回本期究竟多久?——头部科技公司已验证的4.2个月临界阈值
  • 告别Postman!在虚幻引擎里用VaRest插件直接调试API的保姆级教程
  • Claude模型应用风险预警:政治、经济、社会、技术4大变量如何颠覆企业AI部署?
  • 从数据源到可视化:一份免费高精度气温数据的完整“食用”指南(附Python代码)