Material proxies: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
m (categorise)
m (Setting bug notice hidetested=1 param on page where the bug might not need tested in param specified)
 
(50 intermediate revisions by 28 users not shown)
Line 1: Line 1:
[[Category:List of Shader Parameters]]
{{LanguageBar}}
== The basics ==
{{toc-right}}
Material proxies allow materials to change their rendering properties (color, alpha values, etc) using game state and scripted equations. Proxies are defined as a block of text, using key/value pairing, inside of the [[VMT]] description of a material. Proxies are available for all materials.
'''Material proxies''' allow a game's compiled C++ code to manipulate the properties of a [[material]] in-game. They can be used to create dynamic or animated textures.
 
A proxy is defined as in the following example:


<pre>
== Usage ==
"LightmappedGeneric"
Material proxies can be added to some material via its [[VMT]] file, more specificly in a [[KeyValues]]-block named <code>Proxies</code>; Each entry inside it is what we name a '''proxy'''. Any number of proxies can be added to a material. Proxies are executed in the order in which they appear. The game repeatedly executes the list of a material's proxies in very quick succession.{{inline note|name=every tick?}}
 
The name of the proxy determines what parameters it has and what the proxy does.
Many proxies perform specific tasks, but there are other more general ones that together provide rudimentary scripting support within VMT files.{{clarify}}
{{main|List of material proxies}}
 
Typically, each proxy has an output value (e. g. a [[float]] number) which will be written to its <code>resultVar</code> which can be either a shader parameter or variable (see [[#Variables|below]] for variables).
 
For example, the following material has a {{material proxy|Sine}} proxy which generates a float number and writes it to the material's {{ent|$alpha}} shader parameter, which makes the texture fade in and out of view over a period of eight seconds:
<source lang=php>
LightmappedGeneric
{
{
"$basetexture" "shadertest/LightmappedTexture"
$basetexture shadertest/LightmappedTexture


// Inside these braces are where all the proxies go
Proxies // proxies are listed inside this block
"Proxies"
{
{
// This is a sine proxy
Sine // a proxy which produces values of a sine wave
"Sine"
{
{
// This is the data for the sine proxy
sineperiod 8      // the sine cycle length in seconds
// Notice there is no '$' on the proxy variables
sinemin 0      // min output value
"resultVar" "$alpha"
sinemax 1      // max output value
"sineperiod" 8
resultVar $alpha // where the oscillating output value is written to
"sinemin" 0
"sinemax" 1
}
}
}
}
}
}
</pre>
</source>
 
=== Variables ===


The above block of text instructs the material to use the <code>Sine</code> material proxy to modify the <code>alpha</code> value of the material when rendered. The other entries define parameters for the proxy to use in its calculations. Here the sine wave has a <code>sineperiod</code> of 8 seconds and oscillates between a value of "0" and "1".
Materials can declare their own variables for internal use. Such variables must be declared outside the <code>Proxies</code> block, in the body of the material, and must have a default value specified on the right-hand side.


It is possible to define multiple proxies in one material within the Proxies section of that material's definition. If multiple proxies are defined, they are executed in a top to bottom order.
These custom variables can be used to pass results between proxies or to submit hard-coded data to them. They are often employed to chain mathematic function proxies (i. e. {{material proxy|Add}}, {{material proxy|Subtract}}, etc.) together into longer equations. For 2D/3D/4D vectors (<code>$vec "[0 0 0]"</code>), the variable name can have <code>[0]</code> suffixed (<code>"$vec[0]"</code>) to read/write a specific index. Writes to indexed variables should be encased in quotes.


<pre>
The engine encodes these custom variables using a 8-bit signed integer. Therefore, there is a limit of 128 unique custom variables per material.
"Proxies"
 
{  
This example extends the one above by staggering the starting position of the sine wave with a random value produced with the {{material proxy|EntityRandom}} proxy:
// This is executed first
<source lang=php>
"A"
LightmappedGeneric
{
$basetexture shadertest/LightmappedTexture
 
$offset 0 // declare custom var ($offset is not a shader parameter the game knows)
Proxies
{
{
. . .
EntityRandom // generates a random number
{
resultVar $offset // write to custom var
}
Sine
{
resultVar $alpha
timeoffset $offset // read from custom var
sineperiod 8
sinemin 0
sinemax 1
}
}
}
// This is executed second
}
"B"
</source>
 
Now each entity this material is used on pulses to its own schedule.
 
This is an example for writing to indexed variables using the $color vector to create 'random' color pulses:
<source lang=php>
$color "[0 0 0]" // not custom, the game knows "$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]"
}
}
}
}
</source>
</pre>


