AndroidでComputeShaderのテスト

この記事は2017年06月15日にqiitaに投稿した内容です。

環境

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 デフォルトのコンピュートシェーダ(シェルピンスキーのギャスケット的なの)とポイントクラウドです

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

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
#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を入れないと正常に表示されません

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
        }
    }
}