Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

Code Snippets / Other - HLSL Double-sided leaf shader for model trees

Years of Service
User Offline
Joined: 27th Nov 2007
Location: London, UK
Posted: 19th Feb 2013 02:32 Edited at: 19th Feb 2013 03:12
This is kinda a tutorial snippet.

We’ve just released version 1.09 of Forester Pro an easy to use tree creator that exports to X and DBO format for Dark Basic. One of the features of Forester Pro is it can export tree models with either single or double-sided leaf meshes. Models with double-sided meshes can be used in DBPro just by loading them with the load object command. Models with single-sided meshes, which have a lower polycount and thus better performance, need to use a shader to display both sides of the leaf meshes in Dark Basic.

Since shaders are liable to make you run for the hills we thought it would be a good idea to produce a tutorial/snippet on how a double-sided shader works, and how it is implemented in Dark Basic. Once you have a good understanding of shaders it is amazing what you can do with Dark Basic. Shaders are nothing to be frightened of…..well actually….a modicum of fear is inherent…but they aren’t as difficult as they first appear. Hope this is the right place for an easy level shader tutorial.

What is a Shader

Okay, but some people won't know. A shader is a little program that runs on the graphics card (usually) and “beautifies” your game application by changing the appearance of models by adding effects. In Dark Basic they are referred to as effects in code and can do spectacular things such as make a flat plane look like the surface of the ocean, make a simple sphere glow like the sun, or generate mirror-like reflections. Shaders, however, can do some much more down to earth things, such as apply lighting to models and turn single sided planes into double-sided planes. Shaders are magic and well worth learning.

How to Apply a Shader to an Object in Dark Basic

Shaders are little programs in fx files that can be applied to objects in Dark Basic. To load a shader as effect no 1 and apply it to an object we simply use the following code. Depending on the shader you may also need to ensure the model is in the right format.

Shaders and HLSL

For Dark Basic shaders are written in High Level Shader Language, and initially will look very scary. All the code in the shader is contained in the fx file. Lets start with the header of our double-side plane shader, the header is by far the least scary part.

This is just a list of variable declarations, just like the global and dim statements we use to declare variables in Dark Basic. These all define matrices of data (tables like arrays) so a float4 is a matrix with four pieces of data all of which have decimal points (are floating point data), for example, the colour of the ambient light the shader will apply to our model is contain in a 4 by 1 matrix and contains four floating point numbers, whilst the direction of that light is contained in a 3 by 1 matrix as a unit vector. If I’ve already scared you by mentioning the word vector, then sorry…think of them as directions given in the form, left a bit, up a bit, forward a bit.

There are some strange definitions here too, like float4x4 wvp:WorldViewProjection. This is a variable wvp which is a 4 by 4 matrix and stores data that allows us to convert coordinates from world to view (camera) coordinates.

The important thing to understand about variable declarations in this header is we can over-ride them in our Dark Basic code, for example we can use the following command to change the diffuse light intensity.

The next part of our shader will get the texture data that we’ve applied to our model using the Dark Basic texture object command. This defines a variable called detailMap1 that we can use to access the texture data when calculating how light effects the texture.

The rest of the shader will do the number crunching to calculate how the texture will be lit on the model. The number crunching occurs in two parts, first the shader will do some calculations based on the shape of the model, from the positions of vertices and the orientations of faces (i.e. the normal) – this part of the shader is known as the vertex shader, then the output from the vertex shader’s calculations will be used to calculate the colour of each pixel on the screen and, if necessary combine that colour with the colour of the background. This part of the shader is known as the pixel shader. In other words, first the shader deals with shapes, the second gets the colour of screen pixels from that shape.

This is the vertex shader for our double-sided tree shader. It looks intimidating but we will take it apart piece by piece.

First things first. We need to know how to pass data into and out of the vertex shader. This is one of the annoying things about HLSL, everything need to be declared. The input definition is:

So we are passing in the positions of vertexes in a variable called Position, the normal and the texture coordinates.
The output definition looks like this (it is also the input to the pixel shader).

So we are passing out positions, colours and a new set of texture coordinates. Anyway lets get stuck into the vertex shader. Just looking at my nice comments in the code you should be able to work out a lot about what it does. Notice how it uses the matrix multiply command to convert the positions of vertexes in world coordinates into view coordinates (the coordinates of camera) which are then passed to the output using the simple notation of output.Position=mul(input.Position,wvp). Remember that wvp from the declaration? This is what it is for.

The vertex shader plays the same trick with normals, remember the normals of a face determines which way it is pointing. The vertex shader transforms the normal into view coordinates and then reverses any normal that is facing away from us (that has a negative z component). This means that any face, such as a leaf, that is facing away from us (from the camera) will be treated as if it is facing towards us. If you think about this for a second you can see this is the guts of the shader. Even though the planes are single sided, their normal are swapped if they are facing away from us, making them look like they have two sides!

The next part of the vertex shader calculates the intensity of light at any point on our object from the diffuse light direction and then gets the colour simply by multiplying the colour of the diffuse light, by the light intensity and the original intensity of the diffuse light. If you are wondering how this one line can do something quite so spectacular, then remember it is being run on every vertex and face in the model.

The pixel shader of our double-sided tree editor is actually uber simple:

The first part simply gets the colour of the texture from the uv coordinates. It then combines the colours on the model produced by diffuse light (the directional light) and the ambient light (the background light) and multiplies them by the pixel colour from the texture, storing them in the variable output1. Since this is for a leaf we need the texture to have transparent areas as given by the alpha channel of the image. To ensure these areas are transparent the last line simply copies the alpha from the texture to the output alpha. This will only affect the transparency and not the colour of the pixel. Then finally we pass back the colour of all the pixels to the screen.

