Previous topic

Upgrading from HairWorks 1.1.x

Next topic

Release Notes

SDK Samples

How to run the samples

The samples are available pre-built in the bin/win32 and bin/win64 directories respectively. The Dx11 executable is called HairDx11Sample.Win32.exe/HairDx11Sample.Win64.exe. The Dx12 executables are HairDx12Sample.Win32.exe/HairDx12Sample.Win64.exe. The DirectX12 samples only work on Windows 10.

The sample framework builds multiple ‘samples’ into a single executable. You can move between the different samples available in the application using the ‘0’ - ‘9’ keys. Press ‘Esc’ to quit the sample framework. The title bar of the project shows the name of the current sample, and the key that was pressed to get to it in brackets.

How to build the sample project on Dx11

  1. Goto the directory samples/build/Dx11
  2. Select the directory with the visual studio version and if you want 32 or 64 bit
  3. Open HairWorksDx11Samples.sln
  4. Build and run the project

DirectX12

NVIDIA HairWorks 1.2 for DirectX12 is under active development, and is NOT AVAILABLE within this public release. The documentation on DirectX12 usage and the Sdk headers are for illustrative purposes only and may be subject to change. Early access to HairWorks 1.2 for DirectX12 is available to developers on request at visualfx-licensing@nvidia.com

How to build the sample project on Dx12

  1. Goto the directory samples/build/Dx12
  2. Select the directory with the visual studio version and if you want 32 or 64 bit
  3. Open HairWorksDx12Samples.sln
  4. Build and run the project

Sample Framework

All the code that is not directly related to HairWorks is encapsulated in Src/SampleFramework, and Src/External. Also note that the samples use NvCommon located the main Src directory in Src/Nv/Common. Samples generally have “using namespace nvidia::Common” (NvCo) and “using nvidia” (Nv), so they don’t need to explicitly reference types from the NvCommon library or the nvidia namespaces. In your code you may want to explicitly qualify with Nv:: or NvCo:: for clarity or other reasons.

Feel free to modify/copy/adapt codes in the sample codes to suit your need. All the sample functions are implemented for illustration purpose only, and does not represent how certain features should necessarily be implemented.

The HairWorks Sdk is held in the namespace nvidia::HairWorks, or NvHair for short. Types are used from the NvCommon library which are in the nvidia::Common namespace or NvCo for short.

Samples have a variety of options that can be controlled from the command line. The currently available options can be seen by passing ‘-help’ to the command line. Which sample is selected at start up can be choosen using –sampleIndex=. Note that the options available via help can change depending on the sample. For example if you wish to see all the options available for sample 1, you can pass to the command line “-help –sampleIndex=1”

The following list was produced using -help

There are the following command line options:

Some parameters can accept integers or strings. In order to support srings with spaces, quoting with ” is supported and escaping with .

Options prefixed with a single - are for boolean switch. Options with – expect a parameter placed after an equals. For example running with

-fullSpeed –sampleIndex=2

Runs the framework at full speed, using sample at index (and key) 2. The boolean parameters can also be set with the – style, and such as

–fullspeed=0 –sampleIndex=2

Will run at ‘normal speed’ as the full speed parameter has been disabled (0).

Maths types are prefixed with gfsdk_* for historical reasons.

Shaders are in the Src/HairSample/Dx/Shader folder.

Dx11HairSample Class

The DirectX11 hair samples derive from the class Dx11HairSample tat is in the files samplesSrcHairSampleDx11HsDx11HairSample.cpp/.h This class holds the basic code that handles the loading and shutting down of the HairWorks Sdk. The followig code shows how to load the HairWorks dll on windows:

#include <Nv/HairWorks/Platform/Win/NvHairWinLoadSdk.h>

/* static */NvHair::Sdk* Dx11HairUtil::loadHairWorksSdk(Bool debugDlls)
{
        const char* path = NV_NULL;
        if (debugDlls)
        {
#ifdef _WIN64
                path = "NvHairWorksDx11.win64.D.dll";
#else
                path = "NvHairWorksDx11.win32.D.dll";
#endif
        }
        else
        {
#ifdef _WIN64
                path = "NvHairWorksDx11.win64.dll";
#else
                path = "NvHairWorksDx11.win32.dll";
#endif
        }

        return NvHair::loadSdk(path, NV_HAIR_VERSION, MemoryAllocator::getInstance(), Logger::getInstance());
}

Once the SDK is loaded it must be initialized with initRenderResources. Note the use of NvCo::Dx11Type::wrap method to wrap the Dx11 device context such that it can be passed to the HairWorks API:

#include <Nv/Common/Platform/Dx11/NvCoDx11Handle.h>
#include <Nv/HairWorks/Platform/Win/NvHairWinLoadSdk.h>

ID3D11DeviceContext* deviceContext = ...;
ID3D11Device* device = ...;

// load HairWorks dll
m_hairSdk = Dx11HairUtil::loadHairWorksSdk(m_useDebugDlls);
if (!m_hairSdk)
{
        return NV_FAIL;
}
// Initialize DirectX settings for HairWorks runtime
m_hairSdk->initRenderResources(NvCo::Dx11Type::wrap(device), NvCo::Dx11Type::wrap(deviceContext));

When the SDK is no longer needed it can be destroyed:

void Dx11HairSample::onDestroy()
{
        if (m_hairSdk)
        {
                m_hairSdk->freeRenderResources();
                m_hairSdk->release();
                m_hairSdk = NV_NULL;
        }
        Parent::onDestroy();
}

Dx12HairSample Class

The DirectX12 hair samples derive from the class Dx12HairSample this is in the files HsDx12HairSample.h/.cpp. This class holds the basic code that handles the loading and shutting down of the HairWorks Sdk.

Loading the SDK is handled in HairDx12Util::loadHairWorksSdk, as shown in the following code:

#include <Nv/HairWorks/Platform/Win/NvHairWinLoadSdk.h>

/* static */NvHair::Sdk* HairDx12Util::loadHairWorksSdk(Bool debugDlls)
{
        const char* path = NV_NULL;
        if (debugDlls)
        {
#ifdef _WIN64
                path = "NvHairWorksDx12.win64.D.dll";
#else
                path = "NvHairWorksDx12.win32.D.dll";
#endif
        }
        else
        {
#ifdef _WIN64
                path = "NvHairWorksDx12.win64.dll";
#else
                path = "NvHairWorksDx12.win32.dll";
#endif
        }

        return NvHair::loadSdk(path, NV_HAIR_VERSION, MemoryAllocator::getInstance(), Logger::getInstance());
}

Once the SDK is loaded it must be initialized with initRenderResources. Notice the use of NvCo::Dx12Type::wrap to convert the Dx12 types such that they can be passed to the HairWorks SDK:

// Header contains the code to wrap Dx12 types
#include <Nv/Common/Platform/Dx12/NvCoDx12Handle.h>
// Header has Dx12 HairWorks types, and code to allow 'wrapping'
#include <Nv/HairWorks/Platform/Dx12/NvHairDx12SdkHandle.h>

