c# - Unity Shader - 如何有效地重新着色特定坐标?

标签 c# unity-game-engine shader hlsl cg

首先,请允许我解释一下我所得到的内容,然后我将讨论接下来我想要弄清楚的内容。

我有什么

我有一个带纹理的自定义网格,其一些边缘与 Unity 中的整数世界坐标完全对齐。在网格中,我添加了自己的粗略但有效的自定义表面着色器,如下所示:

    Shader "Custom/GridHighlightShader"
{
    Properties
    {
        [HideInInspector]_SelectionColor("SelectionColor", Color) = (0.1,0.1,0.1,1)
        [HideInInspector]_MovementColor("MovementColor", Color) = (0,0.205,1,1)
        [HideInInspector]_AttackColor("AttackColor", Color) = (1,0,0,1)
        [HideInInspector]_GlowInterval("_GlowInterval", float) = 1
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
    }
        SubShader
        {
            Tags { "RenderType" = "Opaque" }
            LOD 200

            CGPROGRAM
            // Physically based Standard lighting model, and enable shadows on all light types
            #pragma surface surf Standard fullforwardshadows

            // Use shader model 3.0 target, to get nicer looking lighting
            #pragma target 3.0

            struct Input
            {
                float2 uv_MainTex;
                float3 worldNormal;
                float3 worldPos;
            };

            sampler2D _MainTex;
            half _Glossiness;
            half _Metallic;
            fixed4 _SelectionColor;
            fixed4 _MovementColor;
            fixed4 _AttackColor;
            half _GlowInterval;
            half _ColorizationArrayLength = 0;
            float4 _ColorizationArray[600];
            half _isPixelInColorizationArray = 0;

            // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
            // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
            // #pragma instancing_options assumeuniformscaling
            UNITY_INSTANCING_BUFFER_START(Props)
                // put more per-instance properties here
            UNITY_INSTANCING_BUFFER_END(Props)

                void surf(Input IN, inout SurfaceOutputStandard o)
                {
                fixed4 c = tex2D(_MainTex, IN.uv_MainTex);

                // Update only the normals facing up and down
                if (abs(IN.worldNormal.x) <= 0.5 && (abs(IN.worldNormal.z) <= 0.5))
                {
                    // If no colors were passed in, reset all of the colors
                    if (_ColorizationArray[0].w == 0)
                    {
                        _isPixelInColorizationArray = 0;
                    }
                    else
                    {
                        for (int i = 0; i < _ColorizationArrayLength; i++)
                        {
                            if (abs(IN.worldPos.x) >= _ColorizationArray[i].x && abs(IN.worldPos.x) < _ColorizationArray[i].x + 1
                                && abs(IN.worldPos.z) >= _ColorizationArray[i].z && abs(IN.worldPos.z) < _ColorizationArray[i].z + 1
                                )
                            {
                                _isPixelInColorizationArray = _ColorizationArray[i].w;
                            }
                        }
                    }

                    if (_isPixelInColorizationArray > 0)
                    {
                        if (_isPixelInColorizationArray == 1)
                        {
                            c = tex2D(_MainTex, IN.uv_MainTex) + (_SelectionColor * _GlowInterval) - 1;
                        }
                        else if (_isPixelInColorizationArray == 2)
                        {
                            c = tex2D(_MainTex, IN.uv_MainTex) + (_MovementColor * _GlowInterval);
                        }
                        else if (_isPixelInColorizationArray == 3)
                        {
                            c = tex2D(_MainTex, IN.uv_MainTex) + (_AttackColor * _GlowInterval);
                        }
                    }
                }
                o.Albedo = c.rgb;
                o.Metallic = _Metallic;
                o.Smoothness = _Glossiness;
                o.Alpha = c.a;

            }
            ENDCG
        }
            FallBack "Diffuse"
}

我将一个 float 输入到着色器中,该 float 随着时间的推移使用一些数学方法在 2 到 3 之间振荡,这是通过 Unity 中的一个简单更新函数完成的:

