more toon shader control + dithered shading
parent
a914c586b2
commit
1f10698811
|
@ -8,6 +8,7 @@ namespace Kav
|
|||
EffectParameter gPositionParam;
|
||||
EffectParameter gAlbedoParam;
|
||||
EffectParameter gNormalParam;
|
||||
EffectParameter gMetallicRoughnessParam;
|
||||
|
||||
EffectParameter shadowMapOneParam;
|
||||
EffectParameter shadowMapTwoParam;
|
||||
|
@ -31,6 +32,7 @@ namespace Kav
|
|||
public Texture2D GPosition { get; set; }
|
||||
public Texture2D GAlbedo { get; set; }
|
||||
public Texture2D GNormal { get; set; }
|
||||
public Texture2D GMetallicRoughness { get; set; }
|
||||
|
||||
public Texture2D ShadowMapOne { get; set; }
|
||||
public Texture2D ShadowMapTwo { get; set; }
|
||||
|
@ -62,6 +64,7 @@ namespace Kav
|
|||
gPositionParam.SetValue(GPosition);
|
||||
gAlbedoParam.SetValue(GAlbedo);
|
||||
gNormalParam.SetValue(GNormal);
|
||||
gMetallicRoughnessParam.SetValue(GMetallicRoughness);
|
||||
|
||||
shadowMapOneParam.SetValue(ShadowMapOne);
|
||||
shadowMapTwoParam.SetValue(ShadowMapTwo);
|
||||
|
@ -88,6 +91,7 @@ namespace Kav
|
|||
gPositionParam = Parameters["gPosition"];
|
||||
gAlbedoParam = Parameters["gAlbedo"];
|
||||
gNormalParam = Parameters["gNormal"];
|
||||
gMetallicRoughnessParam = Parameters["gMetallicRoughness"];
|
||||
|
||||
shadowMapOneParam = Parameters["shadowMapOne"];
|
||||
shadowMapTwoParam = Parameters["shadowMapTwo"];
|
||||
|
|
BIN
Effects/FXB/Deferred_ToonEffect.fxb (Stored with Git LFS)
BIN
Effects/FXB/Deferred_ToonEffect.fxb (Stored with Git LFS)
Binary file not shown.
|
@ -0,0 +1,34 @@
|
|||
float3 HUEtoRGB(in float H)
|
||||
{
|
||||
float R = abs(H * 6 - 3) - 1;
|
||||
float G = 2 - abs(H * 6 - 2);
|
||||
float B = 2 - abs(H * 6 - 4);
|
||||
return saturate(float3(R,G,B));
|
||||
}
|
||||
|
||||
float Epsilon = 1e-10;
|
||||
|
||||
float3 RGBtoHCV(in float3 RGB)
|
||||
{
|
||||
// Based on work by Sam Hocevar and Emil Persson
|
||||
float4 P = (RGB.g < RGB.b) ? float4(RGB.bg, -1.0, 2.0/3.0) : float4(RGB.gb, 0.0, -1.0/3.0);
|
||||
float4 Q = (RGB.r < P.x) ? float4(P.xyw, RGB.r) : float4(RGB.r, P.yzx);
|
||||
float C = Q.x - min(Q.w, Q.y);
|
||||
float H = abs((Q.w - Q.y) / (6 * C + Epsilon) + Q.z);
|
||||
return float3(H, C, Q.x);
|
||||
}
|
||||
|
||||
float3 RGBtoHSL(float3 RGB)
|
||||
{
|
||||
float3 HCV = RGBtoHCV(RGB);
|
||||
float L = HCV.z - HCV.y * 0.5;
|
||||
float S = HCV.y / (1 - abs(L * 2 - 1) + Epsilon);
|
||||
return float3(HCV.x, S, L);
|
||||
}
|
||||
|
||||
float3 HSLtoRGB(float3 HSL)
|
||||
{
|
||||
float3 RGB = HUEtoRGB(HSL.x);
|
||||
float C = (1 - abs(2 * HSL.z - 1)) * HSL.y;
|
||||
return (RGB - 0.5) * C + HSL.z;
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
#include "Macros.fxh"
|
||||
#include "Shadow.fxh"
|
||||
#include "Dither.fxh"
|
||||
|
||||
static const int NUM_SHADOW_CASCADES = 4;
|
||||
|
||||
DECLARE_TEXTURE(gPosition, 0);
|
||||
DECLARE_TEXTURE(gAlbedo, 1);
|
||||
DECLARE_TEXTURE(gNormal, 2);
|
||||
DECLARE_TEXTURE(gMetallicRoughness, 3);
|
||||
DECLARE_TEXTURE(shadowMapOne, 4);
|
||||
DECLARE_TEXTURE(shadowMapTwo, 5);
|
||||
DECLARE_TEXTURE(shadowMapThree, 6);
|
||||
|
@ -88,11 +90,9 @@ float ComputeShadow(float3 positionWorldSpace, float3 N, float3 L)
|
|||
lightSpaceMatrix = LightSpaceMatrixFour;
|
||||
}
|
||||
|
||||
// PCF + Poisson soft shadows
|
||||
|
||||
if (shadowCascadeIndex == 0)
|
||||
{
|
||||
return PoissonShadow(
|
||||
return HardShadow(
|
||||
positionWorldSpace,
|
||||
N,
|
||||
L,
|
||||
|
@ -103,7 +103,7 @@ float ComputeShadow(float3 positionWorldSpace, float3 N, float3 L)
|
|||
}
|
||||
else if (shadowCascadeIndex == 1)
|
||||
{
|
||||
return PoissonShadow(
|
||||
return HardShadow(
|
||||
positionWorldSpace,
|
||||
N,
|
||||
L,
|
||||
|
@ -114,7 +114,7 @@ float ComputeShadow(float3 positionWorldSpace, float3 N, float3 L)
|
|||
}
|
||||
else if (shadowCascadeIndex == 2)
|
||||
{
|
||||
return PoissonShadow(
|
||||
return HardShadow(
|
||||
positionWorldSpace,
|
||||
N,
|
||||
L,
|
||||
|
@ -125,7 +125,7 @@ float ComputeShadow(float3 positionWorldSpace, float3 N, float3 L)
|
|||
}
|
||||
else
|
||||
{
|
||||
return PoissonShadow(
|
||||
return HardShadow(
|
||||
positionWorldSpace,
|
||||
N,
|
||||
L,
|
||||
|
@ -138,30 +138,43 @@ float ComputeShadow(float3 positionWorldSpace, float3 N, float3 L)
|
|||
|
||||
float IntensityBanding(float NdotL)
|
||||
{
|
||||
if (NdotL > 0.5)
|
||||
// if (NdotL > 0.5)
|
||||
// {
|
||||
// return 1.0;
|
||||
// }
|
||||
// else if (NdotL > 0.25)
|
||||
// {
|
||||
// return 0.5;
|
||||
// }
|
||||
// else if (NdotL > 0.0)
|
||||
// {
|
||||
// return 0.25;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return 0.0;
|
||||
// }
|
||||
if (NdotL > 0)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
else if (NdotL > 0.25)
|
||||
{
|
||||
return 0.5;
|
||||
}
|
||||
else if (NdotL > 0.0)
|
||||
{
|
||||
return 0.25;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.0;
|
||||
return 0.25;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: organize this
|
||||
float4 main_ps(PixelInput input) : SV_TARGET0
|
||||
{
|
||||
float2 screenPosition = input.Position.xy;
|
||||
float3 worldPosition = SAMPLE_TEXTURE(gPosition, input.TexCoord).rgb;
|
||||
float3 normal = SAMPLE_TEXTURE(gNormal, input.TexCoord).xyz;
|
||||
float3 albedo = SAMPLE_TEXTURE(gAlbedo, input.TexCoord).rgb;
|
||||
float2 metallicRoughness = SAMPLE_TEXTURE(gMetallicRoughness, input.TexCoord).rg;
|
||||
|
||||
// the lower the glossiness, the sharper the specular highlight
|
||||
float glossiness = lerp(64, 16, 1.0 - metallicRoughness.r);
|
||||
|
||||
float3 V = normalize(EyePosition - worldPosition);
|
||||
float3 L = normalize(DirectionalLightDirection);
|
||||
|
@ -172,23 +185,36 @@ float4 main_ps(PixelInput input) : SV_TARGET0
|
|||
float NdotH = max(dot(N, H), 0.0);
|
||||
|
||||
float lightIntensity = IntensityBanding(NdotL);
|
||||
float3 light = lightIntensity * DirectionalLightColor;
|
||||
//float3 light = lightIntensity * DirectionalLightColor;
|
||||
float3 light = DirectionalLightColor;
|
||||
|
||||
float specularIntensity = pow(NdotH * lightIntensity, 32 * 32);
|
||||
if (lightIntensity < 1)
|
||||
{
|
||||
light *= dither(lightIntensity, screenPosition);
|
||||
}
|
||||
|
||||
float specularIntensity = pow(NdotH * lightIntensity, glossiness * glossiness);
|
||||
float specularSmooth = smoothstep(0.005, 0.01, specularIntensity);
|
||||
|
||||
float3 specular = specularSmooth * float3(1.0, 1.0, 1.0);
|
||||
|
||||
float rimColor = float3(1.0, 1.0, 1.0);
|
||||
if (metallicRoughness.r == 0.0) { specular = float3(0.0, 0.0, 0.0); }
|
||||
|
||||
float3 rimColor = float3(1.0, 1.0, 1.0);
|
||||
float rimThreshold = 0.1;
|
||||
float rimAmount = 0.76;
|
||||
float rimAmount = 1 - metallicRoughness.g;
|
||||
float rimDot = 1 - dot(V, N);
|
||||
float rimIntensity = rimDot * pow(max(NdotL, 0.0), rimThreshold);
|
||||
rimIntensity = smoothstep(rimAmount - 0.01, rimAmount + 0.01, rimIntensity);
|
||||
float3 rim = rimIntensity * rimColor;
|
||||
|
||||
float shadow = ComputeShadow(worldPosition, N, L);
|
||||
float3 color = albedo * (light + specular + rim) * shadow;
|
||||
float3 color = albedo * (light + specular + rim); // * shadow;
|
||||
|
||||
if (shadow < 1)
|
||||
{
|
||||
color *= dither(shadow, screenPosition);
|
||||
}
|
||||
|
||||
return float4(color, 1.0);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
#include "Conversion.fxh"
|
||||
|
||||
// technique from http://alex-charlton.com/posts/Dithering_on_the_GPU/
|
||||
|
||||
uniform float3 palette[8];
|
||||
static const int paletteSize = 8;
|
||||
|
||||
static const int indexMatrix4x4[16] =
|
||||
{
|
||||
0, 8, 2, 10,
|
||||
12, 4, 14, 6,
|
||||
3, 11, 1, 9,
|
||||
15, 7, 13, 5
|
||||
};
|
||||
|
||||
float indexValue(float2 screenCoords)
|
||||
{
|
||||
int x = int(screenCoords.x % 4);
|
||||
int y = int(screenCoords.y % 4);
|
||||
|
||||
return indexMatrix4x4[(x + y * 4)] / 16.0;
|
||||
}
|
||||
|
||||
float hueDistance(float h1, float h2)
|
||||
{
|
||||
float diff = abs(h1 - h2);
|
||||
return min(abs(1.0 - diff), diff);
|
||||
}
|
||||
|
||||
void closestColors(float hue, out float3 ret[2])
|
||||
{
|
||||
float3 closest = float3(-2, 0, 0);
|
||||
float3 secondClosest = float3(-2, 0, 0);
|
||||
float3 temp;
|
||||
|
||||
for (int i = 0; i < paletteSize; i++)
|
||||
{
|
||||
temp = palette[i];
|
||||
float tempDistance = hueDistance(temp.x, hue);
|
||||
if (tempDistance < hueDistance(closest.x, hue))
|
||||
{
|
||||
secondClosest = closest;
|
||||
closest = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tempDistance < hueDistance(secondClosest.x, hue))
|
||||
{
|
||||
secondClosest = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret[0] = closest;
|
||||
ret[1] = secondClosest;
|
||||
}
|
||||
|
||||
float3 dither(float3 color, float2 screenCoords)
|
||||
{
|
||||
float3 colors[2];
|
||||
float3 hsl = RGBtoHSL(color);
|
||||
closestColors(hsl.x, colors);
|
||||
float3 closestColor = colors[0];
|
||||
float3 secondClosestColor = colors[1];
|
||||
float d = indexValue(screenCoords);
|
||||
float hueDiff = hueDistance(hsl.x, closestColor.x) / hueDistance(secondClosestColor.x, secondClosestColor.x);
|
||||
return HSLtoRGB(hueDiff < d ? closestColor : secondClosestColor);
|
||||
}
|
||||
|
||||
// brightColor refers to undithered max color
|
||||
// float3 dither(float3 color, float3 brightColor, float2 screenCoords)
|
||||
// {
|
||||
// float brightHue = RGBtoHSL(brightColor.x);
|
||||
// float colorHue = RGBtoHSL(color.x);
|
||||
// float halfDistance = hueDistance(0.0, brightHue) / 2.0;
|
||||
// float3 closestColor = (colorHue < halfDistance) ? float3(0.0, 0.0, 0.0) : brightColor;
|
||||
// float3 secondClosestColor = brightColor - closestColor;
|
||||
// float d = indexValue(screenCoords);
|
||||
// float distance = abs(closestColor - color);
|
||||
// return (distance < d) ? closestColor : secondClosestColor;
|
||||
// }
|
||||
|
||||
float3 dither(float color, float2 screenCoords) {
|
||||
float closestColor = (color < 0.5) ? 0 : 1;
|
||||
float secondClosestColor = 1 - closestColor;
|
||||
float d = indexValue(screenCoords);
|
||||
float distance = abs(closestColor - color);
|
||||
return (distance < d) ? closestColor : secondClosestColor;
|
||||
}
|
|
@ -85,3 +85,38 @@ float PoissonShadow(
|
|||
|
||||
return visibility;
|
||||
}
|
||||
|
||||
float HardShadow(
|
||||
float3 positionWorldSpace,
|
||||
float3 N,
|
||||
float3 L,
|
||||
float4x4 lightSpaceMatrix,
|
||||
sampler shadowMap,
|
||||
int shadowMapSize
|
||||
) {
|
||||
// float bias = 0.005 * tan(acos(dot(N, L)));
|
||||
// bias = clamp(bias, 0, 0.01);
|
||||
|
||||
float bias = max(0.05 * (1.0 - dot(N, L)), 0.005);
|
||||
|
||||
float4 positionLightSpace = mul(float4(positionWorldSpace, 1.0), lightSpaceMatrix);
|
||||
|
||||
// maps to [-1, 1]
|
||||
float3 projectionCoords = positionLightSpace.xyz / positionLightSpace.w;
|
||||
|
||||
// maps to [0, 1]
|
||||
// NOTE: In XNA, clip space z is [0, 1] range
|
||||
projectionCoords.x = (projectionCoords.x * 0.5) + 0.5;
|
||||
projectionCoords.y = (projectionCoords.y * 0.5) + 0.5;
|
||||
projectionCoords.y *= -1;
|
||||
|
||||
if (projectionCoords.z > 1.0)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float closestDepth = tex2D(shadowMap, projectionCoords.xy);
|
||||
float currentDepth = projectionCoords.z;
|
||||
|
||||
return (currentDepth - bias > closestDepth ? 0.25 : 1.0);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,8 @@ namespace Kav
|
|||
{
|
||||
public Mesh[] Meshes { get; }
|
||||
|
||||
private Color albedoValue;
|
||||
|
||||
public Color Albedo
|
||||
{
|
||||
get { return albedoValue; }
|
||||
set
|
||||
{
|
||||
foreach (var mesh in Meshes)
|
||||
|
@ -23,6 +20,34 @@ namespace Kav
|
|||
}
|
||||
}
|
||||
|
||||
public float Metallic
|
||||
{
|
||||
set
|
||||
{
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
foreach (var meshPart in mesh.MeshParts)
|
||||
{
|
||||
meshPart.Metallic = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float Roughness
|
||||
{
|
||||
set
|
||||
{
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
foreach (var meshPart in mesh.MeshParts)
|
||||
{
|
||||
meshPart.Roughness = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Model(Mesh[] meshes)
|
||||
{
|
||||
Meshes = meshes;
|
||||
|
|
|
@ -346,6 +346,7 @@ namespace Kav
|
|||
Deferred_ToonEffect.GPosition = gPosition;
|
||||
Deferred_ToonEffect.GAlbedo = gAlbedo;
|
||||
Deferred_ToonEffect.GNormal = gNormal;
|
||||
Deferred_ToonEffect.GMetallicRoughness = gMetallicRoughness;
|
||||
|
||||
Deferred_ToonEffect.EyePosition = camera.Position;
|
||||
Deferred_ToonEffect.DirectionalLightDirection = directionalLight.Direction;
|
||||
|
|
Loading…
Reference in New Issue