メモの穴

メモ化

UnityでJSONファイルを読み込むメモ

環境

Windows10
Unity 2019.1.1f1

やること

4パターンの入力ファイルをそれぞれ読み取る。
どのパターンでも流れは同じで以下の通り。

  1. JSONの構造を表現するクラスを用意
  2. 入力ファイルをstring型で受け取り
  3. 1で用意したクラスへデシリアライズ
参考

www.sawalemontea.com

qiita.com

qiita.com

パターン1

シンプルなやつ。

{
  "aaa": "abc",
  "bbb": "150"
}

まずはJSONの構造を表現するためにクラスを作成する。今回は入力の「"aaa": "aaa"」と「"bbb": "150"」に対応させて、string型の変数aaaとint型の変数bbbを持つInputJsonクラスを作成。(クラス名は自由、変数名は入力ファイルのものと同じにする)

[Serializable]
public class InputJson
{
    public string aaa;
    public int bbb;
}

次に入力ファイルをテキストファイルとして読み取り、string型で受け取る。

string inputString = Resources.Load<TextAsset>("input").ToString();

最後にJsonUtility.FromJsonを用いて、先に作成したInputJsonクラスへデシリアライズ

InputJson inputJson = JsonUtility.FromJson<InputJson>(inputString);

以下、C#スクリプト全体

using System;
using UnityEngine;

// 入力されるJSONに合わせてクラスを作成
[Serializable]
public class InputJson
{
    public string aaa;
    public int bbb;
}

public class JsonReader : MonoBehaviour
{
    void Start()
    {
        // 入力ファイルはAssets/Resources/input.json
        // input.jsonをテキストファイルとして読み取り、string型で受け取る
        string inputString = Resources.Load<TextAsset>("input").ToString();
        // 上で作成したクラスへデシリアライズ
        InputJson inputJson = JsonUtility.FromJson<InputJson>(inputString);
        Debug.Log(inputJson.aaa);  // abc
        Debug.Log(inputJson.bbb);  // 150
    }
}

パターン2

ネストした要素。

{
  "aaa": {
    "bbb": "111",
    "ccc": "222"
  }
}

要素に対応したクラスを追加する。

using System;
using UnityEngine;

[Serializable]
public class InputJson
{
    // 追加したAAAクラスの変数aaa。変数名は入力ファイルと揃える。
    public AAA aaa;
}

// 追加
[Serializable]
public class AAA
{
    public int bbb;
    public int ccc;
}

public class JsonReader : MonoBehaviour
{
    void Start()
    {
        string inputString = Resources.Load<TextAsset>("input").ToString();
        InputJson inputJson = JsonUtility.FromJson<InputJson>(inputString);
        Debug.Log(inputJson.aaa.bbb);  // 111
        Debug.Log(inputJson.aaa.ccc);  // 222
    }
}

パターン3

配列。

{
  "aaa": [1, 3, 5, 7, 9]
}
using System;
using UnityEngine;

[Serializable]
public class InputJson
{
    public int[] aaa;
}

public class JsonReader : MonoBehaviour
{
    void Start()
    {
        string inputString = Resources.Load<TextAsset>("input").ToString();
        InputJson inputJson = JsonUtility.FromJson<InputJson>(inputString);
        Debug.Log(inputJson.aaa[3]);  // 7
    }
}

パターン4

オブジェクトの配列。

{
  "aaa": [
    {"bbb": "111", "ccc": "222"},
    {"bbb": "333", "ccc": "444"}
  ]
}
using System;
using UnityEngine;

[Serializable]
public class InputJson
{
    public AAA[] aaa;
}

[Serializable]
public class AAA
{
    public int bbb;
    public int ccc;
}

public class JsonReader : MonoBehaviour
{
    void Start()
    {
        string inputString = Resources.Load<TextAsset>("input").ToString();
        InputJson inputJson = JsonUtility.FromJson<InputJson>(inputString);
        Debug.Log(inputJson.aaa[0].bbb);  // 111
        Debug.Log(inputJson.aaa[1].ccc);  // 444
    }
}


