# 前言

Triplanar mapping 是一种三维贴图方式,分别计算 Z、Y、Z 三个方向的贴图贡献,然后混合。因此它相比一般的二维 UV 贴图方式,最大的好处大概就是:可以避免贴图拉伸。

而我也是基于这个好处,才想研究下它的。

对这个技术,在 Unity3D 官方文档上,有过一段提及,链接地址:https://docs.unity3d.com/Manual/SL-VertexFragmentShaderExamples.html

另外还有一个参考地址,比官方的说明可详细多了:
http://www.martinpalko.com/triplanar-mapping

当然示例主要是对模型,而对于地形来说,就不可能这样简单实现了,我也在网络上找过,相关中文资料基本上没有...... 英文资料... 就不多说了。而对于 Unity3D Terrain 相关的 Triplanar 资料,很抱歉,似乎根本就没有(当然,插件好像还是存在的)。

于是把官方的 Buildin Shader Download 下来,决定自己结合对于单独 Object 的资料进行处理下,建立一个适用于地形的 Triplanar Shader.

# 原理

更深层次的原理我也就没研究了,简单说下实现原理吧。
对于普通 Object 的实现,主要大概分为五步:
1. 取世界坐标 xy、xz、yz 作为三个轴向的 UV
2. 对 UV 通过 UVScale,或者说 TextureScale 进行缩放
3. 使用三个 UV 分别对贴图进行采样,计算出三个方向单独贴图采样
4. 计算法线方向,并对法线进行处理 (法线后面扮演了一个权重调整的角色,很重要)
5. 使用三个 UV 方向计算出来贴分别相乘法线的 X、Y、Z 并相加,取得最后的采样值,作为最终颜色

如果有法线的话,需要照此步骤依次对法线做同样的处理。

# 实现

OK, 现在轮到实现上来了。
首先,可以打开下载下来的 Buildin 地形 shader 看一下,主要大约就是 Standard-FirstPass.shader 了。

考虑到查看方便性问题.... 我决定还是贴一下代码:

Shader "Nature/Terrain/Standard" {
	Properties {
		// set by terrain engine
		[HideInInspector] _Control ("Control (RGBA)", 2D) = "red" {}
		[HideInInspector] _Splat3 ("Layer 3 (A)", 2D) = "white" {}
		[HideInInspector] _Splat2 ("Layer 2 (B)", 2D) = "white" {}
		[HideInInspector] _Splat1 ("Layer 1 (G)", 2D) = "white" {}
		[HideInInspector] _Splat0 ("Layer 0 (R)", 2D) = "white" {}
		[HideInInspector] _Normal3 ("Normal 3 (A)", 2D) = "bump" {}
		[HideInInspector] _Normal2 ("Normal 2 (B)", 2D) = "bump" {}
		[HideInInspector] _Normal1 ("Normal 1 (G)", 2D) = "bump" {}
		[HideInInspector] _Normal0 ("Normal 0 (R)", 2D) = "bump" {}
		[HideInInspector] [Gamma] _Metallic0 ("Metallic 0", Range(0.0, 1.0)) = 0.0	
		[HideInInspector] [Gamma] _Metallic1 ("Metallic 1", Range(0.0, 1.0)) = 0.0	
		[HideInInspector] [Gamma] _Metallic2 ("Metallic 2", Range(0.0, 1.0)) = 0.0	
		[HideInInspector] [Gamma] _Metallic3 ("Metallic 3", Range(0.0, 1.0)) = 0.0
		[HideInInspector] _Smoothness0 ("Smoothness 0", Range(0.0, 1.0)) = 1.0	
		[HideInInspector] _Smoothness1 ("Smoothness 1", Range(0.0, 1.0)) = 1.0	
		[HideInInspector] _Smoothness2 ("Smoothness 2", Range(0.0, 1.0)) = 1.0	
		[HideInInspector] _Smoothness3 ("Smoothness 3", Range(0.0, 1.0)) = 1.0

		// used in fallback on old cards & base map
		[HideInInspector] _MainTex ("BaseMap (RGB)", 2D) = "white" {}
		[HideInInspector] _Color ("Main Color", Color) = (1,1,1,1)
	}

	SubShader {
		Tags {
			"Queue" = "Geometry-100"
			"RenderType" = "Opaque"
		}

		CGPROGRAM
		#pragma surface surf Standard vertex:SplatmapVert finalcolor:SplatmapFinalColor finalgbuffer:SplatmapFinalGBuffer fullforwardshadows
		#pragma multi_compile_fog
		#pragma target 3.0
		// needs more than 8 texcoords
		#pragma exclude_renderers gles
		#include "UnityPBSLighting.cginc"

		#pragma multi_compile __ _TERRAIN_NORMAL_MAP

		#define TERRAIN_STANDARD_SHADER
		#define TERRAIN_SURFACE_OUTPUT SurfaceOutputStandard
		#include "TerrainSplatmapCommon.cginc"

		half _Metallic0;
		half _Metallic1;
		half _Metallic2;
		half _Metallic3;
		
		half _Smoothness0;
		half _Smoothness1;
		half _Smoothness2;
		half _Smoothness3;

		void surf (Input IN, inout SurfaceOutputStandard o) {
			half4 splat_control;
			half weight;
			fixed4 mixedDiffuse;
			half4 defaultSmoothness = half4(_Smoothness0, _Smoothness1, _Smoothness2, _Smoothness3);
			SplatmapMix(IN, defaultSmoothness, splat_control, weight, mixedDiffuse, o.Normal);
			o.Albedo = mixedDiffuse.rgb;
			o.Alpha = weight;
			o.Smoothness = mixedDiffuse.a;
			o.Metallic = dot(splat_control, half4(_Metallic0, _Metallic1, _Metallic2, _Metallic3));
		}
		ENDCG
	}

	Dependency "AddPassShader" = "Hidden/TerrainEngine/Splatmap/Standard-AddPass"
	Dependency "BaseMapShader" = "Hidden/TerrainEngine/Splatmap/Standard-Base"

	Fallback "Nature/Terrain/Diffuse"
}

