20220223のUnityに関する記事は6件です。

スクラッチ合成シェーダを出来るだけわかりやすく手順も添えて説明してみる

本日、ぱふもどきさんがスクラッチ処理を実装している作業実況でシェーダについてコメントさせて頂いたのですが、 やはりYoutubeコメントでは説明が難しく「記事化してもっとわかりやすく説明できないかな?できれば初学者にも伝わりやすい様な…」と思ったので記載してみます。 スクラッチ処理を実装するにあたって 実際のスクラッチを思い描くとわかりやすいかと思いますが、スクラッチ表現を実装するためには3つの要素が必要となります スクラッチの下に隠れている絵(仮にBaseと呼びます) スクラッチがどれくらい削られているか、の具合 スクラッチを削る前に表示されている絵(銀色部分。仮にOverlayと呼びます) その上で、削られていない部分にOverlayの画像を表示し、削られている部分にはBaseの画像を表示すると実装が完了できます Unityの機能に落とし込んでみると、何が必要? 上記3点の機能をデータで再現するならば、言ってしまえば最低限2枚のテクスチャが有れば実現は可能です Baseテクスチャ Overlayテクスチャ(RGBに画像情報、Alpha値に透過度(もともとの透過情報と削られ具合)) この2枚が有り、Overlayテクスチャのアルファ値を操作(削った部分だけ0に)することが出来れば、 実はシェーダを準備しなくても2枚の画像を重ねる、いわゆる「重ね合わせ」で実装可能だったりします。 ただ、ぱふもどきさんの実況の際に参考にされていた記事 に出来るだけ合わせてデータを用意しますので、今回は Baseテクスチャ(下地の絵) Patternテクスチャ(削られ情報) Overlayテクスチャ(スクラッチで削られる絵) の3枚を用いて、シェーダ上で合成して実装する方法で進めます。 シェーダでスクラッチ処理を実装する利点とは さて、実装に進む前に、 実況でも挙がった疑問「なぜシェーダで合成するのか?利点はあるのか?」という点について、 実況では「オブジェクトやマテリアルを1つにまとめられるので管理しやすい」とコメントしましたが、実はそれ以外にも利点は有ります 複雑な合成方法を実現できる 先に上げた「重ね合わせ」の場合は、主にBland命令で合成を行います。アルファブレンドや加算合成などですね。 正直、シンプルな表現で済むならアルファブレンドの「重ね合わせ」で問題ないでしょう。 ただ、そうすると複雑な模様や形状を表現する上では難しくなってしまいます。 後述する「境目に縁取り線を表示する」など自由な表現や合成を行うためにはシェーダ上で合成する事が利点となります。 「オブジェクトやマテリアルを1つにまとめられる」という事の真意 放送のコメントでは「管理が楽」と言いましたが、もう少し深く言及すると、下記の利点が有ります マテリアルが1つで済む GameObjectが1つで済む Z暴れ(Z-Fighting)を考慮しなくても良い 透過部分のOverDrawを考慮しなくて良い 1,2番目については言った通りです。マテリアルも、GameObjectも少ない方が管理が楽。 で、特に3番目ですが、UGUIのような描画順がHierarchyで制御できるならあまり重要ではないですが、 仮に3D空間で実装する場合にBase画像とOverlay画像をカメラから全く同じ距離に置いてしまうと、Z値(カメラからの奥行き情報)が干渉してしまいZ暴れ(Z-Fighting)が発生してしまいます。 「防ぐためには、Overlay画像をカメラに0.0001fだけ近づけて…」としても良いですが微妙に面倒臭いし、離し距離によってはカメラを傾けると隙間が微妙に見えてしまってダサいです。 そういったことを考えなくても良い = 楽 という事でもあります。 4番目については次項で説明します 重ね合わせによるフィルレートの倍化を考慮しなくても良い 重ね合わせの際に完全に透明な部分であったとしても描画負荷は発生します。 Base画像とOverlay画像が、仮に全画面に512x512pxで2枚重ね合わせで表示されたとして、ピクセルを打ち込む回数は512x512x2=524,288回発生する事になります。 これを1回の描画にまとめられるので512x512x1=262,144回のピクセル打ち込みになるため、GPUの負荷を抑えられます。 ※厳密には、重ね合わせの時に使うシェーダと合成で表現するシェーダでは計算内容が違うので単純に50%の負荷軽減になるわけではないです; 逆にシェーダで合成する欠点はある? 当然欠点もあるので何点か紹介します シェーダを用意しなければならない これはまぁその通りで、そもそもシェーダを準備する手間がかかります。さらに言及するとシェーダを扱える人でしか保守改良が難しくなってしまうので、見た目がおかしくなった時の責任がUnity側から自分に移ってくる点は覚悟しておいた方が良いです シェーダの内容次第では、重ね合わせよりも負荷が高くなってしまう 先ほど「重ね合わせよりもフィルレートを抑えられる」と書きましたが、Fragmentシェーダで難しい計算をしてしまうと、結局軽いシェーダで2枚描く以上の負荷になってしまう可能性は無きにしも非ずです。今回のシェーダはそれほど難しい計算はしませんが「必ずしもまとめれば良いだけではない」事は頭の片隅に置いておいた方が良いです。 シェーダが増えるのでSRPBatcherでまとめづらくなってしまう UniversalRPで描画する際に重要なのは「出来るだけ同じシェーダを使う事」なので、スクラッチ用のシェーダを用意する時点でSRPBatcherでまとめる描画単位の対象外となってしまいます。今回の記事ではUniversalRPは扱わないので関係ないですが、UniversalRPで大量のオブジェクトをまとめて描きたい際に障害となり得るので注意しておいた方が良いでしょう 本題。0ベースからスクラッチ表現までを達成します。 先ずはプロジェクトを作る という事で、0から作ってみましょう。 シェーダの合成処理のみにフォーカスするので、今回Patternテクスチャへのアルファ書き込み(スクラッチ削り取り処理)については言及しません。 そちらは先ほどのCAのリンク先をご覧ください。 環境は下記で進めます Unity-Hub 3.1.0 beta1 Unity 2021.2.12f1 先ずは空のプロジェクトを作ります。 初学者向けなのでBuilt-In RenderPipelineの3Dプロジェクトにします。(今回UnversalRPについては触れません) プロジェクト名はScratchTestで良いか。 シェーダを確認するための面の準備 シーンが出来たらHierarchyを右クリック > 3D Object > Plane で台紙となる面を作っておきます 出来ました。この面のマテリアルを見てみると Unityの標準のマテリアルがセットされています。これを自前のスクラッチマテリアルに変えます。 マテリアルを追加 Projectツリービューで右クリック > Create > Material でマテリアルを追加 出来上がったマテリアルのInspectorからシェーダを見ると標準のStandardが設定されています。 Standerdは標準という名前ですが、その実結構な機能が実装されており、初学者が読もうとすると大変難しい内容となっております。 対して、昔からあるUnlit/Textureなどは非常にシンプルな内容なので、今回はそちらを元にスクラッチシェーダを作成していきます。 シェーダを追加 という事でここからはUnlit/Textureをベースに話していきます。 Projectツリービューで右クリック > Create > Shader > Unlit Shaderを選択すると、Unlit/Textureシェーダをベースとしたシェーダファイルが作成されます。 Scratchとでも名付けてやりましょう。 Unlit/Scratchシェーダとして、このシェーダをプロジェクトで扱えるようになります。 面にマテリアルとシェーダを設定 早速シェーダファイルを開いて中身を編集したいところですが、その前にシェーダをマテリアルに設定し、更にマテリアルを面に適用しておきましょう。 Projectツリーから、Scratchマテリアルを選択し、InspectorからシェーダをUnlit/Scratchに変更します。 HierarchyからPlaneを選択して、Inspectorから、Materialsを選択し、0番にScratchマテリアルを設定します。 これにより「Scrachシェーダを持ったScratchマテリアルでPlaneを描画」することが出来るようになったわけです。 テクスチャを準備 せっかくなのでコーディングに入る前に必要なデータを揃えておきましょう    ここに3枚のテクスチャがあるじゃろ?という事で、それぞれBase.png、Pattern.png、Overlay.pngという名前で保存しておきます。 それぞれの意味は先ほどから書いてある通り Base.png … スクラッチの下の画像 Pattern.png … スクラッチの削られ具合(黒(0)で削った部分、白(1)でまだ削られていない部分) Overlay.png … スクラッチの上の画像 です。重要なのはPattern.pngは黒=削り済み、という点でしょうか。 シェーダの中身について やっとコーディング。先ずはScratch.shaderファイルを開きましょう Scratch.shader Shader "Unlit/Scratch" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); // apply fog UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } } 今更ここで各行を語るべくでもないですが、 今回は重要な点「下地の絵(Base)」「削り具合(Pattern)」「削る前の絵(Overlay)」の3枚のテクスチャを結合する処理に関わる部分だけ解説します。 プロパティの追加 一番上の部分。こちらはマテリアルからテクスチャや数値などを設定するための入り口です Properties { _MainTex ("Texture", 2D) = "white" {} } 察しの良い方ならすでに気付いているかもしれませんが、こちらにテクスチャを3枚設定できるように行を追加します Properties { _MainTex ("Base", 2D) = "white" {} _PatternTex ("Pattern", 2D) = "white" {} _OverlayTex ("Overlay", 2D) = "white" {} } _MainTexはUnityで古くから慣例として付けられている名前なので、敢えてそのままにしておきます。 シェーダ内で利用できるようにする プロパティを追加したことで、マテリアルからテクスチャを設定する入り口は準備できましたが、このままではまだシェーダ内部でテクスチャを使う事は出来ません。 シェーダで使いたいパラメータを明示するために、コード中央の定義部 sampler2D _MainTex; float4 _MainTex_ST; こちらも追加します。 sampler2D _MainTex; float4 _MainTex_ST; sampler2D _PatternTex; float4 _PatternTex_ST; sampler2D _OverlayTex; float4 _OverlayTex_ST; これで3つのテクスチャをシェーダ内で利用する事が出来るようになりました。 合成処理 これもまた察しの良い方ならすでに気付いているかもしれませんが、テクスチャの色を参照しているコードは // sample the texture fixed4 col = tex2D(_MainTex, i.uv); こちらです。 合成するために3枚のテクスチャの色を取得しておきましょう。 // sample the texture fixed4 baseCol = tex2D(_MainTex, i.uv); fixed4 patternCol = tex2D(_PatternTex, i.uv); fixed4 overlayCol = tex2D(_OverlayTex, i.uv); 色を合成するためには、自前で複雑な色計算をしても良いのですが、今回はlerp()関数を使います lerp()関数は 最終的な色 = lerp( valueが0の時の色, valueが1の時の色, value ); という値を入れる事で、最終的な色としてvalueで指定した割合で二つの色を混ぜ合わせてくれる処理(線形補間と言います)を行う関数です。 つまり 最終的な色 = lerp( Base【削った後の色】, Overlay【削る前の色】, Pattern【削ったら0、削る前は1】 ); を入れる事で、スクラッチ表現が達成できるようになるわけです。 つまり // sample the texture fixed4 col = tex2D(_MainTex, i.uv); を // sample the texture fixed4 baseCol = tex2D(_MainTex, i.uv); fixed4 patternCol = tex2D(_PatternTex, i.uv); fixed4 overlayCol = tex2D(_OverlayTex, i.uv); fixed4 col = lerp(baseCol, overlayCol, patternCol.r); とすることで、3つのテクスチャを合成した結果が得られます。 注意点としては、 valueには patternCol.r と、ベクトルではなく、1つの小数(赤成分のみ)を渡す ようにしている所です。 コードの完成 という事で出来上がったシェーダコードは以下になります Scratch.shader Shader "Unlit/Scratch" { Properties { _MainTex ("Texture", 2D) = "white" {} _PatternTex ("Pattern", 2D) = "white" {} _OverlayTex ("Overlay", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _PatternTex; float4 _PatternTex_ST; sampler2D _OverlayTex; float4 _OverlayTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 baseCol = tex2D(_MainTex, i.uv); fixed4 patternCol = tex2D(_PatternTex, i.uv); fixed4 overlayCol = tex2D(_OverlayTex, i.uv); fixed4 col = lerp(baseCol, overlayCol, patternCol.r); // apply fog UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } } 早速挙動を確認しましょう。 動作確認 Unityに戻って、Scratchマテリアルを選択。Inspectorから先ほど用意した3枚のテクスチャをそれぞれ当てはめます。 そして、SceneViewを確認すると…! ちゃんとPatternの黒い部分(削った部分)だけ、Baseの色が表示されている事が確認できました! という事でスクラッチの様に削った見た目になるシェーダの実装完了です。お疲れさまでした。 終わりに 出来るだけスクショも多めに、 初めて「自分でシェーダを書いてみよう!」と思った人にも取っつきやすい様に努めたつもりですがいかがでしょうか? もしわかりづらい点などあればツッコミ頂けるとありがたいです。 境界線に色を塗る処理も紹介しようと考えていましたが、記事がかなり長くなってきたので続編で書こうと思います。 続き 早速続きを書きました。1日2回行動。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityでアセットを追加したのに名前空間が見つからない問題

