写了个基于Height Map的Texture Mix的工具,应该还有提升的空间先看看效果
主要思路是通过splat_control这张贴图的四个通道控制_Splat0~_Splat3这四张贴图的混合,如果splat_control对应通道的值为1,那么这个通道对应的贴图就完全显示,为0则完全不显示,通过修改splat_control贴图就可以实现想要的混合效果了;
核心代码如下
fixed4 splat_control = tex2D (_Control, IN.uv_Control).rgba; fixed3 lay1 = tex2D (_Splat0, IN.uv_Splat0); fixed3 lay2 = tex2D (_Splat1, IN.uv_Splat1); fixed3 lay3 = tex2D (_Splat2, IN.uv_Splat2); fixed3 lay4 = tex2D (_Splat3, IN.uv_Splat3); _Alpha = 0.0; Albedo.rgb = (lay1 * splat_control.r + lay2 * splat_control.g + lay3 * splat_control.b+ lay4 * splat_control.a);
混合处
float3 blend(float3 lay1, float3 lay2, float4 splat_control) { float b1 = lay1.a * splat_control.r; float b2 = lay2.a * splat_control.g; float ma = max(b1,b2); b1 = max(b1 - (ma – 0.3), 0) * splat_control.r; b2 = max(b2 - (ma – 0.3), 0) * splat_control.g; return (lay1.rgb * b1 + lay2.rgb * b2)/(b1 + b2); }
下面是工具
using UnityEngine; using System.Collections; [ExecuteInEditMode] [RequireComponent(typeof(MeshCollider))] public class MeshPainter : MonoBehaviour { void Start () { } void Update () { } }
using UnityEngine; using UnityEditor; using System.IO; using System.Collections; [CustomEditor(typeof(MeshPainter))] [CanEditMultipleObjects] public class MeshPainterStyle : Editor { string contolTexName = ""; bool isPaint; float brushSize = 16f; float brushStronger = 0.5f; Texture[] brushTex; Texture[] texLayer; int selBrush = 0; int selTex = 0; int brushSizeInPourcent; Texture2D MaskTex; void OnSceneGUI() { if (isPaint) { Painter(); } } public override void OnInspectorGUI() { if (Cheak()) { GUIStyle boolBtnOn = new GUIStyle(GUI.skin.GetStyle("Button"));//得到Button样式 GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); isPaint = GUILayout.Toggle(isPaint, EditorGUIUtility.IconContent("ClothInspector.PaintValue"), boolBtnOn, GUILayout.Width(35), GUILayout.Height(25));//编辑模式开关 GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); brushSize = (int)EditorGUILayout.Slider("Brush Size", brushSize, 1, 36);//笔刷大小 brushStronger = EditorGUILayout.Slider("Brush Stronger", brushStronger, 0, 1f);//笔刷强度 IniBrush(); layerTex(); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal("box", GUILayout.Width(340)); selTex = GUILayout.SelectionGrid(selTex, texLayer, 4, "gridlist", GUILayout.Width(340), GUILayout.Height(86)); GUILayout.EndHorizontal(); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal("box", GUILayout.Width(318)); selBrush = GUILayout.SelectionGrid(selBrush, brushTex, 9, "gridlist", GUILayout.Width(340), GUILayout.Height(70)); GUILayout.EndHorizontal(); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } } //获取材质球中的贴图 void layerTex() { Transform Select = Selection.activeTransform; texLayer = new Texture[4]; texLayer[0] = AssetPreview.GetAssetPreview(Select.gameObject.GetComponent<MeshRenderer>().sharedMaterial.GetTexture("_Splat0")) as Texture; texLayer[1] = AssetPreview.GetAssetPreview(Select.gameObject.GetComponent<MeshRenderer>().sharedMaterial.GetTexture("_Splat1")) as Texture; texLayer[2] = AssetPreview.GetAssetPreview(Select.gameObject.GetComponent<MeshRenderer>().sharedMaterial.GetTexture("_Splat2")) as Texture; texLayer[3] = AssetPreview.GetAssetPreview(Select.gameObject.GetComponent<MeshRenderer>().sharedMaterial.GetTexture("_Splat3")) as Texture; } //获取笔刷 void IniBrush() { string MeshPaintEditorFolder = "Assets/MeshPaint/Editor/"; ArrayList BrushList = new ArrayList(); Texture BrushesTL; int BrushNum = 0; do { BrushesTL = (Texture)AssetDatabase.LoadAssetAtPath(MeshPaintEditorFolder + "Brushes/Brush" + BrushNum + ".png", typeof(Texture)); if (BrushesTL) { BrushList.Add(BrushesTL); } BrushNum++; } while (BrushesTL); brushTex = BrushList.ToArray(typeof(Texture)) as Texture[]; } //检查 bool Cheak() { bool Cheak = false; Transform Select = Selection.activeTransform; Texture ControlTex = Select.gameObject.GetComponent<MeshRenderer>().sharedMaterial.GetTexture("_Control"); if(Select.gameObject.GetComponent<MeshRenderer>().sharedMaterial.shader == Shader.Find("4Tex_Blend_Normal") || Select.gameObject.GetComponent<MeshRenderer>().sharedMaterial.shader == Shader.Find("zcxshaderlibrary/texBlendWithBump")) { if(ControlTex == null) { EditorGUILayout.HelpBox("当前模型材质球中未找到Control贴图,绘制功能不可用!", MessageType.Error); if (GUILayout.Button("创建Control贴图")) { creatContolTex(); //Select.gameObject.GetComponent<MeshRenderer>().sharedMaterial.SetTexture("_Control", creatContolTex()); } } else { Cheak = true; } } else { EditorGUILayout.HelpBox("当前模型shader错误!请更换!", MessageType.Error); } return Cheak; } //创建Contol贴图 void creatContolTex() { //创建一个新的Contol贴图 string ContolTexFolder = "Assets/MeshPaint/Controler/"; Texture2D newMaskTex = new Texture2D(512, 512, TextureFormat.ARGB32, true); Color[] colorBase = new Color[512 * 512]; for(int t = 0; t< colorBase.Length; t++) { colorBase[t] = new Color(1, 0, 0, 0); } newMaskTex.SetPixels(colorBase); //判断是否重名 bool exporNameSuccess = true; for(int num = 1; exporNameSuccess; num++) { string Next = Selection.activeTransform.name +"_"+ num; if (!File.Exists(ContolTexFolder + Selection.activeTransform.name + ".png")) { contolTexName = Selection.activeTransform.name; exporNameSuccess = false; } else if (!File.Exists(ContolTexFolder + Next + ".png")) { contolTexName = Next; exporNameSuccess = false; } } string path = ContolTexFolder + contolTexName + ".png"; byte[] bytes = newMaskTex.EncodeToPNG(); File.WriteAllBytes(path, bytes);//保存 AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);//导入资源 //Contol贴图的导入设置 TextureImporter textureIm = AssetImporter.GetAtPath(path) as TextureImporter; TextureImporterPlatformSettings texset = textureIm.GetDefaultPlatformTextureSettings(); texset.format = TextureImporterFormat.RGBA32; //texset.maxTextureSize=512; textureIm.SetPlatformTextureSettings(texset); textureIm.isReadable = true; textureIm.anisoLevel = 9; textureIm.mipmapEnabled = false; textureIm.wrapMode = TextureWrapMode.Clamp; AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);//刷新 setContolTex(path);//设置Contol贴图 } //设置Contol贴图 void setContolTex(string peth) { Texture2D ControlTex = (Texture2D)AssetDatabase.LoadAssetAtPath(peth, typeof(Texture2D)); Selection.activeTransform.gameObject.GetComponent<MeshRenderer>().sharedMaterial.SetTexture("_Control", ControlTex); } void Painter() { Transform CurrentSelect = Selection.activeTransform; MeshFilter temp = CurrentSelect.GetComponent<MeshFilter>();//获取当前模型的MeshFilter float orthographicSize = (brushSize * CurrentSelect.localScale.x) * (temp.sharedMesh.bounds.size.x / 200);//笔刷在模型上的正交大小 MaskTex = (Texture2D)CurrentSelect.gameObject.GetComponent<MeshRenderer>().sharedMaterial.GetTexture("_Control");//从材质球中获取Control贴图 brushSizeInPourcent = (int)Mathf.Round((brushSize * MaskTex.width) / 100);//笔刷在模型上的大小 bool ToggleF = false; Event e = Event.current;//检测输入 HandleUtility.AddDefaultControl(0); RaycastHit raycastHit = new RaycastHit(); Ray terrain = HandleUtility.GUIPointToWorldRay(e.mousePosition);//从鼠标位置发射一条射线 if (Physics.Raycast(terrain, out raycastHit, Mathf.Infinity, 1 << LayerMask.NameToLayer("ground")))//射线检测名为"ground"的层 { Handles.color = new Color(1f, 1f, 0f, 1f);//颜色 Handles.DrawWireDisc(raycastHit.point, raycastHit.normal, orthographicSize);//根据笔刷大小在鼠标位置显示一个圆 //鼠标点击或按下并拖动进行绘制 if ((e.type == EventType.MouseDrag && e.alt == false && e.control == false && e.shift == false && e.button == 0) || (e.type == EventType.MouseDown && e.shift == false && e.alt == false && e.control == false && e.button == 0 && ToggleF == false)) { //选择绘制的通道 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; } //targetColor = new Color(0f, 0f, 0f, 0f); Vector2 pixelUV = raycastHit.textureCoord; //计算笔刷所覆盖的区域 int PuX = Mathf.FloorToInt(pixelUV.x * MaskTex.width); int PuY = Mathf.FloorToInt(pixelUV.y * MaskTex.height); int x = Mathf.Clamp(PuX - brushSizeInPourcent / 2, 0, MaskTex.width - 1); int y = Mathf.Clamp(PuY - brushSizeInPourcent / 2, 0, MaskTex.height - 1); int width = Mathf.Clamp((PuX + brushSizeInPourcent / 2), 0, MaskTex.width) - x; int height = Mathf.Clamp((PuY + brushSizeInPourcent / 2), 0, MaskTex.height) - y; Color[] terrainBay = MaskTex.GetPixels(x, y, width, height, 0);//获取Control贴图被笔刷所覆盖的区域的颜色 Texture2D TBrush = brushTex[selBrush] as Texture2D;//获取笔刷性状贴图 float[] brushAlpha = new float[brushSizeInPourcent * brushSizeInPourcent];//笔刷透明度 //根据笔刷贴图计算笔刷的透明度 for (int i = 0; i < brushSizeInPourcent; i++) { for (int j = 0; j < brushSizeInPourcent; j++) { brushAlpha[j * brushSizeInPourcent + i] = TBrush.GetPixelBilinear(((float)i) / brushSizeInPourcent, ((float)j) / brushSizeInPourcent).a; } } //计算绘制后的颜色 for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int index = (i * width) + j; float Stronger = brushAlpha[Mathf.Clamp((y + i) - (PuY - brushSizeInPourcent / 2), 0, brushSizeInPourcent - 1) * brushSizeInPourcent + Mathf.Clamp((x + j) - (PuX - brushSizeInPourcent / 2), 0, brushSizeInPourcent - 1)] * brushStronger; terrainBay[index] = Color.Lerp(terrainBay[index], targetColor, Stronger); } } Undo.RegisterCompleteObjectUndo(MaskTex, "meshPaint");//保存历史记录以便撤销 MaskTex.SetPixels(x, y, width, height, terrainBay, 0);//把绘制后的Control贴图保存起来 MaskTex.Apply(); ToggleF = true; } if(e.type == EventType.MouseUp && e.alt == false && e.button == 0 && ToggleF == true) { SaveTexture();//绘制结束保存Control贴图 ToggleF = false; } } } public void SaveTexture() { var path = AssetDatabase.GetAssetPath(MaskTex); var bytes = MaskTex.EncodeToPNG(); File.WriteAllBytes(path, bytes); AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);//刷新 } }
Shader模块
Shader "zcxshaderlibrary/texBlendWithBump" { Properties { _SpecularColor("SpecularColor",Color)=(1,1,1,1) _Smoothness("Smoothness",range(0,20))=10 _Cutoff("Cutoff",float)=0.5 [Space(10)][Header(Layer)] [Space(30)][Header(Layer1Map)] _Splat0 ("Layer 1(RGBA)",2D) = "white"{} _Splat0_Color("_Splat1 Color",Color) = (1,1,1,1) _BumpSplat0 ("Layer 1 Normal(Bump)", 2D) = "Bump" {} _BumpSplat0Scale("Layer 1 Normal Scale",float)=1 [Space(30)][Header(Layer2Map)] _Splat1 ("Layer 2(RGBA)", 2D) = "white" {} _Splat1_Color("_Splat2 Color",Color) = (1,1,1,1) _BumpSplat1 ("Layer 2 Normal(Bump)", 2D) = "Bump" {} _BumpSplat1Scale("Layer 2 Normal Scale",float)=1 [Space(30)][Header(Layer3Map)] _Splat2 ("Layer 3(RGBA)", 2D) = "white" {} _Splat2_Color("_Splat3 Color",Color) = (1,1,1,1) _BumpSplat2 ("Layer 3 Normal(Bump)", 2D) = "Bump" {} _BumpSplat2Scale("Layer 3 Normal Scale",float)=1 [Space(30)][Header(Layer4Map)] _Splat3 ("Layer 4(RGBA)", 2D) = "white" {} _Splat3_Color("_Splat4 Color",Color) = (1,1,1,1) _BumpSplat3 ("Layer 4 Normal(Bump)", 2D) = "Bump" {} _BumpSplat3Scale("Layer 4 Normal Scale",float)=1 [Space(30)][Header(Blend Texture)] _Control ("Control (RGBA)", 2D) = "white" {} _Weight("Blend Weight" , Range(0.001,1)) = 0.2 } SubShader { Tags { "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" "RenderType"="Opaque" } HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float4 _Splat0_ST; float4 _Splat0_Color; float4 _SpecularColor; float4 _BumpSplat0_ST; float4 _Splat1_ST; float4 _Splat1_Color; float4 _BumpSplat1_ST; float4 _Splat2_ST; float4 _Splat2_Color; float4 _BumpSplat2_ST; float4 _Splat3_ST; float4 _Splat3_Color; float4 _BumpSplat3_ST; float4 _Control_ST; //float _Control; float _Weight; float _Smoothness; float _Cutoff; float _BumpSplat0Scale; float _BumpSplat1Scale; float _BumpSplat2Scale; float _BumpSplat3Scale; TEXTURE2D(_Splat0); SAMPLER(sampler_Splat0); TEXTURE2D(_Splat1); SAMPLER(sampler_Splat1); TEXTURE2D(_Splat2); SAMPLER(sampler_Splat2); TEXTURE2D(_Splat3); SAMPLER(sampler_Splat3); TEXTURE2D(_Control); SAMPLER(sampler_Control); TEXTURE2D(_BumpSplat0); SAMPLER(sampler_BumpSplat0); TEXTURE2D(_BumpSplat1); SAMPLER(sampler_BumpSplat1); TEXTURE2D(_BumpSplat2); SAMPLER(sampler_BumpSplat2); TEXTURE2D(_BumpSplat3); SAMPLER(sampler_BumpSplat3); CBUFFER_END float4 _BaseMap_ST; ENDHLSL Pass { Name "URPSimpleLit" Tags{"LightMode"="UniversalForward"} HLSLPROGRAM #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #pragma vertex vert #pragma fragment frag struct Attributes { float4 positionOS : POSITION; float4 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float2 uv2 : TEXCOORD2; float2 uv3 : TEXCOORD3; float2 uv4 : TEXCOORD4; }; struct Varings { float4 positionCS : SV_POSITION; float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float2 uv2 : TEXCOORD2; float2 uv3 : TEXCOORD3; float2 uv4 : TEXCOORD4; float3 positionWS : TEXCOORD5; float3 viewDirWS : TEXCOORD6; float3 normalWS : NORMAL_WS; //float3 normalWS1 : NORMAL_WS; float4 tangentWS : TANGENT_WS; }; inline float4 Blend(float depth1 ,float depth2,float depth3,float depth4 , float4 control) { float4 blend ; blend.r =depth1 * control.r; blend.g =depth2 * control.g; blend.b =depth3 * control.b; blend.a =depth4 * control.a; float 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); } Varings vert(Attributes IN) { Varings OUT; VertexPositionInputs positionInputs = GetVertexPositionInputs(IN.positionOS.xyz); VertexNormalInputs normalInputs = GetVertexNormalInputs(IN.normalOS.xyz); real sign = IN.tangentOS.w * GetOddNegativeScale(); OUT.positionCS = positionInputs.positionCS; OUT.positionWS = positionInputs.positionWS; OUT.viewDirWS = GetCameraPositionWS() - positionInputs.positionWS; OUT.normalWS = normalInputs.normalWS; OUT.tangentWS = real4(normalInputs.tangentWS, sign); OUT.uv0=TRANSFORM_TEX(IN.uv0,_Splat0); OUT.uv1=TRANSFORM_TEX(IN.uv1,_Splat1); OUT.uv2=TRANSFORM_TEX(IN.uv2,_Splat2); OUT.uv3=TRANSFORM_TEX(IN.uv3,_Splat3); OUT.uv4=TRANSFORM_TEX(IN.uv4,_Control); return OUT; } float4 frag(Varings IN):SV_Target { // float4 splat_control = SAMPLE_TEXTURE2D(_Control, sampler_Control, IN.uv4); //纹理贴图 float4 lay1 = SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, IN.uv0); float4 lay2 = SAMPLE_TEXTURE2D(_Splat1, sampler_Splat1, IN.uv1); float4 lay3 = SAMPLE_TEXTURE2D(_Splat2, sampler_Splat2, IN.uv2); float4 lay4 = SAMPLE_TEXTURE2D(_Splat3, sampler_Splat3, IN.uv3); //Bump贴图 float3 nor1TS = UnpackNormalScale(SAMPLE_TEXTURE2D (_BumpSplat0, sampler_BumpSplat0,IN.uv0),_BumpSplat0Scale); float3 nor2TS = UnpackNormalScale(SAMPLE_TEXTURE2D (_BumpSplat1, sampler_BumpSplat1,IN.uv1),_BumpSplat1Scale); float3 nor3TS = UnpackNormalScale(SAMPLE_TEXTURE2D (_BumpSplat2, sampler_BumpSplat2,IN.uv2),_BumpSplat2Scale); float3 nor4TS = UnpackNormalScale(SAMPLE_TEXTURE2D (_BumpSplat3, sampler_BumpSplat3,IN.uv3),_BumpSplat3Scale); //BaseColor附加到各个图层 lay1.rgb*=lay1.rgb*_Splat0_Color.rgb; lay2.rgb*=lay2.rgb*_Splat1_Color.rgb; lay3.rgb*=lay3.rgb*_Splat2_Color.rgb; lay4.rgb*=lay4.rgb*_Splat3_Color.rgb; //Normal计算 real sgn = IN.tangentWS.w; real3 bitangent = sgn * cross(IN.normalWS.xyz, IN.tangentWS.xyz); real3 nor1WS = mul(nor1TS, real3x3(IN.tangentWS.xyz, bitangent.xyz, IN.normalWS.xyz)); real3 nor2WS = mul(nor2TS, real3x3(IN.tangentWS.xyz, bitangent.xyz, IN.normalWS.xyz)); real3 nor3WS = mul(nor3TS, real3x3(IN.tangentWS.xyz, bitangent.xyz, IN.normalWS.xyz)); real3 nor4WS = mul(nor4TS, real3x3(IN.tangentWS.xyz, bitangent.xyz, IN.normalWS.xyz)); // 转换至世界空间 //计算混合 float4 blend = Blend(lay1.a,lay2.a,lay3.a,lay4.a,splat_control); float3 blendNor = nor1WS*blend.r+nor2WS*blend.g+nor3WS*blend.b+nor4WS*blend.a; //计算主光 Light light = GetMainLight(); float3 diffuse = LightingLambert(light.color, light.direction, IN.normalWS); float3 specular = LightingSpecular(light.color, light.direction, normalize(blendNor), normalize(IN.viewDirWS), _SpecularColor, _Smoothness); //计算附加光照 uint pixelLightCount = GetAdditionalLightsCount(); for (uint lightIndex = 0; lightIndex < pixelLightCount; ++lightIndex) { Light light = GetAdditionalLight(lightIndex, IN.positionWS); diffuse += LightingLambert(light.color, light.direction, IN.normalWS); specular += LightingSpecular(light.color, light.direction, normalize(blendNor), normalize(IN.viewDirWS), _SpecularColor, _Smoothness); } float3 basecolor=lay1.rgb *blend.r + lay2.rgb* blend.g + lay3.rgb * blend.b + lay4.rgb * blend.a;//混合 float4 ambient=float4(SampleSH(blendNor), 1.0)*(basecolor,0); float3 diff=saturate(dot(light.direction,blendNor))*basecolor; real3 viewDirectionWS = SafeNormalize(GetCameraPositionWS() - IN.positionWS); // safe防止分母为0 real3 h = SafeNormalize(viewDirectionWS + light.direction); float3 color=diff*diffuse+specular+ambient.rgb; clip(lay1.a*lay2.a*lay3.a*lay4.a-_Cutoff); return float4(color,0); } ENDHLSL } Pass { Name "ShadowCaster" Tags{"LightMode" = "ShadowCaster"} ZWrite On ZTest LEqual Cull[_Cull] HLSLPROGRAM // Required to compile gles 2.0 with standard srp library #pragma prefer_hlslcc gles #pragma exclude_renderers d3d11_9x #pragma target 2.0 // ------------------------------------- // Material Keywords #pragma shader_feature _ALPHATEST_ON #pragma shader_feature _GLOSSINESS_FROM_BASE_ALPHA //-------------------------------------- // GPU Instancing #pragma multi_compile_instancing #pragma vertex ShadowPassVertex #pragma fragment ShadowPassFragment //由于这段代码中声明了自己的CBUFFER,与我们需要的不一样,所以我们注释掉他 //#include "Packages/com.unity.render-pipelines.universal/Shaders/SimpleLitInput.hlsl" //它还引入了下面2个hlsl文件 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl" ENDHLSL } } }