Skip to content

Commit

Permalink
hairparticle: billboard replication and gravity factor
Browse files Browse the repository at this point in the history
  • Loading branch information
turanszkij committed Feb 16, 2025
1 parent bee1d11 commit efc8bdd
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 90 deletions.
57 changes: 57 additions & 0 deletions Editor/HairParticleWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ void HairParticleWindow::Create(EditorComponent* _editor)
meshComboBox.SetTooltip("Choose a mesh where hair will grow from...");
AddWidget(&meshComboBox);

cameraBendCheckbox.Create("Camera Bend: ");
cameraBendCheckbox.SetTooltip("Enable a slight bending in camera view, that can help hide the card look when looking from above.");
cameraBendCheckbox.OnClick([=](wi::gui::EventArgs args) {
wi::scene::Scene& scene = editor->GetCurrentScene();
for (auto& x : editor->translator.selected)
{
wi::HairParticleSystem* hair = scene.hairs.GetComponent(x.entity);
if (hair == nullptr)
continue;
hair->SetCameraBendEnabled(args.bValue);
hair->SetDirty();
}
});
AddWidget(&cameraBendCheckbox);

countSlider.Create(0, 100000, 1000, 100000, "Strand Count: ");
countSlider.SetSize(XMFLOAT2(wid, hei));
countSlider.SetPos(XMFLOAT2(x, y += step));
Expand Down Expand Up @@ -150,6 +165,24 @@ void HairParticleWindow::Create(EditorComponent* _editor)
dragSlider.SetTooltip("Set hair strand drag, how much its movement slows down over time.");
AddWidget(&dragSlider);

gravityPowerSlider.Create(0, 1, 0.5f, 100, "Gravity Power: ");
gravityPowerSlider.SetSize(XMFLOAT2(wid, hei));
gravityPowerSlider.SetPos(XMFLOAT2(x, y += step));
gravityPowerSlider.OnSlide([&](wi::gui::EventArgs args) {
wi::scene::Scene& scene = editor->GetCurrentScene();
for (auto& x : editor->translator.selected)
{
wi::HairParticleSystem* hair = scene.hairs.GetComponent(x.entity);
if (hair == nullptr)
continue;
hair->gravityPower = args.fValue;
hair->SetDirty();
}
});
gravityPowerSlider.SetEnabled(false);
gravityPowerSlider.SetTooltip("Set hair strand gravity, how much its movement is pulled down constantly.");
AddWidget(&gravityPowerSlider);

randomnessSlider.Create(0, 1, 0.2f, 1000, "Randomness: ");
randomnessSlider.SetSize(XMFLOAT2(wid, hei));
randomnessSlider.SetPos(XMFLOAT2(x, y += step));
Expand Down Expand Up @@ -186,6 +219,24 @@ void HairParticleWindow::Create(EditorComponent* _editor)
segmentcountSlider.SetTooltip("Set the number of segments that make up one strand.");
AddWidget(&segmentcountSlider);

billboardcountSlider.Create(1, 10, 1, 9, "Billboards: ");
billboardcountSlider.SetSize(XMFLOAT2(wid, hei));
billboardcountSlider.SetPos(XMFLOAT2(x, y += step));
billboardcountSlider.OnSlide([&](wi::gui::EventArgs args) {
wi::scene::Scene& scene = editor->GetCurrentScene();
for (auto& x : editor->translator.selected)
{
wi::HairParticleSystem* hair = scene.hairs.GetComponent(x.entity);
if (hair == nullptr)
continue;
hair->billboardCount = (uint32_t)args.iValue;
hair->SetDirty();
}
});
billboardcountSlider.SetEnabled(false);
billboardcountSlider.SetTooltip("Set the number of billboard geometry that make up one strand.");
AddWidget(&billboardcountSlider);

randomSeedSlider.Create(1, 12345, 1, 12344, "Random seed: ");
randomSeedSlider.SetSize(XMFLOAT2(wid, hei));
randomSeedSlider.SetPos(XMFLOAT2(x, y += step));
Expand Down Expand Up @@ -404,12 +455,15 @@ void HairParticleWindow::SetEntity(Entity entity)
widthSlider.SetValue(hair->width);
stiffnessSlider.SetValue(hair->stiffness);
dragSlider.SetValue(hair->drag);
gravityPowerSlider.SetValue(hair->gravityPower);
randomnessSlider.SetValue(hair->randomness);
countSlider.SetValue((float)hair->strandCount);
segmentcountSlider.SetValue((float)hair->segmentCount);
billboardcountSlider.SetValue((float)hair->billboardCount);
randomSeedSlider.SetValue((float)hair->randomSeed);
viewDistanceSlider.SetValue(hair->viewDistance);
uniformitySlider.SetValue(hair->uniformity);
cameraBendCheckbox.SetCheck(hair->IsCameraBendEnabled());

