Unity hair触ってみた - 使ってみる編
事前準備編はこちらから mtholoblog.hatenablog.com
前回まででなんとなくAssetの準備はできたと思うので、実際にアタッチして使ってみようと思います。
用意した素材は + みんな大好きUnity-chan + Unityちゃんにかぶせる頭皮 です。
Unityちゃんはともかく、頭皮って? って感じですが、Unityちゃんは後頭部が髪の毛でおおわれているので、その部分にメッシュがありません。
最初はそこをSphereで埋めてやっていたのですが、どうしてもSphereの顔側(顔内部)で発生したHairが顔を突き抜けてきてしまうので、後頭部を埋めるメッシュを作成しました。 それがこちら
BlenderにUnityちゃんを読み込んで、後頭部の穴空いてる部分にSphere差し込んで、穴に合わせてSphereを切り取ったものです。 ピッタリ後頭部に合わせて作ったのですが、そのまま使うと メッシュの法線通りに毛が生えてくる = 毛が顔にかかってしまう ので、ちょっと回転させて使っています。その影響でスキマが…(今考えるとAssetの法の設定で Mapped Direction 使えば解決できそうですね)。
この後頭部を雑に球形投影でUV開いて
前髪と横髪が短くなるようにマップ作ってAssetを作ったら、
シーン上に配置した頭皮に Hair Instance (Hair生成用スクリプト)をアタッチ。 さっき作ったAssetを適用したら
Hair instanceの設定を作成(この設定が最適解というわけではなく、あくまでやっつけです)
そのほかにもいくつかごにょごにょやって…
シーンを再生!
Unity hairを装備したUnityちゃんに踊ってもらってみた!
— 麻木浅葱 / ARメタバース (@asagi_00a3af) 2022年8月12日
結構激しい動きながらも(大規模な)破綻はなく、結構よい感じに見える。
特に13秒くらいの後ろに下がったときはかなりかわいくってグッドなのでは!! pic.twitter.com/zxPMFCaMVZ
最初の方はぼさぼさですが、結構いい感じですね~!
こちらのパラメータの解説については、数が多すぎるのと、自分も突貫かつ手探りで設定していて、どのパラメータが具体的にどういう動きを規定するものなのか正確に説明する自信がないのですみません…。
ただ、自分が主にいじったのは
- Strand Diameter
- Strand Margin
- Iterations
- Substeps
- Stiffness
- Cell Pressure
- Cell Velocity
- Global Position
- Global Rotation(最終的に元に戻した)
あたりなので、動き(サラサラ感)やコライダーの突き抜け、追従性 などを調整したい場合はこのあたりのパラメータを見てみてください。
Hairの色について
現状、PackageにはHairの色を任意に変える機能はないみたいです。 一応Materialを指定できるにはできるのですが、提供されたHairシェーダーでないとHairとして描画されない上に、提供されたHairシェーダーには色変えのオプションがありません。 なので、超簡易的にではありますが、元のシェーダーに色変え機能を追加しました。
ベースにするのは Packages > Demo Team Hair System > Runtime > HairMaterialDefaultBuiltin.shader
です。
まずこれを丸っとコピーして、以下のように書き換えます。
Shader "Hair/Test/TestHairShader" { Properties { _Color("Color", Color) = (1,1,1,1) _MainTex("Albedo (RGB)", 2D) = "white" {} } CGINCLUDE #pragma target 5.0 #ifndef UNITY_COMMON_INCLUDED #define UNITY_COMMON_INCLUDED #endif #ifndef UNITY_MATRIX_I_M #define UNITY_MATRIX_I_M unity_WorldToObject #endif #ifndef UNITY_PREV_MATRIX_M #define UNITY_PREV_MATRIX_M unity_MatrixPreviousM #endif #ifndef UNITY_PREV_MATRIX_I_M #define UNITY_PREV_MATRIX_I_M unity_MatrixPreviousMI #endif #define real float #define real3 float3 #define real3x3 float3x3 #define SafeNormalize(v) (normalize(v)) float4x4 unity_MatrixPreviousM; float4x4 unity_MatrixPreviousMI; #include "Packages/com.unity.shadergraph/ShaderGraphLibrary/ShaderConfig.cs.hlsl" #ifdef SHADEROPTIONS_CAMERA_RELATIVE_RENDERING #undef SHADEROPTIONS_CAMERA_RELATIVE_RENDERING #define SHADEROPTIONS_CAMERA_RELATIVE_RENDERING (0) #endif #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.shadergraph/ShaderGraphLibrary/ShaderVariables.hlsl" #include "Packages/com.unity.shadergraph/ShaderGraphLibrary/ShaderVariablesFunctions.hlsl" #pragma multi_compile __ HAIR_VERTEX_ID_LINES HAIR_VERTEX_ID_STRIPS #pragma multi_compile __ HAIR_VERTEX_SRC_SOLVER HAIR_VERTEX_SRC_STAGING #pragma multi_compile __ STAGING_COMPRESSION // 0 == staging data full precision // 1 == staging data compressed #ifdef SHADER_API_D3D11 #include "Packages/com.unity.demoteam.hair/Runtime/HairVertex.hlsl" #endif ENDCG SubShader { Tags { "RenderType" = "Opaque" "DisableBatching" = "True" "Queue" = "Geometry" } ZTest LEqual ZWrite On CGPROGRAM #pragma surface StrandSurf Lambert vertex:StrandVert addshadow fullforwardshadows struct Input { float3 strandTangentWS; float3 strandColor; float2 strandUV; float2 surfaceUV; }; sampler2D _MainTex; fixed4 _Color; void StrandVert(inout appdata_full v, out Input o) { o.surfaceUV = v.normal.xy; #ifdef SHADER_API_D3D11 HairVertex hair = GetHairVertex((uint)v.texcoord.x, v.texcoord1.xy, v.vertex.xyz, v.normal.xyz, v.tangent.xyz); { v.vertex = float4(hair.positionOS, 1.0); v.normal = float4(hair.normalOS, 1.0); v.tangent = float4(hair.tangentOS, 1.0); UNITY_INITIALIZE_OUTPUT(Input, o); o.strandTangentWS = TransformObjectToWorldNormal(hair.tangentOS); o.strandColor = hair.strandDebugColor; o.strandUV = hair.strandUV; o.surfaceUV = hair.rootUV; } #else o.strandTangentWS = TransformObjectToWorldNormal(v.tangent.xyz); o.strandColor = float3(1.0, 1.0, 1.0); o.strandUV = float2(0.5, 0.5); o.surfaceUV = float2(0.5, 0.5); #endif } void StrandSurf(Input IN, inout SurfaceOutput o) { #ifdef SHADER_API_D3D11 float3 normalTS = GetStrandNormalTangentSpace(IN.strandUV); #else float3 normalTS = float3(0.0, 0.0, 1.0); #endif o.Albedo = IN.strandColor; o.Normal = TransformTangentToWorld(normalTS, float3x3( normalize(IN.strandTangentWS), normalize(cross(IN.strandTangentWS, o.Normal)), o.Normal) ); fixed4 c = tex2D(_MainTex, IN.surfaceUV) * _Color; o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } Fallback "Hair/Test/TestHairShader" }
基本的にはPropertiesにColorとMaintexを追加し、適用しているだけですが、以下のことに気を付ける必要があります。
includeのパス
#include "Packages/com.unity.demoteam.hair/Runtime/HairVertex.hlsl"
元の記述では #include "HairVertex.hlsl"
となっているのですが、パスの問題でこのままだと正常に処理されないので、パスをPackagesから書く必要があります。
UVの取得
struct Input { float3 strandTangentWS; float3 strandColor; float2 strandUV; float2 surfaceUV; };
元の記述では
struct Input { float3 strandTangentWS; float3 strandColor; float2 strandUV; };
ですが、このままだと生え際の部分のUV(もとのメッシュのUV)が取得できないので o.surfaceUV = hair.rootUV;
と併せて記述する必要があります。
MetalicとSmoothnessは使えない
MetalicとSmoothnessは完全に非対応なようで、現状はこれを設定すると描画が破綻してしまいます。 設定しないようにしましょう。
ほかは通常のSurface shaderとほぼ同様に書けると思います。
また、余談ですが Input に定義されている strandUV
についてですが、これはHairの根元から毛先までのUVなので、これをうまく使えばゲーミング髪の毛とか作れると思います。
以上、現状のレポートでした。
所感
触ってみた感想ですが、ほかのファーシェーダーほぼ触ったことなかったので、リアルタイムでファーがふぁーふぁー動いているのに結構感動していました。 特に動作自体はかなり軽量で、あれだけわさわさ動かしても全然処理落ちしないなんて~! とまさに驚きの連続です。
欲を言えば、 + 事前に生成が必要なこと + 色変えは自分で書かないといけないこと(rootUVとか用意してあるくらいだし、これは普通に後で対応されそう) + 動きが流体ベースなのか、髪を床に寝かせることができない(広がってしまう)こと
あたりが改善されればより完璧だな! と思っています。
それと、現状ではコンピュートシェーダーが動作する環境でないと動作しないとのことで、一部のスマホなどでは動作しないかもです。 自分のPixel 3では動きませんでした…(これは単純に何か設定をミスってる説アリ)。
ファーシェーダー自体は比較的よく見る処理ですが、Unity標準実装となればファーシェーダー採用の敷居をかなり下げることにもなりますし、今後の開発・発表が楽しみですね。
以上、お付き合いいただきありがとうございました。
追記
ゲーミングヘアUnityちゃん作りました。
Unity hairでゲーミングヘアのUnityちゃん作ったwww
— 麻木浅葱 / ARメタバース (@asagi_00a3af) 2022年8月12日
※音は出ません pic.twitter.com/2eWGI7332l
後から知ったんですが、どうやらこの日(13日)はUnityちゃんの誕生日だったらしく…。 めでたい日に変なもん作ってすみませんでした!