ID3D12GraphicsCommandList* commandList = m_appInterface->getDx12CommandList();
ID3D12Device* device = m_appInterface->getDx12Device();

m_hairSdk = HairDx12Util::loadHairWorksSdk(m_useDebugDlls);
if (!m_hairSdk)
{
        return NV_FAIL;
}

m_app->waitForGpu();

NvHair::Dx12InitInfo initInfo;
initInfo.m_targetInfo = m_appInterface->getDx12TargetInfo();

// Initialize DirectX settings for HairWorks runtime
m_hairSdk->initRenderResources(Dx12Type::wrap(device), NvCo::Dx12Type::wrap(commandList), NvHair::Dx12SdkType::wrapPtr(&initInfo));

When the SDK is no longer needed it can be destroyed:

void Dx12HairSample::onDestroy()
{
        if (m_hairSdk)
        {
                m_hairSdk->freeRenderResources();
                m_hairSdk->release();
                m_hairSdk = NV_NULL;
        }
        Parent::onDestroy();
}

Note that in Dx12 it is important and necessary to call onGpuWorkSubmitted after a commandlist populated by HairWorks is submitted to the ID3D12CommandQueue. Not doing so will cause a resource leak on the CPU and GPU. On Dx12 the function be called with a wrapped version of the command queue:

// Header contains the code to wrap Dx12 types
#include <Nv/Common/Platform/Dx12/NvCoDx12Handle.h>

ID3D12CommandQueue* commandQueue = ...;
// The command list that HairWorks has added work to
ID3D12GraphicsCommandList* commandList = ...;
{
        // Execute the command list.
        ID3D12CommandList* commandLists[] = { m_commandList };
        commandQueue->ExecuteCommandLists(NV_COUNT_OF(commandLists), commandLists);
}
m_hairSdk->onGpuWorkSubmitted(NvCo::Dx12Type::wrap(commandQueue));

Simple Sample

_images/HairWorks_FurSampleSimpleScreenshot.jpg

Screenshot of Simple Sample sample.

Summary

This sample creates a simple hair asset made of two triangles (a quad), and creates a hair instance out of the asset. A very simple hair asset made of a quad with 2 triangles and 4 guide hairs are created. Once hair assets are created, we use HairWorks to render hairs on the faces of the growth mesh.

Each rendered hair is drawn in white, and the guide hairs are visualized in red. The growth mesh is drawn as yellow triangles, using default visualization features of HairWorks.

Illustrated Features

This code sample shows

  1. How to create a hair asset and modify the parameter (instance descriptor)
  2. How to set camera information
  3. How to draw hairs with minimal rendering
  4. How to draw visualization for guide hairs and the growth mesh

Running Instructions

  • Camera Zoom : Mouse Wheel
  • Camera Orbit : RMB

Details

The general HairWorks specific vairables - for asset and an instance

// Member variables of a sample class definition

NvHair::AssetDescriptor m_assetDescriptor;               // hair asset descriptor
NvHair::AssetId m_assetId;                                               // hair asset ID
NvHair::InstanceDescriptor m_instanceDescriptor; // hair instance descriptor
NvHair::InstanceId m_instanceId;                                 // hair instance ID

// Initialize to null handle
m_assetId = NvHair::ASSET_ID_NULL;
m_instanceId = NvHair::INSTANCE_ID_NULL;

When the D3D device is created, we first load HairWorks runtime, then create a hair asset and an instance.

This sample manually creates hair asset by filling in the NvHair::AssetDescriptor struct in createAsset()

// Create the hair asset
m_hairSdk->createAsset(m_assetDescriptor, m_assetId);
// Create hair instance.  A valid d3d device must be set before an instance can be created.
m_hairSdk->createInstance(m_assetId, m_instanceId);
// Update instance descriptor
m_hairSdk->updateInstanceDescriptor(m_instanceId, m_instanceDescriptor);

Before anything can be rendered there must be at least one simulation step. Stepping the simulation is as simple as calling stepSimulation

m_hairSdk->stepSimulation();

Assuming the context has been correctly set (which is Dx11/Dx12 specific) , the following code can be used to render the instance:

// Set view matrix and projection matrix
XMMATRIX projection = getProjectionMatrix();
XMMATRIX view = getViewMatrix();
m_hairSdk->setViewProjection((const gfsdk_float4x4&)view, (const gfsdk_float4x4&)projection);

// Render the hair instance
m_hairSdk->renderHairs(m_instanceId);
// Render visualization the hair instance
m_hairSdk->renderVisualization(m_instanceId);

DirectX11 Specifics

On DirectX11 it is necessary to store the pixel shader that is being used:

ComPtr<ID3D11PixelShader> m_pixelShader; // custom pixel shader

The following creates a simple pixel shader that renders every hair color yellow.

ComPtr<ID3DBlob> blob;
SubString code(
        "void PS( out float4 colorOut : SV_Target )\n"
        "{\n"
        "    // Make each pixel yellow, with alpha = 1\n"
        "    colorOut = float4( 1.0f, 1.0f, 0.0f, 1.0f );\n"
        "}\n");
// Compile and create the vertex shader
DWORD compileFlags = D3D10_SHADER_ENABLE_STRICTNESS;
#ifdef _DEBUG
compileFlags |= D3D10_SHADER_DEBUG;
#endif
// Compile and create the pixel shader
NV_RETURN_ON_FAIL(DxUtil::compile(code, "PS", "ps_4_0", compileFlags, blob));
NV_RETURN_ON_FAIL(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), NV_NULL, m_pixelShader.writeRef()));

Before simulation, or rendering we need to call setCurrentContext:

ID3D11DeviceContext* deviceContext = ...;
// Set render context for HairWorks
m_hairSdk->setCurrentContext(Dx11Type::getHandle(deviceContext));

Before rendering as well as calling setCurrentContext, we also need to set the pixel shader

// set your hair pixel shader before rendering hairs
deviceContext->PSSetShader(m_pixelShader, NV_NULL, 0);

DirectX12 Specifics

On DirectX12 the shaders are managed by HairWorks. Each shader is configured using the NvHair::Dx12PixelShaderInfo structure which also describes the format for the render target/s. This is also used to specify custom Constant Buffer View (CBV), Shader Resource Views (Srv) or a dynamic constant buffer. Once this is configured within the struct, it is set on a shader ‘slot’ using the setPixelShader method. The first parameter is the slot number that will be associated with this shader. When rendering the ShaderSettings struct has the m_shaderIndex member that can be used to specify which slot to use. By default HairWorks uses slot 0. Setting up a custom shader:

SubString code(
        "void PS( out float4 colorOut : SV_Target )\n"
        "{\n"
        "    // Make each pixel yellow, with alpha = 1\n"
        "    colorOut = float4( 1.0f, 1.0f, 0.0f, 1.0f );\n"
        "}\n");
// Compile and create the vertex shader
DWORD compileFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef _DEBUG
compileFlags |= D3DCOMPILE_DEBUG;
#endif
// Compile and create the pixel shader
ComPtr<ID3D10Blob> pixelBlob;
NV_RETURN_ON_FAIL(DxUtil::compile(code, "PS", "ps_4_0", compileFlags, pixelBlob));