const MaterialComponent* material = editor->GetCurrentScene().materials.GetComponent(entity);
if (changed || material->IsDirty())
Expand Down Expand Up @@ -522,12 +576,15 @@ void HairParticleWindow::ResizeLayout()

add_fullwidth(infoLabel);
add(meshComboBox);
add_right(cameraBendCheckbox);
add(countSlider);
add(segmentcountSlider);
add(billboardcountSlider);
add(lengthSlider);
add(widthSlider);
add(stiffnessSlider);
add(dragSlider);
add(gravityPowerSlider);
add(randomnessSlider);
add(randomSeedSlider);
add(viewDistanceSlider);
Expand Down
3 changes: 3 additions & 0 deletions Editor/HairParticleWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ class HairParticleWindow : public wi::gui::Window

wi::gui::Label infoLabel;
wi::gui::ComboBox meshComboBox;
wi::gui::CheckBox cameraBendCheckbox;
wi::gui::Slider lengthSlider;
wi::gui::Slider widthSlider;
wi::gui::Slider stiffnessSlider;
wi::gui::Slider dragSlider;
wi::gui::Slider gravityPowerSlider;
wi::gui::Slider randomnessSlider;
wi::gui::Slider countSlider;
wi::gui::Slider segmentcountSlider;
wi::gui::Slider billboardcountSlider;
wi::gui::Slider randomSeedSlider;
wi::gui::Slider viewDistanceSlider;
wi::gui::Slider uniformitySlider;
Expand Down
6 changes: 6 additions & 0 deletions WickedEngine/shaders/ShaderInterop_HairParticle.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum HAIR_FLAGS
{
HAIR_FLAG_REGENERATE_FRAME = 1 << 0,
HAIR_FLAG_UNORM_POS = 1 << 1,
HAIR_FLAG_CAMERA_BEND = 1 << 2,
};

struct HairParticleAtlasRect
Expand Down Expand Up @@ -52,6 +53,11 @@ CBUFFER(HairParticleCB, CBSLOT_OTHER_HAIRPARTICLE)
float xHairUniformity;
uint xHairAtlasRectCount;

float xHairGravityPower;
uint xHairBillboardCount;
float xHair_padding0;
float xHair_padding1;

HairParticleAtlasRect xHairAtlasRects[64];
};

Expand Down
181 changes: 112 additions & 69 deletions WickedEngine/shaders/hairparticle_simulateCS.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -97,57 +97,80 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn
len /= (half)xHairSegmentCount;
const float2 frame = float2(atlas_rect.aspect * xHairAspect * xHairSegmentCount, 1) * len * 0.5;

const uint gfx_vertexcount_per_strand = xHairSegmentCount * 2 + 2;
const uint gfx_vertexcount_per_strand = (xHairSegmentCount * 2 + 2) * xHairBillboardCount;
const uint gfx_indexcount_per_particle = 6 * xHairBillboardCount;
uint v0 = DTid.x * gfx_vertexcount_per_strand;

//draw_line(base, base + tangent, float4(1, 0, 0, 1));
//draw_line(base, base + target, float4(0, 1, 0, 1));
//draw_line(base, base + binormal, float4(0, 0, 1, 1));

float3 bend = 0;
if (xHairFlags & HAIR_FLAG_CAMERA_BEND)
{
// Bend down to camera up vector to avoid seeing flat planes from above
bend = GetCamera().up * (1 - saturate(dot(target, GetCamera().up))) * 0.8;
}

