Skip to content

Commit a1b9e2c

Browse files
committed
add normal-filter UI; add sobel-high/low normal filters; more tests; cleaner tests
1 parent de3618e commit a1b9e2c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1363
-702
lines changed

PixelGraph.CLI/PixelGraph.CLI.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<Version>0.11.0</Version>
4+
<Version>0.11.1</Version>
55
<TargetFramework>netcoreapp3.1</TargetFramework>
66
<AssemblyName>PixelGraph</AssemblyName>
77
<OutputType>Exe</OutputType>

PixelGraph.Common/Extensions/MathEx.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public static bool Equal(this float valueA, float valueB)
1414
return MathF.Abs(valueA - valueB) < float.Epsilon;
1515
}
1616

17+
public static bool Equal(this double valueA, double valueB)
18+
{
19+
return Math.Abs(valueA - valueB) < double.Epsilon;
20+
}
21+
1722
public static void Normalize(ref Vector3 value)
1823
{
1924
float length;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using PixelGraph.Common.Extensions;
2+
using PixelGraph.Common.PixelOperations;
3+
using PixelGraph.Common.Textures;
4+
using SixLabors.ImageSharp;
5+
using SixLabors.ImageSharp.PixelFormats;
6+
using System;
7+
using System.Numerics;
8+
9+
namespace PixelGraph.Common.ImageProcessors
10+
{
11+
internal class NormalBlendProcessor : PixelRowProcessor
12+
{
13+
private readonly Options options;
14+
15+
16+
public NormalBlendProcessor(Options options)
17+
{
18+
this.options = options;
19+
}
20+
21+
protected override void ProcessRow<TP>(in PixelRowContext context, Span<TP> row)
22+
{
23+
var pixelSrc = new Rgba32();
24+
var pixelBlend = new Rgba32();
25+
26+
var blendRow = options.BlendImage.GetPixelRowSpan(context.Y);
27+
28+
for (var x = context.Bounds.Left; x < context.Bounds.Right; x++) {
29+
row[x].ToRgba32(ref pixelSrc);
30+
blendRow[x].ToRgba32(ref pixelBlend);
31+
32+
// Convert Source from Normal to Vector
33+
pixelSrc.GetChannelValueScaledF(ColorChannel.Red, out var normalSrcX);
34+
pixelSrc.GetChannelValueScaledF(ColorChannel.Green, out var normalSrcY);
35+
36+
var vxSrc = Math.Clamp(normalSrcX * 2f - 1f, -1f, 1f);
37+
var vySrc = Math.Clamp(normalSrcY * 2f - 1f, -1f, 1f);
38+
var angleSrcX = MathF.Asin(vxSrc) / MathEx.Deg2Rad;
39+
var angleSrcY = MathF.Asin(vySrc) / MathEx.Deg2Rad;
40+
41+
// Convert Blend from Normal to Vector
42+
pixelBlend.GetChannelValueScaledF(ColorChannel.Red, out var normalBlendX);
43+
pixelBlend.GetChannelValueScaledF(ColorChannel.Green, out var normalBlendY);
44+
45+
var vxBlend = Math.Clamp(normalBlendX * 2f - 1f, -1f, 1f);
46+
var vyBlend = Math.Clamp(normalBlendY * 2f - 1f, -1f, 1f);
47+
var angleBlendX = MathF.Asin(vxBlend) / MathEx.Deg2Rad;
48+
var angleBlendY = MathF.Asin(vyBlend) / MathEx.Deg2Rad;
49+
50+
// TODO: MATH
51+
//var angleFinalX = angleSrcX + angleBlendX;
52+
//var angleFinalY = angleSrcY + angleBlendY;
53+
54+
var blend = options.Blend;
55+
MathEx.Clamp(ref blend, 0f, 1f);
56+
MathEx.Lerp(angleSrcX, angleBlendX, blend, out var angleFinalX);
57+
MathEx.Lerp(angleSrcY, angleBlendY, blend, out var angleFinalY);
58+
59+
// Convert from Vector to Normal
60+
MathEx.Clamp(ref angleFinalX, -90f, 90f);
61+
MathEx.Clamp(ref angleFinalY, -90f, 90f);
62+
63+
var sinX = MathF.Sin(angleFinalX * MathEx.Deg2Rad);
64+
var cosX = MathF.Cos(angleFinalX * MathEx.Deg2Rad);
65+
var sinY = MathF.Sin(angleFinalY * MathEx.Deg2Rad);
66+
var cosY = MathF.Cos(angleFinalY * MathEx.Deg2Rad);
67+
68+
Vector3 v;
69+
v.X = sinX * cosY;
70+
v.Y = sinY * cosX;
71+
v.Z = cosX * cosY;
72+
MathEx.Normalize(ref v);
73+
74+
pixelSrc.SetChannelValueScaledF(ColorChannel.Red, v.X * 0.5f + 0.5f);
75+
pixelSrc.SetChannelValueScaledF(ColorChannel.Green, v.Y * 0.5f + 0.5f);
76+
pixelSrc.SetChannelValueScaledF(ColorChannel.Blue, v.Z);
77+
row[x].FromRgba32(pixelSrc);
78+
}
79+
}
80+
81+
public class Options
82+
{
83+
public Image<Rgb24> BlendImage;
84+
public float Blend = 0f;
85+
}
86+
}
87+
}

PixelGraph.Common/ImageProcessors/NormalMapProcessor.cs

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using PixelGraph.Common.Textures;
44
using SixLabors.ImageSharp;
55
using SixLabors.ImageSharp.PixelFormats;
6+
using System;
67
using System.Numerics;
78

89
namespace PixelGraph.Common.ImageProcessors
@@ -18,20 +19,75 @@ public NormalMapProcessor(Options options)
1819
}
1920

