Aizu-Progressive xr Lab blog

会津大学のVR部であるA-PxLの部員が持ち回りで投稿していくブログです。部員がそれぞれVRに関する出来事やVRにちなんだことについて学んだことを書いていきます。

連絡はサークルTwitterのDMへお願いします。
「面白法人カヤックVR分室」としても活動しています。詳細はこちら

unityでよく使うC#のクラス、メソッド、などの話

f:id:aizu-vr:20210426010930p:plain

はじめまして、学部一年の渡辺です。 初めてのブログということで、自分のような初心者の方に役立ちそうなテーマについて書こうと思います。 と、言ってもまだunityを触り始めて半年弱(モデリングにも挑戦したりしていたので実質3か月くらい)なので、間違いがあるかもしれません。その場合、指摘してくださるとうれしいです。(のちのち画像等追加します)

よく使うもの

はじめに

私は大学でC、サークルでC#を同時にやったため、オブジェクト指向型についての理解をちゃんとせずに勉強してしまったため、かなり学ぶ上で混乱が生じました。 そのためまずオブジェクト指向型の理解をすべきだと思います。

クラス

Input

まずゲームといえばプレイヤーをコントロールしなければなりません。 コントローラーなりキーボードなりの入力を受け付ける。

メソッド 説明
Input.GetKey 押している間
Input.GetKeyDown 押した瞬間
Input.GetKeyUp 離した瞬間
Input.GetMouseButton(0) 左ボタン
Input.GetMouseButton(1) 右ボタン
Input.GetMouseButton(2) 中ボタン

使用例

if (Input.GetKey(KeyCode.W))

Input.GetAxis() float型で返す

使用例

void Update()
{
  float h = horizontalSpeed * Input.GetAxis("Mouse X");
  float v = verticalSpeed * Input.GetAxis("Mouse Y");

  transform.Rotate(v, h, 0);
}

Edit -> Project Settings -> InputでInputManagerが見れます。

GameObject

ゲーム内のオブジェクトを扱うクラス

変数 説明
transform ゲームオブジェクトの位置
layer ゲームオブジェクトのレイヤー
tag ゲームオブジェクトのタグ
name 名前

Transform

オブジェクトの位置、回転、スケールを扱うクラス

変数 説明
position ワールド座標における位置(Vector3)
rotation ワールド座標における方向(Quaternion)
localScale 大きさ
forward 物体が向いている方向
  • transform.LookAt(target((Transform target)));targetに向かせる

SceneManager

シーンの切り替え等に利用できます。 タイトル画面やリザルト画面を作るときに必要になるかと。

使用する際は using UnityEngine.SceneManagementと宣言する必要があります。またFile->BuildSettingsでシーンの登録をする必要があります。

使用例

SceneManager.LoadScene("シーン名");

Rayの使い方


Rayは光線という意味ですが、unityでもよく使います。 ある点から光線を出し、当たったオブジェクトの情報を取得できます。 いろいろとややこしいですが、覚えるとかなり便利かと思います。

Ray

Rayの宣言

Ray(origin(原点), direction(方向))

使用例

Ray ray = new Ray(transform.position, transform.forward);

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

RaycastHit

Rayの当たったものの情報を得る。

変数 説明
transform 衝突したコライダーまたは Rigidbody の Transform
collider ヒットしたコライダー
distance レイの原点から衝突点までの距離

Physics.Raycast

Physics.Raycastには二つ方法があります。

  1. Physics.Raycast(原点, 方向, out hit*1, Rayの長さ, 当たるレイヤー);
  2. Physics.Raycast(ray*2, out hit, Rayの長さ, 当たるレイヤー);

Rayの長さは指定しない場合無限になります。 レイヤーは指定しない場合すべてのレイヤーが対象です。

当たり判定


OnCollisionEnter関数

衝突したかどうかを判断する関数です。

void OnCollisionEnter(Collision 当たった物体)
{
  Debug.Log(当たった物体.gameObject.name);
}

OnTriggerEnter関数

物体のColliderのIsTriggerにチェックを入れた場合、衝突しなくなりすり抜けます。 その場合の当たり判定はOnTriggerEnter関数を使います。

void OnTriggerEnter(Collider 入った物体)
{
  Debug.Log(入った物体.gameObject.name);
}

最後に


今までコピペしてそれっぽいスクリプトを書いていましたが、今後は自力で書けるようになりたいと思い、この記事を書かせていただきました(というかほぼメモ)。同じような方のお力になれていたら幸いです。 学んだことがあったらこれからも追加していきたいと思います。