Unityでの入力ファイルの形式って何がよく使われてるんだろうか。

アタポン計算機の機能追加とか

今年の2月くらいに機能追加してGitHubのREADMEは更新してたんだけど、今さらながらこっちにも書いておく。

前回の記事 memonoana.hatenablog.com


アタポン計算機のURL
https://jagpotato.github.io/calc-event/


今の画面。

f:id:memonoana:20181202231008p:plain

ちょっと見た目が変わった。あと累計ptで上位報酬が貰える最低ラインが20000ptから18000ptに変わったので、目標ポイントの初期値を18000にした。


機能追加としては、計算ボタンの左にある三本線のボタンを押すと、スタミナ消費倍率や1曲のプレイ時間など、より細かな設定ができるようになった。

f:id:memonoana:20181202231521p:plain

元々見た目のシンプルな計算機が欲しくてつくったからオプション的に隠してあるんだけど、絶対最初から表示されてた方が使いやすいと思う。

あとゲームの方で営業が追加されてイベントptの獲得手段が増えたけど、こちらではたぶん対応しない。


未だにそこそこアクセスして頂いていてとても嬉しいので何か要望などがあればぜひ。

Unityで音楽に合わせてアレするやつ

Unityで流れている音楽に合わせて何かしたいときのメモ.

Audio

Unityで音楽を再生する場合,Audio Clip,Audio Source,Audio Listenerを使う.ClipをSourceで再生,Listenerで聞き取って,スピーカーから出力.

docs.unity3d.com

tips.hecomi.com

流れている音楽のデータ取得

Audio Source,Audio Listenerの「GetOutputData」メソッドや「GetSpectrumData」メソッドを使うことで,流れている音楽のその瞬間のデータを取得することができる.

Audio Clipにも「GetData」というメソッドがあるけど,これは音源全体のデータを取得するので,リアルタイムに動作させるような目的には向いてなさそう.音楽を再生する前にあらかじめデータが欲しい場合はこれを使う.

GetOutputData

流れている音楽のその瞬間の波形データを取得する.

float[] samples = new float[1024];
int channel = 0;
AudioSource.GetOutputData(samples, channel);

GetSpectrumData

流れている音楽のその瞬間のスペクトルデータを取得する.

スペクトルデータは,その瞬間の音を周波数ごとに分割し,各周波数の音がどれだけ含まれているかを表す.

float[] samples = new float[1024];
int channel = 0;
AudioSource.GetSpectrumData(samples, channel, FFTWindow.BlackmanHarris);


docs.unity3d.com

docs.unity3d.com

tips.hecomi.com

www.sawalemontea.com

ibako-study.hateblo.jp

よく見るやつ

samplesの値をどれか1つ使ったり,合計したり,並べたりするだけでもそれっぽいものができる.

f:id:memonoana:20181006163414g:plain:w300

f:id:memonoana:20181006155016g:plain:w300

Unityでテクスチャ画像をCubeで表現:DrawMeshInstancedIndirect + Compute Shader

前: Unityでテクスチャ画像をCubeで表現:Instantiate - メモの穴


前回はInstantiateを使ってCubeを生成して並べた。

ただこの方法では、実行するととんでもなく遅い。

Cubeを表示せずに実行した場合がこれ。

f:id:memonoana:20180901163316p:plain:w500

FPSが90~100くらいで、SetPass callsが9。

ここから、Cubeを表示すると、

f:id:memonoana:20180901163734p:plain:w500

FPSが8~9くらいで、SetPass callsが32779。

SetPass callsやここでは出ていないDraw callsは、CPUからGPUに向けてどれだけ命令を送っているのかを示す値。

nn-hokuson.hatenablog.com

light11.hatenadiary.com

前回のスクリプトでは、Cubeを表示する際に、テクスチャ画像のピクセル分(128x128)だけInstantiateで生成しているので、当然SetPass callsが多くなる。

パフォーマンスの改善点は他にもあるとは思うけど、今回はこのSetPass callsを減らしてみる。