private void Update() 
{
    var t = (2 + ((Mathf.Sin(Time.time))));
    meshRenderer.material.SetFloat("_GlowInterval", t);
}

我还向着色器提供一个名为 _ColorizationArray 的 Vector4 数组,该数组存储 0 到 600 个坐标,每个坐标代表要在运行时着色的图 block 。这些图 block 可能会或可能不会突出显示,具体取决于它们在运行时的选择模式值。这是我用来执行此操作的方法:

    public void SetColorizationCollectionForShader()
    {
        var coloredTilesArray = Battlemap.Instance.tiles.Where(x => x.selectionMode != TileSelectionMode.None).ToArray();

        // https://docs.unity3d.com/ScriptReference/Material.SetVectorArray.html                
        // Set the tile count in the shader's own integer variable
        meshRenderer.material.SetInt("_ColorizationArrayLength", coloredTilesArray.Length);

        // Loop through the tiles to be colored only and grab their world coordinates
        for(int i = 0; i < coloredTilesArray.Length; i++)
        {
            // Also grab the selection mode as the w value of a float4
            colorizationArray[i] = new Vector4(coloredTilesArray[i].x - Battlemap.HALF_TILE_SIZE, coloredTilesArray[i].y, coloredTilesArray[i].z - Battlemap.HALF_TILE_SIZE, (float)coloredTilesArray[i].selectionMode);            
        }

        // Feed the overwritten array into the shader
        meshRenderer.material.SetVectorArray("_ColorizationArray", colorizationArray);
    }

结果是这组蓝色发光图 block 在运行时动态设置和更改:

enter image description here

我所有这一切的目标是突出显示网格上的方 block (或图 block ,如果你愿意的话),作为基于网格的战术游戏的一部分,其中单位可以移动到突出显示区域内的任何图 block 。每个单位移动后,它就会受到攻击,其中的图 block 会突出显示为红色,然后轮到下一个单位,依此类推。由于我预计人工智能、运动计算和粒子效果会占用大部分处理时间,因此我需要在运行时动态且非常高效地突出显示图 block 。

我接下来想做什么

哇,好吧。现在,如果您了解有关着色器的任何信息(我当然不知道,我昨天才开始查看 cg 代码),您可能会想“天哪,真是低效的困惑。你在做什么?!如果语句?!在着色器?”我不会责怪你。

我真正想做的事情几乎是一样的,只是效率更高。使用特定的图 block 索引,我想告诉着色器“将这些图 block 内部的表面着色为蓝色,并且仅将这些图 block 着色”,并以对 GPU 和 CPU 都有效的方式执行此操作。

我怎样才能实现这个目标?我已经在 C# 代码中计算图 block 世界坐标并将坐标提供给着色器,但除此之外我不知所措。我意识到我也许应该切换到顶点/片段着色器,但如果可能的话,我也想避免丢失网格上的任何默认动态照明。

此外,是否有一种类型的变量允许着色器使用本地网格坐标而不是世界坐标将网格着色为蓝色?如果能够移动网格而不必担心着色器代码,那就太好了。

编辑:在发布此问题后的两周内,我通过传入一个半 Vector4 数组来表示要实际处理的数组量_ColorizationArrayLength 来编辑着色器,它效果很好,但效率并不高——这会产生 GPU 峰值,在相当现代的显卡上处理需要大约 17 毫秒。我更新了上面的着色器代码以及原始问题的部分内容。

最佳答案

由于您的着色只关心大小相同的正方形网格中的 2d 位置,这些正方形都与同一网格对齐,因此我们可以传入一个 2d 纹理,其颜色表示地面的颜色应该是什么样子。

在着色器中,添加一个 2D _ColorizeMap 和一个 Vector _WorldSpaceRange。该贴图将用于传递应该对世界的哪些部分进行着色,并且范围将告诉着色器如何在世界空间和 UV(纹理)空间之间进行转换。由于游戏网格与世界 x/y 轴对齐,因此我们可以将坐标从世界空间线性缩放到 UV 空间。

