はじめに
NTTコノキューの新井です。
この度、全能の神でも持ち上がらない腰がようやく持ち上がったのでUnityのShaderについて勉強しました。 せっかくなのでコンテンツを作って、見られるかたちで公開したいと思います。
※この記事はNTTドコモR&D Advent Chalendar 2022の6日目記事です。
デモ
今回作ったものの動作です。
アドカレの実装おわった
— 𝕋𝕒𝕜𝕒𝕙𝕚𝕣𝕠 (@_7cancer) 2022年12月5日
ちょっと同期ずれしてる...? pic.twitter.com/1fadu4Zu4c
※ 本コンテンツはXRCity(TOP|XR City)で動作させています。
実装
見て分かった人もいるかもしれないですが、本実装の元ネタは下記のコンテンツです。
STYLYと等身大パネル組み合わせてARコンテンツ作ってみた。現実側にぬるっと出てくるわ#STYLY #vtuber pic.twitter.com/VIPjbiB2sa
— YORIMIYA (@jav6868) 2022年7月31日
めちゃくちゃかわいい...... ということで、こちらを追実装することをモチベーションにしてShaderの勉強をします。
調べてみたところ、Moguraさんがインタビュー記事をまとめていました。
どうやら構成は大まかに下記のように分けられるようです。
- 3Dモデル
- AR演出
- 等身大パネル
今回はShaderの就学という目的があるので、「3Dモデル」はVroidのサンプルモデルを、「等身大パネル」は適当な画像を代替し、「AR演出」に注目して実装方法を書きます。
「AR演出」の要素
AR演出をさらに分解していくと、下記のような要素に分けることができます。
① キャラクターがパネル内で動く演出
② パネルからキャラクターが出てくる演出
③ パネル周りのパーティクルライブっぽい演出
前述の通り今回はShaderに関して書きたいので、①と②を実装します。
YORIMIYAさんの動画を観察してみると、
①: パネル型にくりぬいた動画を再生する
②: キャラクターの描画範囲を絞る
といった内容で”それっぽく”見せることができそうです。 そのため、以降は①と②についての手順を詳しく書いていきます。
環境
- Windows 11
- Unity 2021.1.22f1
- UnityChan
①: パネル型にくりぬいた動画を再生する
順序としては、動画を作る → パネル型にくりぬく。という手順で進めようと思います。 そして動画ですが、下記要素を満たす必要があります。
- キャラクターがパネルの中にいる時は動画内で普通に見える
- キャラクターがパネルから出てきている時は動画内にいなくなっている
そのため、パネルの位置を境界にして描画の有無を決定するShaderを作ります。
頂点フラグメントShaderをつくる
作りました。 Shaderです。
Shader "Custom/DrawRange"
{
Properties
{
[NoScaleOffset] _MainTex ("SurfaceTexture", 2D) = "white" {}
[NoScaleOffset] _MainTex2 ("LiningTexture", 2D) = "white" {}
_Length ("Length", float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque"}
LOD 100
Cull off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _MainTex2;
float _Length;
struct appdata
{
fixed2 uv : TEXCOORD0;
half4 vertex : POSITION;
};
struct v2f
{
fixed2 uv : TEXCOORD0;
half4 vertex : SV_POSITION;
half4 wpos : TEXCOORD1;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.wpos = mul(unity_ObjectToWorld, v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i, fixed facing : VFACE) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);
float v = abs(i.wpos.x) <= _Length/2 ? (abs(i.wpos.y) <= _Length/2 ? (abs(i.wpos.z) <= _Length/2 ? 1 : -1) : -1) : -1;
clip(v);
return (facing > 0)? color : tex2D(_MainTex2, i.uv);
}
ENDCG
}
}
}
処理を解説していきます。 が、その前に動作を見てください。 直観的に理解できると思います。
指定した範囲内でだけ描画されるユニティちゃん pic.twitter.com/vGRUCAC7RI
— 𝕋𝕒𝕜𝕒𝕙𝕚𝕣𝕠 (@_7cancer) 2022年12月5日
では重要な部分を抜粋して解説をします。
Cull off
まず初めにCullの設定です。
こちらを記述することで、ポリゴンの裏側を描画するようになります。
デフォルトではCull backになっているみたいです。
今回はパネルから出る瞬間のキャラクター断面をきれいにするために設定しています。
fixed4 frag (v2f i, fixed facing : VFACE) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);
float v = abs(i.wpos.x) <= _Length/2 ? (abs(i.wpos.y) <= _Length/2 ? (abs(i.wpos.z) <= _Length/2 ? 1 : -1) : -1) : -1;
clip(v);
return (facing > 0)? color : tex2D(_MainTex2, i.uv);
}
Fragment Shader部分です。
まずはセマンティクスにfixed facing : VFACEを追加しています。
これによって、Fragment Shaderに渡されたメッシュがカメラから見てどちらの方向を向いているかとることができます。
また4行目では、渡された頂点が立方体の範囲内なのか判定を行っています。
最後にfacingの値を確認し、ポリゴンの表裏に対応してテクスチャを変えて描画します。
ちなみにVRMモデルやFBXモデルを利用していて、テクスチャのCutOffなどを操作したい場合は下記のように修正/追加しましょう。
Properties
{
_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5 // 追加する
}
fixed _Cutoff; // Pass内に追加する
fixed4 frag (v2f i, fixed facing : VFACE) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);
float v = abs(i.wpos.x) <= _Length/2 ? (abs(i.wpos.y) <= _Length/2 ? (abs(i.wpos.z) <= _Length/2 ? 1 : -1) : -1) : -1;
clip(v == 1 ? color.a - _Cutoff : -1); // 修正する
return (facing > 0)? color : tex2D(_MainTex2, i.uv);
}
ここまでのShaderによって、「パネルから出ていっているキャラクター」っぽく見せる動作が実現できると思います。
動画を撮る
先ほどのShaderを使って動画を撮りましょう。
まずは下記を参考にしてUnityにUnity Recorderを導入しましょう。
もろもろうまいことやって動画を撮ります。
できました。
画面からでてきてる(つもりの)サンプルモデルちゃん pic.twitter.com/MtaXX2PZf3
— 𝕋𝕒𝕜𝕒𝕙𝕚𝕣𝕠 (@_7cancer) 2022年12月5日
動画をくりぬいて再生する
パネル型に動画をくりぬきます。 動画編集技術ではなくShaderで作ります。
実装に関してぱっと思いつくのがマスク処理です。 Unityでどうやって実装するかというと、Stencil Shaderを利用します。
- MaskオブジェクトとMaskedオブジェクトをつくる
- MaskオブジェクトではStencilバッファに値を書き込む
- MaskedオブジェクトではStencilバッファの値を読み取って描画を実行する
これによって、パネルと同じStencilバッファ形状のマスク処理が実行され、動画をパネル型に切り抜くことができそうです。
ステンシルShaderをつくる
作りました。 Shaderです。
まずはMask用Shaderです。
Shader "Custom/UnityChanPanelMask"
{
Properties
{
[NoScaleOffset] _MainTex("Texture", 2D) = "white"{}
}
SubShader
{
Tags {"Queue"="Geometry"}
Pass
{
Stencil
{
Ref 2
Comp always
Pass replace
}
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed4 frag (v2f_img i) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);
return color;
}
ENDCG
}
}
}
内容としては、下記のStencil内でバッファを2に置き換えてます。
Stencil
{
Ref 2
Comp always
Pass replace
}
次にMasked用Shaderです。
Shader "Custom/UnityChanPanelMasked"
{
Properties
{
[NoScaleOffset] _MainTex("Texture", 2D) = "white"{}
}
SubShader
{
Tags {"Queue"="Geometry+1"}
Pass
{
Stencil
{
Ref 2
Comp equal
}
ZTest Always
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed4 frag (v2f_img i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
こちらの内容としては、下記の記述でStencilバッファが2の場合に描画する対象として選択しています。
Stencil
{
Ref 2
Comp equal
}
ここまでのShaderを組み合わせていい感じの画像に適応すると下記のようになります。

ここまでの全部を組み合わせてみる
成果物がこちらになります。 かなりやりたいことに近づいてきました。
動画をStencil Shaderで切り抜き pic.twitter.com/5VLKftp39W
— 𝕋𝕒𝕜𝕒𝕙𝕚𝕣𝕠 (@_7cancer) 2022年12月5日
②: キャラクターの描画範囲を絞る
次にキャラクターがボードから出てくる処理をShaderでつくります。
...と思いましたが、ここまででもうできていました。 描画範囲を限定していたDrawRange Shaderを少しいじれば、キャラクターがボードから出てくる処理っぽいものを実装出来そうです。
Shaderをコピペするなどして実装しましょう。
以上で実装については終了です。
実行結果
ではここまでのすべてを組み込んでコンテンツにしましょう。
しました。
アドカレの実装おわった
— 𝕋𝕒𝕜𝕒𝕙𝕚𝕣𝕠 (@_7cancer) 2022年12月5日
ちょっと同期ずれしてる...? pic.twitter.com/1fadu4Zu4c
これで冒頭のARコンテンツが完成しました。
おわりに
今回はUnityのShaderを使って3Dキャラクターが転送されてくるコンテンツを実装してみました。 Shaderは完全に初心者の状態から書いたため、内容に間違いなどありましたら本記事へコメントをつけていただけると幸いです。
参考
- AvatarSample_A - VRoid Hub
- 【シェーダー基礎】Unityでステンシルを使ったマスク表現方法 - 渋谷ほととぎす通信
- そろそろShaderをやるパート6 画像をマスクする
- Unityで3Dオブジェクトのポリゴンの裏表の両面を描画する - MRが楽しい
- 内蔵シェーダのコード解説(3) - Unlit/Transparent Cutout - 強火で進め
- 【Unity】パソコンのカメラからモーションキャプチャをしてAnimationClipを作成する - はなちるのマイノート
ライセンス表記
- © UTJ/UCL