環境

Windows10
Unity 2018.2.2f1

GPUインスタンシング

同じようなオブジェクトを大量に描画するアプローチとしてGPUインスタンシングというものがある。

docs.unity3d.com

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp


UnityでGPUインスタンシングを扱うにはいくつか方法がある。

gottaniprogramming.seesaa.net

今回はとりあえず上記のサイトに合わせてDrawMeshInstancedIndirectを使ってやってみる。

スクリプト、シェーダー

今回のスクリプトやシェーダーは以下のサイト達を参考にしている。特に1番上のものがベースになっている。同じようなことをやりたい場合はまずこれらのサイトを参考にするといいと思う。

github.com

tips.hecomi.com

gottaniprogramming.seesaa.net

基本的には、制御するためのスクリプトと、描画用のシェーダー(サーフェイスシェーダーや頂点・フラグメントシェーダーなど)が必要で、プラスアルファでコンピュートシェーダーを使う感じ?

今回はCubeの位置をコンピュートシェーダーで計算する。今回みたいにただ並べるだけなら、コンピュートシェーダーを使うメリットは無いような気がするけど、今後の勉強のために使う。

スクリプト

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class CopyImage : MonoBehaviour
{
  [SerializeField]
  private Mesh pixelMesh;           // ピクセルを表現するメッシュ
  [SerializeField]
  private Material pixelMaterial;   // ピクセルのマテリアル(描画用シェーダー)
  [SerializeField]
  private ShadowCastingMode castShadows = ShadowCastingMode.Off;
  [SerializeField]
  private bool receiveShadows = false;
  [SerializeField]
  private ComputeShader positionComputeShader;  // コンピュートシェーダー
  [SerializeField]
  private GameObject image;                     // テクスチャを貼ってるオブジェクト

  private int pixelCount;                 // ピクセル数
  private int positionComputeKernelId;    // カーネルID
  private ComputeBuffer positionBuffer;   // ピクセル位置を格納するバッファ
  private ComputeBuffer colorBuffer;      // ピクセルカラーを格納するバッファ
  private ComputeBuffer argsBuffer;       // DrawMeshInstancedIndirect用のバッファ
  private uint[] args = new uint[] { 0, 0, 0, 0, 0 };  // DrawMeshInstancedIndirect用の配列

  void Start()
  {
    CreateBuffers();
  }

  void Update()
  {
    UpdateBuffers();
    Graphics.DrawMeshInstancedIndirect(pixelMesh, 0, pixelMaterial, pixelMesh.bounds, argsBuffer, 0, null, castShadows, receiveShadows);
  }

  private void CreateBuffers()
  {
    // テクスチャ画像の取得
    Texture2D texture = (Texture2D)image.GetComponent<Renderer>().material.mainTexture;
    // ピクセル数の計算
    int dim = texture.width;
    pixelCount = dim * dim;

    // カーネルIDの取得
    positionComputeKernelId = positionComputeShader.FindKernel("CSMain");

    // バッファの初期化
    argsBuffer = new ComputeBuffer(5, sizeof(uint), ComputeBufferType.IndirectArguments);
    positionBuffer = new ComputeBuffer(pixelCount, 16);
    colorBuffer = new ComputeBuffer(pixelCount, 16);
    
    // テクスチャのピクセルカラーをcolorBufferにセット
    colorBuffer.SetData(texture.GetPixels());

    // 描画用シェーダーにバッファをセット
    pixelMaterial.SetBuffer("positionBuffer", positionBuffer);
    pixelMaterial.SetBuffer("colorBuffer", colorBuffer);

    // argsBufferにメッシュの頂点数とメッシュの数を格納
    uint numIndices = (pixelMesh != null) ? (uint)pixelMesh.GetIndexCount(0) : 0;
    args[0] = numIndices;
    args[1] = (uint)pixelCount;
    argsBuffer.SetData(args);

    // コンピュートシェーダーにバッファ、値をセット
    positionComputeShader.SetBuffer(positionComputeKernelId, "positionBuffer", positionBuffer);
    positionComputeShader.SetFloat("_Dim", dim);
    float cubeScale = image.transform.localScale.x * 10 / dim;
    positionComputeShader.SetFloat("_CubeScale", cubeScale);
    positionComputeShader.SetVector("_Pivot", this.transform.position);
  }

  private void UpdateBuffers()
  {
    // カーネルの実行
    positionComputeShader.Dispatch(positionComputeKernelId, pixelCount / 8, 1, 1);
  }

  void OnDisable()
  {
    // バッファの解放
    if (positionBuffer != null) positionBuffer.Release();
    positionBuffer = null;
    if (colorBuffer != null) colorBuffer.Release();
    colorBuffer = null;
    if (argsBuffer != null) argsBuffer.Release();
    argsBuffer = null;
  }
}

