This is something I wrote for my own model debut, as I knew I won’t be able to afford a custom stinger transition at the time of the debut. Plus I wanted the transition to be a bit out of ordinary - stinger transitions are great, but they do not allow you to modify pixel positions from scene A/B - only fade between A/B. Shaders don’t have that limitation. This initially started with me planning out everythin in Unity’s shader graph to have a better view on each stage of the transition and once I had a general concept, I started translating shader graph to shader language used OBS Shader component. The original transition was suppose to be using sampling from scene A and B, logo and a something similar to a normal texture to produce a much more interesting and complex transition. However mid-project I have run into an issue that seems to be related to a limitation of using only 3 textures that can be sampled. With 4, the results would just break with OBS Shader plugin not providing any error message.

As a workaround, I was planning on looking up how to create hexagons using pixel shaders, however due to the debut being set in stone, I have eventually decided to just simplify it to a simple wave pattern (which I am not exactly happy with).

2025-12-11-TransitionPreview.png If you want, you can always follow me on Twitch or YouTube.

While I won’t provide you with a logo, you are free to use this code with your own logo if you find it interesting:

// Glass-shield transition
// written by SuiMachine / Sui_VT.
// You are free to modify it as you please.
// Credit is not required but appriciated.
//
// There may be a few sections in here that seem like they are overcomplicated but may be intentional
// For example I initially wrote a shader so that it built UVs for a logo and sampleed image_a or image_b
// but then run into an issue where sampling background with normal coorinates would result
// in old data being overriden, so I had to rewrite it in a way where image_a or image_b are sampled only once!

uniform texture2d image_a;
uniform texture2d image_b;
uniform float transition_time = 0.5;
uniform float Aspect_ratio = 1.77777777778;
uniform bool convert_linear = true;

uniform texture2d LogoTexture = "SuiLogo.png";
uniform float2 LogoColorDistortionXY = { 0.2, 0.05 }; //Made it look cooler, though
uniform float LogoColorDistortion_Strength = 0.51;

uniform float3 Logo_Shine_Line = {1, 1, 1};

uniform float Logo_scale = 0;
uniform float2 Logo_positionXY = {0, 0};

uniform float Start_Logo_scale = 10;
uniform float2 Start_Logo_positionXY = {0, 0};

uniform float Distortion_wave_strength = 1;

uniform float phase2Start = 1;
uniform float phase3Start = 1;

float2 GetCenteredUV(float2 uvIn) { return uvIn - float2(0.5, 0.5); }

float2 GetCenteredUVScaled(float2 uvIn, float X_scale)
{
  float2 result = uvIn - float2(0.5, 0.5);
  return result * float2(X_scale, 1.0);
}

float2 RestoreNormalUVCoordinate(float2 uvIn) { return uvIn + float2(0.5, 0.5); }
float2 MirrorUV(float2 uv) { return 1.0 - abs(frac(uv * 0.5) * 2.0 - 1.0); }

//Based on https://realtimevfx.com/t/collection-of-useful-curve-shaping-functions/3704
float SmoothSlowdownCurve(float x) { return 1.0 - pow(abs(x - 1.0), 3.5); }

uniform float4 background_color = {0.0, 0.0, 0.0, 1.0};

void GetTransformedLogo(float2 screenUV, float2 uv, float distortionStrength, out float3 logoPixel, out float2 distortedUV, out float logoAlpha)
{
  float4 sampledLogo = LogoTexture.Sample(textureSampler, uv);
  logoAlpha = sampledLogo.a;
  logoPixel = sampledLogo.rgb;
  
  float2 logoUVDistortion = sampledLogo.rg - LogoColorDistortionXY; //This may need to be modified for blue-ish logos
  logoUVDistortion = logoUVDistortion * distortionStrength;
  
  float2 distoredSceneAUV = screenUV + logoUVDistortion;
  distortedUV = MirrorUV(distoredSceneAUV);  
}

float GetGradient(in float2 screenUV, float percent)
{
    //Create diagnal gradient
    float gradientX = screenUV.x * Aspect_ratio;
    float gradientY = screenUV.y;

    gradientX = gradientX - 0.88;
    gradientY = gradientY - 0.5;
    
    float fullGradient = gradientX + gradientY;

    float tVal = lerp(1.5, -2.5, percent); //this might need to be adjusted on wider aspect ratios :-/
    fullGradient = fullGradient - tVal;
    fullGradient *= 10; //sharper edge
    return fullGradient;
}

void GetWaveyParttern(in float2 centeredScaledUV, in float2 centeredUV, float percent,  out float ramp,  out float visibility)
{
  float len = length(centeredScaledUV);
  float tValue = lerp(-2, 2, percent);
  visibility = len + tValue;

  float secondaryValue = 1 - visibility;
  secondaryValue = secondaryValue * secondaryValue;
  secondaryValue = 1 - secondaryValue;
  ramp = secondaryValue;

  visibility = clamp(visibility, 0, 1);
}

