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.
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
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.
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();
}
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));
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.
This code sample shows
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);
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);
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.
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.
This code sample shows
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);
}
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.
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);
}
.....
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);
Rendering hairs to shadow map
This sample renders hairs to a user provided shadow map and render hairs with shadows.
This code sample shows
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.
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;
}
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.
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.
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);
}
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);
}
This sample shows how to setup skinning matrices for HairWorks.
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.