脚本中开启 HDR Color

1
2
3
4
5
6
[GradientUsage(true)] //添加HDR类型的颜色条
public Gradient Gradient;

[ColorUsageAttribute(true, true)] //添加HDR类型的颜色
public Color Color;

材质属性

材质属性在 C# 代码中通过 MaterialProperty 类进行表示。

要访问 HLSL 代码中定义的变量,可以调用 Material.GetFloatMaterial.SetFloat。还有其他类似的方法;请参阅材质 API 文档以获取完整列表。使用这些 API 访问 HLSL 变量时,变量是否为材质属性并不重要。

在 Unity 编辑器中,可以控制材质属性在 Inspector 窗口中的显示方式。为此,最简单的方法是使用 MaterialPropertyDrawer。对于更复杂的需求,可以使用 MaterialEditorMaterialProperty 和 ShaderGUI 类。有关为着色器创建自定义 GUI 的更多信息,请参阅 ShaderLab:分配自定义编辑器

方法名称 用途
SetColor 更改材质的颜色
SetFloat 设置浮点值
SetInteger 在材质中设置整数值
SetTexture 为材质分配新纹理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 设置贴图
Texture mTexture = Resources.Load("myTexture", typeof(Texture )) as Texture;
material.SetTexture("_MainTex", mTexture );
// 设置整数
material.SetInt("_Int", 1);
// 设置浮点
material.SetFloat("_Float", 0.1f);
// 设置颜色 rgba
material.SetColor("_Color", Color.white);
// 设置向量 xyzw
material.SetVector("_Vector", new Vector4());
// shader的正常接口是没有bool类型的。但通过#pragma multi_compile __ UNITY_name可以实现
// 设置shader的UNITY_name为true
material.EnableKeyword("UNITY_name");
// 设置shader的UNITY_name为true
material.DisableKeyword("UNITY_name");

material 和 shader

1 直接设置 Material 文件

使用 unity 编辑器直接定位包含目标 shader 的 Material,并在脚本中访问。所有引入该 Material 的物体都会发生改变.
1,在脚本中建立公共 Material 参数
2,将脚本绑定到某物体上
3,在 unity edtor 中找到包含 Shader 的 Material 文件并拖拽到刚才的脚本对应参数上

1
2
3
4
5
6
7
public class shaderController : MonoBehaviour
{
public Material material;
void Update() {
material.SetFloat("_Float", Mathf.Sin(Time.frameCount * 0.01f));
}
}

2 设置资源中的 Material 文件

直接设置 Material 文件中的 Shader,所有引入该 Material 的物体都会发生改变
目标 Material 必须在 Asset/Resources 文件夹下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class shaderController : MonoBehaviour
{
public Material material;
void Start()
{
// typeof(Material)是为了防止不同类型文件重名
material = Resources.Load("rshader", typeof(Material)) as Material;
}

void Update()
{
//设置浮点数值
material.SetFloat("_Float", Mathf.Sin(Time.frameCount * 0.01f));
}
}

3 通过指定 shader 设置

该设置只会影响带有当前新建的 Material 的物体,不会影响其他带有该 shader 的物体。
1,读取 shader 资源
2,在内存中创建 Material 并设置相关参数
3,将新建的 Material 赋给目标物体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class shaderController : MonoBehaviour
{
public Material material;
void Start() {
// 获取物体的MeshRenderer组件
MeshRenderer mr = GameObject.Find("Cube222").GetComponent<MeshRenderer> ();
// 根据渲染器路径找到渲染器。跟资源目录无关
Shader shader = Shader.Find("Custom/3d");
// 新建Material
material = new Material(shader);
// 将新材质赋给物体
mr.material = material;
}

void Update() {
material.SetFloat("_Float", Mathf.Sin(Time.frameCount * 0.1f));
}
}

4 通过目标物体直接设置,影响所有的物体

该设置会影响所有引用与该物体相同 Material 的物体
1,获取目标物体
2,获取物体 sharedMaterial 属性
3,设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class shaderController : MonoBehaviour
{
public MeshRenderer mr;
void Start() {
// 获取包含Material的MeshRenderer组件
mr = GameObject.Find("Cube222").GetComponent<MeshRenderer> ();
}

void Update() {
//sharedMaterial代表设置原始的Material,所有引用该材质的对象都会被影响。
//material代表当前的Material,实际上它等于在内存中新建了一个Mertairl。如果不手动释放它会造成内存泄漏。该修改只会影响当前对象
mr.sharedMaterial.SetFloat("_Float", Mathf.Sin(Time.frameCount * 0.1f));
}
}

