Home

Shader basic projects in managed code (VB.net)

Triangle

Simple triangles created via vertex buffer stream data. Vertex buffers require as vertex declaration, populating a vertex buffer, and then streaming points in the forms of points, lines, or triangles using drawprimitives.

 

//inbound matrix for the final worldviewprojection matrix

//inbound ambient factor to control the magnitude of the

//ambient light

float4x4 WorldViewProj;

float AmbientFactor;

//inbound

//position of an element and its color

struct VS_OUTPUT_POSCOLOR

{

float4 Pos : POSITION;

float4 Color : COLOR0;

};

//our main function; accepts as parameters a POSITION

//and COLOR

VS_OUTPUT_POSCOLOR POLYSHDR(float4 Pos : POSITION, float4 Color: COLOR)

{

//initilize our Out out variable

VS_OUTPUT_POSCOLOR Out = (VS_OUTPUT_POSCOLOR)0;

//Out is our outgoing object a float4 position and a color

//Pos is our in point, compute its screen position using

//worldviewprojection, and then compute the color

Out.Pos = mul(Pos, WorldViewProj);

Out.Color = float4(Color.r, Color.g, Color.b, 1.0f);

Out.Color = AmbientFactor * Out.Color;

return Out;

}

 

//externally, this is referenced by this technique

//given below

technique POLYSHDR

{

pass P0

{

VertexShader = compile vs_1_1 POLYSHDR();

}

}

 

Source in Visual Studio 2005

Source as HTML for perusal: Form code | graph.vb | fps.vb | vs.fx

 

 

 

Ambient

Washed out Sphere from  the same program above. Only a few lines of code need to be changed. The sphere is particularly uninteresting because it only has ambient light (it shows no lighting or reflection whatsoever). No changes to the shader file need to be made. It will compute per vertex. Later, pixel shading will be added to show rich lighting.

It requires only a few code changes from the above program.

1) Add a line to create a mesh object in the Form_Load event

SetCameraPosition(Eye, LookAt, UpVec)
fps = New FPS(10)
msh = Mesh.Sphere(mydevice, 1, 10, 10)
myfont = New Direct3D.Font(mydevice, New System.Drawing.Font("Arial", 10.0F, FontStyle.Regular))
2) Create a displaymesh or some such sub that replaces the displaybuff sub given above. It calls msh.DrawSubset instead of drawing the vertex buffer data
Public Sub displayMesh(ByVal msh As Mesh)
Dim numPasses As Integer = myeffect.Begin(0)
For i As Integer = 0 To numPasses - 1
myeffect.BeginPass(i)
msh.DrawSubset(0)
myeffect.EndPass()
Next i
myeffect.End()
End Sub

3) Call displaymesh instead of displaybuff in the draw function

'displaybuff(CreateVB(mydevice), 1)
displayMesh(msh)

 

 

 

 

 
 
Diffuse + mesh objects

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Creating a realistic diffuse light model requires several more changes to the original program.
 
new vectors are needed; the diffuse light color and the light position
Private LightVec As Vector4 = New Vector4(0, -1, 0, 0) ' Light position (where light is coming from) = opposite of this wrt origin (where teapot is)
Private DiffuseColor As Vector4 = New Vector4(0.5, 0.5, 0.5, 1) 'diffuse color
 
a teapot is a little more interesting to look at, but there are several options available with the mesh class
msh = Mesh.Teapot(mydevice)
 
In order to implement diffuse lighting, the normals of faces of the object (as it is moved about in space via the world matrix)  must be computed, which is done with the
inverse transpose of the world matrix; likewise the shader file needs to know the positions of each vector, light position, and norm of each face. The VS_OUTPUT or vertex output (passed to the Pixel shader)
the saturate function clamps the value between [0,1], required for the RGB values only range between 0,1.
 
Dim InvMat As Matrix = Matrix.Invert(World_Matrix)
InvMat = Matrix.TransposeMatrix(InvMat)
myeffect.SetValue(EffectHandle.FromString("InvTransWorld"), InvMat)
myeffect.SetValue(EffectHandle.FromString("DiffuseColor"), DiffuseColor)
myeffect.SetValue(EffectHandle.FromString("Intensity"), 1.5F)
myeffect.SetValue(EffectHandle.FromString("LightVec"), LightVec)
 
