AndroidでComputeShaderのテスト

#環境
Unity5.6.1f1

#概要
AndroidでComputeShaderのテストをしてみました
##追記（2017/06/22）
最近のスマホだとPSIZEがちゃんと動作するみたいなのでコードを変更しました

#スマホ対応状況
以下で確認できます
http://hwstats.unity3d.com/mobile/gpu.html

#OpenGlES3.1の設定
・「BuildSettings」でAndroidに「SwitchPlatform」する
・「PlayerSettings」>「OtherSettings」>「AutoGraphicsAPI」のチェックを外す
・「GraphicAPIs」リストから「OpenGLES2」を削除する
・「RequireES3.1」もしくは「RequireES3.1+AEP」のどちらかにチェックをする

#ComputeShaderの作成
・プロジェクトビューで右クリックし、「Create」>「Shader」>「ComputeShader」を押す

#エディタでエラー「Platform does not support compute shaders」
・解決できないので、エディタ上でテストする時はPCに「SwitchPlatform」する

#プログラム
Assets/TestComputeShader.cs
デフォルトのコンピュートシェーダ（シェルピンスキーのギャスケット的なの）とポイントクラウドです

~~~c#
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;

public class TestComputeShader : MonoBehaviour
{
	[SerializeField] private ComputeShader _computeShader = null;
	[SerializeField] private RawImage _rawImage = null;
	[SerializeField] private Material _materialParticle = null;
	[SerializeField] private RawImage _rawImageCamera = null;
    private RenderTexture _renderTexture = null;
	private ComputeBuffer _particles = null;

	private IEnumerator Start()
	{
		yield return TestDefault();
		yield return TestParticle();
	}

	private void OnDestroy()
	{
		if( _particles != null )
			_particles.Release();
	}

	private IEnumerator TestDefault()
	{
		var groupNum = 32;
		var threadNum = 8;  //computeShaderのnumthreadsの値
		var texSize = groupNum * threadNum;

        _renderTexture = new RenderTexture( texSize, texSize, 0, RenderTextureFormat.ARGB32 );
        _renderTexture.enableRandomWrite = true;
        _renderTexture.Create();
		_rawImage.texture = _renderTexture;

		var kernelIndex = 0;
		var kernelName = "TestDefault";
		kernelIndex = _computeShader.FindKernel( kernelName );
        _computeShader.SetTexture( 0, "Result", _renderTexture );
		_computeShader.Dispatch( kernelIndex, groupNum, groupNum, 1 );

		yield break;
	}

	struct Particle
	{
		Vector3 position;
		Color color;
	};
	private IEnumerator TestParticle()
	{
		var camera = Camera.main;
		var texHeight = 1920;
		var texWidth = texHeight * camera.aspect;
		var renderTexture = new RenderTexture( (int)texWidth, texHeight, 0, RenderTextureFormat.ARGB32 );
		camera.targetTexture = renderTexture;
		_rawImageCamera.texture = renderTexture;

		var groupNum = 4;
		var threadNum = 8;  //computeShaderのnumthreadsの値
		var size = groupNum * threadNum;
		var particleBufferSize = size * size * size;

		_particles = new ComputeBuffer( particleBufferSize, Marshal.SizeOf( typeof( Particle ) ) );

		var kernelIndex = 0;
		var kernelName = "TestParticle";
		kernelIndex = _computeShader.FindKernel( kernelName );
		_computeShader.SetInt( "Size", size );
        _computeShader.SetBuffer( kernelIndex, "Particles", _particles );

		var elements = new float[16];
		while( true )
		{
			var rot = Quaternion.Euler( 0.0f, transform.localRotation.eulerAngles.y + 1.0f, 0.0f );
			transform.localRotation = rot;

			var pos = transform.localPosition;
			pos.z = Mathf.Sin( Time.time ) * 5.0f;
			transform.localPosition = pos;

			var wm = transform.localToWorldMatrix;
			for( int i = 0; i < elements.Length; i++ )
				elements[ i ] = wm[ i ];

			_computeShader.SetFloats( "ObjectToWorld", elements );
			_computeShader.Dispatch( kernelIndex, groupNum, groupNum, groupNum );
			yield return null;
		}

		yield break;
	}

	private void OnRenderObject()
	{
		if( _particles == null )
			return;
		_materialParticle.SetPass( 0 );
		_materialParticle.SetBuffer( "Particles", _particles );
		Graphics.DrawProcedural( MeshTopology.Points, _particles.count );
	}
}
~~~
Assets/ComputeShader.compute

~~~c
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
#pragma kernel TestDefault

RWTexture2D<float4> Result;

[numthreads( 8, 8, 1 )]
void TestDefault( uint3 id : SV_DispatchThreadID )
{
	Result[ id.xy ] = float4( id.x & id.y, ( id.x & 15 ) / 15.0, ( id.y & 15 ) / 15.0, 1.0 );
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
#pragma kernel TestParticle

struct Particle
{
	float3 position;
	float4 color;
};
RWStructuredBuffer<Particle> Particles;
int Size;
float4x4 ObjectToWorld;

[numthreads( 8, 8, 8 )]
void TestParticle( uint3 id : SV_DispatchThreadID )
{
	int index = ( id.z * ( Size * Size ) ) + ( id.y * Size ) + id.x;
	Particles[ index ].position = float3( id.xyz ) / float( Size - 1 ) - 0.5;
	Particles[ index ].position = mul( ObjectToWorld, float4( Particles[ index ].position, 1.0 ) ).xyz;
	Particles[ index ].color = float4( float3( id.xyz ) / float( Size ), 1.0 );
}
~~~
Assets/Particle.shader
\#pragma target 5.0を入れないと正常に表示されません

~~~c
Shader "Custom/TestParticle"
{
	Properties
	{
	}

	SubShader
	{
		Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
		LOD 100
		Blend SrcAlpha OneMinusSrcAlpha

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 5.0
			
			#include "UnityCG.cginc"

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float4 color : COLOR;
				float psize : PSIZE;
			};

			struct Particle
			{
				float3 position;
				float4 color;
			};
			StructuredBuffer<Particle> Particles;

			v2f vert( uint id : SV_VertexID )
			{
				v2f o;
				o.vertex = UnityObjectToClipPos( float4( Particles[ id ].position, 1.0 ) );
				o.color = Particles[ id ].color;
				o.psize = 16.0 / o.vertex.w;
				return o;
			}
			
			fixed4 frag( v2f i ) : SV_Target
			{
				return i.color;
			}
			ENDCG
		}
	}
}
~~~