初一看,大概会觉得地形 shader “也不过如此”,参数很多,实现很少 —— 那就错了!
因为它引用到了 Unity 内置头文件 “TerrainSplatmapCommon.cginc”,好些个实现方法,都在那里面的。而影响地形最主要的一个,就是 “SplatmapMix” 方法,该方法,可以打开头文件 “TerrainSplatmapCommon.cginc” 进行查看。
而且,因为本身计算贴图就是这儿,所以我们需要修改的,也是这一块儿。

那么,稍微查看一下:

#ifdef TERRAIN_STANDARD_SHADER
void SplatmapMix(Input IN, half4 defaultAlpha, out half4 splat_control, out half weight, out fixed4 mixedDiffuse, inout fixed3 mixedNormal)
#else
void SplatmapMix(Input IN, out half4 splat_control, out half weight, out fixed4 mixedDiffuse, inout fixed3 mixedNormal)
#endif
{
	splat_control = tex2D(_Control, IN.tc_Control);
	weight = dot(splat_control, half4(1,1,1,1));

	#if !defined(SHADER_API_MOBILE) && defined(TERRAIN_SPLAT_ADDPASS)
		clip(weight == 0.0f ? -1 : 1);
	#endif

	// Normalize weights before lighting and restore weights in final modifier functions so that the overal
	// lighting result can be correctly weighted.
	splat_control /= (weight + 1e-3f);

	mixedDiffuse = 0.0f;
	#ifdef TERRAIN_STANDARD_SHADER
		mixedDiffuse += splat_control.r * tex2D(_Splat0, IN.uv_Splat0) * half4(1.0, 1.0, 1.0, defaultAlpha.r);
		mixedDiffuse += splat_control.g * tex2D(_Splat1, IN.uv_Splat1) * half4(1.0, 1.0, 1.0, defaultAlpha.g);
		mixedDiffuse += splat_control.b * tex2D(_Splat2, IN.uv_Splat2) * half4(1.0, 1.0, 1.0, defaultAlpha.b);
		mixedDiffuse += splat_control.a * tex2D(_Splat3, IN.uv_Splat3) * half4(1.0, 1.0, 1.0, defaultAlpha.a);
	#else
		mixedDiffuse += splat_control.r * tex2D(_Splat0, IN.uv_Splat0);
		mixedDiffuse += splat_control.g * tex2D(_Splat1, IN.uv_Splat1);
		mixedDiffuse += splat_control.b * tex2D(_Splat2, IN.uv_Splat2);
		mixedDiffuse += splat_control.a * tex2D(_Splat3, IN.uv_Splat3);
	#endif

	#ifdef _TERRAIN_NORMAL_MAP
		fixed4 nrm = 0.0f;
		nrm += splat_control.r * tex2D(_Normal0, IN.uv_Splat0);
		nrm += splat_control.g * tex2D(_Normal1, IN.uv_Splat1);
		nrm += splat_control.b * tex2D(_Normal2, IN.uv_Splat2);
		nrm += splat_control.a * tex2D(_Normal3, IN.uv_Splat3);
		mixedNormal = UnpackNormal(nrm);
	#endif
}