the shader file has major changes, including the addition of a pixel shader
/* final transformation of points from model space to viewport */
float4x4 WorldViewProj;
/* inverse Transpose of the world matrix */
float4x4 InvTransWorld;
/* float4 of the light vector (x,y,z,w) */
float4 LightVec;
/* float4 of the diffuse color (r,g,b,a) */
float4 DiffuseColor;
/* float of the intensity (scaler mult for vec light) */
float Intensity;
 
 
struct VS_OUTPUT_DIFFUSE
{
float4 Pos : POSITION;
float3 Light: TEXCOORD0;
float3 Norm : TEXCOORD1;
};
 
VS_OUTPUT_DIFFUSE POLYSHDR_VS(float4 inPos: POSITION, float3 inNormal: NORMAL)
{
VS_OUTPUT_DIFFUSE Out = (VS_OUTPUT_DIFFUSE)0;
/* inposition transformed by the worldviewproj (final spot on the viewport) */
Out.Pos = mul(inPos, WorldViewProj);
/* light is normalized (made unit 1, retains direction only */
Out.Light = normalize(LightVec);
/* normalize the in normal transformed by the invtransworld; same as transforming a point by the world; only transforming a normal */
Out.Norm = normalize(mul(InvTransWorld,inNormal));
return Out;
}
float4 POLYSHDR_PS(float3 inLight: TEXCOORD0, float3 inNorm: TEXCOORD1) : COLOR
{
//ambient light = A.
float4 A = {0.10f,0.10f,0.1f,1.0f};
//final light is Ambient + diffusecolor * intensity * clamped dot product of the inlight and in the in normal
return A + DiffuseColor * Intensity * saturate(dot(inLight,inNorm));
}
 
 
technique POLYSHDR
{
pass P0
{
VertexShader = compile vs_1_1 POLYSHDR_VS();
PixelShader = compile ps_2_0 POLYSHDR_PS();
}
}
 
 
/**************************************************************************************************/
 Source  (Visual Studio 2005--Visual Basic.net)
 
 
 
 
 

 

Ambient + Diffuse + Specular using Blinn Phong (See Wolfgang Engel text)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Changes to the VB program, comment out diffuse stuff and prepare (all) new specular Created a new small class for putting more than one mesh on the screen etc.
myeffect.SetValue(EffectHandle.FromString("WorldViewProj"), worldViewProj)
'************* diffuse *******************************************
'Dim InvMat As Matrix = Matrix.Invert(World_Matrix)
'InvMat = Matrix.TransposeMatrix(InvMat)
'myeffect.SetValue(EffectHandle.FromString("InvTransWorld"), InvMat)
'myeffect.SetValue(EffectHandle.FromString("DiffuseColor"), DiffuseColor)
'myeffect.SetValue(EffectHandle.FromString("Intensity"), 1.5F)
'myeffect.SetValue(EffectHandle.FromString("LightPos"), LightPos)
'*********************************************************************
'specular
'****************************************************************
myeffect.SetValue(EffectHandle.FromString("World"), Wmat)
myeffect.SetValue(EffectHandle.FromString("specclr"), New Vector4(0.9, 0.9, 0.9, 1))
myeffect.SetValue(EffectHandle.FromString("diffuseclr"), diffusecolor)
myeffect.SetValue(EffectHandle.FromString("EyePos"), New Vector4(Eye.X, Eye.Y, Eye.Z, 1))
myeffect.SetValue(EffectHandle.FromString("LightDir"), New Vector4(0, 0, 0, 0) - LightPos)
Commented shader file
 
/* blinn phong specular */
/* calculates the position of a particular point (pixel) */
/* using the transformation matrices, then figures the intensity of the light based on where */
/* the viewer is with respect to the light source which is reflecting off the surface of the object */
/* calculate specular and diffuse components and add the ambient */
/* the saturate(dot funcs return a value [0,1], the intensity of the light multiplied by the color (rgba) */
/* */
/* final transformation of point from model space (including all rots/xlates) */
/* to the viewport */
float4x4 WorldViewProj;
/* the model transformation matrix (rots/xlates etc) */
float4x4 World;
/* the float4 specular(R,G,B,A) */
float4 specclr;
/* the float4 diffuse(R,G,B,A) */
float4 diffuseclr;
/* the float4 Eye(x,y,z,w) */
float4 EyePos;
/* the float4 LightVec(x,y,z,w) */
float4 LightDir;
 
/* output consists of texture coordinates, position */
struct VS_OUTPUT_SPEC
{
float3 Normalvec: TEXCOORD1;
float3 Viewervec: TEXCOORD2;
float4 Position : POSITION;
float3 Lightvec: TEXCOORD0;
};
 