NvHair::Dx12PixelShaderInfo pixelInfo;
pixelInfo.m_pixelBlob = (const UInt8*)pixelBlob->GetBufferPointer();
pixelInfo.m_pixelBlobSize = pixelBlob->GetBufferSize();

pixelInfo.m_targetInfo = m_appInterface->getDx12TargetInfo();
//pixelInfo.m_numCustomCbv = 1;
//pixelInfo.m_hasDynamicConstantBuffer = true;

// Okay try setting it
m_hairSdk->setPixelShader(0, NvHair::Dx12SdkType::wrapPtr(&pixelInfo));

Before stepping the simulation, or rendering - the context has to be set:

ID3D12GraphicsCommandList* commandList = ...;
m_hairSdk->setCurrentContext(Dx12Type::wrap(commandList));

It is the responsibility of the application (in this case the sample framework) to call onGpuWorkSubmitted with the ID3D12CommandQueue the work was submitted to.

Shading Sample

_images/HairWorks_FurSampleShadingScreenshot.jpg

Screenshot of FurSampleShading sample.

Summary

The sample loads a hair asset from .apx file and textures. It also creates a custom pixel shader to render hair with textures. Then it simulates and renders hairs.

Illustrated Features

This code sample shows

  1. How to load a hair asset from .apx file and modify the parameters.
  2. How to create and set up custom pixel shader for hair rendering
  3. How to set up hair control textures

Running Instructions

  • Camer Zoom : Mouse Wheel
  • Camera Orbit : RMB

Details

Code structures are similar to previous sample.

We add a few more API calls to load hair asset and set up textures, etc. We now use file load API to load hair asset from .apx file. As the sample asset file was exported in DCC that uses different coordinate system, we provide conversion settings when we load the .apx file.

Loading the hair asset in onInit:

// conversion settings to load apx file into.  We use l.h.s / y-up camera in this example.
// note that actual asset might have used different coord system (e.g. r.h.s/z-up), so we need this setting to be properly set.
NvHair::ConversionSettings conversionSettings;
{
        conversionSettings.m_targetHandednessHint = NvHair::HandednessHint::LEFT;
        conversionSettings.m_targetUpAxisHint = NvHair::AxisHint::Y_UP;
        conversionSettings.m_targetSceneUnit = 1.0; // centimeter
}

// Load hair asset from .apx file
{
        String filePath;
        m_app->findMediaPath("media/HumanSamples/Female/Eve/gm_main.apx", filePath);
        StdCFileReadStream stream(filePath);
        NV_RETURN_ON_FAIL(m_hairSdk->loadAsset(&stream, m_assetId, NV_NULL, &conversionSettings));

        // The media for the file, will be in the same directory
        WinPathUtil::getParent(filePath, m_mediaPath);
}

// copy default instance descriptor from the loaded asset
NV_RETURN_ON_FAIL(m_hairSdk->getInstanceDescriptorFromAsset(m_assetId, m_instanceDescriptor));

