首先,请允许我解释一下我已经得到的内容,然后我将介绍我接下来要解决的问题。
我已经得到的内容
我拥有一个在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"
}
我将一个浮点数输入到着色器中,这个浮点数会随着时间使用一些数学方法在2和3之间简单地振荡。这是从Unity中的一个简单更新函数中完成的:
private void Update()
{
var t = (2 + ((Mathf.Sin(Time.time))));
meshRenderer.material.SetFloat("_GlowInterval", t);
}
我还向着色器提供了一个名为_ColorizationArray的Vector4数组,其中存储了0到600个坐标,每个坐标代表运行时要着色的一个图块。这些图块在运行时可能会根据它们的selectionMode值而被高亮或不被高亮。以下是我使用的方法:
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);
}
这就是在运行时动态设置和更改的蓝色发光瓷砖集合的结果:
接下来,我想做的是:使用特定的瓦片索引告诉着色器“只在这些瓦片内部着色为蓝色”,并以对GPU和CPU都高效的方式完成此操作。嗯,好的。现在,如果你对着色器有所了解(而我昨天才开始看cg代码),你可能会想,“哦,天啊,这多么低效的代码!你在干什么?!还有if语句?!在着色器里?”我也不怪你。
我应该如何实现这一点?我已经在C#代码中计算瓦片世界坐标并将其提供给着色器,但除此之外,我一无所知。我意识到我应该可能切换到顶点/片段着色器,但我也想尽可能避免失去网格上的任何默认动态照明。
此外,是否有一种类型的变量可以使着色器使用局部网格坐标而不是世界坐标来将网格涂成蓝色?如果可能的话,能够移动网格而无需担心着色器代码会很好。
编辑:在发布此问题的两周内,我通过传递Vector4数组和half来表示实际处理数组的数量
_ColorizationArrayLength
,来编辑了着色器,它工作得很好,但几乎没有更高效 - 这会产生GPU峰值,需要大约17ms才能在相当现代的图形卡上处理。我已更新上面的着色器代码以及原始问题的某些部分。
tex2d
的重复调用并消除了分支。但作为完全避免在着色器中迭代的替代方案,我提交了一个使用纹理采样而不是迭代索引的答案。这应该在着色器中更快。 - Ruzihm