float4 mainImage(VertData v_in) : TARGET
{
  float2 squareImageScaledUVs = GetCenteredUVScaled(v_in.uv, Aspect_ratio);
  float2 recenteredUVs = GetCenteredUV(v_in.uv);

  if(transition_time < phase2Start)
  {
    float tempT = transition_time / phase2Start;

    //Transform start and UVs
    float2 logoPosition = squareImageScaledUVs / float2(Logo_scale, Logo_scale);
    logoPosition += float2(Logo_positionXY.x * -1.0, Logo_positionXY.y);

    float2 logoPositionStart = squareImageScaledUVs / float2(Start_Logo_scale, Start_Logo_scale);
    logoPositionStart += float2(Start_Logo_positionXY.x * -1.0, Start_Logo_positionXY.y);

    //Use linear interpoation over time and sample pixel
    float2 finalPosition = lerp(RestoreNormalUVCoordinate(logoPositionStart), RestoreNormalUVCoordinate(logoPosition), SmoothSlowdownCurve(tempT));

    float3 logoPixel;
    float2 distortedLogoPixel;
    float logoAlpha;
    GetTransformedLogo(v_in.uv, finalPosition, LogoColorDistortion_Strength, logoPixel, distortedLogoPixel, logoAlpha);

    //Mix with background
    float2 combinedUV = lerp(v_in.uv, distortedLogoPixel, logoAlpha);
    float3 result = image_a.Sample(textureSampler, combinedUV).rgb;

    if(convert_linear)
      result = srgb_nonlinear_to_linear(result);
    return float4(result, 1.0);
  }
  else if(transition_time < phase3Start)
  {
    float tempT = (transition_time - phase2Start) / (phase3Start - phase2Start);
    //Transform start and UVs
    float2 logoPosition = squareImageScaledUVs / float2(Logo_scale, Logo_scale);
    logoPosition += float2(Logo_positionXY.x * -1.0, Logo_positionXY.y);
    logoPosition = RestoreNormalUVCoordinate(logoPosition);

    float sceneVisiblity;
    float rampVisibility;
    GetWaveyParttern(squareImageScaledUVs, recenteredUVs, tempT, rampVisibility, sceneVisiblity);

    float2 distortedUV2 = recenteredUVs *  (1- clamp(rampVisibility, 0, 1));
    distortedUV2 = distortedUV2 + 0.5;
    distortedUV2 = v_in.uv + distortedUV2;
    distortedUV2 = distortedUV2 * 0.5;

    //Mix with background
    float3 logoPixel;
    float2 logoDistoredUV;
    float logoAlpha;
    GetTransformedLogo(distortedUV2, logoPosition, LogoColorDistortion_Strength, logoPixel, logoDistoredUV, logoAlpha);

    float2 sampledBackgroundUV = lerp(v_in.uv, distortedUV2, rampVisibility);
    sampledBackgroundUV = lerp(sampledBackgroundUV, logoDistoredUV, logoAlpha);
    float3 distoredBackgroundA = image_a.Sample(textureSampler, sampledBackgroundUV).rgb;
    float3 distoredBackgroundB = image_b.Sample(textureSampler, sampledBackgroundUV).rgb;

    float gradientValue = GetGradient(v_in.uv, tempT);
    float clampedGadient = clamp(gradientValue, 0, 1);
    float appearLine = clamp(1 - (gradientValue * gradientValue), 0, 1);
    appearLine = appearLine * logoAlpha;
    
    float3 blendedBackgrounds = lerp(distoredBackgroundA, distoredBackgroundB, sceneVisiblity);
    float3 blendedWithLogo = lerp(blendedBackgrounds, logoPixel, logoAlpha);
    float3 result = lerp(blendedBackgrounds, blendedWithLogo, clampedGadient);
    result += appearLine * Logo_Shine_Line;

    if(convert_linear)
      result = srgb_nonlinear_to_linear(result);
    return float4(result, 1.0);
  }
  else
  {
    float tempT = (transition_time - phase3Start) / (1 - phase3Start);

    float tempSub1 = clamp(tempT * 2, 0, 1);
    float tempSub2 = clamp((tempT * 2) - 1, 0, 1);

    float2 finalPosition = squareImageScaledUVs / float2(Logo_scale, Logo_scale);
    finalPosition += float2(Logo_positionXY.x * -1.0, Logo_positionXY.y);

    float3 logoPixel;
    float2 distortedLogoPixel;
    float logoAlpha;
    GetTransformedLogo(v_in.uv, RestoreNormalUVCoordinate(finalPosition), LogoColorDistortion_Strength, logoPixel, distortedLogoPixel, logoAlpha);

    float2 combinedUV = lerp(v_in.uv, distortedLogoPixel, logoAlpha);
    float3 distortedBackground = image_b.Sample(textureSampler, combinedUV);
    float3 blendedWithLogo = lerp(distortedBackground, logoPixel, logoAlpha);
    float3 clearBackground = image_b.Sample(textureSampler, v_in.uv);

    float gradientValue = GetGradient(v_in.uv, tempSub1);
    float clampedGadient = clamp(gradientValue, 0, 1);
    float appearLine = clamp(1 - (gradientValue * gradientValue), 0, 1);

    float3 result = lerp(blendedWithLogo, clearBackground, clampedGadient);
    result = lerp(result, distortedBackground, appearLine);

    if(convert_linear)
      result = srgb_nonlinear_to_linear(result);
    return float4(result, 1.0); 
  }
}