Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
13
Help us understand the problem. What is going on with this article?
@jyakushiiii

【Unity/ShaderGraph】CADライクな断面表示

↓のような表示をするため紆余曲折あったので、共有します
ezgif.com-gif-maker (1).gif

自分のレベルは
・シェーダー言語さっぱり分からない
・Unity歴は1年
・今回初めてShaderGraphを触った
です。このくらいの人向けの記事です(なのでもっといい方法があるかも)

データはこちら

目的

・切断部に面がある(ように見える)断面表示
・サブメッシュのあるオブジェクトに対応するため、1シェーダーで断面表示をする
・ぴったり重なった面や多少のめり込みに対応する

環境

Unity2019.4.2(おそらくこれ以降も可)
今回はライトの情報を取得したいので、URPです

前知識

ShaderGraphの概要と使い方は↓の動画を見てください

方針決め

「Unity 断面表示」などで検索すると、↓がヒットします

これらは深度バッファなどを利用してクリッピングし、その後断面部分を塗っている…ようです。
切断部に面を張っていないのが参考になりました。
しかしこれらは「マスク用」「断面用」のように、複数のシェーダーで組みになっていて、
今回は1つのシェーダーにしたいので、使えませんでした。

また、切断部に色をつけないのであれば、ディソルブシェーダーというジャンルが
断面表示に近い感じです。クリッピングにはアルファを使っているみたいです。
この方法は1つのマテリアルでも使えます。

以上から、クリッピングにはアルファを使い、
切断部を単色塗りつぶしをすることにしました。

試作(Unlit)

はじめPBRのライティングを無効にする方法がわからず、
Unlitにライティングを追加することにしました。

試作の過程

(参考1)https://qiita.com/o_s_t/items/c546962bbd9f064c9908
UnlitからPBRを再現している方がいました(すごい)。
これを全部見ていくと、PBRの処理がよく理解できそうです。

(参考2)https://blogs.unity3d.com/jp/2019/07/31/custom-lighting-in-shader-graph-expanding-your-graphs-in-2019/
こちらは公式で、ライトの情報を取得する方法が書いてあります。
これは後で断面のライティングをするために使います。

(参考3)https://noahbannister.blog/2019/09/23/unity-lwrp-pbr-shader-node/
こちらは通常のPBR処理を呼び出しています。
今回はこちらを使用。
このままのコードではエラーが出たため、修正
SHADERGRAPH_PREVIEWを使って、プレビュー用の値を設定してやらないといけないみたいです

test.hlsl
void LightweightPBR_float
(float3 Albedo,
    float Metallic,
    float3 Specular,
    float Smoothness,
    float Occlusion,
    float3 Emission,
    float Alpha,
    float3 PositionWS,
    float3 NormalWS,
    float3 ViewDirectionWS,
    float FogCoord,
    float3 VertexLighting,
    float3 BakedGI,
    out float4 fragOut)
{
#if SHADERGRAPH_PREVIEW
    fragOut = float4 (1, 1, 1, 1);
#else
    InputData inputData;
    inputData.positionWS = PositionWS;
    inputData.normalWS = NormalWS;
    inputData.viewDirectionWS = ViewDirectionWS;
    inputData.shadowCoord = TransformWorldToShadowCoord(PositionWS);// GetShadowCoord(GetVertexPositionInputs(PositionWS));
    inputData.fogCoord = FogCoord;
    inputData.vertexLighting = VertexLighting;
    inputData.bakedGI = BakedGI;
    fragOut = UniversalFragmentPBR(inputData, Albedo, Metallic, Specular, Smoothness, Occlusion, Emission, Alpha);
#endif
}


結果がこちら

image.png
おおよそ最終版と同じ見た目ですが、他のオブジェクトからの影を受けられていません。

↑こちらの記事を参考すれば実現できそうですが、
この辺りでPBRもmetallic、normal、smoothneesを0にし、emissionを指定すればUnlitのようになることがわかり、PBRにまき直しました。

最終版(PBR)

やっていることは
1.スクリプトから断面用プレーンのtransformをマテリアルに送る
2.アルファで切断、
3.断面部から見える裏面をmetallic、normal、smoothneesは0にして真っ黒に
4.断面色+偽のライティングをemissionに設定
です。

まず、スクリプトです。
[ExecuteInEditMode]と書くとplay時以外にも実行してくれます。
スクリプトからレンダラー経由でマテリアルの情報を書き換えると
インスタンス化(マテリアルがオブジェクトごとに独立)されるので注意。

断面指示.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class 断面指示 : MonoBehaviour
{
    public Renderer[] レンダラー;
    void Update()
    {
        for (int i=0;i< レンダラー.Length; i++)
        {
            Material[] M = レンダラー[i].materials;
            for(int j = 0; j < M.Length; j++)
            {
                M[j].SetVector("_DANMEN_Pos", transform.position);
                M[j].SetVector("_DANMEN_Vec", transform.up);
            }
            レンダラー[i].materials = M;

        }
    }
}

10.png

スクリプトからパラメーターを変更したい場合は、Referenceの名前を"_パラメーター"などに変更しておきます。

image.png

次にグラフの説明です

1_1.png

①重なったメッシュが汚くなるため、法線内側方向に目立たない範囲で縮める

2.png
ezgif.com-gif-maker (2).gif

②通常のアルベドのテクスチャ処理

3.png

③断面部分にライティングをするための処理

4.png
いろいろ厳密には違うんでしょうが、自分にはこれが限界でした。
通常だとオブジェクトのノーマルを入れるところに、スクリプトから渡した断面用プレーンのtransformを使ってライティングを行います。
使い勝手をよくするため、断面色をベースカラーにするか、別の色にするか、偽ライティングを有効にするか切り替えできるようにしています。
ライトの情報は↓の記事よりカスタムファンクションを作成しました。

9.png
ライティングの結果、オブジェクトの影が落ちています。
GIも使っているので、照り返しがあるとリアルな面になります。
(positionは裏面のものなので、なんとなくそれっぽいだけですが)
image.png
ただし、他のオブジェクトからの影は落とせないです。
実際にはここに面がないためです(なにか方法があれば教えてください)。
image.png

④通常のノーマル処理

テクスチャがある時とない時でBranchで切り替え。
(もっとスマートな方法があるかも)
5.png

⑤通常のオクルージョン処理

あっているはず…
6.png

⑥断面位置でカットする処理

7.png
スクリプトから渡した断面用プレーンのtransformを使い、プレーンのどちら側にあるかをベクトルの内積を使って求め、アルファを0or1に切り替えます。

⑦表、裏面(断面部)で切り替える

↑で作ったノードをつなぎます
8.png

これで完成です

まとめ

今回初めてShaderGraphを触りましたが、使いやすかったです。
そもそものシェーダーができる範囲がとても広く、見た目だけでなくもはや機能のような
物を作れると分かり勉強になりました。

また今回Qiitaで記事を書くのが初めてなんですが、そこそこ時間がかかり、
丁寧な記事を書く先人に一層感謝しました。

以上です。
ありがとうございました。
image.png

13
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
13
Help us understand the problem. What is going on with this article?