[追記]
2020/06/21 : メモリリークを回避した形のソースコードに修正しました。
2020/07/09 : C#を使ったテクスチャ変換の部分は不要であることがわかりました。映像が伸びてしまう現象はVideo Playerコンポーネント側の設定の問題であり、後日追記します。
#はじめに
自分が手伝っているイベントのVRizeでちょっとshader使いたいねとなったので、一時期ちょっと勉強していた知識を頼りにshaderを書いてみました。ちなみに凄いこと出来るわけではないので凄い人は期待しないでください。
#対象者
shaderの基本的な事を理解している人
やることはUVスクロールなのでそれが理解できれば大丈夫です。
#完成物
なんとなく3 映像でも同じことできるかなって思ってやってみたらいけたので。
— Yothuba (@Yothuba3) May 23, 2020
さっきよりはややこしくてRenderTextureで映像を流して、それをc#でTextureに変換→shaderに渡して加工してる pic.twitter.com/C2xBe4BudO
こんな感じのテストでを作りました。
#まずは画像をスクロールさせてみる
完成物だけ作りたい人はスルーしてください。
適当なplane(Cubeとかでも可)を用意して、以下のshaderコードと共にマテリアルをオブジェクトにアタッチしてください。
_MainTexに適当な画像を指定すればplane上で画像が表示され、スクロールするはずです。
Shader "Unlit/scroll"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_ScrollSpeed ("ScrollSpeed", float) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _ScrollSpeed;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
if(i.uv.y > 0.7){
i.uv.x = i.uv.x + _Time *_ScrollSpeed * -2;
}
else if(i.uv.y > 0.4000){
i.uv.x = i.uv.x + _Time *_ScrollSpeed * 2.5;
//.uv.y = i.uv.y + _Time *_ScrollSpeed * -0.5;
}
else{
i.uv.x = i.uv.x + _Time *_ScrollSpeed;
//i.uv.y = i.uv.y + _Time * _ScrollSpeed;
}
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
仕組みは簡単で、fragmentシェーダ内でuv.x
に時間経過を取得できる_Time
を乗算することでTextureをuvのx方向にスクロールさせています。また、インスペクタから自由に速度を調節できるようにするために_ScrollSpeed
変数を宣言して使用しています。
更に、if文でuv.y
の値に応じて* -2
や* 2.5
などとしてやることで、部分的にスクロール速度が変化するようになっています。
#動画で同じことをする
動画でやる方法ですが、今回はVideoPlayerコンポーネントを用いて動画をRenderTextureとして扱い、それをshaderに渡すことでshaderで動画を扱えるようにします。手順概要は以下の通りです。
- 動画を流す対象として
RenderTexture
をプロジェクトタブから用意します。 - 動画を流したいオブジェクト(今回はplane)にVideo Playerコンポーネントをアタッチします。
3~5はVideoPlayerコンポーネントでの設定 - Video Playerコンポーネントから
Video Clip
に流したい動画を指定。 -
Render Mode > RenderTexture
に変更。 -
Target Texture
に先ほど作成したRenderTexture
を指定します。 - スクリプトで
RenderTexture
の情報をTexture2D
に渡す(スクリプトの詳細は下) - ユニフォーム変数経由でshaderにテクスチャを渡す
- shaderでエフェクトを掛けて描画
スクリプトの説明がまだなので順番が前後してしまいますが、ここまでの設定で以下のようになっていると思います。
6のRenderTextureからTexture2Dへの変換方法は調べたらすぐにでてきました。参考
参考を頼りに実装したスクリプトがこちらです。(毎フレーム処理を行う関係でメモリリークが発生してしまうので若干書き方が違います。)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovieTranslater : MonoBehaviour
{
public RenderTexture renderTextureFromVideo;
private Texture2D textureForShader;
private Material material;
void Start()
{
this.material = GetComponent<Renderer>().material;
textureForShader = new Texture2D(512, 512, TextureFormat.RGB24, false);//テクスチャの初期化
}
void Update()
{
if(renderTextureFromVideo != null)//一応
{
TranslateRenderTex();
this.material.SetTexture("_MainTex",textureForShader);
}
}
void TranslateRenderTex()
{
RenderTexture.active = renderTextureFromVideo;
textureForShader.ReadPixels(new Rect(0,0, renderTextureFromVideo.width, renderTextureFromVideo.height), 0,0);
textureForShader.Apply();
}
}
RenderTextureからTextureに変換する部分は、TranslateRenderTex
メソッドとして定義しています。
毎フレーム動画の映像が焼きこまれたRenderTextureをTexture2Dに変換し、material.SetTexture
でshaderにテクスチャを渡すことで,
好きなように先ほど作ったshaderや、オリジナルのshaderでエフェクトをかけれるようになります。
ここまでの流れの概略図です。
#終わりに
久々にshaderを書いたのでとても楽しかったです。(ほんの数行だけど)
何かあったらコメントで教えていただけると幸いです。