スクリプトでは主に各種バッファ、値の用意とシェーダーへの設定、DrawMeshInstancedIndirectの実行を行う。

Updateの中でカーネルを実行してるけど、今回の目的だと、ただCubeの位置を1度計算するだけなので、Startでやれば十分。この後動かしたりする場合は、Updateで更新される情報をコンピュートシェーダーに渡したりする。

テクスチャ画像のピクセルカラーを取得するのは前回同様GetPixelsでやってるけど、コンピュートシェーダー使ってもできると思う。

コンピュートシェーダー

#pragma kernel CSMain

RWStructuredBuffer<float4> positionBuffer;

float _Dim;         // 1辺のピクセル数
float _PixelScale;  // ピクセル1個のスケール
float3 _Pivot;      // 並べる基準点

[numthreads(8, 1, 1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
  // ピクセル位置の計算
  float2 uv = float2((id.x % (int)_Dim) / _Dim, floor(id.x / _Dim) / _Dim);
  float x = _Pivot.x + uv.x * _PixelScale * _Dim;
  float y = _Pivot.y + uv.y * _PixelScale * _Dim;
  float z = _Pivot.z;
  float4 pos = float4(x, y, z, _PixelScale);
  positionBuffer[id.x] = pos;
}

コンピュートシェーダーでは、ピクセル位置を計算してpositionBufferに格納する。ピクセル1個のスケールもここで格納する。

描画用シェーダー(サーフェイスシェーダー)

Shader "Custom/CopyImage" {
  Properties {
    _MainTex ("Albedo (RGB)", 2D) = "white" {}
    _Glossiness ("Smoothness", Range(0,1)) = 0.5
    _Metallic ("Metallic", Range(0,1)) = 0.0
  }
  SubShader {
    Tags { "RenderType"="Opaque" }
    LOD 200

    CGPROGRAM
    #pragma surface surf Standard addshadow
    #pragma multi_compile_instancing
    #pragma instancing_options procedural:setup
    #pragma target 3.0

    sampler2D _MainTex;
    half _Glossiness;
    half _Metallic;

    struct Input {
      float2 uv_MainTex;
    };

    #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
    StructuredBuffer<float4> positionBuffer;
    StructuredBuffer<float4> colorBuffer;
    #endif

    void setup () {
      #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
      // ピクセル位置の反映
      float4 position = positionBuffer[unity_InstanceID];
      float scale = position.w;
      unity_ObjectToWorld._11_21_31_41 = float4(scale, 0, 0, 0);
      unity_ObjectToWorld._12_22_32_42 = float4(0, scale, 0, 0);
      unity_ObjectToWorld._13_23_33_43 = float4(0, 0, scale, 0);
      unity_ObjectToWorld._14_24_34_44 = float4(position.xyz, 1);
      #endif
    }

    void surf (Input IN, inout SurfaceOutputStandard o) {
      float4 col = 1.0f;
      #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
      // ピクセルカラーの反映
      col = colorBuffer[unity_InstanceID];
      #else
      col = float4(0, 0, 1, 1);
      #endif
      fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * col;
      o.Albedo = c.rgb;
      o.Metallic = _Metallic;
      o.Smoothness = _Glossiness;
      o.Alpha = c.a;
    }
    ENDCG
  }
  FallBack "Diffuse"
}

サーフェイスシェーダーではpositionBufferとcolorBufferを元に、ピクセルの位置や色を反映させる。

実行

f:id:memonoana:20180902231737p:plain:w500

FPSが90~100くらいで、SetPass callsが13。

だいぶ改善できた。

Unityでテクスチャ画像をCubeで表現:Instantiate

Unityでテクスチャ画像のピクセルカラーを取得できる。

それを使って、テクスチャ画像のピクセルカラーをCubeに反映させて並べることで画像を表現する。

環境

Windows10
Unity 2018.2.2f1

スクリプト

// 並べるCube
[SerializeField]
private GameObject pixelCube;
// 最後にまとめて動かすために、親オブジェクトを用意
[SerializeField]
private GameObject output;

void Start () {
  // テクスチャを取得
  Texture2D texture = (Texture2D)GetComponent<Renderer>().material.mainTexture;
  // テクスチャのピクセルカラーを取得
  Color[] texturePixels = texture.GetPixels();
  // 出力先の親オブジェクトの位置
  Vector3 outputPosition = output.transform.position;
  // 出力するcubeのスケール Planeに対するピクセルの比率に合わせる
  float cubeScale = this.transform.localScale.x * 10 / texture.width;
  // cubeの出力位置
  Vector3 pixelPosition;
  // 出力したcubeオブジェクト
  GameObject pixel;
  for (int y = 0; y < texture.height; y++) {
    for (int x = 0; x < texture.width; x++) {
      // cubeの出力位置を決定 親オブジェクトの位置を基準にcubeのスケールずつずらす
      pixelPosition = new Vector3(outputPosition.x + x * cubeScale, outputPosition.y - y * cubeScale, outputPosition.z);
      // cubeの出力
      pixel = Instantiate(pixelCube, pixelPosition, Quaternion.identity, output.transform);
      // 出力したcubeの色を、対応するテクスチャのピクセルカラーに変更
      pixel.GetComponent<Renderer>().material.color = texturePixels[x + texture.width * y];
      // 出力したcubeのスケールを変更
      pixel.transform.localScale = Vector3.one * cubeScale;
    }
  }
  // 出力結果が上下逆になってるので反転
  output.transform.Rotate(180f, 0f, 0f);
}

Texture2D.GetPixels()でテクスチャ画像のピクセルカラーを配列で取得できる。今回用いる画像は128x128ピクセルなので、配列のサイズは16384になる。最終的に画像のピクセル分Cubeを並べる。

f:id:memonoana:20180816161947p:plain:w400

左が元画像で右がCubeを並べたもの。


次 : Unityでテクスチャ画像をCubeで表現:DrawMeshInstancedIndirect + Compute Shader - メモの穴

UnityでScaleの基準点を変更するメモ

Unityで作成したオブジェクトのScaleを変更すると,

(Y軸方向に伸縮)
f:id:memonoana:20180713112235g:plain:w300

オブジェクトの中心を基準として上下に伸縮する.
この場合,Unityで作成したCubeは基準点がオブジェクトの中心になっているから.

これを,

f:id:memonoana:20180713112921g:plain:w300

こうしたい.

方法1:オブジェクトの親子関係を利用する

参考:UnityでObjectのPivotを変更する - 脳汁portal

  1. 空のオブジェクトを作成して,その子要素にScaleを変更したいオブジェクト(今回はCube)を設定する.

    f:id:memonoana:20180713113817p:plain

  2. EmptyのPositionが新しく基準にしたい位置(今回はCubeの底)に来るように,CubeのPositionを変更する.

  3. Emptyを伸縮させると,Cubeが底を基準に伸縮する.

方法2:モデリングツールでオブジェクトの基準点を変更する

そもそも”Unityで”ではなくて元も子もないんだけど,モデリングツールで基準点を好きな位置に設定すればいい.

Unity側では下の画像のところを「Pivot」にしておく.

f:id:memonoana:20180713125319p:plain

方法3:

何かあれば追加