2
\$\begingroup\$

My game's resolution is 1920 x 1080, and I have a background sprite that is also 1920 x 1080. I have made the background sprite a child of my MainCamera & dragged it further back on the Z-axis so that it is rendered behind everything else in the scene.

Because I have dragged it so far back in the scene, it no longer covers the entirety of my game screen.

To remedy this, I'm resizing my sprite by doing the following:

I've made a child gameObject of the MainCamera and attached my sprite to it. I've also attached the following Script to the child gameobject:

[SerializeField] private Camera _camera;
[SerializeField] private SpriteRenderer _spriteRenderer;

void Update()
    {
        ScaleBackgroundImage();
    }

    private void ScaleBackgroundImage()
    {
        float fovRadians = Mathf.Deg2Rad * _camera.fieldOfView;
        float visibleHeightAtDepth = transform.localPosition.z * Mathf.Tan(fovRadians) * 2f;

        float spriteHeight = _spriteRenderer.sprite.rect.height
                           / _spriteRenderer.sprite.pixelsPerUnit;

        float scaleFactor = visibleHeightAtDepth / spriteHeight;

        _spriteRenderer.transform.localScale = Vector3.one * scaleFactor;
    }

For good measure, here is the what the sprite's game object and camera look like in the inspector:

enter image description here

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Note that you don't have to re-run this every frame in update unless you expect to change the camera's FoV constantly. If you do, I'd switch to the shader option, which will be lighter weight for something you're doing every frame. \$\endgroup\$
    – DMGregory
    Jan 20, 2023 at 17:38
  • \$\begingroup\$ Ah, good to know! At first I just had it in Start() but tried out Update() to see if I might get different results. \$\endgroup\$ Jan 20, 2023 at 19:28

3 Answers 3

2
\$\begingroup\$

Here's how you can compute the necessary scaling factor:

// Angle the camera can see above the center.
float halfFovRadians = camera.fieldOfView * Mathf.Deg2Rad / 2f;

// How high is it from top to bottom of the view frustum,
// in world space units, at our target depth?
float visibleHeightAtDepth = depth * Mathf.Tan(halfFovRadians) * 2f;

// You could also use Sprite.bounds for this.
float spriteHeight = spriteRenderer.sprite.rect.height 
                   / spriteRenderer.sprite.pixelsPerUnit;

// How many times bigger (or smaller) is the height we want to fill?
float scaleFactor = visibleHeightAtDepth / spriteHeight;

// Scale to fit, uniformly on all axes.
spriteRenderer.transform.localScale = Vector3.one * scaleFactor;

This matches the height of the sprite to the height the camera can "see" at that depth. I'm assuming here that your sprite's pivot is its center, placed at (0, 0, depth) in the camera's coordinate system, and that the aspect ratio of the sprite is known to be as wide or wider than the widest supported resolution so it fills the frame.

Alternatively, as I mentioned previously, you could make a shader that does this automatically for a standard Unity quad with vertices running from (-0.5, -0.5) to (0.5, 0.5) by transforming them to the viewport corners:

Shader "Unlit/FillCameraFrame"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        [Toggle(PRESERVE_ASPECT)] _PreserveAspect ("Preserve Aspect Ratio", Float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature PRESERVE_ASPECT

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

        #ifdef PRESERVE_ASPECT
            float4 _MainTex_TexelSize;
        #endif

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = float4(v.vertex.xy * 2.0f, 0.5f, 1);
                o.vertex.y *= -1;

                float2 uv = v.uv;

            #ifdef PRESERVE_ASPECT
                float cameraAspect = _ScreenParams.x * (_ScreenParams.w - 1.0f);
                float invTextureAspect = _MainTex_TexelSize.w * _MainTex_TexelSize.x;
                float conversion = invTextureAspect * cameraAspect;

                // Scale from center, rather than from the corner.
                // (Delete the 0.5 shifts if you prefer to anchor at left)
                uv.x = (v.uv.x - 0.5f) * conversion + 0.5f;
            #endif

                o.uv = TRANSFORM_TEX(uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col * col.a;
            }
            ENDCG
        }
    }
}

