Cleaning Hell, One Vertex at a Time

Technical Art, Minus the Technicalities

Demo

  1. Runtime vertex painting
  2. Layers of blood
  3. Visual effects

1. Runtime vertex painting


Render Texture

or

Vertex Painting

Problems with runtime vertex painting:

  • No GPU instancing, mesh data different for each asset.
  • Introduces additional 3D asset requirements.
  • Low FPS issue when vertex painting.


							// Paint-vert: result = Sphere Overlap Check on a raycast. 
							// This is within a coroutine.
							foreach (var col in result)
							{
								if (!col)
									continue;
								col.TryGetComponent(out Cleanable cleanable);
								if (cleanable)
								{
									Vector3 returnColor = Global.I.PaintVert(
										col.GetComponent<MeshRenderer>(),
										hit.point,
										// Paint strength depending on number of objs being painted
										paintStrengthNormal * result.Length,
										paintRadius,
										// 
										cleanable.KdTree);
								}
								// Paint 1 mesh per frame
								yield return null;
							}
						
  • Splitting tasks over multiple frames with coroutines.
  • Spatial partitioned the mesh using k-d tree.

					// the KDTree was generated during Init()
					KDQuery query = new KDQuery();
					query.Radius(tree, originLocal, radius, results);
			
					float redAmountDelta = 0f;
					float greenAmountDelta = 0f;
					float blueAmountDelta = 0f;
			
					foreach (int i in results)
					{
						Vector3 vertex = vertices[i];
						
						float distanceSqr = (originLocal - vertex).sqrMagnitude;
						if (distanceSqr < radiusSqr)
						{
							float proximity = 1.0f - Mathf.Sqrt(distanceSqr) / radius;
			
							float originalRed = colors[i].r;
							float originalGreen = colors[i].g;
							float originalBlue = colors[i].b;
							float redChange = channelStrength.x * proximity * Time.deltaTime;
							float greenChange = channelStrength.y * proximity * Time.deltaTime;
							float blueChange = channelStrength.z * proximity * Time.deltaTime;
			
							float newRed = Mathf.Clamp01(originalRed - redChange);
							float newGreen = Mathf.Clamp01(originalGreen- greenChange);
							float newBlue = Mathf.Clamp01(originalBlue- blueChange);
							
							redAmountDelta += originalRed - newRed;
							greenAmountDelta += originalGreen - newGreen;
							blueAmountDelta += originalBlue - newBlue;
			
							// Update vertex color array
							colors[i].r = newRed;
							colors[i].g = newGreen;
							colors[i].b = newBlue;
						}
					}
					...
				
  • Occlusion culling to control draw-calls.

2. Layers of blood

  • Master Shader: Triplanar sample for blood layers.
  • Problem: 15 texture samples...
  • Problem: lightmap bake will pick up the redness from mesh

Master Material

Problem: Lightmap picking up colour information from paintable layer.
Solution: "Bake-Mode" in shader (static switch on paintable layers)

3. Visual effects

This was implemented on master shader's emission output.

VFX: Puddle

  • Object-Pooled Decal Projector
  • SetFloat() on raycast to increase puddle size (erosion map lerp)

Puddle Decal Shader
We're modifying only Normal,Metal*,Smoothness.

VFX: Droplets

  • Spawn on raycast from Object-Pool

Thank you!