Shader authoring/Anatomy of a Shader/Shader States
For help, see the VDC Editing Help and Wikipedia cleanup process. Also, remember to check for any notes left by the tagger at this article's talk page.
Shadow And Dynamic State
The meat and potatoes of a shader is inside SHADOW_STATE
and DYNAMIC_STATE
blocks. Both blocks are responsible for specifying all the rendering parameters necessary to render the shader. The difference between the two is that shadow state is only called once per material, so it can not change dynamically. Dynamic state is called every time something with a specific material is rendered, so it can change its parameters over time. The material system makes a distinction between the two for optimization purposes.
Note that each SHADOW_STATE/DYNAMIC_STATE
pair is always followed by a Draw() call. Each SHADOW_STATE/DYNAMIC_STATE/Draw()
sequence represents one rendering pass for your shader. You can use multiple rendering passes to achieve blending effects or to use more textures than you otherwise would be able to.
Shadow State
Most shadow state code uses pShaderShadow
(of type IShaderShadow
) to set its state. For a comprehensive list of the things pShaderShadow supports, look at IShaderShadow
in src\public\materialsystem\ishaderapi.h
. The table below summarizes the primary things done the SHADOW_STATE
block.
Specification | Description |
---|---|
pShaderShadow-> EnableTexture | For each texture that you want to use in your shader, you need to enable that texture in shadow state. Each texture stage corresponds to a sampler inside your pixel shader's .FXC file (SHADER_TEXTURE_STAGE0 corresponds to sampler 0, and so on..) |
pShaderShadow-> SetVertexShader pShaderShadow-> SetPixelShader | This is where you specify which .FXC (or .VSH/.PSH if you're using shader assembly) files your shader will use for its vertex shader and pixel shader. Usually, you'll have one .FXC file associated with each shader C++ class, but sometimes you'll want to choose a different .FXC file, and this call gives you the flexibility to do so. See Combo Variables for a description of the second parameter to these functions. |
pShaderShadow-> VertexShaderVertexFormat | This call specifies which vertex components your .FXC code needs in order to operate.
The first parameter is a combination of the VertexFormatFlags_t flags listed in src\public\materialsystem\imaterial.h. These correspond roughly with the functions in CMeshBuilder (see src\public\materialsystem\imesh.h) used to specify vertex data. The second and third parameters specify how many texture coordinates your shader uses. The second parameter says how many texture coordinates you use, and the (optional) third parameter specifies how many components you'll use per texture coordinate (if you pass NULL for this parameter, then it will assume 2 components per texture coordinate). This also corresponds with your CMeshBuilder::TexCoordXXX calls. For example, if you need a 2D texture coordinate and also a 3D one, the code drawing the primitives would look like this: meshBuilder.TexCoord2f( 0, s, t ); meshBuilder.TexCoord3f( 1, x, y, z ); Then your call to VertexShaderVertexFormat would look like this: int vertexFlags = VERTEX_COLOR | VERTEX_POSITION; int texCoordDimensions = { 2, 3 }; int numTexCoords = 2; pShaderShadow->VertexShaderVertexFormat( vertexFlags, numTexCoords, texCoordDimensions, 0, 0 ); The fourth parameter to VertexShaderVertexFormat specifies the number of bone weights. For skinned models, this should be set to 3. For all other shaders, it can be left at 0. The last parameter specifies a user data size, and can be either 0 or 4. It corresponds to the data passed into CMeshBuilder::UserData, and can be accessed inside the VS_INPUT structure in your vertex shader like this: float4 vUserData : TANGENT; |
DefaultFog DisableFog | These functions control how your shader uses fogging. |
pShaderShadow-> EnableBlending pShaderShadow-> BlendFunc | These functions control how your shader blends with the background (ie: translucency effects). By default, blending is disabled, so whatever your shader draws is opaque. You can use these to turn on translucency and different blending modes. When you enable translucency, all pixels produced by your shader go through this equation:
[output color] = [your shader's output color] * SRC_FACTOR + [the destination pixel's current color] * DEST_FACTOR The two parameters to BlendFunc specify what SRC_FACTOR and DEST_FACTOR are. They are of typeShaderBlendFactor_t (see src\public\materialsystem\ishaderapi.h). The most common way to use translucency is standard alpha blending, where the alpha inside your texture specifies how opaque your texture is (an alpha of 1 means fully opaque and an alpha of 0 means fully translucent). In that case, you would want the equation to be: [output color] = [texture color] * [texture alpha] + [destination pixel's color] * [1 - texture alpha] and the code to produce that would be: pShaderShadow->EnableBlending( true ); pShaderShadow->BlendFunc( SHADER_BLEND_SRC_ALPHA, SHADER_BLEND_ONE_MINUS_SRC_ALPHA ); Note that SHADER_BLEND_SRC_ALPHA refers to the alpha produced by your pixel shader, which can be any value you want. Sometimes it's as simple as the alpha that was in one of the textures. |
pShaderShadow-> EnableDepthWrites | This function specifies whether your shader writes to the Z buffer or not. Usually, translucent things do not write to the Z buffer, because if they did and something tried to draw behind them, it would get masked out by the Z buffer. |
Dynamic State
Code inside the DYNAMIC_STATE block specifies rendering parameters like the SHADOW_STATE block does, but the parameters that it sets are things that are allowed to change over time. The table below lists the most common categories of things that are done inside a DYNAMIC_STATE block. For the complete list, look at the IShaderDynamicAPI class in src\public\materialsystem\ishaderapi.h.
Specification | Description |
---|---|
BindTexture
pShaderAPI->BindLightmap BindWhite BindBlack BindGrey |
These functions all tell which textures are bound to which texture samplers. They all take a parameter of type TextureStage_t, which tells which texture stage (or sampler in the pixel shader) you want the specified texture assigned to. BindTexture takes a second parameter that tells which texture to assign to the texture stage. The variable is one of your shader parameters (of type SHADER_PARAM_TYPE_TEXTURE). BindTexture also takes an optional third parameter which is another shader parameter that tells which frame of the texture you want to bind (if the texture has multiple frames built into it). |
pShaderAPI->SetVertexShaderConstant
pShaderAPI->SetPixelShaderConstant |
These functions are used to set the vertex and pixel shader constants. |
SetVertexShaderTextureTransform
SetVertexShaderTextureScaledTransform |
SetVertexShaderTextureTransform dumps a texture transform into two vertex shader constants, starting at the one you specify in the first parameter. The second parameter specifies the texture transform, which should be specified in one of your shader parameters (of type SHADER_PARAM_TYPE_MATRIX). The two vertex shader constants are setup with a 2x2 rotation/scaling matrix on the left, and the translation on the right: constant 1: [rot] [rot] 0 [u translation] constant 2: [rot] [rot] 0 [v translation] As an example, the code below would bind the default shader parameter called BASETEXTURETRANSFORM: SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_0, BASETEXTURETRANSFORM ); Then, your .FXC code would use it like this: const float4 cBaseTexCoordTransform[2] : register( SHADER_SPECIFIC_CONST_0 ); baseTexCoord.x = dot( i.texCoord, cBaseTexCoordTransform[0] ) + cBaseTexCoordTransform[0].w; baseTexCoord.y = dot( i.texCoord, cBaseTexCoordTransform[1] ) + cBaseTexCoordTransform[1].w; SetVertexShaderTextureScaledTransform is just like SetVertexShaderTextureTransform, but it takes a third parameter of type SHADER_PARAM_TYPE_FLOAT, which is a scaling factor for the matrix. |
pShaderAPI->SetVertexShaderIndex
pShaderAPI->SetPixelShaderIndex |
These functions are used to set the values of the dynamic combo variables that your shader supports. The values passed to these functions come from the classes in your shader include file. |