Merge branch '3.8' into 3.9-beta

This commit is contained in:
Harald Csaszar 2020-03-20 19:39:37 +01:00
commit 95e147d898
9 changed files with 217 additions and 20 deletions

View File

@ -383,6 +383,7 @@
* Updated to latest MonoGame version 3.7.1 * Updated to latest MonoGame version 3.7.1
* Rewrote example project to be cleaner and better demonstrate basic Spine features. * Rewrote example project to be cleaner and better demonstrate basic Spine features.
* Added mix-and-match example to demonstrate the new Skin API. * 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 ## Java
* **Breaking changes** * **Breaking changes**

View File

@ -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();
}
}

View File

@ -26,6 +26,20 @@
<Processor>EffectProcessor</Processor> <Processor>EffectProcessor</Processor>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="SpineEffectNormalmap.fx">
<Name>SpineEffectNormalmap</Name>
<Importer>EffectImporter</Importer>
<Processor>EffectProcessor</Processor>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="SpineEffectOutline.fx">
<Name>SpineEffectOutline</Name>
<Importer>EffectImporter</Importer>
<Processor>EffectProcessor</Processor>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\$(XnaFrameworkVersion)\Microsoft.Xna.GameStudio.ContentPipeline.targets" /> <Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\$(XnaFrameworkVersion)\Microsoft.Xna.GameStudio.ContentPipeline.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -122,18 +122,15 @@
<None Include="data\coin-pro.skel"> <None Include="data\coin-pro.skel">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="data\goblins-mesh.atlas">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="data\goblins.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="data\raptor.png"> <None Include="data\raptor.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="data\spineboy.png"> <None Include="data\spineboy.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="data\raptor_normals.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="data\tank.png"> <Content Include="data\tank.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>

View File

