#define CSHADE_DOTS /* This shader creates a stylized dot pattern based on image features, converting the image into a halftone-like representation using circles. It supports monochrome processing, where users can select specific HSV/HSL/HSI channels to influence the dot pattern, or color processing, allowing independent offset and cropping for each RGB channel. The size of the circles is determined by the input color data, and the shader provides extensive controls for dot density, input multipliers and bias, and foreground/background colors. */ #include "shared/cColor.fxh" #include "shared/cMath.fxh" #include "shared/cMacros.fxh" /* Shader Options */ CSHADE_CREATE_INFO( "\n" "\t"CSHADE_UI_PREPROCESSOR("This shader has a colored version (SHADER_RGB_VERSION).")"\n" "\n" ) #ifndef SHADER_RGB_VERSION #define SHADER_RGB_VERSION 0 #endif uniform int _CircleAmount < ui_label = "Dot Density"; ui_max = 128; ui_min = 1; ui_type = "slider"; ui_tooltip = "Sets the number of circles horizontally and vertically across the screen, influencing the density of the dot pattern."; > = 64; #if SHADER_RGB_VERSION uniform float3 _InputContrast < ui_category = "Input"; ui_label = "Input Color Influence"; ui_max = 2.0; ui_min = 0.0; ui_type = "slider"; ui_tooltip = "Adjusts the intensity of the input color data before it's used to determine the size of the circles."; > = 0.25; uniform float2 _RedChannelOffset < ui_category = "Geometry"; ui_label = "Red Channel Offset"; ui_max = 10.0; ui_min = -10.0; ui_step = 0.1; ui_text = "OFFSET (X, Y)"; ui_type = "slider"; ui_tooltip = "Adjusts the horizontal and vertical offset for the red channel's dot pattern."; > = 0.0; uniform float2 _GreenChannelOffset < ui_category = "Geometry"; ui_label = "Green Channel Offset"; ui_max = 10.0; ui_min = -10.0; ui_step = 0.1; ui_type = "slider"; ui_tooltip = "Adjusts the horizontal and vertical offset for the green channel's dot pattern."; > = 0.0; uniform float2 _BlueChannelOffset < ui_category = "Geometry"; ui_label = "Blue"; ui_max = 10.0; ui_min = -10.0; ui_step = 0.1; ui_type = "slider"; ui_tooltip = "Adjusts the horizontal and vertical offset for the blue channel's dot pattern."; > = 0.0; uniform int4 _RedChannelCrop < ui_category = "Geometry"; ui_label = "Red Channel Crop"; ui_max = 10; ui_min = 0; ui_text = "CROP (N, S, E, W)"; ui_type = "slider"; ui_tooltip = "Defines the cropping boundaries (left, right, top, bottom) for the red channel's dot pattern."; > = 0; uniform int4 _GreenChannelCrop < ui_category = "Geometry"; ui_label = "Green Channel Crop"; ui_max = 10; ui_min = 0; ui_type = "slider"; ui_tooltip = "Defines the cropping boundaries (left, right, top, bottom) for the green channel's dot pattern."; > = 0; uniform int4 _BlueChannelCrop < ui_category = "Geometry"; ui_label = "Blue Channel Crop"; ui_max = 10; ui_min = 0; ui_type = "slider"; ui_tooltip = "Defines the cropping boundaries (left, right, top, bottom) for the blue channel's dot pattern."; > = 0; #else uniform int _Select < ui_category = "Input"; ui_items = "HSV: Hue\0HSV: Saturation\0HSV: Value\0HSL: Hue\0HSL: Saturation\0HSL: Lightness\0HSI: Hue\0HSI: Saturation\0HSI: Intensity\0"; ui_label = "Monochrome Feature Selection"; ui_type = "combo"; ui_tooltip = "Determines which color feature (Hue, Saturation, or Value) is used to generate the dot pattern in monochrome mode."; > = 2; uniform float _InputContrast < ui_category = "Input"; ui_label = "Input Color Influence"; ui_max = 2.0; ui_min = 0.0; ui_type = "slider"; ui_tooltip = "Adjusts the intensity of the input color data before it's used to determine the size of the circles."; > = 0.5; uniform float2 _Offset < ui_category = "Geometry"; ui_label = " "; ui_max = 100.0; ui_min = -100.0; ui_text = "OFFSET (X, Y)"; ui_type = "slider"; ui_tooltip = "Adjusts the horizontal and vertical position of the dot pattern on the screen."; > = 0.0; uniform int4 _Crop < ui_category = "Geometry"; ui_label = " "; ui_max = 10; ui_min = 0; ui_text = "CROP (N, S, E, W)"; ui_type = "slider"; ui_tooltip = "Defines the cropping boundaries (left, right, top, bottom) for the dot pattern, effectively trimming the effect from the edges."; > = 0; #endif uniform float3 _FrontColor < ui_category = "Composition"; ui_label = "Dot Color"; ui_max = 1.0; ui_min = 0.0; ui_type = "color"; ui_tooltip = "Sets the color of the dots (foreground) in the pattern."; > = float3(0.0, 0.0, 0.0); uniform float3 _BackColor < ui_category = "Composition"; ui_label = "Background Color"; ui_max = 1.0; ui_min = 0.0; ui_type = "color"; ui_tooltip = "Sets the background color behind the dots in the pattern."; > = float3(1.0, 1.0, 1.0); #define CSHADE_APPLY_AUTO_EXPOSURE 0 #define CSHADE_APPLY_ABBERATION 0 #include "shared/cShade.fxh" uniform int _ShaderPreprocessorGuide < ui_category = "Preprocessor Guide / Shader"; ui_category_closed = false; ui_label = " "; ui_text = "\nSHADER_COLOR_VERSION - Switches to the shader's colored version.\n\n\tOptions: 0 (disabled), 1 (enabled)\n\n"; ui_type = "radio"; > = 0; /* Textures & Samplers */ CSHADE_CREATE_TEXTURE_POOLED(TempTex0_RGBA8_8, CSHADE_BUFFER_SIZE_0, RGBA8, 8) CSHADE_CREATE_SRGB_SAMPLER(SampleTempTex0, TempTex0_RGBA8_8, LINEAR, LINEAR, LINEAR, MIRROR, MIRROR, MIRROR) sampler2D CShade_SampleColorTexMirror { Texture = CShade_ColorTex; MagFilter = LINEAR; MinFilter = LINEAR; MipFilter = LINEAR; AddressU = MIRROR; AddressV = MIRROR; SRGBTexture = CSHADE_READ_SRGB; }; /* Functions */ struct Tile { float2 Value; float2 Index; float2 Frac; }; Tile GetTiles(float2 Tex, float2 Translation) { Tile Output; float2 Tiles = Tex + (Translation * fwidth(Tex)); // Get tiles Output.Value = Tiles * _CircleAmount; // Shift the tiles so they are ~0.5 units apart from each-other Output.Value.x = (CMath_GetModulus_FLT1(trunc(Output.Value.y), 2.0) == 1.0) ? Output.Value.x + 0.25: Output.Value.x - 0.25; // Get tile index Output.Index = floor(Output.Value); // Get fractional variant of tile Output.Frac = frac(Output.Value); return Output; } float2 GetBlockTex(float2 TileIndex) { return TileIndex / _CircleAmount; } float GetCircleLength(Tile Input) { // Create the UV for the circles float2 CircleTiles = CMath_UNORMtoSNORM_FLT2(Input.Frac); // Shrink the UV so [-1, 1] fills a square #if BUFFER_WIDTH > BUFFER_HEIGHT CircleTiles.x *= CSHADE_ASPECT_RATIO; #else CircleTiles.y *= CSHADE_ASPECT_RATIO; #endif // Compute length return length(CircleTiles); } #if SHADER_RGB_VERSION void CropChannel(inout float Channel, in int BackComponent, in Tile ChannelTiles, in float4 CropArgs) { // Crop the image float SrcColor = _BackColor[BackComponent]; Channel = lerp(SrcColor, Channel, ChannelTiles.Value.x > CropArgs.x); Channel = lerp(SrcColor, Channel, ChannelTiles.Value.x < (_CircleAmount - CropArgs.y)); Channel = lerp(SrcColor, Channel, ChannelTiles.Value.y > CropArgs.z * 2.0); Channel = lerp(SrcColor, Channel, ChannelTiles.Value.y < (_CircleAmount - CropArgs.w * 2.0)); } #endif /* Pixel Shaders */ void PS_Blit(CShade_VS2PS_Quad Input, out float4 Output : SV_TARGET0) { Output = tex2D(CShade_SampleColorTex, Input.Tex0); #if !SHADER_RGB_VERSION switch(_Select) { case 0: Output.a = CColor_RGBtoHSV(Output.rgb).r; break; case 1: Output.a = CColor_RGBtoHSV(Output.rgb).g; break; case 2: Output.a = CColor_RGBtoHSV(Output.rgb).b; break; case 3: Output.a = CColor_RGBtoHSL(Output.rgb).r; break; case 4: Output.a = CColor_RGBtoHSL(Output.rgb).g; break; case 5: Output.a = CColor_RGBtoHSL(Output.rgb).b; break; case 6: Output.a = CColor_RGBtoHSI(Output.rgb).r; break; case 7: Output.a = CColor_RGBtoHSI(Output.rgb).g; break; case 8: Output.a = CColor_RGBtoHSI(Output.rgb).b; break; default: Output.a = 1.0; break; } #endif #if SHADER_RGB_VERSION Output = CColor_ApplyLogContrast(Output.rgb, _InputContrast); #else Output.a = CColor_ApplyLogContrast((float3)Output.a, _InputContrast); #endif } void PS_Main(CShade_VS2PS_Quad Input, out float4 Output : SV_TARGET0) { // Precalculate our needed LOD for all channels float2 TexSize = CMath_GetScreenSizeFromTex(Input.Tex0); float LOD = max(0.0, log2(max(TexSize.x, TexSize.y) / _CircleAmount)); // Initialize variables float4 OutputColor = 0.0; float3 CircleDistance = 0.0; float3 CircleMask = 0.0; #if SHADER_RGB_VERSION // Create per-color tiles Tile RedChannel_Tiles = GetTiles(Input.Tex0.xy, _RedChannelOffset); Tile GreenChannel_Tiles = GetTiles(Input.Tex0.xy, _GreenChannelOffset); Tile BlueChannel_Tiles = GetTiles(Input.Tex0.xy, _BlueChannelOffset); // Generate per-color blocks float3 Blocks = 0.0; Blocks.r = tex2Dlod(SampleTempTex0, float4(GetBlockTex(RedChannel_Tiles.Index), 0.0, LOD)).r; Blocks.g = tex2Dlod(SampleTempTex0, float4(GetBlockTex(GreenChannel_Tiles.Index), 0.0, LOD)).g; Blocks.b = tex2Dlod(SampleTempTex0, float4(GetBlockTex(BlueChannel_Tiles.Index), 0.0, LOD)).b; // Generate per-color, circle-shaped lengths of each channel blocks' texture coordinates float3 CircleLengths = 0.0; CircleLengths.r = GetCircleLength(RedChannel_Tiles); CircleLengths.g = GetCircleLength(GreenChannel_Tiles); CircleLengths.b = GetCircleLength(BlueChannel_Tiles); // Compute mask CircleDistance = smoothstep(0.0, 1.0, CircleLengths + Blocks); CircleMask = smoothstep(0.8, 1.0, CircleDistance); // Compute masked color OutputColor = lerp(_FrontColor, _BackColor, CircleMask); // Per-color cropping CropChannel(OutputColor.r, 0, RedChannel_Tiles, _RedChannelCrop); CropChannel(OutputColor.g, 1, GreenChannel_Tiles, _GreenChannelCrop); CropChannel(OutputColor.b, 2, BlueChannel_Tiles, _BlueChannelCrop); #else // Create tiles Tile MainTiles = GetTiles(Input.Tex0.xy, _Offset); // Get texture information float4 Blocks = tex2Dlod(SampleTempTex0, float4(GetBlockTex(MainTiles.Index), 0.0, LOD)); // Compute circle mask (with fade at small sizes) float CircleLength = GetCircleLength(MainTiles); // Compute mask CircleDistance = smoothstep(0.0, 1.0, CircleLength + Blocks.a); CircleMask = smoothstep(0.8, 1.0, CircleDistance); // Compute masked color OutputColor = lerp(_FrontColor, _BackColor, CircleMask); // Crop the image OutputColor = lerp(_BackColor, OutputColor, MainTiles.Value.x > _Crop.x); OutputColor = lerp(_BackColor, OutputColor, MainTiles.Value.x < (_CircleAmount - _Crop.y)); OutputColor = lerp(_BackColor, OutputColor, MainTiles.Value.y > _Crop.z * 2.0); OutputColor = lerp(_BackColor, OutputColor, MainTiles.Value.y < (_CircleAmount - _Crop.w * 2.0)); #endif // RENDER #if defined(CSHADE_BLENDING) Output = float4(OutputColor.rgb, _CShade_AlphaFactor); #else Output = float4(OutputColor.rgb, 1.0); #endif CShade_Render(Output, Input.HPos.xy, Input.Tex0); } technique CShade_Dots < ui_label = "CShade | Dots"; ui_tooltip = "Creates circles based on image features."; > { pass Blit { VertexShader = CShade_VS_Quad; PixelShader = PS_Blit; RenderTarget = TempTex0_RGBA8_8; } pass Dots { SRGBWriteEnable = CSHADE_WRITE_SRGB; CBLEND_CREATE_STATES() VertexShader = CShade_VS_Quad; PixelShader = PS_Main; } }