// Bottom vertices:
half3x3 TBN = half3x3(tangent, target, binormal);
for (uint vertexID = 0; vertexID < 2; ++vertexID)
half3x3 TBN = half3x3(tangent, normalize(target + bend), binormal);
rng.init(uint2(xHairRandomSeed, DTid.x), 1); // reinit random for consistent billboard variation!
for (uint billboardID = 0; billboardID < xHairBillboardCount; ++billboardID)
{
float3 patchPos = HAIRPATCH[vertexID];
float2 uv = patchPos.xy;
uv = uv * float2(0.5, 0.5) + 0.5;
uv.y = 1 - uv.y;
patchPos.y += 1;
float siz = billboardID == 0 ? 1 : lerp(0.2, 1, rng.next_float());
float rot = billboardID == 0 ? 0 : (rng.next_float() * PI);
float2 rot_sincos;
sincos(rot, rot_sincos.x, rot_sincos.y);
float3x3 variationMatrix = float3x3(
rot_sincos.y * siz, 0, -rot_sincos.x,
0, siz, 0,
rot_sincos.x, 0, rot_sincos.y * siz
);

for (uint vertexID = 0; vertexID < 2; ++vertexID)
{
float3 patchPos = HAIRPATCH[vertexID];
float2 uv = patchPos.xy;
uv = uv * float2(0.5, 0.5) + 0.5;
uv.y = 1 - uv.y;
patchPos.y += 1;

// Sprite sheet UV transform:
uv.xy = mad(uv.xy, atlas_rect.texMulAdd.xy, atlas_rect.texMulAdd.zw);
// Sprite sheet UV transform:
uv.xy = mad(uv.xy, atlas_rect.texMulAdd.xy, atlas_rect.texMulAdd.zw);

// scale the billboard by the texture aspect:
patchPos.xyz *= frame.xyx;
// scale the billboard by the texture aspect:
patchPos.xyz *= frame.xyx;

// rotate the patch into the tangent space of the emitting triangle:
patchPos = mul(patchPos, TBN);
// variation based on billboardID:
patchPos = mul(patchPos, variationMatrix);

float3 position = base + patchPos;
// rotate the patch into the tangent space of the emitting triangle:
patchPos = mul(patchPos, TBN);

if (xHairFlags & HAIR_FLAG_UNORM_POS)
{
position = inverse_lerp(geometry.aabb_min, geometry.aabb_max, position); // remap to UNORM
}
float3 position = base + patchPos;

if (xHairFlags & HAIR_FLAG_UNORM_POS)
{
position = inverse_lerp(geometry.aabb_min, geometry.aabb_max, position); // remap to UNORM
}

vertexBuffer_POS[v0] = float4(position, 0);
vertexBuffer_NOR[v0] = half4(target, 0);
vertexBuffer_UVS[v0] = uv.xyxy; // a second uv set could be used here
vertexBuffer_POS[v0] = float4(position, 0);
vertexBuffer_NOR[v0] = half4(target, 0);
vertexBuffer_UVS[v0] = uv.xyxy; // a second uv set could be used here

if (distance_culled)
{
position = 0; // We can only zero out for raytracing geometry to keep correct prevpos swapping motion vectors!
}
vertexBuffer_POS_RT[v0] = float4(position, 0);
if (distance_culled)
{
position = 0; // We can only zero out for raytracing geometry to keep correct prevpos swapping motion vectors!
}
vertexBuffer_POS_RT[v0] = float4(position, 0);

v0++;
v0++;
}
}

// Bend down to camera up vector to avoid seeing flat planes from above
const float3 bend = GetCamera().up * (1 - saturate(dot(target, GetCamera().up))) * 0.8;

const float dt = clamp(GetFrame().delta_time, 0, 1.0 / 30.0); // clamp delta time to avoid simulation blowing up

const float gravityPower = xHairGravityPower;
const float stiffnessForce = xHairStiffness;
const float dragForce = xHairDrag;
const float3 boneAxis = target;
Expand All @@ -169,7 +192,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn
float3 tail_prev = simulationBuffer[particleID].prevTail;
float3 inertia = (tail_current - tail_prev) * (1 - dragForce);
float3 stiffness = boneAxis * stiffnessForce;
float3 external = 0;
float3 external = gravityPower * float3(0, -1, 0);
float3 wind = sample_wind(tail_current, ((float)segmentID + 1) / (float)xHairSegmentCount);
external += wind;

Expand Down Expand Up @@ -291,43 +314,60 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn
//draw_line(base, base + tangent, float4(1, 0, 0, 1));
//draw_line(base, base + normal, float4(0, 1, 0, 1));
//draw_line(base, base + binormal, float4(0, 0, 1, 1));

for (uint vertexID = 2; vertexID < 4; ++vertexID)