参考にさせていただいたサイト


*1:RaycastHit hit

*2:Ray ray

開発者視点のVR酔い対策

f:id:aizu-vr:20201026124223p:plain 学部2年の星野です。自分が大体のもので酔いやすいこともあり、VRコンテンツを制作する際にVR酔いについてはかなり注意しているつもりです。今回は開発者視点におけるVR酔いの対策について書いていきたいと思います。

VR酔いとは

  • VRを体験している途中や後で、吐き気や頭痛などの症状がでること。

  • 症状としてはほぼ車酔いと同じ

  • 原因としてはいくつか説があるが、視覚と感覚のずれによるものという説が有力(車酔いと同じ)

自分自身も何回かなったことがありますが、その時はかなりキツかったです。(もちろん状況や人によって症状は変わります。)

VR酔いの対策

fpsとリフレッシュシートを確保する
  • fpsはアプリ側、リフレッシュシートはヘッドセット側の映像更新の頻度を表す

  • ちゃんと確保しないと映像が遅延するため酔いやすくなる

  • ときには解析度を犠牲にしてでもfpsを確保

  • VRを違和感なく進行するためには90fps以上必要

次の動きを予想させる
  • 視点や画面が大きく切り替わる前に予告を出したりフェードインやフェードアウトを使うことで、脳に準備をさせる
移動の実装には注意する
  • テレポート

  • コックピットなどの表示(周辺視野を隠す)

  • 移動させない(ルームスケールの範囲内)

実践

自分の参加しているチーム開発のカメラや移動のシステムを担当しました。 下の動画は現在の進捗です。

youtu.be

次の動きを予想させる→フェードイン、アウトを使う。 移動の実装→テレポート

そのおかげか、気持ち悪さや酔いを感じたことはなかったです。(慣れもあるかもしれません)

まとめ

今後もVRコンテンツを制作する時には上であげたことに注意していきたいと思います。

【Odin Inspector】NPCの振る舞いをインスペクターで設定する

はじめに

こんにちは、学部3年の木村です。

タイトルの通り、ゲームに登場するNPCの行動をインスペクターで設定できるようにします。

想定するゲームにはNPCとしてMonster(獅子舞や仏像)が存在し、 各NPCは現在の状態に応じて探索や追跡などの行動をとることができます。
この場合、Dictionaryを使って状態と行動を対応付けたいのですがUnityのInspectorではGenericsを扱えないため、アクセス修飾子をpublicにしたり属性にSerializeFieledを付けたりしてもInspectorには表示されません。抽象クラスやインターフェースも同様です。

そこで、Odin - Inspector and Serializerというアセットを使用しました。

Odinとは

OdinはUnityのInspectorを使いやすくするプラグインです。

導入するだけでList等の表示がいい感じになったり、 (多分)何でもシリアライズできるようになったり、便利な属性が80以上使えるようになります。odininspector.comではどんなことができるようになるかがアニメーション付きで紹介されています。

$55の有料アセットですが、ゲーム制作中のパラメータ調整などが圧倒的に快適になるとても便利なアセットです。

本編

一部省略していますが、 大体以下のような構成です。

Monsterが継承しているSerializedMonoBehaviourは、Dictionary等もシリアライズできる(Inspectorで設定できる)MonoBehaviourです。元のMonoBehaviourの機能はそのまま使えます。

Inspectorでは次のように表示されます。(クラス図ではMonsterに定義しているActionsTableMonsterActionRepositoryで定義されています。)

DictionaryがInspectorに表示されており、Keyに状態(MonsterState)、Valueにその状態のときにとる行動(抽象クラスNpcActionの実装)を指定することでNPCの振る舞いを設定できます。

ついでに

OdinのButton属性を使って、InspectorのボタンからMonsterの状態を変更できるようにしています。

f:id:aizu-vr:20200921050826g:plain

public bool _enableDebug;

[Button, ShowIf(nameof(_enableDebug)), EnableIf(nameof(_enableDebug))]
private void ChangeState(MonsterState state)
{
    // 状態をstateに変更
}

終わり

Odinの紹介と活用例でした。
ゲーム制作中にUnityのインスペクターを使っていると微妙に手の届かないかゆいところがあり、自分でエディタ拡張を書いていたら沼にハマり肝心のゲーム制作が進まないということがあるかもしれません。そんなときにOdinはかゆいところをいい感じにかいてくれます。
ちょっと高めのアセットですがUnityがとても快適になるので、よかったら導入してみてはいかがでしょうか。

