Zh/Material proxies: Difference between revisions

From Valve Developer Community
< Zh
Jump to navigation Jump to search
No edit summary
m (Setting bug notice hidetested=1 param on page where the bug might not need tested in param specified)
 
(5 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{LanguageBar}}
{{LanguageBar|title=材质代理}}
{{translate}}
使用ChatGPT翻译
 
{{toc-right}}
{{toc-right}}
'''材质代理'''允许游戏的编译C++代码操作[[材质]]的属性。许多代理执行特定任务,但[[材质代理列表|也有其他更通用的代理]],它们共同在VMT文件中提供基本的脚本支持。{{clarify}}
'''材质代理'''允许游戏的编译后C++代码在运行时操作{{L|material|材质}}的属性。它们可用于创建动态或动画纹理。


可以向材质添加任意数量的代理,它们会按照出现的顺序执行。
== 使用方法 ==
材质代理可以通过{{L|VMT}}文件添加到材质中,具体位于名为<code>Proxies</code>的{{L|KeyValues}}块内;其中的每个条目即为我们所称的'''代理'''。一个材质可以添加任意数量的代理,代理按出现顺序依次执行。游戏会以极快的速度不断重复执行材质代理列表。{{inline note|name=每帧执行?}}


{{bug|一些用户报告说工具模式下某些功能代理在游戏中无法运行。}}
代理的名称决定了其拥有的参数和执行的功能。
{{todo|该bug的范围尚未测试。}}
许多代理执行特定任务,但也有一些更通用的代理共同为VMT文件提供基础脚本支持。{{clarify}}
{{main|List of material proxies}}


{{bug|使用某些材质代理在动态创建的粒子上时,例如[[env_smokestack]],会导致游戏在加载地图时崩溃。}}
通常,每个代理都有一个输出值(例如{{L|float|浮点数}}),该值将被写入其<code>resultVar</code>,后者可以是着色器参数或变量(变量参见[[#Variables|下文]])。
 
== 用法 ==
 
该材质有一个 <code>Sine</code> 代理,使其在8秒内逐渐[[$alpha|显现和消失]]


例如,以下材质使用{{material proxy|Sine}}代理生成浮点数并写入材质的{{ent|$alpha}}着色器参数,使纹理在8秒周期内渐隐渐现:
<source lang=php>
<source lang=php>
LightmappedGeneric
LightmappedGeneric
Line 22: Line 18:
$basetexture shadertest/LightmappedTexture
$basetexture shadertest/LightmappedTexture


Proxies // 代理列在此块内
Proxies // 代理在此块内列出
{
{
Sine // 产生正弦波的代理
Sine // 生成正弦波值的代理
{
{
resultVar $alpha // 要操作的着色器参数
sineperiod 8      // 正弦周期长度(秒)
sineperiod 8
sinemin 0     // 最小值
sinemin 0
sinemax 1     // 最大值
sinemax 1
resultVar $alpha // 振荡输出值的写入位置
}
}
}
}
Line 37: Line 33:
=== 变量 ===
=== 变量 ===


材质可以声明自己的变量供内部使用。这些变量必须在Proxies块之外声明,在材质的主体部分,并且必须指定默认值。
材质可以声明自定义变量供内部使用。此类变量必须在<code>Proxies</code>块外的材质主体中声明,并需在右侧指定默认值。
 
这些自定义变量可能用于在代理之间传递结果或向其提交硬编码的数据。它们通常用于将数学函数代理(例如 <code>Add</code>、<code>Subtract</code> 等)串联成更长的方程式。对于2D/3D/4D向量(<code>"[0 0 0]"</code>),变量名称可以附加<code>[0]</code>以读取/写入特定索引。写入索引变量时应将其包围在引号中。


引擎使用8位有符号整数对这些自定义变量进行编码。因此,每个材质最多支持128个唯一自定义变量。
这些自定义变量可用于在代理间传递结果或向其提交硬编码数据。它们常被用于将数学函数代理(如{{material proxy|Add}}、{{material proxy|Subtract}}等)链接成更长的方程式。对于2D/3D/4D向量(<code>$vec "[0 0 0]"</code>),变量名可附加<code>[0]</code>(<code>"$vec[0]"</code>)以读写特定索引。对索引变量的写入操作需用引号包裹。


以下示例通过错开正弦波的起始位置来扩展上述示例:
引擎使用8位有符号整数编码这些自定义变量,因此每个材质最多有128个独立自定义变量。


此示例扩展了前例,通过{{material proxy|EntityRandom}}代理生成的随机值错开正弦波的起始位置:
<source lang=php>
<source lang=php>
LightmappedGeneric
LightmappedGeneric
Line 50: Line 45:
$basetexture shadertest/LightmappedTexture
$basetexture shadertest/LightmappedTexture


$offset 0 // 声明自定义变量
$offset 0 // 声明自定义变量($offset不是游戏已知的着色器参数)
Proxies
Proxies
{
{
EntityRandom
EntityRandom // 生成随机数
{
{
resultVar $offset // 写入自定义变量
resultVar $offset // 写入自定义变量
Line 70: Line 65:
</source>
</source>


现在,每个使用该材质的实体都会按照自己的时间表脉冲。
现在使用此材质的每个实体都会按自身节奏脉动。
 
写入索引变量的其他示例:


以下是通过$color向量写入索引变量创建"随机"颜色脉动的示例:
<source lang=php>
<source lang=php>
$color "[0 0 0]"
$color "[0 0 0]" // 非自定义变量,游戏识别"$color"


proxies
proxies
Line 106: Line 100:
</source>
</source>


使用颜色向量创建“随机”颜色脉冲的示例:


{{expand|noborder=1|title=动态纹理变换示例|
<source lang=php>
<source lang=php>
UnlitGeneric
UnlitGeneric
Line 171: Line 165:
}
}
</source>
</source>
}}


动态纹理变换的示例。
=== 拆分向量 ===
 
使用向量存在特殊性,因为<code>"$pos[0]"</code>等向量分量表达式并非总被游戏识别。
== 分割向量 ==
{{important|当访问向量分量或定义向量时,必须使用引号}}
使用向量时有些特殊之处。并非所有代理都能识别向量分量。如果需要单独处理向量的各个分量,则必须先将它们拆分成不同的变量。 
下列代理(且仅限于这些键值)可识别向量分量:
所有代理中的<code>resultVar</code>都会识别向量分量。
<tt>
 
* 所有代理的<code>resultVar</code>
以下代理和仅这些关键字可以识别向量分量: 
* {{material proxy|Clamp}}:         min, max
<code>
* {{material proxy|Sine}}:          offset, max, min, period
* '''Clamp: '''        min, max
* {{material proxy|LinearRamp}}:    rate, initial value
* '''Sine:'''         offset, max, min, period
* {{material proxy|UniformNoise}}:  min, max
* '''LinearRamp:'''   rate, initial value
* {{material proxy|GaussianNoise}}: min, max, mean, halfwidth
* '''UniformNoise:''' min, max
* {{material proxy|WrapMinMax}}:    min, max
* '''GaussianNoise:''' min, max, mean, halfwidth
* {{material proxy|Exponential}}:  min, max, scale, offset
* '''WrapMinMax:'''   min, max
</tt>
* '''Exponential:'''   min, max, scale, offset
如需单独处理向量分量,需先将其拆分到不同变量中。
</code>
上述所有方法都可用于拆分向量,但{{L|Clamp}}是{{L|Cheap|消耗最低}}的方式。
上述所有代理都可以用于分割向量。 但是Clamp是[[Cheap|最便宜]]的选择: 
{{expand|noborder=1|title=示例|
 
<source lang="php">
<source lang="php">
         $pos "[0 0 0]"
         $pos "[0 0 0]"
         $posX .0        //必须为浮点数,否则Clamp无法正确保存值
         $posX .0        //必须是浮点数,否则Clamp无法正确保存值
         $posY .0        //必须为浮点数,否则Clamp无法正确保存值
         $posY .0        //必须是浮点数,否则Clamp无法正确保存值
         $posZ .0        //必须为浮点数,否则Clamp无法正确保存值
         $posZ .0        //必须是浮点数,否则Clamp无法正确保存值
          
          
         $zero 0
         $zero 0
Line 205: Line 199:
         }
         }
          
          
         //拆分3D向量以便进一步使用
         //拆分3D向量供后续使用
         Clamp
         Clamp
         {
         {
Line 230: Line 224:
         }
         }
</source>
</source>
}}


{{warning|引用时需要加引号,以便正确处理向量分量或定义向量。}}
== 编写新代理 ==
 
== 编写新的代理 ==


创建新的代理很简单。它们仅存在于客户端,并应继承自<code>IMaterialProxy</code>或其子类。
创建新代理较为简单。它们仅存在于客户端,应继承自<code>IMaterialProxy</code>或其子类。


你需要这些头文件:
需要包含以下头文件:


* <code>"materialsystem/IMaterialProxy.h"</code>
* <code>"materialsystem/IMaterialProxy.h"</code>
* <code>"materialsystem/IMaterialVar.h"</code>
* <code>"materialsystem/IMaterialVar.h"</code>


以下函数包含在接口中:
接口包含以下函数:


; <code>[[bool]] Init( [[IMaterial]]* pMaterial, [[KeyValues]]* pKeyValues )</code>
; <code>{{L|bool}} Init( {{L|IMaterial}}* pMaterial, {{L|KeyValues}}* pKeyValues )</code>
: 在材质首次[[预加载]]时调用。使用此函数初始化变量并获取你将使用的材质变量的引用。成功时返回true,失败时返回false(此时代理将不会执行)。 
: 材质首次{{L|precache|预缓存}}时调用。用于初始化变量并获取材质变量引用。成功返回true,失败返回false(代理将不运行)。
: <code>pKeyValues</code>包含来自VMT文件的代理参数。
: <code>pKeyValues</code>包含来自VMT文件的代理参数。
 
; <code>void OnBind( void* pC_BaseEntity )</code>
; <code>void OnBind( void* pC_BaseEntity )</code>
: 材质即将在实体上渲染时调用。主要工作在此完成。
: 在材质即将应用到实体进行渲染时调用。这里是执行工作的地方。 
: 编码时需注意:所有使用该材质的实体共享同一材质对象,若修改某实体的材质参数将影响所有实体。由于<code>OnBind()</code>在每次实体渲染前调用,''只要每次重新赋值所需值就不会出现问题''。不要因无变化而提前返回,也不要在代理中存储输入数据。{{note|<code>pC_BaseEntity</code>并不指向{{L|C_BaseEntity}},而是关联的{{L|IClientRenderable}}。直接访问实体的最简单方法是继承自<code>CEntityMaterialProxy</code>(位于<code>proxyentity.h</code>),使用其提供的<code>OnBind(C_BaseEntity*)</code>重载。}}
: 编写此函数时需要记住,所有使用同一材质的实体共享同一个材质对象,若在一个实体上修改了材质,它将在其他所有实体上也发生变化。由于<code>OnBind()</code>会在每次渲染实体时被调用,只要每次都重新分配你想要的值就不会有问题。不要因为没有变化而提前返回,也不要在代理中存储输入数据。{{note|<code>pC_BaseEntity</code>并不会指向<code>[[C_BaseEntity]]</code>,而是指向关联的<code>[[IClientRenderable]]</code>。要直接访问实体,最简单的方法是基于<code>CEntityMaterialProxy</code>(位于<code>proxyentity.h</code>)并使用其提供的<code>OnBind(C_BaseEntity*)</code>重载。}}
; <code>void Release()</code>
 
: {{todo|代理被移除时调用,但具体时机是?}}
; <code>void Release()</code>
; <code>{{L|IMaterial}}* GetMaterial()</code>
: {{todo|代理被移除时调用,但是什么时候呢?}}
: 代理所属的材质。{{tip|若已存储材质变量,可返回<code>IMaterialVar::GetOwningMaterial()</code>而无需新建<code>IMaterial</code>指针}}
 
; <code>[[IMaterial]]* GetMaterial()</code>
: 获取代理所附加的材质。{{tip|如果你有一个材质变量存储,你可以返回<code>IMaterialVar::GetOwningMaterial()</code>,而不是创建一个新的<code>IMaterial</code>指针。}}


=== 接口 ===
=== 接口 ===


代理必须通过<code>EXPOSE_INTERFACE</code>宏向材质暴露其接口:
代理必须通过<code>EXPOSE_INTERFACE</code>宏向材质暴露接口:


<source lang=cpp>
<source lang=cpp>
Line 266: Line 256:
</source>
</source>


代理名称和接口版本之间没有逗号,这是故意的。
代理名称与接口版本间无逗号为刻意设计。


=== 工具录制 ===
=== 工具录制 ===


以下代码已被添加到所有代理中,适用于Orange Box:
橙盒版本中所有代理都添加了以下代码:


<source lang=cpp>
<source lang=cpp>
Line 284: Line 274:
</source>
</source>


它可能与[[Source Filmmaker]]相关。最好在你的代理中也添加这段代码,以防Filmmaker发布!
这可能与{{L|Source Filmmaker}}相关。建议在代理中添加此代码以兼容未来可能发布的电影制作工具!
 
{{tip|<code>CEntityMaterialProxy</code>会自动执行此调用}}
 
== 已知问题 ==


{{tip|<code>CEntityMaterialProxy</code>会自动进行此调用。}}
{{bug|hidetested=1|材质的首个参数无法被代理写入。简单解决方案是在VMT顶部添加虚拟变量}}
{{bug|hidetested=1|有用户报告工具模式无法在游戏中运行某些功能代理。问题范围未经验证}}
{{bug|hidetested=1|在粒子或{{L|prop_static}}上使用访问实体状态的代理(如<code>EntityRandom</code>)可能导致游戏崩溃}}


== 另见 ==
== 另见 ==
* [[List Of Material Proxies|材质代理列表]]
* {{L|List Of Material Proxies|材质代理列表}}
* [[Material proxies programming|材质代理编程]]
* {{L|Material proxies programming|材质代理编程}}
* [[Material Creation|材质创建]]
* {{L|Material Creation|材质创建}}
* Steam指南,解释更多关于各种代理的内容。 [https://steamcommunity.com/sharedfiles/filedetails/?id=594255575 基础], [https://steamcommunity.com/sharedfiles/filedetails/?id=668958242 RNG], [https://steamcommunity.com/sharedfiles/filedetails/?id=749130424 "编程"]
* Steam指南,详解各类代理:[https://steamcommunity.com/sharedfiles/filedetails/?id=594255575 基础], [https://steamcommunity.com/sharedfiles/filedetails/?id=668958242 随机数], [https://steamcommunity.com/sharedfiles/filedetails/?id=749130424 "编程"]


== 外部链接 ==
== 外部链接 ==
* [https://nodraw.net/2010/01/dynamic-materials-with-proxies/ 使用代理的动态材质] - [http://www.nodraw.net NoDraw.net]关于使用代理的材质的实践文章
* [https://nodraw.net/2010/01/dynamic-materials-with-proxies/ 使用代理的动态材质] - [http://www.nodraw.net NoDraw.net]关于材质代理实际应用的文章


[[Category:Source]]
{{ACategory|Source}}
[[Category:Material System]]
{{ACategory|Material System}}
[[Category:C++]]
{{ACategory|C++}}

Latest revision as of 07:18, 20 May 2025

English (en)Português do Brasil (pt-br)中文 (zh)Translate (Translate)
Info content.png
This page is Machine translated
It is not recommended to use machine translation without any corrections.
If the article is not corrected in the long term, it will be removed.
Also, please make sure the article complies with the alternate languages guide.(en)
This notice is put here by LanguageBar template and if you want to remove it after updating the translation you can do so on this page.


材质代理允许游戏的编译后C++代码在运行时操作材质(en)的属性。它们可用于创建动态或动画纹理。

使用方法

材质代理可以通过VMT(en)文件添加到材质中,具体位于名为ProxiesKeyValues(en)块内;其中的每个条目即为我们所称的代理。一个材质可以添加任意数量的代理,代理按出现顺序依次执行。游戏会以极快的速度不断重复执行材质代理列表。[每帧执行?]

代理的名称决定了其拥有的参数和执行的功能。 许多代理执行特定任务,但也有一些更通用的代理共同为VMT文件提供基础脚本支持。[澄清]

通常,每个代理都有一个输出值(例如浮点数(en)),该值将被写入其resultVar,后者可以是着色器参数或变量(变量参见下文)。

例如,以下材质使用Sine代理生成浮点数并写入材质的$alpha着色器参数,使纹理在8秒周期内渐隐渐现:

LightmappedGeneric
{
	$basetexture shadertest/LightmappedTexture

	Proxies // 代理在此块内列出
	{
		Sine // 生成正弦波值的代理
		{
			sineperiod	8      // 正弦周期长度(秒)
			sinemin		0      // 最小值
			sinemax		1      // 最大值
			resultVar	$alpha // 振荡输出值的写入位置
		}
	}
}

变量

材质可以声明自定义变量供内部使用。此类变量必须在Proxies块外的材质主体中声明,并需在右侧指定默认值。

这些自定义变量可用于在代理间传递结果或向其提交硬编码数据。它们常被用于将数学函数代理(如AddSubtract等)链接成更长的方程式。对于2D/3D/4D向量($vec "[0 0 0]"),变量名可附加[0]"$vec[0]")以读写特定索引。对索引变量的写入操作需用引号包裹。

引擎使用8位有符号整数编码这些自定义变量,因此每个材质最多有128个独立自定义变量。

此示例扩展了前例,通过EntityRandom代理生成的随机值错开正弦波的起始位置:

LightmappedGeneric
{
	$basetexture shadertest/LightmappedTexture

	$offset 0 // 声明自定义变量($offset不是游戏已知的着色器参数)
	
	Proxies
	{
		EntityRandom // 生成随机数
		{
			resultVar $offset // 写入自定义变量
		}
		Sine
		{
			resultVar	$alpha
			timeoffset	$offset // 从自定义变量读取
			sineperiod	8
			sinemin		0
			sinemax		1
		}
	}
}

现在使用此材质的每个实体都会按自身节奏脉动。

以下是通过$color向量写入索引变量创建"随机"颜色脉动的示例:

	$color "[0 0 0]" // 非自定义变量,游戏识别"$color"

	proxies
	{
		sine
		{
			sineperiod	1.3
			sinemin		0
			sinemax		1
			timeoffset	0
			resultvar	"$color[0]"
		}
		sine
		{
			sineperiod	1.7
			sinemin		0
			sinemax		1
			timeoffset	0
			resultvar	"$color[1]"
		}
		sine
		{
			sineperiod	2.3
			sinemin		0
			sinemax		1
			timeoffset	0
			resultvar	"$color[2]"
		}
	}


动态纹理变换示例
UnlitGeneric
{
	$basetexture "dev\gradient_dif"
	$color "[1 .8 .6]"

	$detail "dev\noise_512x32"
	$detailscale 1
	$detailblendmode 0
	$detailblendfactor 4.0

	$additive 1
	$nocull 1

	$cvar "[.5 .5]"
	$svar "[1 .25]"
	$rvar 0
	$tvar "[0 0]"

	$sine1 0
	$sine2 0

	proxies
	{
		linearramp
		{
			rate .3
			initialvalue 0
			resultvar "$tvar[1]"
		}
		sine
		{
			sineperiod 1.3
			sinemin -.004
			sinemax .002
			timeoffset 0
			resultvar $sine1
		}
		sine
		{
			sineperiod 1.7
			sinemin -.003
			sinemax .007
			timeoffset .2
			resultvar $sine2
		}
		add
		{
			srcvar1 $sine1
			srcvar2 $sine2
			resultvar "$tvar[0]"
		}
		texturetransform
		{
			centervar $cvar
			scalevar $svar
			rotatevar $rvar
			translatevar $tvar
			resultvar $detailtexturetransform
		}
	}
}

拆分向量

使用向量存在特殊性,因为"$pos[0]"等向量分量表达式并非总被游戏识别。

Icon-Important.png重要:当访问向量分量或定义向量时,必须使用引号

下列代理(且仅限于这些键值)可识别向量分量:

如需单独处理向量分量,需先将其拆分到不同变量中。 上述所有方法都可用于拆分向量,但Clamp(en)消耗最低(en)的方式。

示例
        $pos "[0 0 0]"
        $posX .0        //必须是浮点数,否则Clamp无法正确保存值
        $posY .0        //必须是浮点数,否则Clamp无法正确保存值
        $posZ .0        //必须是浮点数,否则Clamp无法正确保存值
        
        $zero 0
        
        //输出3D向量的代理
        PlayerPosition
        {
                scale                    1
                resultVar               "$pos"
        }
        
        //拆分3D向量供后续使用
        Clamp
        {
            srcVar1                      $zero
            min                         "$pos[0]"
            max                         "$pos[0]"
            resultVar                    $posX
        }
        
        Clamp
        {
            srcVar1                      $zero
            min                         "$pos[1]"
            max                         "$pos[1]"
            resultVar                    $posY
        }
        
        Clamp
        {
            srcVar1                      $zero
            min                         "$pos[2]"
            max                         "$pos[2]"
            resultVar                    $posZ
        }

编写新代理

创建新代理较为简单。它们仅存在于客户端,应继承自IMaterialProxy或其子类。

需要包含以下头文件:

  • "materialsystem/IMaterialProxy.h"
  • "materialsystem/IMaterialVar.h"

接口包含以下函数:

bool(en) Init( IMaterial(en)* pMaterial, KeyValues(en)* pKeyValues )
材质首次预缓存(en)时调用。用于初始化变量并获取材质变量引用。成功返回true,失败返回false(代理将不运行)。
pKeyValues包含来自VMT文件的代理参数。
void OnBind( void* pC_BaseEntity )
材质即将在实体上渲染时调用。主要工作在此完成。
编码时需注意:所有使用该材质的实体共享同一材质对象,若修改某实体的材质参数将影响所有实体。由于OnBind()在每次实体渲染前调用,只要每次重新赋值所需值就不会出现问题。不要因无变化而提前返回,也不要在代理中存储输入数据。
Note.png注意:pC_BaseEntity并不指向C_BaseEntity(en),而是关联的IClientRenderable(en)。直接访问实体的最简单方法是继承自CEntityMaterialProxy(位于proxyentity.h),使用其提供的OnBind(C_BaseEntity*)重载。
void Release()
待完善: 代理被移除时调用,但具体时机是?
IMaterial(en)* GetMaterial()
代理所属的材质。
Tip.png提示:若已存储材质变量,可返回IMaterialVar::GetOwningMaterial()而无需新建IMaterial指针

接口

代理必须通过EXPOSE_INTERFACE宏向材质暴露接口:

EXPOSE_INTERFACE( <className>, <interfaceName>, "<proxyName>" IMATERIAL_PROXY_INTERFACE_VERSION );

代理名称与接口版本间无逗号为刻意设计。

工具录制

橙盒版本中所有代理都添加了以下代码:

#include "toolframework_client.h"

void OnBind(...)
{
	//...

	if ( ToolsEnabled() )
		ToolFramework_RecordMaterialParams( GetMaterial() );
}

这可能与Source Filmmaker(en)相关。建议在代理中添加此代码以兼容未来可能发布的电影制作工具!

Tip.png提示:CEntityMaterialProxy会自动执行此调用

已知问题

Icon-Bug.png错误:材质的首个参数无法被代理写入。简单解决方案是在VMT顶部添加虚拟变量
Icon-Bug.png错误:有用户报告工具模式无法在游戏中运行某些功能代理。问题范围未经验证
Icon-Bug.png错误:在粒子或prop_static(en)上使用访问实体状态的代理(如EntityRandom)可能导致游戏崩溃

另见

外部链接