然后,当法线朝上时(您可以检查法线的 y 是否足够高),获取世界位置的逆 lerp,并从 _ColorizeMap 进行采样以获取如何/是否它应该是彩色的。

Shader "Custom/GridHighlightShader"
{
    Properties
    {
        [HideInInspector]_GlowInterval("_GlowInterval", float) = 1
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
        [HideInInspector]_ColorizeMap("Colorize Map", 2D) = "black" {} 
        _WorldSpaceRange("World Space Range", Vector) = (0,0,100,100)
    }
        SubShader
        {
            Tags { "RenderType" = "Opaque" }
            LOD 200

            CGPROGRAM
            // Physically based Standard lighting model, 
            // and enable shadows on all light types
            #pragma surface surf Standard fullforwardshadows

            // Use shader model 3.0 target, to get nicer looking lighting
            #pragma target 3.0

            struct Input
            {
                float2 uv_MainTex;
                float3 worldNormal;
                float3 worldPos;
            };

            sampler2D _MainTex;
            half _Glossiness;
            half _Metallic;
            half _GlowInterval;

            sampler2D _ColorizeMap;
            fixed4 _WorldSpaceRange;


            // Add instancing support for this shader. 
            // You need to check 'Enable Instancing' on materials that use the shader.
            // See https://docs.unity3d.com/Manual/GPUInstancing.html 
            // for more information about instancing.
            // #pragma instancing_options assumeuniformscaling
            UNITY_INSTANCING_BUFFER_START(Props)
                // put more per-instance properties here
            UNITY_INSTANCING_BUFFER_END(Props)

            void surf(Input IN, inout SurfaceOutputStandard o)
            {
                fixed4 c = tex2D(_MainTex, IN.uv_MainTex);

                // Update only the normals facing up and down
                if (abs(IN.worldNormal.y) >= 0.866)) // abs(y) >= sin(60 degrees)
                {
                    fixed4 colorizedMapUV = (IN.worldPos.xz-_WorldSpaceRange.xy) 
                            / (_WorldSpaceRange.zw-_WorldSpaceRange.xy);

                    half4 colorType = tex2D(_ColorizeMap, colorizedMapUV);

                    c = c + (colorType * _GlowInterval); 
                }
                o.Albedo = c.rgb;
                o.Metallic = _Metallic;
                o.Smoothness = _Glossiness;
                o.Alpha = c.a;

            }
            ENDCG
        }
    FallBack "Diffuse"
}

并删除分支:

Shader "Custom/GridHighlightShader"
{
    Properties
    {
        [HideInInspector]_GlowInterval("_GlowInterval", float) = 1
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
        [HideInInspector]_ColorizeMap("Colorize Map", 2D) = "black" {}
        _WorldSpaceRange("World Space Range", Vector) = (0,0,100,100)
    }
        SubShader
        {
            Tags { "RenderType" = "Opaque" }
            LOD 200
            CGPROGRAM
            // Physically based Standard lighting model, 
            // and enable shadows on all light types
            #pragma surface surf Standard fullforwardshadows

            // Use shader model 3.0 target, to get nicer looking lighting
            #pragma target 3.0

            struct Input
            {
                float2 uv_MainTex;
                float3 worldNormal;
                float3 worldPos;
            };

            sampler2D _MainTex;
            half _Glossiness;
            half _Metallic;
            half _GlowInterval;

            sampler2D _ColorizeMap;
            fixed4 _WorldSpaceRange;

            // Add instancing support for this shader.
            // You need to check 'Enable Instancing' on materials that use the shader.
            // See https://docs.unity3d.com/Manual/GPUInstancing.html 
            // for more information about instancing.
            // #pragma instancing_options assumeuniformscaling
            UNITY_INSTANCING_BUFFER_START(Props)
                // put more per-instance properties here
            UNITY_INSTANCING_BUFFER_END(Props)

            void surf(Input IN, inout SurfaceOutputStandard o)
            {

                half4 c = tex2D(_MainTex, IN.uv_MainTex);
                float2 colorizedMapUV = (IN.worldPos.xz - _WorldSpaceRange.xy)
                        / (_WorldSpaceRange.zw - _WorldSpaceRange.xy);
                half4 colorType = tex2D(_ColorizeMap, colorizedMapUV);

                // abs(y) >= sin(60 degrees) = 0.866
                c = c + step(0.866, abs(IN.worldNormal.y)) * colorType * _GlowInterval;

                o.Albedo = c.rgb;
                o.Metallic = _Metallic;
                o.Smoothness = _Glossiness;
                o.Alpha = c.a;

            }
            ENDCG
        }
            FallBack "Diffuse"
}

