見出し画像

Unity2D(URP)で3Dオブジェクトのアウトラインを書いてみた(その1)

こんにちはDSPSEのプログラム担当のk.suzukiです。

今回は、参加したUnity1Week作品の中で、ゲームキャラをunityのURPでカスタムポストプロセスを書いてアウトライン表現をしてみました。

実際に作ったキャラ表現がこんな感じです

画像1

見てもらうと分かると思いますが、絵ずらがアウトラインのみで構成されています。

普通アウトラインのみと言うと、バリア等に使われるリムライトのシェーダーかポストプロセスによるエッジ抽出を思い浮かべますね。

リムライトのシェーダーは簡単に言うと、カメラから、物体を見たベクトルと、描画する部分の法線を比較し、なるべくカメラに向いて無い物を強調します。

リムライトのシェーダーは今回の作品の中でも書いていて、下の図で言うとアリの表現で使っています。左の主人公と比較してもらうと、若干表現が違うのが分かると思います。

うっすら透明がかるのと、オブジェクトのつなぎ目が割とはっきりしますので、宝石的な輝きが出ます。

画像2

これらの作成方法を解説していきます。

今回は2dで作品を作りました。2Dの中に3dオブジェクトを描くのは面倒な事が多いですが、混在させると表現の幅が広がります。そういう意味で今回は面倒でも3dオブジェクトを用意して、それ用のシェーダーを書いています。

では、実際にやっていこうと思います。


プロジェクトを作るときは2dを選択するか、以下の記事を参考にUniversalRenderPipeLineを選択してください。SRPBatcherが相当強力なので、早めに導入に成れて置いて損はありません。

導入が出来そうな記事を貼っておきます

https://moon-bear.com/2020/07/10/unity-urp/

上の記事だけだとURPのみで2Ð系のパッケージが足りません。

画像13

ここら辺を導入しておいてください。


画像13

まずは、簡単な蟻の方のリムライトシェーダーを解説します。

URPを導入するとPackage下に以下のフォルダが出来ていますので、選択→右クリック→エクスプローラーで表示をします。

画像4

エクスプローラーが表示されますので、Shadersフォルダの中に入り

画像6

Lit.shaderとLitFowardPassをコピーします。

その後適当なフォルダを作ってコピーします。

画像6

コピーしたこれを改変していきます。

Litの中にツラツラと凄い記載がありますね!これを必要最低限までカットして以下の形にします。余計なPASSを全消ししてます。

rimシェーダーは影とか関係無く、どうせTransparentな世界に置かれるので1passでokです。軽くていいですよね。

Litシェーダー改変