5 通过目标物体直接设置,只影响当前的物体

该设置只会影响当前设置 Material 的物体
1,获取目标物体
2,获取物体 Material 属性
3,设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class shaderController : MonoBehaviour
{
public MeshRenderer mr;
void Start() {
// 获取包含Material的MeshRenderer组件
mr = GameObject.Find("Cube222").GetComponent<MeshRenderer> ();
}

void Update() {
//sharedMaterial代表设置原始的Material,所有引用该材质的对象都会被影响。
//material代表当前的Material,实际上它等于在内存中新建了一个Mertairl。如果不手动释放它会造成内存泄漏。该修改只会影响当前对象
//material赋值的过程:
//1.Material m = mr.materia;
//2.设置m.set....;
//3.mr.material = m;
mr.material.SetFloat("_Float", Mathf.Sin(Time.frameCount * 0.1f));
}
}

注意

如果设置无效要注意以下问题

  1. 目标 shader 的渲染器路径是否有重名。
  2. 如果是通过资源文件读取,需要确保它在 Asset/Resources 文件夹下
  3. 区分 material 和 share material 的区别。实际上直接设置 material 等于实例化了一个新的 materaial 并赋给对象,如果不手动销毁,这个新的 material 会一直在内存中驻留。而 sharematerial 则是修改已有的 material,但这个修改会改变所有引用了该 material 的对象。这点务必搞清楚。

脚本控制

子着色器标签

Material.GetTag :读取子着色器标签

1
2
3
4
5
6
7
8
9
// 将此附加到具有渲染器组件的游戏对象
string tagName = "RenderType"; //标签的键名

void Start()
{
Renderer myRenderer = GetComponent<Renderer>();
string tagValue = myRenderer.material.GetTag(ExampleTagName, true, "Tag not found"); //返回标签的值
Debug.Log(tagValue);
}

修改 Queue 标签

1
2
3
4
5
6
7
8
9
10
11
public Shader shader;
public Material material;

void Start()
{
//设置材质球的renderQueue
material.renderQueue = (int)RenderQueue.Geometry;

Debug.Log(material.renderQueue); //读取材质球的renderQueue
//Debug.Log(shader.renderQueue); //读取shader文件中的renderQueue,可读不可写
}

Pass

Material.FindPass:返回通道 passName 的索引,如果不存在,则为 -1

Material.GetPassName 返回索引 pass 处的着色器通道的名称。如果通道不存在,则它返回空字符串。
ShaderData. Pass. Name:此通道的名称
 注意:Material.GetShaderPassEnabled 和 Material.SetShaderPassEnabled 不按名称引用通道;而是使用 LightMode 标签的值引用通道。

Pass 专用 Tags:
 Shader.FindPassTagValue :获取 Tag 值,适用于 Unity 的预定义 Pass 标签,以及您创建的自定义通道标签。