This isn’t quite the end of the shader, theres one more part that pulls together the vertex shader and the pixel shader and tells the code what to do with them. This technique is shown below:

Shaders can have more than one technique, and we can change which one is used from code. They can also run several sets of vertex and pixel shaders one after another, hence the Pass1 notation. In this case there is only one technique and only one pass.

You’ll see that there are several assignments in the technique. Such as AlphaBlendEnable=TRUE; which makes sure the pixel colour is blended with the background if it is partially transparent. The CullMode is set to None, it could be CCW, which means show counter clockwise faces as forward facing, or CW which means show clockwise winding faces as forward facing. In our vertex shader we are controlling which faces we can see by changing their normal so we don’t want culling from preventing them from being visible.

Anyway I hope you can see that actually shaders are very useful and not as mind-blowingly complex as they appear at a first glance. The trick with shaders is not to get too confused at the weirdness. The best way to learn is to take an existing shader and change it just a little and see what it does.

You’ll find the full double-sided shader for trees in the shader directory of Forester Pro available from our website (there is a free version). It is called ambientdiffuse.fx. You are welcome to use this shader in your own projects. Trying changing it a little (but keep a copy) to see how it changes the appearance of trees in Forester Pro. If the tree goes black, you may have introduced a syntax error…the most common are wrong variable names (they are case sensitive), leaving out a semicolon at the end of the line, or using float3 instead of float4 or vice a versa. Dark Shader is an excellent way of tracking down errors in shaders, I thoroughly recommend it.

Anyway…finally…your challenge if you should be willing to accept. Change the shader to:

(1) Make the colours on the model show the model’s normals rather than its diffuse lit texture.

(2) Make the entire tree half transparent.

(3) Make the tree move using the shader.

Make your suggestions on how to do this below (but not you GG, we know you can do it…beginners only).

GrumpyOne - the natural state of the programmer
AGK Backer
Years of Service
User Offline
Joined: 5th Dec 2010
Playing: FFVII
Posted: 19th Feb 2013 11:18
Way too much to read right now, but guessing that it works... AWESOME!

DBPro Master
Years of Service
User Offline
Joined: 21st Nov 2007
Location: Austin, TX
Posted: 27th Feb 2013 07:35
really excellent break down, detailed, yet simple and clear. Would love to see more like this.

Years of Service
User Offline
Joined: 27th Nov 2007
Location: London, UK
Posted: 25th Mar 2013 20:26 Edited at: 25th Mar 2013 20:27

The first challenge was to make the pixel colors shown on the model show the normals rather than the texture colour. Sounds complicated but is actually easy since a glance at the vertex shader tells us that normals and colors have the same dimensions (they are both float4), this means we can just output the normals to the color without any conversion. Both the colors R,G,B,A and the normals X,Y,Z,W have a range 0-1 so the normals will automatically appear as a colour.

We could pass our normals to the pixel shader and swap them into the colors there, however, notice in the pixel shader we calculate the diffuse light color and place it into output.Color, so we could just put the normals into output color instead by making this line.

output.Color = innormal;

We could have used:

output.Color = input.Normal;

but this would not have double sided faces, these were reversed and placed in innormal whilst still in world coordinates. Since we are no longer calculating the diffuse light we can get rid of the line that is calculating it.

There are also now some redundant lines in the Pixel Shader. We no longer need to calculate the texture color of each pixel, nor do we need to take into account the ambient light. The Pixel Shader then just becomes:

And that is's a picture of a tree in Forester Pro showing the normals for the trunk and branches using this shader.

I'll leave it a couple of weeks before adding the next answer so feel free to make suggestions.

GrumpyOne - the natural state of the programmer
Years of Service
User Offline
Joined: 27th Nov 2007
Location: London, UK
Posted: 7th Apr 2013 20:02 Edited at: 7th Apr 2013 20:07
Answer 2#

As promised, the second answer. This is actually really easy so I am sure you have worked it out yourself.

The challenge was to make the trees half transparent, so similar to the ghost effect in DBPro. Transparency is controlled by the alpha channel in the color of each pixel written from the shader to the screen. This was defined as the last line in the Forester Pro double-sided leaf shader in the pixel shader function shown below:

To make the tree half transparent then we simply need to set a value of 0.5 in the alpha channel as shown below:

Setting the transparency to 50% can sometimes be useful, however, there is much more we can do with the alpha channel, for example, if we wanted the transparency to vary with the amount of red in the diffuse image we could use:

Even more useful is we could use a separate image that contains an alpha map, for example a grey scale image with varying shades representing the amount of transparency. To do this we need to apply both the diffuse and alpha map textures to the object using:

texture object objnum, diffuseimg, 0 //0 = first stage image
texture object objnum, alphamapimg, 1 //1 = second stage image

Once applied to the object then we'd need to add an input structure to the shader to load the alphamap image from the object into the shader. Like this:

Then we can access the colors in the alpha map in the pixel shader by adding a new texture sampler and use the red, green or blue channel of each pixel to specify the transparency:

The great thing about this shader is it allows us to add transparency to an object that doesn't depend on the diffuse texture, so we could, for example make a soft edge to an object using a transparency gradient.

If you want to follow this tutorial you can download Forester Pro and make changes to the ambientdiffuse.fx shader you find in the shader directory. You are welcome to use this shader in your own projects.

I'll leave it a week until the next answer. The next one should be fun since it is the basis of a tree wind shader.

GrumpyOne - the natural state of the programmer

Login to post a reply

Server time is: 2024-02-26 00:38:43
Your offset time is: 2024-02-26 00:38:43