新しいアセットをインポートした時、テキストエディターの補完が部分的に利かない場合があります。 困ったことに、追加部分以外のシンタックスハイライトや補完は今まで通り動いてくれるので、自分の不手際かバグかと疑ってしまいますが、ボタン一つで解決できました。 Edit -> Preferences...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity2021でPrefabを解除する方法

Unity2021でPrefabを解除する方法です。 バージョンによってこのメニューが違っていて中々思い出せずに見つけられない事が何度かあったので、自分の記憶に定着させるための備忘録です。 ヒエラルキー上でPrefabを解除したいオブジェクトを右クリックします。 右クリックメニューから、「Prefab」→「Unpack」を選択します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HDRPでシーンが暗くなったり,光りすぎたりする際の対処法 Unity 2021.2.10f1

Unity 2021.1系から 2021.2系に移った時に,HDRPでシーンが暗くなったり,光りすぎたりする現象があり,調べてみたら,2021.2系ではグローバル設定がEdit -> Project Settings → Graphics -> HDRP Global Settings で設定されてしまっていたので, これらの設定を一旦すべてオフにして使用すれば,シーンが暗くなったり,光りすぎたりする現象が解決しました. 個別のシーンにグローバル設定をいれるのが2021.1までのやり方だったので,手間取ってしまいました.個人的には個別のシーンにグローバル設定をいれるのが混乱しにくいので良いと思います.以上!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityプロジェクトをGitHubで管理したい(SourceTree編)

