Skip to content

Authoring an Effect

Chuck Walbourn edited this page Sep 6, 2021 · 22 revisions

This lesson covers writing your own IEffect implementation, specifically a custom effect for rendering a skybox with a cubemap.

Setup

First create a new project using the instructions from the previous lessons: Using DeviceResources and Adding the DirectX Tool Kit which we will use for this lesson.

Writing a skybox shader

A skybox is a shape like a sphere or a cube that surrounds the render scene giving the appearance of the distant sky, horizon, and/or surroundings. In our implementation we are going to be using a cubemap (i.e. 6-faced texture) to provide the texture information on the sky. For the geometry, we'll make use of GeometricPrimitive, and the shaders we are using for the SkyboxEffect are as follows:

SkyboxEffect_Common.hlsli

cbuffer SkyboxConstants : register(b0)
{
    float4x4 WorldViewProj;
}

struct VSOutput
{
    float3 TexCoord : TEXCOORD0;
    float4 PositionPS : SV_Position;
};

SkyboxEffect_VS.hlsl

VSOutput main(float4 position : SV_Position)
{
    VSOutput vout;

    vout.PositionPS = mul(position, WorldViewProj);
    vout.PositionPS.z = vout.PositionPS.w; // Draw on far plane
    vout.TexCoord = position.xyz;

    return vout;
}

SkyboxEffect_PS.hlsl

TextureCube<float4> CubeMap : register(t0);
SamplerState Sampler        : register(s0);

float4 main(float3 texCoord : TEXCOORD0) : SV_TARGET0
{
    return CubeMap.Sample(Sampler, normalize(texCoord));
}

Our vertex shader only makes use of the vertex position information, and generates the texture coordinates assuming the skybox is centered about the origin (0,0,0). It also sets the z value so that it's at the view far plane.

Save these three files to your new project's directory, and then from the top menu select Project / Add Existing Item.... Select "SkyboxEffect_Common.hlsli", "SkyboxEffect_VS.hlsl", and "SkyboxEffect_PS.hlsl" and add them.

View Properties on "SkyboxEffect_VS.hlsl" and for "All Configurations" and "All Platforms", set the "Shader Type" to "Vertex Shader (/vs)" and select "OK".

View Properties on "SkyboxEffect_PS.hlsl" and for "All Configurations" and "All Platforms", set the "Shader Type" to "Pixel Shader (/ps)" and select "OK".

Build and run your project. It will have the same blank scene as before, but should have produced SkyboxEffect_VS.cso and SkyboxEffect_PS.cso.

Implementing the SkyboxEffect

Save the file ReadData.h to your new project's folder. Using to the top menu and select Project / Add Existing Item.... Select "ReadData.h" and hit "OK".

Create a new file SkyboxEffect.h in your project:

#include <vector>

class SkyboxEffect : public DirectX::IEffect
{
public:
    explicit SkyboxEffect(ID3D11Device* device);

    virtual void Apply(
        ID3D11DeviceContext* deviceContext) override;

    virtual void GetVertexShaderBytecode(
        void const** pShaderByteCode,
        size_t* pByteCodeLength) override;

    void SetTexture(ID3D11ShaderResourceView* value);

private:
    Microsoft::WRL::ComPtr<ID3D11VertexShader> m_vs;
    Microsoft::WRL::ComPtr<ID3D11PixelShader> m_ps;
    Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_texture;
    std::vector<uint8_t> m_vsBlob;
};

Create a new file SkyboxEffect.cpp in your project:

#include "pch.h"
#include "SkyboxEffect.h"
#include "ReadData.h"

SkyboxEffect::SkyboxEffect(
    ID3D11Device* device)
{
    m_vsBlob = DX::ReadData(L"SkyboxEffect_VS.cso");

    DX::ThrowIfFailed(
        device->CreateVertexShader(m_vsBlob.data(), m_vsBlob.size(),
            nullptr, m_vs.ReleaseAndGetAddressOf()));

    auto psBlob = DX::ReadData(L"SkyboxEffect_PS.cso");

    DX::ThrowIfFailed(
        device->CreatePixelShader(psBlob.data(), psBlob.size(),
            nullptr, m_ps.ReleaseAndGetAddressOf()));
}