三次元極座標系でパーティクル

学部4年のユムルです。

最近Shaderをかいたり、VRのMV (Music Video) を作ったりして遊んでます。
作った物をVRSNSのVRChatにて人に見せたりして楽しんでいるのですが、最近ブタジエン(https://twitter.com/butadiene121)さんのシェーダーパーティクルに魅せられて、真似して次のようなシェーダーパーティクルを作りました。

これの解説をします。

パーティクルとして使うメッシュと表現の決定

今回はCubeのメッシュをもとに、次の表現を使うことにします。

f:id:aizu-vr:20200904223913p:plain
一つのパーティクル表現

書いたShaderは次のものです。

Shader "PolarParticle/Cube1" {
  Properties { }
  SubShader {
    Tags { "RenderType"="Transparent" "Queue"="Transparent" } // 透明
    Cull off
    Blend SrcAlpha One
    ZWrite Off
    LOD 100

    Pass {
      CGPROGRAM
      #pragma target 5.0
      #pragma vertex VS
      #pragma fragment FS
      
      #include "UnityCG.cginc"

      struct VS_OUT {
        float4 vertex : SV_Position;
        float4 texcoord : TEXCOORD0;
        float4 color : COLOR;
      };

      VS_OUT VS(appdata_full v) { 
        VS_OUT o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.texcoord = v.texcoord;
        o.color = float4(0, 1, 1, 1);
        return o;
      }

      float4 FS(VS_OUT i) : SV_Target {
        float2 uv = i.texcoord.xy;
        float2 p = uv * 2.0 - 1.0;
        float t = 0.01 / min(abs(abs(p.x) - 1.0), abs(abs(p.y) - 1.0));
        t = saturate(t);
        return float4(i.color.xyz, t);
      }
      ENDCG
    }
  }
}

メッシュの大量複製

メッシュをパーティクルとして扱うために、メッシュを大量にコピーしたいです。

私はこれをするために2種類の方法を知っています。 普通は次の方法1を使えば良いと思いますが、VRChatではそれをすることはできないので方法2を使いました。

それぞれの方法で試しに100万個のCubeを表示してみますが、後につくる極座標のパーティクルは1024個で作成します。

方法1

次の記事を参考にします。 shitakami.hatenablog.com feelingames.hatenablog.com

次のコードを書きます。

using UnityEngine;

public class GPUInstance : MonoBehaviour {
  public Mesh mesh;
  public Material material;
  public Bounds bounds;
  public int instanceNum;
  public ComputeBuffer buffer;

  void Start() {
    var args = new uint[5] { 0, 0, 0, 0, 0 };
    args[0] = mesh != null ? (uint) mesh.GetIndexCount(0) : 0;
    args[1] = (uint) instanceNum;
    buffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
    buffer.SetData(args);
  }

  void Update() {
    Graphics.DrawMeshInstancedIndirect(
      mesh,
      0,
      material,
      bounds,
      buffer);
  }

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

取り敢えず大量のCubeを規則正しく並べるシェーダーを書きます、

Shader "PolarParticle/ManyCube" {
  Properties { }
  SubShader {
    Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    Cull off
    Blend SrcAlpha One
    ZWrite Off
    LOD 100

    Pass {
      CGPROGRAM
      #pragma target 5.0
      #pragma vertex VS
      #pragma fragment FS
      
      #include "UnityCG.cginc"

      struct VS_OUT {
        float4 vertex : SV_Position;
        float4 texcoord : TEXCOORD0;
        float4 color : COLOR;
      };

      static uint particleNum = 1000000; // パーティクルの数
      VS_OUT VS(appdata_base v, uint instanceId : SV_InstanceID) {
        VS_OUT o;
        uint id = instanceId;  // パーティクルのID
        uint n = pow(particleNum, 1.0 / 3.0);
        float particleSize = 0.1; // パーティクルのサイズ
        float interval = 1; // パーティクル間の距離
        float zoneLength = (n-1) * interval; // 全体の範囲の長さ
        uint3 axisId = uint3( // 極ごとのID
          id % n,
          id / n % n,
          id / (n * n));
        float3 pos = (float3)(-zoneLength / 2) + axisId * interval;
        v.vertex.xyz = v.vertex.xyz * particleSize + pos;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.texcoord = v.texcoord;
        o.color = float4(0, 1, 1, 1);
        return o;
      }

      float4 FS(VS_OUT i) : SV_Target {
        float2 uv = i.texcoord.xy;
        float2 p = uv * 2.0 - 1.0;
        float a = 0.01 / min(abs(abs(p.x) - 1.0), abs(abs(p.y) - 1.0));
        a = saturate(a);
        return float4(i.color.rgb, a);
      }
      ENDCG
    }
  }
}

書いたシェーダからマテリアルを作成し、GameObjectに先ほどのC#のコードをアタッチして、次のように100万個のCubeが出るように設定します。

f:id:aizu-vr:20200905185451p:plain

シーンを再生すると、100万個のCubeの位置が頂点シェーダーによって決められているのを確認できます。 f:id:aizu-vr:20200905185554p:plain

後のパーティクルの為、今回はInstance Numを1024に設定してください。

方法2

VRChatでメッシュのパーティクルを使う場合は、C#によるスクリプトを組み込んだ作品をアップロードできません。したがって、方法1も使えません。 代わりに間接的にC#を用いて、Meshを複製してアセットファイル化するスクリプトを書き、更にジオメトリシェーダーの[instance(31)]にて31倍にメッシュを増やします。

メッシュを複製したアセットファイルを作成するため、C#のエディタ拡張を書きます

using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;

public class MeshCopy : EditorWindow {
  [MenuItem("MyEditor/MeshCopy")]
  static void Open() {
    GetWindow<MeshCopy>("MeshCopy");
  }

  Mesh useMesh;  // コピー元のメッシュ
  int copyN; // 複製する数

  void OnGUI() {
    useMesh = (Mesh) EditorGUILayout.ObjectField("Use Mesh", useMesh, typeof(Mesh), false);
    copyN = EditorGUILayout.IntField("Copy Num", copyN);
    if (GUILayout.Button("Mesh N Copy")) {
      MeshNCopy();
    }
  }

  void MeshNCopy() {
    var vertices = new List<Vector3>();
    var texcoord = new List<Vector3>();
    var triangles = new List<int>();
    var normals = new List<Vector3>();
    var tangents = new List<Vector4>();
    var colors = new List<Color>();
    // ここのループでメッシュをコピー、texcoordのz値にidを入れておく
    for (int i = 0; i < copyN; i++) {
      vertices.AddRange(useMesh.vertices);
      texcoord.AddRange(useMesh.uv.Select(v => new Vector3(v.x, v.y, i)));
      triangles.AddRange(useMesh.triangles
        .Select(index => index + i * useMesh.vertexCount));
      normals.AddRange(useMesh.normals);
      tangents.AddRange(useMesh.tangents);
      colors.AddRange(useMesh.colors);
    }
    var mesh = new Mesh();
    mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
        // indexFormatを変えておかないとtrianglesの値の上限を余裕で超えてしまう
    mesh.SetUVs(0, texcoord);
    mesh.triangles = triangles.ToArray();
    mesh.normals = normals.ToArray();
    mesh.tangents = tangents.ToArray();
    mesh.colors = colors.ToArray();
    string path = string.Format("Assets/{0}x{1}.asset", useMesh.name, copyN);
    AssetDatabase.CreateAsset(mesh, path);
    AssetDatabase.SaveAssets();
  }
}
#endif

Unity上部のメニューにある、MyEditor -> MeshCopyを開き、Cubeのメッシュを設定し、後でメッシュを更に31倍するので、Copy Numを100万 / 31 = 約33000個 に設定しボタンを押します。
すると、Assets直下に33000個のCubeメッシュのアセットファイルが生成されます。
とはいえこの時点で、メッシュファイルのサイズは81MBになってしまうので、実用的に考えるならCube1024個で2MBくらいのものになるでしょう。

f:id:aizu-vr:20200904234839p:plain

Shaderの方は次のようです。こちらもcubeを規則正しく並べておきます。 メッシュのサイズを抑え、ジオメトリシェーダのinstanceにて更に31倍までポリゴンを増やすことができます。 頂点シェーダーVSは値をそのまま返すのみにし、ジオメトリシェーダGSにて頂点位置を決定します。

Shader "PolarParticle/ManyCube2" {
  Properties { }
  SubShader {
    Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    Cull off
    Blend SrcAlpha One
    ZWrite Off
    LOD 100

    Pass {
      CGPROGRAM
      #pragma target 5.0
      #pragma vertex VS
      #pragma geometry GS
      #pragma fragment FS
      
      #include "UnityCG.cginc"

      struct GS_OUT {
        float4 vertex : SV_Position;
        float4 texcoord : TEXCOORD0;
        float4 color : COLOR;
      };

      appdata_base VS(appdata_base v) { return v; }

      #define instanceN 31
      [instance(instanceN)]
      [maxvertexcount(3)]
      void GS(triangle appdata_base i[3], inout TriangleStream<GS_OUT> stream, uint gsid : SV_GSInstanceID) {
        for (int j = 0; j < 3; j++) {
          appdata_base v = i[j];
          GS_OUT o;
          uint id = gsid + instanceN * v.texcoord.z;  // パーティクルのIDを計算
          uint n = pow(33000 * instanceN, 1.0/3.0);
          float particleSize = 0.1;
          float interval = 1;
          float zoneLength = (n-1) * interval;
          uint3 axisId = uint3(
            id % n,
            id / n % n,
            id / (n * n));
          float3 pos = (float3)(-zoneLength / 2) + axisId * interval;
          v.vertex.xyz = v.vertex.xyz * particleSize + pos;
          o.vertex = UnityObjectToClipPos(v.vertex);
          o.texcoord = v.texcoord;
          o.color = float4(0, 1, 1, 1);
          stream.Append(o);
        }
        stream.RestartStrip();
      }

      float4 FS(GS_OUT i) : SV_Target {
        float2 uv = i.texcoord.xy;
        float2 p = uv * 2.0 - 1.0;
        float a = 0.01 / min(abs(abs(p.x) - 1.0), abs(abs(p.y) - 1.0));
        a = saturate(a);
        return float4(i.color.rgb, a);
      }
      ENDCG
    }
  }
}

SkinnedMeshRendererにてBoundsを設定しつつ使います。

方法1との違いは、頂点シェーダーでなくジオメトリシェーダを使うこと、idの設定に一手間加えることです。
以降のシェーダーのコードは方法1を使う場合のコードとします。もし、VRChatのために方法2を使う人は読み変えてください。

後のパーティクルの為、今回は1024個のパーティクルを表示する為に1024/16 = 64個のCubeメッシュを生成し、後でジオメトリのinstanceにて16倍するように設定してください。

極座標系によるパーティクルの位置決定

x座標、y座標をもとに、座標を決定する座標系を直交座標と言いますが、
極座標系という座標系も存在します。

極座標系は原点からの距離rと、x軸から反時計回りへの角度θによる(r, θ)で座標を決定します。

mathtrain.jp

原点からのrをsinやcosなどの三角関数と共に角度によって適当に決めるようにすると、綺麗な模様が描けます

www.desmos.com

二次元の極座標系(r, θ)を直交座標系(x, y)に変換する式は次の通りです

x = r cosθ
y = r sinθ

今回のパーティクルではこれを三次元で考えます。

mathtrain.jp

三次元極座標は、原点からの距離r、角度はz軸からの角度θとx軸からの角度φによる(r, θ, Φ)によって決まるようです。 今回はUnityのシェーダーで実装しますから、数学でz軸であるところはy軸、y軸のところがz軸であると考えます。 したがって、原点からの距離r、y軸からの角度θ, x軸からの角度Φで考え、 三次元直交座標系(x, y, z)への変換は次のようです。

x = r sinθ cosφ
y = r cosθ
z = r sinθ sinφ

ではシェーダを書いていきます。
パーティクルのidをもとに、パーティクルの位置を極座標系を用いて算出する関数を用意します。
最初に極座標系(r, θ, Φ)の変数と直交座標系への変換のコードを書いておきます。

#define PI UNITY_PI
void particleTransform(uint id, out float3 pos) {
  float r = 1;
  float theta = 0;
  float phi = 0;
  pos = float3(
    r * sin(theta) * cos(phi),
    r * cos(theta),
    r * sin(theta) * sin(phi));
}

さて、二次元極座標において適当なグラフを示したときはr をθをもとに決定していましたが、
今回はidの値が元になります。
したがって、idの値を適当に変数 tに入れて扱うことにします。 `` float t = id * 0.01; θをπ / 2にしておくと、phiの値によりxz座標上の値になります

float t = id * 0.01;
float r = 1;
float theta = PI / 2;
float phi = t;

また、作成した関数を頂点シェーダに適応します。

VS_OUT VS(appdata_base v, uint instanceId : SV_InstanceID) {
  VS_OUT o;
  uint id = instanceId;
  float particleSize = 0.05;
  float3 pos;
  particleTransform(id, pos);
  v.vertex.xyz = v.vertex.xyz * particleSize + pos;
  o.vertex = UnityObjectToClipPos(v.vertex);
  o.texcoord = v.texcoord;
  o.color = float4(0, 1, 1, 1);
  return o;
}

f:id:aizu-vr:20200905191523p:plain

θの値を適当なサイン波にしてみます。
float theta = PI / 2 + sin(5.5 * t) * 0.2;

f:id:aizu-vr:20200905191558p:plain

Φの値も適当なサイン波にしてみます。
float phi = sin(t) * 3 * PI;

f:id:aizu-vr:20200905191621p:plain

θのサイン波の大きさを敢えて小さくしていましたが、試しに大きくしてみます。
float theta = PI / 2 + sin(5.5 * t) * 0.7;

f:id:aizu-vr:20200905191832p:plain

止まっていてもつまらないので、時間によって変化するようにします。
Φの値を時間によって変化させてみます。
float phi = sin(t) * 3 * PI + sin(t + _Time.y * 0.3) * 3;

f:id:aizu-vr:20200905193044g:plain

なかなかかっこよくなってきました。

tの値、rの値も時間によって変化させてみます。
float t = id * 0.01 + _Time.y * 0.1;
float r = 0.2 + sin(3 * t);

f:id:aizu-vr:20200905193744g:plain

パーティクルが全て同じ方向を向いているので、回転の設定をします。

wgld.org

この記事を参考に、回転の関数を書きます。行列のままの方が扱いやすいので行列のまま値の受け渡しを行います。

float3x3 rotate(float angle, float3 axis) {
  float3 a = normalize(axis);
  float s = sin(angle);
  float c = cos(angle);
  float r = 1.0 - c;
  return float3x3(
    a.x * a.x * r + c, a.y * a.x * r + a.z * s, a.z * a.x * r - a.y * s,
    a.x * a.y * r - a.z * s, a.y * a.y * r + c, a.z * a.y * r + a.x * s,
    a.x * a.z * r + a.y * s, a.y * a.z * r - a.x * s, a.z * a.z * r + c);
}

また、サイズと色の設定も適当に設定します。

#define PI UNITY_PI
void particleTransform(uint id, out float3 pos, out float3x3 rot, out float size, out float3 color) {
    // out 変数追加
  float t = id * 0.01 + _Time.y * 0.1;;
  float r = 0.2 + sin(3 * t);
  float theta = PI / 2 + sin(5.5 * t) * 0.7;
  float phi = sin(t) * 3 * PI + sin(t + _Time.y * 0.3) * 3;
  pos = float3(
    r * sin(theta) * cos(phi),
    r * cos(theta),
    r * sin(theta) * sin(phi));
  // rot, size, color設定
  rot = mul(rotate(theta, float3(1, 0, 0)), rotate(phi - _Time.y * 3, float3(0, 1, 0)));
  size = cos(5.3 * t + _Time.y * 3.0) * 0.05;
  color = float3(sin(2.4 + t + _Time.y * 0.6), sin(0.7 + 2 * t - _Time.y * 0.6), sin(3.0 * t + _Time.y * 0.7)) / 2 + 0.5;
}

頂点シェーダも書き変えます。

VS_OUT VS(appdata_base v, uint instanceId : SV_InstanceID) {
  VS_OUT o;
  uint id = instanceId;
  float particleSize = 0.05;
  float3 pos;
  float3x3 rot;
  float size;
  float3 color;
  particleTransform(id, pos, rot, size, color);
  v.vertex.xyz = mul(rot, v.vertex.xyz * size) + pos;
  o.vertex = UnityObjectToClipPos(v.vertex);
  o.texcoord = v.texcoord;
  o.color = float4(color, 1);
  return o;
}

f:id:aizu-vr:20200905194539g:plain

かっこいいのができあがりました。

コードの全体は次のようです。

Shader "PolarParticle/Test" {
  Properties { }
  SubShader {
    Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    Cull off
    Blend SrcAlpha One
    ZWrite Off
    LOD 100

    Pass {
      CGPROGRAM
      #pragma target 5.0
      #pragma vertex VS
      #pragma fragment FS
      
      #include "UnityCG.cginc"

      struct VS_OUT {
        float4 vertex : SV_Position;
        float4 texcoord : TEXCOORD0;
        float4 color : COLOR;
      };

      float3x3 rotate(float angle, float3 axis) {
        float3 a = normalize(axis);
        float s = sin(angle);
        float c = cos(angle);
        float r = 1.0 - c;
        return float3x3(
          a.x * a.x * r + c, a.y * a.x * r + a.z * s, a.z * a.x * r - a.y * s,
          a.x * a.y * r - a.z * s, a.y * a.y * r + c, a.z * a.y * r + a.x * s,
          a.x * a.z * r + a.y * s, a.y * a.z * r - a.x * s, a.z * a.z * r + c);
      }

      #define PI UNITY_PI
      void particleTransform(uint id, out float3 pos, out float3x3 rot, out float size, out float3 color) {
        float t = id * 0.01 + _Time.y * 0.1;;
        float r = 0.2 + sin(3 * t);
        float theta = PI / 2 + sin(5.5 * t) * 0.7;
        float phi = sin(t) * 3 * PI + sin(t + _Time.y * 0.3) * 3;
        pos = float3(
          r * sin(theta) * cos(phi),
          r * cos(theta),
          r * sin(theta) * sin(phi));
        rot = mul(rotate(theta, float3(1, 0, 0)), rotate(phi - _Time.y * 3, float3(0, 1, 0)));
        size = cos(5.3 * t + _Time.y * 3.0) * 0.05;
        color = float3(sin(2.4 + t + _Time.y * 0.6), sin(0.7 + 2 * t - _Time.y * 0.6), sin(3.0 * t + _Time.y * 0.7)) / 2 + 0.5;
      }

      VS_OUT VS(appdata_base v, uint instanceId : SV_InstanceID) {
        VS_OUT o;
        uint id = instanceId;
        float particleSize = 0.05;
        float3 pos;
        float3x3 rot;
        float size;
        float3 color;
        particleTransform(id, pos, rot, size, color);
        v.vertex.xyz = mul(rot, v.vertex.xyz * size) + pos;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.texcoord = v.texcoord;
        o.color = float4(color, 1);
        return o;
      }

      float4 FS(VS_OUT i) : SV_Target {
        float2 uv = i.texcoord.xy;
        float2 p = uv * 2.0 - 1.0;
        float a = 0.01 / min(abs(abs(p.x) - 1.0), abs(abs(p.y) - 1.0));
        a = saturate(a);
        return float4(i.color.rgb, a);
      }
      ENDCG
    }
  }
}

A-PxLが誕生してから、今まで。

f:id:aizu-vr:20200907194002j:plain

こんにちは、ぶろっくのいずです。

初代部長である秋山さん達がこの部を作ってくださった時から3年以上が経ちました。 A-PxLの成り立ちを知る人もいなくなってきた事もあり、今までの活動をまとめました。 (※この記事について修正、追記出来る部員の方がいましたら気軽に変更をお願いします)

目次

A-PxL(旧 会津大学VR部)のこれまでの活動

2016年度

会津大VR部(会津大学) - Japan-VR-circles

3月5日:はてなブログ開設

2017年度

4月6日:会津大学内にカヤックVR分室を設立しました! | 面白法人カヤック

4月28日:Twitter開設

8月11日:会津大学オープンキャンパス

8月中旬:面白法人カヤックインターン(学部1年生2人)

9月上旬:面白法人カヤックインターン(学部3年生3人)

9月29日〜10月1日:VR部 開発合宿

10月7日〜10月8日:蒼翔祭(学祭)

12月:初代部長秋山さんが株式会社AnostVRを設立

2018年度

4月10日:新入生説明会

4月17日:今年の方針の説明 VR概論1

4月24日:VR概論2

5月8日:ゲームサウンドワークショップ

5月15日:3Dモデルワークショップ

5月22日:Git勉強会1

5月29日:Git勉強会2

5月27日:会津マルシェにてVR雪合戦を展示

6月30日:ブレスト研修会

7月3日:チーム進捗報告及びチーム活動

8月11日:オープンキャンパス

9月14日〜9月16日:開発合宿(南会津の宿泊施設CloudCamp)

10月6日〜10月7日:蒼翔祭(学祭)

10月16日:チーム進捗報告及びチーム活動

11月4日:デジゲー博2018(秋葉原UDX)

11月16日:IVRC決勝大会ユース部門銀賞を獲得

youtu.be

11月18日:パソコン甲子園にてゲームの展示

12月1日〜12月22日:健康づくりハッカソン

12月:部内面談

1月:部内面談

2月:次年度の役員決め

2月16日〜2月17日:新潟・山形・会津大学共同ハッカソンに参加naoto80840.github.io

2019年度

4月:役員の分担(代表者の他に副代表者、会計、自治会担当者を増員)

4月5日:部名を「Aizu-Progressive xr Lab(略称:A-PxL)」に変更

5月18日〜5月19日:VRキャラバン活動

6月16日:Oculus Quest体験会

7月13日〜7月14日:新入生対抗ハッカソン

8月11日:オープンキャンパス VR作品体験会

8月23日〜9月14日:学祭に向けてのPR動画制作

10月12日〜10月13日:蒼翔祭(学祭)は台風により中止

12月:部内VRコンテンツ体験会

1月31日:来年度の活動についてミーティング

2月14日:勉強会についての会議

3月1日:追いコン

2020年度

4月5日:clusterにて会津大学VRサークル説明会

4月6日:部室があるAiCTを3Dスキャン

4月15日:clusterにてA-PxL新入生向けの説明会

4月29日〜5月10日:バーチャルマーケット4に出展

5月14日〜6月11日毎週木曜日:新入生向けのプログラミング勉強会

5月15日〜6月12日毎週金曜日:新入生向けのUnity勉強会

5月16日〜5月30日毎週土曜日:新入生向けのBlender勉強会

6月6日〜6月13日毎週土曜日:新入生向けのVFX勉強会

6月17日:OpenAppLab "2020"

7月11日:Git勉強会1

7月14日:部内でVR体験会

7月16日:Git勉強会2

7月17日:Git勉強会3

7月21日:チーム決め

9月:夏休み

10月10日:オンラインの蒼翔祭(学祭)

活動内容の変化

2016年度:?

2017年度:?

2018年度:チームに別れてゲームを制作。

2019年度:ゲーム部と新規開発部(NDD)に別れて活動。ゲーム部は毎週火曜日19:00〜21:00に中講義室M5にて定例会。新規開発部は毎週金曜日19:00〜21:00に研究棟北ラウンジにて定例会。

2020年度:ゲーム部と新規開発部を統合。毎週火曜日19:00からUBICの3Dシアターにて定例会(大学のテスト期間や大型の休みを除く)。周一でチームごとに進捗を報告しゲームを制作。

部員数の推移

(最大アクティブ数はSlackのアナリティクスより集計)

2016年度:総数11人→13人

2017年度:総数33人、最大アクティブ数33人、新部員20?人

2018年度:総数49?人、最大アクティブ数44人、新部員16人

2019年度:総数63?人、最大アクティブ数43人、新部員6人

2020年度:総数77人、最大アクティブ数38人、新部員6人

役員の推移

顧問:マイケル・コーエン先生

2017年度

代表者:Achu→Achu (@Achu_retro) | Twitter

2018年度

代表者:ゆべねこ→ゆべねこ (@yubeshineko) | Twitter

2019年度

代表者(新規開発部リーダー):Bigdra→bigdra (@bigdra50) | Twitter

副代表者(ゲーム部リーダー):なめろうNameChung (@sketch_namerou) | Twitter

会計:ヤミみみ→ヤミみみ (@MtYamaniko) | Twitter

自治会担当者:ぶろっくのいず→ぶろっくのいず (@blocknoise26) | Twitter

2020年度前半(学祭前)

代表者:Bigdra→bigdra (@bigdra50) | Twitter

副代表者:TC→TC (@Meancore226) | Twitter

会計:ヤミみみ→ヤミみみ (@MtYamaniko) | Twitter

自治会担当者:ぶろっくのいず→ぶろっくのいず (@blocknoise26) | Twitter

2020年度後半(学祭後)

???

A-PxLメンバー達のTwitter

twitter.com

部室の変化

2017年度:?

2018年度:会津大学UBIC分室

2019年度:AiCT(あゆむCafe)の展示スペース

2020年度:無し

部名変更の経緯

最初は会津大学VR部という名であったA-PxL。(結局今もVR部と呼んだり呼ばれることも) 2019年3月頃、VRだけでなくARなどxRを扱う部であるのに対し、部名がそぐわないという意見が出る。(※部名の変更がちょうど年度始まりから可能) 「VRやxR関連であること」、「他と被らないオリジナリティのあること」という条件を元に30個?ほどの部名案が出る。その中から「なんちゃらxRなんちゃら(どんな名前だっけ?)」という部名がアンケートで選ばれ、最終的に「Aizu-Progressive xr Lab(略してA-PxL)」という部名になる。A-PxLの読み方は「アイヅピクセル」。

(2020年9月7日更新)

会津大学VR部の部員が持ち回りで投稿していくブログです。特にテーマに縛りを設けずに書いていきます!