[Unity]GLSLをiPadProで表示する備忘録

Tokyo Demo Fest 2016の会場にて
GLSLをiOSで表示するまでの備忘録。

# GLSL Sandboxたのしい

このサイト
http://glslsandbox.com/
や
https://www.shadertoy.com/
を開いて、気になる絵をクリックすれば
GLSLの世界が待っています。

メガデモ好きにはたまらない。

# 今回はこれをiPadProで表示しようと試みる

http://glslsandbox.com/e#30967.4
![image](https://qiita-image-store.s3.amazonaws.com/0/96808/70b794fc-5083-7de1-4fda-ca7da8d20d02.png)

この顔を表示しようと思う。

Tokyo Demo Fest 2016 GLSL Compoでの@notargs さんの作品が元ネタ。
解像度の落とし方とかも教えてもらいました。

# 試して問題になった点／解決方法
・そのままではiOSで表示できなかった。→グラフィック設定を変更して解決。
・解像度依存でShader処理が行われて、描画フレームレートがでない（なめらかなアニメーションにならない）→Shaderの解像度を下げる

解像度を下げるために
カメラ２つとかレンダーターゲットとか使うのでごちゃごちゃしますので注意。

# UnityでShaderを用意
![image](https://qiita-image-store.s3.amazonaws.com/0/96808/30db9584-356c-271a-27b6-010320e9ceea.png)
Create>ShaderでShaderを作る

中身

``` 
Shader "Custom/GLSL Shader" {

    Properties {
        _MicInput ("_MicInput", Float) = 0
        _Size ("_Size", Float) = 1
        _Speed ("_Speed", Float) = 1
    }

	SubShader {
		Pass {
			GLSLPROGRAM

#ifdef VERTEX
void main()
{
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif

#ifdef FRAGMENT

#define PI 3.1415926535

uniform vec4 _ScreenParams;
uniform vec4 _Time;
uniform float _MicInput;
uniform float _Size;
uniform float _Speed;

float time = _Time.x;
float s = _Size;
float speed = _Speed;
//vec2 resolution = vec2(_ScreenParams.x, _ScreenParams.y);


//uniform float time = _Time.x;
vec2 mouse = vec2(0.5, 0.5);
vec2 resolution = vec2(_ScreenParams.x, _ScreenParams.y);//vec2(560.5, 560.5);

float rand(float a, float b)
{
    return fract(sin(dot(vec2(a, b) ,vec2(12.9898,78.233))) * 43758.5453);
}

void main( void ) {
	time = _MicInput;//_Time.x;

	vec2 pos = ( gl_FragCoord.xy - resolution.xy / 2.) / resolution.y + mouse - 0.5;
	pos.y = -pos.y;
	vec3 color = vec3(0, 0, 0);
	
	for(int i = 0; i < 200; i++)
	{
		float fi = float(i);
		float t  = time;//pow(fract(time * 2.), 0.5);
		float t2 =     floor(time * 2.);
		float a = float(i) / PI;
		float len = s*0.3 + (1. - t) * 0.2 * rand(fi, 0.);
		vec2 p = pos + vec2(cos(a), sin(a)) * len;
		float intencity = pow(0.003 / length(p), 1.5);
		color += intencity * vec3(rand(fi, 1.), rand(fi, 2.), rand(fi, 3.));
	}
	for(int i = 0; i < 20; i++)
	{
		float fi = float(i);
		float t  = pow(fract(time * 2.), 0.1);
		float t2 =     floor(time * 2.);
		float a = float(i - 10) / PI / 5.;
		float len = s*0.3 + (1. - t) * 0.2 * rand(fi, 0.);
		vec2 p = pos + vec2(sin(a), cos(a)) * len;
		p += vec2(0, -0.1);
		float intencity = pow(0.003 / length(p), 1.5);
		color += intencity * vec3(rand(fi, 1.), rand(fi, 2.), rand(fi, 3.));
	}
	for(int i = 0; i < 20; i++)
	{
		float fi = float(i);
		float t  = time;//pow(fract(time * 2.), 0.1);
		float t2 =     floor(time * 2.);
		float a = float(i - 10) / PI;
		float len = s*0.04 + (1. - t) * 0.2 * rand(fi, 0.);
		vec2 p = pos + vec2(sin(a), cos(a)) * len;
		p += vec2(-0.13, -0.05);
		float intencity = pow(0.003 / length(p), 1.5);
		color += intencity * vec3(rand(fi, 1.), rand(fi, 2.), rand(fi, 3.));
	}
	for(int i = 0; i < 20; i++)
	{
		float fi = float(i);
		float t  = time;//pow(fract(time * 2.), 0.1);
		float t2 =     floor(time * 2.);
		float a = float(i - 10) / PI;
		float len = s*0.04 + (1. - t) * 0.2 * rand(fi, 0.);
		vec2 p = pos + vec2(sin(a), cos(a)) * len;
		p += vec2(0.13, -0.05);
		float intencity = pow(0.003 / length(p), 1.5);
		color += intencity * vec3(rand(fi, 1.), rand(fi, 2.), rand(fi, 3.));
	}
	color += vec3(fract(pos.y * 20. + time) < 0.5) * 0.05;
	color += vec3(fract(pos.y * 20. - time) < 0.5) * 0.05;
	gl_FragColor = vec4(color, 1.0);

}
#endif
			ENDGLSL
		}
	}
}
```
ほぼそのままコピーしても動く。
ただMicInputとかSizeとかSpeedとかで変更できるようにちょっといじっています。
あと、あとで問題になったので、y軸反転してます。

環境はUnity5.3.1p4 Macでやってます。

# マテリアルを用意
Shaderを適用したマテリアルを用意

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/612d0c4f-3300-79ec-96b9-8e22329a467c.png)

これで適当なBoxとか作って、マテリアルを指定すれば・・・
![image](https://qiita-image-store.s3.amazonaws.com/0/96808/5fcb3b82-67a7-9424-c6ec-5ccdf340ec0b.png)
と、いう感じで表示されます。

```
void main( void ) {
	time = _MicInput;//_Time.x;
```
この行の_MicInputをやめて_Time.xコメント側にして、Unity実行すれば、時間変化もします。

# iOSではそのままでは表示されない
初期状態のままiPad側でみるとピンクの画面がでてしまいます。
たぶんShaderが処理できていない。ので

PlayerSettingのAuto Graphics APIのチェックを外して
OpenGLES2、Metalの順にしています。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/177a46d6-c67a-623f-fe35-2ef35a8cae66.png)

# iPadProだとカクカクする
iPadProの解像度でのレンダリングはさすがにスペック的に厳しいので、
軽くするテクニックを教わりました。

直接Shaderが動くとすべてのピクセル処理しようとするので大変。
そこで、解像度を落としてごまかします。

ここでは、カメラを２つ使います。低い解像度のカメラと通常のメインカメラ。

## 低い解像度用のカメラを用意
![image](https://qiita-image-store.s3.amazonaws.com/0/96808/366737c1-132f-a849-122d-78b9113d6822.png)

今回のShader(Smile)をカメラでとらえます。
このカメラはSmileレイヤーしか撮影しないように設定し、
Qualに先ほどのShaderを貼り、LayerをSmileにしておきます。

## 低い解像度用のカメラの結果をテクスチャにする

Create>Rander Textureを作り・・・

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/ffa42881-bb26-7990-7ed8-559abf367221.png)

Cameraのtarget Textureに設定
![image](https://qiita-image-store.s3.amazonaws.com/0/96808/dcc146bd-96ab-8d34-4b85-8c310e46d23b.png)

サイズを512x512くらいにしておく
![image](https://qiita-image-store.s3.amazonaws.com/0/96808/ca01c617-c074-1306-659d-bfa2d6f3942a.png)

# メインカメラを用意
![image](https://qiita-image-store.s3.amazonaws.com/0/96808/cf10bf0f-a0fc-97c6-dfea-10e30ed681d9.png)

メインカメラは普段のカメラと同じように扱います。

### テクスチャのマテリアルを用意する
さきほどの低い解像度のカメラの撮影結果をもつマテリアルを用意し、Quadに貼り付けます。

![image](https://qiita-image-store.s3.amazonaws.com/0/96808/979c323a-f4d2-9ce2-20b5-b3491039a9b9.png)

メインカメラでQuadを表示すれば、画面一杯に引き伸ばされた512x512テクスチャの別カメラでとらえたShaderの結果が表示されます。

これで、最終レンダリングサイズに依存しない固定サイズでのShaderの結果を得られるので、速度重視な場合に使えます。（画質は落ちるけど、仕方ないね）


# おまけ　Micの入力で動かしたかった

スクリプトからShaderのパラメータを弄ってみます。
とりあえずタッチでいろいろ変わるようにしています。

```
using UnityEngine;
using System.Collections;

public class MicInput : MonoBehaviour
{
    AudioSource aud;

    // Use this for initialization
    void Start()
    {
        foreach (string device in Microphone.devices) {
            Debug.Log("Name: " + device);
        }

        aud = GetComponent<AudioSource>();

        aud.clip = Microphone.Start(null, true, 10, 44100);  // マイクからのAudio-InをAudioSourceに流す
        aud.loop = true;                                      // ループ再生にしておく
        aud.mute = true;                                      // マイクからの入力音なので音を流す必要がない
        while (!(Microphone.GetPosition("") > 0))
        {
        }             // マイクが取れるまで待つ。空文字でデフォルトのマイクを探してくれる
        aud.Play();                                           // 再生する

        Debug.Log("Mic Start");    

        Renderer renderer = this.gameObject.GetComponent<Renderer>();
        material = renderer.sharedMaterial;//.GetComponent<Material>();
    }
    Material material;
	
    float size = 1;

    void Update()
    {
        float vol = Input.acceleration.x;//GetAveragedVolume();
        //Debug.Log(vol.ToString());    

        #if UNITY_EDITOR_OSX
        {

            //if(vol == 0)
            {
                vol = Input.mousePosition.x/Screen.width;
                vol += Mathf.Sin(Time.timeSinceLevelLoad*20f)*0.05f;
            }

            material.SetFloat("_Size",Input.mousePosition.y/Screen.height);
            material.SetFloat("_Speed",Input.mousePosition.x/Screen.width);
        }

        #else
        {  
            if(Input.touchCount == 1)
            {
                {
                    size = Input.touches[0].position.x/Screen.width;
                }
            }
            if(Input.touchCount == 2)
            {
                material.SetFloat("_Speed",Input.touches[1].position.y/Screen.height);
            }

            vol += Mathf.Sin(Time.timeSinceLevelLoad*20f)*0.05f;
        }
        #endif 

        material.SetFloat("_Size",size);
        material.SetFloat("_MicInput",vol);//Mathf.Sin(Time.timeSinceLevelLoad));
    }

    float GetAveragedVolume()
    { 
        float[] data = new float[256];
        float a = 0;
        aud.GetOutputData(data, 0);
        foreach (float s in data)
        {
            a += Mathf.Abs(s);
        }
        return a / 256.0f;
    }
}
```

なんだか、Unity5.3.1だとMic入力しても音量0でしか来なかったので加速度で置き換えてしまってます。
（iOSの問題か、Unityの問題か切り分けできていない）