Skip to content

Instantly share code, notes, and snippets.

@dorodo95
Last active July 11, 2023 00:20
Show Gist options
  • Save dorodo95/d8da68df38335153b4a7599026d2b0fd to your computer and use it in GitHub Desktop.
Save dorodo95/d8da68df38335153b4a7599026d2b0fd to your computer and use it in GitHub Desktop.
Object Smearing via Shader
using UnityEngine;
public class GenerateVectorData : MonoBehaviour
{
private SkinnedMeshRenderer m_mesh;
private Mesh skinnedMeshCache;
private Vector3[] vertexFrameCache1;
private Vector3[] vertexFrameCache2;
private Vector3[] vertexFrameCache3;
private ComputeBuffer vertexBuffer1;
private ComputeBuffer vertexBuffer2;
private ComputeBuffer vertexBuffer3;
void Start()
{
m_mesh = GetComponent<SkinnedMeshRenderer>();
skinnedMeshCache = new Mesh();
//initializing variables and buffers
m_mesh.BakeMesh(skinnedMeshCache);
vertexFrameCache1 = m_mesh.sharedMesh.vertices;
vertexFrameCache2 = m_mesh.sharedMesh.vertices;
vertexFrameCache3 = m_mesh.sharedMesh.vertices;
//We use Buffers because we can't send the vertices as is, sending vector arrays to shader needs to be done as Vector4[], and Mesh.Vertices is a Vector3[]. Buffers allow us to bypass this issue
vertexBuffer1 = new ComputeBuffer(m_mesh.sharedMesh.vertices.Length, 12); //Buffer needs to be the same size as the vertices length and have 3 * 4 (bytes) as size
vertexBuffer2 = new ComputeBuffer(m_mesh.sharedMesh.vertices.Length, 12);
vertexBuffer3 = new ComputeBuffer(m_mesh.sharedMesh.vertices.Length, 12);
}
void Update()
{
//Copying vertex data: this is done from oldest to newest value to keep 4 all frames cached
vertexFrameCache3 = vertexFrameCache2;
vertexFrameCache2 = vertexFrameCache1;
vertexFrameCache1 = skinnedMeshCache.vertices;
m_mesh.BakeMesh(skinnedMeshCache); //We can't access Skinned mesh data directly, so we need to store the information of the mesh
//Sending vertex information to buffers
vertexBuffer3.SetData(vertexFrameCache3);
vertexBuffer2.SetData(vertexFrameCache2);
vertexBuffer1.SetData(vertexFrameCache1);
//This could be done per material instead of a global variable, it's just for proof of concept
Shader.SetGlobalBuffer("_VertexBuffer3", vertexBuffer3);
Shader.SetGlobalBuffer("_VertexBuffer2", vertexBuffer2);
Shader.SetGlobalBuffer("_VertexBuffer1", vertexBuffer1);
}
void OnDestroy()
{
//Make sure to release to avoid GC
vertexBuffer1.Release();
vertexBuffer2.Release();
vertexBuffer3.Release();
}
}
Shader "Custom/Smear"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_MotionMaskTex ("Motion Mask", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
uint id : SV_VERTEXID; //We need this to fetch the correct information from the buffers
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
sampler2D _MainTex;
sampler2D _MotionMaskTex;
StructuredBuffer<float3> _VertexBuffer1; //1 Frame Before
StructuredBuffer<float3> _VertexBuffer2; //2 Frames Before
StructuredBuffer<float3> _VertexBuffer3; //3 Frames Before
v2f vert (appdata v)
{
v2f o;
o.uv = v.uv;
float3 worldNormal = UnityObjectToWorldNormal(v.normal); //We'll use world normal as a mask for the motion
float3 vertexCurrent = v.vertex;
vertexCurrent = mul(unity_ObjectToWorld, float4(vertexCurrent,1)); //everything is done in world space to allow for rotations
float3 vertexCache3 = _VertexBuffer3[v.id];
vertexCache3 = mul(unity_ObjectToWorld, float4(vertexCache3,1));
float3 vertexCache2 = _VertexBuffer2[v.id];
vertexCache2 = mul(unity_ObjectToWorld, float4(vertexCache2,1));
float3 vertexCache1 = _VertexBuffer1[v.id];
vertexCache1 = mul(unity_ObjectToWorld, float4(vertexCache1,1));
vertexCache2 = lerp(vertexCache2, vertexCache3, 0.7); //lerping to weight in previous frames
vertexCache1 = lerp(vertexCache1, vertexCache2, 0.7);
float3 vertexOffset = vertexCache1 - vertexCurrent; //this gives us a Motion Vector based that will serve as our vertex displacement
float weight = saturate(dot(worldNormal, normalize(vertexOffset))); //Dot product between the normal and motion vectors shows us the areas which the motion is affecting
weight *= tex2Dlod(_MotionMaskTex, float4(v.uv,0,0)).r; //A mask texture allows us to have manual control over which areas should receive smearing
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
float3 cachedPos = worldPos.xyz + vertexOffset; //adds the vertex offset to the current frame
worldPos.xyz = lerp(worldPos.xyz, cachedPos, weight * 1.2); //Mask between regular current frame and displaced current frame
o.vertex = mul(UNITY_MATRIX_VP, worldPos);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment