Marmoset Toolbag 3の紹介
こんにちは、学部3年の丹野です。 過去の私の記事を見直すとどれもソフト紹介ばかりなのですが今回も紹介記事です。
自分で調べた内容なので間違いがあったらすみません。
Marmoset Toolbag3というソフトの特徴は大きく分けて
続きを読む細分割曲面を使って蛇口のひねりを作る
学部3年モデリング班の杉山です。
今回は新入生向けチュートリアル作成の過程で制作した「蛇口のひねり」を用いてBlenderの便利なモディファイア「細分割曲面」を紹介していきます。
細分割曲面とは
「細分割曲面」は、低ポリゴンなメッシュオブジェクトを、滑らかな曲面になるようにポリゴン分割化(細分化)する事ができるモディファイアーです。(引用→
細分割曲面の使いどころ
さっそく蛇口を作成していきます。下図はキューブを押しだしなどして作りました。
ここにさっそく細分割曲面適用してみます。すると・・・
こうなります。簡単にきれいな曲面になります。
しかしただ適用するだけでは滑らかになってほしくない部分まで滑らかにされてしまっています。そういう時には、さきほどのリンク先の記事にある「クリース」を使うのもいいですが、「エッジーループを挿入する」ことでも解決することができます。
エッジループを追加し、ひねりのキャップ部分がはっきりしました。
また細分割曲面を使うとメッシュ数が跳ね上がるのである程度分割数を増やした後はスムーズシェードに切り替えましょう。
完成品
スムーズシェード&適当なマテリアルをくっつけて先ほどのキューブの塊が
綺麗な蛇口のひねりになりました!
曲面の多い物のモデリングには欠かせない細分割曲面、この記事がひとつ参考になれば幸いです。
ShurikenのCustomVetexStreamsでパーティクル毎のデータをシェーダーに渡す
こんにちは、学部3年ゲーム部VFX班の森口です。
今日はShurikenの設定項目であるCustomVetexStreamsを使ってシェーダーに値を渡す方法を紹介したいと思います。
目次
初めに
今回の記事は技術評論社さんから出版されているUnityゲームエフェクトマスターガイドを主に参考にしており作成するエフェクトの一部にその本で使われているアセットを用いています。そのあたりの説明は本題とは異なるため省略させていただきますのでご了承ください。 この本は非常に参考になる本ですのでエフェクト制作に興味がある方はぜひ購入してみてください。 Unityゲームエフェクトマスターガイドはこちら www.amazon.co.jp
CustomVertexStreamsとは
CustomVetexStreamsとはUnity5.5から追加された機能で簡単に言うとユーザが定義した各パーティクルが持つデータをシェーダーに渡すことができる機能です。
詳しい機能などは公式リファレンスを参照してください。
例えばテクスチャをUVスクロールするようなエフェクトを作ろうとしたときにUVスクロールを実装したシェーダーをそのまま適応するとすべてのパーティクルが同じようにスクロールしてしまいあまり見栄えのいいものではなくなってしまいます。
ブログ用
— グッチー (@guchimoriVR82) August 27, 2019
思ってたのと違うUVスクロールの例 pic.twitter.com/vEPFoxZKBf
そこでCustomVertexStreamsを使うとパーティクルごとに違う値を割り当て、それぞれが違う動きをしてくれるようになります。
ブログ用その2
— グッチー (@guchimoriVR82) August 27, 2019
いい感じになったUVスクロールの例 pic.twitter.com/9VzomQMrI1
今回はこの機能を使って雷のチャージエフェクトを作成していきたいと思います。
CustomVetexStreamsからデータをシェーダーに送る
では実際に実装していきます。
まずはShurikenのRendererモジュールを見てください。そこにCustomVetexStreamsのチェックボックスがありますのでチェックを入れてください。すると項目が増えたかと思います。
おそらく何も設定していない状態ですと上の画像よりも薄いグレーの中の項目が少ないと思います。その中の項目がシェーダーに送られるデータたちです。上の画像にある項目の中で今回主に使うのはCustom1とCustom2の二つになります。
これらの項目は右下にある「+」をクリックしCustom->Custom1(または2).xyzwをクリックすることで追加できます。
これでデータを送る準備はできたのでシェーダー側にうつってどのように受け取るか見ていきましょう。
まずは頂点シェーダーとフラグメントシェーダーに入力として渡す構造体です。
struct appdata {//頂点シェーダーへの入力 float4 vertex : POSITION; float4 uv : TEXCOORD0; fixed4 color : COLOR; float4 custom1 : TEXCOORD1; fixed4 custom2 : TEXCOORD2; }; struct v2f {//フラグメントシェーダーへの入力 float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 uv : TEXCOORD0; float4 custom1 : TEXCOORD1; float4 custom2 : TEXCOORD2; };
custom1、custom2という変数に先ほどのものが入る形になります。
今回はCustom1にUVスクロールのスピードとEmissionを、Custom2にパーティクルのベースカラーにかけ合わせる色情報を持たせています。
次に頂点シェーダーとフラグメントシェーダーです
v2f vert(appdata v){ v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.color = v.color; o.custom1 = v.custom1; o.custom2 = v.custom2; return o; } fixed4 frag(v2f i) : SV_TARGET{ i.uv.y = i.uv.y+_Time.y*i.custom1.y; //y軸方向のUVスクロール fixed4 c = tex2D(_MainTex, i.uv)*i.color*i.custom2*i.custom1.w; //パーティクルの色にcustom2で指定した色を掛けてEmissionを掛けている return c; }
今回はあらかじめ雷用のテクスチャを作成してから使用しているのでy軸方向のUVスクロールのみですがプロシージャルに作成している場合はx軸方向のスクロールも入れることでさらにいい感じになると思います。
できたシェーダーの全体はこちらです
Shader "Custom/Lightning" { Properties { _MainTex ("MainTex", 2D) = "white" {} } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent"} Blend SrcAlpha OneMinusSrcAlpha Cull off LOD 100 Pass{ CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 struct appdata { float4 vertex : POSITION; float4 uv : TEXCOORD0; fixed4 color : COLOR; float4 custom1 : TEXCOORD1; fixed4 custom2 : TEXCOORD2; }; struct v2f{ float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 uv : TEXCOORD0; float4 custom1 : TEXCOORD1; float4 custom2 : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert(appdata v){ v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.color = v.color; o.custom1 = v.custom1; o.custom2 = v.custom2; return o; } fixed4 frag(v2f i) : SV_TARGET{ i.uv.y = i.uv.y+_Time.y*i.custom1.y; fixed4 c = tex2D(_MainTex, i.uv)*i.color*i.custom2*i.custom1.w; return c; } ENDCG } } FallBack "Diffuse" }
Shurikenの設定
次にShurikenのほうの設定を行います。Mainモジュールなどは皆さんの好みに変えてくれればと思います。 ちなみに私はこんな感じに設定してます。 特に肝心なのが次の設定項目でRendererモジュールの一つ上にあるCustomDataというモジュールにチェックを入れます。このモジュールに設定した項目が先ほどのCustom1,2に対応しています。なのでここの値を変化させることでパーティクルごとのUVスクロールのスピードを変えたりすることができるわけです。 Custom1はVector形でx~wに値を割り振ってあります。今回はyの値がUVスクロールのy軸方向の速度でwがEmissionの値です(別に割り振りには特に意味はないので自由に割り振ってください)。 今回はスクロールスピードをRandomBetweenTwoCurvesにしているのでかなり速度がばらけるようになっています。 後は先ほど作成したシェーダーから作成したマテリアルを割り当ててRenderModeをMeshにして平面を少しねじったようなメッシュを割り当てて中央のコアを作成すれば完成!
ゲームエフェクトマスターガイドの雷シェーダーをShaderGraphからShaderlabのほうに移植した
— グッチー (@guchimoriVR82) August 27, 2019
CustomVetexStreamsの使い方に戸惑ったけどいい感じに移植できてる希ガス pic.twitter.com/SBAdnKAOfE
最後のほうが少し雑になってしまいましたが使うメッシュはシンプルな長方形でもきれいに見えます。
最後に
いかがだったでしょうか。まだまだシェーダーは勉強中なので今回テクスチャの作成は事前に行いましたがもう少し勉強してより良いエフェクトを作れるようになりたいです。 今回の記事はここまでにしたいと思います。ご覧いただきありがとうございました。
参考ページ
シェーダーでルーン文字書いてみた
こんにちは, 今月からA-PxL代表になりました学部2年の木村です. これからA-PxL をもっとxR技術に触れたり, 開発や勉強などしやすい場にすることを目指していきたいと思います!
最近シェーダーの勉強をして遊んでおり, 今回はルーン文字を表示するシェーダーを書いたのでその紹介をしたいと思います.
ルーン文字とは
ルーン文字は「呪術や儀式に用いられた神秘的な文字」と紹介されることもあるが、実際には日常の目的で使われており、ルーン文字で記された書簡や荷札なども多数残されている。呪術にも用いられていたが、それが盛んに行われるようになったのは、むしろラテン文字が普及しルーン文字が古めかしくいかにも神秘的に感じられるようになった時代に入ってからである。
ルーン文字はずっと昔に使われていた文字で, ラテン文字が普及した頃には呪術などにも使われるようになった文字とのことです. アニメやゲームでの魔法演出などによく使われており, 昨年の部内でチーム開発した「DESIGN」という作品でも使っていました.
https://youtu.be/UG9Lq4oFoQk?t=142
木片などにナイフで刻みつけて表記していたようなので, 直線を組み合わせた字形となっていてシェーダーでも書きやすそうです.
実装
前述の通り, ルーン文字は直線を組み合わせた字形のため, 長方形を組み合わせればよさそうです. レイマーチングで使っていた距離関数で2Dの絵も描けるようなのでやってみました. 距離関数とは, 相手との距離を返す関数のことです. こちらに詳しく解説されていましたのでご参照ください.
この表の左上の「フェオ(Feoh)」という文字を書いてみます.
float dRoundRect(float2 pos, float2 size){ float2 d = abs(pos) - size; return length(max(d, .0)) + min(max(d.x, d.y), .0); } float dFeoh(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(pos, float2(width, 10)); return rect1; } float3 map(float2 uv){ float2 fPos = frac(uv) * 2 -1; return dFeoh(fPos, 5); } fixed4 frag (v2f i) : SV_Target{ fixed4 col = 1; col.rgb = step(.5, map(i.uv)); if(col.r >= 1. && col.g >= 1. && col.b >= 1.){ discard; } return col; }
↑は丸みのある長方形の距離関数をサイズ調整しただけです.
static const float PI = 3.141592; float2 rotate(float2 pos, float angle){ angle = angle * PI / 180.; float2 a = normalize(angle); float s = sin(angle); float c = cos(angle); return float2(pos.x * c - pos.y * s, pos.x * s + pos.y * c); } // 略 float dFeoh(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(pos, float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x - 3., pos.y - 6.8), 45.), float2(width, 4.)); return rect2; }
次は長方形を回転させたものです. 変更のない関数などは省略しました.
これで長方形を好きな角度で表示することができるようになったので, 次は組み合わせてみます.
float opUnion(float d1, float d2){ return min(d1, d2); } // 略 float dFeoh(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(pos, float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x - 3., pos.y - 6.8), 45.), float2(width, 4.)); float rect3 = dRoundRect(rotate(float2(pos.x - 3., pos.y - 1.8), 45.), float2(width, 4.)); return opUnion(rect1, opUnion(rect2, rect3)); }
opUnion
は和集合の演算をしています. このように, 和集合や差集合などを使って距離関数の合成ができます.
あとは同様に24個分のルーン文字を作成するだけです.
// ----------------- Rune ------------------------- // フェオ float dFeoh(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(pos, float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x - 3., pos.y - 6.8), 45.), float2(width, 4.)); float rect3 = dRoundRect(rotate(float2(pos.x - 3., pos.y - 1.8), 45.), float2(width, 4.)); return opUnion(rect1, opUnion(rect2, rect3)); } // ウル float dUr(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(float2(pos.x + 5., pos.y + .5), float2(width, 9.5)); float rect2 = dRoundRect(float2(pos.x - 5., pos.y + 2.), float2(width, 8)); float rect3 = dRoundRect(rotate(float2(pos.x, pos.y - 7.5), 106), float2(width, 5.3)); return opUnion(rect1, opUnion(rect2, rect3)); } // ソーン float dThorn(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(pos, float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x - 5.5, abs(pos.y)), 130), float2(width, 7)); return opUnion(rect1, rect2); } // アンスール float dAnsur(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(pos, float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x - 3., pos.y - 7.), -45.), float2(width, 4.)); float rect3 = dRoundRect(rotate(float2(pos.x - 3., pos.y - 2.), -45.), float2(width, 4.)); return opUnion(rect1, opUnion(rect2, rect3)); } // ラド float dRad(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(pos, float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x - 5.5, abs(pos.y - 5.45)), 130), float2(width, 7)); float rect3 = dRoundRect(rotate(float2(pos.x - 4, pos.y + 3.), 130), float2(width, 5.)); return opUnion(rect1, opUnion(rect2, rect3)); } // ケン float dKen(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(rotate(float2(pos.x, abs(pos.y)), 45.), float2(width, 10)); return rect1; } float dGeofu(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(rotate(float2(pos.x, pos.y), 45.), float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x, -pos.y), 45.), float2(width, 10)); return opUnion(rect1, rect2); } float dWynn(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(pos, float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x - 5.5, abs(pos.y - 5.45)), 130), float2(width, 7)); return opUnion(rect1, rect2); } float dHagall(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(float2(pos.x + 6., pos.y + .5), float2(width, 9.5)); float rect2 = dRoundRect(float2(pos.x - 6., pos.y + .5), float2(width, 9.5)); float rect3 = dRoundRect(rotate(float2(pos.x, pos.y), 120), float2(width, 6.5)); return opUnion(rect1, opUnion(rect2, rect3)); } float dNied(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(pos, float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x, pos.y - 1.), 120), float2(width, 6.5)); return opUnion(rect1, rect2); } float dIs(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect = dRoundRect(pos, float2(width, 10)); return rect; } float dJara(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(rotate(float2(pos.x + 6., abs(pos.y - 2.)), 50.), float2(width, 7.)); float rect2 = dRoundRect(rotate(float2(pos.x - 6., abs(pos.y + 2.)), -50.), float2(width, 7.)); return opUnion(rect1, rect2); } float dYr(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(pos, float2(width, 10)); float rect2 = dRoundRect(rotate(float2(pos.x - 3.1, pos.y - 7.35), -50.), float2(width, 4.)); float rect3 = dRoundRect(rotate(float2(pos.x + 3.1, pos.y + 7.35), -50.), float2(width, 4.)); return opUnion(rect1, opUnion(rect2, rect3)); } float dPeorth(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(float2(pos.x + 5., pos.y), float2(width, 8.)); float rect2 = dRoundRect(rotate(float2(abs(pos.x - 1.4), pos.y - 3.15), 53.), float2(width, 8.)); float rect3 = dRoundRect(rotate(float2(abs(pos.x - 1.4), pos.y + 3.15), -53.), float2(width, 8.)); return opUnion(rect1, opUnion(rect2, rect3)); } float dEolh(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(float2(pos.x, pos.y), float2(width, 10.)); float rect2 = dRoundRect(rotate(float2(abs(pos.x), pos.y - 2.), 45.), float2(width, 8.)); return opUnion(rect1, rect2); } float dSigel(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(rotate(float2(pos.x - .7, pos.y + 4.), 53.), float2(width, 4.)); float rect2 = dRoundRect(rotate(float2(pos.x - .7, pos.y - .7), -53.), float2(width, 4.)); float rect3 = dRoundRect(rotate(float2(pos.x - .7, pos.y - 5.5), 53.), float2(width, 4.)); return opUnion(rect1, opUnion(rect2, rect3)); } float dTir(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(float2(pos.x, pos.y), float2(width, 10.)); float rect2 = dRoundRect(rotate(float2(pos.x - 3.3, pos.y - 7.5), -53.), float2(width, 4.)); float rect3 = dRoundRect(rotate(float2(pos.x + 3.3, pos.y - 7.5), 53.), float2(width, 4.)); return opUnion(rect1, opUnion(rect2, rect3)); } float dBeorc(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 2.; float rect1 = dRoundRect(float2(pos.x, pos.y), float2(width, 10.)); float rect2 = dRoundRect(rotate(float2(pos.x - 3.3, pos.y - 7.5), -53.), float2(width, 4.)); float rect3 = dRoundRect(rotate(float2(pos.x - 3.3, pos.y - 2.7), 53.), float2(width, 4.)); float rect4 = dRoundRect(rotate(float2(pos.x - 3.3, pos.y + 7.5), 53.), float2(width, 4.)); float rect5 = dRoundRect(rotate(float2(pos.x - 3.3, pos.y + 2.7), -53.), float2(width, 4.)); return opUnion(rect1, opUnion(rect2, opUnion(rect3, opUnion(rect4, rect5)))); } float dEoh(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(float2(pos.x - 7, pos.y + .5), float2(width, 9.5)); float rect2 = dRoundRect(float2(pos.x + 7., pos.y + .5), float2(width, 9.5)); float rect3 = dRoundRect(rotate(float2(pos.x - 3.5, pos.y - 6.4), 54.), float2(width, 4.3)); float rect4 = dRoundRect(rotate(float2(pos.x + 3.5, pos.y - 6.4), -54.), float2(width, 4.3)); return opUnion(rect1, opUnion(rect2, opUnion(rect3, rect4))); } float dMann(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(float2(pos.x - 7, pos.y + .5), float2(width, 9.5)); float rect2 = dRoundRect(float2(pos.x + 7., pos.y + .5), float2(width, 9.5)); float rect3 = dRoundRect(rotate(float2(pos.x - 3.5, pos.y - 6.9), 60.), float2(width, 4.1)); float rect4 = dRoundRect(rotate(float2(pos.x + 3.5, pos.y - 6.9), -60.), float2(width, 4.1)); float rect5 = dRoundRect(rotate(float2(pos.x + 3.5, pos.y - 3.), 60.), float2(width, 4.1)); float rect6 = dRoundRect(rotate(float2(pos.x - 3.5, pos.y - 3.), -60.), float2(width, 4.1)); return opUnion(rect1, opUnion(rect2, opUnion(rect3, opUnion(rect4, opUnion(rect5, rect6))))); } float dLagu(float2 pos, float size){ pos *= 100. / size; float width = .1; pos.x += 1.; float rect1 = dRoundRect(float2(pos.x, pos.y), float2(width, 9.5)); float rect2 = dRoundRect(rotate(float2(pos.x - 3., pos.y - 6.5), -45.), float2(width, 4.3)); return opUnion(rect1, rect2); } float dIng(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(rotate(float2(pos.x - 3.5, pos.y + 3.5), 45.), float2(width, 5.)); float rect2 = dRoundRect(rotate(float2(pos.x + 3.5, pos.y + 3.5), -45.), float2(width, 5.)); float rect3 = dRoundRect(rotate(float2(pos.x + 3.5, pos.y - 3.5), 45.), float2(width, 5.)); float rect4 = dRoundRect(rotate(float2(pos.x - 3.5, pos.y - 3.5), -45.), float2(width, 5.)); return opUnion(rect1, opUnion(rect2, opUnion(rect3, rect4))); } float dOthel(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(rotate(float2(pos.x - .5, pos.y + 3.5), 45.), float2(width, 6.)); float rect2 = dRoundRect(rotate(float2(pos.x + .5, pos.y + 3.5), -45.), float2(width, 6.)); float rect3 = dRoundRect(rotate(float2(pos.x + .5, pos.y - 3.3), 45.), float2(width, 3.5)); float rect4 = dRoundRect(rotate(float2(pos.x - .5, pos.y - 3.3), -45.), float2(width, 3.5)); return opUnion(rect1, opUnion(rect2, opUnion(rect3, rect4))); } float dDaeg(float2 pos, float size){ pos *= 100. / size; float width = .1; float rect1 = dRoundRect(float2(pos.x - 7, pos.y), float2(width, 9.5)); float rect2 = dRoundRect(float2(pos.x + 7., pos.y), float2(width, 9.5)); float rect3 = dRoundRect(rotate(float2(pos.x, pos.y ), 36.5), float2(width, 11.7)); float rect4 = dRoundRect(rotate(float2(pos.x, pos.y ), -36.5), float2(width, 11.7)); return opUnion(rect1, opUnion(rect2, opUnion(rect3, rect4))); } float dBlank(float2 pos, float size){ pos *= 100. / size; float width = .1; return 1.; } // ---------------------------------------
ルーン文字の距離関数だけで300行近くなってしまいました. しんどい...
これでとりあえずすべての文字がシェーダーで書けるようにはなりましたが, 他にもいろいろ詰め込んで最終的にできたものはこちら↓です
— ryudai (@bigdra50) August 24, 2019
https://github.com/bigdra50/Shaders/blob/master/ShaderLab/Rune.shader
最後に
これをGPUパーティクルにしたものを記事にしたくて実装中でしたが, まだまだ時間がかかりそうだったのでここまでにしました.
だいぶ無理やりな実装になってる気がするのでもっといいやり方あれば教えて頂けると嬉しいです...
参考
ルーン文字関連 https://happy-runes.jimdo.com/%E3%83%AB%E3%83%BC%E3%83%B3%E6%96%87%E5%AD%97%E4%B8%80%E8%A6%A7-%E6%84%8F%E5%91%B3/ https://ja.wikipedia.org/wiki/%E3%83%AB%E3%83%BC%E3%83%B3%E6%96%87%E5%AD%97 距離関数 https://qiita.com/7CIT/items/fe33b9b341b9918b6c3d#%E8%A7%92%E4%B8%B8%E9%95%B7%E6%96%B9%E5%BD%A2 http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm https://www.slideshare.net/shohosoda9/threejs-58238484 図形や文字の描画 http://karanokan.info/2019/03/31/post-2465/ http://www.shaderslab.com/demo-75---matrix-pattern.html http://wordpress.notargs.com/blog/blog/2015/08/21/pixel-shaderglsl-sandbox%e3%81%a7%e3%83%87%e3%82%b8%e3%82%bf%e3%83%ab%e3%81%aa%e3%82%bf%e3%82%a4%e3%83%9e%e3%83%bc%e3%82%92%e4%bd%9c%e3%82%8b/
nfcpyを使って学生証から学籍番号を読み取る
こんにちは。A-PxL元代表の橋本です。先日の集会を最後に代表を引退することになりました。1年と半年、振り返ってみれば楽しいこと、辛いこと、うれしいことが色々ありましたが、どれもいい思い出たちばかりです。
さて、今週は私がブログ当番なので私の最近の話をしようと思います。内容は、学生証から学籍番号を読み取ったことについてです。
目次
動機
会津大では、毎年4月に新入生が入ってくると新入生のために歓迎会を開くことが通例となっており、この機会を使って上級生は新入生に向けて自身が所属するサークルの勧誘活動を行なっております。各サークルはブースを設けており、そこに新入生が行って内容を聞くということをしてます。そして、気になったサークルがあれば、そこのブースにある 紙の名簿に自身の学籍番号と名前を書き込みます。この名簿に書かれた学籍番号を使ってサークル関係者は学内メールを通してサークルの説明会や体験会についての連絡を新入生にするわけですね。
問題なのは、紙の名簿だということです。 メールを送信する際には、紙に書かれた学籍番号をPCに手打ちする作業が出てきます。書き込んでくれた学生が少ない場合は問題ないのですが、多いとそれだけ苦労するというわけです。2018年度の新歓では私たちの部にはなんと50名もの新入生が名簿に記入してくれました。嬉しかったのですが、その後の50人分の学籍番号の手打ち作業はとても辛かったですね(笑)
ということで、この手打ち作業をなくすべく、何かシステムを作らねば! というのが今回このシステムを作ろうと思ったきっかけです。
要件
個人的にNFCリーダーを使った何かをすることに興味がありました。そこで、学生証から学籍番号を読み取ることを考えました。また、読み取ったデータはスプレッドシートに書き込まれるようにするのがいいかと考え、読み込んだ学籍番号はGoogle Spread Sheetに書き込まれるようにしました。さらに、せっかくうちの部ではUnityを使ったアプリ開発を行なっているので、どうせなら読み込まれた学籍番号をUnity上で少し演出を加えて表示させようと思い、この実装も行うことにしました。(最後の1つはシステム的には蛇足ですけどね(笑))
実は、この作品は私が大学で受けていた授業の最終プロジェクトとして作ったものです。そのプロジェクトは、ラズパイを使って何かしよう! というものでした。そのためこのシステムではラズパイを用いております。
今回はICカードリーダー周りについて書いていきます。
NFC、Felicaについて
NFCとは、Near Field Communicationの略で、かざすことデータ交換をするために規定された近距離無線通信技術です。そして、FelicaはNFCの規格をもとにしてSONYが作った規格です。特徴として、Felicaは通信速度が速く、NFCの2倍ほどと言われています。そのこともあって、日本国内では主にFelicaが使われることが多いです。SuicaやPasmo, nanacoカード、WAONカードなどはすべてFelicaです。
詳しくはこちらの記事をお読みください。
[PASMO] FeliCa から情報を吸い出してみる - FeliCaの仕様編 [Android][Kotlin] - Qiita
学籍番号を読み取る
学籍番号を読み取るには読み取るための装置が必要です。手元になかったので買いました。ネットで色々調べた結果、以下の装置を購入しました。
ソニー SONY 非接触ICカードリーダー/ライター PaSoRi RC-S380
- 出版社/メーカー: ソニー(SONY)
- 発売日: 2012/10/10
- メディア: Personal Computers
- 購入: 5人 クリック: 9回
- この商品を含むブログ (18件) を見る
意外と安く手に入るものなんですね。
さて、準備も整ったので、早速読み取ってみましょう。まずは環境を整えます。以下の記事を参考にして整えてみてください。
RaspberryPiで!SONYのPaSoRi(RC-S380)で(NFC)Felica情報を読み取る! - KOKENSHAの技術ブログ
次に、カードのどこの部分に学籍番号が書かれているかを知らなければなりません。そこで、ダンプしてカードの中のどこに学籍番号情報があるかを確認します。
コードはこちらの方のものを使わせていただきました。
Raspberry Pi 3にPaSoRiを接続してSuicaカードをダンプする | TomoSoft
会津大の学生証をダンプした結果
System 809E (unknown) Area 0000--FFFE Area 1000--1FFF Random Service 64: write with key (0x1008) Random Service 68: write with key (0x1108) Random Service 72: write with key (0x1208) Random Service 76: write with key (0x1308) Random Service 128: write with key (0x2008) Area 3000--3FFF Random Service 192: write with key & read w/o key (0x3008 0x300B) 0000: 31 30 30 30 31 32 35 3X 3X 3X 3X 30 30 30 30 30 |1000125XXXX00000| System FE00 (Common Area) Area 0000--FFFE Area 1A80--1AFF Area 1A81--1AFF Random Service 106: write with key & read w/o key (0x1A88 0x1A8B) 0000: 30 31 30 30 31 32 35 3X 3X 3X 3X 00 00 00 30 31 |0100125XXX...01| 0001: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 0002: 30 3X 30 3X 3X 3X 3X 3X 3X 30 3X 3X 30 3X 30 3X |0X0XX0XXX0XX0X01| 0003: 3X 30 3X 3X 30 3X 3X 31 3X 30 30 30 30 30 3X XX |X0XX0XXXX00000XX| Area 1BXX--1BXX Area 1XXX--1BXX Random Service 108: write with key & read with key (0x1BXX 0x1BXX) Area 1XXX--1BXX Area 1BXX--1B7F Random Service 109: write with key & read with key (0xXX48 0xXX4A) Area 4XX0--42XX Area 4XX1--42XX Random Service 267: write with key & read with key (0xXXC8 0x4XXX) Area X3X0--4X3F Area X30X--4X3F Random Service 268: write with key & read with key (0x43XX 0x4XXX) Area XXXX--43XX Area 43XX--4XX7 Random Service 269: write with key & read w/o key (0x43XX 0xXX4B) 0000: 00 XX 00 00 XX 00 XX 00 XX 00 XX 00 XX 00 XX XX |................| * 00 XX 00 XX 00 XX 00 XX 00 XX 00 XX 00 00 XX XX |................| 000X: 0X 0X 00 XX 00 00 XX 00 XX 00 XX XX 00 00 00 00 |................|
データ保護のためところどころにX
を入れています。
どうやら、システム809Eのサービスコード0x300Bに学籍番号らしきものがあるようですね。この値を使って学籍番号を読み取ってみたいと思います。Pythonコードを書いていきます。
こちらの記事を参考にしました。
nfcpy で複数の System Code を持つ NFC タグを扱う方法 - uchan note
#!/usr/bin/env python # -*- coding: utf-8 -*- import binascii import nfc import time # 学生証のサービスコード service_code = 0x300B def on_connect_nfc(tag): # タグのIDなどを出力する # print tag if isinstance(tag, nfc.tag.tt3.Type3Tag): try: sc = nfc.tag.tt3.ServiceCode(service_code >> 6 ,service_code & 0x3f) bc = nfc.tag.tt3.BlockCode(0,service=0) data = tag.read_without_encryption([sc],[bc]) sid = "s" + str(data[4:11]) print sid except Exception as e: print "error: %s" % e else: print "error: tag isn't Type3Tag" def main(): clf = nfc.ContactlessFrontend('usb') while True: clf.connect(rdwr={'on-connect': on_connect_nfc}) time.sleep(3) if __name__ == "__main__": main()
このコードを使うことで会津大の学生証から学籍番号を読み取ることができます。このプログラムでは3秒毎に学生証の学籍番号を読み取ってコンソールに表示させています。
まとめ
最初は難しいのかなと思っていたのですが、思ったほど難しくはなかったですね。先駆者様の方々のおかげです。今回はラズパイを使ったこともありPythonを使用しましたが、調べてみたところ、C#にもNFCを扱うライブラリがあるそうなので、時間があったらやってみたいと思ってます。
来年の新歓はこれで少しは楽になるといいなぁ〜