LoginSignup
6
6

More than 5 years have passed since last update.

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

Posted at

かつて、インターレースという技術がありました。
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

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

image.png

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

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

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

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に貼り付けるやり方をします。
なので、まずカメラ2つ用意して、一つは3D空間を描画するためのカメラ。
これには、InterlaceCameraコンポーネントを付けます。
そして、CullingMaskを3Dに関する部分だけに設定します。

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

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

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

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

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

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

解像度2688x1242で、ポストエフェクトを掛けます。
インターレースなしだと、22FPSでした。
インターレースありだと、46FPSです。
このスクリプトをつけずにキャンバスも使わずカメラ1つで描画した場合、26FPSでした。
image.png
ということで、一応効果あります。
(当然ハード構成によっても、この辺りの効果は変わります。私はMacBook AirのIntel HD Graphics 6000 1536 MBというグラフィックスボードで検証しました。)

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

以上になります。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6