注意:有几个 API 可以直接与 LightMode 通道标签一起使用。有关更多信息,请参阅[[ https://docs.unity3d.com/cn/2022.3/Manual/SL-PassTags.html# 脚本使用 LightMode 标签]( https://docs.unity3d.com/cn/2022.3/Manual/SL-PassTags.html#lightmode-tag-scripts ]]。

LightMode 标签:
Material.SetShaderPassEnabled and ShaderTagId use the value of the LightMode tag to determine how Unity handles a given Pass.
在可编程渲染管线中,您可以为 LightMode 标签创建自定义值。然后,通过配置一个 DrawingSettings 结构,您可以使用这些自定义值来确定在给定 ScriptableRenderContext.DrawRenderers 调用期间要绘制哪些通道。有关更多信息和代码示例,请参阅在自定义可编程渲染管线中创建一个简单的渲染循环

材质属性

API

Shader.Find

通过 Shader.Find (name)可以获得指定名字的 shader,需要注意的是 name 为 Shader 脚本中第一行定义 Shader 的全名,如 Unlit/Texture,Legacy Shaders/Diffuse 等。

如果 shader 没有被引用包含进发布工程,那么 Shader. Find 将不会获得目标 shader。
可以通过以下几种方式把需要的 shader 引用到工程中:

  1. 通过场景中的材质球引用,这样就会把引用的 shader 也一道打包了
  2. 在 Project Settings–>Graphics 下,把 shader 添加进 Always Included Shaders 中,手动太慢,可以使用代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    [MenuItem("Test/测试设置included shader", false, 11)]
    public static void TestIncludedShader()
    {
    string[] myShaders = new string[1]{
    "Legacy Shaders/Diffuse"
    };

    SerializedObject graphicsSettings = new SerializedObject (AssetDatabase.LoadAllAssetsAtPath ("ProjectSettings/GraphicsSettings.asset") [0]);
    SerializedProperty it = graphicsSettings.GetIterator ();
    SerializedProperty dataPoint;
    while (it.NextVisible(true)) {
    if (it.name == "m_AlwaysIncludedShaders") {
    it.ClearArray();

    for (int i = 0; i < myShaders.Length; i++) {
    it.InsertArrayElementAtIndex(i);
    dataPoint = it.GetArrayElementAtIndex (i);
    dataPoint.objectReferenceValue = Shader.Find(myShaders[i]);
    }

    graphicsSettings.ApplyModifiedProperties ();
    }
    }
    }
  3. 把 shader 放到 Resources 目录下

注意

在热更项目中,通常会把 shader 通过引用的方式打成包,然后通过 AssetBundle.LoadAllAssets() 的方式引入到工程中。这种方式引用的 Shader 无法使用 Shader.Find 获取到的。

MaterialPropertyBlock

title:用法 title:单个物体使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test : MonoBehaviour
{
static int baseColorId = Shader.PropertyToID("_BaseColor");

[SerializeField]
Color baseColor = Color.white;

private static MaterialPropertyBlock block;

void Start()
{
if (block == null)
{
block = new MaterialPropertyBlock();
}

block.SetColor(baseColorId, baseColor);

GetComponent<MeshRenderer>().SetPropertyBlock(block);
}
}
title:批量使用,结合GPUInstacing
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
public class MeshBall : MonoBehaviour
{
static int baseColorId = Shader.PropertyToID("_BaseColor");

[SerializeField] private Mesh mesh = default;
[SerializeField] private Material material = default;

Matrix4x4[] matrices = new Matrix4x4[1023];
Vector4[] baseColors = new Vector4[1023];

MaterialPropertyBlock block;

private void Awake()
{
for (int i = 0; i < matrices.Length; i++)
{
matrices[i] = Matrix4x4.TRS(
Random.insideUnitSphere * 10f,
Quaternion.Euler(Random.value * 360f, Random.value * 360f, Random.value * 360f),
Vector3.one * UnityEngine.Random.Range(0.5f, 1.5f)
);
baseColors[i] = new Vector4(Random.value, Random.value, Random.value, Random.Range(0.5f,1.0f));
}
}

void Update()
{
if (block == null)
{
block = new MaterialPropertyBlock();
block.SetVectorArray(baseColorId, baseColors);
}

Graphics.DrawMeshInstanced(mesh, 0, material, matrices, 1023, block);
}
}

对于实例化出来的模型,我们改变它身上的颜色值或者贴图之类,Unity 是会把它当前使用的 ShareMaterial 复制一份实例出来,以做到不同对象身上的材质互不影响的改变参数。但这样做会导致如果使用的对象很多,就会产生很多材质的实例的问题,这样会对内存有一定的消耗。

1686884244393

在场景里面生成了多个 cube,然后用代码改变他们的颜色:

1686884244630

给每一个 cube 随机一种颜色,然后用传统的 material. color 来设置颜色。

1686884244693

结果会是这样,各个 cube 的颜色变化了。

1686884244736

从 Profiler 里面看看内存,会发现场景内存里面,有很多的 InstanceMat(Instance)。这就是生成出来的材质实例。

接下来改一下代码,使用 MaterialPropertyBlock 来作为设置颜色的手段:

1686884244772

可以看到我们会先 new 一个 MaterialPropertyBlock,然后给它赋值,最后用 Renderer. SetPropertyBlock 方式给 MeshRender 设置属性。

运行时,可以看到运行的结果和之前的写法是一样的。

1686884244812

再从 Profiler 里面看看内存。

1686884244846

这次可以看到,并没有生成任何的 Material 的实例出来。