Files
UnrealEngine/Engine/Shaders/Private/MeshPaintPixelShader.usf
2025-05-18 13:04:45 +08:00

250 lines
8.2 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*================================================================================
MeshPaintPixelShader.usf: Mesh texture paint pixel shader
================================================================================*/
#include "Common.ush"
#if MESH_PAINT_USE_PAINTBRUSH
Texture2D s_PaintBrushTexture;
SamplerState s_PaintBrushTextureSampler;
float RotationOffset;
#if MESH_PAINT_ROTATE_TOWARD_DIRECTION
float2 RotationDirection;
#endif
#endif
/** Texture containing a clone of the texture we're currently rendering to. */
Texture2D s_CloneTexture;
SamplerState s_CloneTextureSampler;
float4x4 c_WorldToBrushMatrix;
/** Brush metrics: x = radius, y = falloff range, z = depth, w = depth falloff range */
float4 c_BrushMetrics;
float c_BrushStrength;
float4 c_BrushColor;
float4 c_ChannelFlags;
float c_GenerateMaskFlag;
// @todo MeshPaint: Remove this?
float c_Gamma;
void Main( float4 InPosition : SV_POSITION,
float2 InCloneTextureCoordinates : TEXCOORD0,
float3 InWorldSpaceVertexPosition : TEXCOORD1,
out float4 OutColor : SV_Target0)
{
// First sample the source values from the clone texture
float4 OldColor = Texture2DSample(s_CloneTexture, s_CloneTextureSampler, InCloneTextureCoordinates );
// Brush metrics: x = radius, y = falloff range, z = depth, w = depth falloff range
float BrushRadius = c_BrushMetrics.x;
float BrushRadialFalloffRange = c_BrushMetrics.y;
float BrushDepth = c_BrushMetrics.z;
float BrushDepthFalloffRange = c_BrushMetrics.w;
float4x4 WorldToBrushMatrix = c_WorldToBrushMatrix;
// Project the pixel into the plane of the brush
float3 WorldSpaceVertexPosition = InWorldSpaceVertexPosition;
float3 BrushSpaceVertexPosition = mul(float4( WorldSpaceVertexPosition, 1.0f ), WorldToBrushMatrix).xyz;
float2 BrushSpaceVertexPosition2D = BrushSpaceVertexPosition.xy;
// All paths will compute amount of paint to apply
float PaintAmount = 0;
// Start by using the old color
float4 NewColor = OldColor;
#if MESH_PAINT_USE_FILL_BUCKET
#if MESH_PAINT_USE_PAINTBRUSH
// Flood with a texture selected
float2 LocalTextureCoordinate = InCloneTextureCoordinates;
// Rotate our local coordinate system
float RotationAngleRad = (RotationOffset * (PI / 180.0f));
// Convert from 0->1 to -1->1
LocalTextureCoordinate.xy -= 0.5f;
LocalTextureCoordinate.xy *= 2.0f;
// We need to scale up the texture depending on the angle so we always do a complete fill
// Through geometric calculations we find that the rotation angle will require between 1.0->1.4 times the normal size which changes in the same pattern as a sin wave times twice the period
float LocalRotationFitScale = 1.0f + 0.4f * abs(sin(RotationAngleRad * 2.0f));
LocalTextureCoordinate.xy /= LocalRotationFitScale;
float C = cos(RotationAngleRad);
float S = sin(RotationAngleRad);
float2x2 RotationMatrix = float2x2(
float2( C, S),
float2(-S, C));
LocalTextureCoordinate = mul(RotationMatrix, LocalTextureCoordinate);
// Convert from -1->1 range to 0->1
LocalTextureCoordinate.xy /= 2.0f;
LocalTextureCoordinate.xy += 0.5f;
PaintAmount = 1.0f;
PaintAmount *= c_BrushStrength;
// Determine an alpha blended color which is somewhere between our old color and our new color
float4 AlphaBlendedTextureColor = Texture2DSampleLevel(s_PaintBrushTexture, s_PaintBrushTextureSampler, LocalTextureCoordinate, 0);
AlphaBlendedTextureColor.rgb = lerp(OldColor.rgb, AlphaBlendedTextureColor.rgb, AlphaBlendedTextureColor.a);
// Apply brush color after we have found our alpha blended color
float4 BrushColor = AlphaBlendedTextureColor * c_BrushColor;
NewColor = lerp(OldColor, BrushColor, PaintAmount * c_ChannelFlags);
#else
// Simple flood
PaintAmount = 1.0f;
PaintAmount *= c_BrushStrength;
float4 BrushColor = c_BrushColor;
NewColor = lerp(OldColor, BrushColor, PaintAmount * c_ChannelFlags);
#endif
#elif MESH_PAINT_USE_PAINTBRUSH
// Calculate local UV position of the paint brush
float2 LocalTextureCoordinate = BrushSpaceVertexPosition2D;
// Rotate our local coordinate system
float RotationAngle = (RotationOffset * (PI / 180.0f));
#if MESH_PAINT_ROTATE_TOWARD_DIRECTION
RotationAngle += atan2(RotationDirection.y, RotationDirection.x);
#endif
float C = cos(RotationAngle);
float S = sin(RotationAngle);
float2x2 RotationMatrix = float2x2(
float2( C, S),
float2(-S, C));
LocalTextureCoordinate.xy /= BrushRadius;
LocalTextureCoordinate = mul(RotationMatrix, LocalTextureCoordinate);
// Convert from -1->1 range to 0->1
LocalTextureCoordinate.xy /= 2.0f;
LocalTextureCoordinate.xy += 0.5f;
// Only allow values between 0->1
if (LocalTextureCoordinate.x >= 0.0f && LocalTextureCoordinate.x <= 1.0f && LocalTextureCoordinate.y >= 0.0f && LocalTextureCoordinate.y <= 1.0f)
{
// OK the vertex is overlapping the brush in 2D space, but is it too close or
// two far (depth wise) to be influenced?
float VertexDepthToBrush = abs( BrushSpaceVertexPosition.z );
if( VertexDepthToBrush <= BrushDepth )
{
// Compute amount of paint to apply
PaintAmount = 1.0f;
// For distance falloff, keep it a square instead of a circle
float DistanceToVertex2D = max(abs(BrushSpaceVertexPosition2D.x), abs(BrushSpaceVertexPosition2D.y));
{
// Compute the actual distance
float InnerBrushRadius = BrushRadius - BrushRadialFalloffRange;
if( DistanceToVertex2D > InnerBrushRadius )
{
float RadialBasedFalloff = ( DistanceToVertex2D - InnerBrushRadius ) / BrushRadialFalloffRange;
PaintAmount *= 1.0f - RadialBasedFalloff;
}
}
// Apply depth-based falloff
{
float InnerBrushDepth = BrushDepth - BrushDepthFalloffRange;
if( VertexDepthToBrush > InnerBrushDepth )
{
float DepthBasedFalloff = ( VertexDepthToBrush - InnerBrushDepth ) / BrushDepthFalloffRange;
PaintAmount *= 1.0f - DepthBasedFalloff;
}
}
PaintAmount = saturate(PaintAmount);
PaintAmount *= c_BrushStrength;
// Determine an alpha blended color which is somewhere between our old color and our new color
float4 AlphaBlendedTextureColor = Texture2DSampleLevel(s_PaintBrushTexture, s_PaintBrushTextureSampler, LocalTextureCoordinate, 0);
AlphaBlendedTextureColor.rgb = lerp(OldColor.rgb, AlphaBlendedTextureColor.rgb, AlphaBlendedTextureColor.a);
// Apply brush color after we have found our alpha blended color
float4 BrushColor = AlphaBlendedTextureColor * c_BrushColor;
NewColor = lerp(OldColor, BrushColor, PaintAmount * c_ChannelFlags);
}
}
#else
// Is the brush close enough to the vertex to paint?
float DistanceToVertex2D = length( BrushSpaceVertexPosition2D );
if( DistanceToVertex2D <= BrushRadius )
{
// OK the vertex is overlapping the brush in 2D space, but is it too close or
// two far (depth wise) to be influenced?
float VertexDepthToBrush = abs( BrushSpaceVertexPosition.z );
if( VertexDepthToBrush <= BrushDepth )
{
// Compute amount of paint to apply
PaintAmount = 1.0f;
// Apply radial-based falloff
{
// Compute the actual distance
float InnerBrushRadius = BrushRadius - BrushRadialFalloffRange;
if( DistanceToVertex2D > InnerBrushRadius )
{
float RadialBasedFalloff = ( DistanceToVertex2D - InnerBrushRadius ) / BrushRadialFalloffRange;
PaintAmount *= 1.0f - RadialBasedFalloff;
}
}
// Apply depth-based falloff
{
float InnerBrushDepth = BrushDepth - BrushDepthFalloffRange;
if( VertexDepthToBrush > InnerBrushDepth )
{
float DepthBasedFalloff = ( VertexDepthToBrush - InnerBrushDepth ) / BrushDepthFalloffRange;
PaintAmount *= 1.0f - DepthBasedFalloff;
}
}
PaintAmount *= c_BrushStrength;
float4 BrushColor = c_BrushColor;
NewColor.a = lerp(OldColor.a, BrushColor.a, PaintAmount * c_ChannelFlags.a);
NewColor.r = lerp(OldColor.r, BrushColor.r, PaintAmount * c_ChannelFlags.r);
NewColor.g = lerp(OldColor.g, BrushColor.g, PaintAmount * c_ChannelFlags.g);
NewColor.b = lerp(OldColor.b, BrushColor.b, PaintAmount * c_ChannelFlags.b);
}
}
#endif
OutColor = NewColor;
// Mask will write out 1 for anywhere painted.
if ( c_GenerateMaskFlag == 1.0 )
{
OutColor.rgb = PaintAmount > 0 ? 1 : 0;
}
// Gamma correct the output color.
if( c_Gamma != 1.0 )
{
OutColor.rgb = pow( saturate( OutColor.rgb ), c_Gamma );
}
}