概要 Unityで作成したコードをGitHubでバージョン管理する手順を記載しています。 Unityは、EclipseやAndroid Studio、Visual Studioなどと違い、GitHubとの連携が最初から用意されているわけではないようで、下記のような方法を選択するようです。 Github for Unity(https://unity.github.com/) UnityのAssetとして、GitHub管理したいプロジェクトに追加する方式。 Source Tree(https://www.sourcetreeapp.com/) OS上のファイルをGit管理する。という扱いでUnityをGitHub管理する。 GitHub Desktop(https://desktop.github.com/) GitHub純正のGit管理アプリ。GitHub側に近いUIなので、ローカル上の管理は戸惑うかも。 前提環境 * macOS 12.2.1(Monterey) * SourceTree * Unity 2020.3.29f1(Personalライセンス) * Github Free(https://github.co.jp/pricing.html ) やってみる Unity Hubなどから新規で作成したプロジェクトをGitHubで管理するまでの流れで進めていきます。 個人的に多いのが2Dでのテンプレで作成しているので、下記のような感じの構成になります。 プロジェクト名は「Sample2D」としておきます。 . ├── Assets │   └── Scenes ├── Library │   ├── APIUpdater │   ├── Artifacts │   ├── PackageCache │   ├── PackageManager │   ├── ScriptAssemblies │   ├── ShaderCache │   ├── StateCache │   └── TempArtifacts ├── Logs ├── Packages ├── ProjectSettings ├── UserSettings └── obj └── Debug というわけで、上記のプロジェクトをGitHubへ登録します。 SourceTreeのインストールなど 前回(SourceTreeでGitHubを使う )で解説したので、そちらを一読してください。 GitHubにリポジトリを作成する SourceTreeからGitHubにリポジトリを作成することはできないようです。 なので、先にGitHub側でリポジトリを作成します。 作成するのは、作成したUnityプロジェクトと同名(Sample2D)としておきます。 自分用のリポジトリなのでPrivateを選択します。 そして、.gitignoreを追加します。(重要) ここで、Unity用の.gitignoreが標準で用意されているので、追加しておくと余計なファイルをGitHubに登録しないようになります。 はい。まずは、空っぽのリポジトリができました。 SourceTreeでローカルにクローンしてくる SourceTreeを開いて、リモート側に作成したリポジトリを探します。 見つけたら、リポジトリの右側の「クローン」をクリックして、適当なところにクローンします。 一応書いておきますが、Unityプロジェクトが置かれているところには、すでに同名のフォルダがあるので別の場所にしましょう。 クローンすると、SourceTreeのローカル側に表示されます。 ローカルにクローンしたフォルダにコピーする Unityプロジェクトをクローンしたフォルダにコピーします。 GitHubにプッシュする 正確には、ローカルのGitリポジトリにコミットして、リモートのGitHubにプッシュです。 UnityプロジェクトでGitHub管理にしたくないものの選別は、すでにUnity用の.gitignoreがあるので、気にせずコミットしてOKです。 下記のように、フォルダにコピーした時点で、コミット対象はこのくらい。と数が表示されている(今回は30ファイル)。 リポジトリをダブルクリックして開くとコミット対象のリストが確認できる 「保留中のファイル」のチェックをつけると、すべての対象にチェックがつくので、チェックをつけます。 コミットメッセージに適当に記載して、コミットボタンをおします。 下記のような確認が表示されるのでOKをクリック この時点では、GitHubにはあがってません。まだローカルGitへコミットしただけです。 続いて、GitHubへプッシュします。 上記のように、プッシュに通知がきていると思います。 クリックすると、下記のように確認が表示されるので、一応確認してOKボタンをクリックします。 これで、GitHubにプッシュされたはずです。 GitHubで確認してみる GitHub側で対象のリポジトリを見てみると、無事、Pushできていることが確認できました。 このあとの問題 自分はやらかしたので、一応書いておきますが、Unity Hub側で新しくクローンしてプッシュさせたフォルダに切り替えてください。 そうしないと、Git管理されていないほうを更新していって、でもSourceTreeにはコミット対象はない。と言われるアホなことをしでかします。 以上で、UnityをGitHub管理するまでの手順でした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

本当の継承シングルトンを見せてやんよ【C#】

今回は,継承可能なシングルトン,つまり,この抽象クラスを継承すればなんでもシングルトンになれるぜ〜ってクラスを紹介します。 継承ツリーという概念と、みんな大好きジェネリックが出てきます。 誰しも一回は通る道 public abstract class BaseSingleton<T> { public static T Instance; public BaseSingleton() { Instance = (T)this; } } public class Singleton : BaseSingleton<Singleton> { } これじゃコンパイラーは通しちゃくれません。なぜでしょうか。 Cannot convert type 'BaseSingleton<T>' to 'T'. Instance = (T)this;のところでひっかっています。これはObject => BaseSingleton<T>とObject => Tという二つの継承ツリーが存在しており、言語仕様として別ツリーにはキャストできないからです。つまり、TとBaseSingleton<T>の関係を教えてやればいいのです。 結論 正解は、これ。 public abstract class BaseSingleton<T> where T : BaseSingleton<T> { public static T Instance; public BaseSingleton() { Instance = (T)this; } } public class Singleton : BaseSingleton<Singleton> { } where節により、継承関係が明示されました。 継承ツリーはObject => BaseSingleton<T> => Tとなっており、これならキャスト可能です。 余談: UnityのMonoBehaviourをシングルトンにしてみる 同じように。 public abstract class SingletonBehaviour<T> : MonoBehaviour where T : SingletonBehaviour<T> { public static T instance; public SingletonBehaviour() { instance = (T)this; } private protected virtual void OnDestroy() { instance = null; } } まとめ ジェネリックってほんとにムズカシイ!けどめっちゃ楽しい!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む