This also works for tiling background images, repeating them if a very wide camera viewport exceeds the image's aspect ratio. (Or clamping them and filling the excess with the leftmost/rightmost column of texels, if you set the texture to clamp instead of repeat)

\$\endgroup\$
11
  • \$\begingroup\$ I'm convinced you just know everything, Douglas. Thanks for always providing such a swift and detailed response! \$\endgroup\$ Jan 20, 2023 at 1:40
  • \$\begingroup\$ i agree with this ! \$\endgroup\$ Jan 20, 2023 at 1:40
  • 1
    \$\begingroup\$ You'd want transform.localPosition.z unless you can guarantee your camera is always on the z=0 plane and looking exactly parallel to the z axis (even then, I wouldn't count on it - it makes a land mine that can go off if you ever change that down the line). \$\endgroup\$
    – DMGregory
    Jan 20, 2023 at 14:50
  • 1
    \$\begingroup\$ Oops, I left out a divide-by-two in the scaling factor calculation. I forgot that perspective cameras measure their FoV bottom-to-top, while only orthographic cameras measure middle-to-top. Fixed above. \$\endgroup\$
    – DMGregory
    Jan 20, 2023 at 17:38
  • 1
    \$\begingroup\$ @Job_September_2020 The code at the top of the answer is already C# and will do that job (for the height axis) if you use Renderer.bounds instead of the sprite size. \$\endgroup\$
    – DMGregory
    Jan 20, 2023 at 22:26
2
\$\begingroup\$

Building off of @DMGregory's solution, you can calculate the depth of the object without assuming it's at (0, 0, depth) using some vector math:

Vector Math with the Camera Direction and Object Position to find that the depth of the object is equal to the dot product of the camera direction and the direction from the camera to the object.

This turns into this code:

float depth = Vector3.Dot(spriteRenderer.transform.position - camera.transform.position,
                camera.transform.forward);

So the final (C#) answer becomes:

// Angle the camera can see above the center.
float halfFovRadians = camera.fieldOfView * Mathf.Deg2Rad / 2f;

// What depth is the object at in terms of the camera?
float depth = Vector3.Dot(spriteRenderer.transform.position - camera.transform.position,
    camera.transform.forward);

// How high is it from top to bottom of the view frustum,
// in world space units, at our target depth?
float visibleHeightAtDepth = depth * Mathf.Tan(halfFovRadians) * 2f;

// You could also use Sprite.bounds for this.
float spriteHeight = spriteRenderer.sprite.rect.height
                     / spriteRenderer.sprite.pixelsPerUnit;

// How many times bigger (or smaller) is the height we want to fill?
float scaleFactor = visibleHeightAtDepth / spriteHeight;

// Scale to fit, uniformly on all axes.
spriteRenderer.transform.localScale = Vector3.one * scaleFactor;

In my case, I have a 2D sprite that I would like to keep at the same size regardless of how far away it is. To do this, instead of dividing by the height of the sprite, I instead divide by the pixel height of the game:

...

// How many times bigger (or smaller) is the height we want to fill?
float scaleFactor = visibleHeightAtDepth / screenHeight;

...
\$\endgroup\$
0
\$\begingroup\$

Multiply the width and height by a scaling factor.

\$\endgroup\$
3
  • \$\begingroup\$ This answer would be better if it showed how to determine what specific scaling factor to use. \$\endgroup\$
    – DMGregory
    Jan 20, 2023 at 0:55
  • \$\begingroup\$ I did think about trying to make example code. But didn't want to confuse the user by giving code that didn't fit with theirs. I would use the Z distance to calculate the scale somehow. They are relative to eachother somehow. Sorry I can't be more helpful at this time. (I would if I could) \$\endgroup\$ Jan 20, 2023 at 1:24
  • 1
    \$\begingroup\$ See my answer for an example for future reference then. \$\endgroup\$
    – DMGregory
    Jan 20, 2023 at 1:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .