内定者バイトに本気で挑んだ1ヶ月間!

初めまして!サイバーエージェント22卒内定者の江口大喜と申します。サイバーエージェントでは入社前の内定者の期間で各子会社に配属し、その配属先で内定者バイトとして働くことができます。

内定者の期間、学校はありますがある程度まとまった時間が取れる、成長する絶好の期間ともいえます。また、正式な配属前に各子会社の中で勉強したり社員の方と交流することで会社ごとの文化も知ることができます。

今回私は2月の1ヶ月間でApplibotに内定者バイトとして入らせていただきました。本記事ではその1ヶ月間で自分が行ったタスク。そしてどのような経験をしたのかを紹介させていただきます。

なぜ内定者バイト先にApplibotを選んだのか

サイバーエージェントにゲームクライアントエンジニアとして入社することになった自分はこれからのキャリアパスをどうしたらいいか悩んでいました。元々Unityを使って自分の好きなゲームを作ってきていたのですが、これといった得意分野がありませんでした。さまざまな先輩方や人事の方とお話しさせていただいたり、ゲームにおける自分の好きな領域を考え、グラフィックスを専門にするようなエンジニア像を描くことにしました。

ただ、グラフィックスをネットの情報のみで深く勉強することはハードルが高く、わからないことを質問できるような方が身近にいなかったので苦労していました。そこで内定者バイトとしてグラフィックスを勉強させていただきたい旨を人事の方にお伝えし、サイバーエージェントの子会社内でグラフィックスに強い会社を紹介していただき、その中にApplibotがありました。

ApplibotにはVoxelStudioというエンジニア視点からグラフィック技術の向上を目指すスタジオがあります。

https://www.applibot.co.jp/news/20210309170048/

グラフィックスを勉強しようと思う自分にとってVoxelStudioの存在はとても理想的でした。グラフィックスに強い方々に囲まれる環境に入ってみたい!という思いがあったからです。

配属された後はApplibotでの各プロジェクトを横断に見させていただき、その中でVoxelStudioの研究テーマである「UIにShader処理をかける際の軽量化」をメインタスクとして担当させていただきました。

研究テーマ「UIにShader処理をかける際の軽量化

今回挑んだタスクはVoxelStudioの研究テーマの「UIにShader処理をかける際の軽量化」です。

これを実現するためにオフスクリーンレンダリングを使います。オフスクリーンレンダリングとはスクリーンには表示されないレンダリングをいいます。

今回の軽量化は例えばあるUIにブラーのぼかしをかける際、毎フレームブラーをかけていては負荷が重いです。そのエフェクトやUIがアニメーションしないのであれば、キャプチャしたUIに1フレームだけブラーをかけその結果を保持しておけばブラーの負荷は毎フレーム発生することなくUIを表示できます。

今回はこのオフスクリーンレンダリングをUnityで実装することにしました。

実行環境

Unity 2020.3.25f1

URP 10.7.0

実現したいもの

まず、前提として上図のような画面があったとします。シーンには3DオブジェクトのPlaneとCube(それぞれ床と壁に見立てています)、そしてCapsule(プレイヤーに見立てています)があります。

UIとして白色のボタンとImageが一つずつ。そして中央に赤い半透明のImageがあります。

今実現したいのは赤いImageをキャプチャし、ポストプロセスをかけ(今回はブラー)、その結果を保持しておくことです。

実現するために(シーン設定)

まず、上の二枚目のスクリーンショットのようにカメラを二つ用意します。

一つはメインカメラ。RenderTypeはBase、ProjectionはPerspectiveとします。

もう一つメインカメラとは別でUI描画用のカメラを用意します。。RenderTypeはBaseに。このカメラは画面に映っているものをキャプチャするため、ProjectionはOrthographicにします。

そしてこのUI描画用カメラの子階層に通常のCanvasとは別のCanvasを配置します。後述しますが、キャプチャしたいUIをこの追加したCanvasの子要素にするためです。

下のスクリーンショットのようにCanvasコンポーネントのRenderModeをScreen Space – Cameraに。そしてRenderCameraをUI描画用カメラに設定します。

そしてCanvasの中身を以下のスクリーンショットのように設定します。ここではMainCanvasという名前にし、RenderModeをScreenSpace-CameraでRenderCameraをMainCameraに設定します。

また、左下に四角で括っているようにMainCanvasの子階層にCaptureNodeFrameという名前のImageを生成しておきます。また、このCaptueNodeFrameのImageコンポーネントのアルファ値は0にしておきます。

これはキャプチャを行う枠になります。そして、この枠にこれから作る「CaptureNode」というスクリプトをアタッチします。

今回はその枠の階層に赤色半透明のImageと黒色のTextを置いています。

UI描画用カメラは画面には出力せず、RenderTextureとして出力させます。

それをMainCanvas内のRawImageに映し出し、そのRawImageを含むMainCanvasを(もちろんキャラクターや背景も)MainCameraが映し出す仕組みです。

また、キャプチャしたいものだけをUI描画用カメラは撮りたいのでCullingMaskでキャプチャしたいものだけを指定し、逆にMainCameraの方はキャプチャさせたいものはCullingMaskで切っています。

