Unity3D 中纯 Shader 的双色纹理的平铺计算与实现

发布于:2025-08-17 ⋅ 阅读:(15) ⋅ 点赞:(0)

我们不使用纹理贴图,通过Shader程序化生成条纹来实现黑黄双色纹理的平铺,目的是确保在任何尺寸下都不会变形。

1 创建自定义Shader

在Unity编辑器的Project窗口中:

  • 在【Assets】下创建【Shader】目录
  • 右键点击【Shader】目录,选择 【Create > Shader > Standard Surface Shader】
    在这里插入图片描述
  • 命名为 “TwoColorStripes.shader”

打开新创建的Shader文件,替换为以下代码:

Shader "Custom/TwoColorStripes"
{
    Properties
    {
        _ColorA ("Color A", Color) = (0,0,0,1)    // 黑色
        _ColorB ("Color B", Color) = (1,0.92,0.016,1) // 黄色(RGB:255,235,4)
        _StripeWidth ("Stripe Width", Float) = 0.5   // 每条条纹的宽度(单位:米)
        _Direction ("Direction", Vector) = (1,0,0,0) // 默认沿X轴方向
        [Toggle] _WorldSpace ("World Space", Float) = 0 // 是否使用世界坐标
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        
        #pragma target 3.0
        
        struct Input
        {
            float3 worldPos; // 世界坐标
        };

        fixed4 _ColorA;
        fixed4 _ColorB;
        float _StripeWidth;
        float4 _Direction;
        float _WorldSpace;

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // 计算条纹位置
            float position;
            
            if (_WorldSpace > 0.5)
            {
                // 使用世界坐标
                position = dot(IN.worldPos, _Direction.xyz);
            }
            else
            {
                // 使用物体本地坐标
                position = dot(IN.worldPos - unity_ObjectToWorld._m03_m13_m23, _Direction.xyz);
            }
            
            // 计算条纹模式
            float stripe = frac(position / _StripeWidth);
            
            // 选择颜色
            o.Albedo = (stripe < 0.5) ? _ColorA.rgb : _ColorB.rgb;
            o.Metallic = 0;
            o.Smoothness = 0;
            o.Alpha = 1;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

如果使用了 Universal Render Pipeline (URP) ,我们需要创建URP兼容的Shader:

Shader "Custom/URP_TwoColorStripes"
{
    Properties
    {
        _ColorA ("Color A", Color) = (0,0,0,1)
        _ColorB ("Color B", Color) = (1,0.92,0.016,1)
        _StripeWidth ("Stripe Width", Float) = 0.5
        _Direction ("Direction", Vector) = (1,0,0,0)
        [Toggle] _WorldSpace ("World Space", Float) = 0
    }
    
    SubShader
    {
        Tags 
        { 
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
            "Queue" = "Geometry"
        }
        
        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode" = "UniversalForward" }
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            
            struct Attributes
            {
                float4 positionOS   : POSITION;
                float3 normalOS     : NORMAL;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float3 positionWS   : TEXCOORD0;
                float3 normalWS     : TEXCOORD1;
            };

            CBUFFER_START(UnityPerMaterial)
                half4 _ColorA;
                half4 _ColorB;
                float _StripeWidth;
                float4 _Direction;
                float _WorldSpace;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                
                // 顶点位置变换
                VertexPositionInputs vertexInput = GetVertexPositionInputs(IN.positionOS.xyz);
                OUT.positionHCS = vertexInput.positionCS;
                OUT.positionWS = vertexInput.positionWS;
                
                // 法线变换
                VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS);
                OUT.normalWS = normalInput.normalWS;
                
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                // 计算条纹位置
                float position;
                
                if (_WorldSpace > 0.5)
                {
                    // 使用世界坐标
                    position = dot(IN.positionWS, _Direction.xyz);
                }
                else
                {
                    // 使用物体本地坐标
                    float3 objectPos = mul(unity_WorldToObject, float4(IN.positionWS, 1.0)).xyz;
                    position = dot(objectPos, _Direction.xyz);
                }
                
                // 计算条纹模式
                float stripe = frac(position / _StripeWidth);
                
                // 选择颜色
                half4 baseColor = (stripe < 0.5) ? _ColorA : _ColorB;
                
                // 基本光照计算
                Light mainLight = GetMainLight();
                float3 normal = normalize(IN.normalWS);
                float NdotL = saturate(dot(normal, mainLight.direction));
                half3 diffuse = mainLight.color * NdotL;
                
                // 组合最终颜色
                half3 color = baseColor.rgb * (diffuse + SampleSH(normal));
                return half4(color, baseColor.a);
            }
            ENDHLSL
        }
    }
    
    FallBack "Universal Render Pipeline/Simple Lit"
}

2 创建材质

在Project窗口中:

  • 右键点击【Create > Material】
  • 命名为 “Mat_BlackYellowStripes”
  • 在 Material 的 Inspector,将Shader设置为 “Custom/TwoColorStripes”
    在这里插入图片描述
  • 按需调整材质属性:Color A为黑色 (0,0,0),Color B为黄色 (RGB: 255/255, 235/255, 4/255),Stripe Width为0.54
    在这里插入图片描述

