In-depth :: VertexShaders
>> root\raw\shader_bin\shader_src

Table of Contents

Preprocessors :: Constants :: Input-Output

VertexShaders are programmable functions in the rendering pipeline, that get executed for every vertex of a mesh.
They are used to transform the individual attributes of vertices, eg. vertex colors, normals, position, rotation, scale, lighting and most importantly,
transformations from one space (dimension) into another (eg. model-space to projection-space).

1. Preprocessors

VertexShaders need specific preprocessors at the beginning, so that global-shader-constants, defined within shader_vars.h, can be mapped correctly.
For that to work, you obv. have to include shader_vars.h too.

/* snippet */

#define PC
#define IS_VERTEX_SHADER    1 // <
#define IS_PIXEL_SHADER     0
#include <shader_vars.h>

2. Constants > root\raw\shader_bin\shader_src\shader_vars.h

Shader constants, just like Code-Textures or Code-Variables, are exposed by code and pre-defined.
Most of them are read-only and used to hold general data like:

matrices, time, lighting-information, fog-information, screen-size, parameters assigned within your technique, samplers …

Some constants defined in shader_vars.h can only be used within VertexShaders (constants explicitly registered via VERTEX_REGISTER( index )).
Note: Some of them still work within PixelShaders (eg. renderTargetSize). What this should tell you is, that shader_vars.h is not
a stock file and not 100% correct. Using matrices within PixelShaders will def. throw an error … trust me, I tried.

3. Input and Output - structs

The vertex input-struct has to include all semantics that are defined within the technique that called your shader. See » [Technique/Semantics]
Using structs to manage your In- and Output is advised, tho there are different ways of managing your data.

/* snippet */

#include <shader_vars.h>

struct VertexInput  // struct that holds the input data declared within a technique
{
    float4 position : POSITION;     // declare Semantic "POSITION" as position (vertex position)
    float4 normal   : NORMAL;       // ^ (vertex normal)
    float4 color    : COLOR;        // ^ (vertex color)
    float2 texcoord : TEXCOORD0;    // ^ (vertex UV as float2 because we only need the x and y)
};

struct VertexOutput // vertex shader output data (used as input for the pixel shader)
{
    float4 position : POSITION;     // the transformed vertex position (not usable in the pixel shader)
    float4 normal   : NORMAL;       // eg. we want to use vertex normal within the pixelshader
    float4 color    : COLOR;        // ^
    float2 texcoord : TEXCOORD0;    // ^
};

the above is an example, using a technique with the following semantics defined:

/* snippet */

vertex.position     = code.position;    // vertex position in model-space
vertex.normal       = code.normal;      // vertex normal 
vertex.color[0]     = code.color;       // vertex color
vertex.texcoord[0]  = code.texcoord[0]; // vertex uv

using structs requires your vs_main function to return a completely initialized output struct:

Example:

/* ... */
#include <shader_vars.h>

struct VertexInput
{
    float4 position : POSITION;
    float4 normal   : NORMAL;
    float4 color    : COLOR;
    float2 texcoord : TEXCOORD0;
};

struct VertexOutput
{
    float4 position : POSITION;
    float4 color    : COLOR;
    float4 normal   : NORMAL;
    float2 texcoord : TEXCOORD0;
};

VertexOutput vs_main( const VertexInput input )  // vs_main returns the output-struct; we also reference our input-struct as "input"
{
    // create the output-struct object
    VertexOutput output;   

    // transform the vertex from model to projection-space and put the result into the output-struct member "position"
    output.position = mul(float4(input.position.xyz, 1.0f), worldViewProjectionMatrix); 
	
    // we need to pass input parameters into the pixelshader if they werent used for any calculations for other ouput-struct members
    output.color = input.color;
    output.normal = input.normal;
    output.texcoord = input.texcoord;

    // return the fully initialized struct
    return pixel;
}

if you only need some of the input parameters within the VertexShader, eg. to modify vertex positions, you don’t need to pass them to your PixelShader:

Example:

/* ... */

struct VertexOutput
{
    float4 position : POSITION; 
    //float4 color    : COLOR;  // we only use color inside the vertex shader to modify vertex positions
    float4 normal   : NORMAL;       
    float2 texcoord : TEXCOORD0;
};

VertexOutput vs_main( const VertexInput input ) 
{
    VertexOutput output;   

    float waveStrength = 20.0f;
    
    // animate the vertex z-coord by its alpha channel value (xmodels that use vertex-colors like flags or moving grass) 
    float modZ = input.position.z + (input.color.a * (sin(gameTime.w) * waveStrength));
    
    // transform the vertex with its stock xy-coords and modified z-coord
    output.position = mul(float4(input.position.xy, modZ, 1.0f), worldViewProjectionMatrix); 
	
    //output.color = input.color; // we no longer pass color as we only needed it inside the vertex shader
    output.normal = input.normal;
    output.texcoord = input.texcoord;

    // return the fully initialized struct
    return pixel;
}

You can use the TEXCOORD 0-6 Semantic if you want to use certain values or constants within your PixelShader,

that are only accessible within the VertexShader stage:

Example:

struct VertexInput
{
    float4 position : POSITION;     // vertex position
    float2 texcoord : TEXCOORD0;    // vertex uv
};

struct VertexOutput
{
    float4 position : POSITION;     // transformed vertex position
    float2 texcoord : TEXCOORD0;    // vertex uv
    float4 worldPos : TEXCOORD1;    // pass the untransformed vertex position inside TEXCOORD1
    float4 eyePos   : TEXCOORD2;    // pass the eyePos
};

VertexOutput vs_main( const VertexInput input ) 
{
    VertexOutput output;   

    output.position = mul(float4(input.position.xyz, 1.0f), worldViewProjectionMatrix); 
    
    output.texcoord = input.texcoord;
    output.worldPos = input.position;           // if you need untransformed vertex positions within your pixelshader
    output.eyePos   = inverseViewMatrix[3];     // if you need the players eye position (in world space)

    // return the fully initialized struct
    return pixel;
}

Techniques :: PixelShader