rng.init(uint2(xHairRandomSeed, DTid.x), 1); // reinit random for consistent billboard variation!
for(uint billboardID = 0; billboardID < xHairBillboardCount; ++billboardID)
{
float3 patchPos = HAIRPATCH[vertexID];
float2 uv = patchPos.xy;
uv = uv * float2(0.5, 0.5) + 0.5;
uv.y = lerp((float)segmentID / (float)xHairSegmentCount, ((float)segmentID + 1) / (float)xHairSegmentCount, uv.y);
uv.y = 1 - uv.y;
patchPos.y += 1;
float siz = billboardID == 0 ? 1 : lerp(0.2, 1, rng.next_float());
float rot = billboardID == 0 ? 0 : (rng.next_float() * PI);
float2 rot_sincos;
sincos(rot, rot_sincos.x, rot_sincos.y);
float3x3 variationMatrix = float3x3(
rot_sincos.y * siz, 0, -rot_sincos.x,
0, siz, 0,
rot_sincos.x, 0, rot_sincos.y * siz
);

for (uint vertexID = 2; vertexID < 4; ++vertexID)
{
float3 patchPos = HAIRPATCH[vertexID];
float2 uv = patchPos.xy;
uv = uv * float2(0.5, 0.5) + 0.5;
uv.y = lerp((float)segmentID / (float)xHairSegmentCount, ((float)segmentID + 1) / (float)xHairSegmentCount, uv.y);
uv.y = 1 - uv.y;
patchPos.y += 1;

// Sprite sheet UV transform:
uv.xy = mad(uv.xy, atlas_rect.texMulAdd.xy, atlas_rect.texMulAdd.zw);
// Sprite sheet UV transform:
uv.xy = mad(uv.xy, atlas_rect.texMulAdd.xy, atlas_rect.texMulAdd.zw);

// scale the billboard by the texture aspect:
patchPos.xyz *= frame.xyx;
// scale the billboard by the texture aspect:
patchPos.xyz *= frame.xyx;

// variation based on billboardID:
patchPos = mul(patchPos, variationMatrix);

// rotate the patch into the tangent space of the emitting triangle:
patchPos = mul(patchPos, TBN);
// rotate the patch into the tangent space of the emitting triangle:
patchPos = mul(patchPos, TBN);

float3 position = base + patchPos;
float3 position = base + patchPos;

if (xHairFlags & HAIR_FLAG_UNORM_POS)
{
position = inverse_lerp(geometry.aabb_min, geometry.aabb_max, position); // remap to UNORM
}
if (xHairFlags & HAIR_FLAG_UNORM_POS)
{
position = inverse_lerp(geometry.aabb_min, geometry.aabb_max, position); // remap to UNORM
}

vertexBuffer_POS[v0] = float4(position, 0);
vertexBuffer_NOR[v0] = half4(normal, 0);
vertexBuffer_UVS[v0] = uv.xyxy; // a second uv set could be used here
vertexBuffer_POS[v0] = float4(position, 0);
vertexBuffer_NOR[v0] = half4(normal, 0);
vertexBuffer_UVS[v0] = uv.xyxy; // a second uv set could be used here

if (distance_culled)
{
position = 0; // We can only zero out for raytracing geometry to keep correct prevpos swapping motion vectors!
}
vertexBuffer_POS_RT[v0] = float4(position, 0);
if (distance_culled)
{
position = 0; // We can only zero out for raytracing geometry to keep correct prevpos swapping motion vectors!
}
vertexBuffer_POS_RT[v0] = float4(position, 0);

v0++;
v0++;
}
}

// Frustum culling:
Expand All @@ -343,21 +383,24 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn
uint waveOffset;
if (WaveIsFirstLane() && waveAppendCount > 0)
{
InterlockedAdd(indirectBuffer[0].IndexCountPerInstance, waveAppendCount * 6, waveOffset);
InterlockedAdd(indirectBuffer[0].IndexCountPerInstance, waveAppendCount * gfx_indexcount_per_particle, waveOffset);
}
waveOffset = WaveReadLaneFirst(waveOffset);

if (visible)
{
uint prevCount = waveOffset + WavePrefixSum(6u);
uint i0 = particleID * 6;
uint prevCount = waveOffset + WavePrefixSum(gfx_indexcount_per_particle);
uint i0 = particleID * gfx_indexcount_per_particle;
uint ii0 = prevCount;
culledIndexBuffer[ii0 + 0] = i0 + 0;
culledIndexBuffer[ii0 + 1] = i0 + 1;
culledIndexBuffer[ii0 + 2] = i0 + 2;
culledIndexBuffer[ii0 + 3] = i0 + 3;
culledIndexBuffer[ii0 + 4] = i0 + 4;
culledIndexBuffer[ii0 + 5] = i0 + 5;
for (uint billboardID = 0; billboardID < xHairBillboardCount; ++billboardID)
{
culledIndexBuffer[ii0++] = i0++;
culledIndexBuffer[ii0++] = i0++;
culledIndexBuffer[ii0++] = i0++;
culledIndexBuffer[ii0++] = i0++;
culledIndexBuffer[ii0++] = i0++;
culledIndexBuffer[ii0++] = i0++;
}
}

// Offset next segment root to current tip:
Expand Down
Loading

0 comments on commit efc8bdd

Please sign in to comment.