唔... 看起来信息量略大啊。

实际上,功能也就是计算四个通道的 Diffuse 贴图和法线贴图而已,只是因为有四个通道分别对应控制贴图的 R、G、B、A,所以看起来才一大团,理解就好。
而且,后面加入了对 triplanar 的计算之后....... 还得翻一番。

然后是顶点计算:

void SplatmapVert(inout appdata_full v, out Input data)
{
	UNITY_INITIALIZE_OUTPUT(Input, data);
	data.tc_Control = TRANSFORM_TEX(v.texcoord, _Control);	// Need to manually transform uv here, as we choose not to use 'uv' prefix for this texcoord.
	float4 pos = UnityObjectToClipPos(v.vertex);
	UNITY_TRANSFER_FOG(data, pos);

#ifdef _TERRAIN_NORMAL_MAP
	v.tangent.xyz = cross(v.normal, float3(0,0,1));
	v.tangent.w = -1;
#endif
}

内置的地形 Shader 暂时就看到这里,那么在修改之前,首先,我们需要确定一件事儿:相对于默认地形 Shader,如果想把它修改成 triplanar, 差了哪些参数?

答:worldPos (世界坐标)、worldNormal (世界空间法线)

这两个参数都是从 Vertex 片段程序中计算,而看看上面那段 SplatmapVert 程序,明显没有计算 —— 因为一般使用也用不上。
既然如此,我们就得自食其力了。

将 CGPROGRAM 下一行的 #pragma 修改如下:

#pragma surface surf Standard vertex:vert finalcolor:SplatmapFinalColor finalprepass:SplatmapFinalPrepass finalgbuffer:SplatmapFinalGBuffer fullforwardshadows

然后新建一个我们自己的 Input Struct,用于将参数从顶点程序传入片段..... 噢,对于 Unity3D 的 Surface Shader,应该叫做 “surf” 处理程序 (嘛... 实际上就是被 Unity 自动嵌入片段程序中的一个方法而已)。

		struct Input
		{
			float2 tc_Control : TEXCOORD4;
			float3 wNormal;
			float3 worldPos;
			UNITY_FOG_COORDS(5)
		};

因为只需要这么几个参数,使用 Triplanar 计算的地形是不需要贴图 UV 的,所以就这样了。

OK!然后是我们自己的顶点计算程序:

		void vert(inout appdata_full v, out Input o)
		{
			UNITY_INITIALIZE_OUTPUT(Input, o);
			o.tc_Control = TRANSFORM_TEX(v.texcoord, _Control);
			float4 pos = mul(UNITY_MATRIX_MVP, v.vertex);
			UNITY_TRANSFER_FOG(o, pos);

			o.wNormal =normalize(mul(_Object2World, fixed4(v.normal, 0)).xyz);

			#ifdef _TERRAIN_NORMAL_MAP
			v.tangent.xyz = cross(v.normal, float3(0, 0, 1));
			v.tangent.w = -1;
			#endif
		}

在这儿,一定、必定以及肯定:要注意一件事儿:UNITY_INITIALIZE_OUTPUT (Input, o)

这个 Unity 内置的宏将会初始化 Input 结构体中的变量,但是它是根据 “名字” 来的!所以,千万得跟内置的名字一样才行!比如说 “worldPos”,就一定得叫这个名字才行 —— 刚开始我就取名叫 “wPos”,结果坑的我一脸。最后浪费了好大一段时间,调试最终结果不正确的问题!
但是这个宏的实际定义方法,我却没在 Unity 的头文件中找到,若有人翻到了,希望提醒一声!

接下来,我们就可以在 Surf 方法中,具体处理了。

然后,稍微把 TerrainSplatmapCommon.cginc 中的 SplatmapMix 移过来一点:

			splat_control = tex2D(_Control, IN.tc_Control);
			weight = dot(splat_control, half4(1, 1, 1, 1));
			splat_control /= (weight + 1e-3f);

按照上述几个步骤进进行处理:

			//triplanar---------------<<<<<<<<<<<<
			//计算权重
			float3 N =normalize( IN.wNormal);
			half3 blendWeights = pow(abs(IN.wNormal), _TriplanarBlendSharpness);
			blendWeights /= dot(blendWeights, 1.0);
			half2 xUV = IN.worldPos.zy;// / _TextureScale;
			half2 yUV = IN.worldPos.xz;// / _TextureScale;
			half2 zUV = IN.worldPos.xy;// / _TextureScale;

			//通常 Triplanar实现,只是多了三张贴图处理
			fixed4 tex0X =  tex2D(_Splat0, (xUV*_Splat0_ST.xy + _Splat0_ST.zw)/ _TextureScale);
			fixed4 tex0Y = tex2D(_Splat0, (yUV*_Splat0_ST.xy + _Splat0_ST.zw) / _TextureScale);
			fixed4 tex0Z = tex2D(_Splat0, (zUV*_Splat0_ST.xy + _Splat0_ST.zw) / _TextureScale);

			fixed4 tex1X = tex2D(_Splat1, (xUV*_Splat1_ST.xy + _Splat1_ST.zw) / _TextureScale);
			fixed4 tex1Y = tex2D(_Splat1, (yUV*_Splat1_ST.xy + _Splat1_ST.zw) / _TextureScale);
			fixed4 tex1Z =  tex2D(_Splat1, (zUV*_Splat1_ST.xy + _Splat1_ST.zw) / _TextureScale);

			fixed4 tex2X = tex2D(_Splat2, (xUV*_Splat2_ST.xy + _Splat2_ST.zw)/ _TextureScale);
			fixed4 tex2Y = tex2D(_Splat2, (yUV*_Splat2_ST.xy + _Splat2_ST.zw)/ _TextureScale);
			fixed4 tex2Z = tex2D(_Splat2, (zUV*_Splat2_ST.xy + _Splat2_ST.zw)/ _TextureScale);

			fixed4 tex3X = tex2D(_Splat3, (xUV*_Splat3_ST.xy + _Splat3_ST.zw)/ _TextureScale);
			fixed4 tex3Y = tex2D(_Splat3, (yUV*_Splat3_ST.xy + _Splat3_ST.zw)/ _TextureScale);
			fixed4 tex3Z = tex2D(_Splat3, (zUV*_Splat3_ST.xy + _Splat3_ST.zw)/ _TextureScale);

			fixed4 tex0 = tex0X*blendWeights.x + tex0Y * blendWeights.y + tex0Z * blendWeights.z;
			fixed4 tex1 =  tex1X*blendWeights.x + tex1Y * blendWeights.y + tex1Z * blendWeights.z;
			fixed4 tex2 = tex2X*blendWeights.x + tex2Y * blendWeights.y + tex2Z * blendWeights.z;
			fixed4 tex3 = tex3X*blendWeights.x + tex3Y * blendWeights.y + tex3Z * blendWeights.z;

			//融合权重,添加高光
			tex0 *= splat_control.r *half4(1.0, 1.0, 1.0, defaultSmoothness.r);
			tex1 *= splat_control.g *half4(1.0, 1.0, 1.0, defaultSmoothness.g);
			tex2 *= splat_control.b *half4(1.0, 1.0, 1.0, defaultSmoothness.b);
			tex3 *= splat_control.a *half4(1.0, 1.0, 1.0, defaultSmoothness.a);

			mixedDiffuse = tex0 + tex1 + tex2 + tex3;

