对于导入unity的spine来说,他会对每个spine生成独有的材质,虽然他们使用的是同一个shader,但由于附带独有的贴图,这样在项目使用中会由于材质贴图不同而导致无法合批.
而为什么选用urp,因为在built-in管线中,对于GPU-instancing,即使通过使用图集的方式统一了贴图,也会由于spine组件在渲染时所生成的网格随着动画播放导致即使是同一个spine,每一帧的网格也会不一样,最后由于网格不同而无法通过GPU-instancing合批.
而使用texture数组的方式在GPU-instancing更不可行,因为贴图无法作为GPU-instancing的合法属性,因此只能转换管线为urp考虑使用Dynamic batching或是SRP batching.
在urp管线下使用Dynamic batching条件很简单,首先在配置文件开启
现在开启了动态合批,我们要想办法让所有的spine使用同一个贴图,不然他们会单独绘制,如下图所示:
这样是不行的,必须让他们使用同一张贴图,这里解决思路是把用到的贴图打包成图集,当运行时让shader去根据设定的uv采样对应区域以获得原本的贴图.
这里用一个脚本计算对应的贴图在shader中的位置,一共使用了三个变量去确定采样,下方为计算思路。
对于已知要采样的uv1点(u1,v1) 可以计算出这个点相对于整个texture左下角的位置(x1,y1) [需传入整张texture的宽高] 根据这个已知要采样的uv1点(u1,v1) 可以计算出换算后的点相对于精灵矩形左下角的位置(x2,y2) [需传入精灵texture的宽高] 再加上精灵矩形左下角的坐标,计算出换算点的位置(x3,y3) [需传入精灵texture左下角相对于整个texture的位置坐标] 根据换算点的位置(x3,y3) 可以计算出换算点在整个texture的uv
可知要传入图集的宽高,图集中对应精灵的宽高,以及这张精灵的左下角相对于图集左下角的位置,即可把要采样的uv映射到既定的精灵范围内。而这里是改造的spine-shader:“CustomURP/SpineTestShader”,它是基于“Spine/Skeleton Fill”改造,移除了不需要的功能,这里贴出来Spine/Skeleton Fill。
// - Unlit
// - Premultiplied Alpha Blending (Optional straight alpha input)
// - Double-sided, no depth
Shader "Spine/Skeleton Fill" {
Properties {
_FillColor ("FillColor", Color) = (1,1,1,1)
_FillPhase ("FillPhase", Range(0, 1)) = 0
[NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
// Outline properties are drawn via custom editor.
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
[HideInInspector] _OutlineOpaqueAlpha("Opaque Alpha", Range(0,1)) = 1.0
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
}
SubShader {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
Blend One OneMinusSrcAlpha
Cull Off
ZWrite Off
Lighting Off
Stencil {
Ref[_StencilRef]
Comp[_StencilComp]
Pass Keep
}
Pass {
Name "Normal"
CGPROGRAM
#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "CGIncludes/Spine-Common.cginc"
sampler2D _MainTex;
float4 _FillColor;
float _FillPhase;
struct VertexInput {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.uv = v.uv;
o.vertexColor = PMAGammaToTargetSpace(v.vertexColor);
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag (VertexOutput i) : SV_Target {
float4 rawColor = tex2D(_MainTex,i.uv);
float finalAlpha = (rawColor.a * i.vertexColor.a);
#if defined(_STRAIGHT_ALPHA_INPUT)
rawColor.rgb *= rawColor.a;
#endif
float3 finalColor = lerp((rawColor.rgb * i.vertexColor.rgb), (_FillColor.rgb * finalAlpha), _FillPhase); // make sure to PMA _FillColor.
return fixed4(finalColor, finalAlpha);
}
ENDCG
}
Pass {
Name "Caster"
Tags { "LightMode"="ShadowCaster" }
Offset 1, 1
ZWrite On
ZTest LEqual
Fog { Mode Off }
Cull Off
Lighting Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed _Cutoff;
struct VertexOutput {
V2F_SHADOW_CASTER;
float4 uvAndAlpha : TEXCOORD1;
};
VertexOutput vert (appdata_base v, float4 vertexColor : COLOR) {
VertexOutput o;
o.uvAndAlpha = v.texcoord;
o.uvAndAlpha.a = vertexColor.a;
TRANSFER_SHADOW_CASTER(o)
return o;
}
float4 frag (VertexOutput i) : SV_Target {
fixed4 texcol = tex2D(_MainTex, i.uvAndAlpha.xy);
clip(texcol.a * i.uvAndAlpha.a - _Cutoff);
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
FallBack "Diffuse"
CustomEditor "SpineShaderWithOutlineGUI"
}
在这里可以先试用GPU-instancing验证数值是否正确,因为上面也说过GPU-instancing由于动画中的网格不一致导致并不能合批,但这里仅用于验证图集uv映射的正确性,下为测试脚本:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Game.Core;
using Spine.Unity;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.PlayerLoop;
using UnityEngine.U2D;
public class TestScript : MonoBehaviour
{
public SpriteAtlas spriteAtlas;
public Material commonMaterial;
public List<OneUnit> oneUnits = new List<OneUnit>();
MaterialPropertyBlock materialPropertyBlock = new();
[Serializable]
public class OneUnit{
public string textureName;
public MeshRenderer meshRenderer;
public MeshFilter meshFilter;
public SkeletonAnimation skeletonAnimation;
}
/*
// var border = sprite.border; // 边界大小 (0,0,0,0)
// var bounds = sprite.bounds; // 边界 center(0,0,0,0) extends(3.38,3.38,0.10) max(3.38,3.38,0.10) min(-3.38,-3.38,-0.10) size(6.76,6.76,0.20)
// var packed = sprite.packed; // 是否被打包到图集中 true
// var packingMode = sprite.packingMode; // 图集打包方式 Rectangle
// var packingRotation = sprite.packingRotation; // 图集打包旋转 None
// var pivot = sprite.pivot; // 在原始纹理矩形中的位置 x:256 y:256
// var pixelsPerUnit = sprite.pixelsPerUnit; // 每单位像素数 75.73965
// var rect = sprite.rect; // 原始纹理上精灵的位置 center(256,256) width:512 height:512
// var spriteAtlasTextureScale = sprite.spriteAtlasTextureScale; // 纹理的变体比例 1
// var textureRect = sprite.textureRect; // 纹理上使用的矩形 center(1110,256) position(854,0) max(1366,512) min(854,0) x:854 y:0 size(512,512)
// var textureRectOffset = sprite.textureRectOffset; // 纹理上使用的矩形偏移量 x:0 y:0
*/
private void Start()
{
Texture2D texture2D = null;
foreach(var oneUnit in oneUnits){
// 目标图片精灵
Sprite sprite = spriteAtlas.GetSprite(oneUnit.textureName);
// 整个图集的texture2D
if(texture2D == null){
texture2D = sprite.texture;
commonMaterial.SetTexture("_MainTex", texture2D);
}
// 对于已知要采样的uv1点(u1,v1) 可以计算出这个点相对于整个texture左下角的位置(x1,y1) [需传入整张texture的宽高]
// 根据这个已知要采样的uv1点(u1,v1) 可以计算出换算后的点相对于精灵矩形左下角的位置(x2,y2) [需传入精灵texture的宽高]
// 再加上精灵矩形左下角的坐标,计算出换算点的位置(x3,y3) [需传入精灵texture左下角相对于整个texture的位置坐标]
// 根据换算点的位置(x3,y3) 可以计算出换算点在整个texture的uv
Vector2 textureWidthAndHeight = new Vector2(texture2D.width, texture2D.height); // 整张texture的宽高
Vector2 spriteTextureWidthAndHeight = new Vector2(sprite.textureRect.width, sprite.textureRect.height); // 精灵texture的宽高
Vector2 spriteTextureLeftDownPos = sprite.textureRect.position; // 精灵texture左下角相对于整个texture的位置坐标
materialPropertyBlock = new();
materialPropertyBlock.SetVector("textureWidthAndHeight", new Vector4(textureWidthAndHeight.x, textureWidthAndHeight.y, 0, 0));
materialPropertyBlock.SetVector("spriteTextureWidthAndHeight", new Vector4(spriteTextureWidthAndHeight.x, spriteTextureWidthAndHeight.y, 0, 0));
materialPropertyBlock.SetVector("spriteTextureLeftDownPos", new Vector4(spriteTextureLeftDownPos.x, spriteTextureLeftDownPos.y, 0, 0));
oneUnit.meshRenderer.SetPropertyBlock(materialPropertyBlock);
commonMaterial.SetTexture("_MainTex", texture2D);
var originalMaterial = oneUnit.skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial;
oneUnit.skeletonAnimation.CustomMaterialOverride[originalMaterial] = commonMaterial; // spine的特殊材质替换,可以去官方文档查看详情
}
}
}
以下为使用GPU-instancing的shader文件
Shader "CustomURP/SpineTestShader" {
Properties {
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
Blend One OneMinusSrcAlpha
Cull Off
ZWrite Off
Lighting Off
Stencil {
Ref 1
Comp Always
Pass Keep
}
Pass {
Name "Normal"
CGPROGRAM
#pragma multi_compile_instancing
#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Spine-Common.cginc"
sampler2D _MainTex;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(vector, textureWidthAndHeight)
UNITY_DEFINE_INSTANCED_PROP(vector, spriteTextureWidthAndHeight)
UNITY_DEFINE_INSTANCED_PROP(vector, spriteTextureLeftDownPos)
UNITY_INSTANCING_BUFFER_END(Props)
// 把要采样的uv转换到图集上对应的uv位置,这样才能正确采样图片
float2 ConvertUVToSamplerUV(float2 uv, float2 textureWidthAndHeight, float2 spriteTextureWidthAndHeight, float2 spriteTextureLeftDownPos){
float2 uv1 = uv;
// 对于已知要采样的uv1点(u1,v1) 可以计算出这个点相对于整个texture左下角的位置(x1,y1) [需传入整张texture的宽高]
float2 uv1PosRefFullTexture = float2(uv1.x * textureWidthAndHeight.x, uv1.y * textureWidthAndHeight.y);
// 根据这个已知要采样的uv1点(u1,v1) 可以计算出换算后的点相对于精灵矩形左下角的位置(x2,y2) [需传入精灵texture的宽高]
float2 uv2PosRefSpriteTexture = float2(uv1.x * spriteTextureWidthAndHeight.x, uv1.y * spriteTextureWidthAndHeight.y);
// 再加上精灵矩形左下角的坐标,计算出换算点的位置 [需传入精灵texture左下角相对于整个texture的位置坐标]
float2 uv2PosRefFullTexture = uv2PosRefSpriteTexture + spriteTextureLeftDownPos;
// 根据换算点的位置 可以计算出换算点在整个texture的uv
float2 uv2 = float2(uv2PosRefFullTexture.x / textureWidthAndHeight.x, uv2PosRefFullTexture.y / textureWidthAndHeight.y);
return uv2;
}
struct VertexInput {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.uv = ConvertUVToSamplerUV(v.uv, UNITY_ACCESS_INSTANCED_PROP(Props, textureWidthAndHeight),
UNITY_ACCESS_INSTANCED_PROP(Props, spriteTextureWidthAndHeight), UNITY_ACCESS_INSTANCED_PROP(Props, spriteTextureLeftDownPos));
o.vertexColor = PMAGammaToTargetSpace(v.vertexColor);
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag (VertexOutput i) : SV_Target {
UNITY_SETUP_INSTANCE_ID(i);
float4 rawColor = tex2D(_MainTex,i.uv);
float finalAlpha = (rawColor.a * i.vertexColor.a);
#if defined(_STRAIGHT_ALPHA_INPUT)
rawColor.rgb *= rawColor.a;
#endif
float3 finalColor = lerp((rawColor.rgb * i.vertexColor.rgb), finalAlpha, 0);
return fixed4(finalColor, finalAlpha);
}
ENDCG
}
}
FallBack "Diffuse"
CustomEditor "SpineShaderWithOutlineGUI"
}
可见已经验证计算成功,所有的spine都是用的一张图集计算自身的uv做采样,贴图显示正常,但由于网格问题无法合批,还是分成了五次绘制。
接下来转为使用SRP合批,关于如何使用SRP合批,在官网上有详细的说明
Unity - Manual: GPU instancing
这里简单说明,就是三个要求:
1:着色器必须在名为UnityPerDraw
的单个常量缓冲区中声明所有内置引擎属性
2:着色器必须在名为UnityPerMaterial
单个常量缓冲区中声明所有材质属性。
3:不能使用MaterialPropertyBlock,他与GPU-instancing冲突且优先级更高。
但是不能使用MaterialPropertyBlock会带来新的问题,不能给每个材质实例的变量单独赋值,就不能通过上面GPU-instancing的方法传入那三个变量去确定采样uv。
解决思路:通过修改spine插件代码,在生成网格时把这三个变量写入到顶点数据中,最后shader从顶点数据获取并计算最终的采样uv。
以下为改造为SRP合批的shader文件
Shader "CustomURP/SpineTestShader" {
Properties {
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
Blend One OneMinusSrcAlpha
Cull Off
ZWrite Off
Lighting Off
Stencil {
Ref 1
Comp Always
Pass Keep
}
Pass {
Name "Normal"
CGPROGRAM
#pragma multi_compile_instancing
#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Spine-Common.cginc"
CBUFFER_START(UnityPerMaterial)
sampler2D _MainTex;
CBUFFER_END
// 把要采样的uv转换到图集上对应的uv位置,这样才能正确采样图片
float2 ConvertUVToSamplerUV(float2 uv, float2 textureWidthAndHeight, float2 spriteTextureWidthAndHeight, float2 spriteTextureLeftDownPos){
float2 uv1 = uv;
// 对于已知要采样的uv1点(u1,v1) 可以计算出这个点相对于整个texture左下角的位置(x1,y1) [需传入整张texture的宽高]
float2 uv1PosRefFullTexture = float2(uv1.x * textureWidthAndHeight.x, uv1.y * textureWidthAndHeight.y);
// 根据这个已知要采样的uv1点(u1,v1) 可以计算出换算后的点相对于精灵矩形左下角的位置(x2,y2) [需传入精灵texture的宽高]
float2 uv2PosRefSpriteTexture = float2(uv1.x * spriteTextureWidthAndHeight.x, uv1.y * spriteTextureWidthAndHeight.y);
// 再加上精灵矩形左下角的坐标,计算出换算点的位置 [需传入精灵texture左下角相对于整个texture的位置坐标]
float2 uv2PosRefFullTexture = uv2PosRefSpriteTexture + spriteTextureLeftDownPos;
// 根据换算点的位置 可以计算出换算点在整个texture的uv
float2 uv2 = float2(uv2PosRefFullTexture.x / textureWidthAndHeight.x, uv2PosRefFullTexture.y / textureWidthAndHeight.y);
return uv2;
}
struct VertexInput {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
float2 textureWidthAndHeight : TEXCOORD4;
float2 spriteTextureWidthAndHeight : TEXCOORD5;
float2 spriteTextureLeftDownPos : TEXCOORD6;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.uv = ConvertUVToSamplerUV(v.uv, v.textureWidthAndHeight, v.spriteTextureWidthAndHeight, v.spriteTextureLeftDownPos);
o.vertexColor = PMAGammaToTargetSpace(v.vertexColor);
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag (VertexOutput i) : SV_Target {
float4 rawColor = tex2D(_MainTex,i.uv);
float finalAlpha = (rawColor.a * i.vertexColor.a);
#if defined(_STRAIGHT_ALPHA_INPUT)
rawColor.rgb *= rawColor.a;
#endif
float3 finalColor = lerp((rawColor.rgb * i.vertexColor.rgb), finalAlpha, 0);
return fixed4(finalColor, finalAlpha);
}
ENDCG
}
}
FallBack "Diffuse"
CustomEditor "SpineShaderWithOutlineGUI"
}
可见顶点数据中多了三个变量用于计算最终采样uv。所有材质属性都声明在既定位置。CBUFFER_START(UnityPerMaterial)
材质属性
CBUFFER_END
而对于spine插件的改造主要集中在MeshGenerator脚本中的FillVertexData方法,这里把改造后的方法贴出:
public void FillVertexData (Mesh mesh, bool isUnit,
Vector2 textureWidthAndHeight = default,// 整张texture的宽高
Vector2 spriteTextureWidthAndHeight = default,// 精灵texture的宽高
Vector2 spriteTextureLeftDownPos = default)// 精灵texture左下角相对于整个texture的位置坐标
{
Vector3[] vbi = vertexBuffer.Items;
Vector2[] ubi = uvBuffer.Items;
Color32[] cbi = colorBuffer.Items;
int vbiLength = vbi.Length;
// Zero the extra.
{
int listCount = vertexBuffer.Count;
Vector3 vector3zero = Vector3.zero;
for (int i = listCount; i < vbiLength; i++)
vbi[i] = vector3zero;
}
// Set the vertex buffer.
{
mesh.vertices = vbi;
mesh.uv = ubi;
mesh.colors32 = cbi;
mesh.bounds = GetMeshBounds();
}
{
if (settings.addNormals) {
int oldLength = 0;
if (normals == null)
normals = new Vector3[vbiLength];
else
oldLength = normals.Length;
if (oldLength != vbiLength) {
Array.Resize(ref this.normals, vbiLength);
Vector3[] localNormals = this.normals;
for (int i = oldLength; i < vbiLength; i++) localNormals[i] = Vector3.back;
}
mesh.normals = this.normals;
}
if (settings.tintBlack) {
if (uv2 != null) {
// Sometimes, the vertex buffer becomes smaller. We need to trim the size of the tint black buffers to match.
if (vbiLength != uv2.Items.Length) {
Array.Resize(ref uv2.Items, vbiLength);
Array.Resize(ref uv3.Items, vbiLength);
uv2.Count = uv3.Count = vbiLength;
}
mesh.uv2 = this.uv2.Items;
mesh.uv3 = this.uv3.Items;
}
}
}
{
// 如果是单位,进行额外合批处理
if(isUnit){
// 保证数组大小
if(textureWidthAndHeights.Length != vbiLength){
textureWidthAndHeights = new Vector2[vbiLength];
}
if(spriteTextureWidthAndHeights.Length != vbiLength){
spriteTextureWidthAndHeights = new Vector2[vbiLength];
}
if(spriteTextureLeftDownPoss.Length != vbiLength){
spriteTextureLeftDownPoss = new Vector2[vbiLength];
}
for(int i = 0; i < vbiLength; i++){
textureWidthAndHeights[i] = textureWidthAndHeight;
spriteTextureWidthAndHeights[i] = spriteTextureWidthAndHeight;
spriteTextureLeftDownPoss[i] = spriteTextureLeftDownPos;
}
mesh.uv5 = textureWidthAndHeights;
mesh.uv6 = spriteTextureWidthAndHeights;
mesh.uv7 = spriteTextureLeftDownPoss;
}
}
}
改动代码为这些:
这三个值传入的位置为uv5,uv6和uv7,但是在shader中则是使用TEXCOORD4,TEXCOORD5,TEXCOORD6接收
float2 textureWidthAndHeight : TEXCOORD4;
float2 spriteTextureWidthAndHeight : TEXCOORD5;
float2 spriteTextureLeftDownPos : TEXCOORD6;
这是因为mesh中没有uv0只有uv1~8,而TEXCOORD对应的则为0~7
接下来是把数值传输给spine让他在生成网格时对应赋值到顶点数据即可。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Game.Core;
using Spine.Unity;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.PlayerLoop;
using UnityEngine.U2D;
public class TestScript : MonoBehaviour
{
public SpriteAtlas spriteAtlas;
public Material commonMaterial;
public List<OneUnit> oneUnits = new List<OneUnit>();
[Serializable]
public class OneUnit{
public string textureName;
public MeshRenderer meshRenderer;
public MeshFilter meshFilter;
public SkeletonAnimation skeletonAnimation;
}
/*
// var border = sprite.border; // 边界大小 (0,0,0,0)
// var bounds = sprite.bounds; // 边界 center(0,0,0,0) extends(3.38,3.38,0.10) max(3.38,3.38,0.10) min(-3.38,-3.38,-0.10) size(6.76,6.76,0.20)
// var packed = sprite.packed; // 是否被打包到图集中 true
// var packingMode = sprite.packingMode; // 图集打包方式 Rectangle
// var packingRotation = sprite.packingRotation; // 图集打包旋转 None
// var pivot = sprite.pivot; // 在原始纹理矩形中的位置 x:256 y:256
// var pixelsPerUnit = sprite.pixelsPerUnit; // 每单位像素数 75.73965
// var rect = sprite.rect; // 原始纹理上精灵的位置 center(256,256) width:512 height:512
// var spriteAtlasTextureScale = sprite.spriteAtlasTextureScale; // 纹理的变体比例 1
// var textureRect = sprite.textureRect; // 纹理上使用的矩形 center(1110,256) position(854,0) max(1366,512) min(854,0) x:854 y:0 size(512,512)
// var textureRectOffset = sprite.textureRectOffset; // 纹理上使用的矩形偏移量 x:0 y:0
*/
private void Start()
{
Texture2D texture2D = null;
foreach(var oneUnit in oneUnits){
// 目标图片精灵
Sprite sprite = spriteAtlas.GetSprite(oneUnit.textureName); // 2002
// 整个图集的texture2D
if(texture2D == null){
texture2D = sprite.texture;
commonMaterial.SetTexture("_MainTex", texture2D); // 贴图无法使用materialPropertyBlock传递
}
// 对于已知要采样的uv1点(u1,v1) 可以计算出这个点相对于整个texture左下角的位置(x1,y1) [需传入整张texture的宽高]
// 根据这个已知要采样的uv1点(u1,v1) 可以计算出换算后的点相对于精灵矩形左下角的位置(x2,y2) [需传入精灵texture的宽高]
// 再加上精灵矩形左下角的坐标,计算出换算点的位置(x3,y3) [需传入精灵texture左下角相对于整个texture的位置坐标]
// 根据换算点的位置(x3,y3) 可以计算出换算点在整个texture的uv
Vector2 textureWidthAndHeight = new Vector2(texture2D.width, texture2D.height); // 整张texture的宽高
Vector2 spriteTextureWidthAndHeight = new Vector2(sprite.textureRect.width, sprite.textureRect.height); // 精灵texture的宽高
Vector2 spriteTextureLeftDownPos = sprite.textureRect.position; // 精灵texture左下角相对于整个texture的位置坐标
var originalMaterial = oneUnit.skeletonAnimation.SkeletonDataAsset.atlasAssets[0].PrimaryMaterial;
oneUnit.skeletonAnimation.CustomMaterialOverride[originalMaterial] = commonMaterial; // to enable the replacement.
commonMaterial.SetTexture("_MainTex", texture2D);
oneUnit.skeletonAnimation.isUnit = true;
oneUnit.skeletonAnimation.textureWidthAndHeight = textureWidthAndHeight;
oneUnit.skeletonAnimation.spriteTextureWidthAndHeight = spriteTextureWidthAndHeight;
oneUnit.skeletonAnimation.spriteTextureLeftDownPos = spriteTextureLeftDownPos;
}
}
}
这样所有的spine就可以合并到一批去了,当然注意在配置中开启SRP合批,就在Dynamic合批的上方。
而想要使用Dynamic合批和很简单,只要声明一个变量在外并在代码中使用即可。可能是不使用会被自动剔除,不会破坏SRP合批。
这样就会破坏SRP合批以Dynamic合批运行。
但是Dynamic合批除了必须要求材质相同以外还有一些限制:
1:Dynamic合批无法应用于包含超过 900 个顶点属性和 300 个顶点的网格
2:多通道着色器的游戏对象无法Dynamic合批
3:旧版延迟渲染则不支持Dynamic合批
而在使用着两个合批方式的时候,要根据具体情况测试,我这里有一些简单的测试参考,Dynamic合批在复杂情况下Batches更少但是SetPass calls可能会更多,而SRP合批在复杂情况下Batches更多但SetPass calls可能会较少,根据普遍认知SetPass calls的耗时会更长但并不是绝对的,具体选用哪种还是要根据实际情况测试。