Shader "test/Lit"
{
   Properties
   {
       // Specular vs Metallic workflow
       [HideInInspector] _WorkflowMode("WorkflowMode", Float) = 1.0

       [MainTexture] _BaseMap("Albedo", 2D) = "white" {}
       [MainColor] _BaseColor("Color", Color) = (1,1,1,1)

       _Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5

       _Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5
       _GlossMapScale("Smoothness Scale", Range(0.0, 1.0)) = 1.0
       _SmoothnessTextureChannel("Smoothness texture channel", Float) = 0

       _Metallic("Metallic", Range(0.0, 1.0)) = 0.0
       _MetallicGlossMap("Metallic", 2D) = "white" {}

       _SpecColor("Specular", Color) = (0.2, 0.2, 0.2)
       _SpecGlossMap("Specular", 2D) = "white" {}

       [ToggleOff] _SpecularHighlights("Specular Highlights", Float) = 1.0
       [ToggleOff] _EnvironmentReflections("Environment Reflections", Float) = 1.0

       _BumpScale("Scale", Float) = 1.0
       _BumpMap("Normal Map", 2D) = "bump" {}

       _Parallax("Scale", Range(0.005, 0.08)) = 0.005
       _ParallaxMap("Height Map", 2D) = "black" {}

       _OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0
       _OcclusionMap("Occlusion", 2D) = "white" {}

       [HDR] _EmissionColor("Color", Color) = (0,0,0)
       _EmissionMap("Emission", 2D) = "white" {}

       _DetailMask("Detail Mask", 2D) = "white" {}
       _DetailAlbedoMapScale("Scale", Range(0.0, 2.0)) = 1.0
       _DetailAlbedoMap("Detail Albedo x2", 2D) = "linearGrey" {}
       _DetailNormalMapScale("Scale", Range(0.0, 2.0)) = 1.0
       [Normal] _DetailNormalMap("Normal Map", 2D) = "bump" {}

        _RimEffect("_RimEffect", Float) = 1.0
       [HDR]_RimColor("_RimColor", Color) = (0,0,0,0)      

       // SRP batching compatibility for Clear Coat (Not used in Lit)
       [HideInInspector] _ClearCoatMask("_ClearCoatMask", Float) = 0.0
       [HideInInspector] _ClearCoatSmoothness("_ClearCoatSmoothness", Float) = 0.0

       // Blending state
       _Surface("__surface", Float) = 0.0
       _Blend("__blend", Float) = 0.0
       _AlphaClip("__clip", Float) = 0.0
       _SrcBlend("__src", Float) = 1.0
       _DstBlend("__dst", Float) = 0.0
       _ZWrite("__zw", Float) = 1.0
       _Cull("__cull", Float) = 2.0


       _ReceiveShadows("Receive Shadows", Float) = 1.0
       // Editmode props
       [HideInInspector] _QueueOffset("Queue offset", Float) = 0.0

       // ObsoleteProperties
       [HideInInspector] _MainTex("BaseMap", 2D) = "white" {}
       [HideInInspector] _Color("Base Color", Color) = (1, 1, 1, 1)
       [HideInInspector] _GlossMapScale("Smoothness", Float) = 0.0
       [HideInInspector] _Glossiness("Smoothness", Float) = 0.0
       [HideInInspector] _GlossyReflections("EnvironmentReflections", Float) = 0.0

       [HideInInspector][NoScaleOffset]unity_Lightmaps("unity_Lightmaps", 2DArray) = "" {}
       [HideInInspector][NoScaleOffset]unity_LightmapsInd("unity_LightmapsInd", 2DArray) = "" {}
       [HideInInspector][NoScaleOffset]unity_ShadowMasks("unity_ShadowMasks", 2DArray) = "" {}
   }

   SubShader
   {
       // Universal Pipeline tag is required. If Universal render pipeline is not set in the graphics settings
       // this Subshader will fail. One can add a subshader below or fallback to Standard built-in to make this
       // material work with both Universal Render Pipeline and Builtin Unity Pipeline
       Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "UniversalMaterialType" = "Lit" "IgnoreProjector" = "True" "ShaderModel"="4.5"}
       LOD 300

       // ------------------------------------------------------------------
       //  Forward pass. Shades all light in a single pass. GI + emission + Fog
       Pass
       {
           // Lightmode matches the ShaderPassName set in UniversalRenderPipeline.cs. SRPDefaultUnlit and passes with
           // no LightMode tag are also rendered by Universal Render Pipeline
           Name "ForwardLit"
           Tags{"LightMode" = "UniversalForward"}

           Blend[_SrcBlend][_DstBlend]
           ZWrite[_ZWrite]
           Cull[_Cull]

           HLSLPROGRAM
           #pragma exclude_renderers gles gles3 glcore
           #pragma target 4.5

           // -------------------------------------
           // Material Keywords
           #pragma shader_feature_local _NORMALMAP
           #pragma shader_feature_local_fragment _ALPHATEST_ON
           #pragma shader_feature_local_fragment _ALPHAPREMULTIPLY_ON
           #pragma shader_feature_local_fragment _EMISSION
           #pragma shader_feature_local_fragment _METALLICSPECGLOSSMAP
           #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
           #pragma shader_feature_local_fragment _OCCLUSIONMAP
           #pragma shader_feature_local _PARALLAXMAP
           #pragma shader_feature_local _ _DETAIL_MULX2 _DETAIL_SCALED
           #pragma shader_feature_local_fragment _SPECULARHIGHLIGHTS_OFF
           #pragma shader_feature_local_fragment _ENVIRONMENTREFLECTIONS_OFF
           #pragma shader_feature_local_fragment _SPECULAR_SETUP
           #pragma shader_feature_local _RECEIVE_SHADOWS_OFF

           // -------------------------------------
           // Universal Pipeline keywords
           #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
           #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
           #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
           #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
           #pragma multi_compile_fragment _ _SHADOWS_SOFT
           #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION
           #pragma multi_compile _ LIGHTMAP_SHADOW_MIXING
           #pragma multi_compile _ SHADOWS_SHADOWMASK

           // -------------------------------------
           // Unity defined keywords
           #pragma multi_compile _ DIRLIGHTMAP_COMBINED
           #pragma multi_compile _ LIGHTMAP_ON
           #pragma multi_compile_fog

           //--------------------------------------
           // GPU Instancing
           #pragma multi_compile_instancing
           #pragma multi_compile _ DOTS_INSTANCING_ON

           #pragma vertex LitPassVertex
           #pragma fragment LitPassFragment

           #include "LitInput.hlsl"
           #include "LitForwardPass.hlsl"
           ENDHLSL
       }
   }

   FallBack "Hidden/Universal Render Pipeline/FallbackError"
   //CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitShader"
}