/* Vertex output passsed to PS subroutine */
VS_OUTPUT_SPEC POLYSHDR_VS(float4 inPosition: POSITION, float3 inNormal: NORMAL)
{
VS_OUTPUT_SPEC Out = (VS_OUTPUT_SPEC)0;
/* transform inposition by worldviewproj (final transformation to the screen) */
Out.Position = mul(inPosition,WorldViewProj);
/* transform face normal by world (rots/xlates etc) */
Out.Normalvec = mul(inNormal, World);
/* tranform inposition by world (same thing done to normal) */
float4 inPosWorld = mul(inPosition,World);
Out.Lightvec = LightDir;
Out.Viewervec = EyePos-inPosWorld;
return Out;
}
float4 POLYSHDR_PS(float3 inViewer:TEXCOORD2, float3 inNorm:TEXCOORD1, float3 inLight:TEXCOORD0) : COLOR
{
/* compute the unit normal */
float3 UNormal = normalize(inNorm);
/* compute the unit light vec */
float3 ULightDir = normalize(inLight);
/* compute the unit lightdir, viewer sum (see sum of vectors) */
float3 UHalf = normalize(ULightDir + normalize(inViewer));
/* diffuse is a scaler clamped between 0,1 */
float diffuse = saturate(dot(UNormal,ULightDir));
/* specular is a scaler clamped [0,1] */
float specular = pow(saturate(dot(UNormal,UHalf)),30);
/* in this case, the ambient light is just a reduced diffuse color light */
float4 ambient = 0.1 * diffuseclr;
/* sum them to get the final light for this point */
return ambient + diffuseclr * diffuse + specclr * specular;
}
 
technique POLYSHDR
{
pass P0
{
VertexShader = compile vs_1_1 POLYSHDR_VS();
PixelShader = compile ps_2_0 POLYSHDR_PS();
}
}
 
 
/**************************************************************************************************/
 
Source (Visual Studio 2005 VB.net)

Bump mapping

 

[Bump mapped Venus implemented in DirectX 9.0, HLSL and Visual Basic.net.]

Normal map from Celestia

 

 

 

 

 

The source code to implement the main application can be found here. I ported Engel's C++ to VB.net
 
The source code for the bump mapping HLSL (.fx file) can be found here.

 

As you can see in the picture, the light source is up and to the right of you, and it's shining down on the planet. What bump mapping does is reflect light as though the surface of the sphere actually had ridges and gullies; all of the reflection you see which appears to be mountain sides or gullies in the sun, and the resultant shadowing, is actually faked by the normal map which holds information on which way each pixel is facing.

 

 

 

As the fx files become more complicated, it may be best to get something like Nvidia FX composer, not to do any fancy graphics or anything, but just to be able to compile the HLSL files. I downloaded it today (11/5/07) and it certainly enables me to check the syntax errors in my fx files.

For the specular files and beyond this will be necessary==>Fx Composer Nvidia

I have to admit, it's nice to for the first time be able to see an IDE showing me obvious errors in my shader files.

 

 

 

 

(More to be added....) 11/2007

 

 

 

Per Vertex

 

 

[An example in CsGL per-vertex specular highlights (two lights): fixed function lighting, no shaders; you can subtlety see the difference between this and the next picture based on the "boxy" nature of the highlights here (per vertex) versus the per pixel continuous look below.]

Below is the fixed function OpenGL CsGL .net code implemented in Visual Basic. It gives a basic demonstration of how to implement a simple simulation with a light or two, balls, points, lines, etc.

 

CSGL Wrapper sample code for this prog. If you have used OpenGL, then this will be familiar

 

 

 

Per Pixel

[An example of DirectX per-pixel specular and diffuse lighting (one light). Not fixed function. All pixel manipulation done in shader code, and the highlights appear continuous rather than choppy. Per vertex versus per pixel.]

 

 

DirectX shader managed code for this prog. See specular lighting tutorial here=>

 

 

 

OpenGL and DirectX since .net (including Java): Lighting, graphics, wrappers, and trying to get it all in one package

 

Although realism in computer graphics really began with the availability of the OpenGL API in 1992, early games (Doom Quake etc) didn't use it initially. However its use was popularized when John Carmack ported Quake to OpenGL (glQuake) in around 1996. Simultaneously, Microsoft was working its DirectX API which included a lot more than just graphics. It included everything a power app needs: graphics, sound, input etc. In time, DirectX would overtake OpenGL as the standard for 3D graphics, but even now they essentially offer the same features graphically.

 

OpenGL Resources

 

CsGL wrapper for .net

OpenGL Redbook tutorials.  These are the basics of OpenGL and graphics in general.

 

For information on specular, ambient, diffuse lighting in an unintuitive and overwhelming way,  see this Wikipedia page .

 

Resources for Unmanaged and Managed DirectX

 

Around Chapter 9 of Miller's book, you'll hit HLSL shading and you'll forget about the first 8 chapters and do everything over using shaders.  It's a good way to learn the basics of graphics programming, which you need to know in order to get through shaders. This book does not go into a great amount of detail on HLSL. And Engel's book (below), is basically a collection of articles that explain how to do certain high level effects using HLSL.

 

What Miller does in his book is do everything in managed DirectX in both VB.net and C#. With that foundation, Engel's book can be used to implement all of the stuff in Engel's book with a little work. . Engel's book is in C++, so you will have to port your code from C++ to VB or C#. 

 

Bump mapping requires using shaders. Each pixel contains not only a light component, but a normal component--a facing component. For the shader, the projection Matrix, an Eye, direction Vectors, and colors for specular, diffuse, and ambient lights,  and any other things from the program as passed to the shader and from there everything is computed by pixel and/or vertex (generally both).
 

Kurt Bingham

KB 6/23/07

A quick aside on using HLSL for the graphical output:

 

Shaders 101

 

Basically, there are 3 things to keep in mind when outputting graphics using HLSL (assuming you already know how to do this with using fixed functions). The HLSL semantics, the Vertex Declaration, and the Vertexbuffer data. Review the tutorial (which is excellent) here to see a very simple C# vertexshader program. He creates a struct myowncustomvertex, whereas I plod onward using the pre-created CustomVertex class. Since CustomVertex.PositionColored doesn't have a structure I want (X,Y,Z, R,G,B), I'm going to funnel the data in through the CustomVertex.PositionNormal, which does.

 

The vertex declaration simply defines the kind of data your vertexbuffer will contain, and in what order. It also specifies a DeclarationUsage. This isn't really a fixed usage that the shader is going to force; just because you call it a COLOR, doesn't mean that shader will use it for COLOR, and in fact, unless you tell the shader to do something with this field info, it won't do anything with it.

 

It's just a way of putting your data in a slot that you can retrieve from the shader code. It's a way of passing variables through the buffer stream.  But since a shader isn't like a typical program, things are slightly different. When you wind up your shader file and let it go,  it  won't return to the main program until its done processing all of the vertices in the buffers. So any special instructions (like color) applying to a specific vertex must be defined in the vertex declaration and contained in the vertexbuffer and specified for that point when you create the buffer.

 

 The substitution below helps explain this.

 

Dim elements() As VertexElement = {New VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0), _

New VertexElement(0, 12, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Normal, 0), VertexElement.VertexDeclarationEnd}

The DeclarationType.Float3 up top is my position for each point--remember the VertexElement defines the format of each member of a stream. The top one also has a UsageDeclaration of POSITION. I will access the float3 values there by requesting the POSITION channel information. The 2nd line is also a float3, and it's usage is  DeclarationUsage.Normal.

 

When I fill the vertexbuffer, I'm going to use CustomVertex.PositionNormal, which has X,Y,Z, and NX,NY,NZ. The NX,NY,NZ which are usually the normals are going to instead contain my colors RGB.

 

vb = New VertexBuffer(GetType(CustomVertex.PositionNormal), UBound(myObjects.grid), myDevice, Usage.WriteOnly, CustomVertex.PositionNormal.Format, Pool.Managed)

Dim verts As CustomVertex.PositionNormal() = CType(vb.Lock(0, 0), CustomVertex.PositionNormal())

For i = 0 To UBound(verts)

Dim pc As CustomVertex.PositionNormal = New CustomVertex.PositionNormal _

(myObjects.grid(i).p.p.X, myObjects.grid(i).p.p.Y, myObjects.grid(i).p.p.Z, 0, 0, myObjects.grid(i).PPc)

verts(i) = CType(pc, CustomVertex.PositionNormal)

Next

 

The New CustomVertex.PositionNormal constructor allows me to put in my X,Y,Z values and then follow it by the normals, which I am using 0,0,PPc.

In the shader, I will be able to reference my RGB float3 by requesting the Normal field values.

 

/************** this is the shader code ***************************/

float4x4 ProjectionMatrix;

 

struct VS_OUTPUT_POLYSHDR

{

float4 Pos : POSITION;

float4 Color : COLOR0;

};

 

 

