Last active
July 11, 2023 00:20
-
-
Save dorodo95/d8da68df38335153b4a7599026d2b0fd to your computer and use it in GitHub Desktop.
Object Smearing via Shader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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