【Unity Shaders】使用纹理进行效果 – 打包和混合纹理

纹理不仅仅可以用来存储颜色信息,还可以存储很多数据信息这些数据信息可以分别存储到R,G,B,A四个部分,然后再打包成一张的质地,像下图这样:

为什么这样做有好处呢?在我们的应用中,纹理的数目将很大程度上影响应用的性能。因此,为了减少纹理的数量,我们可以看看着色器中使用的那些图片可以合并成一张,以此来优化性能。

任何灰度图都可以被打包进另一个新的纹理的RGBA四个中的某一个香奈儿。这听起来不是很明白,没关系,这篇文章就会展示如何做到这点。

一个常用的场景是,你想要混合多张纹理到一个表面上。这在terrain Shaders(地形渲染)中很常见,这种时候你往往需要很好的将一张纹理和另一张混合起来。

这篇文章中将会告诉你,怎样完成一个由4张纹理混合渲染而得到的地形着色器。

开始工作

  1. 还是创建一个新的着色器文件,并为这个着色器创建一个新的材料,名字可以称为TextureBlending;
  2. 创建一个新的场景,以便来测试我们的着色;
  3. 接下来,你需要4张用于混合的纹理它们可以是任何图片,但是为了得到一个效果较好的地形着色器,建议你分别准备一张草地(草),泥土(泥),小石子污垢),石头(石头)的质感。书的资源中所有游戏了这样四张质地(统一着色器和效果食谱/ 5084_Code /团结资产/ 5084_02_UnityAssets /素材目录下的Chapter02_Grass0103_2_S.jpg,Chapter02_RockSmooth0045_2_S.jpg,Chapter02_SandPebbles0027_1_S.jpg, Chapter02_SandPebbles0030_3_S.jpg):
  4. 最后,也是奇妙所在,我们需要一张混合的纹理(Unity Shaders and Effects Cookbook / 5084_Code / Unity assets / 5084_02_UnityAssets / Textures目录下的 Chapter02_TerrainBlend_001.jpg),它是由多个灰度图混合而成的。将会告诉我们以上四张质地在目标地形上是如何分布的:

实现

  1. 首先,向着色器的属性块中添加一些新的性质。我们需要5个sampler2D对象,也就是纹理,以及两个颜色的属性,和一个用于调整整体地形颜色的值。
    	属性{
    		_MainTint(“Diffuse Tint”,Color)=(1,1,1,1)
    		
    		//添加下面的属性,以便我们可以输入所有的纹理
    		_ColorA(“地形颜色A”,颜色)=(1,1,1,1)
    		_ColorB(“地形颜色B”,颜色)=(1,1,1,1)
    		_RTexture(“红色通道纹理”,2D)=“”{}
    		_GTexture(“绿色通道纹理”,2D)=“”{}
    		_BTexture(“蓝色通道纹理”,2D)=“”{}
    		_ATexture(“Alpha Channel Texture”,2D)=“”{}
    		_BlendTex(“混合纹理”,2D)=“”{}
    	}
  2. 在SubShader中创建8个变量,分别对应属性中的8个特性,以建立和它们之间的链接。
    		CGPROGRAM
    		#pragma表面冲浪Lambert
    
    		float4 _MainTint;
    		float4 _ColorA;
    		float4 _ColorB;
    		sampler2D _RTexture;
    		sampler2D _GTexture;
    		sampler2D _BTexture;
    		sampler2D _BlendTex;
    		sampler2D _ATexture;
  3. 为了根据每一张不同的纹理来改变其在地形上的平铺率(平铺率,可以理解为地上某些区域草比较密集,某些地区石头比较多等),我们需要修改结构体。
    		struct输入{
    			float2 uv_RTexture;
    			float2 uv_GTexture;
    			float2 uv_BTexture;
    			float2 uv_ATexture;
    			float2 uv_BlendTex;
    		};
  4. 在冲浪函数中,得到每张纹理的信息,并分别存储在它们对应的变量中。
    			//从混合纹理获取像素数据
    			//我们需要一个float 4,因为纹理 
    			//将返回R,G,B和A或X,Y,Z和W
    			float4 blendData = tex2D(_BlendTex,IN.uv_BlendTex);
    			
    			//从我们想要混合的纹理获取数据
    			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);
  5. 使用lerp函数将四张纹理混合lerp函数有三个参数,lerp (value:a ,value:b ,blend:c )。它们从前两个参数中得到数据,并使用最后一个参数混合前两个值。
    			//不,我们需要创建一个新的RGBA值并添加所有 
    			//不同的混合纹理回到一起
    			float4 finalColor;
    			finalColor = lerp(rTexData,gTexData,blendData.g);
    			finalColor = lerp(finalColor,bTexData,blendData.b);
    			finalColor = lerp(finalColor,aTexData,blendData.a);
    			finalColor.a = 1.0;
  6. 最后,我们使用混合纹理的R通道值混合两个颜色色调值,并将结果与​​之前的混合值相乘。
    			//添加我们的地形着色颜色
    			float4 terrainLayers = lerp(_ColorA,_ColorB,blendData.r);
    			finalColor * = terrainLayers;
    			finalColor = saturate(finalColor);
    				
    			o.Albedo = finalColor.rgb * _MainTint.rgb;
    			o.Alpha = finalColor.a;
