Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
HLSL Development Cookbook

You're reading from   HLSL Development Cookbook Implement stunning 3D rendering techniques using the power of HLSL and DirectX 11

Arrow left icon
Product type Paperback
Published in Jun 2013
Publisher Packt
ISBN-13 9781849694209
Length 224 pages
Edition 1st Edition
Tools
Arrow right icon
Author (1):
Arrow left icon
Doron Feinstein Doron Feinstein
Author Profile Icon Doron Feinstein
Doron Feinstein
Arrow right icon
View More author details
Toc

Directional light


Directional light is mainly used for simulating light coming from very large and far light sources, such as the sun and the moon. Because the light source is very large and far, we can assume that all light rays are parallel to each other, which makes the calculations relatively simple.

The following screenshot shows the same model we used to demonstrate ambient light under directional light:

Getting ready

When rendering an outdoor scene that uses directional light to represent the sun or the moon, it is very common to combine the directional light calculation with the ambient light calculation. However, you may still want to support ambient light with no directional light for indoor rendering. For this reason, we will allocate a separate constant buffer for the values used when calculating the directional light. Use the following values in the constant buffer descriptor:

Constant Buffer Descriptor Parameter

Value

Usage

D3D11_USAGE_DYNAMIC

BindFlags

D3D11_BIND_CONSTANT_BUFFER

CPUAccessFlags

D3D11_CPU_ACCESS_WRITE

ByteWidth

8

The reset of the descriptor fields should be set to 0.

The three light values are needed for calculating the directional light: direction, intensity, and color. When rendering a scene with a fixed time of day, those values can be picked in advance by an artist. The only thing to keep in mind is that when this light source represents the Sun/Moon, the sky has to match the selected values (for example, low angle for the Sun means that the sky should show sunset/sunrise).

When time of day is dynamic, you will need multiple values for the different parts of the day/night cycle. An easy way to accomplish that is by picking values for a group of specific times in the day/night cycle (for instance, a value for every 3 hours in the cycle) and interpolate between those values based on the actual position in the cycle. Again, those values have to match the sky rendering.

To apply the light values on a given scene element, a few specific values are needed for the light calculation. Those scene element values will be referred to as the material. The material usually holds per-pixel values such as normal, diffuse color, and specular values. The material values can originate from texture samples or from global values.

How to do it...

Similar to the ambient light, all directional, light-related calculations are handled in the pixel shader. We will be using the following constant buffer declaration in the shader for the new constant buffer:

cbuffer DirLightConstants : register( b0 )
{
  float3 DirToLight    : packoffset( c0 );float3 DirLightColor : packoffset( c1 );
}

Although this may be counterintuitive, the direction used for directional light calculations is actually the inversed direction (direction to the light). To calculate that value, just negate the light direction. The inverted direction is stored in the first shader constant DirToLight.

The light intensity value is important when rendering to a high-dynamic range (HDR) target. HDR is a technique that calculates light values in a range wider than 0 to 1 (for more detail, check the HDR rendering recipe in Chapter 4, Postprocessing, about post processing).To improve performance, you should combine the light intensity value with the light color (make sure that you convert the color to linear space first). If you are not using an HDR target, make sure that the combined intensity and color value is lower than one. This combined light intensity and color is stored in the second shader constant DirLightColor.

The material is defined by the following structure:

struct Material
{
   float3 normal;
   float4 diffuseColor;
   float specExp;
   float specIntensity;
};

The material values should be prepared in the pixel shader before calling the function that calculates the final lit color of the pixel. The normals should be in world space and normalized. The diffuse value can be a constant color or a sample from a texture. When a material doesn't support specular highlights, just set specExp to 1 and specIntensity to 0, otherwise use appropriate values based on the desired look (see explanation to specular light in the How it works... section of this recipe).

Here is the code for calculating the directional light value based on the input parameters:

float3 CalcDirectional(float3 position, Material material)
{
   // Phong diffuse
   float NDotL = dot(DirToLight, material.normal);
   float3 finalColor = DirLightColor.rgb * saturate(NDotL);
   
   // Blinn specular
   float3 ToEye = EyePosition.xyz - position;
   ToEye = normalize(ToEye);
   float3 HalfWay = normalize(ToEye + DirToLight);
   float NDotH = saturate(dot(HalfWay, material.normal));
   finalColor += DirLightColor.rgb * pow(NDotH, material.specExp) * material.specIntensity;
   
   return finalColor * material.diffuseColor.rgb;
}

This function takes the pixel's world position and material values, and it outputs the pixel's lit color value.

How it works…

The Blinn-Phong light equation used in the previous code is very popular, as it is easy to compute and provides pleasing visual results. The equation is split into two components: a diffuse and a specular component. The following figure shows the different vectors used in the directional light calculation:

Diffuse light is defined as a light reflected by the mesh surface equally in all directions. As you can see from the calculation, the diffuse light value for a given pixel is only affected by the normal N and by the direction to light L using the dot product value. If you recall from linear algebra, the dot product equals to:

Dot(N, L) = |N||L|cos(α)

Where α is the angle between N and L. Since all vectors are normalized, the size of N and the size of L is one, so the dot product in this case is equal to the cosine of the angle between the vectors. This means that the diffuse light gets brighter, as the normal N and the direction to the light L get closer to being parallel and dimmer as they get closer to being perpendicular.

Specular light, as opposed to diffuse light, gets reflected by the mesh in a specific direction. Light coming from the light source gets reflected in the direction R. Calculating the reflection vector R is a bit expensive, so Blinn's equation provides a very good and fast approximation using the half-way vector H (the vector at half the angle between the direction to the viewer V and the direction to the light L). If you imagine how the H light is going to move when V and L move, you will see that the angle between H and N gets smaller when the angle between R and V gets smaller. Using the dot product of N and H, we get a good estimate to how close the view direction is to the reflected vector R.

The power function is then used to calculate the intensity of the reflected light for the given angle between N and H. The higher the material's specular exponent is, the smaller the light spread is.

There's more…

For performance reasons, it's very common to combine the ambient light calculation with the directional light in the same shader. In most scenes, there is only one directional light source, so by calculating both directional and ambient light in the same shader, you can save one draw call per mesh.

All you have to do is just add the value of the directional light to the ambient light value like this:

// Calculate the ambient color
float4 finalColor;
finalColor.rgb = CalcAmbient(Normal, material.diffuseColor.rgb);
   
// Calculate the directional light
finalColor.rgb += CalcDirectional(worldPosition, material);
You have been reading a chapter from
HLSL Development Cookbook
Published in: Jun 2013
Publisher: Packt
ISBN-13: 9781849694209
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image