以下のスクリプトはCaptureNodeスクリプト内でRawImageをMainCanvas内に生成しているメソッドになります。キャプチャしたいフレームにこのスクリプトをアタッチしている前提なので自分のRectTransformからRawImageのサイズを設定しています。(キャプチャしたいUIと同じサイズのRawImage)

private void CapturePrepare()
{
        // このスクリプトをアタッチしたUIの情報取得
        RectTransform rectTransform = GetComponent<RectTransform>();
        float anchoredPositionX = rectTransform.anchoredPosition.x;
        float anchoredPositionY = rectTransform.anchoredPosition.y;
        _captureFrameRect = rectTransform;

        // rawImageをアタッチするからゲームオブジェクト生成
        GameObject rawImageObject = new GameObject("CaptureRawImage");

        // MainCameraが映すキャンバスの階層に移動
        rawImageObject.transform.SetParent(MainCanvas.transform,false);
        
        // アタッチしたUIと同じサイズのrawImage生成
        RawImage captureRawImage = rawImageObject.AddComponent<RawImage>();
        captureRawImage.GetComponent<RectTransform>().anchoredPosition =
            new Vector2(anchoredPositionX, anchoredPositionY);

        RectTransform rawImageRectTransform = rawImageObject.GetComponent<RectTransform>();
        rawImageRectTransform.sizeDelta = new         Vector2(rectTransform.rect.width, rectTransform.rect.height);
}

次は肝心のキャプチャをするメソッドになります。キャプチャ出力用のRenderTextureを生成し、キャプチャ専用カメラのtargetTextureにセットしています。

private void Capture()
{
        // RawImage取得(_captureRawImageが該当)
        _captureRawImageRect = _captureRawImage.GetComponent<RectTransform>();
        
        _captureFrameRect = GetComponent<RectTransform>();

        // キャプチャ用のRenderTexture生成
        RenderTexture captureRenderTexture = new RenderTexture((int) _captureFrameRect.rect.width, (int) _captureFrameRect.rect.height, 16, RenderTextureFormat.ARGB32);
        captureRenderTexture.Create();
        _captureRenderTexture = captureRenderTexture;
        
        // カメラの設定
        _captureNodeCamera.targetTexture = captureRenderTexture;

        //〜〜〜〜中略〜〜〜〜〜
        
        // CaptureNode用のキャンバスに自身を移動
        // NodeCanvasはCaptureNode用のキャンバス
        transform.SetParent(NodeCanvas.transform, false);
        
        // Captureしたいものをフィットさせる
        _captureFrameRect.anchorMin = Vector2.zero;
        _captureFrameRect.anchorMax = Vector2.one;
        _captureFrameRect.offsetMin = Vector2.zero;
        _captureFrameRect.offsetMax = Vector2.zero;
        _captureFrameRect.pivot = new Vector2( 0.5f, 0.5f );
        _captureFrameRect.rotation = Quaternion.identity;
        _captureFrameRect.localScale = Vector3.one;
        
        // RawImageに反映
        _captureRawImage.texture = captureRenderTexture;
    }

このスクリプトでは元々MainCanvas内にあったキャプチャしたいUIをCapture用のCanvasに移しています。その後、UIをキャンバスサイズに伸ばしてフィットさせます。これにより移動したUIを画面に映し出したときに意図したサイズに修正できます。

ただ、このやり方ではキャプチャしたいUIを全てストレッチさせなくてはならないという煩わしさがあります。理想的には赤い部分だけを切り取って描画したかったのですが、勤務日7日という短い時間の関係でこの方法をとりました。

これによってゲーム画面は以下のようになります。UIが動かない場合このキャプチャしたUIにブラーなどのエフェクトをかけ、その結果を保持すれば、毎フレームエフェクトをかけなくてもよくなり、その分の負荷は軽くなります。

ブラーをかける前。真ん中の赤色半透明のUIはキャプチャしたもの。

最終的に得られたゲーム画面が以下の通りです。得たいキャプチャのUI画像と色が少し変わってしまっています。これはどうやらUI画像専用カメラのSolidColorの影響だと現在考えています。

例えば以下の画像だとBackGroundColorは黄緑色をしていますが、アルファ値を0にしています。しかし黄緑色がキャプチャしたいUI画像に載ってしまい、色が変わってしまっていると思います。

SolidColorでBackGroundColorを赤寄りにした時。アルファ値は0にしてある
ブラーの処理をかけているUIキャプチャ。SoldColorの色をUIと同じ色にしている

また、BackGroundTypeをUnInitializedにした場合は以下のようになり、これも色が少し変わってしまっています。

URPではなくBuilt-inのRenderPipelineで作った際にはBackGroundTypeはSolidColorでBackGroundColorのアルファ値は0にすればSolidColorの色は出ず、元々のUIの色で描画されて希望のものが作れました。

以上のように目標のようなアウトプットは出せませんでしたが、新しい技術を取り入れようとした際にこのような壁にぶち当たるという経験はなかなか貴重だと感じ、それを体験できて嬉しかったです。

アウトプット以外で得られた知見

