A while ago Neophyte started a fantastic thread to provide a complete knowledge reference for working with pixel and vertex shaders in DarkBASIC Professional. The thread has expired now, but all that great information shouldn't be lost, so I'm making a new one with all of his tutorials and some additional gems from that same thread.
Due to the sheer bulk of all the information he and others posted I will be presenting all of the information in a few posts.
Feel free to post here with questions or knowledge about shaders.
Here is Neophyte's original introduction.
Shaders are programs that run exclusively on modern graphics hardware. However, there are many different profiles for shaders which dictate what graphics hardware they can run and what they are capable of.
Profiles consist of different instruction sets that have varying capablities. They are usually refered to by number. For example, one of the earlier vertex shader versions is called vertex shader 1.1 or VS_1_1 or vs 1.1 for short. Shader profile is one of the most common reasons that a shader won't run on your hardware.
A complete list of profiles is as follows.
Vertex shader 1.1
Vertex shader 2.0
Pixel shader 1.1
Pixel shader 1.2
Pixel shader 1.3
Pixel shader 1.4
Pixel shader 2.0
Generally, if a card can support a profile it can support all profiles below it. For example, the Geforce 4 ti 4200 supports Pixel shader 1.3. So it also supports Pixel shader 1.2 and Pixel shader 1.1.
For information on what profile your graphics hardware supports go to the website of the graphics card and it will usually tell you.
Here's a general reference as to what supports what. Cards newer than the listed cards here almost always contain all of the profiles listed for the older cards.
Nvidia Geforce 3 ti - VS 1.1, PS 1.1
Notes:
One of the earliest shader capable cards out there. It supports the basic bare minimum shader profiles and Nvidia cards older than this do not support shaders whatsoever.
ATI Radeon 8500 - VS 1.1, PS 1.1, 1.2, 1.3, 1.4
Notes:
Came out 6 months after the Geforce 3 ti. This is ATI's entry into the shader capable graphics card market. It supports the basic vertex shader profile as well as several new pixel shader profiles most noteably PS 1.4.
Nvidia Geforce 4 ti - VS 1.1, PS 1.1, 1.2, 1.3
Notes:
Adds several new pixel shader versions to the geforce series. Does not support pixel shader 1.4 as this was ATI's creation.
ATI Radeon 9500 - VS 1.1, 2.0 PS 1.1, 1.2, 1.3, 1.4, 2.0
Notes:
Added VS 2.0 and PS 2.0 to the Radeon series. This card and all other Radeons after it should be able to run any shader out there currently.
Nvidia GeforceFX 5200 - VS 1.1, 2.0, PS 1.x, 2.0
Notes:
This card and all other Geforces after it should run almost any shader that you can find out there.
Nvidia GeforceFX 5700 - VS 1.1, 2.x PS 1.x, 2.x
Notes:
The GeforceFX 5700 and up(with the exception of the 5800) add new extensions to the 2.0 model for both vertex and pixel shaders which makes them pixel and vertex shader 2.x capable.
Nvidia Geforce 6800 - VS 1.1, 2.x, 3.0 PS 1.x, 2.x, 3.0
Notes:
Adds the new Shader Model 3.0(VS and PS 3.0) to the Geforce series.
A special note about Geforce 4 MX:
This is the one card that breaks the rule of newer cards supporting the profiles of older cards. This card doesn't support any of the pixel shader profiles and it only supports VS 1.0 which was a vertex shader profile that was never intended to be used in graphics hardware. Its a terrible card that is nothing more than a souped up geforce 2 and I'd recommend avoiding it and buying something far better like a GeforceFX 5200 which is around the same price as Geforce 4 MX but absolutely blows it away in terms of performance and features. I'd even recommend getting the older Geforce 3 ti over the MX.
What profile a shader uses is largely determined by what instructions or how many instructions it uses. Shaders in the .fx file format have their profiles explicitly stated in the .fx file so determining what profile a shader uses is a piece of cake. Shaders that are not in the .fx file format(which we will deal with shortly) are another story.
Shaders written in assembly have what profile they use at the top of their code in abbrevated form. So if you see a VS 1.1 at the top of an assembly shader than it means that it uses the Vextex Shader 1.1 profile.
Shaders written in HLSL, High Level Shader Langauge, do not have their profile explicitly stated in them if they are not in the .fx file format. These are a lot harder to determine whether they work for one profile or not so your best hope of finding out is to look at whatever information came with it if you got it off the net or if that fails just try compiling it with a shader compiler under various profiles. If you are really knowledgable about shaders you could determine what profile it uses by spotting any instructions that are specific to that particular profile but this won't always work as a shader could be of a certain profile because of instruction count limits and not because of instructions used.
DBPro doesn't deal with straight HLSL shader files, though it can deal with assembly files. So concerns about shader profile won't really factor in until you decided to convert an HLSL shader to the .fx file format.
Resources for shader developers
RenderMonkey from ATi
http://www.ati.com/developer/rendermonkey/
FX Composer from nVidia
http://developer.nvidia.com/object/fx_composer_home.html
Other resources, posted by Neophyte
If you want to move ahead here are a few more resources for you to look over. I don't have the time to go over them in detail unfortunately so I'm afraid you'll be on your on for the time being.
From issue 9 of the DBPro news letter:
http://www.fairyengine.com/articles/hlsl2sided.htm
This one deals with pixel shaders, how to set them up in DirectX 8(not very useful for us) and the pixel shading asm language(useful for us). Note: I haven't had the time to go over how to integrate ASM shaders into the FX file system but it is really easy. For a good example of how check out the bump.fx shader that came with DBPro and that I posted above.
http://www.cfxweb.net/modules.php?name=News&file=article&sid=1305
This one is from Microsoft. Deals with HLSL and hemispherical lighting:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndrive/html/directx02192001.asp
Here is a power point slide by Wolfgang Engel dealing with HLSL:
http://www.shaderx.com/direct3d.net/GDC2003/index.html
Here is another tutorial dealing with HLSL this time using RenderMonkey, a tool for developing shaders by ATI. Note: The .fx export for the current version of RenderMonkey is broken. Don't bother trying to export shaders from RenderMonkey because it would be quicker to create the .fx file by hand and paste your source into it than to have to modify the .fx file produced.
http://www.shaderx.com/direct3d.net/tutorials/RenderMonkey%20Movies/1Steps/RenderMonkey.htm
And to top it off, here is the page where I found the above two tutorials. It contains another four tutorials, Fundamentals of Vertex shaders, Programming Vertex Shaders, Fundamentals of Pixel Shaders, and Programming Pixel Shaders that might be of interest to you.
http://www.shaderx.com/direct3d.net/index.html
Its tough finding good material thats meant for absolute newbies. A lot of this stuff makes the assumption that you are familar with the graphics pipeline and how it works. I learned about this through another book called "Real Time Rendering Second edition". The best that I can offer is a link to its massive resource page.
http://www.realtimerendering.com/
A good start would be under Rendering Pipeline. There is a link that details the Direct3D rendering pipeline as well as the pipeline for OpenGL. I know its an extremely dense read and a bit much to a hit a beginner with, but it is all I can come up with for the time being. I haven't really had the time lately to spend on these forums.
The old thread for reference purposes:
http://forum.thegamecreators.com/?m=forum_view&t=28992&b=1&p=5
Neophyte's First Tutorial:
THE FX FILE FORMAT - PART ONE - BUMPREFLECTHLSL SHADER IN DBPRO
The .fx file format is a simple, easy, to use format that encapsulates a number of parameters required to run a shader. These parameters include, but are not limited to, the shaders themselves, various techniques, the number of passes to be made, textures used, models used, constants, render states, sampler states, shader states, light properties, materials, etc. Basically, almost anything that can make up a graphical scene is or can be included in a .fx file.
In this tutorial, I will cover how to set constants, loading in the media that DBPro requires to run shaders as well as a brief overview of the FX file. The specific shader that we will be using is the BumpReflectHLSL shader that comes with Nvidia's FX Composer. The Media for this tutorial can also be found with Nvidia's FX Composer.
Here is the BumpReflectHLSL shader in .fx format:
/*********************************************************************NVMH3****
File: $Id: //sw/devrel/SDK/MEDIA/HLSL/BumpReflectHLSL.fx#4 $
Copyright NVIDIA Corporation 2002
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED
*AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS
BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES
WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS)
ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF NVIDIA HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
Comments:
Bumped reflection, DX9
******************************************************************************/
/// un-tweakables //////////////////////
float4x4 worldMatrix : World < string UIWidget="None"; >; // World or Model matrix
float4x4 wvpMatrix : WorldViewProjection < string UIWidget="None"; >; // Model*View*Projection
float4x4 worldViewMatrix : WorldView < string UIWidget="None"; >;
float4x4 worldViewMatrixI : WorldViewInverse < string UIWidget="None"; >;
float4x4 viewInverseMatrix : ViewInverse < string UIWidget="None"; >;
float4x4 viewMatrix : View < string UIWidget="None"; >;
// Tweakables /////////////////
float bumpHeight
<
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 2.0;
float UIStep = 0.1;
> = 0.5;
///////////////////
texture normalMap : NORMAL
<
string ResourceName = "default_bump_normal.dds";
string TextureType = "2D";
>;
texture cubeMap : ENVIRONMENT
<
string ResourceName = "nvlobby_cube_mipmap.dds";
string TextureType = "Cube";
>;
sampler2D normalMapSampler = sampler_state
{
Texture = <normalMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
};
samplerCUBE envMapSampler = sampler_state
{
Texture = <cubeMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
};
/////////////////////////////
struct a2v {
float4 Position : POSITION; //in object space
float3 TexCoord : TEXCOORD0;
float3 Tangent : TANGENT0; //in object space
float3 Binormal : BINORMAL0; //in object space
float3 Normal : NORMAL; //in object space
};
struct v2f {
float4 Position : POSITION; //in projection space
float4 TexCoord : TEXCOORD0;
float4 TexCoord1 : TEXCOORD1; //first row of the 3x3 transform from tangent to cube space
float4 TexCoord2 : TEXCOORD2; //second row of the 3x3 transform from tangent to cube space
float4 TexCoord3 : TEXCOORD3; //third row of the 3x3 transform from tangent to cube space
};
///////////////// vertex shader //////////////////
v2f BumpReflectVS(a2v IN,
uniform float4x4 WorldViewProj,
uniform float4x4 World,
uniform float4x4 ViewIT)
{
v2f OUT;
// Position in screen space.
OUT.Position = mul(IN.Position, WorldViewProj);
// pass texture coordinates for fetching the normal map
OUT.TexCoord.xyz = IN.TexCoord;
OUT.TexCoord.w = 1.0;
// compute the 4x4 tranform from tangent space to object space
float3x3 TangentToObjSpace;
// first rows are the tangent and binormal scaled by the bump scale
TangentToObjSpace[0] = float3(IN.Tangent.x, IN.Binormal.x, IN.Normal.x);
TangentToObjSpace[1] = float3(IN.Tangent.y, IN.Binormal.y, IN.Normal.y);
TangentToObjSpace[2] = float3(IN.Tangent.z, IN.Binormal.z, IN.Normal.z);
OUT.TexCoord1.x = dot(World[0].xyz, TangentToObjSpace[0]);
OUT.TexCoord1.y = dot(World[1].xyz, TangentToObjSpace[0]);
OUT.TexCoord1.z = dot(World[2].xyz, TangentToObjSpace[0]);
OUT.TexCoord2.x = dot(World[0].xyz, TangentToObjSpace[1]);
OUT.TexCoord2.y = dot(World[1].xyz, TangentToObjSpace[1]);
OUT.TexCoord2.z = dot(World[2].xyz, TangentToObjSpace[1]);
OUT.TexCoord3.x = dot(World[0].xyz, TangentToObjSpace[2]);
OUT.TexCoord3.y = dot(World[1].xyz, TangentToObjSpace[2]);
OUT.TexCoord3.z = dot(World[2].xyz, TangentToObjSpace[2]);
float4 worldPos = mul(IN.Position, World);
// compute the eye vector (going from shaded point to eye) in cube space
float4 eyeVector = worldPos - ViewIT[3]; // view inv. transpose contains eye position in world space in last row.
OUT.TexCoord1.w = eyeVector.x;
OUT.TexCoord2.w = eyeVector.y;
OUT.TexCoord3.w = eyeVector.z;
return OUT;
}
///////////////// pixel shader //////////////////
float4 BumpReflectPS(v2f IN,
uniform sampler2D NormalMap,
uniform samplerCUBE EnvironmentMap,
uniform float BumpScale) : COLOR
{
// fetch the bump normal from the normal map
float3 normal = tex2D(NormalMap, IN.TexCoord.xy).xyz * 2.0 - 1.0;
normal = normalize(float3(normal.x * BumpScale, normal.y * BumpScale, normal.z));
// transform the bump normal into cube space
// then use the transformed normal and eye vector to compute a reflection vector
// used to fetch the cube map
// (we multiply by 2 only to increase brightness)
float3 eyevec = float3(IN.TexCoord1.w, IN.TexCoord2.w, IN.TexCoord3.w);
float3 worldNorm;
worldNorm.x = dot(IN.TexCoord1.xyz,normal);
worldNorm.y = dot(IN.TexCoord2.xyz,normal);
worldNorm.z = dot(IN.TexCoord3.xyz,normal);
float3 lookup = reflect(eyevec, worldNorm);
return texCUBE(EnvironmentMap, lookup);
}
//////////////////////////////// technique ////////////////
technique BumpReflect0
{
pass p0
{
VertexShader = compile vs_2_0 BumpReflectVS(wvpMatrix,worldMatrix,viewInverseMatrix);
Zenable = true;
ZWriteEnable = true;
CullMode = None;
PixelShader = compile ps_2_0 BumpReflectPS(normalMapSampler,envMapSampler,bumpHeight);
}
}
technique ReflectNoPixelShader
{
pass p0
{
Zenable = true;
ZWriteEnable = true;
CullMode = None;
NormalizeNormals = true;
LocalViewer = true;
TextureFactor = 0x008080ff; // 808080FF == 0,0,0,1
TexCoordIndex[ 1 ] = 1 | CameraSpaceReflectionVector;
TextureTransform[ 1 ] = <worldViewMatrix>;
TextureTransformFlags[1] = Count3;
Texture[1] = <cubeMap>;
MinFilter[1] = Linear;
MagFilter[1] = Linear;
MipFilter[1] = Point;
Texture[0] = <normalMap>;
MinFilter[0] = Linear;
MagFilter[0] = Linear;
MipFilter[0] = Point;
ColorOp[0] = DotProduct3;
ColorArg1[0] = Texture;
ColorArg2[0] = TFactor;
AlphaOp[0] = SelectArg1;
AlphaArg1[0] = Texture;
AlphaArg2[0] = Current;
ColorOp[1] = Modulate;
ColorArg1[1] = Texture;
ColorArg2[1] = Current;
AlphaOp[1] = SelectArg1;
AlphaArg1[1] = Current;
AlphaArg2[1] = Current;
}
pass p1
{
Zenable = true;
ZWriteEnable = false;
CullMode = None;
NormalizeNormals = true;
LocalViewer = true;
TextureFactor = 0x0000ff80;
TextureTransform[ 1 ] = <worldViewMatrixI>;
TexCoordIndex[ 1 ] = 1 | CameraSpaceReflectionVector;
TextureTransformFlags[1] = Count3;
Texture[1] = <cubeMap>;
MinFilter[1] = Linear;
MagFilter[1] = Linear;
MipFilter[1] = Point;
Texture[0] = <normalMap>;
MinFilter[0] = Linear;
MagFilter[0] = Linear;
MipFilter[0] = Point;
ColorOp[0] = DotProduct3;
ColorArg1[0] = Texture;
ColorArg2[0] = TFactor;
AlphaOp[0] = SelectArg1;
AlphaArg1[0] = Texture;
AlphaArg2[0] = Current;
ColorOp[1] = Modulate;
ColorArg1[1] = Texture;
ColorArg2[1] = Current;
AlphaOp[1] = SelectArg2;
AlphaArg1[1] = Texture;
AlphaArg2[1] = Current;
SrcBlend = SrcAlpha;
DestBlend = InvSrcAlpha;
AlphaBlendEnable = true;
}
pass p2
{
Zenable = true;
ZWriteEnable = false;
CullMode = None;
NormalizeNormals = true;
LocalViewer = true;
TextureFactor = 0x00ff0080;
TextureTransform[ 1 ] = <worldViewMatrixI>;
TexCoordIndex[ 1 ] = 1 | CameraSpaceReflectionVector;
TextureTransformFlags[1] = Count3;
Texture[1] = <cubeMap>;
MinFilter[1] = Linear;
MagFilter[1] = Linear;
MipFilter[1] = Point;
Texture[0] = <normalMap>;
MinFilter[0] = Linear;
MagFilter[0] = Linear;
MipFilter[0] = Point;
ColorOp[0] = DotProduct3;
ColorArg1[0] = Texture;
ColorArg2[0] = TFactor;
AlphaOp[0] = SelectArg1;
AlphaArg1[0] = Texture;
AlphaArg2[0] = Current;
ColorOp[1] = Modulate;
ColorArg1[1] = Texture;
ColorArg2[1] = Current;
AlphaOp[1] = SelectArg2;
AlphaArg1[1] = Texture;
AlphaArg2[1] = Current;
SrcBlend = SrcAlpha;
DestBlend = InvSrcAlpha;
AlphaBlendEnable = true;
}
}
/////////////////////// eof ///
It can be broken up roughly into Seven sections.
The first section is called "un-tweakables."
/// un-tweakables //////////////////////
float4x4 worldMatrix : World < string UIWidget="None"; >; // World or Model matrix
float4x4 wvpMatrix : WorldViewProjection < string UIWidget="None"; >; // Model*View*Projection
float4x4 worldViewMatrix : WorldView < string UIWidget="None"; >;
float4x4 worldViewMatrixI : WorldViewInverse < string UIWidget="None"; >;
float4x4 viewInverseMatrix : ViewInverse < string UIWidget="None"; >;
float4x4 viewMatrix : View < string UIWidget="None"; >;
The un-tweakables are a series of constants that can't be manipulated by hand. You can't just increase one of the values in them by one and expect to see coherent results. These un-tweakables usually consist of a series matricies that are necessary to display 3D models on the screen. An example of one of these maticies would be this:
float4x4 worldMatrix : World < string UIWidget="None"; >;
This section of code can be split up into four parts: type, variable name, semantic, and annotion.
The float4x4 part is the type. This denotes the fact that the variable is a 4 by 4 array of floating point numbers. Most matricies are size float4x4 though they can be smaller.
The worldMatrix part is the variable name. The variable name always comes directly after the type. This is important as we might need this variable name later on if we need to set the constant manually. Setting a constant manually is usually necessary when DBPro can't set it for you due to an unrecognized semantic.
Which leads us to Semantics, the next part of in this section of code. The part that comes after the ":" symbol is the Semantic; in this case it is World. DBPro recognizes a certain set of case-insenstive semantics and will supply the necessary data for them. The current list of supported semantics as of Upgrade 5.2 is as follows:
World
View
Projection
WorldView
WorldViewProjection
WorldIT
ViewIT
The final part of this section of code is the annotion. It consists of everything between the < and > symbols. Annotions are optional and are entirely application specific. There is no current standard for what can be in an annotion. They are mainly used by content creation applications like FX Composer to provide additional information like what kind of User Interface gadget the constant can use if any. You'll see in this section of code that the UI widget is set to "none." That's because matricies like these can't be easily manipulated due to the complex math behind making a valid matrix.
The next section of the .FX file is called "tweakables".
// Tweakables /////////////////
float bumpHeight
<
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 2.0;
float UIStep = 0.1;
> = 0.5;
There is only one in this file, bumpHeight, but there is usually more. As you can see, bumpHeight is of type "float" and is to be set to 0.5. DBPro, as of upgrade 5.2, won't set it to 0.5 though. You'll have to set bumpHeight manually.
The next section of the .FX file format contains the textures to be loaded and their respective sampler states.
///////////////////
texture normalMap : NORMAL
<
string ResourceName = "default_bump_normal.dds";
string TextureType = "2D";
>;
texture cubeMap : ENVIRONMENT
<
string ResourceName = "nvlobby_cube_mipmap.dds";
string TextureType = "Cube";
>;
sampler2D normalMapSampler = sampler_state
{
Texture = <normalMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
};
samplerCUBE envMapSampler = sampler_state
{
Texture = <cubeMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
};
This section will need a little modifying as DBPro, as of Upgrade 5.2, doesn't support FX Composers naming conventions. You'll have to change the line:
string Resource = "default_bump_normal.dds";
to:
string name = "default_bump_normal.dds";
Basically, you change "Resource" to "name" and DBPro will understand that you want to load a texture. Do the same for the cube map so it's line looks like this:
string name = "nvlobby_cube_mipmap.dds";
These two textures are included with FX Composer. You'll find them under MEDIA->HLSL_textures and MEDIA->textures->cubemaps respectively. Make sure that you include these files in the same directory as your shader so they load with it.
The section after the previous one holds the structures that are passed into and out of the Pixel and Vertex shader if any.
struct a2v {
float4 Position : POSITION; //in object space
float3 TexCoord : TEXCOORD0;
float3 Tangent : TANGENT0; //in object space
float3 Binormal : BINORMAL0; //in object space
float3 Normal : NORMAL; //in object space
};
struct v2f {
float4 Position : POSITION; //in projection space
float4 TexCoord : TEXCOORD0;
float4 TexCoord1 : TEXCOORD1; //first row of the 3x3 transform from tangent to cube space
float4 TexCoord2 : TEXCOORD2; //second row of the 3x3 transform from tangent to cube space
float4 TexCoord3 : TEXCOORD3; //third row of the 3x3 transform from tangent to cube space
};
These really aren't very important for our purposes right now so I'll skip over them.
The next two sections are the vertex shader and the pixel shader respectively.
///////////////// vertex shader //////////////////
v2f BumpReflectVS(a2v IN,
uniform float4x4 WorldViewProj,
uniform float4x4 World,
uniform float4x4 ViewIT)
{
v2f OUT;
// Position in screen space.
OUT.Position = mul(IN.Position, WorldViewProj);
// pass texture coordinates for fetching the normal map
OUT.TexCoord.xyz = IN.TexCoord;
OUT.TexCoord.w = 1.0;
// compute the 4x4 tranform from tangent space to object space
float3x3 TangentToObjSpace;
// first rows are the tangent and binormal scaled by the bump scale
TangentToObjSpace[0] = float3(IN.Tangent.x, IN.Binormal.x, IN.Normal.x);
TangentToObjSpace[1] = float3(IN.Tangent.y, IN.Binormal.y, IN.Normal.y);
TangentToObjSpace[2] = float3(IN.Tangent.z, IN.Binormal.z, IN.Normal.z);
OUT.TexCoord1.x = dot(World[0].xyz, TangentToObjSpace[0]);
OUT.TexCoord1.y = dot(World[1].xyz, TangentToObjSpace[0]);
OUT.TexCoord1.z = dot(World[2].xyz, TangentToObjSpace[0]);
OUT.TexCoord2.x = dot(World[0].xyz, TangentToObjSpace[1]);
OUT.TexCoord2.y = dot(World[1].xyz, TangentToObjSpace[1]);
OUT.TexCoord2.z = dot(World[2].xyz, TangentToObjSpace[1]);
OUT.TexCoord3.x = dot(World[0].xyz, TangentToObjSpace[2]);
OUT.TexCoord3.y = dot(World[1].xyz, TangentToObjSpace[2]);
OUT.TexCoord3.z = dot(World[2].xyz, TangentToObjSpace[2]);
float4 worldPos = mul(IN.Position, World);
// compute the eye vector (going from shaded point to eye) in cube space
float4 eyeVector = worldPos - ViewIT[3]; // view inv. transpose contains eye position in world space in last row.
OUT.TexCoord1.w = eyeVector.x;
OUT.TexCoord2.w = eyeVector.y;
OUT.TexCoord3.w = eyeVector.z;
return OUT;
}
///////////////// pixel shader //////////////////
float4 BumpReflectPS(v2f IN,
uniform sampler2D NormalMap,
uniform samplerCUBE EnvironmentMap,
uniform float BumpScale) : COLOR
{
// fetch the bump normal from the normal map
float3 normal = tex2D(NormalMap, IN.TexCoord.xy).xyz * 2.0 - 1.0;
normal = normalize(float3(normal.x * BumpScale, normal.y * BumpScale, normal.z));
// transform the bump normal into cube space
// then use the transformed normal and eye vector to compute a reflection vector
// used to fetch the cube map
// (we multiply by 2 only to increase brightness)
float3 eyevec = float3(IN.TexCoord1.w, IN.TexCoord2.w, IN.TexCoord3.w);
float3 worldNorm;
worldNorm.x = dot(IN.TexCoord1.xyz,normal);
worldNorm.y = dot(IN.TexCoord2.xyz,normal);
worldNorm.z = dot(IN.TexCoord3.xyz,normal);
float3 lookup = reflect(eyevec, worldNorm);
return texCUBE(EnvironmentMap, lookup);
}
Now there can be more than one vertex and/or pixel shader in the .FX file or than can be none at all(which will be covered in a subsequant tutorial). These sections will only exist for HLSL shaders. Assembly shaders are contained in another section.
The final section of the .FX file is the technique section.
//////////////////////////////// technique ////////////////
technique BumpReflect0
{
pass p0
{
VertexShader = compile vs_2_0 BumpReflectVS(wvpMatrix,worldMatrix,viewInverseMatrix);
Zenable = true;
ZWriteEnable = true;
CullMode = None;
PixelShader = compile ps_2_0 BumpReflectPS(normalMapSampler,envMapSampler,bumpHeight);
}
}
technique ReflectNoPixelShader
{
pass p0
{
Zenable = true;
ZWriteEnable = true;
CullMode = None;
NormalizeNormals = true;
LocalViewer = true;
TextureFactor = 0x008080ff; // 808080FF == 0,0,0,1
TexCoordIndex[ 1 ] = 1 | CameraSpaceReflectionVector;
TextureTransform[ 1 ] = <worldViewMatrix>;
TextureTransformFlags[1] = Count3;
Texture[1] = <cubeMap>;
MinFilter[1] = Linear;
MagFilter[1] = Linear;
MipFilter[1] = Point;
Texture[0] = <normalMap>;
MinFilter[0] = Linear;
MagFilter[0] = Linear;
MipFilter[0] = Point;
ColorOp[0] = DotProduct3;
ColorArg1[0] = Texture;
ColorArg2[0] = TFactor;
AlphaOp[0] = SelectArg1;
AlphaArg1[0] = Texture;
AlphaArg2[0] = Current;
ColorOp[1] = Modulate;
ColorArg1[1] = Texture;
ColorArg2[1] = Current;
AlphaOp[1] = SelectArg1;
AlphaArg1[1] = Current;
AlphaArg2[1] = Current;
}
pass p1
{
Zenable = true;
ZWriteEnable = false;
CullMode = None;
NormalizeNormals = true;
LocalViewer = true;
TextureFactor = 0x0000ff80;
TextureTransform[ 1 ] = <worldViewMatrixI>;
TexCoordIndex[ 1 ] = 1 | CameraSpaceReflectionVector;
TextureTransformFlags[1] = Count3;
Texture[1] = <cubeMap>;
MinFilter[1] = Linear;
MagFilter[1] = Linear;
MipFilter[1] = Point;
Texture[0] = <normalMap>;
MinFilter[0] = Linear;
MagFilter[0] = Linear;
MipFilter[0] = Point;
ColorOp[0] = DotProduct3;
ColorArg1[0] = Texture;
ColorArg2[0] = TFactor;
AlphaOp[0] = SelectArg1;
AlphaArg1[0] = Texture;
AlphaArg2[0] = Current;
ColorOp[1] = Modulate;
ColorArg1[1] = Texture;
ColorArg2[1] = Current;
AlphaOp[1] = SelectArg2;
AlphaArg1[1] = Texture;
AlphaArg2[1] = Current;
SrcBlend = SrcAlpha;
DestBlend = InvSrcAlpha;
AlphaBlendEnable = true;
}
pass p2
{
Zenable = true;
ZWriteEnable = false;
CullMode = None;
NormalizeNormals = true;
LocalViewer = true;
TextureFactor = 0x00ff0080;
TextureTransform[ 1 ] = <worldViewMatrixI>;
TexCoordIndex[ 1 ] = 1 | CameraSpaceReflectionVector;
TextureTransformFlags[1] = Count3;
Texture[1] = <cubeMap>;
MinFilter[1] = Linear;
MagFilter[1] = Linear;
MipFilter[1] = Point;
Texture[0] = <normalMap>;
MinFilter[0] = Linear;
MagFilter[0] = Linear;
MipFilter[0] = Point;
ColorOp[0] = DotProduct3;
ColorArg1[0] = Texture;
ColorArg2[0] = TFactor;
AlphaOp[0] = SelectArg1;
AlphaArg1[0] = Texture;
AlphaArg2[0] = Current;
ColorOp[1] = Modulate;
ColorArg1[1] = Texture;
ColorArg2[1] = Current;
AlphaOp[1] = SelectArg2;
AlphaArg1[1] = Texture;
AlphaArg2[1] = Current;
SrcBlend = SrcAlpha;
DestBlend = InvSrcAlpha;
AlphaBlendEnable = true;
}
}
There can be any number of different techniques in a .FX file but there most always be at least one. There are two techniques in this paticular shader, BumpReflect0 and ReflectNoPixelShader. BumpReflect0 contains two shaders that use profiles pixel shader 2.0 and vertex shader 2.0. This is the high quality version of the effect. The second technique, ReflectNoPixelShader, is a lower quality version of the effect and will run on most cards. Its image quality is based on how well tessellated the model is though. DBPro will operate on the first technique if your card can run it. If it can't it will move to the next available technique and see if it can run that. So no need to switch techniques to get what version you need to run it on a card, unless you are deliberately running the technique below the cards capabilites to boost your games performance. Or running a different effect using the shaders in the effect file. The choice is your's.
But enough talk. Lets get to some code.
In order to figuare out quickly what constant's you are going to need to set start a new project and type in this code:
load effect "BumpReflectHLSL.fx",1,1
make object sphere 1,10,100,100 : `Make it highly tessellated.
set object effect 1,1
PERFORM CHECKLIST FOR EFFECT VALUES 1
do
FOR C=1 TO CHECKLIST QUANTITY()
PRINT "Name:";CHECKLIST STRING$(C);
PRINT " Vartype:";CHECKLIST VALUE (C);
PRINT " Hook:";CHECKLIST VALUE B(C)
NEXT C
loop
This will print all of the constants to the screen. The important part to notice is the value of "Hook: ". It will be 1 if DBPro knows how to set the value and 0 if it doesn't. A gentle reminder: Make sure that you have the media for this shader in the same directory as the shader before you run this code.
Now you should see a 0 value after the hook for worldViewMatrixI, viewInverseMatrix, and bumpHeight. Will deal with bumpHeight first because it is the easiest. In order to set the bumpHeight constant all you have to do is type in the following code before the do loop:
set effect constant float 1,"bumpHeight", 0.5
Now we know that bumpHeight is of value float due to "VarType: " having a value of 3. The value of VarType corresponds to what type the constant expects. Here are the values and their corresponding Types:
Value: 0 Type: Undetermined. This occurs for textures and sampler states. Since we don't need to be set anything with a Value of 0 these should be ignored.
Value: 1 Type: Boolean.
Value: 2 Type: Integer.
Value: 3 Type: Float.
Value: 4 Type: Vector.
Value: 5 Type: Matrix.
Now we move on to the two matricies that need to be set, worldViewMatrixI and viewInverseMatrix. Lets start with worldViewMatrixI.
Figuaring out what these matrices need is usually pretty easy as most coders name their variables after the order in which the matrices are multipled or operated on. For example, with worldViewMatrixI we can deduce that we will need a world matrix and a view matrix to multiple together and an inverse operation to perform on the result. So write in the following code above the do loop to set up the matrices we will need for this constant:
null = make matrix4(1)
null = make matrix4(2)
Here we are making two 4x4 matricies numbered 1 and 2. The null return value is not important. It will always be 1 and isn't really needed other than to fill out the requirement of a variable for the make matrix4 to return its result into.
Next up is filling in these matricies. Now the DBPro 3DMath command set has a large selection of possible matrices to choose from. We want two paticular matrices, World and View, so we use the World Matrix4 and the View Matrix4 commands:
world matrix4 1
view matrix4 2
Its as easy that. We now have our matricies filled out so lets multiply them.
DBpro's 3Dmath command set is really simple so don't let other people fool you into thinking it's too tough to approach. Take the above command for example. All we did was multiply matrix 1(our world matrix) by matrix 2(our view matrix) and store the result back into matrix 1. So now we have matrix 1, which contains world*view, and matrix 2 which contains just view. Pretty simple, no?
All we have to do now is perform one last operation on matrix 1 and we are ready to set our first matrix constant. Type this in after the above code:
null# = inverse matrix4(1,1)
The null# is just a place holder for the necessary return value needed by inverse matrix(which is also a float and not an integer for some bizarre reason).
With this simple operation performed on our world*view matrix we now have the necessary result needed to set our constant. So without further ado here is the code to set the constant:
set effect constant matrix 1, "worldViewMatrixI", 1
That's one down. Now for number 2.
Recall that we already stored our view matrix into matrix number 2. Now to get the value for viewInverseMatrix we have to, as you might have guessed, invert our view matrix. We'll use the same command as we did last time:
null# = inverse matrix4(2,2)
Since that is all that we have to do to get the needed result we type in the following code to set the constant:
set effect constant matrix 1, "viewInverseMatrix", 2
With that done you should be able to run the the shader and see the BumpReflectHLSL shader in all of its glory.
A Quick Note: if you left the code for checking your constants in your do loop you'll notice that the hook values for the constants you just set are still 0. This doesn't mean that you didn't set them. DBPro, as of Upgrade 5.2, doesn't check to see if you set a constant. Only whether the semantics associated with that constant are supported or not.
This concludes the first part of THE FX FILE FORMAT tutorial series. For the complete code to run the BumpReflectHLSL shader look in the source button below this post.
If you find any factual errors or would like to recommend any additions to this post that would increase clarity(not content) than please post in this thread and I'll see what changes I can make.
End of Tutorial
It is a perilous occupation for TGC to post anything by way of a promise, as the words get etched in indestructable marble for all time.
-Lee Bamber