You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
8.5 KiB
C#

using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace FluffyGroomingTool {
public class VerletSimulation {
internal readonly ComputeShader compute;
internal ComputeBuffer verletNodesBuffer, verletRestPositionsBuffer;
private readonly int nodesCount;
private readonly FurRendererSettings furRendererSettings;
internal readonly int verletKernel;
public VerletSimulation(int nodesCount, FurRendererSettings furRendererSettings) {
this.nodesCount = nodesCount / 2;
this.furRendererSettings = furRendererSettings;
compute = Object.Instantiate(Resources.Load<ComputeShader>("VerletSimulation"));
verletKernel = compute.FindKernel("StepAndSolve");
}
public void update(int strandsCount, int strandNodesCount, int layerNodeStartIndex, Vector3 worldSpaceCameraPos,
VerletSimulationSettings simulationSettings, float normalPercent) {
if (simulationSettings?.enableMovement == true && Application.isPlaying && verletNodesBuffer != null) {
stepRelaxAndCollision(strandsCount, strandNodesCount, layerNodeStartIndex, worldSpaceCameraPos, simulationSettings, normalPercent);
}
}
private void initBuffers() {
if (verletNodesBuffer == null) {
verletNodesBuffer = new ComputeBuffer(nodesCount, sizeof(float) * 15 + sizeof(int));
verletRestPositionsBuffer = new ComputeBuffer(nodesCount, sizeof(float) * 8);
}
}
private void stepRelaxAndCollision(int strandsCount, int strandNodesCount, int layerNodeStartIndex, Vector3 worldSpaceCameraPos,
VerletSimulationSettings simulationSettings, float normalPercent) {
compute.SetBuffer(verletKernel, "verletNodes", verletNodesBuffer);
compute.SetBuffer(verletKernel, "verletNodesShape", verletNodesBuffer);
compute.SetBuffer(verletKernel, "_RestPositions", verletRestPositionsBuffer);
compute.SetFloat(ShaderID.SOURCE_MESH_NORMAL_TO_STRAND_NORMAL_PERCENT, normalPercent);
compute.SetInt("verletNodesCount", nodesCount);
compute.SetVector("worldSpaceCameraPos", worldSpaceCameraPos);
compute.SetInt("layerVertexStartIndex", layerNodeStartIndex);
compute.SetFloat("shapeConstraintRoot", simulationSettings.stiffnessRoot);
compute.SetFloat("shapeConstraintTip", simulationSettings.stiffnessTip);
compute.SetInt("numberOfFixedNodesInStrand", simulationSettings.isFirstNodeFixed ? 1 : 0);
compute.SetVector("_Gravity", simulationSettings.gravity);
compute.SetFloat("deltaTime", furRendererSettings.runPhysicsInFixedUpdate ? Time.fixedDeltaTime : Time.smoothDeltaTime);
compute.SetFloat("_Decay", 1f - simulationSettings.drag);
compute.SetFloat("stepSize", 1f / simulationSettings.constraintIterations);
compute.SetInt("solverIterations", simulationSettings.constraintIterations);
var nearestPow = CullAndSortController.nextPowerOf2(strandNodesCount);
var dispatchCount = nearestPow * strandsCount;
compute.SetInt("nearestPow", nearestPow);
compute.SetInt("strandPointsCount", strandNodesCount);
compute.setKeywordEnabled("COLLIDE_WITH_SOURCE_MESH", simulationSettings.isVerletColliderEnabled());
compute.setKeywordEnabled("USE_FORWARD_COLLISION", simulationSettings.useForwardCollision);
compute.Dispatch(verletKernel, dispatchCount.toCsGroups(), 1, 1);
}
public void dispose() {
verletNodesBuffer?.Dispose();
verletRestPositionsBuffer?.Dispose();
}
public void setupVerlet(ComputeShader furSetupComputeShader, int kernel, VerletSimulationSettings simulationSettings) {
if (simulationSettings?.enableMovement == true && Application.isPlaying) {
initBuffers();
furSetupComputeShader.SetBuffer(kernel, "_RestPositions", verletRestPositionsBuffer);
furSetupComputeShader.SetBuffer(kernel, "verletNodes", verletNodesBuffer);
furSetupComputeShader.EnableKeyword("VERLET_ENABLED");
furSetupComputeShader.setKeywordEnabled("INIT_VERLET", framesCount < 10);
compute.setKeywordEnabled("INIT_VERLET", true);
framesCount++;
} else {
furSetupComputeShader.DisableKeyword("VERLET_ENABLED");
furSetupComputeShader.DisableKeyword("INIT_VERLET");
compute.DisableKeyword("INIT_VERLET");
}
}
private int framesCount;
public void setupWind(FurRenderer fr) { setupWind(compute, verletKernel, fr.furRendererSettings); }
public static void setupWind(ComputeShader computeShader, int kernel, FurRendererSettings furRendererSettings) {
computeShader.SetFloat(ShaderID.WIND_GUST, furRendererSettings.windProperties.gustFrequency);
computeShader.SetFloat(ShaderID.TIME, Time.time);
computeShader.SetFloat(ShaderID.WIND_STRENGTH, furRendererSettings.windProperties.windStrength);
var windForwardDirection = Quaternion.Euler(0f, furRendererSettings.windProperties.windDirectionDegree, 0f) * Vector3.forward;
computeShader.SetVector(ShaderID.WIND_DIRECTION, windForwardDirection);
computeShader.SetTexture(kernel, ShaderID.WIND_DISTORTION_MAP, furRendererSettings.windProperties.getWindTexture());
}
public void setFurMeshBuffer(GraphicsBuffer furMeshBuffer, int furMeshBufferStride) {
compute.SetInt(ShaderID.FUR_MESH_BUFFER_STRIDE, furMeshBufferStride);
compute.SetBuffer(verletKernel, ShaderID.FUR_MESH_BUFFER, furMeshBuffer);
}
public int getKernel() { return verletKernel; }
}
}
[Serializable]
public class VerletSimulationSettings : ICloneable {
public bool enableMovement = true;
[Range(4, 32),
Tooltip(
"How many times the Verlet physics solver should loop. Higher value gives more precise physics with less stretching but comes at the cost of performance.")]
public int constraintIterations = 18;
[Tooltip("Gravity direction.")] public Vector3 gravity = Vector3.down * 0.3f;
[Tooltip("Low value makes it very bouncy. High values will make it look like its under water.")] [Range(0f, 1f)]
public float drag = 0.5f;
[Tooltip("Hair shape constraint at the root of the strand.")] [Range(0f, 200f)]
public float stiffnessRoot = 30f;
[Tooltip("Hair shape constraint at the tip of the strand.")] [Range(0f, 200f)]
public float stiffnessTip = 5f;
[Tooltip(
"This will create a realtime Signed Distance Field(SDF) collider of the source mesh, that the hairs will collide with. Comes with some performance cost.")]
public bool collideWithSourceMesh;
[Tooltip("How far out the hair particles will be pushed from the collider.")] [Range(0f, 0.1f)]
public float colliderSkinWidth = 0.01f;
[Tooltip("How far out the hair particles will be pushed from the collider.")] [Range(4f, 90f)]
public int sdfColliderResolution = 15;
[HideInInspector, Range(1f, 1.4f)] public float gridAllocationMultiplier = 1f;
[HideInInspector, Range(0.7f, 1f)] public float extraPaddingInCells = 0.9f;
[Tooltip("More precise collision, but can sometimes be a bit slower.")]
public bool useForwardCollision = true;
public bool isFirstNodeFixed;
[Range(0.0f, 50f)] public float keepShapeStrength;
public bool isUnsupportedSDFPlatform;
public bool isVerletColliderEnabled() { return collideWithSourceMesh && enableMovement && !isUnsupportedSDFPlatform; }
public bool isSDFCollisionEnabled() { return enableMovement && !isUnsupportedSDFPlatform; }
public object Clone() {
return new VerletSimulationSettings {
enableMovement = enableMovement,
constraintIterations = constraintIterations,
gravity = gravity,
drag = drag,
stiffnessRoot = stiffnessRoot,
stiffnessTip = stiffnessTip,
collideWithSourceMesh = collideWithSourceMesh,
colliderSkinWidth = colliderSkinWidth,
sdfColliderResolution = sdfColliderResolution,
gridAllocationMultiplier = gridAllocationMultiplier,
useForwardCollision = useForwardCollision,
isFirstNodeFixed = isFirstNodeFixed,
isUnsupportedSDFPlatform = isUnsupportedSDFPlatform
};
}
}