インターレースを使って、描画の高速化

かつて、インターレースという技術がありました。
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%AC%E3%83%BC%E3%82%B9

描画を１ライン置きに飛ばすことで、描画ピクセルを半分に抑える技術です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/59173/c942a746-851c-b495-15d7-4809196143e8.png)


十分なスペックを持ったデバイスがあるなかで、もはやこんな技術を使ってるハードはもはや無いでしょう。しかし、UNITYでアンビエントオクリュージョンや、バンプマップなどピクセルシェーダーの負荷が大きい場合、ひょっとすると描画ピクセルが半分で済むインターレースの要素技術が生かせる可能性もなくはないです。

ただ__チラつきの原因にもなるので、全然お勧めしません。__
意図的に、レトロゲーム風にするのに使えるかもしれません。
あと__そもそもピクセルシェーダー処理が重くない場合は、逆に重くなるかもしれません。__
(ハードウェアの機能ではなく、ソフトウェアで疑似的に処理内容を似せているため)
さらに、__UNITYはProjectionMatrix書き換えると、カメラからのレイキャストなど問題出るので、本当にお勧めしません。__
という注意書きを入れつつ、やり方を説明します。

まず、下記のような２つのファイルを作って下さい。
Ｃ＃コードと、シェーダーです。

~~~InterlaceCamera.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Camera))]
[ExecuteInEditMode]
public class InterlaceCamera : MonoBehaviour {
    [SerializeField]
    Material _mtlInterlace;
    [SerializeField]
    RawImage _rawImage;
    [SerializeField]
    bool _useInterlace = true;

    Camera _cam;
    bool _isOdd = false;
    RenderTexture[] _twoRT;
    RenderTexture _rtToRawImage;

    private void Awake()
    {
        _cam = GetComponent<Camera>();
        _twoRT = new RenderTexture[2];
        int texWidth = Screen.width;
        int texHeight = Screen.height / 2;
        for (int i = 0; i < _twoRT.Length; ++i)
        {
            RenderTexture rt = new RenderTexture(texWidth, texHeight, 24);
            rt.antiAliasing = 1;
            _twoRT[i] = rt;
        }
        _rtToRawImage = new RenderTexture(texWidth, texHeight * 2,24);
        _rtToRawImage.antiAliasing = 1;
        _rawImage.texture = _rtToRawImage;
    }

    private void OnDestroy()
    {
        if (_cam != null)
        {
            _cam.targetTexture = null;
            _cam.ResetProjectionMatrix();
        }
        if (_rawImage!=null)
        {
            _rawImage.texture = null;
        }
        for (int i = 0; i < _twoRT.Length; ++i)
        {
            if (_twoRT[i] != null)
            {
                DestroyImmediate(_twoRT[i]);
            }
        }
        if (_rtToRawImage != null)
        {
            DestroyImmediate(_rtToRawImage);
        }
    }

	void Update () {
        if (_useInterlace)
        {
            _cam.targetTexture = _isOdd ? _twoRT[0] : _twoRT[1];

            RenderTexture rt = _cam.targetTexture;
            float halfPixel = 0.25f / (float)rt.height;
            Matrix4x4 mat = Matrix4x4.Translate(new Vector3(0f, _isOdd ? -halfPixel : halfPixel, 0f));
            mat.m00 *= 2f;

            _cam.ResetProjectionMatrix();
            Matrix4x4 matProj = _cam.projectionMatrix;
            matProj = mat * matProj;
            _cam.projectionMatrix = matProj;

            _isOdd = !_isOdd;
        }
        else
        {
            _cam.targetTexture = _rtToRawImage;
            _cam.ResetProjectionMatrix();
            _cam.ResetWorldToCameraMatrix();
        }
    }

    private void OnPostRender()
    {
        if (_useInterlace)
        {
            _mtlInterlace.SetTexture("_OddTex", _twoRT[0]);
            _mtlInterlace.SetTexture("_EvenTex", _twoRT[1]);
            _mtlInterlace.SetPass(0);
            RenderTexture prev = RenderTexture.active;
            Graphics.Blit(null, _rtToRawImage, _mtlInterlace);
            RenderTexture.active = prev;
        }
    }
}
~~~


続いてシェーダーコード。

~~~InterlaceShader.shader
Shader "Unlit/InterlaceShader"
{
	Properties
	{
		_OddTex("Odd Texture", 2D) = "white" {}
		_EvenTex ("Odd Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

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

			struct v2f
			{
				float2 uv : TEXCOORD0;
			};

			sampler2D _OddTex;
			sampler2D _EvenTex;
			
			v2f vert (appdata v, out float4 wpos : SV_POSITION)
			{
				v2f o;
				wpos = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}
			
			fixed4 frag (v2f i, UNITY_VPOS_TYPE vpos : VPOS) : SV_Target
			{
				fixed4 col = (((uint)vpos.y & 1) == 0) ? tex2D(_EvenTex, i.uv) : tex2D(_OddTex, i.uv);
				return col;
			}
			ENDCG
		}
	}
}
~~~


今回のやり方では、カメラで撮った画像を加工して、RawImageに貼り付けるやり方をします。
なので、まずカメラ２つ用意して、一つは３Ｄ空間を描画するためのカメラ。
これには、InterlaceCameraコンポーネントを付けます。
そして、CullingMaskを３Ｄに関する部分だけに設定します。

もう一つは、ＵＩだけを描画するカメラとします。
カメラのCullingMaskをＵＩだけにします。

RawImageを作ります。画面全体の大きさにしましょう。
マテリアルを作って、先ほどのシェーダーを選択します。

カメラ１を選択し、InterlaceCameraコンポーネント部分で、マテリアルとRawImageを設定します。

あとは、３Ｄ空間にモデルを置いてカメラに映るようにして確認しましょう。
UseInterlaceのチェックを切り替えると、インターレースの使用の有無を切り替えられます。

__昔のゲームは、こういうのが理由でチラついていたんだなぁと感じていただければ幸いです。__


そして、本当に高速化されるのか。
確認しました。

解像度2688x1242で、ポストエフェクトを掛けます。
インターレースなしだと、22FPSでした。
インターレースありだと、46FPSです。
このスクリプトをつけずにキャンバスも使わずカメラ１つで描画した場合、26FPSでした。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/59173/2ce348f5-bcfd-450b-8702-315d1dbcf1d4.png)
ということで、一応効果あります。
（当然ハード構成によっても、この辺りの効果は変わります。私はMacBook AirのIntel HD Graphics 6000 1536 MBというグラフィックスボードで検証しました。）

しかし、そもそも解像度が低くてポストエフェクト無いなど、ピクセル処理が重く無い場合は逆効果でしたので、こ注意ください。


以上になります。