== Variables ==
Like a standard programming language, the material proxies may declare temporary local variables to be used to store intermediate values for further use later in the proxy. Variables are declared outside of the ''Proxies'' block and have default values specified on their right-hand side.


<pre>
{{expand|noborder=1|title=Example of a dynamic texture transform|
"LightmappedGeneric"
<source lang=php>
UnlitGeneric
{
{
"$basetexture" "shadertest/LightmappedTexture"
$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]"


// A local variable for this material, defaulting to "0.5"
$sine1 0
"$myScale" 0.5
$sine2 0
"Proxies"
 
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
}
}
}
}
}
</pre>
</source>
}}
 
=== Splitting a vector ===
Using vectors is quirky, because vector component expressions such as <code>"$pos[0]"</code> are not always recognized by the game.
{{important|Quotes are needed when addressing a vector's component, or when defining a vector.}}
The following proxies, and only these keyvalues, can recognize vector components:
<tt>
* The <code>resultVar</code> of all proxies
* {{material proxy|Clamp}}:        min, max
* {{material proxy|Sine}}:          offset, max, min, period
* {{material proxy|LinearRamp}}:    rate, initial value
* {{material proxy|UniformNoise}}:  min, max
* {{material proxy|GaussianNoise}}: min, max, mean, halfwidth
* {{material proxy|WrapMinMax}}:    min, max
* {{material proxy|Exponential}}:  min, max, scale, offset
</tt>
If a vector's components need be processed separately, they need to be split into different variables first.
All of the above can be used to split a vector. However, Clamp is the [[Cheap|cheapest]] to use.
{{expand|noborder=1|title=Example|
<source lang="php">
        $pos "[0 0 0]"
        $posX .0        //must be float or Clamp will not save the value properly
        $posY .0        //must be float or Clamp will not save the value properly
        $posZ .0        //must be float or Clamp will not save the value properly
       
        $zero 0
       
        //Proxy that outputs a 3d vector
        PlayerPosition
        {
                scale                    1
                resultVar              "$pos"
        }
       
        //Split the 3d vector for further use
        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
        }
</source>
}}


There is no practical limit to the number of local variables declared. Local variables may be used to store results from proxies, or to pass data into proxies as parameters. They are often employed to chain mathematic function proxies (i.e. ''Add'', ''Subtract'', etc) together into longer equations for rendering values.
== Writing new proxies ==


== Common result variables ==
New proxies are easy to create. They exist on the client only and should inherit from <code>IMaterialProxy</code> or one of its descendants.
The following values are commonly used as $resultVar variables.
{|
| <code>$alpha</code> || Fade value (0 = transparent, 1 = opaque)
|-
| <code>$color</code> || Modulation color (R,G,B) (1,1,1) = white, (0,0,0) = black
|-
| <code>$envmapmaskscale</code> || An amount to scale the environment map mask
|-
| <code>$frame</code> || Frame number of an animated texture
|-
| <code>$envmaptint</code> || Modulation color for the environment map
|-
| <code>$selfillumtint</code> || Modulation color for the self-illumination
|-
|}


{{Note|<code>$alpha</code> and <code>$color</code> values are clamped between 0 and 1.}}
You will need these #includes:


{{Note|Certain variables like <code>$color</code> and <code>$baseTextureOffset</code> can be accessed like an [[array]]. <code>"$color[0]"</code> would access the red component of the variable.}}
* <code>"materialsystem/IMaterialProxy.h"</code>
* <code>"materialsystem/IMaterialVar.h"</code>


== Writing material proxy implementations ==
These functions are included in the interface:
Although many generic proxies are included in the client already, it will sometimes be necessary to create a custom material proxy for your MOD. To do this, code will be required on the client-side. Material proxies are descended from the <code>IMaterialProxy</code> interface class. The proxy has three main functions to do its work in. The first is the Init() function, defined as:


<pre>
; <code>[[bool]] Init( [[IMaterial]]* pMaterial, [[KeyValues]]* pKeyValues )</code>
bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
: Called when the material is first [[precache]]d. Use this function to initialise variables and grab references to the material vars you will be using. Return true on success and false on failure (in which case the proxy will not be run).
pMaterial
: <code>pKeyValues</code> contains the proxy parameters from the VMT file.
Material we're acting on
; <code>void OnBind( void* pC_BaseEntity )</code>
: Called when the material is about to be rendered on an entity. This is where the work is done.
: When coding this function it is important to remember that all entities using a material share the same material object, and that if you change it on one entity it changes everywhere else too. Since <code>OnBind()</code> is called every time an entity comes up for rendering this is not a problem ''so long as you reassign the value you want every time''. Don't return early just because there has been no change, and don't store any input data in the proxy. {{note|<code>pC_BaseEntity</code> doesn't lead to a <code>[[C_BaseEntity]]</code> as its name suggests, but rather to the associated <code>[[IClientRenderable]]</code>. The easiest way to access the entity directly is to base your class on <code>CEntityMaterialProxy</code> (in <code>proxyentity.h</code>) and use the <code>OnBind(C_BaseEntity*)</code> overload it provides.}}
; <code>void Release()</code>
: {{todo|Called when the proxy is removed, but when is that?}}
; <code>[[IMaterial]]* GetMaterial()</code>
: The material the proxy is attached to. {{tip|If you have a material var stored, you can return <code>IMaterialVar::GetOwningMaterial()</code> here instead of creating a new <code>IMaterial</code> pointer.}}


pKeyValues
=== Interface ===
List of key-value pairs for this material
</pre>


The <code>Init()</code> function is called when a material is first created in a session. This function allows you to setup internal state for the material proxy, usually consisting of obtaining references to ''material variables'' for later use.
The proxy must expose its interface to materials with the <code>EXPOSE_INTERFACE</code> macro:


<pre>
<source lang=cpp>
void OnBind( void *pC_BaseEntity );
EXPOSE_INTERFACE( <className>, <interfaceName>, "<proxyName>" IMATERIAL_PROXY_INTERFACE_VERSION );
pC_BaseEntity
</source>
Entity this material is being applied to (if any)
</pre>


The <code>OnBind()</code> function is called every time <code>Bind()</code> is called on a material. This is where most of the material proxy's work is done.
The lack of a comma between the proxy name and interface version is intentional.


Finally, the proxy must expose its interface so that the named reference to the proxy can be linked up to the class implementation. This is done via the <code>EXPOSE_INTERFACE</code> macro.
=== Tools recording ===


<pre>
This code was added to all proxies in the Orange Box:
EXPOSE_INTERFACE( className, interfaceName, proxyName IMATERIAL_PROXY_INTERFACE_VERSION );
</pre>


{{Note|See <code>DummyProxy.cpp</code> for an example of a simple material proxy.}}
<source lang=cpp>
#include "toolframework_client.h"


== Proxy list ==
void OnBind(...)
The following proxies are defined in the client DLL for use in any Source game.
{
{|
//...
! Proxy Name || Description || Variables
|-
| <code>Add</code> || Adds two variables together. ||
''srcVar1''
* Name of the first source variable.
''srcVar2''
* Name of the second source variable.
''resultVar''
* ( srcVar1 + ''srcVar2'')
|-
| <code>Multiply</code> || Multiplies two variables together. ||
''srcVar1''
* Name of the first source variable.
''srcVar2''
* Name of the second source variable.
''resultVar''
* ( ''srcVar1'' * ''srcVar2'')
|-
| <code>Subtract</code> || Subtracts the second variable from the first. ||
''srcVar1''
* Name of the first source variable.
''srcVar2''
* Name of the second source variable.
''resultVar''
* ( ''srcVar1'' - ''srcVar2'')
|-
| <code>Divide</code> || Divides the first variable by the second. ||
''srcVar1''
* Name of the first source variable.
''srcVar2''
* Name of the second source variable.
''resultVar''
* ( ''srcVar1'' / ''srcVar2'')
|-
| <code>Equals</code> || Copies a variable to the result variable. ||
''srcVar1''
* Name of the source variable.
''resultVar''
* Set equal to ''srcVar1''
|-
| <code>Abs</code> || Computes the absolute value of a variable. ||
''srcVar1''
* Name of the source variable.
''resultVar''
* abs( ''srcVar1'')
|-
| <code>Frac</code> || Returns the fractional component of a number.<br>{{Note|If a vector type variable is provided, this function will return the fractional component of each member of the array.}} ||
''srcVar1''
* Number to find the fractional component of
|-
| <code>Exponential</code> || Computes ( A * e (''srcVar1''+B) ). ||
''srcVar1''
* Name of the source variable.
''scale''
* Specifies the value of A
''offset''
* Specifies the value of B
''minVal''
* Minimum clamping value
''maxVal''
* Maximum clamping value
''resultVar''
* ( A * e (''srcVar1''+B) )
|-
| <code>Clamp</code> || Clamps a variable to a specified range of values. ||
''srcVar1''
* Name of the source variable.
''min''
* Minimum clamping value
''max''
* Maximum clamping value
''resultVar''
* clamp( ''srcVar1'', ''Min'', ''Max'' )
|-
| <code>LessOrEqual</code> || Returns different values based relation of supplied variables. ||
''srcVar1''
* Name of the first source variable.
''srcVar1''
* Name of the second source variable.
''lessEqualVar''
* Name of material variable to copy to result if (''srcVar1'' <= ''srcVar2'')
''greaterVar''
* Name of material var to copy to result if (''srcVar1'' > ''srcVar2'')
''resultVar''
* Resulting value
|-
| <code>Sine</code> || Creates a sine wave. ||
''sineperiod''
* Period of the sine wave in seconds.
''sinemin''
* Minimum value of the sine wave.
''sinemax''
* Maximum value of the sine wave.
''timeoffset''
* Used to start the sine wave at a different point in time
''resultVar''
* Resulting value.
|-
| <code>LinearRamp</code> || Produces an ever increasing value. ||
rate
* Rate of increase of the value.
''initialValue''
* Initial value to increase.
''resultVar''
* Resulting value.
|-
| <code>UniformNoise</code> || Produces a noisy signal where each value is equally likely to occur ||
''minVal''
* Minimum value range.
''maxVal''
* Maximum value range.
''resultVar''
* Resulting value.
|-
| <code>GaussianNoise</code> || Produces a noisy signal where values are near the average. ||
mean
* The average random value.
''halfWidth''
* The distance from the average at which it's only 30% likely to occur.
''minVal''
* Minimum value range (clamped).
''maxVal''
* Maximum value range (clamped).
''resultVar''
* Resulting value.
|-
| <code>MatrixRotate</code> || Creates a rotation matrix from the provided axis and angle. ||
''axisVar''
* Axis of rotation (x,y,z) = (0,1,2).
''angle''
* Degrees of rotation around axis.
''resultVar''
* Rotation matrix.
|-
| <code>PlayerProximity</code> || Stores the distance from the entity the material is on to the player into a variable. <br> {{Note|Does not work on world surfaces.}} ||
''scale''
* An amount to scale the distance by.
''resultVar''
* Resulting value.
|-
| <code>PlayerView</code> || Stores the dot product of the view direction + the direction from the camera to the entity the material is on.<br>{{Note|Does not work on world surfaces.}} ||
''scale''
* Amount to scale the dot product by.
''resultVar''
* Resulting value.
|-
| <code>PlayerSpeed</code> || Stores the speed of the player into a variable. ||
''scale''
* Amount to scale the speed by.
''resultVar''
* Resulting value.
|-
| <code>PlayerPosition</code> || Stores the player's position into a variable.<br>{{Note|Only works for vector variables like <code>$baseTextureOffset</code> or <code>$color</code>.}} ||
''scale''
* An amount to scale the position by.
''resultVar''
* Resulting value.
|-
| <code>EntitySpeed</code> || Stores the entity's speed into a variable.<br>{{Note|Does not work on world surfaces.}} ||
''scale''
* An amount to scale the speed by.
''resultVar''
* Resulting value.
|-
| <code>TextureTransform</code> || Generates a texture transform matrix. ||
''centerVar''
* Name of a variable holding the center of rotation and scaling. [Optional]
''scaleVar''
* Name of scale variable (2D vector). [Optional]
''rotateVar''
* Name of rotation angle variable (float). [Optional]
''translateVar''
* Name of the translation variable (2D vector).
''resultVar''
* Resulting value.
|-
| <code>Empty</code> || Used to comment out proxies. Surround a bunch of proxies with the empty proxy to cause those proxies to not operate.||
|-
| <code>TextureScroll</code> || Returns a transform matrix or vector that will translate a texture at a given angle at a given rate. ||
''textureScrollVar''
* Destination for the resulting transformation.
''textureScrollRate''
* Rate of scroll in units per second.
''textureScrollAngle''
* Angle of rotation to move along. (90 = right, 180 = left, etc)
|-
| <code>ToggleTexture</code> || Toggles a texture based on the frame number set by the attached entity.<br> {{Note|Must be attached to an entity.}} ||
''toggleTextureVar''
* Texture to modify based on frames.
''toggleTextureFrameNumVar''
* Variable used for frame number.
''toggleShouldWrap''
* Whether the animation should wrap over not.
|-
| <code>RandomEntity</code> || A proxy that returns a random number associated with the entity the material is applied to. Can be helpful for making animated textures not all be in sync. ||
''scale''
* Amount to scale the random number by.
''resultVar''
* Resulting value.
|-
| <code>CurrentTime</code> || Returns the current time as a float. ||
''resultVar''
* The current time.
|-
| <code>PlayerHealth</code> || Stores the player health (0-1) in a variable. ||
''scale''
* Amount to scale the health value by.
''resultVar''
* Resulting value.
|-
| <code>PlayerDamageTime</code> || Stores the time since the player was last damaged, in a variable. ||
''scale''
* Amount to scale the time by.
''resultVar''
* Resulting value.
|-
| <code>MaterialModify</code> || Sets the base texture to a value held by the entity (used for entity controlled texture animations).<br>{{Note|Only works when attached to an entity.}} ||
No return value.
|-
| <code>WaterLOD</code> || Coordinates water LOD values between the map's <code>env_waterlod</code> entity and the materials internal values. ||
<code>$CHEAPWATERSTARTDISTANCE</code>
* Start distance for cheap water
{{note|must be set outside of proxy block.}}
<code>$CHEAPWATERENDDISTANCE</code>
* End distance for cheap water
{{note|must be set outside of proxy block.}}
No return value.
|-
| <code>BreakableSurface</code> || Sets the base texture to a material name held by the entity (used for switching surface material on shatter)<br>{{Note|Must be attached to a <code>func_breakablesurface</code> entity.}} ||
No return value.
|-
| <code>ConveyorScroll</code> || Returns the scroll parameters for a texture used as a conveyor. <br>{{Note|Must be attached to <code>func_conveyor</code> entity.}} ||
''textureScrollVar''
* Name of variable to place result in.
{{note|must be a matrix or vector type variable (i.e. <code>$baseTextureOffset</code>).}}
|-
| <code>LampBeam</code> || Modulates the material's alpha value based on angle between the beam's direction and the viewer's eye point. This is used to make the beams of volumetric light on lights fade as you look at them dead on.<br>{{Note|Must be attached to entity for angle use.}}||
No return value.
|-
| <code>LampHalo</code> || Modulates the material's alpha value based on angle between the beam's direction and the viewer's eye point.<br>Like the LampBeam proxy, but used for the halo at the beam's base.<br>{{Note|Must be attached to entity for angle use.}}||
No return value.
|-
| <code>AnimatedTexture</code> || Increments frame variable ||
''animatedtexturevar''
* Texture to increment frame for (i.e. <code>$basetexture</code>, <code>$bumpmap</code>)
''animatedtextureframenumvar''
* Frame variable to increment (i.e. <code>$frame</code>)
''animatedtextureframerate''
* Framerate in frames per second
|-
| <code>Camo</code> || {{note|Only used by material "Dev\dev_camo.vmt".}} ||
''camopatterntexture''


''camoboundingboxmin''
if ( ToolsEnabled() )
ToolFramework_RecordMaterialParams( GetMaterial() );
}
</source>


''camoboundingboxmax''
It's probably related to the [[Source Filmmaker]]. It's a good idea to add it to your proxy too in case the Filmmaker is ever released!


''surfaceprop''
{{tip|<code>CEntityMaterialProxy</code> makes the call by itself.}}
|-
 
| <code>HeliBlade</code> || ||
== Bugs ==
|-
 
| <code>ParticleSphereProxy</code> || {{note|Only used by material "particle\SmokeStack.vmt".}} Seems to be defined in <code>particle_proxies.cpp</code>. Valve remark: "FIXME:  Is this even needed any more?" ||
{{bug|hidetested=1|The first parameter in a material cannot be written to by proxies. A simple workaround is to add a dummy variable at the top of the VMT.}}
No known parameters.
{{bug|hidetested=1|Some users have reported that the tools mode will not run some functional proxies in game. Extent of bug untested.}}
|-
{{bug|hidetested=1|Using material proxies that access entity state, such as <code>EntityRandom</code> on particles or [[prop_static]], may cause the game to crash.}}
| <code>Shadow</code> || {{note|Only used by material "Decals\rendershadow.vmt".}} ||
No known parameters.
|-
| <code>ShadowModel</code> || {{note|Only used by material "Decals\rendermodelshadow.vmt".}} ||
No known parameters.
|}


== See also ==
== See also ==
* [[List Of Material Proxies]]
* [[Material proxies programming]]
* [[Material Creation]]
* [[Material Creation]]
* Steam guides, explaining more about various Proxies. [https://steamcommunity.com/sharedfiles/filedetails/?id=594255575 Basics], [https://steamcommunity.com/sharedfiles/filedetails/?id=668958242 RNG], [https://steamcommunity.com/sharedfiles/filedetails/?id=749130424 "Programming"]
== External links ==
* [https://nodraw.net/2010/01/dynamic-materials-with-proxies/ Dynamic Materials With Proxies] - An article covering practical uses of materials with proxies by [http://www.nodraw.net NoDraw.net]


[[Category:Source]]
[[Category:Material System]]
[[Category:Material System]]
[[Category:C++]]

Latest revision as of 07:07, 20 May 2025

English (en)Português do Brasil (pt-br)中文 (zh)Translate (Translate)

Material proxies allow a game's compiled C++ code to manipulate the properties of a material in-game. They can be used to create dynamic or animated textures.

Usage

Material proxies can be added to some material via its VMT file, more specificly in a KeyValues-block named Proxies; Each entry inside it is what we name a proxy. Any number of proxies can be added to a material. Proxies are executed in the order in which they appear. The game repeatedly executes the list of a material's proxies in very quick succession.[every tick?]

The name of the proxy determines what parameters it has and what the proxy does. Many proxies perform specific tasks, but there are other more general ones that together provide rudimentary scripting support within VMT files.[Clarify]

Main article:  List of material proxies

Typically, each proxy has an output value (e. g. a float number) which will be written to its resultVar which can be either a shader parameter or variable (see below for variables).

For example, the following material has a Sine proxy which generates a float number and writes it to the material's $alpha shader parameter, which makes the texture fade in and out of view over a period of eight seconds:

LightmappedGeneric
{
	$basetexture shadertest/LightmappedTexture

	Proxies // proxies are listed inside this block
	{
		Sine // a proxy which produces values of a sine wave
		{
			sineperiod	8      // the sine cycle length in seconds
			sinemin		0      // min output value
			sinemax		1      // max output value
			resultVar	$alpha // where the oscillating output value is written to
		}
	}
}

Variables

Materials can declare their own variables for internal use. Such variables must be declared outside the Proxies block, in the body of the material, and must have a default value specified on the right-hand side.

These custom variables can be used to pass results between proxies or to submit hard-coded data to them. They are often employed to chain mathematic function proxies (i. e. Add, Subtract, etc.) together into longer equations. For 2D/3D/4D vectors ($vec "[0 0 0]"), the variable name can have [0] suffixed ("$vec[0]") to read/write a specific index. Writes to indexed variables should be encased in quotes.

The engine encodes these custom variables using a 8-bit signed integer. Therefore, there is a limit of 128 unique custom variables per material.

This example extends the one above by staggering the starting position of the sine wave with a random value produced with the EntityRandom proxy:

LightmappedGeneric
{
	$basetexture shadertest/LightmappedTexture

	$offset 0 // declare custom var ($offset is not a shader parameter the game knows)
	
	Proxies
	{
		EntityRandom // generates a random number
		{
			resultVar $offset // write to custom var
		}
		Sine
		{
			resultVar	$alpha
			timeoffset	$offset // read from custom var
			sineperiod	8
			sinemin		0
			sinemax		1
		}
	}
}

Now each entity this material is used on pulses to its own schedule.

This is an example for writing to indexed variables using the $color vector to create 'random' color pulses:

	$color "[0 0 0]" // not custom, the game knows "$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]"
		}
	}


Example of a dynamic texture transform
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
		}
	}
}

Splitting a vector

Using vectors is quirky, because vector component expressions such as "$pos[0]" are not always recognized by the game.

Icon-Important.pngImportant:Quotes are needed when addressing a vector's component, or when defining a vector.

The following proxies, and only these keyvalues, can recognize vector components:

If a vector's components need be processed separately, they need to be split into different variables first. All of the above can be used to split a vector. However, Clamp is the cheapest to use.

Example
        $pos "[0 0 0]"
        $posX .0        //must be float or Clamp will not save the value properly
        $posY .0        //must be float or Clamp will not save the value properly
        $posZ .0        //must be float or Clamp will not save the value properly
        
        $zero 0
        
        //Proxy that outputs a 3d vector
        PlayerPosition
        {
                scale                    1
                resultVar               "$pos"
        }
        
        //Split the 3d vector for further use
        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
        }

Writing new proxies

New proxies are easy to create. They exist on the client only and should inherit from IMaterialProxy or one of its descendants.

You will need these #includes:

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

These functions are included in the interface:

bool Init( IMaterial* pMaterial, KeyValues* pKeyValues )
Called when the material is first precached. Use this function to initialise variables and grab references to the material vars you will be using. Return true on success and false on failure (in which case the proxy will not be run).
pKeyValues contains the proxy parameters from the VMT file.
void OnBind( void* pC_BaseEntity )
Called when the material is about to be rendered on an entity. This is where the work is done.
When coding this function it is important to remember that all entities using a material share the same material object, and that if you change it on one entity it changes everywhere else too. Since OnBind() is called every time an entity comes up for rendering this is not a problem so long as you reassign the value you want every time. Don't return early just because there has been no change, and don't store any input data in the proxy.
Note.pngNote:pC_BaseEntity doesn't lead to a C_BaseEntity as its name suggests, but rather to the associated IClientRenderable. The easiest way to access the entity directly is to base your class on CEntityMaterialProxy (in proxyentity.h) and use the OnBind(C_BaseEntity*) overload it provides.
void Release()
Todo: Called when the proxy is removed, but when is that?
IMaterial* GetMaterial()
The material the proxy is attached to.
Tip.pngTip:If you have a material var stored, you can return IMaterialVar::GetOwningMaterial() here instead of creating a new IMaterial pointer.

Interface

The proxy must expose its interface to materials with the EXPOSE_INTERFACE macro:

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

The lack of a comma between the proxy name and interface version is intentional.

Tools recording

This code was added to all proxies in the Orange Box:

#include "toolframework_client.h"

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

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

It's probably related to the Source Filmmaker. It's a good idea to add it to your proxy too in case the Filmmaker is ever released!

Tip.pngTip:CEntityMaterialProxy makes the call by itself.

Bugs

Icon-Bug.pngBug:The first parameter in a material cannot be written to by proxies. A simple workaround is to add a dummy variable at the top of the VMT.
Icon-Bug.pngBug:Some users have reported that the tools mode will not run some functional proxies in game. Extent of bug untested.
Icon-Bug.pngBug:Using material proxies that access entity state, such as EntityRandom on particles or prop_static, may cause the game to crash.

See also

External links