2021
protected override void ProcessPixel(ref Rgba32 pixel, in PixelContext context)
22+
{
23+
Vector3 normal;
24+
switch (options.Filter) {
25+
case NormalMapFilters.SobelHigh:
26+
CalculateSobel3HighPass(in context, out normal);
27+
break;
28+
case NormalMapFilters.SobelLow:
29+
CalculateSobel3LowPass(in context, out normal);
30+
break;
31+
case NormalMapFilters.Spline:
32+
CalculateSpline(in context, out normal);
33+
break;
34+
default:
35+
CalculateSobel3(in context, out normal);
36+
break;
37+
}
38+
39+
pixel.SetChannelValueScaledF(ColorChannel.Red, normal.X * 0.5f + 0.5f);
40+
pixel.SetChannelValueScaledF(ColorChannel.Green, normal.Y * 0.5f + 0.5f);
41+
pixel.SetChannelValueScaledF(ColorChannel.Blue, normal.Z);
42+
}
43+
44+
private void CalculateSobel3(in PixelContext context, out Vector3 normal)
2145
{
2246
var k = new float[3, 3];
2347
PopulateKernel_3x3(ref k, in context);
2448
GetSobelDerivative(ref k, out var derivative);
49+
CalculateNormal(in derivative, out normal);
50+
}
2551

26-
Vector3 normal;
27-
normal.X = derivative.X;
28-
normal.Y = derivative.Y;
29-
normal.Z = 1f / options.Strength;
30-
MathEx.Normalize(ref normal);
52+
private void CalculateSobel3HighPass(in PixelContext context, out Vector3 normal)
53+
{
54+
var k = new float[3, 3];
55+
PopulateKernel_3x3(ref k, in context);
56+
57+
ApplyHighPass(ref k[0,0], in k[1,1]);
58+
ApplyHighPass(ref k[1,0], in k[1,1]);
59+
ApplyHighPass(ref k[2,0], in k[1,1]);
60+
ApplyHighPass(ref k[0,1], in k[1,1]);
61+
ApplyHighPass(ref k[2,1], in k[1,1]);
62+
ApplyHighPass(ref k[0,2], in k[1,1]);
63+
ApplyHighPass(ref k[1,2], in k[1,1]);
64+
ApplyHighPass(ref k[2,2], in k[1,1]);
65+
66+
GetSobelDerivative(ref k, out var derivative);
67+
CalculateNormal(in derivative, out normal);
68+
}
69+
70+
private void CalculateSobel3LowPass(in PixelContext context, out Vector3 normal)
71+
{
72+
var k = new float[3, 3];
73+
PopulateKernel_3x3(ref k, in context);
74+
75+
ApplyLowPass(ref k[0,0], in k[1,1]);
76+
ApplyLowPass(ref k[1,0], in k[1,1]);
77+
ApplyLowPass(ref k[2,0], in k[1,1]);
78+
ApplyLowPass(ref k[0,1], in k[1,1]);
79+
ApplyLowPass(ref k[2,1], in k[1,1]);
80+
ApplyLowPass(ref k[0,2], in k[1,1]);
81+
ApplyLowPass(ref k[1,2], in k[1,1]);
82+
ApplyLowPass(ref k[2,2], in k[1,1]);
83+
84+
GetSobelDerivative(ref k, out var derivative);
85+
CalculateNormal(in derivative, out normal);
86+
}
3187

32-
pixel.SetChannelValueScaled(ColorChannel.Red, normal.X * 0.5f + 0.5f);
33-
pixel.SetChannelValueScaled(ColorChannel.Green, normal.Y * 0.5f + 0.5f);
34-
pixel.SetChannelValueScaled(ColorChannel.Blue, normal.Z);
88+
private void CalculateSpline(in PixelContext context, out Vector3 normal)
89+
{
90+
throw new NotImplementedException();
3591
}
3692

3793
private void PopulateKernel_3x3(ref float[,] kernel, in PixelContext context)
@@ -56,6 +112,14 @@ private void PopulateKernel_3x3(ref float[,] kernel, in PixelContext context)
56112
}
57113
}
58114