VS_OUTPUT_POLYSHDR POLYSHDR(float4 Pos : POSITION, float4 Color: NORMAL)

{

VS_OUTPUT_POLYSHDR Out = (VS_OUTPUT_POLYSHDR)0;

Out.Pos = mul(Pos, ProjectionMatrix);

Out.Color = float4(Color.x,Color.y, Color.z,1.0f);

return Out;

}

 

technique POLYSHDR

{

pass P0

{

VertexShader = compile vs_1_1 POLYSHDR();

}

}

}

 

This is basically what was done for the coloring of the Polytrope program.

 

-------------------------------------------------------------------------------------------------------

 

On the other hand, if you want to be explicit, and you're willing to create your own custom vertex, you can implement the entire process with the following code. The only difference is that one uses the native CustomVertex type, and this next one does it old school style and creates a custom structure.

 

When you declare a 'single' in VB, that maps to a 'float' on the card. Again, a subtle difference is, when you make your vertex declaration, you don't get the option to use single3 (for a vector3), only float3. Yet, when you're creating your structure, you won't see float as a primitive type because there is no float in Visual Basic! You'll just declare them single and public.

 

All the shader is looking for, say for something coming in as a POSITION is x,y,z,w. If it's COLOR, it's r,g,b,a.

Thus, create your vertex structure:

Structure myvertex
Public x As Single
Public y As Single
Public z As Single
Public w As Single
Public r As Single
Public g As Single
Public b As Single
Public a As Single
Public Sub New(ByVal ix As Single, ByVal iy As Single, ByVal iz As Single, ByVal iw As Single, ByVal ir As Single, ByVal ig As Single, ByVal ib As Single, ByVal ia As Single)
x = ix
y = iy
z = iz
w = iw
r = ir
g = ig
b = ib
a = ia
End Sub
End Structure

My vertex is really a PositionColored vertex. However, the PositionColored supplied by DirectX has an integer for the color. Doh, not helpful since I need 4 floats for a color in the shader, and I'm not about to convert them inside the shader at the shader's expense.

Now, my vertex declaration corresponds with this data type:

Dim posE As VertexElement = New VertexElement(0, 0, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.Position, 0)
Dim ColE As VertexElement = New VertexElement(0, 16, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.Color, 0)
Dim elements() As VertexElement = {posE, ColE, VertexElement.VertexDeclarationEnd}
decl = New VertexDeclaration(mydevice, elements)

The shader is expecting to be able to access public XYZW from the POSITION component and RGBA from the COLOR component of the vertex stream.

I will load it up as follows:

Public Function CreateVB(ByVal mydevice As Device) As VertexBuffer
Dim vp() As Vector3 = New Vector3() {New Vector3(-1, -1, 0), New Vector3(1, 1, 0), _
New Vector3(1, -1, 0), New Vector3(0, 0, 0)}
Dim vb As VertexBuffer
Dim ub As Integer = UBound(vp)
Dim i As Integer
vb = New VertexBuffer(GetType(myvertex), ub + 1, mydevice, Usage.WriteOnly, VertexFormats.None, Pool.Managed)
Dim verts As myvertex() = CType(vb.Lock(0, 0), myvertex())
For i = 0 To ub
Dim r As Single = 0
Dim g As Single = 1
Dim b As Single = 0
Dim pc As myvertex = New myvertex(vp(i).X, vp(i).Y, vp(i).Z, 1, r, g, b, 1)
verts(i) = CType(pc, myvertex)
 
Next
vb.Unlock()
Return vb
End Function

 Myvertex has been defined. I now create a instance of it for each entry and send it to the shader. The shader now creates the right objects with the associated colors.

Public Sub displaybuff(ByVal vb As VertexBuffer, ByVal numverts As Integer)
mydevice.SetStreamSource(0, vb, 0)
mydevice.VertexDeclaration = decl
Dim numPasses As Integer = myeffect.Begin(0)
For i As Integer = 0 To numPasses - 1
myeffect.BeginPass(i)
mydevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1)
myeffect.EndPass()
Next i
myeffect.End()
End Sub

Of course there are several thing that need to be done in between all of these steps, including setting the matrices and setting parameters in the shader file (publics, like the worldviewProj matrix etc.), and these steps are not shown.

 

The entire little rotatable triangle program can be found at the links below:

Form code | graph.vb | fps.vb | vs.fx

 

 

 

 

 

Shaders 101 07/06/2007

KB

 

Kurt Bingham 2/2008

Home