首先功能是在Unity中能在任意的模型上实现类似unity自带的地形系统里的贴图绘制功能。
能够支持最多4张自定义贴图的融合。
源码是在微元素论坛上大佬写的,可以在论坛搜索下E3D MeshPainter这个插件。我稍微改了下并且融合到了自己的Shader中,这里记录下学习的过程。
主要原理是利用一张贴图(SplatMap)的RGBA通道,分别指定不同的模型区域。一个通道对应一套需要融合的贴图。为了优化效果,一套贴图只有两张图,第一张图的RGB通道存放固有色,A通道存放高度信息,A通道的图是为了在刷地形的过程中能实现先绘制缝隙(高度图黑色区域)的功能。第二张图RG通道存放法线信息,B通道存放粗糙度。金属度用0-1的值控制。
//采样所有贴图
float4 OutPutBaseColor;
half4 OutPutBaseColor0 = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv * _BaseMap_ST.xy + _BaseMap_ST.zw) * _BaseColor;
half4 OutPutBaseColor1 = SAMPLE_TEXTURE2D(_BaseMap1, sampler_BaseMap1, i.uv * _BaseMap1_ST.xy + _BaseMap1_ST.zw) * _BaseColor1;
half4 OutPutBaseColor2 = SAMPLE_TEXTURE2D(_BaseMap2, sampler_BaseMap2, i.uv * _BaseMap2_ST.xy + _BaseMap2_ST.zw) * _BaseColor2;
half4 OutPutBaseColor3 = SAMPLE_TEXTURE2D(_BaseMap3, sampler_BaseMap3, i.uv * _BaseMap3_ST.xy + _BaseMap3_ST.zw) * _BaseColor3;
half3 normalTangent0 = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, i.uv * _BaseMap_ST.xy + _BaseMap_ST.z).xyz;
half3 normalTangent1 = SAMPLE_TEXTURE2D(_NormalMap1, sampler_NormalMap1, i.uv * _BaseMap1_ST.xy + _BaseMap1_ST.zw).xyz;
half3 normalTangent2 = SAMPLE_TEXTURE2D(_NormalMap2, sampler_NormalMap2, i.uv * _BaseMap2_ST.xy + _BaseMap2_ST.zw).xyz;
half3 normalTangent3 = SAMPLE_TEXTURE2D(_NormalMap3, sampler_NormalMap3, i.uv * _BaseMap3_ST.xy + _BaseMap3_ST.zw).xyz;
half4 var_Control = SAMPLE_TEXTURE2D(_Control, sampler_Control, i.uv * _Control_ST.xy + _Control_ST.zw);
half4 blend = HeightBlendTex(_BlendWeight,OutPutBaseColor0.a,OutPutBaseColor1.a,OutPutBaseColor2.a,OutPutBaseColor3.a,var_Control);
OutPutBaseColor = (OutPutBaseColor0 * blend.r + OutPutBaseColor1 * blend.g + OutPutBaseColor2 * blend.b + OutPutBaseColor3 * blend.a) * _ColorAll;
half OutPutSpecGloss;
OutPutSpecGloss = blend.r * lerp(0.0,normalTangent0.b,_BlendRoughness.x)
+ blend.g * lerp(0.0,normalTangent1.b,_BlendRoughness.y)
+ blend.b * lerp(0.0,normalTangent2.b,_BlendRoughness.z)
+ blend.a * lerp(0.0,normalTangent3.b,_BlendRoughness.w);
float OutPutRoughness = 1-sqrt(OutPutSpecGloss);
half OutPutMetallicGloss;
OutPutMetallicGloss = _BlendMetallic.r * blend.r + _BlendMetallic.g * blend.g + _BlendMetallic.b * blend.b + _BlendMetallic.a * blend.a;
half3 normal = (float3(normalTangent0.rg,1) * 2 - 0.42) * blend.r;
half3 normal1 = (float3(normalTangent1.rg,1) * 2 - 0.42) * blend.g;
half3 normal2 = (float3(normalTangent2.rg,1) * 2 - 0.42) * blend.b;
half3 normal3 = (float3(normalTangent3.rg,1) * 2 - 0.42) * blend.a;
half3 normalTangent = normalize(normal + normal1 + normal2 + normal3);
half3 normalWorld = normalize(half3(
dot(i.TtoW0.xyz, normalTangent),
dot(i.TtoW1.xyz, normalTangent),
dot(i.TtoW2.xyz, normalTangent)));
half4 HeightBlendTex(half weight,half depth1,half depth2,half depth3,half depth4,half4 control)
{
half4 blend ;
blend.r =depth1 * control.r;
blend.g =depth2 * control.g;
blend.b =depth3 * control.b;
blend.a =depth4 * control.a;
half ma = max(blend.r, max(blend.g, max(blend.b, blend.a)));
blend = max(blend - ma + weight , 0) * control;
return blend/(blend.r + blend.g + blend.b + blend.a);
}
那么现在的问题就是如何在编辑模式下如何实时修改那种控制图。 原理是先使用射线检测得到鼠标点击到的信息,得到UV位置等信息。
Physics.Raycast(terrain, out raycastHit, Mathf.Infinity, 1 << LayerMask.NameToLayer(useLayers[i]))
然后将控制图的颜色信息提取出来。
Color[] terrainBay = MaskTex.GetPixels(x, y, width, height, 0); //得到x,y位置width,height笔刷尺寸的颜色信息
提取出来后将颜色信息和需要绘制的颜色做Lerp。绘制的颜色需要4个颜色,分别是纯的RGBA。
//定义绘制的颜色
Color targetColor = new Color(1f, 0f, 0f, 0f);
switch (selTex) //选择绘制的贴图
{
case 0:
targetColor = new Color(1f, 0f, 0f, 0f);
break;
case 1:
targetColor = new Color(0f, 1f, 0f, 0f);
break;
case 2:
targetColor = new Color(0f, 0f, 1f, 0f);
break;
case 3:
targetColor = new Color(0f, 0f, 0f, 1f);
break;
}
//使用笔刷在原颜色和绘制颜色中Lerp
terrainBay[index] = Color.Lerp(terrainBay[index], targetColor, Stronger); //设置颜色绘制的颜色 使用Stronger在原图和绘制颜色中做Lerp
最后将新的颜色传回控制图。
MaskTex.SetPixels(x, y, width, height, terrainBay, 0); //使用terrainBay颜色 绘制x,y位置width,height尺寸的颜色信息
剩下的就是一些UnityEditor的编辑器样式修改。