整体代码如下:
着色器“自定义/纹理优化”{
	属性{
		_MainTint(“Diffuse Tint”,Color)=(1,1,1,1)
		
		//添加下面的属性,以便我们可以输入所有的纹理
		_ColorA(“地形颜色A”,颜色)=(1,1,1,1)
		_ColorB(“地形颜色B”,颜色)=(1,1,1,1)
		_RTexture(“红色通道纹理”,2D)=“”{}
		_GTexture(“绿色通道纹理”,2D)=“”{}
		_BTexture(“蓝色通道纹理”,2D)=“”{}
		_ATexture(“Alpha Channel Texture”,2D)=“”{}
		_BlendTex(“混合纹理”,2D)=“”{}
	}
	SubShader {
		标签{“RenderType”=“不透明”}
		LOD 200
		
		CGPROGRAM
		#pragma表面冲浪Lambert

		float4 _MainTint;
		float4 _ColorA;
		float4 _ColorB;
		sampler2D _RTexture;
		sampler2D _GTexture;
		sampler2D _BTexture;
		sampler2D _BlendTex;
		sampler2D _ATexture;

		struct输入{
			float2 uv_RTexture;
			float2 uv_GTexture;
			float2 uv_BTexture;
			float2 uv_ATexture;
			float2 uv_BlendTex;
		};

		void surf(Input IN,inout SurfaceOutput o){
			//从混合纹理获取像素数据
			//我们需要一个float 4,因为纹理 
			//将返回R,G,B和A或X,Y,Z和W
			float4 blendData = tex2D(_BlendTex,IN.uv_BlendTex);
			
			//从我们想要混合的纹理获取数据
			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);
			
			//不,我们需要创建一个新的RGBA值并添加所有 
			//不同的混合纹理回到一起
			float4 finalColor;
			finalColor = lerp(rTexData,gTexData,blendData.g);
			finalColor = lerp(finalColor,bTexData,blendData.b);
			finalColor = lerp(finalColor,aTexData,blendData.a);
			finalColor.a = 1.0;
			
			//添加我们的地形着色颜色
			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”
}

我们新建一个地形,并把创建的材质赋给它后,可以看到以下效果:

解释

上面的代码看起来有点复杂,但实际上混合的实质非常简单。为了完成以上效果,我们使用了CGFX标准库中内置的线性插值函数。该函数允许我们从参数一合参数二之间挑选一个值,并使用参数三作为混合程度它的工作原理如下所示:

例如,我们想要在1和2之间找到一个中间值,我们可以使用0.5作为第三个参数,那么它将会返回1.5。因为一张纹理的四个通道RGBA值都是简单的浮子类型,取值范围在-到1,因此可以使用它们作为混合程度值,即线性插值的第三个参数来完成我们混合纹理的需要。

在Shader中,我们只从混合纹理的四个通道中选择一个来控制每个像素颜色的混合结果例如,我们从草纹理和污垢纹理中提取颜色值,并使用混合纹理对应的G通道值进行线性插值运算。

如果可视化上述计算,可以参见下图:

Shader可以如此简单地使用混合纹理的四个通道值,以及其他用于颜色的纹理(如草纹等),来创建出最后的混合而得到的纹理这个最后的纹理成为我们最终的地形颜色,并会和漫射灯(上述代码中的_MainTint变量)进行乘法运算,来得到最终效果。

细节的话,你可能会好奇上述代码中的两个颜色值_ColorA和_ColorB的用途是什么。从代码里可以看出来我们使用了混合纹理的R通道值用于混合这两个颜色值,并和之前4张纹理的混合结果相乘,这两个颜色值混合的结果可以看成是该地形本身的颜色,例如有红土地,黄土地,黑土地之类的区别。

扩展 – 灰度图

最后,讲一下灰度图的原理,如果你对灰度图十分了解,那么就可以关闭这个网页了。
当你把四张纹理按不同的顺序对RGBA赋值的话,就会得到不同的效果。
例如,当材料的督察按照左边这样赋值后,会得到右面的地形效果。
如果按照另一个顺序赋值,则会得到不同的效果。
这里面的原因当然是因为在混合纹理中不同的通道值不一样我们的混合纹理如下所示:
正如灰度图的名字所示,灰度图中没有彩色只有灰色,即介于白色和黑色之间的颜色。那么RGBA四个通道的灰度图是如何得到最右边的彩色图的呢?
简单说来,这四个通道分别代表了红,绿,蓝,和透明度四种颜色值的浓度。我们以[R通道的灰度图,即最左边的灰度图为例。
在[R通道的灰度图中,越亮的部分,即颜色越白的部分,表示红色光在此处越亮,亮度级别越接近1(亮度范围在这里为0到1),表现为颜色越红,这可以从最右边的彩色图看出来(当然还会和蓝绿进行颜色混合得到最后彩色效果)。
相反,越黑的地方表示红色越弱,亮度级别越接近0。
为了方便记忆,我们可以记住下面四条原理:
  1. 通道中的纯白,代表了该色光在此处为最高亮度,亮度级别是1。
  2. 通道中的纯黑,代表了该色光在此处完全不发光,亮度级别是0。
  3. 介于纯黑纯白之间的灰度,代表了不同的发光程度,亮度级别介于0至1之间。
  4. 灰度中越偏白的部分,表示色光亮度值越高,越偏黑的部分则表示亮度值越低。
用于我们的例子中,在进行lerp (value:a ,value:b ,blend:c )计算时,当我们使用R通道值作为第三个参数时,R通道灰度图中越亮的部分越接近1),在地形表现中越接近值b的结果。在上述代码中,使用R通道混合的是两个颜色值_ColorA和_ColorB。当我们取消其他的影响时,并设置两个颜色值左图所示时,我们可以预测R灰度图中越亮的部分对应到地形中则越接近白色(_ColorB),反之越越的部分越接近红色(_ColorA),如右图所示:
以此原理,对应到四个通道,混合就会得到最终的效果。
最后,这篇教程里用到的纹理是使用World Machine(有免费版,但是有限制)这个软件创建的。