[Help] Render Streaming AR

Our work around was as described by @kazuki_unity729 , although I’ve only just seen their response today.

We split the rendered frame in two - left half for colour, right half for alpha stored as greyscale.

To allow for the existing render buffer to have its alpha channel stored in one half of the output a custom post-process was implemented.

The Unity tutorial on post processing for the HDRP (High Definition Render Pipeline) was followed, to get an example set up:

https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@7.1/manual/Custom-Post-Process.html

By default the HD render pipeline uses a compressed 32-bit frame buffer for post processing, with 11 bits for both the red and green channels and 10 bits for the blue channel. Unfortunately it has no alpha channel. This meant that reading or writing to the alpha channel during the post-processing pass had no impact on the result.

The solution was to change the post-processing buffer format to an alpha supporting (but twice-the-size - 64bits) buffer format. Obviously this meant that the renderer uses more memory but that seems like a reasonable trade off.

See this forum thread for more details:

Once the alpha channel was accessible the existing greyscale post processing effect (from the Unity tutorial) was re-written to squeeze the colour into the left side of the image, and write the alpha into the right side. Below is the relevant fragment shader code:

float4 CustomPostProcess(Varyings input) : SV_Target
{
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
    float2 squishedTexC = input.texcoord * float2(2.0f, 1.0f);
    uint2 positionSS = fmod(squishedTexC, float2(1.0f, 1.0f)) * _ScreenSize.xy;
    float4 srcColour = LOAD_TEXTURE2D_X(_InputTexture, positionSS).rgba;
    float4 colour = float4(srcColour.rgb, 1.0f);
    float4 alpha = float4(srcColour.a, outColour.a, outColour.a, 1.0f);
    // avoiding branching:
    // h = 0 when x < 1 (colour half)
    // and 1 when x >= 1 (alpha half)
    float h = clamp((float)sign(squishedTexC.x - 0.9999f), 0.0f, 1.0f);
    return lerp(colour, alpha, h);
}

And for decoding on the client side:

fixed4 frag(v2f IN) : SV_Target
{
    // read the colour from the left side
    float2 colorTexCoord = IN.texcoord * float2(0.5f, 1.0f);
    float3 feedColour = tex2D(_MainTex, colorTexCoord).rgb;
    // and the alpha from the right side
    float2 alphaTexCoord = colorTexCoord + float2(0.5f, 0.0f);
    // Use the luminance of sample for the alpha since that should be the highest
    // resolution assuming the video compression used chroma-sub-sampling
    // e.g. YUV 4:2:2
    float feedAlpha = Luminance(tex2D(_MainTex, alphaTexCoord).rgb);

    // Since the buffer from the renderer contains blended colour
    // 1-feedAlpha * c0 + feedAlpha * c1
    // where we actually want c1 (blended using feedAlpha with
    // the feed from the camera).
    // If we presume that the transparent areas (c0) are BLACK then
    // the colour from the feed will be feedAlpha * c1.
    // so c1 = feedColour / feedAlpha. 
    float3 colour = feedColour/feedAlpha;
    return float4(colour, feedAlpha);
}

A more efficient way of doing this could be to use a blend mode on the renderer that doesn’t do any alpha blending, but this would have implications for the rendering of translucent objects overlapping other objects.