然后在您的 C# 代码中,创建一个不进行过滤的纹理。开始将纹理全黑,然后根据突出显示的方式向纹理添加颜色。另外,告诉着色器颜色贴图表示的世界空间范围(minX,minZ,maxX,maxZ):

    public void SetColorizationCollectionForShader()
{   
    Color[] selectionColors = new Color[4] { Color.clear, new Color(0.5f, 0.5f, 0.5f, 0.5f), Color.blue, Color.red };
    float leftMostTileX = 0f + Battlemap.HALF_TILE_SIZE;
    float backMostTileZ = 0f + Battlemap.HALF_TILE_SIZE;

    float rightMostTileX = leftMostTileX + (Battlemap.Instance.GridMaxX - 1)
            * Battlemap.TILE_SIZE;
    float forwardMostTileZ = backMostTileZ + (Battlemap.Instance.GridMaxZ - 1)
            * Battlemap.TILE_SIZE;

    Texture2D colorTex = new Texture2D(Battlemap.Instance.GridMaxX, Battlemap.Instance.GridMaxZ);
    colorTex.filterMode = FilterMode.Point;

    Vector4 worldRange = new Vector4(
            leftMostTileX - Battlemap.HALF_TILE_SIZE,
            backMostTileZ - Battlemap.HALF_TILE_SIZE,
            rightMostTileX + Battlemap.HALF_TILE_SIZE,
            forwardMostTileZ + Battlemap.HALF_TILE_SIZE);

    meshRenderer.material.SetVector("_WorldSpaceRange", worldRange);        

    // Loop through the tiles to be colored only and grab their world coordinates
    for (int i = 0; i < Battlemap.Instance.tiles.Length; i++)
    {
        // determine pixel index from position
        float xT = Mathf.InverseLerp(leftMostTileX, rightMostTileX,
                Battlemap.Instance.tiles[i].x);
        int texXPos = Mathf.RoundToInt(Mathf.Lerp(0f, Battlemap.Instance.GridMaxX - 1.0f, xT));

        float yT = Mathf.InverseLerp(backMostTileZ, forwardMostTileZ,
                Battlemap.Instance.tiles[i].z);
        int texYPos = Mathf.RoundToInt(Mathf.Lerp(0f, Battlemap.Instance.GridMaxZ - 1.0f, yT));

        colorTex.SetPixel(texXPos, texYPos, selectionColors[(int)Battlemap.Instance.tiles[i].selectionMode]);
    }
    colorTex.Apply();

    // Feed the color map into the shader
    meshRenderer.material.SetTexture("_ColorizeMap", colorTex);
}

图 block 的边界可能存在一些不稳定,并且纹理空间/世界空间之间可能存在一些对齐问题,但这应该可以帮助您入门。

关于c# - Unity Shader - 如何有效地重新着色特定坐标?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57703169/

相关文章:

c# - Entity Framework 重复主键

c# - 在列表框中显示图像会锁定文件

xcode4 - 如何在unity3d上使用xcode游戏

unity-game-engine - Unity 中采集卡的媒体,带有 Unity 的 Vlc 插件

unity3d - 透明着色器允许下面的对象显示在上面

c++ - OpenGL 着色器有时可以编译,有时则不能

C# 将一维数组转换为二维

c# - Unity - 无法访问对另一个脚本的引用

android - 形状的渲染顺序

c# - Fluent Validation 验证器在添加验证代码之前就会导致错误