This is what I end up with

but I still get different outline width for the same width value for different textures.

Related Discussions
...

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.

  • sindex ha risposto a questo messaggio

    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?

      sindex The difference is from the same atlas texture.

      You're saying that the difference is visible on the same Atlas Texture, but your screenshot below shows "2 Materials" and thus two atlas textures, which explains the different width.

      • sindex ha risposto a questo messaggio

        @sindex You might want to try just multiplying the width before passing it to the function with mainTextureTexelSize.x * OutlineReferenceTexWidth;

        Harald
        I meant that they are different textures but used by the same atlas

        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;

        • Harald ha risposto a questo messaggio

          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.

          • sindex ha risposto a questo messaggio
          • sindex ha messo mi piace.

            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.

            outline-shader-screenspace-width-v01.zip
            5kB
            6 giorni dopo

            @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 to disabled 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

            4 giorni dopo

            Thank you for your help and for releasing this feature.

            Glad it helped, thanks for getting back to us.