重要なところはそれほど無く以下の部分に注目です

・1行目はシェーダー名です。好きな名前でシェーダー名を付けて下さい。

・35行目ら辺にパラメータを追加しています。Rimに関するパラメータです。外側の強さと色になります。

        _RimEffect("_RimEffect", Float) = 1.0
       [HDR]_RimColor("_RimColor", Color) = (0,0,0,0)    

・42行目ら辺の// Blending stateを手動設定できるように、Hideininspectorを消します。

      // Blending state
       _Surface("__surface", Float) = 0.0
       _Blend("__blend", Float) = 0.0
       _AlphaClip("__clip", Float) = 0.0
       _SrcBlend("__src", Float) = 1.0
       _DstBlend("__dst", Float) = 0.0
       _ZWrite("__zw", Float) = 1.0
       _Cull("__cull", Float) = 2.0

・Tagsの所に注目です。今回は3Dオブジェクトを2Dの世界用のカメラが写すのでUniversalForward→Universal2Dに変更します。

Tags{"LightMode" = "Universal2D"}

・#include "Packages/com.unity.render-pipelines.universal/Shaders~~~"の行を改変用のshader直下を見るように前のパスを削り以下に変更します。

           #include "LitInput.hlsl"
           #include "LitForwardPass.hlsl"

・一番下の行はStandardShaderのEditorGuiに関するテンプレートになります。新しいパラメータを追加したら、テンプレートに無いため、隠れてしまい設定できません。冗長には成りますが、コメントアウトして素のパラメータ設定表示に戻します。

    //CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.LitShader"


入力変数可変

Litの中に新しいパラメータの受け口として_RimEffectと_RimColorを追加しています。このままだと、単なるパラメータの受け口の為uniform変数を登録していきます。

登録する場所はどうせならSrpBatcherを効かせたいですよね!なので、Cbufferの記載があるLitInputの中身を改変します。

こちらは、先ほどコピーしたLitInputの中身を改変してください。

#ifndef UNIVERSAL_LIT_INPUT_INCLUDED
#define UNIVERSAL_LIT_INPUT_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ParallaxMapping.hlsl"

#if defined(_DETAIL_MULX2) || defined(_DETAIL_SCALED)
#define _DETAIL
#endif