Diffuse 颜色就是这样处理了,法线贴图与此一致,就不在这儿贴出来浪费空间了。

如果注意到对 Splat_ST 的计算,这个跟 TRANSFORM_TEX 宏的功能差不多,如果有兴趣,也可以在 UnityCG.cginc 头文件找到,主要就是重新计算被修改了缩放大小的贴图:

// Transforms 2D UV by scale/bias property
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

# 效果

对比效果

从图中可以非常明显地看出,Triplanar 技术对于贴图拉伸的补正作用。自带的地形 Shader 已经把贴图拉成线了,Triplanar 基本完全正常。

# 总结

OK,事情基本上到此为止了。

Triplanar 相对普通贴图方式,效果很大,但是还有一点需要注意:性能。

因为 Triplanar 会分别对每张贴图都采样三次,再加上其它计算的消耗,也就是是说它功能虽好,但是性能注定会比普通贴图方式更耗 —— 至少也是 2 倍吧。

所以,任何事情,都不是无偿的啊。

而我之所以研究这个,还是跟自己做的 Demo 有关,普通游戏也就罢了,如果处理好斜坡贴都和陡度的话,还是可以在一定程度上掩盖贴图拉伸问题的。不过我这次做的 Demo,因为想加入 “挖地形” 的功能,比如说挖矿什么真的可以挖个坑之类的。那这个问题就比较突出了 —— 如果一锄子下去,地形都 “花了”,那就.......... 所以,才需要这么个东西。

—— 嘛,我承认最近快要离开的项目也想用这技术,最后貌似是采用的一个插件了。

就是这样。

# 源码

源码.... 本来不想贴出来的,毕竟这么大一坨。但是断断续续会让看的人很困扰的吧?
所以,源码如下:

版本:2016.11.21

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

//2016.11.18 by cwhisme
//Note:在基于Unity Buildin Terrain Shader的基础上修改
//Unity 内置的地形shader可以在官方网站上进行下载
//加入了Triplanar计算,避免贴图拉伸问题

