20211127のUnityに関する記事は8件です。

NrealLightで半透明な物を録画したくて色々ためした

これは STYLYアドベントカレンダー の2日目の記事です! STYLY for Nrealの一人称録画機能を開発した STYLY for Nrealの一人称録画機能を開発しました。 NrealとはARグラスの一種です。 一人称録画機能とは、NrealLightについているカメラで現実世界を撮影したものを、AR映像と合成し、動画ファイルに出力する機能です。 STYLY for Nrealにご興味のある方は、こちらをご参照ください。 NRSDKの First Person View Video Capture を使った NRSDKには First Person View Video Capture という機能があります。STYLYでも内部的にはこのSDKを利用しています。 基本的に、このサンプルコード通りに実装すれば、一人称録画機能は作れるのですが、一部のオブジェクトが録画に映らないという現象に遭遇しました。 (グラス越しにはちゃんと見えています) 半透明なオブジェクトが録画に映らない サンプルアプリを試してみたところ、一部のオブジェクトが録画に反映されていませんでした。 フレームデバッガー等で調査したところ、 Render Queue が Transparent だと録画に映らない ことがわかりました。 NRSDKのコードを調査したところ、 NRSDK\Resources\Record\Shaders\AlphaBlend.shader に以下の行を見つけました。 fixed3 col_tex = lerp(col_bg.rgb, col.rgb, col.a) ここで、実写映像 col_bg.rgb と CG映像 col.rgb をαブレンドしているのですが、 RenderQueue が Transparent の場合、 col.a が 0 になっており、CG映像が消えていることがわかりました。 Render Queue を AlphaTest にすると録画に映る Render Queue を AlphaTest にすると、半透明な物でもNrealの録画に映ることがわかりました! 以下の映像は、 Transparent の箱と AlphaTest の箱を並べて、Nrealで撮影したものです。 ご覧の通り、 Transparent の箱は映っていませんが、 AlphaTest の箱は映っています! (ちなみに、箱の中にある玉は Opaque です。) 念のため、Nreal Communityにも投稿しました。 とりあえず録画に映したければ AlphaTest にするのが手っ取り早いです。 しかし、 Render Queue をいじることによる弊害もあります。 Render Queue をいじると前後関係が不自然になる Render Queue をいじると、前後関係が不自然になります。 以下の図をご覧ください。 左側の箱はRenderQueueが Transparent (3000) 右側の箱はRenderQueueが AlphaTest (2450) 箱の中にある玉はすべて Opaque (2000) Transparent の箱の奥に AlphaTest の箱がある時は見えますが、 AlphaTest の箱の奥に Transparent の箱がある時は見えません。玉は常に見えます。 これは描画順が Opaque (2000) < AlphaTest(2450) < Transparent(3000) だからです。 ご覧の通り、前後関係が不自然に見えるため、 AlphaTest の多用は避けたいところです。 ただ、録画に半透明なオブジェクトを映す方法は、今のところこれしか見つけていないので、どうしても映したいときは、この方法を使わざるをえないのが現状です。 さいごに なんともモヤっとしますが、調査は以上です。 録画時にしか再現しない現象だったため、調査・対策にかなり時間がかかってしまいました。 今後、同じ症状に遭遇した人の助けになれば幸いです。 本記事作成にあたり、以下の情報を参考にさせていただきました。ありがとうございました。 STYLY Manual | STYLY for Nreal マニュアル Nreal Developer | First Person View Video Capture Nreal Community | I’m having trouble capturing video about transparent object
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

同人プロジェクトマネジメント入門:概要編

同人プロジェクトマネジメント入門の目次はこちら はじめに マネジメントの各工程についての注意点、解決策を書いていきますが、その章立てに対して正当性を確認するため、概要編ではマネジメントとはどのような仕事なのかについて書いていきます。 定義 マネージャー:「同人プロジェクトの進行を担当する人」ぐらいの意味。実際の同人プロジェクトでは、基本的にしっかり役割を決めることが少ないです。 ビジネスに比べた同人プロジェクトの特徴 強制力がない 給料をもらっている人間がならば、ある程度プロジェクトにコミットする義務がありますが、あくまでもサークルですから、強制力が存在しません。何らかの方法で強制力を生み出さないと、だれも仕事をしないという状況が生まれてしまいます。 同様に、プロジェクトをかき乱すようなメンバーがいたとしてもそのメンバーに対して強制力をもって追い出すなどの対処もできません。 とにかく権力構造がフラットで、トップダウン型のプロジェクトが非常に難しいことが特徴です。 教訓:強制力を生む努力が必要! 仕様・完成を自分で決められる 「クライアント」「取引先」などの概念が存在せず、何を作りたいかは各メンバーの要望によって決まります。 これは基本的にはいいことです。理不尽な仕様を押し付けられることはないですし、マネージャーからするとコントロールできるものが多いのはいいことです。 非常に自由ですが、決まったフォーマットがないがゆえに最初は仕様決定に失敗することも多いです。 この特徴によって、世の中に出回っている「プロジェクトマネジメント入門」などの情報がうまく活用出来ない場合が多いです。 教訓:仕様の決め方に工夫を凝らすべし! コミュニティ・友情を重視する必要がある 基本的にモチベーション駆動開発なので、いくつか気にかけるべきポイントも発生します。 例えば、責任を決めないと開発が回らないくせに「責任」は禁句であることや、失踪者を出したら大抵完成までいかないなどです。 また、重要なポイントとして、ほとんどの場合モチベーションは急速に下がり、滅多に回復しないものであるという事実もあります。 教訓:同人プロジェクトは人間が最大の経営資源!気持ちよく働かせよう! 特徴まとめ 良くも悪くも柔軟性があるのが特徴です。 マネージャーは、この柔軟性を殺さないようにプロジェクトを回さないといけません。しかしまったく柔軟性のない締め切りと戦わなければなりません マネージャーの働く場面 1. 「何を」やるのか仕様を決める 同人マネージャーの仕様の最大の特徴は、常に一番上流の工程にいるということです。すべてを決めるだけの権限を持っています。この工程はプロジェクトの最初期であり、この場面での人間関係の構成が残りの全体に影響します。 同好の士が集まってプロジェクトを作る場合「企画者=マネージャー=リーダー」になることも多い印象です。 2. 「どうやって」やるのか計画を立てる 「いつ」「いつまでに」やるのか 時間を決定するのは非常に重要な問題です。しかしながら、時間を決定したら期待通りに進行するわけでもありません。 逆に、「予定を決定しておいて予定通りに負えられない」とい状況は、うまくいかない罪悪感などを生んでモチベーションを削りかねません。同人プロジェクトマネージャーは時間に関してシビアな意思決定をする必要があります。 「誰が」やるのか 仕事の割り当ても重要なマネジメント対象です。 しかしながら、これも難しい決定です。例えば、本人の力量を知らずに仕事を割り当ててしまったとき、もしビジネスならば、マネジメントの判断で本人の仕事を変えることで対応できます。しかし、ビジネスに比べて同人プロジェクトだと「仕事を奪う」という判断が極端に難しいです。人間関係やモチベーションが重要なので、仕事を奪う判断は比較的に重くなります。 その他の計画 金で調達してくる 同人プロジェクトでは、各種素材をお金で調達することができます。コストマネジメントも仕事です。 助っ人を呼ぶ サークルなどでは外部から助っ人を呼ぶことが出来るでしょう。「最終助っ人を呼べば何とかなる」といった状況を作っておくのもマネージャーの仕事です。 勉強させる プロジェクトメンバのスキルが足りない場合には、一定期間勉強に費やさせることが必要です。 ツールの導入 世の中には開発を円滑にするためのツールが多くあります。実際、Githubのpull requestを使って開発を進めるプロジェクトもあるでしょう。しかしながら、これらは基本的に業務で使うことが想定されていることがメインで、かなりきっちりしているベストプラクティスの情報が多いです。同人プロジェクトでは、もう少し「雑な」運用の方が効果的なことが多いです。 3. 仕様と計画を修正し続ける プロジェクトの変更要因を並べました。これらのイベントが発生するたびに、マネージャーは仕様と計画を修正して、プロジェクトを円滑に進めていきます。 リスク管理 メンバー同士がぎくしゃくしている メンバーが失踪、病気、(「トラックに轢かれる」) 納品したコードの品質が悪いことが発覚 主に人間関係要因と技術的要因の2パターンがあり、単発で一気にプロジェクトの雲行きが怪しくなってくる状況。備えておくしかない。 進捗管理 「終わりませんでした!」というやつ 「この時期は試験があってやれませんね」というやつ 「あいつが仕事しないので俺が働けねぇな」というやつ じわじわとプロジェクトを闇に包んでいくタイプ。離散的なリスクに対して、連続的に発生するため、予想・対応を早めることが大事。 変更管理 「作ってみたら、こっちの方が面白いと分かった」(ポジティブ) 「作ってみたら、ゲームにならないことが分かった」(ネガティブ) 要件定義をきっちりしておけば避けられるかもしれないが、同人プロジェクトでガチガチの要件定義書なんてあるはずがなく、また、ゲームの面白さという定量化しにくい値が重要な変数であることも相まって、避けられない変更である。 おわりに 今回は、何を変えるべきかについて解説しました。次回からは、今回挙げた工程のそれぞれについて、どのような対応策が考えられるかについて書いていきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

同人プロジェクトマネジメント入門:目次

はじめに 過去の連載のUnity設計入門や、Unity・MVP設計実践では、ソフトウェア工学のアプローチでプロジェクトの混沌を避ける方法を書きました。今回はより文系的なアプローチでプロジェクトを成功に導く方法について書いていこうと思います。 対象読者 最大のターゲットは、ゲーム制作サークルのリーダーやマネージャーです。 より広いターゲットは、同人サークルで何らかの製作をしているすべてのマネージャーです。 プロジェクト管理をノリでしようと思っている人は、結構な確率でやけどしますから、初心者マネージャーさんには読んでほしい記事です。 前提条件 今回のメイン対象は「Unityを使ったゲーム開発プロジェクト、ただし仕事ではない」になります。 しかし、「同人プロジェクト全般」において役立つ情報を多く載せています。 リンク 概要編 仕様書作成編1 仕様書作成編2 仕様書作成編3 計画作成編1 計画作成編2 計画作成編3 人間関係編1 人間関係編2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ROS講座129 Unityプロジェクトをgit管理する

環境 この記事は以下の環境で動いています。 項目 値 CPU Core i5-8250U Windows 10 unity 2020.3(LTS) 概要 ROS講座でUnityのプログラムが出てきます。Unityのプログラムをgitで管理する方法を記します。またサンプルProjectをgitでダウンロードして使用する方法も解説します。 Unityのgit管理 github for unityのインストール Asset Storeで「github for unity」をダウンロードします。 PackageManagerで上記をImport 設定をする メニューバーの「Window」->「GitHub」をクリック 「setting」タブに移り、出てくるページの「Name」と「Email」に適当な値を入れます。この情報はcommitメッセージに入るだけで、githubの登録アドレスと違っても、実在しなくても大丈夫です。 初期化をする 「Initialize」タブに移り、「Initialize a git repository for this project」というボタンを押す。ここで.gitignoreなどが自動で作られて、それらのファイルのみのInitial commitが作られます。 「Assets」などのフォルダはまだコミットされていません。「Changes」タブに移って、一覧からcommitしたいファイル(全部チェックで大丈夫です)をチェックして、画面下部の「Commit Summary」と「Commit Description」を埋めて「commit to [master]」を押しましょう githubと連携する remote repositoryを作る githubの使用するアカウントで新たにリポジトリを作ります。 sshキーの登録 どうやらPCの「/c/Users/[user名]/.ssh/」にssh鍵が必要な見たいです。今回はgitbashを使いました。 gitbashを起動 ディレクトリ移動:cd ~/.ssh/ 鍵の作成: ssh-keygen.exe -t rsa(あとは指示に従って) ~/.ssh/id_rsa.pubをgithubに登録 リモートリポジトリの設定&ログインをする&Push Unityに戻ります リモートリポジトリの設定 メニューバーの「Window」->「GitHub」をクリック 出てくるウィンドウで「Setting」タブを開く 「Remote: origin」の欄にリポジトリのアドレスを入れる(例:git@github.com:project-srs/ros_lecture_unity_app.git) ログイン 画面右上の「Log In」を押す 出てくる画面で「Sign in with your blowser」を押す。 出てくる画面でgithubの連携のために認証をする pushをする ウィンドウ上部の「Push」を押します。 githubからcloneしたProjectを使用する 例えば当プロジェクトの「https://github.com/project-srs/ros_lecture_unity_app」を使用する場合の手順です。 リポジトリのclose 外部ツールでcloseする必要があります。今回はgitbashで~/に移動して「git clone git@github.com:project-srs/ros_lecture_unity_app.git」します。 Unity hubで開く Unity Hubを開き「リストに追加」を押して上記のディレクトリを開きます。 コメント Unity Teamを使って構成管理をするのがUnityの正式のようです。 昔はmetaファイルが隠しファイルかそうでないかの選択があったそうですが、今はなくなっています(.metaファイルが普通のファイルとして出来ます)またバイナリ形式かテキスト形式かを選択できるのですが、現在はデフォルトがテキスト形式のようです。 最初のコミット時点で500kBほどでした。最初に.gitattributeが設定されているのでgit LFSに対応していているようです(未確認ですが)。 参考 https://qiita.com/toRisouP/items/97c4cddcb735acde2f03 目次ページへのリンク ROS講座の目次へのリンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Prefabを一定間隔でランダムな場所に生成

HDRPでも動くこと確認済み。 蝶々の.vfxで試したのでバタフライメイカーという名前になってます もう少し使いやすく改良します、いずれ using System.Collections; using System.Collections.Generic; using UnityEngine; public class ButterFliesMaker : MonoBehaviour { public GameObject butterFliesPrefab; float span = 1.0f; float delta = 0; void Update() { this.delta += Time.deltaTime; if(this.delta > this.span) { this.delta = 0; GameObject birth = Instantiate(butterFliesPrefab); int px = Random.Range(-7, 6); birth.transform.position = new Vector3(px, 5.0f,0.0f); } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VRChatterのためのシェーダーの基本、完全に理解した

はじめに こんにちは、ひら吉です。 最近、noteに一般的な記事ばかり書いていて エンジニア系の記事は久々の投稿になります。 ※本業はインフラ系ITエンジニア(Linux/Cisco/Juniperを使ったサーバ・ネットワーク設計)。 さて、今日は趣味のVRChatに関係する記事を書きますよ。 記事の対象者は、私のような野良プログラマー向けです。 (軽めのプログラムを書けるくらいの方) VRChatって? 最近、有名な「メタバース」の一種です。 メタバース (英: Metaverse) は、コンピュータやコンピュータネットワークの中に構築された現実世界とは異なる3次元の仮想空間やそのサービスのことを指す[1]。将来的にインターネット環境が到達するであろうコンセプトで、利用者はオンライン上に構築された3DCGの仮想空間に世界中から思い思いのアバターと呼ばれる自分の分身で参加し、相互にコミュニケーションしながら買い物やサービス内での商品の制作・販売といった経済活動を行なったり、そこをもう一つの「現実」として新たな生活を送ったりすることが想定されている[2]。(Wikipediaより) つまり「インターネットのアバターを使ったコミュニケーション(雑談・遊びなど)のための場所!」です。 VRChatをもっと楽しむならUnityとBlender必須! コミュニケーションのためにUnityとBlender?と思うかもしれないですね。 Unityはゲーム開発ツール、Blenderは3Dモデリングツールです。 でも実は、Unityがあると、自分の好きなアバターをVRChatで使うことができます。 そのアバターかっこいいね/かわいいね どこで買ったの? そう言われたら嬉しいですよね。だから、ますます自分のアバターをアップしようとするんです! さらに! Blenderを使えば、アバターをゼロから作ることすらできます。 実はBoothというサイトでアバターを購入する人がほとんどですが、 中には自分でゼロから作ってしまうつわものも稀にいます。 モデリングは、芸術的センス・経験が必要なので、経験がないと難しいと思いますが、 特に絵画創作経験があると比較的容易にアバターを作ってしまえるように見受けられます。 そう、そして彼らは神として崇められるのです…(半分冗談・半分本気) そんな世界で一体のアバターを作れたら最高ですよね。 ※私は絵画創作経験もなく、アバターのような曲線系の複雑なモデルは作れなかったので、  単純な直線主体の温泉モデルくらいは作っていまして、Boothで公開しています。 [温泉街の猫の店] https://hirakichi.booth.pm/items/1765812 そしてシェーダー入門 そして、VRChatで更に楽しむ要素として「シェーダー」があります。 シェーダーとは、物体表面の模様の描画方法を記述する方法の事です。 なんのこっちゃと思うかもしれませんが、 ・アバターに色を付けたり ・反射させたり ・もしくは色が動いたり するのがシェーダーの役割なんです。 見た目はプログラミングとそっくりなのですが、考え方が全然違うのでさっぱりでした。 普通にUnityやBlenderを使うのに比べて、別次元の難しさがありますね。 そこで今回、改めて学ぶことにしたのでその軌跡をまとめておきます。 完全に理解したと書いておきながら、どこまで理解できたのやら(笑) このページが一番分かりやすかった こちらのページが有名だと思います。 「7日間でマスターするUnityシェーダ入門」 https://nn-hokuson.hatenablog.com/entry/2018/02/15/140037 こんな感じでリンクが張られているので 「上から順に全部理解していくこと」 が大事だと思います。 そうすると理解しやすかったです。 これが私の勉強法。 なお、他にもいろいろと参考書などはありますが、私は上記ページが一番分かりやすかったです。 手っ取り早く理解するにはお勧めのページです。 (と言っても、全然手っ取り早く理解できないですけど(笑)積み重ねが大事。) とりあえずSurfaceシェーダーだけ書こう 最初からいろいろやろうとしても混乱するだけです。 Surfaceシェーダーだけ書くことを意識しましょう。 後段のLightingなどはまた慣れてきたら勉強すればよいと思います。 その辺りの基本原則は以下のページに書かれています。 【Unityシェーダ入門】Unityのシェーダで遊んでみよう https://nn-hokuson.hatenablog.com/entry/2016/09/12/202320 簡単にまとめると、書き方の基本は、 Unityのシェーダーのプロパティから値やテクスチャを設定したいなら、Propertiesタグを使う struct Inputに、Surfaceシェーダー内で使いたい値の変数(例えばワールド座標、テクスチャなど)を用意する void surf関数の中で、Surfaceシェーダーの挙動を記述する です。 まず基本を作ってみましょう とりあえずStandard Surface Shaderを作成して、Shader01という名前を付けます。 そしてMaterialを作ってMat01と命名します。 そして適当な平面(Plane)を設置し、そこにMat01をドラッグ&ドロップします。 この段階だと、Mat01のシェーダーがStandardになっているはずなので、 先ほど作成したShader01に差し替えましょう。 ※自分で作ったシェーダーはCustomの下にあります。 次はShader01の中身を触るために、Shader01をダブルクリックしてください。Visual Studioが立ち上がるはずです。 うえぇ~プログラミング?と思ったあなた。 きっとこの時点で嫌になったと思います(笑) そう見た目はプログラミングそのものなのです。 この言語は「HLSL言語」と呼ばれるそうです。 Shader "Custom/Shader01" { Properties { _Color ("Color", Color) = (1,1,1,1) _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 // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. // #pragma instancing_options assumeuniformscaling UNITY_INSTANCING_BUFFER_START(Props) // put more per-instance properties here UNITY_INSTANCING_BUFFER_END(Props) void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } でも、それでも基本を理解すればそんなに難しくはないので、堪え忍びましょう…。 さて、構造的には下図のイメージでそこまで難しくないと思います。 SurfaceシェーダーのInput構造体で使える値については、 Unityの以下のマニュアルに記載されています。 ※VRChatで利用可能なUnity2018.4向けのマニュアル。 https://docs.unity3d.com/ja/2018.4/Manual/SL-SurfaceShaders.html この辺りはよく使うと思われる値です。 テクスチャ座標の名前は、uv の後にテクスチャ名が来る形にする必要があります float3 worldPos - ワールド座標 float3 viewDir - ビュー方向 各パラメータの意味合いについては、以下のページも 分かりやすかったので、必要に応じてご参照ください。 https://atmarkit.itmedia.co.jp/ait/articles/1801/05/news010.html 何が難しいかというとテクニックが随所に登場する点 透明 例えば、以下のページ。 【Unityシェーダ入門】氷のような半透明シェーダを作る https://nn-hokuson.hatenablog.com/entry/2016/10/07/221724 この半透明を実現するために、このページの著者は次のアルゴリズムで実装しています。 ドラゴンの輪郭部分の透明度が低くなっているのに対して、中央部分の透明度は高くなって つまり輪郭ほど濃く、中央部ほど薄くなる計算にすればよいのです。 それを実現するために、 viewDirとworldNormalの内積 を使っています。これがまさにテクニックです。 内積は、数学的には、viewDir・worldNormal = |viewDir|*|worldNormal|*cos(viewDir方向からworldNormal方向の角度)になりますが、なんのこっちゃと思うと思います。 視線方向と法線方向が同じ場合は(中心に近いから)薄くする = cosの値が1に近くなる = Alphaを0に近づける 視線方向と法線方向が違うほど(輪郭に近いから)濃くする = cosの値が0に近くなる = Alphaを1に近づける だから、色の薄さを表すパラメータ o.Alpha に 1-|viewDir・worldNormal| の結果を設定すれば実現できます。 (※cosはマイナスにもなるが、ここでは内積の大きさを使いたいので絶対値を使っている) o.Alpha = 1-(abs(dot(IN.viewDir, IN.worldNormal))); …数学が得意な方であれば理解できたと思います。 でもなかなかこういうのは理解が難しいです。 だから「暗記」で良いんです。 透明にするときはこの式を使う。 o.Alpha = 1-(abs(dot(IN.viewDir, IN.worldNormal))); それで良いんです。これがまさにテクニック。 ゲームのお仕事でシェーダー作成をしてきた知人から言わせると、 シェーダーは「他の人が公開しているシェーダーをコピペして使うもの」だそうです。 だから、きっとそういうものなのだろうと思います。 波打つ表現 【Unityシェーダ入門】円やリングをかっこよく動かす方法 https://nn-hokuson.hatenablog.com/entry/2016/11/14/203745 こちらのページから抜粋したコードは下記のとおりです。 void surf (Input IN, inout SurfaceOutputStandard o) { float dist = distance( fixed3(0,0,0), IN.worldPos); float val = abs(sin(dist*3.0)); if( val > 0.98 ){ o.Albedo = fixed4(1, 1, 1, 1); } else { o.Albedo = fixed4(110/255.0, 87/255.0, 139/255.0, 1); } } このコードを読み解くと 原点(0,0,0)から各座標IN.worldPosの距離distを計算する distの値をsinに入れて、sinカーブの0.98-1の範囲の場合だけ、fixed4(1,1,1,1)の色=白で描画、 それ以外はfixed4(110/255.0,87/255.0,139/255.0,1)=紫で描画する という形でコードを記述しているのが分かります。 この sinカーブの0.98-1の範囲の場合だけ というのがキーになっていて、これが同心円の表現部分になります。 なお、上の状態だと、円が同心円状に描かれるだけなので、_Timeを使うと波打たせることができます。 float val = abs(sin(dist*3.0-_Time*100)); _TimeはUnityで定義された関数で、ステージがロードされてからの経過時間だそうです。 ※ https://kurotorimkdocs.gitlab.io/kurotorimemo/030-Programming/Shader/UnityShaderSnippet/ ちなみに、-_Time*100とするのか、+_Time*100とするのかで、波の動く方向が反対になります。 (マイナスの場合、中心から外側へ波が出ていく。プラスの場合、外から中心に集まる。) こんな感じで表現されます(この動画はマイナスの場合(-_Time*100))。 おまけ「シェーダーは保存しないと反映されないよう」 シェーダーを変更しても反映されねえなと思っていたのですが、 Visual Studio側でセーブしないとダメなんですね。 UdonSharpを書くときはAttachしていたので、Attachすれば反映されると思っていましたが、 シェーダーの場合は、Attachではなく保存が必要だそうです。 ランダム性の実装 【Unityシェーダ入門】シェーダで作るノイズ5種盛り https://nn-hokuson.hatenablog.com/entry/2017/01/27/195659 おもしろいですね。 まさかランダム関数が用意されていないので自分で用意する必要があるとは。 float random (fixed2 p) { return frac(sin(dot(p, fixed2(12.9898,78.233))) * 43758.5453); } UV座標の値を元に、Alberoの値(色)を設定すれば色にノイズを乗せられるようです。 void surf (Input IN, inout SurfaceOutputStandard o) { float c = random(IN.uv_MainTex); o.Albedo = fixed4(c,c,c,1); } 次にライティングも触ってみよう 【Unityシェーダ入門】トゥーンシェーダを自作してみる https://nn-hokuson.hatenablog.com/entry/2017/03/27/204255 このページを見ると、ライティングの記述方法が解説されています。 ライティングになると一段難しくなりますね…。 とりあえずシェーダーを書きたい場合は、 Surfaceシェーダーまでで良いとは思いますが、 アバター向けのシェーダはライティングも 考慮した方が映えそうです。 頂点シェーダー(Vertex Shader)も使ってみる 頂点シェーダーを使うと 平面が波打つ表現 のような海面のようなものを作るときに使えるようです。 そのためにVert関数をハックして、そこに独自関数を記述します。 【Unityシェーダ入門】シェーダで旗や水面をなびかせる https://nn-hokuson.hatenablog.com/entry/2017/04/04/201537 考え方としては、上下方向(y方向)の座標をsin関数で上下させるイメージです。 そうすると表示として上下しているように見えます。 上のSurfaceシェーダーにこの頂点シェーダーの処理を入れるときの注意点は まず「頂点シェーダーをハックするよ(上書きするよ)」という以下の一文が必要になるそうです。 #pragma vertex:vert その上で、vert関数の実装です。 void vert(inout appdata_full v, out Input o ) { UNITY_INITIALIZE_OUTPUT(Input, o); float amp = 0.5*sin(_Time*100 + v.vertex.x * 100); v.vertex.xyz = float3(v.vertex.x, v.vertex.y+amp, v.vertex.z); } こちら、上記ページよりそのまま引っ張ってきたコードですが、 これで波打ちますね。こんな感じになりました。 Dissolveシェーダとtex2D 「【Unityシェーダ入門】Dissolve(溶けるような)シェーダをつくる」 https://nn-hokuson.hatenablog.com/entry/2017/04/14/204822 このページの溶けるシェーダーは比較的簡単でした。 ノイズとなるテクスチャを用意する ノイズテクスチャの濃淡に応じて、実際にモデルに張り付けるテクスチャの表示部分を決める(ノイズテクスチャの濃い部分だけを表示するイメージ。濃いかどうかの閾値はThreshold(スレッショルド、閾値)で調整可能。) fixed4 m = tex2D (_DisolveTex, IN.uv_MainTex); half g = m.r * 0.2 + m.g * 0.7 + m.b * 0.1; しかし読み解くうえでなんじゃこりゃ?というのがtex2Dという関数。 調べてみると tex2D関数は、UV座標(uv_MainTex)からテクスチャ(_MainTex)上のピクセルの色を計算して返します。 らしいです。 (引用元 https://qiita.com/kaiware007/items/ffe7c546bc71136cf8da) ということで、単純に色を取得する関数のようです。 また half の式はどうも「グレースケール変換」を行っているそうです。 こういうのもテクニックですよね。 RGB値それぞれに定数をかけている式を見ても、それだけだと意味は分からないので シェーダー特有のノウハウのような気がしました。 結局数学必要じゃない?そんなことはないよ。 はい、私もシェーダーを書いていて、数学の知識が必要だなーと思いました。 (ちゃんと理解して完全に未知のシェーダーを生み出すなら、数学の知識が必要だとは思いますが、 普通に使う分には必要ないと思いました。) 例えば、 sinやcosが-1~1の値を取ること sinやcosのグラフを書くとカーブを描くこと(横軸x, 縦軸y=sin(x)としたときにカーブのグラフになる) ベクトルの内積の計算式と、内積をとって内積ゼロのときは直交ベクトルになっている 等。この辺りの知識がないと理解は難しいと思います。 でも問題ないと思います。 上にも書いた通り「他の人のシェーダープログラムを参考(パクる?)」にすれば良いんです。 シェーダーに限った話ではないですが、プログラミングは他のより良質なプログラムを書く人を 真似ることで成長します。 だから、思い描いたシェーダーに近いシェーダーを見つけて、 それを真似ることこそが、シェーダーを上手に使いこなすコツだと思いました。 勉強の仕方 まず基本的なシェーダーの構造(HLSL言語の構造)を理解したうえで、 上で紹介した通り「透明」「波打つ表現」のような表現のテクニックを学ぶのが近道だと思います。 そのテクニックは、Webで「シェーダー サンプル」のような形で検索すると出てくると思いますし、 以下のshadertoyのページもいろいろとサンプルがあって参考になりそうです。 慣れてきたら、自身で数式とグラフを見ながら、こんなものが作れそうだーという発想をしていくと、 新しいシェーダーを作れるんでしょうね~と思いました。 シェーダーの参考ページ 例えば粘性を表現する数式が紹介されていたりします。 まぁ正直これを数式で書くって神業ですよね。 数式をここまで感覚的に操れるのはすごい。 他にもいろいろとシェーダーの書き方があるのでこのページを参考に、 期待する表現に近づけると良さそうです。 最後に 私がぜんぜんシェーダーが分からなかったので少しまじめに取り組んでみました。 知っている人にとっては当たり前でも、 当たり前の知識がないのが初学者には辛いところなので、 まとめてみたところです。 もし参考になれば幸いです。 ご意見等あれば頂けば幸いです。 というわけで記事を書きながら焼いていたパンが焼けたので 食べたいと思います。 それではまたーひら吉でした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スーパーボールを重ねて落とす「すっとびボール」を物理エンジン(Unity)で再現する

概要 Unityは物理エンジンであり、 これを用いて物理実験をシミュレーションすることが可能である。 今回はUnityを用いて「すっとびボール」をシミュレーションする。 環境 Unity 2019.3.7f1 仕様 上の段のボール(直径1m)を高さ10mから落とす。 下の段のボール(直径1.5m)が床に着地したとき、上の段のボールが高く跳ねる。 実装 まず、床を実装する。 ヒエラルキーウィンドウで座標が(0,0,0)でScaleが(1,1,1)の Planeを作る。 次に、スーパーボールとなる球を実装する。 ヒエラルキーウィンドウにScaleが(1,1,1)で座標が(0,10,0)のSphereをBall1とし、 Scaleが(1.5,1.5,1.5)で座標が(0,8.75,0)のSphereをBall2として作る。 すなわち、上の段のボールがBall1、下の段のボールがBall2となる。 (Ball1とBall2のマテリアルを変更しておくと見やすい。私はBall1を赤、Ball2を青とした。) Ball1とBall2にRigidbodyをアタッチし、 Ball1の方のRigidbodyのMassを1に、Ball2の方のRigidbodyのMassを3にする。 ボール用のPhysic Materialを作る。 プロジェクトウィンドウにPhysic Materialを作り、 Bouncinessを0.9にする。 これをBall1とBall2のSphere ColliderのMaterialに入れる。 ここまでやれば、シーンウィンドウが以下の画像のようになっているはずである。 ここからスクリプトを書いていく。 スクリプトをこのように書く。 GalileanCannon.cs public class GalileanCannon : MonoBehaviour { public Rigidbody ball1; public Rigidbody ball2; private float v0; private float v1; private float v2; private float e1 = 0.9f; private float e0 = 0.9f; private bool flag = true; private bool isGrounded = false; void Start() { ball1.isKinematic = true; ball2.isKinematic = true; //速度を求める v0 = Mathf.Sqrt(2 * 9.81f * (ball2.transform.position.y - (ball2.transform.localScale.y / 2.0f))); v1 = ((e0 * ball2.mass - (e0 * e1 + e1 + 1) * ball1.mass) * v0) / (ball2.mass + ball1.mass); v2 = (((e0 * e1 + e0 + e1) * ball2.mass - ball1.mass) * v0) / (ball2.mass + ball1.mass); } void FixedUpdate() { //スペースキーを押したら開始 if (Input.GetKeyDown(KeyCode.Space)) { ball1.isKinematic = false; ball2.isKinematic = false; } //ball2が地面についたらball1が跳ねる if(isGrounded == true && flag == true) { ball2.velocity = new Vector3(0, v1, 0); ball1.velocity = new Vector3(0, v2, 0); isGrounded = false; flag = false; } } void OnCollisionEnter(Collision collision) { //ball2が地面と接触したかを一回だけ判定する if (collision.gameObject.name == "Ball2" && flag == true) { isGrounded = true; } } } 変数を見てみよう。 GalileanCannon.cs public Rigidbody ball1; public Rigidbody ball2; private float v0; private float v1; private float v2; private float e1 = 0.9f; private float e0 = 0.9f; private bool flag = true; private bool isGrounded = false; ball1は上の段のボール、ball2は下の段のボールである。 v0はball2が地面と接触する直前の速度、 v1はball2が地面と衝突したあとのball2の速度、 v2はball2が地面と衝突したあとのball1の速度である。 e1、e0は反発係数である。 isGroundedは地面に接触したかどうかを判定するものであり、 flagは地面と衝突した最初の1回だけシミュレーションを行うための判定である。 スクリプトの動作は、スクリプトに書いてあるので割愛する。 このスクリプトをPlaneに加え、 スクリプトのball1とball2にそれぞれBall1とBall2を入れる。 これで完成である。 あとは再生ボタンを押してスペースキーを押せばシミュレーションが始まる。 カメラの位置を見やすい位置に変更するのを忘れずに。 まとめ オブジェクトを作り、計算式をスクリプトに書く。 計算式さえ理解すれば、スクリプトはさほど難しくはない。 今回はこのようなスクリプトになったが、もっと良いスクリプトがありそうなので、 自分でも考えてみてください。 参考文献 塚本浩司:「すっとびボールの研究史 物理教育 49巻6号」,2001年12月 P.537-541 Wikipedia https://ja.wikipedia.org/wiki/%E3%81%99%E3%81%A3%E3%81%A8%E3%81%B3%E3%83%9C%E3%83%BC%E3%83%AB (Wikipediaの途中式が異なっているので、お気をつけて)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

地面の種類によって足音を変え,音声が再生される間隔も調整する処理

だいぶ前の話にはなるんですけど,Unity公式がリリースしているLost Cryptというサンプルプロジェクト内でいい感じのコードの書き方を発見し,それを調整したものを現在も使用しているので共有します。 判定したい地面レイヤーを設定 自分の場合はこんな感じでいろいろ。 地面を判定する 大きく分けてコライダーで判定する方法と,Rayで判定する方法の2つがあります。 コライダーのOnCollisionEnter等のイベント関数で判定をすると壁に接触した時にも地面と同じ判定が出る為,今回はRayで地面の検知をします。 // 地面の種類 public enum GroundType { Ground_Grass, // 草 Ground_Stone, // 石 // 一部省略 Airborne // 空中 } private GroundType groundType = GroundType.Airborne; // 取得するレイヤー情報 private int ground_Grass; private int ground_Stone; [SerializeField] private float rayDistance; private void Start() { ground_Grass = LayerMask.GetMask(nameof(GroundType.Ground_Grass)); ground_Stone = LayerMask.GetMask(nameof(GroundType.Ground_Stone)); } private void UpdateGrounding() { if (GroundRayHit(ground_Grass)) { groundType = GroundType.Ground_Grass; } else if (GroundRayHit(ground_Stone)) { groundType = GroundType.Ground_Stone; } else { groundType = GroundType.Airborne; } // 指定したレイヤーが地面にヒットしたかどうか bool GroundRayHit(int layer) { var pos = transform.position; // Unityエディタでのみ起動 #if UNITY_EDITOR // シーンビューにRayを視覚化 // 引数(開始地点, 終了地点, Rayの色) Debug.DrawLine(pos, pos + (Vector2.down * rayDistance), Color.red); #endif // 引数(Rayの開始地点, Rayが向かう方向, Rayの判定距離, 判定するレイヤー) return Physics2D.Raycast(pos, Vector2.down, rayDistance, layer); } } 音声側の処理を設定 まずフィールドに下記のような項目を追加します。 [SerializeField] private AudioSource _audioSource; [Header("Audio Clips")] [SerializeField] private AudioClip[] _stepsGrass, _stepsStone; [Header("Steps Interval")] [SerializeField] private float _stepsInterval; private float _stepsTimer; 次にインスペクターから必要な音声をアタッチします。 メソッドも追加します。 // 接地中の地面から足音を再生 private void AudioSteps(GroundType groundType, float speedNormalized) { // 空中だった場合は処理を返す if (groundType == GroundType.Airborne) return; // 経過時間 * プレイヤーの移動速度をタイマーに加算 _stepsTimer += Time.deltaTime * speedNormalized; // 設定した時間を超えた場合 if (_stepsInterval <= _stepsTimer) { // 現在接地中の地面からランダムな着地音を返す _audioSource.PlayOneShot(SelectedRandomSFX(groundType)); // 音が鳴った後はタイマーをリセット _stepsTimer = 0.0f; } } // 接触している地面に応じて地面の接触音をとる private AudioClip SelectedRandomSFX(GroundType groundType) { // 地面のタイプを判定 // 判定したものを配列に代入 AudioClip[] steps = groundType switch { GroundType.Ground_Grass => _stepsGrass, // 草 GroundType.Ground_Stone => _stepsStone, // 石 _ => null }; // 足音の配列の中からランダムに1つを取得 return steps[Random.Range(0, steps.Length)]; } 自分の場合ジャンプから着地した時にも同一の音声を再生する為メソッドを分けていますが,着地音を別に用意する場合はAudioSteps内に直接SelectedRandomSFXの内容を記述してもいいです。 実際に使用する ここまで書いた全ての内容を統合し,実際に使用してみます。 private float walkSpeed = 1.0f; private float dashSpeed => walkSpeed * 2; private Rigitbody rb; private void Start() { rb = GetComponent<Rigitbody>(); } private void Update() { var speed = walkSpeed; if (Input.GetKeyDown(KeyCode.Shift)) { speed = dashSpeed; } rb.velocity = new Vector2(speed, rb.velocity.y); // 横移動の速さに疾走スピードを割る(疾走中だった場合は1になる) // 今回の場合,歩いている時の速度は走っている時の速度の半分に設定しているので,音の再生速度も半分 float horizontalSpeedNormalized = Mathf.Abs(rb.velocity.x) / dashSpeed; AudioSteps(groundType, horizontalSpeedNormalized); } 以上です。 音声側の処理を記述する場所を変更するなどして使用してください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む