接上回 Rounded Cube 3.本章主题如下:

  • Create a custom shader. 使用自定义shader

此为本人阅读笔记不作为转载处理,详细还请参看原文. 原文地址

虽然我们现在可以区分面,但我们仍然没有纹理坐标。 假设我们想在整个立方体上显示一个网格模式,以便我们可以看到单个四边形。 我们怎样才能做到这一点?

贴图(纹理)覆盖四边形

我们可以使用自定义着色器来找出如何应用纹理,而不是将UV坐标存储在网格中。 这是一个刚创建的着色器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Shader "Custom/Rounded Cube Grid" {
Properties {
_Color ("Color", Color) = (1,1,1,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
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

这是默认的表面着色器。 重要的是它定义了一个输入结构,它要求主纹理的坐标。 这些坐标在surf函数中使用,为每个渲染片段调用。 由于我们没有这样的坐标,我们必须用其他的东西替换uv_MainTex。– Google这段翻译的真好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Input {
float2 cubeUV;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D(_MainTex, IN.cubeUV) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
}

由于每个顶点定义了UV,所以我们必须添加一个每个顶点调用的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex:vert
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 cubeUV;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
void vert (inout appdata_full v, out Input o) {
UNITY_INITIALIZE_OUTPUT(Input, o);
}
void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D(_MainTex, IN.cubeUV) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG

为了检查我们的着色器是否工作,直接使用顶点位置的XY坐标作为UV。

1
2
3
4
void vert (inout appdata_full v, out Input o) {
UNITY_INITIALIZE_OUTPUT(Input, o);
o.cubeUV = v.vertex.xy;
}

使用xy作为UV
使用xy作为UV

这对于Z面来说是合理的,但其他的却是一团糟。 我们需要为它们使用不同的顶点坐标。 所以我们有一个选择,我们可以通过添加关键字枚举着色器属性来支持。

这里划重点了 使用关键枚举来决定渲染不同的面,背后的工作原理是:枚举对应的宏定义

1
2
3
4
5
6
7
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
[KeywordEnum(X, Y, Z)] _Faces ("Faces", Float) = 0
}

可以通过定义关键字义,使我们能够为每个选项编写不同的代码。

1
2
3
4
5
6
7
8
9
10
void vert (inout appdata_full v, out Input o) {
UNITY_INITIALIZE_OUTPUT(Input, o);
#if defined(_FACES_X)
o.cubeUV = v.vertex.yz;
#elif defined(_FACES_Y)
o.cubeUV = v.vertex.xz;
#elif defined(_FACES_Z)
o.cubeUV = v.vertex.xy;
#endif
}

每种材质使用不同的坐标

刚开始看起来不错,但网格线不适合实际四边形。 更糟糕的是,当我们使用世界空间顶点位置时,移动或旋转立方体时会变得很奇怪。

在四舍五入之前,我们需要原始立方体的顶点位置。 如果我们可以将它们存储在网格中,我们可以将它们传递给着色器。 由于我们不使用顶点颜色,因此我们可以使用顶点颜色通道来达到此目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private Color32[] cubeUV;
private void CreateVertices () {
int cornerVertices = 8;
int edgeVertices = (xSize + ySize + zSize - 3) * 4;
int faceVertices = (
(xSize - 1) * (ySize - 1) +
(xSize - 1) * (zSize - 1) +
(ySize - 1) * (zSize - 1)) * 2;
vertices = new Vector3[cornerVertices + edgeVertices + faceVertices];
normals = new Vector3[vertices.Length];
cubeUV = new Color32[vertices.Length];
mesh.vertices = vertices;
mesh.normals = normals;
mesh.colors32 = cubeUV;
}
private void SetVertex (int i, int x, int y, int z) {
normals[i] = (vertices[i] - inner).normalized;
vertices[i] = inner + normals[i] * roundness;
cubeUV[i] = new Color32((byte)x, (byte)y, (byte)z, 0);
}

这里我们必须使用Color32来代替通常的Color类型,因为顶点颜色组件被存储为单个字节。 整个颜色是四个字节,与单个浮点大小相同。

如果我们使用常规颜色,那么Unity将从0-1浮点数转换为0-255字节,截断该范围之外的所有内容。 通过直接转换为字节,我们可以处理多达255的多维数据集,这应该是足够的。

在着色器方面,我们现在可以使用顶点颜色而不是其位置。 当着色器将顶点颜色通道解释为0-1范围内的值时,我们必须通过乘以255来撤消此转换。

1
2
3
4
5
6
7
8
9
10
void vert (inout appdata_full v, out Input o) {
UNITY_INITIALIZE_OUTPUT(Input, o);
#if defined(_FACES_X)
o.cubeUV = v.color.yz * 255;
#elif defined(_FACES_Y)
o.cubeUV = v.color.xz * 255;
#elif defined(_FACES_Z)
o.cubeUV = v.color.xy * 255;
#endif
}

网格使用原始立方体位置

我们终于有了一个功能性的网格纹理。 请注意,每对面之一的UV坐标是镜像的,但这不是重点,因为我们使用的是对称纹理。