353 lines
13 KiB
Plaintext
353 lines
13 KiB
Plaintext
|
|
Shader "Hidden/NiloToon/NiloToonMotionBlur"
|
|
{
|
|
Properties
|
|
{
|
|
_MainTex ("Texture", 2D) = "white" {}
|
|
}
|
|
|
|
SubShader
|
|
{
|
|
// require Unity2022.3 or above
|
|
PackageRequirements
|
|
{
|
|
"com.unity.render-pipelines.universal": "14.0.0"
|
|
}
|
|
|
|
Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"}
|
|
ZWrite Off ZTest Always Blend Off Cull Off
|
|
|
|
HLSLINCLUDE
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
|
|
|
|
TEXTURE2D_X(_MainTex);
|
|
SAMPLER(sampler_MainTex);
|
|
TEXTURE2D_X(_TileMaxTex);
|
|
SAMPLER(sampler_TileMaxTex);
|
|
TEXTURE2D_X(_NeighborMaxTex);
|
|
SAMPLER(sampler_NeighborMaxTex_linear_clamp);
|
|
TEXTURE2D_X(_MotionVectorTexture);
|
|
SAMPLER(sampler_MotionVectorTexture);
|
|
TEXTURE2D_X(_CameraDepthTexture);
|
|
SAMPLER(sampler_CameraDepthTexture);
|
|
#if UNITY_VERSION <= 202310
|
|
SAMPLER(sampler_LinearClamp);
|
|
SAMPLER(sampler_PointClamp);
|
|
#endif
|
|
|
|
|
|
TEXTURE2D_X(_CopyTex);
|
|
SAMPLER(sampler_CopyTex);
|
|
|
|
//float4 _MainTex_TexelSize;
|
|
float _NumSamples;
|
|
float _TileSize;
|
|
float _Intensity;
|
|
float _SoftZExtent;
|
|
float4 _SourceMV_TexelSize;
|
|
float _MaxBlurRadius;
|
|
|
|
struct Attributes
|
|
{
|
|
float4 positionOS : POSITION;
|
|
float2 texcoord : TEXCOORD0;
|
|
};
|
|
|
|
struct Varyings
|
|
{
|
|
float4 positionHCS : SV_POSITION;
|
|
float2 texcoord : TEXCOORD0;
|
|
};
|
|
|
|
Varyings Vert(Attributes input)
|
|
{
|
|
Varyings output;
|
|
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
|
|
output.texcoord = input.texcoord;
|
|
return output;
|
|
}
|
|
|
|
float2 GetMotionVector(float2 uv)
|
|
{
|
|
// Unity doc to sample URP's motion vector
|
|
// https://docs.unity3d.com/6000.0/Documentation/Manual/urp/features/motion-vectors-sample.html
|
|
return SAMPLE_TEXTURE2D_X(_MotionVectorTexture, sampler_LinearClamp, uv).xy;
|
|
}
|
|
|
|
// return = is A at the back relative to B
|
|
float SoftDepthCompare(float depthA, float depthB)
|
|
{
|
|
// Soft depth comparison function
|
|
return saturate(1.0 - (depthA - depthB) / _SoftZExtent);
|
|
}
|
|
|
|
float ConeWeight(float2 X, float2 Y, float2 v)
|
|
{
|
|
// Cone function: linear falloff based on distance along the motion vector
|
|
return saturate(1.0 - length(X - Y) / (length(v) + 1e-4));
|
|
}
|
|
|
|
float CylinderWeight(float2 X, float2 Y, float2 v)
|
|
{
|
|
// Cylinder function for simultaneous blurriness
|
|
float dist = length(X - Y);
|
|
float vLen = length(v) + 1e-4;
|
|
return saturate(1.0 - smoothstep(0.95 * vLen, 1.05 * vLen, dist));
|
|
}
|
|
|
|
float Random(float2 uv)
|
|
{
|
|
// Simple hash-based random function
|
|
return frac(sin(dot(uv , float2(12.9898,78.233))) * 43758.5453);
|
|
}
|
|
|
|
// copied from URP's CameraMotionBLur.shader
|
|
float2 ClampVelocity(float2 velocity, float maxVelocity)
|
|
{
|
|
float len = length(velocity);
|
|
return (len > 0.0) ? min(len, maxVelocity) * (velocity * rcp(len)) : 0.0;
|
|
}
|
|
|
|
float4 TileMaxFilter(Varyings input) : SV_Target
|
|
{
|
|
// Source texture dimensions
|
|
float sourceWidth = _SourceMV_TexelSize.z;
|
|
float sourceHeight = _SourceMV_TexelSize.w;
|
|
|
|
// The size of the tile in pixels
|
|
int tileSize = _TileSize;
|
|
|
|
// Compute number of tiles (ensure integer values)
|
|
float numTilesX = ceil(sourceWidth / tileSize);
|
|
float numTilesY = ceil(sourceHeight / tileSize);
|
|
|
|
// Compute tile indices in TileMax RT
|
|
float2 tileCoord = input.texcoord * float2(numTilesX, numTilesY);
|
|
int2 tileIndex = int2(tileCoord);
|
|
|
|
// Compute the origin of the tile in source texture pixel coordinates
|
|
float2 tileOrigin = float2(tileIndex) * tileSize;
|
|
|
|
// Initialize variables
|
|
float2 maxVelocity = float2(0, 0);
|
|
float maxLength = 0;
|
|
|
|
// Loop over the pixels in the tile
|
|
for (int y = 0; y < tileSize; y++)
|
|
{
|
|
int py = int(tileOrigin.y) + y;
|
|
//if (py >= sourceHeight)
|
|
// break; // Avoid accessing beyond texture height
|
|
|
|
for (int x = 0; x < tileSize; x++)
|
|
{
|
|
int px = int(tileOrigin.x) + x;
|
|
//if (px >= sourceWidth)
|
|
// break; // Avoid accessing beyond texture width
|
|
|
|
float2 pixelCoord = float2(px, py);
|
|
|
|
// Convert pixel coordinates to UVs in the source texture
|
|
float2 uv = pixelCoord / float2(sourceWidth, sourceHeight);
|
|
uv = clamp(uv, 0.0, 1.0); // Ensure UVs are within [0,1]
|
|
|
|
// Sample the motion vector at this UV
|
|
float2 velocity = GetMotionVector(uv);
|
|
|
|
float velocityLength = length(velocity);
|
|
|
|
if (velocityLength > maxLength)
|
|
{
|
|
maxVelocity = velocity;
|
|
maxLength = velocityLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
return float4(maxVelocity, 0, 1);
|
|
}
|
|
|
|
float4 NeighborMaxFilter(Varyings input) : SV_Target
|
|
{
|
|
float2 maxVelocity = float2(0, 0);
|
|
float maxLength = 0;
|
|
|
|
// Compute the texel size of TileMaxTex in UV space
|
|
float2 texelSize = _TileSize * _SourceMV_TexelSize.xy; // Equivalent to tile size in UVs
|
|
|
|
for (int y = -1; y <= 1; y++)
|
|
{
|
|
for (int x = -1; x <= 1; x++)
|
|
{
|
|
float2 offset = float2(x, y) * texelSize;
|
|
float2 uv = input.texcoord + offset;
|
|
|
|
// Clamp UVs to prevent sampling outside the texture
|
|
uv = clamp(uv, 0.0, 1.0);
|
|
|
|
float2 velocity = SAMPLE_TEXTURE2D_X(_TileMaxTex, sampler_LinearClamp, uv).xy;
|
|
float velocityLength = length(velocity);
|
|
|
|
if (velocityLength > maxLength)
|
|
{
|
|
maxVelocity = velocity;
|
|
maxLength = velocityLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
return float4(maxVelocity, 0, 1);
|
|
}
|
|
|
|
float4 NaiveMotionBlur(Varyings input) : SV_TARGET
|
|
{
|
|
float2 velocity = GetMotionVector(input.texcoord);
|
|
|
|
float4 color = 0;
|
|
for (int i = -32; i < 32; i++)
|
|
{
|
|
color += SAMPLE_TEXTURE2D_X(_CopyTex, sampler_LinearClamp, input.texcoord + velocity * i * 0.005);
|
|
}
|
|
|
|
return color * (1.0/65.0);
|
|
}
|
|
float4 MotionBlur(Varyings input) : SV_Target
|
|
{
|
|
float _ExposureTime = (1000/60) * _Intensity;
|
|
float _SampleCount = 15; // atleast 3, better to be odd number
|
|
float _EarlyExitThreshold = 1e-4;
|
|
//---------------------------
|
|
float2 uv = input.texcoord;
|
|
float2 pixelPos = uv * _ScaledScreenParams.xy;
|
|
|
|
// Retrieve the dominant motion vector from NeighborMax texture
|
|
float2 vN = SAMPLE_TEXTURE2D_X(_NeighborMaxTex, sampler_LinearClamp, uv).xy;
|
|
vN *= _ExposureTime;
|
|
|
|
vN = ClampVelocity(vN, _MaxBlurRadius);
|
|
|
|
float vNLength = length(vN);
|
|
|
|
float4 color = SAMPLE_TEXTURE2D_X(_MainTex, sampler_MainTex, uv);
|
|
|
|
// Check for significant motion
|
|
if (vNLength <= _EarlyExitThreshold)
|
|
{
|
|
// No significant motion, return original color
|
|
return color;
|
|
}
|
|
|
|
// Initialize accumulation variables
|
|
float weightSum = 0.5;
|
|
float3 colorSum = color.rgb * weightSum;
|
|
|
|
// Get per-pixel motion vector
|
|
float2 velocity = SAMPLE_TEXTURE2D_X(_MotionVectorTexture, sampler_LinearClamp, uv).xy;
|
|
velocity *= _ExposureTime;
|
|
|
|
// Clamp velocity
|
|
velocity = ClampVelocity(velocity, _MaxBlurRadius);
|
|
|
|
// Get depth of current pixel
|
|
float depth = Linear01Depth(SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, uv).r,_ZBufferParams); // DX: 1 at near, 0 at far. Linear
|
|
|
|
// Jitter to prevent ghosting
|
|
float jitter = Random(uv) - 0.5;
|
|
|
|
//jitter *= 0.5;
|
|
|
|
// Number of samples along motion vector
|
|
int S = _SampleCount;
|
|
|
|
for (int i = 0; i < S; i++)
|
|
{
|
|
// Fraction along the motion vector (-0.5 to 0.5)
|
|
float t = (i + jitter) / S - 0.5;
|
|
|
|
// Sample position along the motion vector
|
|
float2 sampleOffset = vN * t;
|
|
float2 sampleUv = uv + sampleOffset * _SourceMV_TexelSize.xy;
|
|
|
|
// Ensure sampleUv is within texture bounds
|
|
sampleUv = saturate(sampleUv);
|
|
|
|
// Retrieve sample color and depth
|
|
float3 sampleColor = SAMPLE_TEXTURE2D_X_LOD(_MainTex, sampler_LinearClamp, sampleUv, 0).rgb;
|
|
float sampleDepth = Linear01Depth(SAMPLE_TEXTURE2D_X_LOD(_CameraDepthTexture, sampler_CameraDepthTexture, sampleUv, 0).r, _ZBufferParams); // DX: 1 at near, 0 at far. Linear
|
|
float2 sampleVelocity = GetMotionVector(sampleUv);
|
|
sampleVelocity *= _ExposureTime;
|
|
|
|
// Clamp the length of velocity to _MaxBlurRadius
|
|
sampleVelocity = ClampVelocity(sampleVelocity, _MaxBlurRadius);
|
|
|
|
// [copy directly according to McGuire12Blur paper]
|
|
// Fore- vs. background classification of Y relative to X
|
|
// f means front
|
|
// b means back
|
|
// depth input is 0~1 (we assume linear is better)
|
|
// Note: we swap f&b's value, it is different to paper's design, but it is the correct way in unity
|
|
float f = SoftDepthCompare(sampleDepth, depth); // f = is offseted pixel(Y) at the front of original pixel(X)
|
|
float b = SoftDepthCompare(depth, sampleDepth); // b = is offseted pixel(Y) at the back of original pixel(X)
|
|
float alphaY = 0.0;
|
|
|
|
// [copy directly according to McGuire12Blur paper]
|
|
float2 X = pixelPos; // original pixel coord index
|
|
float2 Y = pixelPos + sampleOffset; // offseted pixel coord index
|
|
float2 VX = velocity; // original pixel velocity
|
|
float2 VY = sampleVelocity; // offseted pixel velocity
|
|
|
|
// TODO: length(X-Y) and Velocity should be using the same unit
|
|
alphaY += f * ConeWeight(Y, X, VY); // case 1: Y contributes when Y is moving+blurry and passes in front of X
|
|
alphaY += b * ConeWeight(X, Y, VX); // case 2: X itself is moving+blurry so we should be able to see through
|
|
alphaY += CylinderWeight(Y, X, VY) * CylinderWeight(X, Y, VX) * 2.0; // case 3: 2 moving blur together
|
|
|
|
// test
|
|
alphaY *= 1.0 / max(1,distance(X,Y)/_TileSize);
|
|
|
|
// Accumulate color and weight
|
|
colorSum += sampleColor * alphaY;
|
|
weightSum += alphaY;
|
|
}
|
|
|
|
// Normalize accumulated color
|
|
float3 finalColor = colorSum / max(weightSum, 1e-4);
|
|
|
|
return float4(finalColor, 1.0);
|
|
}
|
|
|
|
ENDHLSL
|
|
|
|
Pass // Tile max
|
|
{
|
|
Name "Tile Max"
|
|
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment TileMaxFilter
|
|
ENDHLSL
|
|
}
|
|
|
|
Pass // Neighbor max
|
|
{
|
|
Name "Neighbor Max"
|
|
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment NeighborMaxFilter
|
|
ENDHLSL
|
|
}
|
|
|
|
Pass // Motion blur
|
|
{
|
|
Name "Motion Blur"
|
|
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
//#pragma fragment NaiveMotionBlur
|
|
#pragma fragment MotionBlur
|
|
ENDHLSL
|
|
}
|
|
}
|
|
} |