@ -70,8 +70,20 @@ namespace Spine {
} }
protected override void LoadContent () { protected override void LoadContent () {
// Two color tint effect, comment line 80 to disable
var spineEffect = Content.Load<Effect>("spine-xna-example-content\\SpineEffect"); bool useNormalmapShader = false;
Effect spineEffect;
if (!useNormalmapShader) {
// Two color tint effect. Note that you can also use the default BasicEffect instead.
spineEffect = Content.Load<Effect>("spine-xna-example-content\\SpineEffect");
}
else {
spineEffect = Content.Load<Effect>("spine-xna-example-content\\SpineEffectNormalmap");
spineEffect.Parameters["Light0_Direction"].SetValue(new Vector3(-0.5265408f, 0.5735765f, -0.6275069f));
spineEffect.Parameters["Light0_Diffuse"].SetValue(new Vector3(1, 0.9607844f, 0.8078432f));
spineEffect.Parameters["Light0_Specular"].SetValue(new Vector3(1, 0.9607844f, 0.8078432f));
spineEffect.Parameters["Light0_SpecularExponent"].SetValue(2.0f);
}
spineEffect.Parameters["World"].SetValue(Matrix.Identity); spineEffect.Parameters["World"].SetValue(Matrix.Identity);
spineEffect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up)); spineEffect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up));
@ -85,15 +97,23 @@ namespace Spine {
// String name = "spineboy-ess"; // String name = "spineboy-ess";
// String name = "goblins-pro"; // String name = "goblins-pro";
// String name = "raptor-pro"; String name = "raptor-pro";
// String name = "tank-pro"; // String name = "tank-pro";
String name = "coin-pro"; //String name = "coin-pro";
if (useNormalmapShader)
name = "raptor-pro"; // we only have normalmaps for raptor
String atlasName = name.Replace("-pro", "").Replace("-ess", ""); String atlasName = name.Replace("-pro", "").Replace("-ess", "");
if (name == "goblins-pro") atlasName = "goblins-mesh"; if (name == "goblins-pro") atlasName = "goblins-mesh";
bool binaryData = false; bool binaryData = false;
Atlas atlas = new Atlas(assetsFolder + atlasName + ".atlas", new XnaTextureLoader(GraphicsDevice)); Atlas atlas;
if (!useNormalmapShader) {
atlas = new Atlas(assetsFolder + atlasName + ".atlas", new XnaTextureLoader(GraphicsDevice));
}
else {
atlas = new Atlas(assetsFolder + atlasName + ".atlas", new XnaTextureLoader(GraphicsDevice,
loadMultipleTextureLayers: true, textureSuffixes: new string[] { "", "_normals" }));
}
float scale = 1; float scale = 1;
if (name == "spineboy-ess") scale = 0.6f; if (name == "spineboy-ess") scale = 0.6f;
if (name == "raptor-pro") scale = 0.5f; if (name == "raptor-pro") scale = 0.5f;

View File

@ -147,6 +147,10 @@ namespace Spine {
triangleCount = 0; triangleCount = 0;
lastTexture = item.texture; lastTexture = item.texture;
device.Textures[0] = lastTexture; device.Textures[0] = lastTexture;
if (item.textureLayers != null) {
for (int layer = 1; layer < item.textureLayers.Length; ++layer)
device.Textures[layer] = item.textureLayers[layer];
}
} }
int[] itemTriangles = item.triangles; int[] itemTriangles = item.triangles;
@ -182,7 +186,8 @@ namespace Spine {
} }
public class MeshItem { public class MeshItem {
public Texture2D texture; public Texture2D texture = null;
public Texture2D[] textureLayers = null;
public int vertexCount, triangleCount; public int vertexCount, triangleCount;
public VertexPositionColorTextureColor[] vertices = { }; public VertexPositionColorTextureColor[] vertices = { };
public int[] triangles = { }; public int[] triangles = { };

View File

@ -36,7 +36,7 @@ using System.Text;
namespace Spine { namespace Spine {
/// <summary> /// <summary>
/// Batch drawing of lines and shapes that can be derrived from lines. /// Batch drawing of lines and shapes that can be derived from lines.
/// ///
/// Call drawing methods in between Begin()/End() /// Call drawing methods in between Begin()/End()
/// </summary> /// </summary>

View File

@ -109,7 +109,7 @@ namespace Spine {
float attachmentZOffset = zSpacing * i; float attachmentZOffset = zSpacing * i;
float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA;
Texture2D texture = null; object textureObject = null;
int verticesCount = 0; int verticesCount = 0;
float[] vertices = this.vertices; float[] vertices = this.vertices;
int indicesCount = 0; int indicesCount = 0;
@ -120,7 +120,7 @@ namespace Spine {
RegionAttachment regionAttachment = (RegionAttachment)attachment; RegionAttachment regionAttachment = (RegionAttachment)attachment;
attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A;
AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject;
texture = (Texture2D)region.page.rendererObject; textureObject = region.page.rendererObject;
verticesCount = 4; verticesCount = 4;
regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2);
indicesCount = 6; indicesCount = 6;
@ -131,7 +131,7 @@ namespace Spine {
MeshAttachment mesh = (MeshAttachment)attachment; MeshAttachment mesh = (MeshAttachment)attachment;
attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A;
AtlasRegion region = (AtlasRegion)mesh.RendererObject; AtlasRegion region = (AtlasRegion)mesh.RendererObject;
texture = (Texture2D)region.page.rendererObject; textureObject = region.page.rendererObject;
int vertexCount = mesh.WorldVerticesLength; int vertexCount = mesh.WorldVerticesLength;
if (vertices.Length < vertexCount) vertices = new float[vertexCount]; if (vertices.Length < vertexCount) vertices = new float[vertexCount];
verticesCount = vertexCount >> 1; verticesCount = vertexCount >> 1;
@ -196,7 +196,12 @@ namespace Spine {
// submit to batch // submit to batch
MeshItem item = batcher.NextItem(verticesCount, indicesCount); MeshItem item = batcher.NextItem(verticesCount, indicesCount);
item.texture = texture; if (textureObject is Texture2D)
item.texture = (Texture2D) textureObject;
else {
item.textureLayers = (Texture2D[]) textureObject;
item.texture = item.textureLayers[0];
}
for (int ii = 0, nn = indicesCount; ii < nn; ii++) { for (int ii = 0, nn = indicesCount; ii < nn; ii++) {
item.triangles[ii] = indices[ii]; item.triangles[ii] = indices[ii];
} }

View File

@ -35,20 +35,60 @@ using Microsoft.Xna.Framework.Graphics;
namespace Spine { namespace Spine {
public class XnaTextureLoader : TextureLoader { public class XnaTextureLoader : TextureLoader {
GraphicsDevice device; GraphicsDevice device;
string[] textureLayerSuffixes = null;
public XnaTextureLoader (GraphicsDevice device) { /// <summary>
/// Constructor.
/// </summary>
/// <param name="device">The graphics device to be used.</param>
/// <param name="loadMultipleTextureLayers">If <c>true</c> multiple textures layers
/// (e.g. a diffuse/albedo texture and a normal map) are loaded instead of a single texture.
/// Names are constructed based on suffixes added according to the <c>textureSuffixes</c> parameter.</param>
/// <param name="textureSuffixes">If <c>loadMultipleTextureLayers</c> is <c>true</c>, the strings of this array
/// define the path name suffix of each layer to be loaded. Array size must be equal to the number of layers to be loaded.
/// The first array entry is the suffix to be <c>replaced</c> (e.g. "_albedo", or "" for a first layer without a suffix),
/// subsequent array entries contain the suffix to replace the first entry with (e.g. "_normals").
///
/// An example would be:
/// <code>new string[] { "", "_normals" }</code> for loading a base diffuse texture named "skeletonname.png" and
/// a normalmap named "skeletonname_normals.png".</param>
public XnaTextureLoader (GraphicsDevice device, bool loadMultipleTextureLayers = false, string[] textureSuffixes = null) {
this.device = device; this.device = device;
if (loadMultipleTextureLayers)
this.textureLayerSuffixes = textureSuffixes;
} }
public void Load (AtlasPage page, String path) { public void Load (AtlasPage page, String path) {
Texture2D texture = Util.LoadTexture(device, path); Texture2D texture = Util.LoadTexture(device, path);
page.rendererObject = texture;
page.width = texture.Width; page.width = texture.Width;
page.height = texture.Height; page.height = texture.Height;
if (textureLayerSuffixes == null) {
page.rendererObject = texture;
}
else {
Texture2D[] textureLayersArray = new Texture2D[textureLayerSuffixes.Length];
textureLayersArray[0] = texture;
for (int layer = 1; layer < textureLayersArray.Length; ++layer) {
string layerPath = GetLayerName(path, textureLayerSuffixes[0], textureLayerSuffixes[layer]);
textureLayersArray[layer] = Util.LoadTexture(device, layerPath);
}
page.rendererObject = textureLayersArray;
}
} }
public void Unload (Object texture) { public void Unload (Object texture) {
((Texture2D)texture).Dispose(); ((Texture2D)texture).Dispose();
} }
private string GetLayerName (string firstLayerPath, string firstLayerSuffix, string replacementSuffix) {
int suffixLocation = firstLayerPath.LastIndexOf(firstLayerSuffix + ".");
if (suffixLocation == -1) {
throw new Exception(string.Concat("Error composing texture layer name: first texture layer name '", firstLayerPath,
"' does not contain suffix to be replaced: '", firstLayerSuffix, "'"));
}
return firstLayerPath.Remove(suffixLocation, firstLayerSuffix.Length).Insert(suffixLocation, replacementSuffix);
}
} }
} }