Unity HDRPのLitシェーダーを改造してレイマーチングする(GBuffer編)
なにをやったか
今回はUnityのHDRPのシェーダーを改造して、HDRPのシーン上にレイマーチングオブジェクトを表示しました!
Unity HDRP + Raymarchingのプロジェクト公開しました!
— かねた (@kanetaaaaa) 2019年8月11日
興味があれば動かしてみてください!!https://t.co/hcBJKSWzC9
コードの整理ができたらブログに情報を残します#unity3d #raymarching pic.twitter.com/6Jc0HeeOAB
がむさんによる作例もあります
Unity HDRP + Raymarching by @kanetaaaaa を試してみました!
— がむ (@gam0022) 2019年8月20日
カッコいいシーンが無限に作れてしまう😍
これは凄いです🙏#unity3d #raymarchinghttps://t.co/EK6JsHpTBZ pic.twitter.com/ZueP2hfzet
UnityとHDRPのバージョン
- Unity 2019.2.0f1
- HDRP 6.9.0-preview
今回作ったプロジェクトはこちらのリポジトリで公開しています!
もしかしたらHDRPでカスタムシェーダーを作るときに何かの足しになるかもしれないです!!
仕組み
まずどうやってUnityの世界にレイマーチングオブジェクトを表示するのかの仕組みを簡単に説明します。
3Dではフォワードやディファードレンダリングという言葉をよく聞くと思います。HDRPではディファードをさらに発展させたものをハイブリットに使用してレンダリングしています。
ディファードレンダリングでは、GBufferと呼ばれるライティングに必要な情報を収集するパスと、そのGBufferを利用して実際にライティングを行うパスに分かれて世界を表現します。
今回はこのGBufferを生成するパスにレイマーチングによる情報で上書きすることで、ライティングをHDRPの強力なライティングソリューションにお任せするということをしています。
既にUnityでレイマーチングをするのは偉大な先駆者の方々がいらっしゃるので、詳しくはそちらの方々の解説を参照してください。
この記事ではHDRPでレイマーチングするにあたっての知識にフォーカスします。
レイマーチング用シェーダーファイルの構成
レイマーチングオブジェクトのマテリアルは、以下の6つのユーザーシェーダーで構成されます。
- 全てのレイマーチングオブジェクト共通の RaymarchingUtility.hlsl
- 距離関数やマテリアル情報を記載する DF.hlsl
- HDRPの
Lit.shader
をレイマーチング用に改造した Raymarching.shader
もし独自のレイマーチングオブジェクトをこのプロジェクトに追加したければ、 DF.hlsl
と Raymarching.shader
をコピーして、 DF.hlsl
の distanceFunction
と getDistanceFunctionSurfaceData
を実装すればOKです。
GBufferパス
HDRPではマテリアルタイプ毎にGBufferに格納する内容が異なるっぽい(GBuffer0.aとGBuffer2が異なるみたい)ですが、今回のレイマーチングはHDRPのStandardマテリアルのみで描画しています。(今後別マテリアルにも対応したいです)
シェーダー内のコメントによると、特に追加設定の無いStandardマテリアルのGBufferの内容は以下のようになっています。(バージョンアップで変わる可能性もあります)
最終的にこのフォーマットでレンダリングすればHDRPの強力なライティングソリューションで描画してくれます!
既存のLit.shaderで使用されているShaderPassGBuffer.hlslのGBufferパスを覗いてみると、こちらの一行でGBufferに情報を書き込んでいました。
SurfaceData surfaceData; BuiltinData builtinData; GetSurfaceAndBuiltinData(input, V, posInput, surfaceData, builtinData); ENCODE_INTO_GBUFFER(surfaceData, builtinData, posInput.positionSS, outGBuffer);
このインターフェースに乗っ取って、SurfaceDataとBuiltinDataをレイマーチングで生成できれば上手くGBufferに情報を書き込むことが出来そうです。
GBufferパスでレイマーチングを行うように改造する
このプロジェクトでは3つのレイマーチングオブジェクトがありますが、その中のMengerEmitを例に解説します。
GBufferパスのフラグメントシェーダーでレイマーチングを行いたいので、該当の処理を見てみます。
HDRPのシェーダーは各処理毎にシェーダーファイルが分かれていて、includeを差し替えることでいろいろなパスに切り替えれるようになっています。
なのでGBufferのフラグメントシェーダーも実際は.shaderファイルには無くて、ShaderPassGBuffer.hlslに切り出されています。
このhlslファイルを丸々コピーしてレイマーチング用のGBufferパスにカスタマイズしていきます。
GBufferパス改造の本質と言える部分は以下の箇所です。
float3 ray = normalize(input.positionRWS);
float3 pos = GetRayOrigin(input.positionRWS);
DistanceFunctionSurfaceData surface = Trace(pos, ray, GBUFFER_MARCHING_ITERATION);
SurfaceData surfaceData;
BuiltinData builtinData;
ToHDRPSurfaceAndBuiltinData(input, V, posInput, surface, surfaceData, builtinData);
float depth = WorldPosToDeviceDepth(surface.Position);
ENCODE_INTO_GBUFFER(surfaceData, builtinData, posInput.positionSS, outGBuffer);
上記のコードを一つずつ解説していきます。
RayOriginとRayDirectionについて
レイマーチングを行うにはレイマーチを始める点である RayOrigin
と進む方向である RayDirection
が必要です。
今回は球を描画する際にレイマーチングを行うので、本来ならばカメラのワールド座標をRayOriginに、カメラ位置からフラグメントの位置へのベクトルをRayDirectionにするはずです。
しかし私は今回以下のように実装しました。
// RaymarchingUtility.hlsl float3 GetRayOrigin(float3 positionRWS) { float3 pos = float3(0.0, 0.0, 0.0); return pos; } float3 ray = normalize(input.positionRWS); float3 pos = GetRayOrigin(input.positionRWS);
RayOriginが原点...?なんだこれは?と思いますよね...
実はHDRPではカメラの平行移動による座標値の精度低下を抑えるために、MVP行列のMに当たるモデル変換行列の位置成分から既にカメラ座標が差し引かれています。カメラ空間レンダリングというらしいです。
#define UNITY_MATRIX_M ApplyCameraTranslationToMatrix(GetRawUnityObjectToWorld()) float4x4 ApplyCameraTranslationToMatrix(float4x4 modelMatrix) { // To handle camera relative rendering we substract the camera position in the model matrix #if (SHADEROPTIONS_CAMERA_RELATIVE_RENDERING != 0) modelMatrix._m03_m13_m23 -= _WorldSpaceCameraPos; #endif return modelMatrix; }
そしてその変換行列でObject to World変換を行ったフラグメント座標が input.positionRWS
に格納されています。(RWSは Relative World Space
の略だと思います)
つまり、HDRPではフラグメントシェーダーの時点でカメラ位置が原点の空間に変換されているので、カメラ位置を表すRayOriginが原点になります!
RayDirectionも本来ならカメラ座標からフラグメントのワールド座標へのベクトルを求める必要がありますが、今回はカメラ位置が原点の空間なので input.positionRWS
をnormalizeするだけで求めることができます。
カメラ空間レンダリングを行わないとどうなるのかと言ったことはこちらの記事で詳しく解説してくれています。(こちらはレイトレーサーの話なので少し文脈が異なりそうですが...)
実際にHDRPではどういう理由で採用されて、どう動いているかはこちらのHDRPのマニュアルに記載されています。
サーフェースデータをHDRPのデータに変換してGBufferに書き込む
DistanceFunctionSurfaceData surface = Trace(pos, ray, GBUFFER_MARCHING_ITERATION); SurfaceData surfaceData; BuiltinData builtinData; ToHDRPSurfaceAndBuiltinData(input, V, posInput, surface, surfaceData, builtinData); ENCODE_INTO_GBUFFER(surfaceData, builtinData, posInput.positionSS, outGBuffer);
座標変換等少し特殊なことをしていますが、いつものようにスフィアトレーシングをしてサーフェースデータを計算します。(該当の処理はこの辺)
その後 ToHDRPSurfaceAndBuiltinData
でHDRPのライティングに必要なデータに変換します。
関数の中身はこのようになっています。
void ToHDRPSurfaceAndBuiltinData(FragInputs input, float3 V, inout PositionInputs posInput, DistanceFunctionSurfaceData surface, out SurfaceData surfaceData, out BuiltinData builtinData) { surfaceData = (SurfaceData)0; surfaceData.materialFeatures = MATERIALFEATUREFLAGS_LIT_STANDARD; surfaceData.normalWS = surface.Normal; surfaceData.ambientOcclusion = surface.Occlusion; surfaceData.perceptualSmoothness = surface.Smoothness; surfaceData.specularOcclusion = GetSpecularOcclusionFromAmbientOcclusion(ClampNdotV(dot(surfaceData.normalWS, V)), surfaceData.ambientOcclusion, PerceptualSmoothnessToRoughness(surfaceData.perceptualSmoothness)); surfaceData.baseColor = surface.Albedo; surfaceData.metallic = surface.Metallic; input.positionRWS = surface.Position; posInput.positionWS = surface.Position; float alpha = 1.0; #if HAVE_DECALS if (_EnableDecals) { DecalSurfaceData decalSurfaceData = GetDecalSurfaceData(posInput, alpha); ApplyDecalToSurfaceData(decalSurfaceData, surfaceData); } #endif GetBuiltinData(input, V, posInput, surfaceData, alpha, surface.BentNormal, 0.0, builtinData); builtinData.emissiveColor = surface.Emissive; }
HDRPは描画の計算に SurfaceData
と BuiltinData
が必要で、既存のGBufferパスもこの二つを計算する処理が書かれています。
SurfaceData
はレイマーチングによって簡単にデータを作ることができますが、 BuiltinData
はGI等HDRPの仕組みに依存したデータが必要です。
これは GetBuiltinData
というLitBuiltinData.hlslに定義されている関数で SurfaceData
等の情報を使って取得してくれるのでそちらにお任せします。
Emissiveのみ取得後に上書きする必要があるので注意です。
この関数を拡張すれば、SSSマテリアル等にも対応できるのではないかと思っているので今後検証していきたいです。
この関数で取得した SurfaceData
と BuiltinData
を ENCODE_INTO_GBUFFER
を用いてGBufferに書き込みます。
レイマーチングオブジェクトの深度で上書きする
このままだとポリゴンの形状のままの深度値で計算されてしまうので、レイマーチングによって計算された深度で上書きします。
既にGBufferパスのフラグメントシェーダーには、深度を書き込む処理が実装されていますが、プリプロセッサで分岐しているので #define _DEPTHOFFSET_ON
を定義します。
+ #define _DEPTHOFFSET_ON void Frag( PackedVaryingsToPS packedInput, OUTPUT_GBUFFER(outGBuffer) #ifdef _DEPTHOFFSET_ON , out float outputDepth : SV_Depth #endif ) { ・・・ #ifdef _DEPTHOFFSET_ON outputDepth = depth; #endif
HDRPのカスタマイズの内容
今回レイマーチングをHDRPで動かすにあたってHDRP本体に二行ほどの修正を行いました。
- RenderDBuffer(hdCamera, cmd, renderContext, cullingResults); - // We can call DBufferNormalPatch after RenderDBuffer as it only affect forward material and isn't affected by RenderGBuffer - // This reduce lifteime of stencil bit - DBufferNormalPatch(hdCamera, cmd, renderContext, cullingResults); #if ENABLE_RAYTRACING bool validIndirectDiffuse = m_RaytracingIndirectDiffuse.ValidIndirectDiffuseState(); cmd.SetGlobalInt(HDShaderIDs._RaytracedIndirectDiffuse, validIndirectDiffuse ? 1 : 0); #endif RenderGBuffer(cullingResults, hdCamera, renderContext, cmd); + // This will bind the depth buffer if needed for DBuffer) + RenderDBuffer(hdCamera, cmd, renderContext, cullingResults); + // We can call DBufferNormalPatch after RenderDBuffer as it only affect forward material and isn't affected by RenderGBuffer + // This reduce lifteime of stencil bit + DBufferNormalPatch(hdCamera, cmd, renderContext, cullingResults);
具体的に言うとGBufferパスの後にD(ecal)Bufferパスを描画するようにしました。
また Lit.shader
をコピーして作成した Raymarching.shader
から DepthOnlyPass
を消去しました。
なぜこのような変更を加えたかというと、デプステクスチャのコピーのタイミングに関係があります。
HDRenderPipeline.csを除くとデプステクスチャーがコピーされる可能性があるタイミングが以下の二か所にあります。
Decalは設定によってON/OFFを切り替えれるため、DBufferパスを実行する場合はその直前に、しなかった場合はGBufferパスの直後にデプステクスチャーがコピーされるという実装になっているようです。
このままだと何が問題かというと、Decalが有効の場合、GBufferパスの前に既にデプステクスチャがコピーされてしまっているので、レイマーチングで上書きした深度がその後のパスに反映されないという状態になります。
このように前後関係がめちゃくちゃになります。
そのため、GBufferによる深度をコピー前に上書きするために、このような変更を加えました。
この時3つの解決策を思いつきましたが、結局GとDBufferの順番を入れ替えるという方法で解決することにしました。
- ①DBufferとGBufferを入れ替える
- メリット
- 入れ替えるだけで簡単に解決できる
- 追加のコピーや描画が不要なのでコストが安い
- デメリット
- GBufferパスでDBufferを参照しているため、入れ替えることによって1フレーム前のDBufferが参照されてしまう
- メリット
- ➁Depth Only Passでもレイマーチングを行って正しい深度を計算する
- メリット
- HDRP本体に変更を加える必要がないためクリーンに解決できる
- デメリット
- 追加のレイマーチングが必要なため負荷が非常に高い
- メリット
- ➂DBuffer前とGBuffer後、どちらもデプスをコピーするように変更する
- メリット
- 追加のレイマーチングをするのに比べてテクスチャのコピーなので負荷が少ない
- デメリット
- HDRP側の変更が入れ替えるものより少し多い
- 入れ替えるものに比べて少し負荷が高い
- メリット
今回は①の入れ替える方法を採用しましたが、個人的には負荷や正確性を考えて➂が良いのかなと思います。
さいごに
筆が遅すぎて、このプロジェクトを作ってから結構時間が空いてしまいましたが、ここまでの情報でHDRP上でレイマーチングできます!
HDRPのカスタムシェーダーを手書きするのはあまり賢い手段ではないのですが、何かの足しになれば幸いです。
まだ影とモーションベクターのパスの解説ができていないので、来月ぐらいにはそちらも書きたいです。
Unity HDRPをカスタマイズしてアウトラインパスを追加する
HDRPを弄れる環境はこちらで既に整えました
今回は実際にカスタマイズして周作に最適なアウトラインパスを追加してみます。(Render Graphがリリースされたらここの情報は無になります(多分...))
使用するバージョン
- Unity 2019.2.0f1
- HDRP 6.9.0-preview
今回のプロジェクトはこちらです。 github.com
Lit.shaderをカスタマイズしてアウトラインパスを追加する
大元となるのはHDRPのLit.shaderなので丸々コピーしてきてアウトラインパスを追加しましょう。
今回使うバージョンである6.9.0-previewの Lit.shader
はこちらです。
Lit.shader
にはいくつかのパスが既に記述されているので、それを参考にアウトラインを描画するパスを追加します。
Pass { Name "Outline" Tags { "LightMode" = "Outline" } // アウトライン用のパスのためカリング設定を反対にする Cull Front ZTest On ZWrite On HLSLPROGRAM #define SHADERPASS SHADERPASS_FORWARD #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Material.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/Lit.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/ShaderPass/LitSharePass.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/LitData.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/VertMesh.hlsl" PackedVaryingsType Vert(AttributesMesh inputMesh) { VaryingsType varyingsType; // オブジェクトスペースで法線方向に頂点を膨らませる inputMesh.positionOS += inputMesh.normalOS * 0.02; varyingsType.vmesh = VertMesh(inputMesh); return PackVaryingsType(varyingsType); } #ifdef TESSELLATION_ON PackedVaryingsToPS VertTesselation(VaryingsToDS input) { VaryingsToPS output; output.vmesh = VertMeshTesselation(input.vmesh); return PackVaryingsToPS(output); } #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/TessellationShare.hlsl" #endif // TESSELLATION_ON void Frag( PackedVaryingsToPS packedInput, out float4 outColor : SV_Target ) { // アウトラインは黒色 outColor = float4(0.0, 0.0, 0.0, 1.0); } #pragma vertex Vert #pragma fragment Frag ENDHLSL }
HDRPのコードを書き換えてOutlineパスを実行する
最近HDRPのコードを読んでいて描画の大部分は HDRenderPipeline.cs
に記述されていそうな感じがします。
少なくともパスを追加して呼び出すということがしたいだけであれば、ここを起点に読み進めればできそうです!
というわけでOutlineを呼び出す処理を書いていきます。
Outlineパス差し込み
実装は置いておいて HDRenderPipeline.cs
にパスを差し込みます。
今回はアウトラインということなので、DefferdやForwardのライティング処理が終わってから実行したいです。
空描画の次あたりに差し込んでみます。
RenderForwardEmissive(cullingResults, hdCamera, renderContext, cmd);
RenderSky(hdCamera, cmd);
++ RenderOutline(cullingResults, hdCamera, renderContext, cmd);
RenderTransparentDepthPrepass(cullingResults, hdCamera, renderContext, cmd);
RenderOutlineを実装する
差し込みましたが実装がまだないのでその他のRender関数を参考に作ります。
今回は RenderForwardOpaque
を参考にしました。
void RenderOutline(CullingResults cullResults, HDCamera hdCamera, ScriptableRenderContext renderContext, CommandBuffer cmd) { bool debugDisplay = m_CurrentDebugDisplaySettings.IsDebugDisplayEnabled(); using (new ProfilingSample(cmd, "Render Outline", CustomSamplerId.Outline.GetSampler())) { bool msaa = hdCamera.frameSettings.IsEnabled(FrameSettingsField.MSAA); RenderTargetIdentifier[] renderTarget = null; renderTarget = mMRTSingle; renderTarget[0] = msaa ? m_CameraColorMSAABuffer : m_CameraColorBuffer; HDUtils.SetRenderTarget(cmd, renderTarget, m_SharedRTManager.GetDepthStencilBuffer(msaa)); var rendererList = RendererList.Create(CreateOpaqueRendererListDesc(cullResults, hdCamera.camera, HDShaderPassNames.s_OutlineName)); DrawOpaqueRendererList(renderContext, cmd, hdCamera.frameSettings, rendererList); } }
CustomSamplerIdとHDShaderPassNamesにOutlineを追加
RenderOutlineで使用する変数等を追加します。
HDCustomSamplerId.cs
にプロファイリング用に使用する列挙体を追加します
ForwardPassName,
++ Outline,
ForwardTransparentDepthPrepass,
HDStringConstants.cs
にパス名の文字列とIDを追加します。
public static readonly string s_ForwardStr = "Forward";
++ public static readonly string s_OutlineStr = "Outline";
public static readonly string s_DepthOnlyStr = "DepthOnly";
public static readonly ShaderTagId s_ForwardName = new ShaderTagId(s_ForwardStr);
++ public static readonly ShaderTagId s_OutlineName = new ShaderTagId(s_OutlineStr);
public static readonly ShaderTagId s_DepthOnlyName = new ShaderTagId(s_DepthOnlyStr);
動作確認
これでアウトラインパスが動作するHDRPとシェーダーの準備ができたので、作成したシェーダーをアサインしたマテリアルをメッシュに割り当てて動作確認します。
問題なく動作していそうです!
単純なパスなら意外とさっくりと追加できました、もっと良い方法や考慮すべきことが残っていそうな気がするので何か情報をお持ちの方は是非教えていただきたいです。
Unity HDRPのコードを弄れる環境を整える
カスタマイズするか不明ですが、UPMでインストールしたパッケージはカスタマイズしずらいので、公式リポジトリをforkした物を使うプロジェクトを準備しておく。
フォークする
後で使うのでHDRPが含まれているこちらのSRP公式リポジトリをforkする。 github.com
HDRPプロジェクト作る
今回は最近正式リリースされたUnity2019.2.0f1を使用してHDRPプロジェクトを作る。(Unity Hub等でインストールしておく)
この後forkしたSRPをサブモジュール設定するために、作ったプロジェクトは適当にgit管理しておく。
ついでに Packages/manifest.json
を覗いて使用しているHDRPのバージョンを確認しておく。
このUnityバージョンで使用するHDRPは 6.9.0-preview
らしい
プロジェクトでforkしたSRPを使用するためにサブモジュール設定をする。
プロジェクトのディレクトリに移動して以下のコマンドで設定(kaneta1992の部分はそれぞれのユーザー名に合わせる。)
kanet@DESKTOP-82NLVQ1 MINGW64 ~/HDRPExperiments (master) $ git submodule add git@github.com:kaneta1992/ScriptableRenderPipeline.git ScriptableRenderPipeline
設定したら ScriptableRenderPipeline
ディレクトリにリポジトリがクローンされるので、中に入って使用するバージョンをチェックアウトする
kanet@DESKTOP-82NLVQ1 MINGW64 ~/HDRPExperiments/ScriptableRenderPipeline (master) $ git checkout 6.9.0-preview
manifest.jsonを書き換えて認識させる
プロジェクトはまだ公式パッケージを使用しているので、 manifest.json
にサブモジュール設定したディレクトリを設定する。
"com.unity.render-pipelines.high-definition": "file:../ScriptableRenderPipeline/com.unity.render-pipelines.high-definition",
パッケージのインポートが走って特にエラーが発生していなければ準備完了。
今回準備したリポジトリ github.com
2019年7月に作ったシェーダーを紹介する(VRChatライブコーディングもやったぞ!)
はじめに
毎月やっている月毎に制作したシェーダーの紹介第6回目です。
今月は先月までと比べて忙しい時期だった(後先月頑張りすぎて燃え尽きていた...)ので、あまり活動できませんでしたがいくつかシェーダーを作ったので紹介します。
7/3
仕事から帰ってきて友人と通話している間にのんびりシェーダーライブコーディングの練習をしていてできたものです。
Travelerに登場した物体と似たようなものを出そうという意思と、bloom表現をボリューメトリックにしようと考えてできました。
一度のマーチングループで衝突判定とボリューム累積を行っているので、結構いい加減な見た目になっていますが、これはこれでありだと思ってます。
動くものはこちらから glslfan.com
7/16
ネオンとブラーがいい感じのシェーダーを作りました🤔https://t.co/g2tK3NE58o#shadertoy #glsl #raymarching pic.twitter.com/k4iXZzFjdq
— かねた (@kanetaaaaa) 2019年7月15日
会社の同僚にこちらのページを教えてもらい、インスパイアされて作り始めました。 www.reddit.com
Glow表現
レイマーチングでGlow表現をする時はボリューム累積を行うのが一般的なので、今回も例に漏れずそのようにしています。
自分が今までやっていたボリューム累積と異なるところとして
- マーチ数は変更せずにシーンの深度を見てマーチ距離を決定する
- ディザパターンを使用して見た目の情報量を増やす
を取り入れてみました。
深度を見てマーチ距離を決定することで、距離が近い箇所のボリューム累積が正確になり何もしないよりかは良い見た目になったと思います。
ディザパターンはこちらのShadertoyで実装されていたベイヤー配列というパターンを使わせていただきました。
特殊な距離関数合成
距離関数に関する情報はiq氏のページがとても有名ですが、MERCURYというデモグループのこちらのページにもとても有益な距離関数の情報が載っています(こちらは主に距離関数同士の合成に関するメソッドが多数あります)
スパイクブラー
このブラーをスパイクブラーと呼ぶか知りませんが、トゲトゲになるので僕はそのように読んでいます。
実は全然特殊なことをしていなくて、スクリーン中央から放射状にノイズを作成してアニメーション変数iTimeのオフセットとして利用しています。
iTimeにオフセットしてモーションブラーは過去にTraveler 2で利用したテクニックですが、これを放射状なノイズに変更するだけで全然違った印象になりました!
7/17
昨日VRChatで二時間弱ライブコーディングをやりました!
— かねた (@kanetaaaaa) 2019年7月17日
その時にできたものです🤔https://t.co/CmE0gP16jv#glsl #raymarching #neort #VRライブコーディングバトル pic.twitter.com/whdWFfEpIU
帰宅直前、玄関の前あたりでブタジエン氏に誘われて2度目のVRChatライブコーディングをやりました!
今回はなんと坪倉氏制作の4人ライブコーディングバトル特化の闘技場風のワールドで、私含めて4名のライブコーダーで二時間弱で同時にコーディングを実現できました!!
世界初!VR内でライブコーディングバトルを行いました!マジでとんでもない空間だった…。
— 坪倉輝明@メディアアーティスト (@kohack_v) 2019年7月17日
また軽率に開催したい。#glsl #raymarching #vrchat
【テスト配信】VRChatでライブコーディング【VRChat生放送 #20】 https://t.co/vVlApFC2j2 pic.twitter.com/VeN0FLLeS8
当日の様子はこちらにアーカイブされています。
私はアイデアが思いつかないときは過去に見た映像などを再現しようと頑張る傾向にあります。
コーディングが終わった後に、「元ネタを忘れてしまったがTwitterで見た映像を再現しようとした」という話をしたら、観覧者の一人だったfotfla氏が元ネタを知っていて、まさかの裏ライブコーディングで同じネタで作っていました...!
先に言い訳をしておくと、これを書こうとしたら(大元ネタはKeijiroさんのやつ)まさかかねたさんとかぶった。https://t.co/9YnyZGQD4xhttps://t.co/GbTgXD3heO
— fotfla (@fotfla) 2019年7月17日
こんなこともあるのかと盛り上がっていました。
さいごに
7月も何とかシェーダーを作ることができました。
3日坊主で有名な僕が、7ヵ月も続けられていることが今でも驚きです...(それだけシェーダーで遊ぶのは楽しい)
最近はGLSLやシェーダーライブコーディングを始める人が多くて、TLを眺めるだけでもとても楽しいですし、VRChatというプラットフォームでみんなでワイワイシェーダーを書いてボイチャするという空間が楽しすぎました!
8月は何を作るかまだ未定ですが、もし何か作ったら紹介します。
LWRP学習 レンダリングパスを追加する
そろそろUnityも触らないとなぁと思ったので、LWRPを少しずつ学習していこうと思います。
手始めに、目標を達成するためにレンダリングパスを追加してみます。
今回はいずれシャドウマップを使ってエフェクトを作成したかったので、Screen Space Shadowパスをコピーして追加したパスでもう一度シャドウを生成するということをやったので雑なメモを残します。
大体こちらの記事の通りに進めればできました!
ScriptableRenderPassを作る
Scriptable Render Pipelineのリポジトリから該当の処理を探してほぼ丸コピしました。
using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.LWRP; public sealed class MyShadowPass : ScriptableRenderPass { Material m_ScreenSpaceShadowsMaterial; RenderTargetHandle m_ScreenSpaceShadowmap; RenderTextureDescriptor m_RenderTextureDescriptor; const string m_ProfilerTag = "Kaneta Shadows"; public MyShadowPass(RenderPassEvent evt, Material screenspaceShadowsMaterial) { m_ScreenSpaceShadowsMaterial = screenspaceShadowsMaterial; m_ScreenSpaceShadowmap.Init("_ScreenSpaceShadowmapTexture"); renderPassEvent = evt; } public void Setup(RenderTextureDescriptor baseDescriptor) { m_RenderTextureDescriptor = baseDescriptor; m_RenderTextureDescriptor.depthBufferBits = 0; m_RenderTextureDescriptor.msaaSamples = 1; m_RenderTextureDescriptor.colorFormat = true ? RenderTextureFormat.R8 : RenderTextureFormat.ARGB32; } public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { cmd.GetTemporaryRT(m_ScreenSpaceShadowmap.id, m_RenderTextureDescriptor, FilterMode.Bilinear); RenderTargetIdentifier screenSpaceOcclusionTexture = m_ScreenSpaceShadowmap.Identifier(); ConfigureTarget(screenSpaceOcclusionTexture); ConfigureClear(ClearFlag.All, Color.white); } /// <inheritdoc/> public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (m_ScreenSpaceShadowsMaterial == null) { Debug.LogErrorFormat("Missing {0}. {1} render pass will not execute. Check for missing reference in the renderer resources.", m_ScreenSpaceShadowsMaterial, GetType().Name); return; } if (renderingData.lightData.mainLightIndex == -1) return; Camera camera = renderingData.cameraData.camera; bool stereo = renderingData.cameraData.isStereoEnabled; CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag); if (!stereo) { cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity); cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, m_ScreenSpaceShadowsMaterial); cmd.SetViewProjectionMatrices(camera.worldToCameraMatrix, camera.projectionMatrix); } else { // Avoid setting and restoring camera view and projection matrices when in stereo. RenderTargetIdentifier screenSpaceOcclusionTexture = m_ScreenSpaceShadowmap.Identifier(); Blit(cmd, screenSpaceOcclusionTexture, screenSpaceOcclusionTexture, m_ScreenSpaceShadowsMaterial); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } /// <inheritdoc/> public override void FrameCleanup(CommandBuffer cmd) { cmd.ReleaseTemporaryRT(m_ScreenSpaceShadowmap.id); } }
ScriptableRendererFeatureを作る
元のシャドウパス生成処理を真似てScriptableRendererFeatureを作ります。
using UnityEngine; using UnityEngine.Rendering.LWRP; [CreateAssetMenu(fileName = "MyShadowPassFeature", menuName = "Sample/MyShadowPassFeature", order = 1)] public sealed class MyShadowPassFeature : ScriptableRendererFeature { private MyShadowPass currentPass; public override void Create() { var screenspaceShadowsMaterial = new Material(Shader.Find("Hidden/Lightweight Render Pipeline/ScreenSpaceShadows")); if (currentPass == null) currentPass = new MyShadowPass(RenderPassEvent.BeforeRenderingPrepasses, screenspaceShadowsMaterial); } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { RenderTextureDescriptor cameraTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor; currentPass.Setup(cameraTargetDescriptor); renderer.EnqueuePass(currentPass); } }
多分動いてるはず...
2019年6月の成果物を紹介する(シェーダーライブコーディングをやったぞ!)
はじめに
毎月やっている月毎に制作したシェーダーの紹介第5回目です。
今月はこちらの勉強会に登壇したこともあって、シェーダー以外にも成果物がありました!!
そちらも合わせて紹介します。今回は月末に成果物が偏ってしまったので、日付毎の紹介はしません。
KLab TECH Meetup #4で登壇した!
「シェーダーライブコーディングのすすめ」という題で、いかに面白くワクワクするものかという事を熱く語ってきました。
一生遊べると噂のシェーダーライブコーディングを布教するために趣味全開で話します!
— かねた (@kanetaaaaa) 2019年5月28日
ご興味がある方は是非聴きに来て下さい!!https://t.co/RB5rScY9oh#klab_meetup
さらに、登壇後の懇親会で先月から練習していたシェーダーライブコーディングを実演することもできました!!
昨日の勉強会の懇親会中に20分間のライブコーディングでシェーダーを作りました!
— かねた (@kanetaaaaa) 2019年6月19日
初めて人前でコーディングをしたんですが、めちゃくちゃ楽しかったです!!
(当日動かなかったpmod修正済です...)
差分
- q.x = abs(p.x ) - 10.;
+ q.x = abs(q.x ) - 10.;https://t.co/LH3TT4YzSU#klab_meetup pic.twitter.com/k61c3O2ZA1
先日の #klab_meetup の懇親会で行った20分のライブコーディング映像を公開しました!
— かねた (@kanetaaaaa) 2019年6月21日
実況解説は@gam0022 さんと@songofsaya_さんです
突発ながら面白い実況で場を盛り上げてくださって非常に楽しかったです!
動画でもこの空間の楽しさが伝わると思うので是非ご覧ください!https://t.co/1CDeXMfJlT
動画を見てもらえばわかるように大盛況で、この実演で見に来てくださった方々に確実にシェーダーライブコーディングの楽しさを伝えることができたと確信できました!
突発ながら実況解説で盛り上げてくださった我無氏とさやちゃんぐbot氏に感謝です!!
野次が飛んで来たり、煽られながらするライブコーディングは最高に面白かったです!
当日に行ったライブコーディングは実は完全即興ではなく、2日程前に試作品を作ってから数回通しで練習しています。
私自身ライブコーディングを始めてまだまだ日が浅いので完全即興となるとどんなルックにしようと考えたり、技量的な制約であまりかっこよくならないので、楽しんでもらうという意味では練習してきてよかったと思います。(当日は思わぬ不具合でライブ感を演出することもできました?)
が、いつかは完全即興で盛り上げてみたいです!
今回のパフォーマンスが少しでも楽しいと感じた人は、Tokyo Demo Festに参加しましょう、100倍楽しめることを保証します。
今回の発表内容を記事向けにしてこちらに投稿しているのでご興味がある方は是非初めて見てください。
レイマーチングを完全に理解できるかもしれないシェーダー
先ほどの登壇資料の途中に登場した解説図を作るために、シェーダーで作ったアニメーションです。
本日の資料のために眺めるだけでレイマーチングを完全に理解できるかもしれないシェーダーを作りました🤔https://t.co/Hia4I0Dgii#klab_meetup pic.twitter.com/kIuU4USxRJ
— かねた (@kanetaaaaa) 2019年6月19日
もともとはシェーダーで作る予定はなくて、良い作図ソフトが無いか、同じく登壇者の我無氏に聞いたところ
「かねたさんならGLSLで作図すると思った...:(」と煽られてしまったので泣きながら作りました。
結果的に掛かった時間は半日程で、発表後の受けも良かったためシェーダーで作ってよかったです!
発表中にも言いましたが、シェーダーライブコーディングはお勧めしますが、作図をシェーダーオンリーでやるのはお勧めしません!!
ちなみにアニメーションの情報はすべて timeLine
という関数に集約されています。
float sum = 0.0; float tl(float val, float offset, float range) { float im = sum + offset; float ix = im + range; sum += offset + range; return clamp((val - im) / (ix - im), 0.0, 1.0); } void timeLine(float time) { float t = tl(time, 1.0, 0.5); uvToP = mix(0.0, 1.0, eio(t)); t = tl(time, 1.0, 1.0); cameraPos = mix(vec3(0.0), vec3(5., 5., -2.), eio(t)); cameraTarget = vec3(0.0, 0.0, 5.); t = tl(time, 0.5, 1.0); raySphereAlpha = mix(0., 1., t); cornersAlpha = mix(0., 1., t); .......
tl
という関数は、4月に投稿したRemapという関数を変形したものです。
第一引数は現在時刻
第二引数は最後に定義したアニメーション終了から再生開始までの待ち時間
第三引数はアニメーションの幅(長さ)です
戻り値はそのアニメーションが再生開始(0.0)~再生終了(1.0)までの状態をfloatで返します。
tl
関数を使うたびに、そのアニメーションの終了時刻がグローバル変数の sum
に記録されていくので、それを使って関数内で良しなにアニメーションを制御しています。
tl
でどんどんアニメーションを定義していって、戻り値を使って位置情報等を合成すると今回のような複雑なアニメーションを簡単に定義できます。(読めるとは言っていない..)
VRChatで即興ライブコーディング
ブタジエン氏にお誘いをいただいて、VRChat内でシェーダーライブコーディングができるのかテストという名目で完全即興で行いました!
時間は正確に測っていませんが20分~30分ぐらいだった気がします。
(ポキャブラリー→ボキャブラリー)お誘いいただいて、数十分VRChat内でわいわい言いながらシェーダーライブコーディングやっていました!
— かねた (@kanetaaaaa) 2019年6月23日
やはり完全即興だと考える時間が多くて難しいですね🤔
ポキャブラリーを増やさねば...!https://t.co/DUAOHeiExE#glsl pic.twitter.com/erjqyb6EV9
地面と空がさみしいですね... 地面はチェッカーボードぐらい入れても良かったかなぁと今なら思います。
完全即興だとまだまだなのでこれからも精進します!
この会で普段twitterで私が眺めている強い人達と初めて会話ができてとても刺激になりました!
次の機会もあるかも?とのことなのでかっこいいシェーダーをライブコーディングできるように練習しておきます!!
さいごに
6月後半は激動の週でした。
登壇でいろいろな方とも交流が生まれて今後がとても楽しみです!
そしていろいろな方がレイマーチングやシェーダーライブコーディングに興味を持ってくれて楽しいです!
映像も良いですが、そろそろ音楽シェーダーに手を付けないといけないなとも感じています。(Houdiniもやりたい...)
7月は何を作るかまだ未定ですが、もし何か作ったら紹介します。
2019年5月に作ったシェーダーを紹介する
はじめに
毎月やっている月毎に制作したシェーダーの紹介第4回目です。
腰を据えてがっつりシェーダーを書いていた前月までと異なって、今月は1シェーダーにつき60分以内で作ったものばかりになりました。
このように短時間でシェーダーを作成するのは、シェーダーライブコーディングというジャンルで、それに挑戦しました。
4/25
現時点では何も見ずに30分でこれが限界だった(軽率なIFS)
— かねた (@kanetaaaaa) 2019年4月25日
(法線計算できなかった...https://t.co/7AlXMBLHA3 pic.twitter.com/uiaYPOwUvH
世界最大規模のデモパーティであるRevision 2019が4/19 ~ 22に開催されて、そこで行われたシェーダーライブコーディングに影響されて、現時点の実力でどんなものを30分で作れるか挑戦してみたものです。
競技中は事前に用意したコードを持ち込んだり、ググったりできないため、前月までのシェーダーを作っていた時のようにスムーズにやりたいことを実現できませんでした。
このシェーダーを作るうえで以下の詰まりポイントがありました。
- カメラ行列の計算(crossの順番等..)
- カメラ行列とレイの掛け順
- IFSするたびにスケールを変えようと思ってできなかった
- 法線計算しようとしたけどできなかった
- 全部mainに書くんじゃなくて距離関数は分けた方が整理しやすい...(IFSするあたりで結構混乱した)
4/26
その1
新幹線で30分
— かねた (@kanetaaaaa) 2019年4月26日
まだ時間があるのでもう一度やるかもhttps://t.co/1YNlsb4JQ9 pic.twitter.com/ZXXn1Ovb4Q
実家に帰る新幹線内で30分で作ったものです。
前回の反省のうちカメラに関する処理と、法線に関する処理を習得してから取り掛かりました。そのため時間にもある程度余裕ができたので、マテリアル分けに挑戦しました。
実際に動くものはこちらから見れます
https://glslfan.com/?channel=-LdJnYWiIXubgZJAMZJq&viewer=true
その2
二回目
— かねた (@kanetaaaaa) 2019年4月26日
プリミティブ感が拭えない
既に自分のできることに限界を感じているので、テクを覚えるターンが必要なのかもしれない..https://t.co/7Ni3YUJHYM pic.twitter.com/QcjXihaNKh
実家に帰る新幹線内で30分で作ったものその2です。
glslfanで作っているときに@gam0022氏が見に来てちょっかいを入れられながら作っていました。(意見をもらいながら作れるのはライブコーディングならではです!)
今回はIFSのイテレーションごとにマテリアルを変えるものに挑戦してました。
4/27
実家で30分https://t.co/tM1jGppAkT pic.twitter.com/XcnwIJ1i1x
— かねた (@kanetaaaaa) 2019年4月27日
実家で30分かけて作ったものです。
相変わらずIFSでめちゃくちゃな形状を作成しています。
先月も利用した、常に視線の反対側にライトを配置するものでライティングをしてみました。
このような角ばった形状では鉱石のような見た目になって綺麗です。
(RevisionのShader ShowDownでも同じようなライティングをしているものがあったので影響されました)
5/4
実家から東京へ帰る新幹線でおそらく60分ぐらいで作りました。
今まで着手開始から行き当たりばったりの、その場の思い付きでシェーダーを書いていましたが、この辺りから事前にどういったルックにしようか考えてから作るようになりました。また30分で完成させるのは、まだ自分にとってハードルが高いと感じたので制限時間を60分にしました。
今回はビートを刻むポストエフェクト(今回は色収差もどき)と実家で習得したPolar Foldingと鏡面反射を絶対に使用すると決めていました。
くるくる回っている球が鏡面反射をしているのですが、かなりわかりずらいですね...
5/5
少し前にtwitterで見かけたトーラストンネルをライブコーディングで作りました🤔
— かねた (@kanetaaaaa) 2019年5月4日
トーラスのuvで詰まって合計60分ぐらいかかりましたhttps://t.co/4ts64uErcZ pic.twitter.com/QNYSmrq4z3
Twitterで見かけたイケてるシェーダーに似たものを60分で作りました。
元ネタはこちらです
Raytracing a torus with polygonal cross-section by forming it out of clipped cones: https://t.co/hmshTUgD8M pic.twitter.com/eaubJFBBfq
— Edd Biddulph🐚 (@eddbiddulph) 2019年5月3日
無限に見えるトンネルは、実はトーラスの内部をぐるぐる回っているだけです。
トーラスのUV計算に時間がかかった上に、もう一度作れと言われてもとっさに出てこないので復習しておきます...
またこのシェーダーは今まで書いたシェーダーの中で一番短いものでした!
985文字でこのルック!
5/7
就業時間後の帰る前の15分で作りました。
Shader ShowDownで登場したシェーダーに若干インスピレーションを貰っています。
Modで増やした形状にオフセットをつけたかったんですが、疑似乱数がうまくいかずに時間切れになってしまいました...
5/15
久しぶりにライブコーディングをしたhttps://t.co/iVkKO04GoO pic.twitter.com/CtRg9FFbgB
— かねた (@kanetaaaaa) 2019年5月15日
しばらくゲームにはまっており、なかなかシェーダーを書けなかったので、ボリュームライトを絶対に使うぞという気持ちでリハビリも兼ねて60分制限で作りました。
形状を予め決めていなかったので、ボリュームライトがあまり目立たないシェーダーになってしまいました。
5/17
移動の新幹線で60分コーディングhttps://t.co/eufDMfRFKc pic.twitter.com/XdfFdSt4Pw
— かねた (@kanetaaaaa) 2019年5月17日
前回のリベンジで、今度はどんな形状にするか決めてから、今月二度目の実家に帰る新幹線内で作りました。こちらも60分です。
ソフトシャドウだけ自力で書けなかったのでカンニングをしてしまいましたが、ステージとボリュームライトの雰囲気がマッチしていてそれなりのルックになったと思います。
また、今月意図的に学んだテクニックをたくさん使ったので集大成のようなものになりました。
- 法線とポイントライト
- 距離関数の引き延ばし
- Polar Folding
- ボリュームライト
反省点として単色のシェーダーを作ってしまいがちなので、もっといろいろな作品を見て色使いを真似したいなと考えています。
さいごに
今月はライブコーディング月間でした。
今後の課題として以下のものがあるので、来月はこれらを消化していきたいです。
- ノイズ系の習得
- Value Noise
- FBM
- SmoothMin等の特殊な距離感数の合成
- ライブコーディングにおけるマテリアル分け手法の確立
- 未だに右往左往している..
- ライティングの習得
- AO
- ソフトシャドウ
- スペキュラ反射
- ポストエフェクト
- トーンマッピング
来月も、もし何か作ったら紹介します。