// NOTE: Do not ifdef the properties here as SRP batcher can not handle different layouts.
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
float4 _DetailAlbedoMap_ST;
half4 _BaseColor;
half4 _SpecColor;
half4 _EmissionColor;
half _Cutoff;
half _Smoothness;
half _Metallic;
half _BumpScale;
half _Parallax;
half _OcclusionStrength;
half _ClearCoatMask;
half _ClearCoatSmoothness;
half _DetailAlbedoMapScale;
half _DetailNormalMapScale;
half _Surface;

float  _RimEffect;
half4  _RimColor;
CBUFFER_END
ーーーーーーーーーーーーーーーーー長いので以下略ーーーーーーーーーーーーーーーーー

このCBUFFERの間に追加すると、同マテリアル間でパラメータをに差異があったとしてもバッチングされます。

確認方法はシェーダーを選択するとインスペクターに内容が表示されます。

画像7

その中でSRP Batcher Compatible状態なら、マテリアルコピーって変化を付けてもSetpasscallがバッチングされます。もう最高!


次はVertexShaderとFragmentShaderを改変していきます。

両者は、直下にコピーしたLitForwardPass.hlslに記載されています。

そのファイルの中のLitPassVertexがVertexShaderでLitPassFragmentがFragmentShaderに相当します。

今回のRimシェーダは頂点を特に弄らないのでFragmentShaderを改変していきます。

half4 LitPassFragment(Varyings input) : SV_Target
{
   UNITY_SETUP_INSTANCE_ID(input);
   UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

#if defined(_PARALLAXMAP)
#if defined(REQUIRES_TANGENT_SPACE_VIEW_DIR_INTERPOLATOR)
   half3 viewDirTS = input.viewDirTS;
#else
   half3 viewDirTS = GetViewDirectionTangentSpace(input.tangentWS, input.normalWS, input.viewDirWS);
#endif
   ApplyPerPixelDisplacement(viewDirTS, input.uv);
#endif

   SurfaceData surfaceData;
   InitializeStandardLitSurfaceData(input.uv, surfaceData);

   InputData inputData;
   InitializeInputData(input, surfaceData.normalTS, inputData);

   half4 color = UniversalFragmentPBR(inputData, surfaceData);

   color.rgb = MixFog(color.rgb, inputData.fogCoord);
   color.a = OutputAlpha(color.a, _Surface);
   
   float val = max( 1 - (dot(input.viewDirWS.xyz, input.normalWS.xyz)  * _RimEffect ),0);
   color += (_RimColor * val * val);
   return color;
}

注目してほしい改変箇所はたったの2行です。

   float val = max( 1 - (dot(input.viewDirWS.xyz, input.normalWS.xyz)  * _RimEffect ),0);
   color += (_RimColor * val * val);

returnの前に、この2行だけ追加しています。

解説すると、input.viewDirWSが、カメラの方向ベクトルで、input.normalWSがピクセルの法線です。

dot積を取り、1から引くことにより、一番カメラビューに向いて無い部分が強調され、一番カメラ側を向いている部分が薄くなります。

このシェーダーをオブジェクトに当てて行きます。

画像8

まずシーンにオブジェクト追加(ゲームオブジェクト→3dオブジェクト→カプセル)

新規にマテリアル追加(好きなフォルダでアセット→作成→マテリアル)

画像9

作られたマテリアルを選択し、Shaderを作った(今回だとtest/Litに一番上の行で改変している)シェーダに変えます。

パラメータ設定

画像10

Colorはオブジェクトの下地色です。アルファを低くして透明にすることをお忘れなく!

画像11

下の方の設定。

_RimColorは外周の色です。_RimEffectは外周の太さです。

__surface~レンダーキューlまではショットのまま設定してください。

_RimColorはHDR対応させてBloomとか合わせると結構光ります。

画像12


ここまでで、長くなりすぎてしまった感があるので、仕切り直します。

次は本題である、アウトライン用のカスタムポストプロセスを書いて行きます。




この記事が気に入ったらサポートをしてみませんか?