void SkyboxEffect::Apply(
    ID3D11DeviceContext* deviceContext)
{
    deviceContext->PSSetShaderResources(0, 1, m_texture.GetAddressOf());

    deviceContext->VSSetShader(m_vs.Get(), nullptr, 0);
    deviceContext->PSSetShader(m_ps.Get(), nullptr, 0);
}

void SkyboxEffect::GetVertexShaderBytecode(
    void const** pShaderByteCode,
    size_t* pByteCodeLength)
{
    assert(pShaderByteCode != nullptr && pByteCodeLength != nullptr);
    *pShaderByteCode = m_vsBlob.data();
    *pByteCodeLength = m_vsBlob.size();
}

void SkyboxEffect::SetTexture(
    ID3D11ShaderResourceView* value)
{
    m_texture = value;
}

For a DirectX Tool Kit effect, only Apply and GetVertexShaderBytecode are required. We added SetTexture as a way to set the texture resource which can be changed dynamically.

Build and make sure it compiles.

Many of the DirectX Tool Kit built-in effects support a number of properties which influence the selection of the VS/PS shader combination. This typically is implemented by creating the ID3D11VertexShader and ID3D11PixelShader objects on-demand when Apply is called based on current property values. Since our skybox effect has only one set of shaders, we go ahead and create them in the constructor.

Adding camera settings

While we've implemented the basic effect, we are missing a key bit of information. The vertex shader makes use of a constant buffer to provide the transformation matrix. While we could just add another effect-specific method to control this, we will instead add the standard IEffectMatrices interface to our skybox effect.

In SkyboxEffect.h, modify your class's declaration to add the additional interface:

class SkyboxEffect : public DirectX::IEffect, public DirectX::IEffectMatrices

Then add to the public section:

void XM_CALLCONV SetWorld(DirectX::FXMMATRIX value) override;
void XM_CALLCONV SetView(DirectX::FXMMATRIX value) override;
void XM_CALLCONV SetProjection(DirectX::FXMMATRIX value) override;
void XM_CALLCONV SetMatrices(DirectX::FXMMATRIX world, DirectX::CXMMATRIX view, DirectX::CXMMATRIX projection) override;

And finally add to the private section (note we have a good reason for not having a m_world matrix variable which we will get to shortly):

DirectX::SimpleMath::Matrix m_view;
DirectX::SimpleMath::Matrix m_proj;
DirectX::SimpleMath::Matrix m_worldViewProj;

In SkyboxEffect.cpp, add these methods:

void SkyboxEffect::SetWorld(FXMMATRIX /*value*/)
{
    // Skybox doesn't use the world matrix by design.
}

void SkyboxEffect::SetView(FXMMATRIX value)
{
    m_view = value;
}

void SkyboxEffect::SetProjection(FXMMATRIX value)
{
    m_proj = value;
}

void SkyboxEffect::SetMatrices(FXMMATRIX /*world*/, CXMMATRIX view, CXMMATRIX projection)
{
    // Skybox doesn't use the world matrix by design.
    m_view = view;
    m_proj = projection;
}

It should be noted that we are very careful in the use of C++ multiple-inheritance only for interfaces. See C++ Core Guidelines.

Managing the constant buffer

UNDER CONSTRUCTION

Rendering the sky

Save the files lobbycube.dds to your new project's folder. Using to the top menu and select Project / Add Existing Item.... Select "lobbycube.dds" and hit "OK".

UNDER CONSTRUCTION

More to explore

  • Usable versions of the SkyboxEffect class can be downloaded from here: .cpp / .h, and you will need the three shader files above as well.

Next lessons: Using HDR rendering

For Use

  • Universal Windows Platform apps
  • Windows desktop apps
  • Windows 11
  • Windows 10
  • Windows 8.1
  • Xbox One

Architecture

  • x86
  • x64
  • ARM64

For Development

  • Visual Studio 2026
  • Visual Studio 2022 (17.12 or later)
  • clang/LLVM v12 - v21
  • MinGW 14.2, 15.2, 16.1
  • CMake 3.21

Related Projects

DirectX Tool Kit for DirectX 12

DirectXMesh

DirectXTex

DirectXMath

Win2D

Tools

Test Suite

Model Viewer

Content Exporter

DxCapsViewer

Clone this wiki locally