// Create custom pixel shader for HairWorks rendering
NV_RETURN_ON_FAIL(Dx11Util::loadPixelShader(m_app, SMP_HAIR_SAMPLE_ROOT "/Dx/Shader/ShadingHairShader.hlsl", "ps_main", "ps_5_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, m_pixelShader));

// create hair instance
m_hairSdk->createInstance(m_assetId, m_instanceId);
m_instanceDescriptor.m_useBackfaceCulling = false;
m_hairSdk->updateInstanceDescriptor(m_instanceId, m_instanceDescriptor);

// set up texture resources
NV_RETURN_ON_FAIL(HairDx11Util::readTextures(m_app, m_mediaPath, m_hairSdk, m_assetId, m_instanceId));

// Set the camera
{
        XMVECTOR modelCenter = XMVectorSet(0, 175, 0, 0);
        XMVECTOR camCenter = XMVectorAdd(modelCenter, XMVectorSet(0, 0, -50, 0));
        m_camera.setTarget(modelCenter, camCenter);
}

Example shader

samplesSrcHairSampleDxShadingHairShader.hlsl is an example custom pixel shader that performs lighting.

In shaders it is important to include the common shader header file <Nv/HairWorks/Shader/NvHairShaderCommon.h>. Doing so requires the shader loader to be able to handle include paths. This is achieved in the samples using DxIncludeHandler class in “Nv/Common/Platform/Dx/NvCoDxIncludeHandler.cpp”. If having an include path handler is inconvenient you can place the HairWorks headers in the same directory as your custom shader and just use #include “NvHairShaderCommon.h”

The provided example shader in “ShadingHairShader.hlsl” shows a complete hair shader that can be used to render textured hair.

First we declare standard shader resource that we mapped in above C++ code.

NV_HAIR_DECLARE_SHADER_RESOURCES(t0, t1, t2); // shader resources

This defines resources that are needed for a HairWorks shader to retrieve standard attributes such as tangents and normals.

Then we define two texture buffers, one for root and another for tip color:

Texture2D       g_rootHairColorTexture  : register(t5); // texture map for hair root colors
Texture2D       g_tipHairColorTexture   : register(t6); // texture map for hair tip colors

The next block defines a cbuffer variable bound at b0 (default location for HairWorks constant buffer):

cbuffer cbPerFrame : register(b0)
{
        // .... Add other cbuffer variables to get your own data

        NvHair_ConstantBuffer   g_hairConstantBuffer; // hairworks portion of constant buffer data

        // .... Add other cbuffer variables to get your own data
}

In this sample, the content of HairWorks constant buffer is automatically filled and contains all the data necessary to render hairs. (see Shadow Sample to see how users can customize cbuffer definitions.)

As the shader needs to sample textures, we provide a default sampler:

SamplerState texSampler: register(s0); // we use a texture sampler for hair textures in this sample

In the ps_main() function, we see

// get all the attibutes needed for hair shading with this function
NvHair_ShaderAttributes attr = NvHair_GetShaderAttributes(input, g_hairConstantBuffer);

// set up hair material.
NvHair_Material mat = g_hairConstantBuffer.defaultMaterial;

The variable NvHair_ShaderAttributes contains all the per fragment attribute required for shading, such as world space location, normal, tex coords, etc.

The variable NvHair_Material are a part of the constant buffer that contains shared material properties.

All subsequent calls to hairworks shader functions require these data structures.

We now see:

// sample hair color from textures
float3 hairColor = NvHair_SampleHairColorTex(mat, texSampler, g_rootHairColorTexture, g_tipHairColorTexture, attr.texcoords);

This returns blended hair color based on the two textures and control variables. In PBR context, one may think of this as the ‘albedo’ color for hair. Certain engines that require linear space lighting may apply gamma conversion to this color.

Get variables needed for handling ‘glint’ (speckles of dirt and irregularities in hair):

const float glintScale = mat.glintStrength;
float glint = (glintScale > 0.0f) ? glint = NvHair_ComputeHairGlint(g_hairConstantBuffer, mat, attr) : 0.0f;

In the main loop, we accumulate contribution from each light:

float4 color = float4(0.0f, 0.0f, 0.0f, 1.0f);

[unroll]
for (int i = 0; i < NUM_LIGHTS; i++)
{
        float3 Lcolor = lightColor[i];
        float3 Ldir = normalize(lightDir[i]); // light direction defined from shaded point toward the light

        color.rgb += NvHair_ComputeHairShadingWithGlint(Lcolor, Ldir, attr, mat, hairColor.rgb);
}

The shader function NvHair_ComputeHairShadingWithGlint() computes hair color based on light color, direction, and sampled hair texture color.

Note that light must be pointing away from the shaded point, not into it.

DirectX11 Details

Once the hair asset is loaded, we get texture file names from the asset, and create texture resources.

These shader resources are then set to HairWorks runtime.

The process is implemented in HairDx11Util::readTextures Src/SampleFramework/Common/Dx11/SmpHairDx11Util.cpp.

/* static */Result HairDx11Util::readTextures(App* app, const SubString& mediaPath, NvHair::Sdk* hairSdk, NvHair::AssetId assetId, NvHair::InstanceId instanceId)
{
        // ... Getting the device
        ID3D11Device* device = ...;

        String filePath;

        // Go through each known hair texture type::
        for (int t = 0; t < NvHair::TextureType::COUNT_OF; ++t)
        {
                char textureFileName[1024];

                // In each loop, we use **getTextureName()** API to retrieve texture file name::

                NV_RETURN_ON_FAIL(hairSdk->getTextureName(assetId, (NvHair::TextureType::Enum)t, textureFileName));
                if (textureFileName[0] == 0)
                {
                        continue;
                }
                WinPathUtil::append(mediaPath, SubString(SubString::INIT_CSTR, textureFileName), filePath);

                // Then we create a shader resource view for the texture, and set it for HairWorks runtime::

                NvCo::ComPtr<ID3D11ShaderResourceView> textureSrv;
                NV_RETURN_ON_FAIL(Dx11Util::readTextureSrv(device, filePath, textureSrv));
                NV_RETURN_ON_FAIL(hairSdk->setTexture(instanceId, NvHair::TextureType::Enum(t), Dx11Type::getHandle(textureSrv.get())));
        }
        return NV_OK;
}

Note that texture shader resource may be separately maintained by the game engine. In that case, one may implement different mechanism to identify and retrieve shader resources.

At each frame, we render hairs and visualization.

// set your hair pixel shader before rendering hairs
deviceContext->PSSetShader(m_pixelShader, NV_NULL, 0);

// set texture sampler for texture color in hair shader
{
        ID3D11SamplerState* states[] = { m_linearSampler };
        deviceContext->PSSetSamplers(0, 1, states);
}

{
        // get standard shader resources for attribute interpolation
        ID3D11ShaderResourceView* srvs[NvHair::ShaderResourceType::COUNT_OF];
        m_hairSdk->getShaderResources(m_instanceId, NV_NULL, NV_COUNT_OF(srvs), Dx11Type::getPtr(srvs));
        // set to resource slot for our shader
        deviceContext->PSSetShaderResources(0, NV_COUNT_OF(srvs), srvs);
}

// set textures
{
        const NvHair::TextureType::Enum types[] = { NvHair::TextureType::ROOT_COLOR, NvHair::TextureType::TIP_COLOR };
        ID3D11ShaderResourceView* textureSrvs[NV_COUNT_OF(types)] = { NV_NULL };
        m_hairSdk->getTextures(m_instanceId, types, NV_COUNT_OF(types), Dx11Type::getPtr(textureSrvs));
        // Use the slots after NvHair::ShaderResourceType::COUNT_OF to hold these two textures
        deviceContext->PSSetShaderResources(NvHair::ShaderResourceType::COUNT_OF, 2, textureSrvs);
}

// Render the hair instance
m_hairSdk->renderHairs(m_instanceId);
// Render visualization the hair instance
m_hairSdk->renderVisualization(m_instanceId);

{
        // Unbind
        const int numResources = NvHair::ShaderResourceType::COUNT_OF + 2;
        ID3D11ShaderResourceView* nullResources[numResources] = { NV_NULL };
        deviceContext->PSSetShaderResources(0, numResources, nullResources);
}

First, we set d3d context and camera info, as in previous sample.

Note that HairWorks does not change D3D state such as sampler, shader resource, etc., so users must provide such resources when needed.

getShaderResources() call returns standard shader attributes such as tangents, normals, etc. As our shader needs such resources for proper lighting, we get the resources from HairWorks and pass them to our shader by the standard D3D PSSetShaderResources() call.

In addition, we map two texture (root color and tip color) resources to our shader.

.....

{
        // get standard shader resources for attribute interpolation
        ID3D11ShaderResourceView* srvs[NvHair::ShaderResourceType::COUNT_OF];
        m_hairSdk->getShaderResources(m_instanceId, NV_NULL, NV_COUNT_OF(srvs), Dx11Type::getPtr(srvs));
        // set to resource slot for our shader
        deviceContext->PSSetShaderResources(0, NV_COUNT_OF(srvs), srvs);
}

// set textures
{
        const NvHair::TextureType::Enum types[] = { NvHair::TextureType::ROOT_COLOR, NvHair::TextureType::TIP_COLOR };
        ID3D11ShaderResourceView* textureSrvs[NV_COUNT_OF(types)] = { NV_NULL };
        m_hairSdk->getTextures(m_instanceId, types, NV_COUNT_OF(types), Dx11Type::getPtr(textureSrvs));
        // Use the slots after NvHair::ShaderResourceType::COUNT_OF to hold these two textures
        deviceContext->PSSetShaderResources(NvHair::ShaderResourceType::COUNT_OF, 2, textureSrvs);
}

        .....

DirectX12 specifics

The following code reads the shader using the DxUtil, which internally uses DxIncludeHandler. Note in the code below, because we are using two additional textures for hair rendering, the number of shader resource views has + 2

ComPtr<ID3DBlob> pixelBlob;
NV_RETURN_ON_FAIL(DxUtil::findAndReadShader(m_app, SMP_HAIR_SAMPLE_ROOT "/Dx/Shader/ShadingHairShader.hlsl", "ps_main", "ps_5_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, pixelBlob));

NvHair::Dx12PixelShaderInfo pixelInfo;
pixelInfo.m_pixelBlob = (const UInt8*)pixelBlob->GetBufferPointer();
pixelInfo.m_pixelBlobSize = pixelBlob->GetBufferSize();
pixelInfo.m_targetInfo = m_appInterface->getDx12TargetInfo();

pixelInfo.m_hasDynamicConstantBuffer = true;
pixelInfo.m_numSrvs = NvHair::ShaderResourceType::COUNT_OF + 2;

// Okay try setting it
m_hairSdk->setPixelShader(0, NvHair::Dx12SdkType::wrapPtr(&pixelInfo));

The loading of the textures is handled via:

// set up texture resources
NV_RETURN_ON_FAIL(HairDx12Util::readTextures(m_app, m_mediaPath, m_hairSdk, m_assetId, m_instanceId));

This just loads, and sets the texture resource:

/* static */Result HairDx12Util::readTextures(Smp::App* app, const SubString& mediaPath, NvHair::Sdk* hairSdk, NvHair::AssetId assetId, NvHair::InstanceId instanceId)
{
        String filePath;
        for (int t = 0; t < NvHair::TextureType::COUNT_OF; ++t)
        {
                char textureFileName[1024];

                NV_RETURN_ON_FAIL(hairSdk->getTextureName(assetId, (NvHair::TextureType::Enum)t, textureFileName));
                if (textureFileName[0] == 0)
                {
                        continue;
                }
                WinPathUtil::append(mediaPath, SubString(SubString::INIT_CSTR, textureFileName), filePath);

                NvCo::ComPtr<ID3D12Resource> texture;
                NV_RETURN_ON_FAIL(Dx12Util::readTexture(app, filePath, texture));

                NV_RETURN_ON_FAIL(hairSdk->setTexture(instanceId, NvHair::TextureType::Enum(t), Dx12Type::wrap(texture.get())));
        }
        return NV_OK;
}

Finally rendering it is necessary to set up the srvs, and the constant buffer to render correctly:

{
        D3D12_CPU_DESCRIPTOR_HANDLE srvs[NvHair::ShaderResourceType::COUNT_OF + 2];
        // Get shader resources (these may change each render/simulate)
        m_hairSdk->getShaderResources(m_instanceId, NV_NULL, NvHair::ShaderResourceType::COUNT_OF, Dx12Type::wrapPtr(srvs));
        // Get the additional textures
        const NvHair::TextureType::Enum types[] = { NvHair::TextureType::ROOT_COLOR, NvHair::TextureType::TIP_COLOR };
        m_hairSdk->getTextures(m_instanceId, types, NV_COUNT_OF(types), Dx12Type::wrapPtr(srvs + NvHair::ShaderResourceType::COUNT_OF));

        NvHair::Dx12RenderInfo renderInfo;
        renderInfo.init();
        renderInfo.m_srvs = srvs;

        m_hairSdk->renderHairs(m_instanceId, NV_NULL, NvHair::Dx12SdkType::wrapPtr(&renderInfo));
}
// Render visualization the hair instance
m_hairSdk->renderVisualization(m_instanceId);

Shadow Sample

_images/HairWorks_FurSampleShadowScreenshot.jpg

Screenshot of the Shadow Sample sample.

Rendering hairs to shadow map

_images/HairWorks_FurSampleShadowMap.jpg

Visualization of shadow map.

Summary

This sample renders hairs to a user provided shadow map and render hairs with shadows.

Illustrated Features

This code sample shows

  1. How to use custom constant buffer
  2. How to render hairs to user’s own shadow map
  3. How to compute hair shadows in the hair shader

Running Instructions

  • Camer Zoom : Mouse Wheel
  • Camera Orbit : RMB
  • ‘v’ : toggle on/off shadow map visualization
  • ‘s’ : turn on/off shadow rendering

Details

This sample adds new data structures and codes for custom shader resource management and controls.

We create 2 shaders, one for hair shading and another for shadow pass. We also create custom constant buffer.

Our custom constant buffer adds 2 new variables to the standard HairWorks constant buffer:

struct ConstantBuffer
{
        XMMATRIX        lightView;
        XMMATRIX        lightWorldToTex;
        NvHair::ShaderConstantBuffer    m_hairConstantBuffer;
};

These are used to pass light matrices to the hair shader.

The shadow shader is in “samples/Src/HairSample/Dx/Shader/ShadowShadowShader.hlsl”. The shadow shader is shown below:

#include <Nv/HairWorks/Shader/NvHairShaderCommon.h>

cbuffer cbPerFrame : register(b0)
{
        NvHair_ConstantBuffer   g_hairConstantBuffer; // hairworks portion of constant buffer data
}

float ps_main(NvHair_PixelShaderInput input) : SV_Target0
{
        return NvHair_ScreenToView(input.position, g_hairConstantBuffer).z;
}

This shader simply computes linear depth (distance from camera) and writes this value to the render target.

The shadow map typically contains all the scene objects. If we have multiple hair instances, we use the same buffer so that hairs can cast shadows between them or from/to other scene objects.

In shadow rendering pass, we set shadow camera’s view projection matrix:

XMMATRIX projection = m_shadowMap->m_lightProjection;
XMMATRIX view = m_shadowMap->m_lightView;
m_hairSdk->setViewProjection((const gfsdk_float4x4&)view, (const gfsdk_float4x4&)projection, NvHair::HandednessHint::LEFT);

Note that we use left handed system for shadow camera in this sample, so we provide the hint to setViewProjection call. If your shadow camera is different, change it accordingly.

We use NvHair::ShaderSettings to indicate we are rendering hairs for shadow map:

{
        // render hairs to shadow buffer
        NvHair::ShaderSettings settings;

        settings.m_useCustomConstantBuffer = false;
        settings.m_shadowPass = true;

        m_hairSdk->renderHairs(m_instanceId, &settings);
}

Note that we defined m_useCustomConstantBuffer to be false in the above shader settings, so the constant buffer used in this shader is automatically filled by HairWorks.

Preparing custom constant buffers

In “ShadowDx11Sample::onRender(Float elapsedTime)”

As we now use custom constant buffer, we have to copy the buffer using standard D3D APIs:

// copy additional data we defined (light matrices)
ConstantBuffer* constantBuffer = (ConstantBuffer*)mappedSubResource.pData;
constantBuffer->lightView = m_shadowMap->m_lightView;
constantBuffer->lightWorldToTex = m_shadowMap->m_lightWorldToTex;
// use HairWorks API to fill the HairWorks portion of constant buffer
m_hairSdk->prepareShaderConstantBuffer(m_instanceId, constantBuffer->m_hairConstantBuffer);}

Note that we use the prepareShaderConstantBuffer() API to fill HairWorks portion of the constant buffer.

In addition, we copy our own data (light matrices) to the constant buffer as well.

This maps to the cbuffer definition of our hair shader:

cbuffer cbPerFrame : register(b0)
{
        // user side definition of light projection matrices added for shadow map sampling
        row_major float4x4      g_lightView;
        row_major float4x4      g_lightWorldToTex;

        // .... Add other cbuffer variables to get your own data ....

        // hairworks portion of constant buffer data
        NvHair_ConstantBuffer   g_hairConstantBuffer;
}

Computing shadows in the shader

The shader for this sample defines the following function:

inline float GetShadowFactor(NvHair_ConstantBuffer hairConstantBuffer, NvHair_Material mat, NvHair_ShaderAttributes attr)
{
        // texcoord of sample point in shadow texture
        float2 shadowTexcoords = mul(float4(attr.P, 1), g_lightWorldToTex).xy;

        // depth of this point in light's view space
        float viewSpaceDepth = mul(float4(attr.P, 1), g_lightView).z;

        // apply PCF filtering of absorption depth from linear depth texture
        // use gain = 1.0f for right handed light camera.
        float gain = -1.0f; // left handed light camera.

        float filteredDepth = NvHair_ShadowFilterDepth(g_shadowTexture, shadowSampler, shadowTexcoords, viewSpaceDepth, gain);

        // convert absorption depth into lit factor
        float lit = NvHair_ShadowLitFactor(hairConstantBuffer, mat, filteredDepth);

        return lit;
}

We compute hair shadows (lit factor) in the following steps.

  1. First get the world space position of our hair fragment (attr.P)
  2. Using the light matrix, we get corresponding texture coord of the point (shadowTexcoords).
  3. We also computes view space depth of the point using light camera used to generate the shadow map.
  4. We now compute penetration distance from the hair surface using NvHair_ShadowFilterDepth().
  5. Finally, we convert the depth to soft shadows using NvHair_ShadowLitFactor().

Note that this function may be completely bypassed if users wish to implement their own shadow sampling function, especially when other shadow sampling schemes are defined and widely used throughout their game engine. In that case, replace above function with any of such scehemes.

DirectX11 specifics

The sample needs a shader and constant buffer for shadow rendering pass, and a constant buffer:

// Custom pixel shader for back buffer rendering
ComPtr<ID3D11PixelShader> m_hairPixelShader;
// custom pixel shader for shadowmap rendering pass
ComPtr<ID3D11PixelShader> m_shadowPixelShader;
// custom constant buffer for use with custom pixel shader
ComPtr<ID3D11PixelShader> m_shadowPixelShader;
...
// Create custom pixel shader for hair rendering
NV_RETURN_ON_FAIL(Dx11Util::loadPixelShader(m_app, SMP_HAIR_SAMPLE_ROOT "/Dx/Shader/ShadowHairShader.hlsl", "ps_main", "ps_5_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, m_hairPixelShader));
// Create custom pixel shader for hair shadow pass
NV_RETURN_ON_FAIL(Dx11Util::loadPixelShader(m_app, SMP_HAIR_SAMPLE_ROOT "/Dx/Shader/ShadowShadowShader.hlsl", "ps_main", "ps_5_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, m_shadowPixelShader));

{
        D3D11_BUFFER_DESC desc;
        {
                desc.Usage = D3D11_USAGE_DYNAMIC;
                desc.ByteWidth = sizeof(ConstantBuffer);
                desc.StructureByteStride = 0;
                desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
                desc.MiscFlags = 0;
                desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
        }
        NV_RETURN_ON_FAIL(device->CreateBuffer(&desc, NV_NULL, m_constantBuffer.writeRef()));
}

...

The onPreRender() function renders hairs to a user prepared shadow render target.

void ShadowDx11HairSample::onPreRender(Float elapsedTime)
{
        ID3D11DeviceContext* context = m_appInterface->getDx11DeviceContext();
        // Set render context for HairWorks
        m_hairSdk->setCurrentContext(Dx11Type::getHandle(context));

        // shadow rendering pass ..
        {
                // prepare shadow map render target, etc. (usually happens in your app/engine)
                m_shadowMap->bindAndClear(context);

                // 1. ... render other scene objects to shadow map...

                // 2. get shadow camera's view projection matrix and set to HairWorks
                {
                        XMMATRIX projection = m_shadowMap->m_lightProjection;
                        XMMATRIX view = m_shadowMap->m_lightView;
                        m_hairSdk->setViewProjection((const gfsdk_float4x4&)view, (const gfsdk_float4x4&)projection, NvHair::HandednessHint::LEFT);
                }

                // 3. set shader settings and render hairs with our custom hair shadow shader
                {
                        // use custom shadow pass shader
                        context->PSSetShader(m_shadowPixelShader, NV_NULL, 0);

                        // render hairs to shadow buffer
                        NvHair::ShaderSettings settings;

                        settings.m_useCustomConstantBuffer = false;
                        settings.m_shadowPass = true;

                        m_hairSdk->renderHairs(m_instanceId, &settings);
                }
        }
}

Then, it renders hair colors to the final render target.

void ShadowDx11HairSample::onRender(Float elapsedTime)
{
        ID3D11DeviceContext* context = m_appInterface->getDx11DeviceContext();
        // ... render other scene objects ...

        m_app->clearRenderTarget(NV_NULL);

        // hair rendering pass
        {
                // 1. get view and projection matrix from your app, set to HairWorks
                {
                        XMMATRIX projection = getProjectionMatrix();
                        XMMATRIX view = getViewMatrix();
                        m_hairSdk->setViewProjection((const gfsdk_float4x4&)view, (const gfsdk_float4x4&)projection);
                }

                // 2. prepare HairWorks constant buffer and copy shadow matrices to our custom buffer
                {
                        D3D11_MAPPED_SUBRESOURCE mappedSubResource;
                        context->Map(m_constantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedSubResource);
                        ConstantBuffer* constantBuffer = (ConstantBuffer*)mappedSubResource.pData;
                        {
                                // copy additional data we defined (light matrices)
                                constantBuffer->lightView = m_shadowMap->m_lightView;
                                constantBuffer->lightWorldToTex = m_shadowMap->m_lightWorldToTex;
                                // use HairWorks API to fill the HairWorks portion of constant buffer
                                m_hairSdk->prepareShaderConstantBuffer(m_instanceId, constantBuffer->m_hairConstantBuffer);
                        }
                        context->Unmap(m_constantBuffer, 0);
                        context->PSSetConstantBuffers(0, 1, m_constantBuffer.readRef());
                }

                // 3. set resources for hair shader (color textures, shadow tex, attribute resources)
                // The resource mapping here should match declarations in the shader.
                {
                        // get standard shader resources for attribute interpolation
                        ID3D11ShaderResourceView* srvs[NvHair::ShaderResourceType::COUNT_OF];
                        m_hairSdk->getShaderResources(m_instanceId, NV_NULL, NvHair::ShaderResourceType::COUNT_OF, Dx11Type::getPtr(srvs));
                        context->PSSetShaderResources(0, NvHair::ShaderResourceType::COUNT_OF, srvs);

                        // set texture resource
                        NvHair::TextureType::Enum types[] = { NvHair::TextureType::ROOT_COLOR, NvHair::TextureType::TIP_COLOR };
                        ID3D11ShaderResourceView* textureSrvs[3] = { NV_NULL };

                        m_hairSdk->getTextures(m_instanceId, types, NV_COUNT_OF(types), Dx11Type::getPtr(textureSrvs));

                        // shadow srv, which is user side creation
                        textureSrvs[2] = m_shadowMap->m_backSrv;

                        context->PSSetShaderResources(NvHair::ShaderResourceType::COUNT_OF, 3, textureSrvs);
                }

                // 4. set texture sampler for color sampling and shadow PCF sampling
                {
                        ID3D11SamplerState* states[2] =
                        {
                                m_linearSampler,
                                m_pointSampler
                        };
                        context->PSSetSamplers(0, NV_COUNT_OF(states), states);
                }

                // 5. set hair shader and render hairs
                {
                        // set your hair pixel shader before rendering hairs
                        context->PSSetShader(m_hairPixelShader, NV_NULL, 0);

                        // set shader settings.
                        NvHair::ShaderSettings settings;
                        settings.m_useCustomConstantBuffer = true; // we use our own constant buffer

                        // Render the hair instance
                        m_hairSdk->renderHairs(m_instanceId, &settings);
                }

                // 6. clear shader resource references
                {
                        const Int numResources = NvHair::ShaderResourceType::COUNT_OF + 3;
                        ID3D11ShaderResourceView* nullResources[numResources] = { NV_NULL };
                        context->PSSetShaderResources(0, numResources, nullResources);
                }
        }
}

When rendering the Shadow map, make sure the correct pixel shader is used:

// use custom shadow pass shader
context->PSSetShader(m_shadowPixelShader, NV_NULL, 0);

In “ShadowDx11HairSample::onRender(Float elapsedTime)”, as we now use custom constant buffer, we have to copy the buffer using standard D3D APIs:

// 2. prepare HairWorks constant buffer and copy shadow matrices to our custom buffer
{
        D3D11_MAPPED_SUBRESOURCE mappedSubResource;
        context->Map(m_constantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedSubResource);
        ConstantBuffer* constantBuffer = (ConstantBuffer*)mappedSubResource.pData;
        {
                // copy additional data we defined (light matrices)
                constantBuffer->lightView = m_shadowMap->m_lightView;
                constantBuffer->lightWorldToTex = m_shadowMap->m_lightWorldToTex;
                // use HairWorks API to fill the HairWorks portion of constant buffer
                m_hairSdk->prepareShaderConstantBuffer(m_instanceId, constantBuffer->m_hairConstantBuffer);
        }
        context->Unmap(m_constantBuffer, 0);
        context->PSSetConstantBuffers(0, 1, m_constantBuffer.readRef());
}

As in Shading sample, we set shader resources for the pixel shader manually.

First we set the standard shader attribute resources. We also set color texture resources as well as shadow textures. We also set two texture samplers, one for color (linear) and another for shadow (point).

// 3. set resources for hair shader (color textures, shadow tex, attribute resources)
// The resource mapping here should match declarations in the shader.
{
        // get standard shader resources for attribute interpolation
        ID3D11ShaderResourceView* srvs[NvHair::ShaderResourceType::COUNT_OF];
        m_hairSdk->getShaderResources(m_instanceId, NV_NULL, NvHair::ShaderResourceType::COUNT_OF, Dx11Type::getPtr(srvs));
        context->PSSetShaderResources(0, NvHair::ShaderResourceType::COUNT_OF, srvs);

        // set texture resource
        NvHair::TextureType::Enum types[] = { NvHair::TextureType::ROOT_COLOR, NvHair::TextureType::TIP_COLOR };
        ID3D11ShaderResourceView* textureSrvs[3] = { NV_NULL };

        m_hairSdk->getTextures(m_instanceId, types, NV_COUNT_OF(types), Dx11Type::getPtr(textureSrvs));

        // shadow srv, which is user side creation
        textureSrvs[2] = m_shadowMap->m_backSrv;

        context->PSSetShaderResources(NvHair::ShaderResourceType::COUNT_OF, 3, textureSrvs);
}

Once constant buffers and resources are set, we now render hairs:

// 5. set hair shader and render hairs
{
        // set your hair pixel shader before rendering hairs
        context->PSSetShader(m_hairPixelShader, NV_NULL, 0);

        // set shader settings.
        NvHair::ShaderSettings settings;
        settings.m_useCustomConstantBuffer = true; // we use our own constant buffer

        // Render the hair instance
        m_hairSdk->renderHairs(m_instanceId, &settings);
}

DirectX12 Specifics

We need to set up two shaders - one for the shadowmap rendering and the other for the main render target rendering:

// Create custom pixel shader for HairWorks rendering
{
        ComPtr<ID3DBlob> pixelBlob;
        NV_RETURN_ON_FAIL(DxUtil::findAndReadShader(m_app, SMP_HAIR_SAMPLE_ROOT "/Dx/Shader/ShadowHairShader.hlsl", "ps_main", "ps_5_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, pixelBlob));

        NvHair::Dx12PixelShaderInfo pixelInfo;
        pixelInfo.m_pixelBlob = (const UInt8*)pixelBlob->GetBufferPointer();
        pixelInfo.m_pixelBlobSize = pixelBlob->GetBufferSize();
        pixelInfo.m_targetInfo = m_appInterface->getDx12TargetInfo();
        pixelInfo.m_hasDynamicConstantBuffer = true;

        pixelInfo.m_numSrvs = NvHair::ShaderResourceType::COUNT_OF + 3;

        // Set pixel shader at slot 0
        m_hairSdk->setPixelShader(0, NvHair::Dx12SdkType::wrapPtr(&pixelInfo));
}

{
        ComPtr<ID3DBlob> pixelBlob;
        NV_RETURN_ON_FAIL(DxUtil::findAndReadShader(m_app, SMP_HAIR_SAMPLE_ROOT "/Dx/Shader/ShadowShadowShader.hlsl", "ps_main", "ps_5_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, pixelBlob));

        NvHair::Dx12PixelShaderInfo pixelInfo;
        pixelInfo.m_pixelBlob = (const UInt8*)pixelBlob->GetBufferPointer();
        pixelInfo.m_pixelBlobSize = pixelBlob->GetBufferSize();

        pixelInfo.m_targetInfo.init();
        pixelInfo.m_targetInfo.m_renderTargetFormats[0] = DXGI_FORMAT_R32_FLOAT;
        pixelInfo.m_targetInfo.m_depthStencilFormat = DXGI_FORMAT_D32_FLOAT;

        pixelInfo.m_hasDynamicConstantBuffer = true;

        // Set pixel shader at slot 1
        m_hairSdk->setPixelShader(1, NvHair::Dx12SdkType::wrapPtr(&pixelInfo));
}

The shadow shader is in slot 0, and the regular render target shader is in slot 1. We also need a Shader Resource View (SRV) that will reference the shadow map target when rendering to the back buffer:

ID3D12Device* device = m_appInterface->getDx12Device();

m_srvHeap.init(device, 1, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE);
// Create the srv for the shadow buffer
ID3D12Resource* resource = m_shadowMap->m_renderTarget;
D3D12_SHADER_RESOURCE_VIEW_DESC desc;
Dx12HelperUtil::calcSrvDesc(resource, DXGI_FORMAT_UNKNOWN, desc);
// Create on the the heap
device->CreateShaderResourceView(resource, &desc, m_srvHeap.getCpuHandle(0));

The rendering to the shadow map takes place in the onPreRender method. The shadow buffer is cleared, and the projection set. A barrier is needed to wait until the shadow map can be used as a render target. After the hair is rendered, using shader at slot 1 (the shadow map shader), and using the expanded shadowPass enabled, another barrier is added to ensure the shadow map is available as a shader resource when used in the back buffer shader.

void ShadowDx12HairSample::onPreRender(Float elapsedTime)
{
        ID3D12GraphicsCommandList* commandList = m_appInterface->getDx12CommandList();

        // Set render context for HairWorks
        m_hairSdk->setCurrentContext(Dx12Type::wrap(commandList));
        m_shadowMap->bindAndClear(m_app);

        // shadow rendering pass ..
        {
                // 1. ... render other scene objects to shadow map...

                // 2. get shadow camera's view projection matrix and set to HairWorks
                {
                        XMMATRIX projection = m_shadowMap->m_lightProjection;
                        XMMATRIX view = m_shadowMap->m_lightView;
                        m_hairSdk->setViewProjection(HairDx12Util::getViewport(m_shadowMap->m_viewport), (const gfsdk_float4x4&)view, (const gfsdk_float4x4&)projection, NvHair::HandednessHint::LEFT);
                }

                // 3. set shader settings and render hairs with our custom hair shadow shader
                {
                        // render hairs to shadow buffer
                        {
                                D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_shadowMap->m_renderTarget, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET);
                                commandList->ResourceBarrier(1, &barrier);
                        }

                        {
                                NvHair::ShaderSettings settings;
                                settings.m_shadowPass = true;
                                settings.m_shaderIndex = 1;

                                m_hairSdk->renderHairs(m_instanceId, &settings);
                        }
                        {
                                D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_shadowMap->m_renderTarget, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
                                commandList->ResourceBarrier(1, &barrier);
                        }
                }
        }
}

Finally onRender renders to the backbuffer using the shadow map:

void ShadowDx12HairSample::onRender(Float elapsedTime)
{
        // When I enter the command list is open for adding things to render
        m_app->clearRenderTarget(NV_NULL);

        ID3D12GraphicsCommandList* commandList = m_appInterface->getDx12CommandList();

        // Set render context for HairWorks
        m_hairSdk->setCurrentContext(Dx12Type::wrap(commandList));

        // Set view matrix and projection matrix
        XMMATRIX projection = getProjectionMatrix();
        XMMATRIX view = getViewMatrix();
        NvHair::Viewport viewport(HairDx12Util::getViewport(m_appInterface->getDx12Viewport()));
        m_hairSdk->setViewProjection(viewport, (const gfsdk_float4x4&)view, (const gfsdk_float4x4&)projection, NvHair::HandednessHint::LEFT);

        // Render
        {
                // Get the srvs needed to render
                const NvHair::TextureType::Enum types[] = { NvHair::TextureType::ROOT_COLOR, NvHair::TextureType::TIP_COLOR };
                const Int numSrvs = NV_COUNT_OF(types) + NvHair::ShaderResourceType::COUNT_OF + 1;
                D3D12_CPU_DESCRIPTOR_HANDLE srvs[numSrvs];

                // These may change every simulate/render call
                m_hairSdk->getShaderResources(m_instanceId, NV_NULL, NvHair::ShaderResourceType::COUNT_OF, Dx12Type::wrapPtr(srvs));
                m_hairSdk->getTextures(m_instanceId, types, NV_COUNT_OF(types), Dx12Type::wrapPtr(srvs + NvHair::ShaderResourceType::COUNT_OF));
                // Set the shadow map srv
                srvs[numSrvs - 1] = m_srvHeap.getCpuStart();

                NvHair::ShaderSettings shaderSettings;
                shaderSettings.m_useCustomConstantBuffer = true;
                shaderSettings.m_shaderIndex = 0;

                // Calculate constant buffer
                ConstantBuffer constantBuffer;
                m_hairSdk->prepareShaderConstantBuffer(m_instanceId, constantBuffer.hairConstants);
                // copy additional data we defined (light matrices)
                constantBuffer.lightView = m_shadowMap->m_lightView;
                constantBuffer.lightWorldToTex = m_shadowMap->m_lightWorldToTex;

                NvHair::Dx12RenderInfo renderInfo;

                renderInfo.m_constantBufferData = &constantBuffer;
                renderInfo.m_constantBufferSize = sizeof(constantBuffer);

                renderInfo.m_srvs = srvs;

                // Render the hair instance
                m_hairSdk->renderHairs(m_instanceId, &shaderSettings, NvHair::Dx12SdkType::wrapPtr(&renderInfo));
        }
        // Render visualization the hair instance
        m_hairSdk->renderVisualization(m_instanceId);
}

Skinning Sample

_images/HairWorks_FurSampleSkinningScreenshot.jpg

Skinned animation of the monster sample character.

Summary

This sample shows how to setup skinning matrices for HairWorks.

Illustrated Features

  1. This sample updates the skinning matrices using UpdateSkinningMatrices() API.
  2. This sample uses bone visualization feature.

Running Instructions

  • Camera Zoom : Mouse Wheel
  • Camera Orbit : RMB
  • ‘p’ : toggle on/off animation
  • ‘b’ : toggle on/off bone hierarchy visualization
  • ‘g’ : toggle on/off growth mesh visualization
  • ‘h’ : toggle on/off hair rendering
  • ‘s’ : toggle on/off hair simulation

Details

At each frame, this sample updates skinning matrices of the hair asset and run hair simulation.

This sample uses precomputed skinning matrices g_skinningMatrices that are stored in SkinningMatrices.h.

_updateInstanceDescriptor(m_instanceDescriptor);
m_hairSdk->updateInstanceDescriptor(m_instanceId, m_instanceDescriptor);

if (m_animationEnable)
        m_animationFrame++;

m_animationFrame = (m_animationFrame >= m_numAnimationFrames) ? 0 : m_animationFrame;

// Makes sure its 16 byte aligned
const XMMATRIX* skinningMatrices = m_animatedSkinMatrices + (m_animationFrame * m_numBones);

// Update polygonal mesh animation
m_meshSet.updateMeshes(m_numBones, skinningMatrices);

// Update skinning matrices for the frame
m_hairSdk->updateSkinningMatrices(m_instanceId, m_numBones, (const gfsdk_float4x4*)skinningMatrices);

// run simulation for all hairs
m_hairSdk->stepSimulation();

This sample also uses several query API to construct growth mesh from a hairworks asset.

......
NV_RETURN_ON_FAIL(m_hairSdk->getRootVertices(m_assetId, (gfsdk_float3*)positions.begin()));
// Fill the bone indices info
NV_RETURN_ON_FAIL(m_hairSdk->getBoneIndices(m_assetId, (gfsdk_float4*)boneIndices.begin()));
// Fill the bone weights info
NV_RETURN_ON_FAIL(m_hairSdk->getBoneWeights(m_assetId, (gfsdk_float4*)boneWeights.begin()));
// Fill the face indices info
NV_RETURN_ON_FAIL(m_hairSdk->getFaceIndices(m_assetId, indices.begin()));

......

Note that this is only for illustration purpose - usual game engines would have character mesh drawing/mangement in a separate code block.

Number of bones and bone order should match between HairWorks asset and character.

If that’s not the case, please refer to the setBoneRemapping() API in the header.