mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-06 07:14:55 +08:00
Merge branch '3.8' into 3.9-beta
This commit is contained in:
commit
95e147d898
@ -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**
|
||||
|
||||
115
spine-xna/example-content/SpineEffectNormalmap.fx
Normal file
115
spine-xna/example-content/SpineEffectNormalmap.fx
Normal 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();
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 = { };
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user