本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。
这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。
========================================== 分割线 ==========================================
这次的内容是打包合并textures。
Textures不仅仅可以用来存储颜色信息,还可以存储很多数据信息。这些数据信息可以分别存储到R、G、B、A四个部分,然后再打包成一张Texture,像下图这样:
为什么这样做有好处呢?在我们的应用中,textures的数目将很大程度上影响应用的性能。因此,为了减少textures的数量,我们可以看看Shader中使用的那些图片可以合并成一张,以此来优化性能。
任何灰度图都可以被打包进另一个新的texture的RGBA四个中的某一个chanel。这听起来不是很明白,没关系,这篇文章就会展示如何做到这点。
一个常用的场景是,你想要混合多张textures到一个surface上。这在terrain Shaders(地形渲染)中很常见,这种时候你往往需要很好的将一张texture和另一张混合起来。
这篇文章中将会告诉你,怎样完成一个由4张textures混合渲染而得的terrain Shader。
Properties { _MainTint ("Diffuse Tint", Color) = (1,1,1,1) //Add the properties below so we can input all of our textures _ColorA ("Terrain Color A", Color) = (1,1,1,1) _ColorB ("Terrain Color B", Color) = (1,1,1,1) _RTexture ("Red Channel Texture", 2D) = ""{} _GTexture ("Green Channel Texture", 2D) = ""{} _BTexture ("Blue Channel Texture", 2D) = ""{} _ATexture ("Alpha Channel Texture", 2D) = ""{} _BlendTex ("Blend Texture", 2D) = ""{} }
CGPROGRAM #pragma surface surf Lambert float4 _MainTint; float4 _ColorA; float4 _ColorB; sampler2D _RTexture; sampler2D _GTexture; sampler2D _BTexture; sampler2D _BlendTex; sampler2D _ATexture;
struct Input { float2 uv_RTexture; float2 uv_GTexture; float2 uv_BTexture; float2 uv_ATexture; float2 uv_BlendTex; };
//Get the pixel data from the blend texture //we need a float 4 here because the texture //will return R,G,B,and A or X,Y,Z, and W float4 blendData = tex2D(_BlendTex, IN.uv_BlendTex); //Get the data from the textures we want to blend float4 rTexData = tex2D(_RTexture, IN.uv_RTexture); float4 gTexData = tex2D(_GTexture, IN.uv_GTexture); float4 bTexData = tex2D(_BTexture, IN.uv_BTexture); float4 aTexData = tex2D(_ATexture, IN.uv_ATexture);
//No we need to contruct a new RGBA value and add all //the different blended texture back together float4 finalColor; finalColor = lerp(rTexData, gTexData, blendData.g); finalColor = lerp(finalColor, bTexData, blendData.b); finalColor = lerp(finalColor, aTexData, blendData.a); finalColor.a = 1.0;
//Add on our terrain tinting colors float4 terrainLayers = lerp(_ColorA, _ColorB, blendData.r); finalColor *= terrainLayers; finalColor = saturate(finalColor); o.Albedo = finalColor.rgb * _MainTint.rgb; o.Alpha = finalColor.a;
Shader "Custom/TextureBlending" { Properties { _MainTint ("Diffuse Tint", Color) = (1,1,1,1) //Add the properties below so we can input all of our textures _ColorA ("Terrain Color A", Color) = (1,1,1,1) _ColorB ("Terrain Color B", Color) = (1,1,1,1) _RTexture ("Red Channel Texture", 2D) = ""{} _GTexture ("Green Channel Texture", 2D) = ""{} _BTexture ("Blue Channel Texture", 2D) = ""{} _ATexture ("Alpha Channel Texture", 2D) = ""{} _BlendTex ("Blend Texture", 2D) = ""{} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert float4 _MainTint; float4 _ColorA; float4 _ColorB; sampler2D _RTexture; sampler2D _GTexture; sampler2D _BTexture; sampler2D _BlendTex; sampler2D _ATexture; struct Input { float2 uv_RTexture; float2 uv_GTexture; float2 uv_BTexture; float2 uv_ATexture; float2 uv_BlendTex; }; void surf (Input IN, inout SurfaceOutput o) { //Get the pixel data from the blend texture //we need a float 4 here because the texture //will return R,G,B,and A or X,Y,Z, and W float4 blendData = tex2D(_BlendTex, IN.uv_BlendTex); //Get the data from the textures we want to blend float4 rTexData = tex2D(_RTexture, IN.uv_RTexture); float4 gTexData = tex2D(_GTexture, IN.uv_GTexture); float4 bTexData = tex2D(_BTexture, IN.uv_BTexture); float4 aTexData = tex2D(_ATexture, IN.uv_ATexture); //No we need to contruct a new RGBA value and add all //the different blended texture back together float4 finalColor; finalColor = lerp(rTexData, gTexData, blendData.g); finalColor = lerp(finalColor, bTexData, blendData.b); finalColor = lerp(finalColor, aTexData, blendData.a); finalColor.a = 1.0; //Add on our terrain tinting colors float4 terrainLayers = lerp(_ColorA, _ColorB, blendData.r); finalColor *= terrainLayers; finalColor = saturate(finalColor); o.Albedo = finalColor.rgb * _MainTint.rgb; o.Alpha = finalColor.a; } ENDCG } FallBack "Diffuse" }
例如,我们想要在1和2之间找到一个中间值,我们可以使用0.5作为第三个参数,那么它将会返回1.5。因为一张texture的四个通道RGBA值都是简单的float类型,取值范围在-到1,因此可以使用它们作为混合程度值,即lerp的第三个参数来完成我们混合texture的需要。
在Shader中,我们仅从blend texture的四个通道中选择一个来控制每个像素颜色的混合结果。例如,我们从grass texture和dirt texture中提取颜色值,并使用blend texture对应的G通道值进行lerp运算。
如果可视化上述计算,可以参见下图:
Shader可以如此简单地使用blend texture的四个通道值,以及其他用于颜色的texture(如grass texture等),来创建出最后的混合而得的texture。这个最后的texture成为我们最终的地形颜色,并会和diffuse light(上述代码中的_MainTint变量)进行乘法运算,来得到最终效果。
细心的话,你可能会好奇上述代码中的两个颜色值_ColorA和_ColorB的用途是什么。从代码里可以看出来我们使用了blend texture的R通道值用于混合这两个颜色值,并和之前4张texture的混合结果相乘,这两个颜色值混合的结果可以看成是该地形本身的颜色,例如有红土地、黄土地、黑土地之类的区别。