Shader "CWHISME/TriplannarTerrain"
{
	Properties{
		// set by terrain engine
		[HideInInspector] _Control("Control (RGBA)", 2D) = "red" {}
		[HideInInspector] _Splat3("Layer 3 (A)", 2D) = "white" {}
		[HideInInspector] _Splat2("Layer 2 (B)", 2D) = "white" {}
		[HideInInspector] _Splat1("Layer 1 (G)", 2D) = "white" {}
		[HideInInspector] _Splat0("Layer 0 (R)", 2D) = "white" {}
		[HideInInspector] _Normal3("Normal 3 (A)", 2D) = "bump" {}
		[HideInInspector] _Normal2("Normal 2 (B)", 2D) = "bump" {}
		[HideInInspector] _Normal1("Normal 1 (G)", 2D) = "bump" {}
		[HideInInspector] _Normal0("Normal 0 (R)", 2D) = "bump" {}
		[HideInInspector][Gamma] _Metallic0("Metallic 0", Range(0.0, 1.0)) = 0.0
		[HideInInspector][Gamma] _Metallic1("Metallic 1", Range(0.0, 1.0)) = 0.0
		[HideInInspector][Gamma] _Metallic2("Metallic 2", Range(0.0, 1.0)) = 0.0
		[HideInInspector][Gamma] _Metallic3("Metallic 3", Range(0.0, 1.0)) = 0.0
		[HideInInspector] _Smoothness0("Smoothness 0", Range(0.0, 1.0)) = 1.0
		[HideInInspector] _Smoothness1("Smoothness 1", Range(0.0, 1.0)) = 1.0
		[HideInInspector] _Smoothness2("Smoothness 2", Range(0.0, 1.0)) = 1.0
		[HideInInspector] _Smoothness3("Smoothness 3", Range(0.0, 1.0)) = 1.0

		// used in fallback on old cards & base map
		[HideInInspector] _MainTex("BaseMap (RGB)", 2D) = "white" {}
		[HideInInspector] _Color("Main Color", Color) = (1,1,1,1)

		_TextureScale("Texture Scale",float) = 100
		_TriplanarBlendSharpness("Triplanar Blend Sharpness",float) = 1
	}

		SubShader{
		Tags{
			"Queue" = "Geometry-100"
			"RenderType" = "Opaque"
		}

		CGPROGRAM
		#pragma surface surf Standard vertex:vert finalcolor:SplatmapFinalColor finalprepass:SplatmapFinalPrepass finalgbuffer:SplatmapFinalGBuffer fullforwardshadows
		#pragma multi_compile_fog
		#pragma target 3.0
		// needs more than 8 texcoords
		#pragma exclude_renderers gles
		#include "UnityPBSLighting.cginc"

		#pragma multi_compile __ _TERRAIN_NORMAL_MAP

		//#define TERRAIN_STANDARD_SHADER
		#define TERRAIN_SURFACE_OUTPUT SurfaceOutputStandard
		//#include "TerrainSplatmapCommon.cginc"

		sampler2D _Control;
		float4 _Control_ST;
		sampler2D _Splat0, _Splat1, _Splat2, _Splat3;
		half4 _Splat0_ST, _Splat1_ST, _Splat2_ST, _Splat3_ST;
		#ifdef _TERRAIN_NORMAL_MAP
		sampler2D _Normal0, _Normal1, _Normal2, _Normal3;
		#endif

		half _Metallic0;
		half _Metallic1;
		half _Metallic2;
		half _Metallic3;

		half _Smoothness0;
		half _Smoothness1;
		half _Smoothness2;
		half _Smoothness3;

		float _TriplanarBlendSharpness;
		float _TextureScale;

		struct Input
		{
			float2 tc_Control : TEXCOORD4;
			float3 wNormal;
			float3 worldPos;
			UNITY_FOG_COORDS(5)
		};

		void vert(inout appdata_full v, out Input o)
		{
			UNITY_INITIALIZE_OUTPUT(Input, o);
			o.tc_Control = TRANSFORM_TEX(v.texcoord, _Control);
			float4 pos = mul(UNITY_MATRIX_MVP, v.vertex);
			UNITY_TRANSFER_FOG(o, pos);

			o.wNormal =normalize(mul(unity_ObjectToWorld, fixed4(v.normal, 0)).xyz);

			#ifdef _TERRAIN_NORMAL_MAP
			v.tangent.xyz = cross(v.normal, float3(0, 0, 1));
			v.tangent.w = -1;
			#endif
		}

		void surf(Input IN, inout SurfaceOutputStandard o) {
			half4 splat_control;
			half weight;
			fixed4 mixedDiffuse=0;
			half4 defaultSmoothness = half4(_Smoothness0, _Smoothness1, _Smoothness2, _Smoothness3);
			//SplatmapMix(IN, defaultSmoothness, splat_control, weight, mixedDiffuse, o.Normal);

			//Custom begin=================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<
			splat_control = tex2D(_Control, IN.tc_Control);
			weight = dot(splat_control, half4(1, 1, 1, 1));
			splat_control /= (weight + 1e-3f);

			//triplanar---------------<<<<<<<<<<<<
			//计算权重
			float3 N =normalize( IN.wNormal);
			half3 blendWeights = pow(abs(IN.wNormal), _TriplanarBlendSharpness);
			blendWeights /= dot(blendWeights, 1.0);
			half2 xUV = IN.worldPos.zy;// / _TextureScale;
			half2 yUV = IN.worldPos.xz;// / _TextureScale;
			half2 zUV = IN.worldPos.xy;// / _TextureScale;

			//通常 Triplanar实现,只是多了三张贴图处理
			fixed4 tex0X =  tex2D(_Splat0, (xUV*_Splat0_ST.xy + _Splat0_ST.zw)/ _TextureScale);
			fixed4 tex0Y = tex2D(_Splat0, (yUV*_Splat0_ST.xy + _Splat0_ST.zw) / _TextureScale);
			fixed4 tex0Z = tex2D(_Splat0, (zUV*_Splat0_ST.xy + _Splat0_ST.zw) / _TextureScale);

			fixed4 tex1X = tex2D(_Splat1, (xUV*_Splat1_ST.xy + _Splat1_ST.zw) / _TextureScale);
			fixed4 tex1Y = tex2D(_Splat1, (yUV*_Splat1_ST.xy + _Splat1_ST.zw) / _TextureScale);
			fixed4 tex1Z =  tex2D(_Splat1, (zUV*_Splat1_ST.xy + _Splat1_ST.zw) / _TextureScale);

			fixed4 tex2X = tex2D(_Splat2, (xUV*_Splat2_ST.xy + _Splat2_ST.zw)/ _TextureScale);
			fixed4 tex2Y = tex2D(_Splat2, (yUV*_Splat2_ST.xy + _Splat2_ST.zw)/ _TextureScale);
			fixed4 tex2Z = tex2D(_Splat2, (zUV*_Splat2_ST.xy + _Splat2_ST.zw)/ _TextureScale);

			fixed4 tex3X = tex2D(_Splat3, (xUV*_Splat3_ST.xy + _Splat3_ST.zw)/ _TextureScale);
			fixed4 tex3Y = tex2D(_Splat3, (yUV*_Splat3_ST.xy + _Splat3_ST.zw)/ _TextureScale);
			fixed4 tex3Z = tex2D(_Splat3, (zUV*_Splat3_ST.xy + _Splat3_ST.zw)/ _TextureScale);

			fixed4 tex0 = tex0X*blendWeights.x + tex0Y * blendWeights.y + tex0Z * blendWeights.z;
			fixed4 tex1 =  tex1X*blendWeights.x + tex1Y * blendWeights.y + tex1Z * blendWeights.z;
			fixed4 tex2 = tex2X*blendWeights.x + tex2Y * blendWeights.y + tex2Z * blendWeights.z;
			fixed4 tex3 = tex3X*blendWeights.x + tex3Y * blendWeights.y + tex3Z * blendWeights.z;

			//融合权重,添加高光
			tex0 *= splat_control.r *half4(1.0, 1.0, 1.0, defaultSmoothness.r);
			tex1 *= splat_control.g *half4(1.0, 1.0, 1.0, defaultSmoothness.g);
			tex2 *= splat_control.b *half4(1.0, 1.0, 1.0, defaultSmoothness.b);
			tex3 *= splat_control.a *half4(1.0, 1.0, 1.0, defaultSmoothness.a);

			mixedDiffuse = tex0 + tex1 + tex2 + tex3;

			//mixedDiffuse += 
			//mixedDiffuse += splat_control.g * tex2D(_Splat1, IN.uv_Splat1) * half4(1.0, 1.0, 1.0, defaultSmoothness.g);
			//mixedDiffuse += splat_control.b * tex2D(_Splat2, IN.uv_Splat2) * half4(1.0, 1.0, 1.0, defaultSmoothness.b);
			//mixedDiffuse += splat_control.a * tex2D(_Splat3, IN.uv_Splat3) * half4(1.0, 1.0, 1.0, defaultSmoothness.a);

			//---------法线---------<<<<<<<<<<<<<<<<<<<<<<<<<<<<
			#ifdef _TERRAIN_NORMAL_MAP
			fixed4 nrm = 0.0f;

			fixed4 nm0X = tex2D(_Normal0, (xUV* _Splat0_ST.xy + _Splat0_ST.zw)/ _TextureScale);
			fixed4 nm0Y = tex2D(_Normal0, (yUV* _Splat0_ST.xy + _Splat0_ST.zw) / _TextureScale);
			fixed4 nm0Z = tex2D(_Normal0, (zUV* _Splat0_ST.xy + _Splat0_ST.zw) / _TextureScale);
											
			fixed4 nm1X = tex2D(_Normal1, (xUV* _Splat1_ST.xy + _Splat1_ST.zw) / _TextureScale);
			fixed4 nm1Y = tex2D(_Normal1, (yUV* _Splat1_ST.xy + _Splat1_ST.zw) / _TextureScale);
			fixed4 nm1Z = tex2D(_Normal1, (zUV* _Splat1_ST.xy + _Splat1_ST.zw) / _TextureScale);
													  
			fixed4 nm2X = tex2D(_Normal2, (xUV* _Splat2_ST.xy + _Splat2_ST.zw)/ _TextureScale);
			fixed4 nm2Y = tex2D(_Normal2, (yUV* _Splat2_ST.xy + _Splat2_ST.zw)/ _TextureScale);
			fixed4 nm2Z = tex2D(_Normal2, (zUV* _Splat2_ST.xy + _Splat2_ST.zw)/ _TextureScale);
						 								
			fixed4 nm3X = tex2D(_Normal3, (xUV* _Splat3_ST.xy + _Splat3_ST.zw)/ _TextureScale);
			fixed4 nm3Y = tex2D(_Normal3, (yUV* _Splat3_ST.xy + _Splat3_ST.zw)/ _TextureScale);
			fixed4 nm3Z = tex2D(_Normal3, (zUV* _Splat3_ST.xy + _Splat3_ST.zw)/ _TextureScale);

			fixed4 nm0 = nm0X*blendWeights.x +nm0Y * blendWeights.y + nm0Z * blendWeights.z;
			fixed4 nm1 =  nm1X*blendWeights.x + nm1Y * blendWeights.y + nm1Z * blendWeights.z;
			fixed4 nm2 = nm2X*blendWeights.x + nm2Y * blendWeights.y + nm2Z * blendWeights.z;
			fixed4 nm3 = nm3X*blendWeights.x + nm3Y * blendWeights.y + nm3Z * blendWeights.z;

			nm0 *= splat_control.r;
			nm1 *= splat_control.g;
			nm2 *= splat_control.b;
			nm3 *= splat_control.a;

			nrm = nm0 + nm1 + nm2 + nm3;
			//nrm += splat_control.r * tex2D(_Normal0, IN.uv_Splat0);
			//nrm += splat_control.g * tex2D(_Normal1, IN.uv_Splat1);
			//nrm += splat_control.b * tex2D(_Normal2, IN.uv_Splat2);
			//nrm += splat_control.a * tex2D(_Normal3, IN.uv_Splat3);
			o.Normal = UnpackNormal(nrm);
			#endif
			//End Custom=================================================<<<<<<<<<<<<<<<<<<<<<<<<<<<

			o.Albedo = max(fixed3(0.001, 0.001, 0.001), mixedDiffuse.rgb); 
			o.Alpha = weight;
			o.Smoothness = mixedDiffuse.a;
			o.Metallic = dot(splat_control, half4(_Metallic0, _Metallic1, _Metallic2, _Metallic3));
		}

		void SplatmapFinalColor(Input IN, TERRAIN_SURFACE_OUTPUT o, inout fixed4 color)
		{
			color *= o.Alpha;
			#ifdef TERRAIN_SPLAT_ADDPASS
			UNITY_APPLY_FOG_COLOR(IN.fogCoord, color, fixed4(0, 0, 0, 0));
			#else
			UNITY_APPLY_FOG(IN.fogCoord, color);
			#endif
		}

		void SplatmapFinalPrepass(Input IN, TERRAIN_SURFACE_OUTPUT o, inout fixed4 normalSpec)
		{
			normalSpec *= o.Alpha;
		}

		void SplatmapFinalGBuffer(Input IN, TERRAIN_SURFACE_OUTPUT o, inout half4 diffuse, inout half4 specSmoothness, inout half4 normal, inout half4 emission)
		{
			diffuse.rgb *= o.Alpha;
			specSmoothness *= o.Alpha;
			normal.rgb *= o.Alpha;
			emission *= o.Alpha;
		}

		ENDCG
	}

	Fallback "Nature/Terrain/Diffuse"
}