This is what I end up with
but I still get different outline width for the same width value for different textures.
Different outline width
Please note that the derivatives returned by the ddx()
and ddy()
calls are not single float values but float2
vectors. Thus assigning them to a float actually uses just the U coord change in both X and Y directions. This should nevertheless return a useful value though, even if you're ignoring the V coord change. Do you see large differences just across different atlas page textures with different texture resolutions and not when within the same atlas texture (with differently scaled )? Unfortunately I'm not good enough debugging and compensating this code in my head, but you will likely need to fulfil that float xOffset = OutlineWidth * length(derivatives)
in the end in Spine-Outline-Common.cginc
:
float outlineWidthCompensated = OutlineWidth / (OutlineReferenceTexWidth * mainTextureTexelSize.x);
float xOffset = mainTextureTexelSize.x * outlineWidthCompensated;
It's likely easier to add a separate code-branch to (or version of) computeOutlinePixel()
instead of passing a parameter OutlineWidth
which has every pre-multiplication in order to cancel the lines above out.
#ifndef WORLDSPACE_OUTLINE_WIDTH
float outlineWidthCompensated = OutlineWidth / (OutlineReferenceTexWidth * mainTextureTexelSize.x);
float xOffset = mainTextureTexelSize.x * outlineWidthCompensated;
float yOffset = mainTextureTexelSize.y * outlineWidthCompensated;
float xOffsetDiagonal = mainTextureTexelSize.x * outlineWidthCompensated * 0.7;
float yOffsetDiagonal = mainTextureTexelSize.y * outlineWidthCompensated * 0.7;
#else
// perhaps something like the following lines:
..
float2 ddxUV = ddx(i.uv);
float2 ddyUV = ddy(i.uv);
float2 ddu = float2(ddxUV.x, ddyUV.x);
float2 ddv = float2(ddxUV.y, ddyUV.y);
float xOffset = length(ddu) * OutlineWidth;
float yOffset = length(ddv) * OutlineWidth;
float xOffsetDiagonal = xOffset * 0.7;
float yOffsetDiagonal = yOffset * 0.7;
The above is just a guess and untested though, it might be incorrect.
Harald Do you see large differences just across different atlas page textures with different texture resolutions and not when within the same atlas texture (with differently scaled )?
The difference is from the same atlas texture.
I've noticed something, this happens with the code I posted and your solution.
In the preview window I notice that the outline width of both textures is the same, but in the editor they are not.
What can cause this?
I've tried your suggestion, but didn't help. This is the code
float width = _OutlineWidth * _MainTex_TexelSize.x * _OutlineReferenceTexWidth;
float4 texColor = computeOutlinePixel(_MainTex, _MainTex_TexelSize.xy, i.uv, i.vertexColorAlpha,
width, _OutlineReferenceTexWidth, _OutlineMipLevel,
_OutlineSmoothness, _ThresholdEnd, _OutlineOpaqueAlpha, _OutlineColor);
return texColor;
For comparison I've added outline to another skeleton, with all materials having the same width and this is the result. They are using different atlas
sindex I've tried your suggestion, but didn't help. This is the code
float width = _OutlineWidth * _MainTex_TexelSize.x * _OutlineReferenceTexWidth;
Sorry my recommendation was not clear. I meant in addition to what you had beforehand already, the derivatives have to be used of course, I didn't mean to remove them and replace them with mainTextureTexelSize.x * OutlineReferenceTexWidth
. So in combination it would be e.g. float width = _OutlineWidth * length(der) * mainTextureTexelSize.x * OutlineReferenceTexWidth
.
As I mentioned above, it would likely be easier to add the modifications inside the computeOutlinePixel()
function and add an alternative codebranch there which just uses the derivatives as I've suggested in the posting. Have you tried that?
Yes I've done that. This is how it looks like
`float4 computeOutlinePixel(sampler2D mainTexture, float2 mainTextureTexelSize,
float2 uv, float vertexColorAlpha,
float OutlineWidth, float OutlineReferenceTexWidth, float OutlineMipLevel,
float OutlineSmoothness, float ThresholdEnd, float OutlineOpaqueAlpha, float4 OutlineColor) {
float4 texColor = fixed4(0, 0, 0, 0);
#ifndef WORLDSPACE_OUTLINE_WIDTH
float outlineWidthCompensated = OutlineWidth / (OutlineReferenceTexWidth * mainTextureTexelSize.x);
float xOffset = mainTextureTexelSize.x * outlineWidthCompensated;
float yOffset = mainTextureTexelSize.y * outlineWidthCompensated;
float xOffsetDiagonal = mainTextureTexelSize.x * outlineWidthCompensated * 0.7;
float yOffsetDiagonal = mainTextureTexelSize.y * outlineWidthCompensated * 0.7;
#else
float2 ddxUV = ddx(mainTextureTexelSize.uv);
float2 ddyUV = ddy(mainTextureTexelSize.uv);
float2 ddu = float2(ddxUV.x, ddyUV.x);
float2 ddv = float2(ddxUV.y, ddyUV.y);
float xOffset = length(ddu) * OutlineWidth;
float yOffset = length(ddv) * OutlineWidth;
float xOffsetDiagonal = xOffset * 0.7;
float yOffsetDiagonal = yOffset * 0.7;
#endif
float pixelCenter = tex2D(mainTexture, uv).a;
float4 uvCenterWithLod = float4(uv, 0, OutlineMipLevel);
float pixelTop = tex2Dlod(mainTexture, uvCenterWithLod + float4(0, yOffset, 0, 0)).a;
float pixelBottom = tex2Dlod(mainTexture, uvCenterWithLod + float4(0, -yOffset, 0, 0)).a;
float pixelLeft = tex2Dlod(mainTexture, uvCenterWithLod + float4(-xOffset, 0, 0, 0)).a;
float pixelRight = tex2Dlod(mainTexture, uvCenterWithLod + float4(xOffset, 0, 0, 0)).a;
#if _USE8NEIGHBOURHOOD_ON
float numSamples = 8;
float pixelTopLeft = tex2Dlod(mainTexture, uvCenterWithLod + float4(-xOffsetDiagonal, yOffsetDiagonal, 0, 0)).a;
float pixelTopRight = tex2Dlod(mainTexture, uvCenterWithLod + float4(xOffsetDiagonal, yOffsetDiagonal, 0, 0)).a;
float pixelBottomLeft = tex2Dlod(mainTexture, uvCenterWithLod + float4(-xOffsetDiagonal, -yOffsetDiagonal, 0, 0)).a;
float pixelBottomRight = tex2Dlod(mainTexture, uvCenterWithLod + float4(xOffsetDiagonal, -yOffsetDiagonal, 0, 0)).a;
float average = (pixelTop + pixelBottom + pixelLeft + pixelRight +
pixelTopLeft + pixelTopRight + pixelBottomLeft + pixelBottomRight)
* vertexColorAlpha / numSamples;
#else // 4 neighbourhood
float numSamples = 4;
float average = (pixelTop + pixelBottom + pixelLeft + pixelRight) * vertexColorAlpha / numSamples;
#endif
float thresholdStart = ThresholdEnd * (1.0 - OutlineSmoothness);
float outlineAlpha = saturate(saturate((average - thresholdStart) / (ThresholdEnd - thresholdStart)) - pixelCenter);
outlineAlpha = pixelCenter > OutlineOpaqueAlpha ? 0 : outlineAlpha;
return lerp(texColor, OutlineColor, outlineAlpha);
}`
@sindex Oh you did, you never mentioned that above.
Did you add a #define WORLDSPACE_OUTLINE_WIDTH
in your main shader then? Otherwise that codebranch will not be executed of course.
Also please note that in this forum you can use triple-backticks ``` before and after a code-block for multi-line code.
Harald Oh you did, you never mentioned that above.
I meant it with this line,
sindex I've noticed something, this happens with the code I posted and your solution.
I am sorry for not being clear enough.
This is where I've defined the keyword
Properties {
[NoScaleOffset] _MainTex("Main Texture", 2D) = "black" {}
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
[Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
[MaterialToggle(WORLDSPACE_OUTLINE_WIDTH)] _UseWorldSpaceOutline("Use World Space Outline", Float) = 1
// Outline properties are drawn via custom editor.
[HideInInspector] _OutlineWidth("Outline Width", Range(0,16)) = 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 {
// Universal Pipeline tag is required. If Universal render pipeline is not set in the graphics settings
// this Subshader will fail.
Tags { "RenderPipeline" = "UniversalPipeline" "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
LOD 100
Cull Off
ZWrite Off
Blend One OneMinusSrcAlpha
Stencil {
Ref[_StencilRef]
Comp[_StencilComp]
Pass Keep
}
Pass {
Name "Outline"
HLSLPROGRAM
// Required to compile gles 2.0 with standard srp library
#pragma prefer_hlslcc gles
#pragma exclude_renderers d3d11_9x
//--------------------------------------
// GPU Instancing
#pragma multi_compile_instancing
#pragma vertex vertOutline
#pragma fragment fragOutline
#pragma shader_feature _ _USE8NEIGHBOURHOOD_ON
#pragma shader_feature _ WORLDSPACE_OUTLINE_WIDTH_ON
#define USE_URP
#define fixed4 half4
#define fixed3 half3
#define fixed half
#define NO_CUTOFF_PARAM
#include "Packages/com.esotericsoftware.spine.urp-shaders/Shaders/Include/Spine-Input-Outline-URP.hlsl"
#include "Spine-Custom-URP-Outline-Pass.hlsl"
ENDHLSL
}
and how I am using it
float4 computeOutlinePixel(sampler2D mainTexture, float2 mainTextureTexelSize,
float2 uv, float vertexColorAlpha,
float OutlineWidth, float OutlineReferenceTexWidth, float OutlineMipLevel,
float OutlineSmoothness, float ThresholdEnd, float OutlineOpaqueAlpha, float4 OutlineColor) {
float4 texColor = fixed4(0, 0, 0, 0);
#if WORLDSPACE_OUTLINE_WIDTH_ON
float outlineWidthCompensated = OutlineWidth / (OutlineReferenceTexWidth * mainTextureTexelSize.x);
float xOffset = mainTextureTexelSize.x * outlineWidthCompensated;
float yOffset = mainTextureTexelSize.y * outlineWidthCompensated;
float xOffsetDiagonal = mainTextureTexelSize.x * outlineWidthCompensated * 0.7;
float yOffsetDiagonal = mainTextureTexelSize.y * outlineWidthCompensated * 0.7;
#else
float2 ddxUV = ddx(uv);
float2 ddyUV = ddy(uv);
float2 ddu = float2(ddxUV.x, ddyUV.x);
float2 ddv = float2(ddxUV.y, ddyUV.y);
float xOffset = length(ddu) * OutlineWidth * mainTextureTexelSize.x * OutlineReferenceTexWidth;
float yOffset = length(ddv) * OutlineWidth * mainTextureTexelSize.x * OutlineReferenceTexWidth;
float xOffsetDiagonal = xOffset * 0.7;
float yOffsetDiagonal = yOffset * 0.7;
#endif
float pixelCenter = tex2D(mainTexture, uv).a;
float4 uvCenterWithLod = float4(uv, 0, OutlineMipLevel);
float pixelTop = tex2Dlod(mainTexture, uvCenterWithLod + float4(0, yOffset, 0, 0)).a;
float pixelBottom = tex2Dlod(mainTexture, uvCenterWithLod + float4(0, -yOffset, 0, 0)).a;
float pixelLeft = tex2Dlod(mainTexture, uvCenterWithLod + float4(-xOffset, 0, 0, 0)).a;
float pixelRight = tex2Dlod(mainTexture, uvCenterWithLod + float4(xOffset, 0, 0, 0)).a;
#if _USE8NEIGHBOURHOOD_ON
float numSamples = 8;
float pixelTopLeft = tex2Dlod(mainTexture, uvCenterWithLod + float4(-xOffsetDiagonal, yOffsetDiagonal, 0, 0)).a;
float pixelTopRight = tex2Dlod(mainTexture, uvCenterWithLod + float4(xOffsetDiagonal, yOffsetDiagonal, 0, 0)).a;
float pixelBottomLeft = tex2Dlod(mainTexture, uvCenterWithLod + float4(-xOffsetDiagonal, -yOffsetDiagonal, 0, 0)).a;
float pixelBottomRight = tex2Dlod(mainTexture, uvCenterWithLod + float4(xOffsetDiagonal, -yOffsetDiagonal, 0, 0)).a;
float average = (pixelTop + pixelBottom + pixelLeft + pixelRight +
pixelTopLeft + pixelTopRight + pixelBottomLeft + pixelBottomRight)
* vertexColorAlpha / numSamples;
#else // 4 neighbourhood
float numSamples = 4;
float average = (pixelTop + pixelBottom + pixelLeft + pixelRight) * vertexColorAlpha / numSamples;
#endif
float thresholdStart = ThresholdEnd * (1.0 - OutlineSmoothness);
float outlineAlpha = saturate(saturate((average - thresholdStart) / (ThresholdEnd - thresholdStart)) - pixelCenter);
outlineAlpha = pixelCenter > OutlineOpaqueAlpha ? 0 : outlineAlpha;
return lerp(texColor, OutlineColor, outlineAlpha);
}
Harald Also please note that in this forum you can use triple-backticks ``` before and after a code-block for multi-line code.
Ty for letting me know this
- Modificato
@sindex Sorry to hear your isue persists. I've implemented a first test version for just the Spine-Skeleton-Outline.shader
, you can find the modified files attached in the zip file below.
It keeps the outline width constant in screen-space (although semitransparency and smoothness will differ at high-res vs low-res textures).
If the issue still persists on your end, my guess is that your problem is either not enough padding in the atlas textures or polygon attachments which are cutting off the outline too tightly, see the blog post here for details.
@sindex We've just pushed an official update to the 4.2 branch adding an Width in Screen Space
for all outline shaders.
From the changelog:
All Spine Outline shaders, including the URP outline shader, now provide an additional parameter
Width in Screen Space
. Enable it to keep the outline width constant in screen space instead of texture space. Requires more expensive computations, so enable only where necessary. Defaults todisabled
to maintain existing behaviour.
A new spine-unity 4.2 unitypackage and a new 4.2 Spine URP shaders UPM package is available for download:
https://esotericsoftware.com/spine-unity-download
Issue ticket for later reference:
EsotericSoftware/spine-runtimes1615
Thank you for your help and for releasing this feature.
Glad it helped, thanks for getting back to us.