3 创建控制脚本

  • 创建新的C#脚本 “StripeController.cs”:
    在这里插入图片描述
using UnityEngine;

[RequireComponent(typeof(Renderer))]
public class StripeController : MonoBehaviour
{
    [Header("条纹设置")]
    public int stripeCount = 8; // 条纹总数(偶数)
    public Vector3 direction = Vector3.right; // 条纹方向

    [Header("调试选项")]
    public bool updateInRuntime = true;

    private Material stripeMaterial;
    private Vector3 lastScale;

    void Start()
    {
        InitializeMaterial();
        UpdateStripeProperties();
    }

    void Update()
    {
        if (updateInRuntime && transform.hasChanged)
        {
            UpdateStripeProperties();
            transform.hasChanged = false;
        }
    }

    private void InitializeMaterial()
    {
        Renderer renderer = GetComponent<Renderer>();

        // 创建材质实例(确保每个物体独立)
        stripeMaterial = new Material(renderer.sharedMaterial);
        renderer.material = stripeMaterial;
    }

    public void UpdateStripeProperties()
    {
        if (stripeMaterial == null) return;

        // 获取物体边界尺寸
        Bounds bounds = GetComponent<Renderer>().bounds;
        Vector3 size = bounds.size;

        // 计算实际长度(沿条纹方向)
        float length = Vector3.Dot(size, direction);

        // 计算条纹宽度
        float stripeWidth = length / stripeCount;

        // 更新材质属性
        stripeMaterial.SetFloat("_StripeWidth", stripeWidth);
        stripeMaterial.SetVector("_Direction", direction);

        // 可选:自动确定使用世界坐标还是本地坐标
        bool useWorldSpace = (transform.parent != null);
        stripeMaterial.SetFloat("_WorldSpace", useWorldSpace ? 1 : 0);

        Debug.Log($"更新条纹: 长度={length:F2}m, 条纹数={stripeCount}, 宽度={stripeWidth:F2}m");
    }

    // 针对特定尺寸的快捷设置
    public void Apply432Setup()
    {
        // 设置条纹数量为8(4黑+4黄)
        stripeCount = 8;

        // 默认沿X轴(物体长度方向)
        direction = Vector3.right;

        UpdateStripeProperties();
    }

    [ContextMenu("手动更新条纹设置")]
    public void ManualUpdate()
    {
        UpdateStripeProperties();
    }
}

4 将材质和脚本应用到物体

  • 在Hierarchy中创建一个选择您的踢脚线物体
    在这里插入图片描述

  • 给物添加组件:
    将 “Mat_BlackYellowStripes” 材质拖到Renderer组件的Material槽
    在这里插入图片描述

  • 添加 “StripeController” 脚本组件
    在这里插入图片描述

  • 配置脚本参数:
    设置Stripe Count为8(4条黑+4条黄)
    设置Direction为物体长度方向(通常是X轴:1,0,0)

  • 点击运行测试:
    物体应显示等宽的黑黄交替条纹
    修改物体缩放时,条纹会自动调整
    在这里插入图片描述

5 可视化配置

  • 在Project中的【Assets】目录下创建【Editor】目录
  • 创建编辑器扩展脚本 “StripeControllerEditor.cs”:
    在这里插入图片描述
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(StripeController))]
public class StripeControllerEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        StripeController controller = (StripeController)target;

        GUILayout.Space(10);

        // 预览颜色
        EditorGUILayout.LabelField("颜色预览", EditorStyles.boldLabel);
        Material mat = controller.GetComponent<Renderer>().sharedMaterial;
        if (mat != null)
        {
            Color colorA = mat.GetColor("_ColorA");
            Color colorB = mat.GetColor("_ColorB");

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.ColorField(colorA);
            EditorGUILayout.ColorField(colorB);
            EditorGUILayout.EndHorizontal();
        }

        // 操作按钮
        GUILayout.Space(10);
        if (GUILayout.Button("应用设置", GUILayout.Height(30)))
        {
            controller.ManualUpdate();
        }

        // 针对4.32m物体的快捷按钮
        GUILayout.Space(5);
        if (GUILayout.Button("应用4.32m预设"))
        {
            controller.Apply432Setup();
        }

        // 显示当前条纹信息
        GUILayout.Space(10);
        EditorGUILayout.LabelField("当前条纹设置", EditorStyles.boldLabel);
        if (mat != null)
        {
            float stripeWidth = mat.GetFloat("_StripeWidth");
            Vector3 direction = mat.GetVector("_Direction");
            EditorGUILayout.LabelField($"条纹宽度: {stripeWidth:F2}m");
            EditorGUILayout.LabelField($"方向: ({direction.x:F1}, {direction.y:F1}, {direction.z:F1})");
        }
    }
}
#endif

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到