上記のオフスクリーンレンダリングを作ったことで得られた知見もあります。

当初、BloomなどはUnity標準搭載のPostProcessingStackを使用する予定でした。そのためにはキャプチャ用のカメラのRendering > PostProcessingのチェックボックスをオンにする必要があります。しかし、これをオンにするとRendering後のUIの色味がおかしくなったように感じます。

これは今回カメラのEnvironment > BackgroundType を SolidColorにしているのですが、このSolidColorの色のアルファ値がPostProcessingのチェックボックスをオンにすることで1になってしまうことに由来するようです。

これにはだいぶハマってしまいましたがUnityForumで、これはURPの仕様であるというものを見つけProcessingStackは諦め、自作のエフェクトで対応になりました。

https://forum.unity.com/threads/post-processing-with-multiple-cameras-is-currently-very-problematic.1028533/

こういう発見も実際にやってみないとわからない気づきであり、研究テーマを進めた小さなアウトプットの一つなのかなあと思います。

フルリモートワークとしての工夫

自分は神戸に住んでおり、学校の授業もあったのでフルリモートで入社させていただきました。内定者バイトでは出社することもできますが、リモートで出社することもできます。※週2日はリモート推奨

しかし、フルリモートで入社し働くのはなかなかにハードルがあります。やはり対面とは違い、周りに先輩方がいらっしゃらないので進捗の管理、相談、質問など不安なことはありました。

内定者バイトでは一人の内定者に一人のトレーナーさんがついてくださいます。そのトレーナーさんにこれらの懸念点を相談したところ、色々な解決策を提示してくださいました。

まるでTwitter感覚の進捗スレッド

リモート出社では進捗が見えにくい現状があります。そこでVoxelStudioのメンバーの皆さんが入っていらっしゃるSlackに進捗スレという名前で呟き、そのスレッドに進捗を書いていくことにしました。

このスレッドにははっきりとした進捗がなくてもTwitter感覚で呟きます。

「この記事読も〜」「ここわからない、、、」「この〇〇はなんで△△なん?」

など、敬語でもなくざっくばらんに書いていきます。これによって進捗スレに書くハードルはぐっと下がり、トレーナーさんやいろんな方が、私が今何しているのかを簡単にみることができます。自分の感覚では出社して作業しているところに先輩方がふらっとPCをのぞいてくださる感覚でした。

この進捗スレを書くことでわからないことがあったらトレーナーさんはじめVoxelStudioの方が教えてくださったり相談に乗ってくださいました。

それだけではありません。自分が進捗を細かく書くということが重要でした。進捗を書くことで自分が今タスクのどの地点にいて、達成のために次は何をして、何の参考記事を読むのかということが自分の頭の中で整理されるのです。

会社を知れるオンラインランチ

Applibotでは基本的に13:00-14:00の一時間が昼休憩です。この時間を生かし、さまざまな方とオンラインランチさせていただきました。

内定者バイトというのは会社の文化や人を知れることが利点であるということをはじめに書きましたがリモートというのはその利点を弱めてしまうように思えます。

しかし、逆にオンラインという強みをいかしApplibotのいろんな方とランチをご一緒させていただきました。VoxelStudioの方をはじめ、別プロジェクトの方、新卒4年目までの集まりなど、さまざまな組み合わせでご一緒させていただきApplibotの雰囲気を感じることができました。

業務と全く関係ない話から、Applibotにあるサークルの話、グラフィックスのおすすめの本などを聞いたり、雑談が盛り上がったりと楽しい時間を過ごしました。

朝1、昼1、夕1、退勤前1回のメンターさんとの面談

メンターさんとの面談は細かく、日に4回行っていただきました。まず朝1に面談。ここでは昨日までの進捗確認と今日一日の目標設定。今の悩んでいる点を洗い出し、質問させていただきました。目標設定をすることでモチベーション向上や不安解消になり、かなり重要でした。

昼1は進捗確認です。今詰まっている点などを挙げていき、昼食後、誰かに相談できないかなど様々な確認を行います。

夕1は昼と同様進捗確認ですが、今日の退勤までの時間を見て「今日一日でどこまで進め、どこからを次の出勤日に回すのか」を確認します。今日一日で無理に終わらせようとせず、長い目で見た時にしっかりと進捗を出せるように今日一日の残り目標を決めます。

退勤前に一回、今日の最後の進捗確認です。次に何をしていくのか、今日の反省点などがあれば考えて次に生かす面談になります。

このようにトレーナーさんには手厚くサポートしていただき、毎日の不安を次の出勤日にもし越すことなく過ごすことができました。本当にありがたいです、、、、!!

終わりに

今回は自分が一ヶ月、内定者バイトとしてVoxelStudioに入らせていただいたことを、今回研究したことだけでなくリモートならではの工夫点も織り交ぜながら書かせていただきました。

今回書かせていただけた通り、Applibotでは自分が学びたい!という思いを尊重していただき、挑戦できるだけでなく、それに伴うサポートもかなり手厚いことが特徴です!

興味のある方は是非Applibotに来て見てください!

最後まで読んでいただきありがとうございました!


関連記事一覧

  1. この記事へのコメントはありません。