diff --git a/CHANGELOG.md b/CHANGELOG.md index aaaacc5b8..730fb2a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -383,6 +383,7 @@ * Updated to latest MonoGame version 3.7.1 * Rewrote example project to be cleaner and better demonstrate basic Spine features. * Added mix-and-match example to demonstrate the new Skin API. +* Added normalmap support via `SpineEffectNormalmap` and support for loading multiple texture layers following a suffix-pattern. Please see the example code on how to use them. ## Java * **Breaking changes** diff --git a/spine-xna/example-content/SpineEffectNormalmap.fx b/spine-xna/example-content/SpineEffectNormalmap.fx new file mode 100644 index 000000000..ff86b8bcb --- /dev/null +++ b/spine-xna/example-content/SpineEffectNormalmap.fx @@ -0,0 +1,115 @@ +float4x4 World; +float4x4 View; +float4x4 Projection; + +// Light0 parameters. +// Default values set below, change them via spineEffect.Parameters["Light0_Direction"] and similar. +float3 Light0_Direction = float3(-0.5265408f, -0.5735765f, -0.6275069f); +float3 Light0_Diffuse = float3(1, 1, 1); +float3 Light0_Specular = float3(1, 1, 1); +float Light0_SpecularExponent = 2.0; // also called "shininess", "specular hardness" + +sampler TextureSampler : register(s0); +sampler NormalmapSampler : register(s1); + +// TODO: add effect parameters here. + +float NormalmapIntensity = 1; + +float3 GetNormal(sampler normalmapSampler, float2 uv, float3 worldPos, float3 vertexNormal) +{ + // Reconstruct tangent space TBN matrix + float3 pos_dx = ddx(worldPos); + float3 pos_dy = ddy(worldPos); + float3 tex_dx = float3(ddx(uv), 0.0); + float3 tex_dy = float3(ddy(uv), 0.0); + float divisor = (tex_dx.x * tex_dy.y - tex_dy.x * tex_dx.y); + float3 t = (tex_dy.y * pos_dx - tex_dx.y * pos_dy) / divisor; + + float divisorBinormal = (tex_dy.y * tex_dx.x - tex_dx.y * tex_dy.x); + float3 b = (tex_dx.x * pos_dy - tex_dy.x * pos_dx) / divisorBinormal; + + t = normalize(t - vertexNormal * dot(vertexNormal, t)); + b = normalize(b - vertexNormal * dot(vertexNormal, b)); + float3x3 tbn = float3x3(t, b, vertexNormal); + + float3 n = 2.0 * tex2D(normalmapSampler, uv).rgb - 1.0; +#ifdef INVERT_NORMALMAP_Y + n.y = -n.y; +#endif + n = normalize(mul(n * float3(NormalmapIntensity, NormalmapIntensity, 1.0), tbn)); + return n; +} + +void GetLightContributionBlinnPhong(inout float3 diffuseResult, inout float3 specularResult, + float3 lightDirection, float3 lightDiffuse, float3 lightSpecular, float specularExponent, float3 normal, float3 viewDirection) +{ + diffuseResult += lightDiffuse * max(0.0, dot(normal, -lightDirection)); + half3 halfVector = normalize(-lightDirection + viewDirection); + float nDotH = max(0, dot(normal, halfVector)); + specularResult += lightSpecular * pow(nDotH, specularExponent); +} + +struct VertexShaderInput +{ + float4 Position : POSITION0; + float4 Color : COLOR0; + float4 TextureCoordinate : TEXCOORD0; + float4 Color2 : COLOR1; +}; + +struct VertexShaderOutput +{ + float4 Position : POSITION0; + float4 Color : COLOR0; + float4 TextureCoordinate : TEXCOORD0; + float4 Color2 : COLOR1; + float3 WorldNormal : TEXCOORD1; + float4 WorldPosition : TEXCOORD2; // for tangent reconstruction +}; + +VertexShaderOutput VertexShaderFunction(VertexShaderInput input) +{ + VertexShaderOutput output; + + float4 worldPosition = mul(input.Position, World); + float4 viewPosition = mul(worldPosition, View); + output.Position = mul(viewPosition, Projection); + output.TextureCoordinate = input.TextureCoordinate; + output.Color = input.Color; + output.Color2 = input.Color2; + + output.WorldNormal = mul(transpose(View), float4(0, 0, 1, 0)).xyz; + output.WorldPosition = worldPosition; + return output; +} + +float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0 +{ + float4 texColor = tex2D(TextureSampler, input.TextureCoordinate); + float3 normal = GetNormal(NormalmapSampler, input.TextureCoordinate, input.WorldPosition, input.WorldNormal); + float3 viewDirection = -input.WorldNormal; + + float alpha = texColor.a * input.Color.a; + float4 output; + output.a = alpha; + output.rgb = ((texColor.a - 1.0) * input.Color2.a + 1.0 - texColor.rgb) * input.Color2.rgb + texColor.rgb * input.Color.rgb; + + float3 diffuseLight = float3(0, 0, 0); + float3 specularLight = float3(0, 0, 0); + GetLightContributionBlinnPhong(diffuseLight, specularLight, + Light0_Direction, Light0_Diffuse, Light0_Specular, Light0_SpecularExponent, normal, viewDirection); + output.rgb = diffuseLight * output.rgb + specularLight; + return output; +} + +technique Technique1 +{ + pass Pass1 + { + // TODO: set renderstates here. + + VertexShader = compile vs_3_0 VertexShaderFunction(); + PixelShader = compile ps_3_0 PixelShaderFunction(); + } +} diff --git a/spine-xna/example-content/spine-xna-example-content.contentproj b/spine-xna/example-content/spine-xna-example-content.contentproj index bc2de899e..a9f87d98f 100644 --- a/spine-xna/example-content/spine-xna-example-content.contentproj +++ b/spine-xna/example-content/spine-xna-example-content.contentproj @@ -26,6 +26,20 @@ EffectProcessor + + + SpineEffectNormalmap + EffectImporter + EffectProcessor + + + + + SpineEffectOutline + EffectImporter + EffectProcessor + +