1. 数学运算函数Shader中的基础工具包数学函数是Shader编程中最基础也最常用的工具。就像木匠离不开锤子和锯子Shader开发者必须熟练掌握这些数学工具。我在实际项目中经常遇到新手对着sin/cos函数发懵的情况其实理解起来很简单。以sin函数为例它可以用来制作周期性变化的效果。比如要实现一个上下浮动的旗帜效果可以这样写float wave sin(_Time.y * _WaveSpeed position.x * _WaveDensity) * _WaveHeight; position.y wave;这里_Time.y是Unity内置的时间变量_WaveSpeed控制波动速度_WaveHeight控制波动幅度。实测下来这种简单的数学函数组合就能实现相当自然的效果。lerp函数是另一个常用工具它能在两个值之间进行线性插值。我经常用它来做颜色过渡或位置渐变float3 color lerp(_StartColor, _EndColor, saturate(_Progress));saturate函数确保插值系数在0到1之间这是个好习惯。记得有一次我忘了加saturate结果当_Progress超出范围时出现了奇怪的渲染错误调试了好久才发现问题所在。数学函数组合使用能产生更复杂的效果。比如要模拟水面波纹float wave1 sin(dot(position.xz, _Direction1) * _Frequency1 _Time.y * _Speed1); float wave2 sin(dot(position.xz, _Direction2) * _Frequency2 _Time.y * _Speed2); float height (wave1 wave2) * 0.5 * _Amplitude;这种叠加多个正弦波的做法可以产生更自然的波动效果。关键在于调整各个参数找到合适的平衡点。2. 几何函数处理空间关系的神器几何函数在Shader中主要负责处理向量和空间关系。reflect函数是我用得最多的一个它可以根据入射方向和法线计算反射方向。实现镜面反射效果时reflect函数必不可少float3 viewDir normalize(_WorldSpaceCameraPos - worldPos); float3 reflectDir reflect(-viewDir, worldNormal); float4 reflection texCUBE(_CubeMap, reflectDir);这里需要注意传入reflect函数的是-viewDir因为函数要求入射方向指向表面。这个细节坑过不少新手包括当年的我。另一个实用函数是distance它可以计算两点间距离。在做边缘发光效果时特别有用float dist distance(worldPos, _CenterPoint); float glow 1.0 - saturate(dist / _Radius);normalize函数也值得特别关注。它用于将向量单位化这在光照计算中尤为重要。记得有次性能优化时我发现一个shader中有大量重复的normalize调用合并优化后性能提升了近20%。cross函数可以计算两个向量的叉积得到垂直于这两个向量的新向量。在做切线空间计算时经常用到float3 binormal cross(normal, tangent.xyz) * tangent.w;这里tangent.w存储了副切线的方向信息通常是±1。这个细节容易被忽略但会影响最终的法线贴图效果。3. 光照计算函数让场景活起来的关键光照计算是Shader最核心的部分之一而dot函数在这里扮演着重要角色。N·L法线与光线方向的点积是漫反射光照的基础float3 lightDir normalize(_WorldSpaceLightPos0.xyz); float NdotL dot(normal, lightDir); float diffuse max(0, NdotL) * _LightColor0.rgb;lit函数则更进一步它封装了完整的Phong光照模型计算float4 light lit(NdotL, NdotH, _SpecularPower); float3 specular light.z * _SpecularColor;这里NdotH是法线与半角向量的点积_SpecularPower控制高光锐利度。在实际项目中我通常会根据美术需求调整这些参数。smoothstep函数可以产生平滑的过渡效果非常适合用于边缘光或渐变float rim 1.0 - saturate(dot(normal, viewDir)); float rimLight smoothstep(_RimMin, _RimMax, rim) * _RimColor;通过调整_RimMin和_RimMax可以控制发光区域的范围。这个技巧在角色和场景特效中都很常用。4. 实战案例组合运用各种函数现在我们把学到的函数组合起来实现一个完整的水面Shader。这个案例来自我去年做的一个海洋项目// 波浪计算 float wave1 sin(dot(input.uv, _WaveDir1) * _WaveFreq1 _Time.y * _WaveSpeed1); float wave2 cos(dot(input.uv, _WaveDir2) * _WaveFreq2 _Time.y * _WaveSpeed2); float height (wave1 wave2) * _WaveHeight; // 法线计算 float3 derivX float3(1, 0, ddx(height)); float3 derivZ float3(0, 1, ddy(height)); float3 normal normalize(cross(derivX, derivZ)); // 光照计算 float3 lightDir normalize(_WorldSpaceLightPos0.xyz); float NdotL dot(normal, lightDir); float3 diffuse max(0, NdotL) * _LightColor0.rgb; // 镜面反射 float3 viewDir normalize(_WorldSpaceCameraPos - worldPos); float3 reflectDir reflect(-viewDir, normal); float3 specular pow(max(0, dot(reflectDir, lightDir)), _Gloss) * _SpecularColor; // 最终颜色 float3 finalColor lerp(_DeepColor, _ShallowColor, height) * diffuse specular;这个Shader结合了sin/cos、dot、reflect、cross等多种函数实现了动态波浪、光照和镜面反射效果。关键在于参数的调整需要反复测试才能达到理想效果。在移动平台上我会简化计算比如减少波浪层数或使用更简单的光照模型。性能优化是个永恒的话题需要根据目标平台做针对性调整。
UnityShader函数实战指南:从数学运算到光照计算
1. 数学运算函数Shader中的基础工具包数学函数是Shader编程中最基础也最常用的工具。就像木匠离不开锤子和锯子Shader开发者必须熟练掌握这些数学工具。我在实际项目中经常遇到新手对着sin/cos函数发懵的情况其实理解起来很简单。以sin函数为例它可以用来制作周期性变化的效果。比如要实现一个上下浮动的旗帜效果可以这样写float wave sin(_Time.y * _WaveSpeed position.x * _WaveDensity) * _WaveHeight; position.y wave;这里_Time.y是Unity内置的时间变量_WaveSpeed控制波动速度_WaveHeight控制波动幅度。实测下来这种简单的数学函数组合就能实现相当自然的效果。lerp函数是另一个常用工具它能在两个值之间进行线性插值。我经常用它来做颜色过渡或位置渐变float3 color lerp(_StartColor, _EndColor, saturate(_Progress));saturate函数确保插值系数在0到1之间这是个好习惯。记得有一次我忘了加saturate结果当_Progress超出范围时出现了奇怪的渲染错误调试了好久才发现问题所在。数学函数组合使用能产生更复杂的效果。比如要模拟水面波纹float wave1 sin(dot(position.xz, _Direction1) * _Frequency1 _Time.y * _Speed1); float wave2 sin(dot(position.xz, _Direction2) * _Frequency2 _Time.y * _Speed2); float height (wave1 wave2) * 0.5 * _Amplitude;这种叠加多个正弦波的做法可以产生更自然的波动效果。关键在于调整各个参数找到合适的平衡点。2. 几何函数处理空间关系的神器几何函数在Shader中主要负责处理向量和空间关系。reflect函数是我用得最多的一个它可以根据入射方向和法线计算反射方向。实现镜面反射效果时reflect函数必不可少float3 viewDir normalize(_WorldSpaceCameraPos - worldPos); float3 reflectDir reflect(-viewDir, worldNormal); float4 reflection texCUBE(_CubeMap, reflectDir);这里需要注意传入reflect函数的是-viewDir因为函数要求入射方向指向表面。这个细节坑过不少新手包括当年的我。另一个实用函数是distance它可以计算两点间距离。在做边缘发光效果时特别有用float dist distance(worldPos, _CenterPoint); float glow 1.0 - saturate(dist / _Radius);normalize函数也值得特别关注。它用于将向量单位化这在光照计算中尤为重要。记得有次性能优化时我发现一个shader中有大量重复的normalize调用合并优化后性能提升了近20%。cross函数可以计算两个向量的叉积得到垂直于这两个向量的新向量。在做切线空间计算时经常用到float3 binormal cross(normal, tangent.xyz) * tangent.w;这里tangent.w存储了副切线的方向信息通常是±1。这个细节容易被忽略但会影响最终的法线贴图效果。3. 光照计算函数让场景活起来的关键光照计算是Shader最核心的部分之一而dot函数在这里扮演着重要角色。N·L法线与光线方向的点积是漫反射光照的基础float3 lightDir normalize(_WorldSpaceLightPos0.xyz); float NdotL dot(normal, lightDir); float diffuse max(0, NdotL) * _LightColor0.rgb;lit函数则更进一步它封装了完整的Phong光照模型计算float4 light lit(NdotL, NdotH, _SpecularPower); float3 specular light.z * _SpecularColor;这里NdotH是法线与半角向量的点积_SpecularPower控制高光锐利度。在实际项目中我通常会根据美术需求调整这些参数。smoothstep函数可以产生平滑的过渡效果非常适合用于边缘光或渐变float rim 1.0 - saturate(dot(normal, viewDir)); float rimLight smoothstep(_RimMin, _RimMax, rim) * _RimColor;通过调整_RimMin和_RimMax可以控制发光区域的范围。这个技巧在角色和场景特效中都很常用。4. 实战案例组合运用各种函数现在我们把学到的函数组合起来实现一个完整的水面Shader。这个案例来自我去年做的一个海洋项目// 波浪计算 float wave1 sin(dot(input.uv, _WaveDir1) * _WaveFreq1 _Time.y * _WaveSpeed1); float wave2 cos(dot(input.uv, _WaveDir2) * _WaveFreq2 _Time.y * _WaveSpeed2); float height (wave1 wave2) * _WaveHeight; // 法线计算 float3 derivX float3(1, 0, ddx(height)); float3 derivZ float3(0, 1, ddy(height)); float3 normal normalize(cross(derivX, derivZ)); // 光照计算 float3 lightDir normalize(_WorldSpaceLightPos0.xyz); float NdotL dot(normal, lightDir); float3 diffuse max(0, NdotL) * _LightColor0.rgb; // 镜面反射 float3 viewDir normalize(_WorldSpaceCameraPos - worldPos); float3 reflectDir reflect(-viewDir, normal); float3 specular pow(max(0, dot(reflectDir, lightDir)), _Gloss) * _SpecularColor; // 最终颜色 float3 finalColor lerp(_DeepColor, _ShallowColor, height) * diffuse specular;这个Shader结合了sin/cos、dot、reflect、cross等多种函数实现了动态波浪、光照和镜面反射效果。关键在于参数的调整需要反复测试才能达到理想效果。在移动平台上我会简化计算比如减少波浪层数或使用更简单的光照模型。性能优化是个永恒的话题需要根据目标平台做针对性调整。