115+
private void CalculateNormal(in Vector2 derivative, out Vector3 normal)
116+
{
117+
normal.X = derivative.X;
118+
normal.Y = derivative.Y;
119+
normal.Z = 1f / options.Strength;
120+
MathEx.Normalize(ref normal);
121+
}
122+
59123
private static void GetSobelDerivative(ref float[,] kernel, out Vector2 derivative)
60124
{
61125
var topSide = kernel[0, 0] + 2f * kernel[0, 1] + kernel[0, 2];
@@ -67,10 +131,21 @@ private static void GetSobelDerivative(ref float[,] kernel, out Vector2 derivati
67131
derivative.X = topSide - bottomSide;
68132
}
69133

134+
private static void ApplyHighPass(ref float value, in float level)
135+
{
136+
if (value < level) value = level;
137+
}
138+
139+
private static void ApplyLowPass(ref float value, in float level)
140+
{
141+
if (value > level) value = level;
142+
}
143+
70144
public class Options
71145
{
72146
public Image<Rgba32> Source;
73147
public ColorChannel HeightChannel;
148+
public NormalMapFilters Filter;
74149
public float Strength = 1f;
75150
public bool WrapX = true;
76151
public bool WrapY = true;

PixelGraph.Common/ImageProcessors/OverlayProcessor.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ protected override void ProcessRow<TP>(in PixelRowContext context, Span<TP> row)
5757

5858
if (pixelInputRange > 0) {
5959
var valueInputRange = mapping.InputMaxValue - mapping.InputMinValue;
60-
value *= (double)valueInputRange / pixelInputRange;
60+
if (!valueInputRange.Equal(pixelInputRange))
61+
value *= valueInputRange / pixelInputRange;
6162
}
6263

6364
value += mapping.InputMinValue;
@@ -68,7 +69,11 @@ protected override void ProcessRow<TP>(in PixelRowContext context, Span<TP> row)
6869
}
6970

7071
// Common Processing
71-
value = (value + mapping.Shift) * mapping.Scale;
72+
if (!mapping.ValueShift.Equal(0f))
73+
value += mapping.ValueShift;
74+
75+
if (!mapping.ValueScale.Equal(1f))
76+
value *= mapping.ValueScale;
7277

7378
if (mapping.OutputPerceptual) MathEx.LinearToPerceptual(ref value);
7479

@@ -80,8 +85,10 @@ protected override void ProcessRow<TP>(in PixelRowContext context, Span<TP> row)
8085

8186
var valueOut = value - mapping.OutputMinValue;
8287

83-
if (valueRange > float.Epsilon)
84-
valueOut *= (double)pixelRange / valueRange;
88+
if (pixelRange == 0 || valueRange == 0)
89+
valueOut = 0;
90+
else if (!valueRange.Equal(pixelRange))
91+
valueOut *= pixelRange / valueRange;
8592

8693
valueOut += mapping.OutputRangeMin;
8794

PixelGraph.Common/Material/MaterialNormalProperties.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using PixelGraph.Common.ResourcePack;
2+
using PixelGraph.Common.Textures;
23
using YamlDotNet.Serialization;
34

45
namespace PixelGraph.Common.Material
@@ -7,12 +8,14 @@ public class MaterialNormalProperties
78
{
89
public const float DefaultStrength = 1f;
910
public const float DefaultNoise = 0f;
11+
public const NormalMapFilters DefaultFilter = NormalMapFilters.Sobel3;
1012

1113
public string Texture {get; set;}
1214
public decimal? Strength {get; set;}
1315
public decimal? Noise {get; set;}
1416
public decimal? CurveX {get; set;}
1517
public decimal? CurveY {get; set;}
18+
public NormalMapFilters? Filter {get; set;}
1619

1720
public ResourcePackNormalXChannelProperties InputX {get; set;}
1821

PixelGraph.Common/Material/MaterialOcclusionProperties.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ namespace PixelGraph.Common.Material
44
{
55
public class MaterialOcclusionProperties
66
{
7-
public const float DefaultQuality = 0.1f;
7+
public const float DefaultQuality = 0.08f;
88
public const float DefaultZScale = 25f;
9-
public const float DefaultZBias = 1.0f;
10-
public const int DefaultSteps = 32;
9+
public const float DefaultZBias = 0.1f;
10+
public const int DefaultSteps = 16;
1111

1212
public ResourcePackOcclusionChannelProperties Input {get; set;}
1313
public string Texture {get; set;}

PixelGraph.Common/PixelGraph.Common.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<Version>0.11.0</Version>
4+
<Version>0.11.1</Version>
55
<TargetFramework>netcoreapp3.1</TargetFramework>
66
<Platforms>AnyCPU;x64</Platforms>
77
</PropertyGroup>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace PixelGraph.Common.Textures
4+
{
5+
public enum NormalMapFilters
6+
{
7+
[EnumMember(Value = "sobel3")]
8+
Sobel3,
9+
10+
[EnumMember(Value = "sobel-high")]
11+
SobelHigh,
12+
13+
[EnumMember(Value = "sobel-low")]
14+
SobelLow,
15+
16+
[EnumMember(Value = "spline")]
17+
Spline,
18+
}
19+
}

0 commit comments

Comments
 (0)