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.

234 lines
12 KiB
C#

4 years ago
using System.Linq;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Rendering;
namespace FluffyGroomingTool {
[ExecuteAlways]
public class HairRenderer : MonoBehaviour {
public HairContainer hairContainer;
public Camera lodCamera;
public Material material;
public FurRendererSettings settings;
public SphereCollider[] sphereColliders;
public CapsuleCollider[] capsuleColliders;
public SDFCollider[] sdfColliders; //TODO
public bool motionVectors = true;
private ComputeBuffer hairStrandPointsBuffer, verletNodesBuffer, strandShapeBuffer;
private ComputeShader hairRendererCompute;
private int allInOneKernel;
private Material motionVectorMaterial;
private MeshBaker meshBaker;
private SDFColliderCommon sdfColliderCommon;
private ComputeBuffer colliderBuffer;
private ColliderStruct[] collidersStruct;
internal readonly FluffyRenderersController fluffyRenderersController = new FluffyRenderersController();
public bool isUrp;
public HeadersExpanded headerExpanded = new HeadersExpanded();
private void OnEnable() {
initialize();
if (Application.isPlaying && !ColliderHelper.collidersAssigned(sphereColliders, capsuleColliders)) {
ErrorLogger.logNoColliders();
}
RenderPipelineManager.beginFrameRendering += beginFrameRendering;
Camera.onPreRender += cameraPreRender;
DuplicateCleaner.checkDuplicates();
}
internal int hairContainerID;
private void checkForHairContainer() {
if (hairContainerID != hairContainer.id) {
var existingFurCreator = GetComponent<FurCreator>();
if (existingFurCreator != null) {
DestroyImmediate(existingFurCreator);
}
clearResources();
initialize();
}
}
private void loadDefaultMaterial() {
CurrentRenderer = GetComponent<Renderer>();
DefaultMaterialLoader.loadDefaultMaterial(
out _, out isUrp, ref material, out _, ref motionVectorMaterial, "Strands", CurrentRenderer
);
}
private void initialize() {
if (hairContainer != null) {
if (settings == null) {
settings = new FurRendererSettings {
verletSimulationSettings = new VerletSimulationSettings(),
sourceMeshNormalToStrandNormalPercent = 0.9f
};
}
hairContainerID = hairContainer.id;
if (hairContainer.isSkinned) {
meshBaker = new MeshBaker(gameObject, Instantiate(Resources.Load<ComputeShader>(ShaderID.MESH_BAKER_CS_NAME)));
sdfColliderCommon = new SDFColliderCommon(GetComponent<Renderer>(), meshBaker, settings.verletSimulationSettings);
}
loadDefaultMaterial();
var pointsCount = hairContainer.hairStrandPoints.Length;
var verticesCount = pointsCount * 2;
verletNodesBuffer = new ComputeBuffer(
hairContainer.hairStrandPoints.Length,
sizeof(float) * 15 + sizeof(int),
ComputeBufferType.Default
);
var triIndices =
FurMeshCreator.generateTriangleIndicesForStrands(hairContainer.pointsPerStrand, pointsCount / hairContainer.pointsPerStrand);
hairRendererCompute = Instantiate(Resources.Load<ComputeShader>("HairRenderer"));
allInOneKernel = hairRendererCompute.FindKernel("AllInOneKernel");
hairRendererCompute.SetInt("strandPointsCount", hairContainer.pointsPerStrand);
ColliderHelper.setupCollidersBuffer(ref colliderBuffer, ref collidersStruct, sphereColliders, capsuleColliders);
fluffyRenderersController.createRendererObject(DefaultMaterialLoader.isHdrp(), isUrp, motionVectorMaterial, verticesCount,
triIndices);
var furMeshBufferStride = fluffyRenderersController.getVertexBufferStride();
hairRendererCompute.SetInt(ShaderID.FUR_MESH_BUFFER_STRIDE, furMeshBufferStride);
hairRendererCompute.SetBuffer(allInOneKernel, ShaderID.FUR_MESH_BUFFER, fluffyRenderersController.hairMeshBuffer);
hairStrandPointsBuffer = new ComputeBuffer(hairContainer.hairStrandPoints.Length, Marshal.SizeOf<HairStrandPointStruct>());
hairStrandPointsBuffer.SetData(hairContainer.hairStrandPoints.ToList().Select(it => it.convertToStruct()).ToArray());
hairRendererCompute.SetBuffer(allInOneKernel, "hairStrandPoints", hairStrandPointsBuffer);
hairRendererCompute.EnableKeyword("INITIALIZE_VERLET_NODES");
strandShapeBuffer = hairContainer.createShapeBuffer(null);
}
}
private void updateAndRenderHair() {
if (hairContainer != null) {
fluffyRenderersController.setupRenderers(false, material, transform.position, CurrentRenderer, isUrp, motionVectors);
var thisTransform = transform;
if (hairContainer.isSkinned) {
if (meshBaker == null) {
initialize();
return;
}
meshBaker.bakeSkinnedMesh(false);
if (settings.verletSimulationSettings.isVerletColliderEnabled()) {
sdfColliderCommon.createSDF(thisTransform, hairRendererCompute, allInOneKernel);
}
hairRendererCompute.SetBuffer(allInOneKernel, ShaderID.SOURCE_MESH, meshBaker.bakedMesh);
}
var rotation = thisTransform.rotation;
hairRendererCompute.SetMatrix(ShaderID.LOCAL_TO_WORLD_MATRIX, thisTransform.localToWorldMatrix);
hairRendererCompute.SetMatrix(ShaderID.OBJECT_ROTATION_MATRIX, Matrix4x4.Rotate(rotation));
hairRendererCompute.SetBuffer(allInOneKernel, "strandShapeBuffer", strandShapeBuffer);
hairRendererCompute.SetBuffer(allInOneKernel, "verletNodes", verletNodesBuffer);
hairRendererCompute.SetFloat("strandsWidth", getStrandsWidth());
var strandNodesCount = hairContainer.pointsPerStrand; //Get from layer
int nearestPow = CullAndSortController.nextPowerOf2(strandNodesCount);
var nodesCount = hairContainer.hairStrandPoints.Length;
var strandsCount = nodesCount / strandNodesCount;
int dispatchCount = nearestPow * strandsCount;
var layerNodeStartIndex = 0;
//This should be done in a common function in VerletSimulation.cs
hairRendererCompute.SetInt("nearestPow", nearestPow);
hairRendererCompute.SetInt("strandPointsCount", strandNodesCount);
hairRendererCompute.SetVector("worldSpaceCameraPos", lodCamera.getCamera().transform.position);
hairRendererCompute.SetFloat("sourceMeshNormalToStrandNormalPercent", settings.sourceMeshNormalToStrandNormalPercent);
hairRendererCompute.SetInt("verletNodesCount", nodesCount);
hairRendererCompute.SetInt("layerVertexStartIndex", layerNodeStartIndex);
hairRendererCompute.SetFloat("shapeConstraintRoot", settings.verletSimulationSettings.stiffnessRoot);
hairRendererCompute.SetFloat("keepShapeStrength", settings.verletSimulationSettings.keepShapeStrength);
hairRendererCompute.SetFloat("shapeConstraintTip", settings.verletSimulationSettings.stiffnessTip);
hairRendererCompute.SetInt("numberOfFixedNodesInStrand", settings.verletSimulationSettings.isFirstNodeFixed ? 1 : 0);
hairRendererCompute.setKeywordEnabled("IS_SKINNED", hairContainer.isSkinned);
hairRendererCompute.SetVector("_Gravity", settings.verletSimulationSettings.gravity);
hairRendererCompute.SetFloat("deltaTime", Time.smoothDeltaTime);
hairRendererCompute.SetFloat("_Decay", 1f - settings.verletSimulationSettings.drag);
hairRendererCompute.SetFloat("stepSize", 1f / settings.verletSimulationSettings.constraintIterations);
hairRendererCompute.SetInt("solverIterations", settings.verletSimulationSettings.constraintIterations);
ColliderHelper.setupColliderProperties(ref colliderBuffer, ref collidersStruct, sphereColliders, capsuleColliders,
hairRendererCompute,
allInOneKernel);
VerletSimulation.setupWind(hairRendererCompute, allInOneKernel, settings);
hairRendererCompute.setKeywordEnabled("VERLET_ENABLED", Application.isPlaying && settings.verletSimulationSettings.enableMovement);
hairRendererCompute.setKeywordEnabled("COLLIDE_WITH_SOURCE_MESH",
settings.verletSimulationSettings.isVerletColliderEnabled() && hairContainer.isSkinned);
hairRendererCompute.setKeywordEnabled("USE_FORWARD_COLLISION", settings.verletSimulationSettings.useForwardCollision);
hairRendererCompute.SetFloat(ShaderID.EXTRA_SCALE, 1f + (thisTransform.lossyScale.x - hairContainer.objectScaleAtSkinning));
hairRendererCompute.Dispatch(allInOneKernel, dispatchCount.toCsGroups(), 1, 1);
hairRendererCompute.DisableKeyword("INITIALIZE_VERLET_NODES");
collideWithOtherSDFColliders();
}
}
private float getStrandsWidth() { return FurCreator.getInterpolatedWidth(hairContainer.strandsWidth, 0.0025f); }
private void collideWithOtherSDFColliders() {
if (settings.verletSimulationSettings.isSDFCollisionEnabled() && sdfColliders != null) {
foreach (var sdfCollider in sdfColliders) {
if (sdfCollider != null) {
sdfCollider.collideWith(verletNodesBuffer, fluffyRenderersController.getRendererBounds(),
fluffyRenderersController.hairMeshBuffer,
fluffyRenderersController.getVertexBufferStride());
}
}
}
}
private void beginFrameRendering(ScriptableRenderContext rc, Camera[] cameras) { updateAndRenderHair(); }
private void cameraPreRender(Camera cam) {
if (lodCamera.getCamera() == cam) {
updateAndRenderHair();
}
}
private void OnDisable() {
clearResources();
RenderPipelineManager.beginFrameRendering -= beginFrameRendering;
Camera.onPreRender -= cameraPreRender;
}
private void clearResources() {
meshBaker?.dispose();
fluffyRenderersController.destroy();
hairStrandPointsBuffer?.Dispose();
verletNodesBuffer?.Dispose();
sdfColliderCommon?.dispose();
strandShapeBuffer?.Dispose();
}
public Renderer CurrentRenderer { get; set; }
public void recreateSdfCollider() {
if (hairContainer.isSkinned) {
sdfColliderCommon?.dispose();
sdfColliderCommon = new SDFColliderCommon(GetComponent<Renderer>(), meshBaker, settings.verletSimulationSettings);
}
}
public void recreate() {
clearResources();
initialize();
}
void Update() { checkForHairContainer(); }
public void rebuildShapeBuffer() { hairContainer.createShapeBuffer(strandShapeBuffer); }
}
}