diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 2c2ea249e..3db7aca51 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -5,7 +5,6 @@ on: concurrency: group: ${{ github.ref }} - cancel-in-progress: true env: UPX_VERSION: 4.2.4 diff --git a/Content/terrain/grass.wiscene b/Content/terrain/grass.wiscene index 15013f708..d7759bed2 100644 Binary files a/Content/terrain/grass.wiscene and b/Content/terrain/grass.wiscene differ diff --git a/Content/terrain/props.wiscene b/Content/terrain/props.wiscene index a20dddf8e..764c4eaae 100644 Binary files a/Content/terrain/props.wiscene and b/Content/terrain/props.wiscene differ diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 6df09d7eb..8136759cc 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -2024,8 +2024,9 @@ void EditorComponent::Update(float dt) for (size_t i = 0; i < scene.hairs.GetCount(); ++i) { Entity entity = scene.hairs.GetEntity(i); + const wi::HairParticleSystem& hair = scene.hairs[i]; HierarchyComponent* hier = scene.hierarchy.GetComponent(entity); - if (hier != nullptr && hier->parentID == hovered.entity) + if (hair.meshID == hovered.entity || (hier != nullptr && hier->parentID == hovered.entity)) { XMVECTOR P = XMLoadFloat3(&hovered.position); P += XMLoadFloat3(&hovered.normal) * 2; @@ -2036,8 +2037,8 @@ void EditorComponent::Update(float dt) ForceFieldComponent& force = scene.forces.Create(grass_interaction_entity); TransformComponent& transform = scene.transforms.Create(grass_interaction_entity); force.type = ForceFieldComponent::Type::Point; - force.gravity = -80; - force.range = 3; + force.gravity = -40; + force.range = 2; transform.Translate(P); break; } diff --git a/Editor/HairParticleWindow.cpp b/Editor/HairParticleWindow.cpp index 08e0a1fb0..bb9e267b0 100644 --- a/Editor/HairParticleWindow.cpp +++ b/Editor/HairParticleWindow.cpp @@ -114,7 +114,7 @@ void HairParticleWindow::Create(EditorComponent* _editor) widthSlider.SetTooltip("Set hair strand width multiplier. This only scales the hair particles horizontally."); AddWidget(&widthSlider); - stiffnessSlider.Create(0, 20, 5, 1000, "Stiffness: "); + stiffnessSlider.Create(0, 10, 0.5f, 100, "Stiffness: "); stiffnessSlider.SetSize(XMFLOAT2(wid, hei)); stiffnessSlider.SetPos(XMFLOAT2(x, y += step)); stiffnessSlider.OnSlide([&](wi::gui::EventArgs args) { @@ -127,11 +127,29 @@ void HairParticleWindow::Create(EditorComponent* _editor) hair->stiffness = args.fValue; hair->SetDirty(); } - }); + }); stiffnessSlider.SetEnabled(false); stiffnessSlider.SetTooltip("Set hair strand stiffness, how much it tries to get back to rest position."); AddWidget(&stiffnessSlider); + dragSlider.Create(0, 1, 0.5f, 100, "Drag: "); + dragSlider.SetSize(XMFLOAT2(wid, hei)); + dragSlider.SetPos(XMFLOAT2(x, y += step)); + dragSlider.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->drag = args.fValue; + hair->SetDirty(); + } + }); + dragSlider.SetEnabled(false); + dragSlider.SetTooltip("Set hair strand drag, how much its movement slows down over time."); + AddWidget(&dragSlider); + randomnessSlider.Create(0, 1, 0.2f, 1000, "Randomness: "); randomnessSlider.SetSize(XMFLOAT2(wid, hei)); randomnessSlider.SetPos(XMFLOAT2(x, y += step)); @@ -150,22 +168,23 @@ void HairParticleWindow::Create(EditorComponent* _editor) randomnessSlider.SetTooltip("Set hair length randomization factor. This will affect randomness of hair lengths."); AddWidget(&randomnessSlider); - //segmentcountSlider.Create(1, 10, 1, 9, "Segment Count: "); - //segmentcountSlider.SetSize(XMFLOAT2(wid, hei)); - //segmentcountSlider.SetPos(XMFLOAT2(x, y += step)); - //segmentcountSlider.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->segmentCount = (uint32_t)args.iValue; - // } - //}); - //segmentcountSlider.SetEnabled(false); - //segmentcountSlider.SetTooltip("Set hair strand segment count. This will affect simulation quality and performance."); - //AddWidget(&segmentcountSlider); + segmentcountSlider.Create(1, 10, 1, 9, "Segments: "); + segmentcountSlider.SetSize(XMFLOAT2(wid, hei)); + segmentcountSlider.SetPos(XMFLOAT2(x, y += step)); + segmentcountSlider.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->segmentCount = (uint32_t)args.iValue; + hair->SetDirty(); + } + }); + segmentcountSlider.SetEnabled(false); + segmentcountSlider.SetTooltip("Set the number of segments that make up one strand."); + AddWidget(&segmentcountSlider); randomSeedSlider.Create(1, 12345, 1, 12344, "Random seed: "); randomSeedSlider.SetSize(XMFLOAT2(wid, hei)); @@ -384,9 +403,10 @@ void HairParticleWindow::SetEntity(Entity entity) lengthSlider.SetValue(hair->length); widthSlider.SetValue(hair->width); stiffnessSlider.SetValue(hair->stiffness); + dragSlider.SetValue(hair->drag); randomnessSlider.SetValue(hair->randomness); countSlider.SetValue((float)hair->strandCount); - //segmentcountSlider.SetValue((float)hair->segmentCount); + segmentcountSlider.SetValue((float)hair->segmentCount); randomSeedSlider.SetValue((float)hair->randomSeed); viewDistanceSlider.SetValue(hair->viewDistance); uniformitySlider.SetValue(hair->uniformity); @@ -503,9 +523,11 @@ void HairParticleWindow::ResizeLayout() add_fullwidth(infoLabel); add(meshComboBox); add(countSlider); + add(segmentcountSlider); add(lengthSlider); add(widthSlider); add(stiffnessSlider); + add(dragSlider); add(randomnessSlider); add(randomSeedSlider); add(viewDistanceSlider); diff --git a/Editor/HairParticleWindow.h b/Editor/HairParticleWindow.h index b7e77a42a..8dfcea053 100644 --- a/Editor/HairParticleWindow.h +++ b/Editor/HairParticleWindow.h @@ -23,6 +23,7 @@ public: wi::gui::Slider lengthSlider; wi::gui::Slider widthSlider; wi::gui::Slider stiffnessSlider; + wi::gui::Slider dragSlider; wi::gui::Slider randomnessSlider; wi::gui::Slider countSlider; wi::gui::Slider segmentcountSlider; diff --git a/WickedEngine/shaders/ShaderInterop_HairParticle.h b/WickedEngine/shaders/ShaderInterop_HairParticle.h index 9fd72aecf..6708e49c8 100644 --- a/WickedEngine/shaders/ShaderInterop_HairParticle.h +++ b/WickedEngine/shaders/ShaderInterop_HairParticle.h @@ -8,10 +8,8 @@ struct PatchSimulationData { - float3 position; - uint tangent_random; - uint3 normal_velocity; // packed fp16 - uint binormal_length; + float3 prevTail; + float3 currentTail; }; enum HAIR_FLAGS @@ -37,7 +35,7 @@ CBUFFER(HairParticleCB, CBSLOT_OTHER_HAIRPARTICLE) uint xHairFlags; float xHairLength; float xHairStiffness; - float xHairRandomness; + float xHairDrag; uint xHairParticleCount; uint xHairStrandCount; @@ -49,8 +47,8 @@ CBUFFER(HairParticleCB, CBSLOT_OTHER_HAIRPARTICLE) uint xHairInstanceIndex; uint xHairLayerMask; + float xHairRandomness; float xHairAspect; - float xHairPadding1; float xHairUniformity; uint xHairAtlasRectCount; diff --git a/WickedEngine/shaders/ShaderInterop_Renderer.h b/WickedEngine/shaders/ShaderInterop_Renderer.h index a8af72ba9..50ca00350 100644 --- a/WickedEngine/shaders/ShaderInterop_Renderer.h +++ b/WickedEngine/shaders/ShaderInterop_Renderer.h @@ -1193,7 +1193,7 @@ struct alignas(16) FrameCB uint lights; uint decals; uint forces; - uint padding; + int indirect_debugbufferindex; ShaderEntity entityArray[SHADER_ENTITY_COUNT]; float4x4 matrixArray[SHADER_ENTITY_COUNT]; diff --git a/WickedEngine/shaders/globals.hlsli b/WickedEngine/shaders/globals.hlsli index 89da6005f..e051883fc 100644 --- a/WickedEngine/shaders/globals.hlsli +++ b/WickedEngine/shaders/globals.hlsli @@ -884,6 +884,35 @@ inline min16uint2 GetTemporalAASampleRotation() { return uint2(GetFrame().tempor inline bool IsStaticSky() { return GetScene().globalenvmap >= 0; } inline half GetGIBoost() { return unpack_half2(GetFrame().giboost_packed).x; } +// Indirect debug drawing functionality, useable from any shader: +inline void draw_line(float3 a, float3 b, float4 color = 1) +{ + [branch] + if (GetFrame().indirect_debugbufferindex < 0) + return; + RWByteAddressBuffer indirect_debug_buffer = bindless_rwbuffers[descriptor_index(GetFrame().indirect_debugbufferindex)]; + uint prevCount; + indirect_debug_buffer.InterlockedAdd(0, 2, prevCount); // VertexCountPerInstance += 2 + struct DebugLine + { + float4 posA; + float4 colorA; + float4 posB; + float4 colorB; + } debugline; + debugline.posA = float4(a, 1); + debugline.posB = float4(b, 1); + debugline.colorA = color; + debugline.colorB = color; + indirect_debug_buffer.Store(sizeof(IndirectDrawArgsInstanced) + sizeof(DebugLine) * prevCount / 2, debugline); +} +inline void draw_point(float3 pos, float size = 1, float4 color = 1) +{ + draw_line(pos - float3(size, 0, 0), pos + float3(size, 0, 0), color); + draw_line(pos - float3(0, size, 0), pos + float3(0, size, 0), color); + draw_line(pos - float3(0, 0, size), pos + float3(0, 0, size), color); +} + // Mie scaterring approximated with Henyey-Greenstein phase function. // https://www.alexandre-pestana.com/volumetric-lights/ #define G_SCATTERING 0.66 diff --git a/WickedEngine/shaders/hairparticleVS.hlsl b/WickedEngine/shaders/hairparticleVS.hlsl index 685997e4b..169b6ebf9 100644 --- a/WickedEngine/shaders/hairparticleVS.hlsl +++ b/WickedEngine/shaders/hairparticleVS.hlsl @@ -4,7 +4,7 @@ Buffer primitiveBuffer : register(t0); -VertexToPixel main(uint vid : SV_VERTEXID) +VertexToPixel main(uint vid : SV_VertexID) { ShaderMeshInstance inst = HairGetInstance(); ShaderGeometry geometry = HairGetGeometry(); diff --git a/WickedEngine/shaders/hairparticle_simulateCS.hlsl b/WickedEngine/shaders/hairparticle_simulateCS.hlsl index fd09df25f..fee7364ea 100644 --- a/WickedEngine/shaders/hairparticle_simulateCS.hlsl +++ b/WickedEngine/shaders/hairparticle_simulateCS.hlsl @@ -3,8 +3,11 @@ #include "ShaderInterop_HairParticle.h" static const float3 HAIRPATCH[] = { + // root (for every strand): float3(-1, -1, 0), float3(1, -1, 0), + + // cap (for every segment of every strand): float3(-1, 1, 0), float3(1, 1, 0), }; @@ -25,7 +28,7 @@ RWBuffer vertexBuffer_NOR : register(u6); [numthreads(THREADCOUNT_SIMULATEHAIR, 1, 1)] void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIndex : SV_GroupIndex) { - if (DTid.x >= xHairParticleCount) + if (DTid.x >= xHairStrandCount) return; ShaderGeometry geometry = HairGetGeometry(); @@ -63,76 +66,118 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn g = 1 - g; } float2 bary = float2(f, g); + + const float4x4 worldMatrix = xHairTransform.GetMatrix(); // compute final surface position on triangle from barycentric coords: float3 position = attribute_at_bary(pos0, pos1, pos2, bary); position = mul(xHairBaseMeshUnormRemap.GetMatrix(), float4(position, 1)).xyz; half3 target = normalize(attribute_at_bary(nor0, nor1, nor2, bary)); + target = normalize(mul((half3x3)worldMatrix, target)); half3 tangent = normalize(mul(half3(hemispherepoint_cos(rng.next_float(), rng.next_float()).xy, 0), get_tangentspace(target))); half3 binormal = cross(target, tangent); half strand_length = attribute_at_bary(length0, length1, length2, bary); - - uint tangent_random = 0; - tangent_random |= (uint)((uint)(tangent.x * 127.5 + 127.5) << 0); - tangent_random |= (uint)((uint)(tangent.y * 127.5 + 127.5) << 8); - tangent_random |= (uint)((uint)(tangent.z * 127.5 + 127.5) << 16); - tangent_random |= (uint)(rng.next_float() * 255) << 24; const uint currentFrame = uint(noise_gradient_3D(position * xHairUniformity) * 1000) % xHairAtlasRectCount; const HairParticleAtlasRect atlas_rect = xHairAtlasRects[currentFrame]; - - uint binormal_length = 0; - binormal_length |= (uint)((uint)(binormal.x * 127.5 + 127.5) << 0); - binormal_length |= (uint)((uint)(binormal.y * 127.5 + 127.5) << 8); - binormal_length |= (uint)((uint)(binormal.z * 127.5 + 127.5) << 16); - binormal_length |= (uint)(lerp(1, rng.next_float(), saturate(xHairRandomness)) * strand_length * 255) << 24; - + // Identifies the hair strand root particle: const uint strandID = DTid.x * xHairSegmentCount; // Transform particle by the emitter object matrix: - const float4x4 worldMatrix = xHairTransform.GetMatrix(); float3 base = mul(worldMatrix, float4(position.xyz, 1)).xyz; - target = normalize(mul((half3x3)worldMatrix, target)); - const float3 root = base; - - float3 diff = GetCamera().position - root; + + float3 diff = GetCamera().position - base; const float distsq = dot(diff, diff); const bool distance_culled = distsq > sqr(xHairViewDistance); + + half len = lerp(1, rng.next_float(), saturate(xHairRandomness)) * strand_length; + len *= xHairLength; + len *= atlas_rect.size; + 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; + 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)); + + // Bottom vertices: + half3x3 TBN = half3x3(tangent, target, binormal); + 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); + + // 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); + + 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 + + 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++; + } // 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 - half3 normal = 0; - - const float delta_time = clamp(GetFrame().delta_time, 0, 1.0 / 30.0); // clamp delta time to avoid simulation blowing up + const float stiffnessForce = xHairStiffness; + const float dragForce = xHairDrag; + const float3 boneAxis = target; + const float boneLength = len; for (uint segmentID = 0; segmentID < xHairSegmentCount; ++segmentID) { // Identifies the hair strand segment particle: const uint particleID = strandID + segmentID; - simulationBuffer[particleID].tangent_random = tangent_random; - simulationBuffer[particleID].binormal_length = binormal_length; if (xHairFlags & HAIR_FLAG_REGENERATE_FRAME) { - simulationBuffer[particleID].position = base; - simulationBuffer[particleID].normal_velocity = f32tof16(target); + float3 tail = base + boneAxis * boneLength; + simulationBuffer[particleID].prevTail = tail; + simulationBuffer[particleID].currentTail = tail; } - - normal += f16tof32(simulationBuffer[particleID].normal_velocity); - normal = normalize(normal); - - float len = (binormal_length >> 24) & 0x000000FF; - len /= 255.0; - len *= xHairLength; - len *= atlas_rect.size; - - float3 tip = base + normal * len; - float3 midpoint = lerp(base, tip, 0.5); + + float3 tail_current = simulationBuffer[particleID].currentTail; + float3 tail_prev = simulationBuffer[particleID].prevTail; + float3 inertia = (tail_current - tail_prev) * (1 - dragForce); + float3 stiffness = boneAxis * stiffnessForce; + float3 external = 0; + float3 wind = sample_wind(tail_current, ((float)segmentID + 1) / (float)xHairSegmentCount); + external += wind; + + float3 tail_next = tail_current + inertia + dt * (stiffness + external); + float3 to_tail = normalize(tail_next - base); + tail_next = base + to_tail * boneLength; // Accumulate forces, apply colliders: - half3 force = 0; for (uint i = forces().first_item(); i < forces().end_item(); ++i) { ShaderEntity entity = load_entity(i); @@ -150,153 +195,148 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn float3 N = normalize(A - B); A -= N * range; B += N * range; - float3 C = closest_point_on_segment(A, B, midpoint); - float3 dir = C - midpoint; + float3 C = closest_point_on_segment(A, B, tail_next); + float3 dir = C - tail_next; float dist = length(dir); dir /= dist; dist = dist - range - len * 0.5; if (dist < 0) { - tip = tip - dir * dist; + tail_next += dir * dist; + to_tail = normalize(tail_next - base); + tail_next = base + to_tail * boneLength; } } else { - float3 closest_point = closest_point_on_segment(base, tip, entity.position); + float3 closest_point = closest_point_on_segment(base, tail_next, entity.position); float3 dir = entity.position - closest_point; float dist = length(dir); dir /= dist; switch (type) { - case ENTITY_TYPE_FORCEFIELD_POINT: - force += dir * entity.GetGravity() * (1 - saturate(dist / range)); - break; - case ENTITY_TYPE_FORCEFIELD_PLANE: - force += entity.GetDirection() * entity.GetGravity() * (1 - saturate(dist / range)); - break; - case ENTITY_TYPE_COLLIDER_SPHERE: - dist = dist - range - len; - if (dist < 0) - { - tip = tip - dir * dist; - } - break; - case ENTITY_TYPE_COLLIDER_PLANE: - dir = normalize(entity.GetDirection()); - dist = plane_point_distance(entity.position, dir, closest_point); - if (dist < 0) - { - dir *= -1; - dist = abs(dist); - } - dist = dist - len; - if (dist < 0) - { - float4x4 planeProjection = load_entitymatrix(entity.GetMatrixIndex()); - const float3 clipSpacePos = mul(planeProjection, float4(closest_point, 1)).xyz; - const float3 uvw = clipspace_to_uv(clipSpacePos.xyz); - [branch] - if (is_saturated(uvw)) + case ENTITY_TYPE_FORCEFIELD_POINT: + tail_next += dt * dir * entity.GetGravity() * (1 - saturate(dist / range)); + to_tail = normalize(tail_next - base); + tail_next = base + to_tail * boneLength; + break; + case ENTITY_TYPE_FORCEFIELD_PLANE: + tail_next += dt * entity.GetDirection() * entity.GetGravity() * (1 - saturate(dist / range)); + to_tail = normalize(tail_next - base); + tail_next = base + to_tail * boneLength; + break; + case ENTITY_TYPE_COLLIDER_SPHERE: + dist = dist - range - len; + if (dist < 0) { - tip = tip + dir * dist; + tail_next += dir * dist; + to_tail = normalize(tail_next - base); + tail_next = base + to_tail * boneLength; } - } - break; - default: - break; + break; + case ENTITY_TYPE_COLLIDER_PLANE: + dir = normalize(entity.GetDirection()); + dist = plane_point_distance(entity.position, dir, closest_point); + if (dist < 0) + { + dir *= -1; + dist = abs(dist); + } + dist = dist - len; + if (dist < 0) + { + float4x4 planeProjection = load_entitymatrix(entity.GetMatrixIndex()); + const float3 clipSpacePos = mul(planeProjection, float4(closest_point, 1)).xyz; + const float3 uvw = clipspace_to_uv(clipSpacePos.xyz); + [branch] + if (is_saturated(uvw)) + { + tail_next -= dir * dist; + to_tail = normalize(tail_next - base); + tail_next = base + to_tail * boneLength; + } + } + break; + default: + break; } } } } - // Pull back to rest position: - force += (target - normal) * xHairStiffness; - - force *= delta_time; - - // Simulation buffer load: - half3 velocity = f16tof32(simulationBuffer[particleID].normal_velocity >> 16u); - - // Apply surface-movement-based velocity: - const float3 old_base = simulationBuffer[particleID].position; - const float3 old_normal = f16tof32(simulationBuffer[particleID].normal_velocity); - const float3 old_tip = old_base + old_normal * len; - const float3 surface_velocity = old_tip - tip; - velocity += surface_velocity; - - // Apply forces: - half3 newVelocity = velocity + force; - half3 newNormal = normal + newVelocity * delta_time; - newNormal = normalize(newNormal); - if (dot(target, newNormal) > 0.5) // clamp the offset + // Don't allow tail to go below the axis plane: + float below_plane = plane_point_distance(base, boneAxis, tail_next); + if (below_plane < 0) { - normal = newNormal; - velocity = newVelocity; + tail_next -= boneAxis * below_plane; } - // Drag: - velocity *= 0.98; - - // Store simulation data: - simulationBuffer[particleID].position = base; - simulationBuffer[particleID].normal_velocity = f32tof16(normal) | (f32tof16(velocity) << 16u); + // Store simulation: + simulationBuffer[particleID].prevTail = tail_current; + simulationBuffer[particleID].currentTail = tail_next; + //draw_point(tail_next, 0.1, float4(0,1,0,1)); + //draw_line(base, tail_next, float4(1,0,0,1)); + + half3 normal = to_tail; + // Write out render buffers: // These must be persistent, not culled (raytracing, surfels...) - uint v0 = particleID * 4; - uint i0 = particleID * 6; - - half3x3 TBN = half3x3(tangent, normalize(normal + bend), binormal); // don't derive binormal, because we want the shear! - float3 rootposition = base - normal * 0.1 * len; // inset to the emitter a bit, to avoid disconnect: - const float2 frame = float2(atlas_rect.aspect * xHairAspect, 1) * len * 0.5; + half3 normal_bend = normalize(normal + bend); + binormal = cross(normal_bend, tangent); + tangent = cross(binormal, normal_bend); + TBN = half3x3(tangent, normal_bend, binormal); - for (uint vertexID = 0; vertexID < 4; ++vertexID) + //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) { - // expand the particle into a billboard cross section, the patch: float3 patchPos = HAIRPATCH[vertexID]; - float2 uv = vertexID < 6 ? patchPos.xy : patchPos.zy; + 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); - + // 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); - - // simplistic wind effect only affects the top, but leaves the base as is: - const float3 wind = sample_wind(rootposition, segmentID + patchPos.y); - - float3 position = rootposition + patchPos + wind; - + + 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 + vertexID] = float4(position, 0); - vertexBuffer_NOR[v0 + vertexID] = half4(normalize(normal + wind), 0); - vertexBuffer_UVS[v0 + vertexID] = 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 + vertexID] = float4(position, 0); + vertexBuffer_POS_RT[v0] = float4(position, 0); + + v0++; } // Frustum culling: ShaderSphere sphere; - sphere.center = (base + tip) * 0.5; + sphere.center = (base + tail_next) * 0.5; sphere.radius = len; - const bool visible = !distance_culled && GetCamera().frustum.intersects(sphere); + bool visible = !distance_culled && GetCamera().frustum.intersects(sphere); + visible = true; // Optimization: reduce to 1 atomic operation per wave const uint waveAppendCount = WaveActiveCountBits(visible); @@ -310,6 +350,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn if (visible) { uint prevCount = waveOffset + WavePrefixSum(6u); + uint i0 = particleID * 6; uint ii0 = prevCount; culledIndexBuffer[ii0 + 0] = i0 + 0; culledIndexBuffer[ii0 + 1] = i0 + 1; @@ -320,6 +361,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 Gid : SV_GroupID, uint groupIn } // Offset next segment root to current tip: - base = tip; + base = tail_next; + target = normal; } } diff --git a/WickedEngine/wiEnums.h b/WickedEngine/wiEnums.h index 48e306282..aef11b060 100644 --- a/WickedEngine/wiEnums.h +++ b/WickedEngine/wiEnums.h @@ -82,6 +82,8 @@ namespace wi::enums enum BUFFERTYPES { BUFFERTYPE_FRAMECB, + BUFFERTYPE_INDIRECT_DEBUG_0, + BUFFERTYPE_INDIRECT_DEBUG_1, BUFFERTYPE_COUNT }; diff --git a/WickedEngine/wiHairParticle.cpp b/WickedEngine/wiHairParticle.cpp index eb15d5da6..5fca3bb5b 100644 --- a/WickedEngine/wiHairParticle.cpp +++ b/WickedEngine/wiHairParticle.cpp @@ -109,8 +109,12 @@ namespace wi bd.misc_flags |= ResourceMiscFlag::RAY_TRACING; } + const uint32_t gfx_vertexcount_per_strand = segmentCount * 2 + 2; + const uint32_t gfx_vertexcount = strandCount * gfx_vertexcount_per_strand; + const uint32_t gfx_indexcount = 6 * particleCount; + const size_t position_stride = GetFormatStride(position_format); - const Format ib_format = GetIndexBufferFormatRaw(particleCount * 4); + const Format ib_format = GetIndexBufferFormatRaw(gfx_vertexcount); const uint64_t alignment = device->GetMinOffsetAlignment(&bd) * sizeof(IndirectDrawArgsIndexedInstanced) * // additional alignment @@ -118,14 +122,14 @@ namespace wi ; simulation_view.size = sizeof(PatchSimulationData) * particleCount; - vb_pos[0].size = position_stride * 4 * particleCount; - vb_pos[1].size = position_stride * 4 * particleCount; - vb_nor.size = sizeof(MeshComponent::Vertex_NOR) * 4 * particleCount; - vb_uvs.size = sizeof(MeshComponent::Vertex_UVS) * 4 * particleCount; - wetmap.size = sizeof(uint16_t) * 4 * particleCount; - ib_culled.size = GetFormatStride(ib_format) * 6 * particleCount; + vb_pos[0].size = position_stride * gfx_vertexcount; + vb_pos[1].size = position_stride * gfx_vertexcount; + vb_nor.size = sizeof(MeshComponent::Vertex_NOR) * gfx_vertexcount; + vb_uvs.size = sizeof(MeshComponent::Vertex_UVS) * gfx_vertexcount; + wetmap.size = sizeof(uint16_t) * gfx_vertexcount; + ib_culled.size = GetFormatStride(ib_format) * gfx_indexcount; indirect_view.size = sizeof(IndirectDrawArgsIndexedInstanced); - vb_pos_raytracing.size = position_stride * 4 * particleCount; + vb_pos_raytracing.size = position_stride * gfx_vertexcount; bd.size = AlignTo(indirect_view.size, alignment) + @@ -217,7 +221,40 @@ namespace wi vb_pos_raytracing.descriptor_uav = device->GetDescriptorIndex(&generalBuffer, SubresourceType::UAV, vb_pos_raytracing.subresource_uav); buffer_offset += vb_pos_raytracing.size; - primitiveBuffer = wi::renderer::GetIndexBufferForQuads(particleCount); + + bd = {}; + bd.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::INDEX_BUFFER; + if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING)) + { + bd.misc_flags |= ResourceMiscFlag::RAY_TRACING; + } + bd.format = Format::R32_UINT; + bd.stride = GetFormatStride(bd.format); + bd.size = bd.stride * gfx_indexcount; + auto fill_ib = [&](void* dst) + { + uint32_t* primitiveData = (uint32_t*)dst; + uint32_t i = 0; + uint32_t v0 = 0; + for (uint32_t strandID = 0; strandID < strandCount; ++strandID) + { + for (uint32_t segmentID = 0; segmentID < segmentCount; ++segmentID) + { + primitiveData[i++] = v0 + 0; + primitiveData[i++] = v0 + 1; + primitiveData[i++] = v0 + 2; + primitiveData[i++] = v0 + 2; + primitiveData[i++] = v0 + 1; + primitiveData[i++] = v0 + 3; + v0 += 2; + } + v0 += 2; + } + assert(v0 == gfx_vertexcount); + assert(i == gfx_indexcount); + }; + device->CreateBuffer2(&bd, fill_ib, &primitiveBuffer); + device->SetName(&primitiveBuffer, "HairParticleSystem::primitiveBuffer"); } @@ -376,6 +413,7 @@ namespace wi hcb.xHairAspect = hair.width * (float)std::max(1u, desc.width) / (float)std::max(1u, desc.height); hcb.xHairLength = hair.length; hcb.xHairStiffness = hair.stiffness; + hcb.xHairDrag = hair.drag; hcb.xHairRandomness = hair.randomness; hcb.xHairStrandCount = hair.strandCount; hcb.xHairSegmentCount = std::max(hair.segmentCount, 1u); @@ -590,6 +628,16 @@ namespace wi archive >> atlas_rects[i].size; } } + + if (seri.GetVersion() >= 2) + { + archive >> drag; + } + else + { + // Old stiffness remap to new: + stiffness *= 0.05f; + } } else { @@ -618,6 +666,11 @@ namespace wi archive << atlas_rects[i].size; } } + + if (seri.GetVersion() >= 2) + { + archive << drag; + } } } diff --git a/WickedEngine/wiHairParticle.h b/WickedEngine/wiHairParticle.h index 1e340283b..6f6db2ebc 100644 --- a/WickedEngine/wiHairParticle.h +++ b/WickedEngine/wiHairParticle.h @@ -83,7 +83,8 @@ namespace wi uint32_t segmentCount = 1; uint32_t randomSeed = 1; float length = 1.0f; - float stiffness = 10.0f; + float stiffness = 0.5f; + float drag = 0.1f; float randomness = 0.2f; float viewDistance = 200; wi::vector vertex_lengths; diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index 46c5500b0..fa0ca5a83 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -152,6 +152,9 @@ Texture shadowMapAtlas_Transparent; int max_shadow_resolution_2D = 1024; int max_shadow_resolution_cube = 256; +GPUBuffer indirectDebugStatsReadback[GraphicsDevice::GetBufferCount()]; +bool indirectDebugStatsReadback_available[GraphicsDevice::GetBufferCount()] = {}; + wi::vector> renderableBoxes; wi::vector> renderableBoxes_depth; wi::vector> renderableSpheres; @@ -196,16 +199,18 @@ struct RenderBatch uint32_t meshIndex; uint32_t instanceIndex; uint16_t distance; - uint16_t camera_mask; + uint8_t camera_mask; + uint8_t lod_override; // if overriding the base object LOD is needed, specify less than 0xFF in this uint32_t sort_bits; // an additional bitmask for sorting only, it should be used to reduce pipeline changes - inline void Create(uint32_t meshIndex, uint32_t instanceIndex, float distance, uint32_t sort_bits, uint16_t camera_mask = 0xFFFF) + inline void Create(uint32_t meshIndex, uint32_t instanceIndex, float distance, uint32_t sort_bits, uint8_t camera_mask = 0xFF, uint8_t lod_override = 0xFF) { this->meshIndex = meshIndex; this->instanceIndex = instanceIndex; this->distance = XMConvertFloatToHalf(distance); this->sort_bits = sort_bits; this->camera_mask = camera_mask; + this->lod_override = lod_override; } inline float GetDistance() const @@ -287,9 +292,9 @@ struct RenderQueue { batches.clear(); } - inline void add(uint32_t meshIndex, uint32_t instanceIndex, float distance, uint32_t sort_bits, uint16_t camera_mask = 0xFFFF) + inline void add(uint32_t meshIndex, uint32_t instanceIndex, float distance, uint32_t sort_bits, uint8_t camera_mask = 0xFF, uint8_t lod_override = 0xFF) { - batches.emplace_back().Create(meshIndex, instanceIndex, distance, sort_bits, camera_mask); + batches.emplace_back().Create(meshIndex, instanceIndex, distance, sort_bits, camera_mask, lod_override); } inline void sort_transparent() { @@ -2067,6 +2072,24 @@ void LoadBuffers() device->CreateBuffer(&bd, nullptr, &buffers[BUFFERTYPE_FRAMECB]); device->SetName(&buffers[BUFFERTYPE_FRAMECB], "buffers[BUFFERTYPE_FRAMECB]"); + bd.size = sizeof(IndirectDrawArgsInstanced) + (sizeof(XMFLOAT4) + sizeof(XMFLOAT4)) * 1000; + bd.bind_flags = BindFlag::VERTEX_BUFFER | BindFlag::UNORDERED_ACCESS; + bd.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::INDIRECT_ARGS; + device->CreateBuffer(&bd, nullptr, &buffers[BUFFERTYPE_INDIRECT_DEBUG_0]); + device->SetName(&buffers[BUFFERTYPE_INDIRECT_DEBUG_0], "buffers[BUFFERTYPE_INDIRECT_DEBUG_0]"); + device->CreateBuffer(&bd, nullptr, &buffers[BUFFERTYPE_INDIRECT_DEBUG_1]); + device->SetName(&buffers[BUFFERTYPE_INDIRECT_DEBUG_1], "buffers[BUFFERTYPE_INDIRECT_DEBUG_1]"); + + bd.size = sizeof(IndirectDrawArgsInstanced); + bd.usage = Usage::READBACK; + bd.bind_flags = {}; + bd.misc_flags = {}; + for (auto& buf : indirectDebugStatsReadback) + { + device->CreateBuffer(&bd, nullptr, &buf); + device->SetName(&buf, "indirectDebugStatsReadback"); + } + { TextureDesc desc; desc.bind_flags = BindFlag::SHADER_RESOURCE; @@ -2897,8 +2920,8 @@ void RenderMeshes( uint32_t dataOffset = 0; uint8_t userStencilRefOverride = 0; bool forceAlphatestForDithering = false; + uint8_t lod = 0; AABB aabb; - uint32_t lod = 0; } instancedBatch = {}; uint32_t prev_stencilref = STENCILREF_DEFAULT; @@ -3095,11 +3118,12 @@ void RenderMeshes( const ObjectComponent& instance = vis.scene->objects[instanceIndex]; const AABB& instanceAABB = vis.scene->aabb_objects[instanceIndex]; const uint8_t userStencilRefOverride = instance.userStencilRef; + const uint8_t lod = batch.lod_override == 0xFF ? (uint8_t)instance.lod : batch.lod_override; // When we encounter a new mesh inside the global instance array, we begin a new RenderBatch: if (meshIndex != instancedBatch.meshIndex || userStencilRefOverride != instancedBatch.userStencilRefOverride || - instance.lod != instancedBatch.lod + lod != instancedBatch.lod ) { batch_flush(); @@ -3111,7 +3135,7 @@ void RenderMeshes( instancedBatch.userStencilRefOverride = userStencilRefOverride; instancedBatch.forceAlphatestForDithering = 0; instancedBatch.aabb = AABB(); - instancedBatch.lod = instance.lod; + instancedBatch.lod = lod; } const float dither = std::max(instance.GetTransparency(), std::max(0.0f, batch.GetDistance() - instance.fadeDistance) / instance.radius); @@ -3963,6 +3987,28 @@ void UpdatePerFrameData( frameCB.texture_wind_prev_index = device->GetDescriptorIndex(&textures[TEXTYPE_3D_WIND_PREV], SubresourceType::SRV); frameCB.texture_caustics_index = device->GetDescriptorIndex(&textures[TEXTYPE_2D_CAUSTICS], SubresourceType::SRV); + // See if indirect debug buffer needs to be resized: + if (indirectDebugStatsReadback_available[device->GetBufferIndex()] && indirectDebugStatsReadback[device->GetBufferIndex()].mapped_data != nullptr) + { + const IndirectDrawArgsInstanced* indirectDebugStats = (const IndirectDrawArgsInstanced*)indirectDebugStatsReadback[device->GetBufferIndex()].mapped_data; + const uint64_t required_debug_buffer_size = sizeof(IndirectDrawArgsInstanced) + (sizeof(XMFLOAT4) + sizeof(XMFLOAT4)) * clamp(indirectDebugStats->VertexCountPerInstance, 0u, 4000000u); + if (buffers[BUFFERTYPE_INDIRECT_DEBUG_0].desc.size < required_debug_buffer_size) + { + GPUBufferDesc bd; + bd.size = required_debug_buffer_size; + bd.bind_flags = BindFlag::VERTEX_BUFFER | BindFlag::UNORDERED_ACCESS; + bd.misc_flags = ResourceMiscFlag::BUFFER_RAW | ResourceMiscFlag::INDIRECT_ARGS; + device->CreateBuffer(&bd, nullptr, &buffers[BUFFERTYPE_INDIRECT_DEBUG_0]); + device->SetName(&buffers[BUFFERTYPE_INDIRECT_DEBUG_0], "buffers[BUFFERTYPE_INDIRECT_DEBUG_0]"); + device->CreateBuffer(&bd, nullptr, &buffers[BUFFERTYPE_INDIRECT_DEBUG_1]); + device->SetName(&buffers[BUFFERTYPE_INDIRECT_DEBUG_1], "buffers[BUFFERTYPE_INDIRECT_DEBUG_1]"); + } + } + + // Indirect debug buffers: 0 is always WRITE, 1 is always READ + std::swap(buffers[BUFFERTYPE_INDIRECT_DEBUG_0], buffers[BUFFERTYPE_INDIRECT_DEBUG_1]); + frameCB.indirect_debugbufferindex = device->GetDescriptorIndex(&buffers[BUFFERTYPE_INDIRECT_DEBUG_0], SubresourceType::UAV); + // Note: shadow maps always assumed to be valid to avoid shader branching logic const Texture& shadowMap = shadowMapAtlas.IsValid() ? shadowMapAtlas : *wi::texturehelper::getBlack(); const Texture& shadowMapTransparent = shadowMapAtlas_Transparent.IsValid() ? shadowMapAtlas_Transparent : *wi::texturehelper::getWhite(); @@ -4539,6 +4585,10 @@ void UpdateRenderData( auto prof_updatebuffer_cpu = wi::profiler::BeginRangeCPU("Update Buffers (CPU)"); auto prof_updatebuffer_gpu = wi::profiler::BeginRangeGPU("Update Buffers (GPU)", cmd); + device->CopyBuffer(&indirectDebugStatsReadback[device->GetBufferIndex()], 0, &buffers[BUFFERTYPE_INDIRECT_DEBUG_0], 0, sizeof(IndirectDrawArgsInstanced), cmd); + indirectDebugStatsReadback_available[device->GetBufferIndex()] = true; + barrier_stack.push_back(GPUBarrier::Buffer(&buffers[BUFFERTYPE_INDIRECT_DEBUG_0], ResourceState::COPY_SRC, ResourceState::COPY_DST)); + barrier_stack.push_back(GPUBarrier::Buffer(&vis.scene->meshletBuffer, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS)); barrier_stack.push_back(GPUBarrier::Buffer(&buffers[BUFFERTYPE_FRAMECB], ResourceState::CONSTANT_BUFFER, ResourceState::COPY_DST)); @@ -4626,6 +4676,16 @@ void UpdateRenderData( barrier_stack.push_back(GPUBarrier::Image(&textures[TEXTYPE_3D_WIND_PREV], textures[TEXTYPE_3D_WIND_PREV].desc.layout, ResourceState::UNORDERED_ACCESS)); barrier_stack.push_back(GPUBarrier::Image(&textures[TEXTYPE_2D_CAUSTICS], textures[TEXTYPE_2D_CAUSTICS].desc.layout, ResourceState::UNORDERED_ACCESS)); + // Indirect debug buffer - clear indirect args: + IndirectDrawArgsInstanced debug_indirect = {}; + debug_indirect.VertexCountPerInstance = 0; + debug_indirect.InstanceCount = 1; + debug_indirect.StartVertexLocation = 0; + debug_indirect.StartInstanceLocation = 0; + device->UpdateBuffer(&buffers[BUFFERTYPE_INDIRECT_DEBUG_0], &debug_indirect, cmd, sizeof(debug_indirect)); + barrier_stack.push_back(GPUBarrier::Buffer(&buffers[BUFFERTYPE_INDIRECT_DEBUG_0], ResourceState::COPY_DST, ResourceState::UNORDERED_ACCESS)); + barrier_stack.push_back(GPUBarrier::Buffer(&buffers[BUFFERTYPE_INDIRECT_DEBUG_1], ResourceState::UNORDERED_ACCESS, ResourceState::VERTEX_BUFFER | ResourceState::INDIRECT_ARGUMENT | ResourceState::COPY_SRC)); + // Flush buffer updates: barrier_stack_flush(cmd); @@ -6114,18 +6174,21 @@ void DrawShadowmaps( if (object.IsRenderable() && object.IsCastingShadow()) { // Determine which cascades the object is contained in: - uint16_t camera_mask = 0; + uint8_t camera_mask = 0; + uint8_t shadow_lod = 0xFF; for (uint32_t cascade = 0; cascade < cascade_count; ++cascade) { if ((cascade < (cascade_count - object.cascadeMask)) && shcams[cascade].frustum.CheckBoxFast(aabb)) { camera_mask |= 1 << cascade; + uint8_t candidate_lod = (uint8_t)vis.scene->ComputeObjectLODForView(object, aabb, vis.scene->meshes[object.mesh_index], shcams[cascade].view_projection); + shadow_lod = std::min(shadow_lod, candidate_lod); } } if (camera_mask == 0) continue; - renderQueue.add(object.mesh_index, uint32_t(i), 0, object.sort_bits, camera_mask); + renderQueue.add(object.mesh_index, uint32_t(i), 0, object.sort_bits, camera_mask, shadow_lod); const uint32_t filterMask = object.GetFilterMask(); if (filterMask & FILTER_TRANSPARENT || filterMask & FILTER_WATER) @@ -6231,7 +6294,11 @@ void DrawShadowmaps( const ObjectComponent& object = vis.scene->objects[i]; if (object.IsRenderable() && object.IsCastingShadow()) { - renderQueue.add(object.mesh_index, uint32_t(i), 0, object.sort_bits); + uint8_t shadow_lod = 0xFF; + uint8_t candidate_lod = (uint8_t)vis.scene->ComputeObjectLODForView(object, aabb, vis.scene->meshes[object.mesh_index], shcam.view_projection); + shadow_lod = std::min(shadow_lod, candidate_lod); + + renderQueue.add(object.mesh_index, uint32_t(i), 0, object.sort_bits, 0xFF, shadow_lod); const uint32_t filterMask = object.GetFilterMask(); if (filterMask & FILTER_TRANSPARENT || filterMask & FILTER_WATER) @@ -6380,18 +6447,21 @@ void DrawShadowmaps( if (object.IsRenderable() && object.IsCastingShadow()) { // Check for each frustum, if object is visible from it: - uint16_t camera_mask = 0; + uint8_t camera_mask = 0; + uint8_t shadow_lod = 0xFF; for (uint32_t camera_index = 0; camera_index < camera_count; ++camera_index) { if (frusta[camera_index].CheckBoxFast(aabb)) { camera_mask |= 1 << camera_index; + uint8_t candidate_lod = (uint8_t)vis.scene->ComputeObjectLODForView(object, aabb, vis.scene->meshes[object.mesh_index], cameras[camera_index].view_projection); + shadow_lod = std::min(shadow_lod, candidate_lod); } } if (camera_mask == 0) continue; - renderQueue.add(object.mesh_index, uint32_t(i), 0, object.sort_bits, camera_mask); + renderQueue.add(object.mesh_index, uint32_t(i), 0, object.sort_bits, camera_mask, shadow_lod); const uint32_t filterMask = object.GetFilterMask(); if (filterMask & FILTER_TRANSPARENT || filterMask & FILTER_WATER) @@ -6487,7 +6557,7 @@ void DrawShadowmaps( const ObjectComponent& object = vis.scene->objects[i]; if (object.IsRenderable()) { - uint16_t camera_mask = 0; + uint8_t camera_mask = 0; if (shcam.frustum.CheckBoxFast(aabb)) { camera_mask |= 1 << 0; @@ -7187,6 +7257,33 @@ void DrawDebugWorld( device->EventEnd(cmd); } + // GPU-generated indirect debug lines: + { + device->EventBegin("Indirect Debug Lines - 3D", cmd); + + device->BindPipelineState(&PSO_debug[DEBUGRENDERING_LINES], cmd); + + MiscCB sb; + XMStoreFloat4x4(&sb.g_xTransform, camera.GetViewProjection()); + sb.g_xColor = XMFLOAT4(1, 1, 1, 1); + device->BindDynamicConstantBuffer(sb, CB_GETBINDSLOT(MiscCB), cmd); + + const GPUBuffer* vbs[] = { + &buffers[BUFFERTYPE_INDIRECT_DEBUG_1], + }; + const uint32_t strides[] = { + sizeof(XMFLOAT4) + sizeof(XMFLOAT4), + }; + const uint64_t offsets[] = { + sizeof(IndirectDrawArgsInstanced), + }; + device->BindVertexBuffers(vbs, 0, arraysize(vbs), strides, offsets, cmd); + + device->DrawInstancedIndirect(&buffers[BUFFERTYPE_INDIRECT_DEBUG_1], 0, cmd); + + device->EventEnd(cmd); + } + if (!renderableLines2D.empty()) { device->EventBegin("Debug Lines - 2D", cmd); @@ -8565,7 +8662,7 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd) const ObjectComponent& object = vis.scene->objects[i]; if (object.IsRenderable() && !object.IsNotVisibleInReflections()) { - uint16_t camera_mask = 0; + uint8_t camera_mask = 0; for (uint32_t camera_index = 0; camera_index < arraysize(cameras); ++camera_index) { if (cameras[camera_index].frustum.CheckBoxFast(aabb)) diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 23786ebbd..115bc138f 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -4405,14 +4405,7 @@ namespace wi::scene // LOD select: if (mesh.subsets_per_lod > 0) { - // LOD selection based on screen projection: - XMFLOAT4 rect = aabb.ProjectToScreen(camera.GetViewProjection()); - float width = rect.z - rect.x; - float height = rect.w - rect.y; - float maxdim = std::max(width, height); - float lod_max = float(mesh.GetLODCount() - 1); - float lod = clamp(std::log2(1.0f / maxdim) + object.lod_bias, 0.0f, lod_max); - object.lod = uint32_t(lod); + object.lod = ComputeObjectLODForView(object, aabb, mesh, camera.GetViewProjection()); } union SortBits @@ -7172,6 +7165,17 @@ namespace wi::scene return ocean.GetDisplacedPosition(worldPosition); } + uint32_t Scene::ComputeObjectLODForView(const ObjectComponent& object, const AABB& aabb, const MeshComponent& mesh, const XMMATRIX& ViewProjection) const + { + XMFLOAT4 rect = aabb.ProjectToScreen(ViewProjection); + float width = rect.z - rect.x; + float height = rect.w - rect.y; + float maxdim = std::max(width, height); + float lod_max = float(mesh.GetLODCount() - 1); + float lod = clamp(std::log2(1.0f / maxdim) + object.lod_bias, 0.0f, lod_max); + return uint32_t(lod); + } + bool Scene::IsWetmapProcessingRequired() const { return wetmap_fadeout_time > 0; diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index dea3086fa..3a6932f8b 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -47,7 +47,7 @@ namespace wi::scene wi::ecs::ComponentManager& animations = componentLibrary.Register("wi::scene::Scene::animations", 2); // version = 2 wi::ecs::ComponentManager& animation_datas = componentLibrary.Register("wi::scene::Scene::animation_datas"); wi::ecs::ComponentManager& emitters = componentLibrary.Register("wi::scene::Scene::emitters", 2); // version = 2 - wi::ecs::ComponentManager& hairs = componentLibrary.Register("wi::scene::Scene::hairs", 1); // version = 1 + wi::ecs::ComponentManager& hairs = componentLibrary.Register("wi::scene::Scene::hairs", 2); // version = 2 wi::ecs::ComponentManager& weathers = componentLibrary.Register("wi::scene::Scene::weathers", 6); // version = 6 wi::ecs::ComponentManager& sounds = componentLibrary.Register("wi::scene::Scene::sounds", 1); // version = 1 wi::ecs::ComponentManager& videos = componentLibrary.Register("wi::scene::Scene::videos"); @@ -574,6 +574,9 @@ namespace wi::scene // Note that the input position to this function will be taken on the XZ plane and modified by the displacement map's XZ value, and the Y (vertical) position will be taken from the ocean water height and displacement map only. XMFLOAT3 GetOceanPosAt(const XMFLOAT3& worldPosition) const; + // Computes the LOD for an object AABB for a given view projection matrix + uint32_t ComputeObjectLODForView(const ObjectComponent& object, const wi::primitive::AABB& aabb, const MeshComponent& mesh, const XMMATRIX& ViewProjection) const; + private: void UpdateHumanoidFacings(); diff --git a/WickedEngine/wiTerrain.cpp b/WickedEngine/wiTerrain.cpp index b5fd57c6c..587402349 100644 --- a/WickedEngine/wiTerrain.cpp +++ b/WickedEngine/wiTerrain.cpp @@ -377,7 +377,6 @@ namespace wi::terrain request.y = 0; } } - locker.unlock(); } Terrain::Terrain() diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 89d64d71b..66f5822f5 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 679; + const int revision = 680; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);