Files
UnrealEngineUWP/Engine/Shaders/PostProcessMotionBlur.usf
Martin Mittring f6a96e7739 optimized motionblur postprocess
[CL 2286656 by Martin Mittring in Main branch]
2014-09-05 17:47:58 -04:00

618 lines
20 KiB
Plaintext

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PostProcessMotionBlur.usf: PostProcessing MotionBlur
=============================================================================*/
#include "Common.usf"
#include "PostProcessCommon.usf"
#include "DeferredShadingCommon.usf" // FGBufferData
// Spaces:
// screen space: -1..1 -1..1
// normalized_motionblur_velocity: 2d vector where the max motionblur is defined in a unit circle around 0
// world_space
// half_res_pixels
// similar implementation to: http://graphics.cs.williams.edu/papers/MotionBlurI3D12/McGuire12Blur.pdf
// but adapted for half resolution and not using randomization/noise
// from the paper: We use SOFT Z EXTENT = 1mm to 10cm for our results
#define SOFT_Z_EXTENT 1
// 0: use more texture lookups avoiding .Gather() lookups
#define GATHER_OPTIMIZATION (FEATURE_LEVEL >= FEATURE_LEVEL_SM5)
// 0:off / 1:on, useful to debug the motionblur algorithm
#define MOTIONBLUR_TESTCHART 0
// helper function, from the paper but adjusted to avoid division by zero
float cone(float2 X, float2 Y, float2 v)
{
// to avoid div by 0
float Bias = 0.001f;
// better for no background velocity
return length(X - Y) < length(v);
}
// helper function, from the paper but adjusted to avoid division by zero
float cylinder(float2 X, float2 Y, float2 v)
{
// to avoid div by 0
float Bias = 0.001f;
return 1 - smoothstep(0.95f * length(v), 1.05f * length(v) + Bias, length(X - Y) );
}
// helper function, from the paper
// note this assumes negative z values
// is zb closer than za?
float softDepthCompare(float za, float zb)
{
return saturate(1 - (za - zb) / SOFT_Z_EXTENT);
}
// ------------------------------------------
// MOTION_BLUR_QUALITY == 0:visualize, 1:low, 2:medium, 3:high, 4:very high
// to scale to normalized motionblur velocity
// xy:includes y flip, zw:unused
float4 VelocityScale;
// Last frame's view projection matrix (world-camera) to clip)
float4x4 PrevViewProjMatrix;
// .xy multiply, .zw:add
// to transform the UV to a normalized view 0..1
float4 TextureViewMad;
// xy:IndividualVelocityScale.xy zw:unused, from postprocess settings
float4 MotionBlurParameters;
// ------------------------------------------
// debug motionblur (very useful, keep)
// @param ScreenPos -1..1 -1..1 for viewport
// @param Velocity in -1..1 range for full motionblur
// @apram Color RGB and depth in alpha
// @param AvgObject 0:background, 1:foregound
void OverrideWithTestChart(float2 ScreenPos, inout float2 ObjectVelocity, inout float2 BackgroundVelocity, inout float4 Color, inout float AvgObject)
{
#if MOTIONBLUR_TESTCHART == 1
// needs to be inside the loop to prevent NVIDIA driver optimizetion (blinking)
float2 PixelPos = ScreenPos * ScreenPosToPixel.xy + ScreenPosToPixel.zw + 0.5f - 25;
float3 BackgroundColor = lerp(0.0, 0.3f, PseudoRandom(PixelPos));
float3 ForegroundColor = lerp(float3(1, 0, 0), float3(1, 1, 0), PseudoRandom(PixelPos));
int2 tile = (int2)floor(PixelPos / 12.0f);
int2 experiment = (int2)floor(tile / 5.0f);
if(experiment.x >= 0 && experiment.y >= 0 && experiment.x < 10 && experiment.y < 5)
{
int2 localtile = uint2(tile) % 5;
bool bForeground = localtile.x == 2 && localtile.y == 2;
Color.rgb = bForeground ? ForegroundColor : BackgroundColor;
// depth
Color.a = bForeground ? 100.0f : 1000.0f;
bool bLeftSide = experiment.x < 5;
if(!bLeftSide)
{
experiment.x -= 5;
}
float ForegroundAngle = (experiment.x - 1) * (6.283f / 12);
float BackgroundAngle = (experiment.y - 1) * (6.283f / 12) + 3.1415f/2;
// ForegroundR with very small amounts needs extra testing so we do a non linear scale
float ForegroundR = pow(experiment.x / 5.0f, 2);
float BackgroundR = pow(experiment.y / 5.0f, 2);
float2 ForegroundXY = ForegroundR * float2(sin(ForegroundAngle), cos(ForegroundAngle));
float2 BackgroundXY = BackgroundR * float2(sin(BackgroundAngle), cos(BackgroundAngle));
BackgroundVelocity.xy = BackgroundXY;
if(bLeftSide)
{
ObjectVelocity.xy = ForegroundXY;
AvgObject = bForeground;
}
else
{
ObjectVelocity.xy = bForeground ? ForegroundXY : BackgroundXY;
AvgObject = 1.0f;
}
}
#endif
}
// ------------------------------------------
// motion blur setup vertex shader
void SetupVS(
in float4 InPosition : ATTRIBUTE0,
in float2 InTexCoord : ATTRIBUTE1,
out float2 OutUV: TEXCOORD0,
out float2 OutUVs[4]: TEXCOORD1,
out float4 OutPosition : SV_POSITION
)
{
DrawRectangle(InPosition, InTexCoord, OutPosition, OutUV);
// float2 ScreenPos = OutPosition.xy;
float2 HalfPixelOffset = PostprocessInput1Size.zw * float2(0.5f, 0.5f);
// no filtering (2x2 kernel) to get no leaking in Depth of Field, lefttop,righttop,leftbottom,rightbottom
OutUVs[0] = OutUV + HalfPixelOffset * float2(-1, -1);
OutUVs[1] = OutUV + HalfPixelOffset * float2( 1, -1);
OutUVs[2] = OutUV + HalfPixelOffset * float2(-1, 1);
OutUVs[3] = OutUV + HalfPixelOffset * float2( 1, 1);
}
// motion blur setup pixel shader, downsamples to half resolution
// MRT0: velocity in normalized_motionblur_velocity and mask in 0:background..1:object (needed for soft masked)
// MRT1: color and depth in alpha
void SetupPS(
float2 UV : TEXCOORD0, // UV into the full resolution source RT
float2 UVs[4] : TEXCOORD1, // UV for high quality 4 samples with blurring
out float4 OutVelocity : SV_Target0,
out float4 OutColor : SV_Target1)
{
// detect find objects
float4 ObjectMask;
float2 VelocitySamples[4];
#if GATHER_OPTIMIZATION
{
// using Gather: xyzw in counter clockwise order starting with the sample to the lower left of the queried location
float4 Red = PostprocessInput0.GatherRed(PostprocessInput0Sampler, UV);
float4 Green = PostprocessInput0.GatherGreen(PostprocessInput0Sampler, UV);
VelocitySamples[0] = float2(Red.w, Green.w);
VelocitySamples[1] = float2(Red.z, Green.z);
VelocitySamples[2] = float2(Red.x, Green.x);
VelocitySamples[3] = float2(Red.y, Green.y);
}
#else
UNROLL for( int i = 0; i < 4; i++ )
{
VelocitySamples[i] = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UVs[i], 0).xy;
}
#endif
UNROLL for( int i = 0; i < 4; i++ )
{
ObjectMask[i] = VelocitySamples[i].x > 0;
VelocitySamples[i] = DecodeVelocityFromTexture(VelocitySamples[i]);
VelocitySamples[i] = ObjectMask[i] ? VelocitySamples[i] : 0;
}
float AvgObject = dot(ObjectMask, 0.25f);
ObjectMask = (AvgObject > 0.5f) ? ObjectMask : (1 - ObjectMask);
// bias to avoid div by 0
float InvTotalWeight = 1.0 / ( dot( ObjectMask, 1 ) + 0.000001f );
float4 SumColorAndDepth = 0;
#if GATHER_OPTIMIZATION
float4 DepthValues = PostprocessInput2.GatherRed(PostprocessInput2Sampler, UV, 0).r;
#else
float4 DepthValues = float4(
PostprocessInput2.SampleLevel(PostprocessInput2Sampler, UVs[2], 0).r,
PostprocessInput2.SampleLevel(PostprocessInput2Sampler, UVs[3], 0).r,
PostprocessInput2.SampleLevel(PostprocessInput2Sampler, UVs[1], 0).r,
PostprocessInput2.SampleLevel(PostprocessInput2Sampler, UVs[0], 0).r);
#endif
SumColorAndDepth += float4(PostprocessInput1.SampleLevel(PostprocessInput1Sampler, UVs[0], 0).rgb, DepthValues.x) * ObjectMask.x;
SumColorAndDepth += float4(PostprocessInput1.SampleLevel(PostprocessInput1Sampler, UVs[1], 0).rgb, DepthValues.y) * ObjectMask.y;
SumColorAndDepth += float4(PostprocessInput1.SampleLevel(PostprocessInput1Sampler, UVs[2], 0).rgb, DepthValues.z) * ObjectMask.z;
SumColorAndDepth += float4(PostprocessInput1.SampleLevel(PostprocessInput1Sampler, UVs[3], 0).rgb, DepthValues.w) * ObjectMask.w;
OutColor = SumColorAndDepth * InvTotalWeight;
// clamp to avoid artifacts from exceeding fp16 through framebuffer blending of multiple very bright lights
OutColor.rgb = min(float3(256 * 256, 256 * 256, 256 * 256), OutColor.rgb);
OutColor.a = ConvertFromDeviceZ(OutColor.a);
{
float2 SumVelocity = 0;
FLATTEN for( int i = 0; i < 4; i++ )
{
SumVelocity += VelocitySamples[i] * ObjectMask[i];
}
OutVelocity.xy = SumVelocity * InvTotalWeight * VelocityScale.xy;
}
// for debugging
/*
{
float2 BackgroundVelocity = 0;
OverrideWithTestChart(UVAndScreenPos.zw, OutVelocity.xy, BackgroundVelocity, OutColor, AvgObject);
}
*/
// 0:background, 1:object layer
OutVelocity.b = AvgObject > 0.5f;
// clamp motion vector in a disc from -1 to 1 (the maximum motion vector range)
{
half Len = dot(OutVelocity.xy, OutVelocity.xy);
float ScaleFix = rsqrt(Len);
FLATTEN if(Len < 1)
{
ScaleFix = 1.0f;
}
FLATTEN if(Len < 0.01f)
{
ScaleFix = 0;
}
OutVelocity.xy *= ScaleFix * OutVelocity.b;
}
// alpha is used to normalize the velocity after blurring (0:background, 1:object)
OutVelocity.a = 1;
// debug, uncomment to geneate small and large horizontal motion and the objects
// OutVelocity.xy = lerp(float2(0,0), (InUV.y > 0.8f) ? float2(1,0) : float2(0.02f,0), OutVelocity.b);
}
// used to visualize the motion blur
// @return 0/1
float Compute3DCheckerBoard(float3 Pos)
{
float3 TiledWorldPos = frac(Pos) > 0.5f;
return (float)((uint)dot(float3(1,1,1), TiledWorldPos) % 2);
}
uint GetStepCountFromQuality()
{
#if MOTION_BLUR_QUALITY == 1
return 4;
#elif MOTION_BLUR_QUALITY == 2
return 6;
#elif MOTION_BLUR_QUALITY == 3
return 8;
#else // MOTION_BLUR_QUALITY == 4
return 16;
#endif
}
// This,
// - Gets the MainPS pass to low cost under no motion.
// - Somewhat cleans up the blur/no blur transition.
// - Reduces the motion blur on small motion (where it is not needed).
// - Removes the background blur (seems higher quality).
#define MOTION_BLUR_EARLY_EXIT 1
// motionblur pixel shader
// input:
// 0: RGB:scene color, A: depth in half resolution from MotionBlurSetup
// 1: blurred quarter res velocity from MotionBlurSetup
// 2: half res velocity from MotionBlurSetup
// half resolution output: RGB: color, A:blend to full res factor
void MainPS(float4 UVAndScreenPos : TEXCOORD0, out float4 OutColor : SV_Target0)
{
// Screen Quad UV 0..1
float2 UV = UVAndScreenPos.xy;
// screen position in [-1, 1] screen space
float2 ScreenSpacePos = UVAndScreenPos.zw;
OutColor = 0;
// RGB:color, A:depth
float4 ColorAndDepth = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV.xy, 0);
// can be moved to VS
float3 ScreenVector = mul(float4(ScreenSpacePos, 1, 0), View.ScreenToWorld).xyz;
// world space position of the current pixel
float3 OffsetWorldPos = ScreenVector * ColorAndDepth.a;
// previous frame clip space position of the current pixel
float4 PrevClipPos = mul(float4(OffsetWorldPos, 1), PrevViewProjMatrix);
// previous frame screen coordinates of the current pixel
float3 PrevScreenCoord = PrevClipPos.xyz / PrevClipPos.w;
// we split the content in a object and background layer
// background velocity in half_res_pixels
float2 PixelBackgroundVelocity;
{
// the background velocity in normalized_motionblur_velocity
float2 NormBackgroundVelocity;
{
NormBackgroundVelocity = (UVAndScreenPos.zw - PrevScreenCoord.xy) * MotionBlurParameters.xy;
// for debugging
{
float2 Velocity = 0;
float4 Color = 0;
float AvgObject = 0;
OverrideWithTestChart(ScreenSpacePos, Velocity, NormBackgroundVelocity, OutColor, AvgObject);
}
// clamp the max motionblur within the unit radius
float MotionLength = length(NormBackgroundVelocity);
FLATTEN if(MotionLength > 1.0f)
{
NormBackgroundVelocity /= MotionLength;
}
}
// in (-1..1 -1..1 for the screen)
float2 ScreenBackgroundVelocity = NormBackgroundVelocity * MotionBlurParameters.zw;
// in half_res_pixels
PixelBackgroundVelocity = ScreenBackgroundVelocity * ScreenPosToPixel.xy;
}
// RG: xy motion in normalized_motionblur_velocity, B: object weight 0:background..1:object
float3 NormSoftMaskedVelocity;
{
float4 SoftMaskedTexture = PostprocessInput1.SampleLevel(PostprocessInput1Sampler, UV.xy, 0);
NormSoftMaskedVelocity = SoftMaskedTexture.rgb / (SoftMaskedTexture.a + 1.0f / 255.0f);
}
// how many motionblur samples affects quality and performance (we do multiple texture lookups per sample)
const uint StepCount = GetStepCountFromQuality();
// we start accumulation with the current pixel with a small weight to define div by near 0 case to be the current pixel color
const float4 ColorAccumStartValue = float4(ColorAndDepth.rgb, 1) * 0.001f;
// RGB:weighed Sum, A:weight to compute average color
float4 BackgroundColorAccum = ColorAccumStartValue;
#if MOTION_BLUR_EARLY_EXIT
if(true)
{
BackgroundColorAccum = float4(PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV.xy, 0).rgb, 1);
}
else
{
#endif
// create background motion layer
UNROLL for(uint i = 0; i < StepCount; ++i)
{
float delta = (i / (float)(StepCount - 1)) - 0.5f;
float2 LocalUV = UV + delta * PixelBackgroundVelocity * PostprocessInput0Size.zw; // can be optimized
float2 NormalizedView = LocalUV * TextureViewMad.xy + TextureViewMad.zw;
FLATTEN if(all(NormalizedView > 0 && NormalizedView < 1))
{
float ObjectSample = PostprocessInput2.SampleLevel(PostprocessInput2Sampler, LocalUV.xy, 0).b;
ObjectSample = saturate(ObjectSample * 4);
float4 ColorSample = float4(PostprocessInput0.SampleLevel(PostprocessInput0Sampler, LocalUV.xy, 0).rgb, 1);
BackgroundColorAccum += ColorSample * (1 - ObjectSample);
}
}
#if MOTION_BLUR_EARLY_EXIT
}
#endif
// object velocity in half_res_pixel
float2 PixelObjectBlurredVelocity;
{
// SoftMasked Velocity in normalized_motionblur_velocity, lower resolution and blurred,
// divide normalizes which is needed because of gaussian blurs, bias to avoid division by 0
half2 NormBlurredVelocity = NormSoftMaskedVelocity.xy / (NormSoftMaskedVelocity.b + 1.0f / 255.0f);
// Update the pixel velocity
float2 ScreenBlurredVelocity = NormBlurredVelocity * MotionBlurParameters.zw;
PixelObjectBlurredVelocity = ScreenBlurredVelocity * ScreenPosToPixel.xy;
}
// --------------------------------------
// camera and background
float2 PixelCombinedVelocity = lerp(PixelBackgroundVelocity, PixelObjectBlurredVelocity, NormSoftMaskedVelocity.b);
#if MOTION_BLUR_EARLY_EXIT
float deBlur = dot(PixelCombinedVelocity, PixelCombinedVelocity);
deBlur = sqrt(max(deBlur, 1.0/65536.0));
deBlur = saturate((deBlur - 2.0) * (1.0/4.0));
if(deBlur == 0.0)
{
OutColor = float4(0.0, 0.0, 0.0, 0.0);
return;
}
else
{
#endif
{
// in pixels
float2 X = UV * PostprocessInput0Size.xy;
float4 cXzX = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV.xy, 0);
float3 cX = cXzX.rgb;
float zX = -cXzX.w; // negated as this was in paper
// in pixels
float4 vXvX = PostprocessInput2.SampleLevel(PostprocessInput2Sampler, UV.xy, 0);
float2 vX = vXvX.xy;
vX = (vXvX.b > 0.5f) ? (vX * MotionBlurParameters.zw * ScreenPosToPixel.xy) : PixelBackgroundVelocity;
// to avoid div by 0
float Bias = 1.0f;
float StartAlpha = 1 / (length(vX) + Bias);
float3 BackgroundColor = BackgroundColorAccum.rgb / BackgroundColorAccum.a;
float ColorAccumSum = 0.0001f;
float4 ColorAccum = float4(BackgroundColor, 1) * ColorAccumSum;
float4 SecondColorAccum = float4(BackgroundColor, 1) * ColorAccumSum;
UNROLL for(uint e = 0; e < StepCount; ++e)
{
// we want to have the samples starting from inside going outwards
uint iMid = StepCount / 2;
uint iSign = (e % 2) * 2 - 1;
uint i = iMid + (e / 2) * iSign;
// -0.5 .. 0.5
float delta = (i / (float)(StepCount - 1)) - 0.5f;
float2 LocalUV = UV + delta * PixelCombinedVelocity * PostprocessInput0Size.zw; // can be optimized
float2 NormalizedView = LocalUV * TextureViewMad.xy + TextureViewMad.zw;
FLATTEN if(all(NormalizedView > 0 && NormalizedView < 1))
{
// in pixels
float2 Y = LocalUV * PostprocessInput0Size.xy;
float4 cYzY = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, LocalUV.xy, 0);
float3 cY = cYzY.rgb;
float zY = -cYzY.w; // negated as this was in paper
// in pixels
float4 vYvY = PostprocessInput2.SampleLevel(PostprocessInput2Sampler, LocalUV.xy, 0);
float2 vY = vYvY.xy;
vY = (vYvY.b > 0.5f) ? (vY * MotionBlurParameters.zw * ScreenPosToPixel.xy) : PixelBackgroundVelocity;
// vY = PixelBackgroundVelocity;
float f = softDepthCompare(zX, zY);
float b = softDepthCompare(zY, zX);
float ay = 0;
// Blurry Y in front of any X
// the sample we look at (Y) is fast enough to affect me (direction not taken into account)
ay += f * cone(Y, X, vY);
// Any Y behind blurry X, estimate background
ay += b * cone(X, Y, vX);
// Simultaneously blurry X and Y
ay += cylinder(Y, X, vY) * cylinder(X, Y, vX) * 2;
// mask out Y that are not part of the moving direction we current process
// ay *= 1 - saturate((length(vY - PixelCombinedVelocity) - length(vY - PixelBackgroundVelocity)) * 0.12f);
ay = saturate(ay);
ColorAccum += float4(cY.rgb, 0) * ay;
ColorAccumSum += ay;
float MaskAlreadyFound = saturate(1 - SecondColorAccum.a);
SecondColorAccum += float4(cY.rgb, 1) * (1 - ay) * MaskAlreadyFound;
}
}
// color content behind motion vector
// float3 SecondColor = SecondColorAccum / (StepCount - ColorAccumSum);
float3 SecondColor = SecondColorAccum .rgb / SecondColorAccum.a;
// camera blurred background with the moving object removed
// float LerpFactor = length(vX) - 1 > length(PixelBackgroundVelocity);
float LerpFactor = saturate((length(PixelBackgroundVelocity) - length(vX)) ); // better
float3 BehindMovingObject = lerp(BackgroundColor, SecondColor, LerpFactor);
OutColor = float4(ColorAccum / ColorAccumSum);
float BackgroundFraction = length(PixelCombinedVelocity) < length(vX) - 1;
float MovingObjectFraction = ColorAccumSum / StepCount;
// take the faster movement (background or non background)
OutColor = lerp(float4(BehindMovingObject, 1), OutColor, MovingObjectFraction);
OutColor.a = 1;
#if MOTIONBLUR_TESTCHART != 1
// compute how much full res should blend in
// works but without softedge (seems to be the issues with half res already)
// comment tyhe next line to see the half res result
OutColor.a = saturate( lerp(length(PixelBackgroundVelocity), length(PixelCombinedVelocity), MovingObjectFraction) * 0.25f);
#endif
#if MOTION_BLUR_EARLY_EXIT
OutColor.a = deBlur;
#endif
// prepare for the following compositing pass
OutColor.rgb *= OutColor.a;
}
// --------------------------------------
#if MOTION_BLUR_EARLY_EXIT
}
#endif
#if MOTION_BLUR_QUALITY == 0
// visualize motion blur
{
float3 AbsWorldPos = View.ViewOrigin.xyz + OffsetWorldPos;
float3 WorldCheckerboard = Compute3DCheckerBoard(AbsWorldPos * 0.02f) * 0.1f + Compute3DCheckerBoard(AbsWorldPos * 0.002f) * 0.3f + Compute3DCheckerBoard(AbsWorldPos * 0.0002f) * 0.6f;
OutColor = float4(lerp(WorldCheckerboard, OutColor.rgb, 0.7f), 1);
float2 NewPixelPosCentered = UV * PostprocessInput0Size.xy;
// -1..1
float2 NormalizedDirection = PixelObjectBlurredVelocity / ScreenPosToPixel.xy * MotionBlurParameters.xy;
// -1..1
float2 ScreenLocalTilePos = frac(NewPixelPosCentered / 16.0f) * 2 * 1.2f - 1;
// -1..1
float2 PerpDirection = float2(ScreenLocalTilePos.y, -ScreenLocalTilePos.x);
float DirectionMask = 1 - saturate(abs(dot(PerpDirection, normalize(NormalizedDirection))) * 6);
float StrengthMask = 1 - saturate((length(PerpDirection) - length(NormalizedDirection)) * 6);
float OrientationMask = saturate(dot(normalize(ScreenLocalTilePos), NormalizedDirection) * 6);
float DiskMask = saturate((length(PerpDirection) - 1.0f) * 6);
bool bSelectorOpaque = PostprocessInput2.SampleLevel(PostprocessInput2Sampler, UV.xy, 0).b > 0.5f;
float3 LineColor = lerp(float3(1,0,0), float3(0,1,0), OrientationMask);
float3 LineMask = DirectionMask * StrengthMask;
float3 TintColor = bSelectorOpaque ? float3(0.2f, 0.2f, 0.6f) : float3(0.5f, 0.5f, 0.5f);
OutColor.rgb = lerp(lerp(WorldCheckerboard, TintColor, 0.5f), LineColor, LineMask);
OutColor.rgb *= lerp(1.0f, 0.9f, DiskMask);
}
#endif
}
void MainRecombinePS(float4 UVAndScreenPos : TEXCOORD0, out float4 OutColor : SV_Target0)
{
float2 UV = UVAndScreenPos.xy;
float3 FullResSceneColor = PostprocessInput0.SampleLevel(PostprocessInput0Sampler, UV, 0).rgb;
float4 MotionBlurOutput = PostprocessInput1.SampleLevel(PostprocessInput1Sampler, UV, 0);
float Mask = MotionBlurOutput.a;
OutColor.rgb = FullResSceneColor * (1 - Mask) + MotionBlurOutput.rgb;
OutColor.a = 0;
}