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
* 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**

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>
</Compile>
</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" />
<!-- 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.

View File

@ -122,18 +122,15 @@
<None Include="data\coin-pro.skel">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</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">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="data\spineboy.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="data\raptor_normals.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="data\tank.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@ -70,8 +70,20 @@ namespace Spine {
}
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["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 = "goblins-pro";
// String name = "raptor-pro";
String name = "raptor-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", "");
if (name == "goblins-pro") atlasName = "goblins-mesh";
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;
if (name == "spineboy-ess") scale = 0.6f;
if (name == "raptor-pro") scale = 0.5f;

View File

@ -147,6 +147,10 @@ namespace Spine {
triangleCount = 0;
lastTexture = item.texture;
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;
@ -182,7 +186,8 @@ namespace Spine {
}
public class MeshItem {
public Texture2D texture;
public Texture2D texture = null;
public Texture2D[] textureLayers = null;
public int vertexCount, triangleCount;
public VertexPositionColorTextureColor[] vertices = { };
public int[] triangles = { };

View File

@ -36,7 +36,7 @@ using System.Text;
namespace Spine {
/// <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()
/// </summary>

View File

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

View File

@ -35,20 +35,60 @@ using Microsoft.Xna.Framework.Graphics;
namespace Spine {
public class XnaTextureLoader : TextureLoader {
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;
if (loadMultipleTextureLayers)
this.textureLayerSuffixes = textureSuffixes;
}
public void Load (AtlasPage page, String path) {
Texture2D texture = Util.LoadTexture(device, path);
page.rendererObject = texture;
page.width = texture.Width;
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) {
((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);
}
}
}