54
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

HoudiniでUV展開&リグ入門

Posted at

この記事はHoudini Apprenticeアドベントカレンダー2019 25日目の記事です。
HoudiniのUV展開とリグについて、網羅的に機能を確認し、自力でUV展開&リグのセットアップができる状態を目指します。

しかし、この記事・・・大変申し訳ないことに、体調不良で100%の状態で完成しておりません。
現状は、だいたい8割ぐらいの内容となっています。できるだけ早く完成させるようにしますので、しばしお待ちください。

#UV展開
まずはUVです。
俺はキャラクターなんか作らないし、HoudiniでUV展開なんて一生しない!!と思っている方もいるかと思います。しかしHoudiniでのUV展開はプロシージャルなプロジェクトにおいて非常に重要な役割を果たします。
なぜかというと、UV展開したUV座標は必ず0~1に収まるからです。この0~1に格納された整合性のあるモデルはプロシージャルの上流において非常に柔軟な素材となります。
キャラクターを作らない方も、この機会にUV展開について勉強してみましょう。

#UVの種類
Geometry内でノードを呼び出す際にuvと検索すると、
image.png

けっこうな数あります。
それもそのはず、HoudiniでUV展開する手法は1つではありません。
手動で展開させる場合もあれば、プロシージャルで展開させる場合もあります。
まずは、どんな種類のUV展開があるのかを見ていきましょう。

※前回の復習となりますが、UV展開のビューを見るためには、space+5を押します。

##UV Quick Shade

image.png
image.png

名前にQuickとあるように、とにかく時間が無いとか、Gridにバシッと一枚絵を出す際に使用します。
UV Quick Shadeの中に入るとMaterialノードがあります。なので1ノード作るだけでshop_materialpathも付与してくれます。滅茶苦茶楽ですね。
デフォルトでは、$HFS/houdini/pic/uvgrid_grey.pic が適用されています。必要に応じて画像を変えて確認してみてください。
image.png

ただし、速度と引き換えに自由度はほぼありません。projection方向はX・Y・Zに限定されています。
なんだ、平面のGridぐらいにしか使えないのか・・・。と思われるかもしれませんが、このノードはちょっとした特徴があります。先ほどのQuickShadeのノード内をもう一度見てください。そこには何故かswitchノードがあります。
switchの条件は

if (!hasdetailattrib(opinputpath(".", 1), "currentlayer") || (detail(opinputpath(".", 1), "currentlayer", 0) == 1),
    max(haspointattrib(opinputpath(".", 1), "uv"), hasvertexattrib(opinputpath(".", 1), "uv")),
    max(haspointattrib(opinputpath(".", 1), "uv" + detail(opinputpath(".",1), "currentlayer", 0)), 
        hasvertexattrib(opinputpath(".", 1), "uv" + detail(opinputpath(".",1), "currentlayer", 0))))

です。つまり、入力にUVのAttributeが存在しない場合は、平面にUVを貼るけれども、UVを持っている(has***attrib)場合はそのまま利用すると書いてあります。よってこれから紹介するUV展開を行ったあとにQuickShadeを繋げることで簡単にuvgrid_grey.picマテリアルを適用したモデルを確認することができます。

##UV Unwrap
image.png

image.png

さきほどのUV Quick Shadeは、平面しか使えませんでした。しかし、3Dソフトは普通は立体物を扱います。そこでUV Unwrapを利用します。簡単なUVであれば自動でUVを合理的に平坦化、重複なしのグループに分離してくれます。

image.png

ここで重要なのはPlanesです。
今回はBoxを展開させようとしたので、Planesを6としています。
これは、簡単に言えば 左・右・前・後・上・下 の6方向からPlane状にProjectionして、いい感じにUVをカットしてくれます。ただし、Planes=アイランド数ではありません。あくまで形状に応じて切り分けを行ってくれます。

LayoutはカットしたUVをUV座標上にどのように展開したいか
Spacingは各アイランド間のマージンです。
RotateはPlanesをあてがう軸を指定します。

UV Unwrapでは、立方体のような単純なジオメトリならきれいに出力されます。しかし、単純ではない。たとえば、球状のものを展開しようとした場合は、別のノードでUV展開をした方がよさそうです。

##UV Texture
image.png

UV展開において、もっとも基本となるノードがUV Textureノードです。
平面はもちろん、球状や筒状のものをUV展開しようとした場合は、このノードを利用します。

投影タイプ
Orthographic 指定した軸方向に直接投影します。 grid.png
Polar 指定した軸方向で球状に閉じます。 poll1.png
Cylindrical 指定した軸方向で円柱状に閉じます。 pol.png
Row & Columns メッシュで構築されたジオメトリ用。U座標は、Row(横の列)の方向に、V座標はColumn(縦の列)の方向に沿います。 line.png
Face 各フェースにテクスチャのコピーを各フェース法線方向にマッピングして、テクスチャを適切な向きにします。 しかし、マップは各ポリゴンにフィットするようにスケールされず、さらに各ポリゴンの形状で歪みません。 mes.png
Modify Source 既にソースにテクスチャUV座標があれば、そのUV座標が保持されますが、スケールとオフセットがされません。 UV EditとUV Transformも参照してください。
Uniform Spline ポリゴンカーブとNURBS/Bezierのカーブ/サーフェスのみ。各カーブ/サーフェスの基底をUV方向で均一にサンプリングして、その値をテクスチャ座標としてサーフェスのポイント/頂点に割り当てます。
Average Spline ポリゴンカーブとNURBS/Bezierのカーブ/サーフェスのみ。各制御頂点の座標、GrevilleポイントをテクスチャUVとして使用します。
Arc Length Spline ポリゴンカーブとNURBS/Bezierのカーブ/サーフェスのみ。各カーブ/サーフェスの基底をサーフェスの円弧長に基づいてサンプリングし、その値をテクスチャ座標としてサーフェスのポイント/頂点に割り当てます。
Perspective From Camera オブジェクトのワールド空間とカメラの投影空間が正確に一致するようにテクスチャ座標を割り当てます。

各投影タイプの解説はこちらから引用

##UV Project
UV Textureは、あくまでTexture Typeを選択するだけでした。
歪んだ形状に展開したい場合は、状況に応じて選択した形状を回転させたくなることがあります。そうした場合は、UV Projectをお勧めします。

UV Projectでは何種類かのProjection手法が準備されているので、形状に応じて最適な手法を選択します。
image.png

##UV Flatten
UV Flattenは、このノードだけで正しく展開することはできません。
まずは下準備をした後、後処理が必要となります。

UV Flattenは名前の通り、UVを展開させます。しかし、展開するためにはエッジに切れ目を入れなければなりません。

UVをカットする手法は2種類存在します。エッジを直接指定するか、エッジグループで指定するかです。
###手動でUV展開
SideFXからUV展開のチュートリアルが出ているので、動画で見たい方はこちらから

UVのカットは、選択モードにおいて Cut UV Layout を使用します。
image.png
するとBoxが展開されました。

※後日追記
###UV Edit
そこで、UV Editを使用します。これはプロシージャルが破綻するので注意が必要です。

###UV Fuse
合わせて、UVの結合も必要に応じて行います。

が、、方向が斜めになってしまいました。

##UV Transform
image.png
これを調整するのが、UV Transformです。

###UV Overwrap
tubeの場合などは、重なってしまう場合があります。
ビューポートオプションから、どの部分が重なっているのか確認することができます。
image.png

##UV Pelt
UV Flattenに非常に似ていますが、こちらはストレッチがかかります。
正確に言えば、テクスチャ領域の端側に引っ張ることでUVを緩めます。
これ一つで望ましい形状にするのは難しいでしょう

以下ヘルプ参照

UVPeltは、革職人が皮はぎ(Pelt)を作る動作と非常に似ています。 サーフェスのトポロジーが円盤に近くなるようにジオメトリにカットを加えます。 このサーフェスの境界は、フレームに繋がって、伸ばされます。
閉じたフェースをPeltのフレームとして使うことができます。 フレームを指定しなかった場合は、円が使われます。 カットをした後にPeltするジオメトリの箇所を指定するには、ヒントポリゴンを使います。 ヒントポリゴンを含んだセクションの一番長い境界がフレームに繋げられるエッジになります。

##AutoUV
AutoUVは、非常に楽にUV展開を行ってくれます。
チュートリアル動画はこちら

もともと、AutoUVは、GameDevelopmentToolsetの機能としてリリースされていました。Houdini18では、SideFXLabに統合されています。

SideFXLabのインストールは、今年のアドベントカレンダーにも登場しましたね。
SideFX Labs Toolの新規のインストール方法、Package機能に関して検証してみる

とはいえ、基本的にやることは、これまでのGameDevelopmentToolset同様、アタリのコントローラーから最新版のAuto UVを呼び出すだけです。
image.png
このAutoUVは、初めて登場した際は「Shortest Path」と「cluster」の2種類のメソッドが用意されていました。現在のAutoUVは4種類のメソッドが用意されています。デフォルト手法もバージョンに応じて変わっています。

image.png

手法 特徴 展開
Shortest Path H16時のデフォルト手法です。UV シームを作成するため、サーフェスの曲率に応じて最短距離を割り出すアルゴリズムを使用しています。 1shortpath.png
Cluster メッシュを四辺形で分割してUVアイランドを作成します。 cluster.png
Axis Aligned UV Unwrap に似ていますが、細かくなりすぎた UV アイランドをよりうまくまとめています。現在欠番。
UV Unwrap H16.5時のデフォルト手法です。基本的な UV Unwrap 法です。 unwrap.png
UV AutoSeam H17時からのデフォルト手法です。GeometryからUVカットラインを検索し、UV Viewで表示することができます。 5auto.png

##RizomUV
Houdini18から、SideFXLabにサードパーティのUV展開で非常に高機能な RizomUV の機能を利用できるノードが提供されています。注意点として、2019/12現在、RizomUVはSideFXLabのプロダクションビルドには含まれていません。よって、このノードを取得するには、インストールプロセス中に「Production Build Only」のチェックを外す必要があります。
lablab.png
アップデートすると、ノードが現れます。
image.png

さて、このRizomUVですが、Houdiniインストール直後そのままで使用できるわけではありません。RizomUVも別途購入する必要があります(←重要!)

###RizomUVの購入とインストール
まずは、RizomUVソフトウェアが必要です。Rizom LabのWebサイトから30日間の評価版を購入するか、インディーライセンス、もしくはプロライセンスを購入します。インディーライセンスだと、1か月単位(14.9ユーロ:約1800円)、もしくは永久ライセンス(149ユーロ:約18000円)となります。僕はうかうかしていたら、評価期間が切れてしまっていたので、1か月のライセンスを購入します。
image.png

image.png
購入には、名前、住所、電話番号の入力が必要です。決算方法はクレジットカードの他paypalなども選択できます。
image.png

https://www.rizom-lab.com/my-account/licenses/
購入時、もしくはマイアカウント画面でHOST IDの入力を行います。HOST IDとは、rizom-labが発行するPC固有のIDです。
https://www.rizom-lab.com/rizomuv-vs/
このHOST IDはソフトの立ち上げ時、ライセンスアクティベーション画面に表示されています。
image.png

なので、このあたりでRizomUVの実行ファイルをダウンロードしてインストールしましょう。

image.png
この長いコードをマイアカウント画面でコピペして貼り付けます。
image.png
成功するとライセンスファイルをダウンロードできるようになります。
image.png
そして、ライセンスアクティベーション画面のFOLDERから、ライセンスファイルが置かれているディレクトリパスを指定します。
image.png
アクティベーションに成功すると、RUNのボタンが押せるようになります。
run.png
ソフトが立ち上がりましたね。準備完了です。
soft.png

###RizomUVでUV展開
では、Houdiniの画面に戻ります。
一連のノードでUV展開をするチュートリアルは以下のサイトにあります。
https://www.sidefx.com/ja/tutorials/sidefx-labs-rizomuv-bridge/

動画と同じ豚だと、新鮮味がないので、今回はSideFXLabで突如として現れたtestgeometry_luizを利用します。
image.png
※unwrapする前にattributeを削除しておきます。
image.png
Rizomuv_unwrapを作成し、ディスプレイフラグを立てると、処理が開始されます。
※処理には多少の時間がかかります。よってAutoCookは切っておいたほうが無難です
image.png

※この時点でライセンスが上手くアクティベーションされていない場合は、アクティベーションがあるかどうかを聞かれます。

unwrapが完了したら、UVを確認してみます。
unwrapp.png

このUV展開に特化したソフトは、高いだけあって凄まじいクオリティです。
というか人のモデルって、ひとつのアイランドにできるんだ・・・

しかし、1つのアイランドでは、どこがシームなのかがわかりにくいです。
そこで、もうひとつのSideFXLabのノードが登場します。
それが「Lab UV Visualize」です。これはUV可視化専用のノードです。UV可視化のための機能をいくつも持ち細かい需要に答えてくれる、いわば「UV Quick Shade」の上位版です。
image.png

では、Visualize Seemsのチェックボックスをオンにしてみます。
image.png
すごい!んだけど、そこ切る!?って感じです。
修正が必要ですね。※そもそもアイランドがひとつというのが無理がある。

Rizomuv_unwrapノードに戻り、Advancedを開いてStretch Controlを有効にしたり、Bruchを有効にしたり、様々なパラメータで試行錯誤を行います。
image.png

良い感じにシームが切れたとしても、アイランド内のUVの偏りが生じることがあります。
そういった場合は、Rizomuv_optimeseを行うことで、UVあたりの面積を均一化させることができます。Rizomuv_optimeseは必要に応じてiterationを上げて調整します。

※時間がないので、今後追記予定

#Bake Texture
UV展開できたので、UVテクスチャ用のべイクを出力します。
基本的にはカメラを置いて、ROPネットワークのbakeROPを作成して Cd 要素を出力していきます。

※後日追記予定

#UVに関する参考リンク:サイトや動画など
Houdini初級編:UV & Texture
Introduction to Houdini 16: Simple Procedural UVs
Setting Up a Character UV Map for a Character in Houdini 16
Houdini UDIMワークフロー
Houdini sidefx official Lesson8 UVs and Materials
Houdini sidefx official Lesson8 UVs and Materials

#Bone
SOPに慣れ切った体のユーザにとってHoudiniのボーンは、なかなか手を出し辛い分野かと思います。
しかし、Houdiniの群衆はバージョンを追うごとに良い感じになっているし、ちょっと気にはなるんだよね・・・。という方もいるのではないでしょうか。
時間が無い場合は、どうしても新しい分野を開拓するのは難しくなります。しかし、いつまでもmixamoに逃げてしまうと、進歩することができません。よって今回はボーンについて正面から学んでいきます。

##ボーンチェーン
まずは、Boneの種類について確認します。Tabでboneと入力するとBoneとBones、Bones fromCurveなどあります。
BoneとBonesの違いは、その名の通り1つだけ作るか、連続してボーンを作るかの違いです。
image.png
まずはビューポート上でbonesを呼び出してみてください。
続けてクリックすることで、ボーンチェーンにボーンが追加されます。
必要分のボーンを追加したらEnterを押すか、右クリック→Finish Drawingで確定させます。
ボーンチェーンって何?という方はネットワークビューを見てみてください。名前の通りボーンがチェーン状態になっています!このひとかたまりをボーンチェーンと呼びます。
image.png
各ボーンチェーンは、チェーンのルートボーンでできています。
image.png

ここで、初めてHoudiniでボーンを扱う人はギョッとなるかと思います。
Houdiniは、ジオメトリレベルではなく、オブジェクトレベルでボーンを扱います。
object.png

###補足
※ルートはただのnullノードです
※まず最初に1つのボーンだけ作成して、後からCtrl+左クリックでボーン分割も可能です。
※マウスホイールを回転させるとボーンのdivisionを変更できます。
bonedivision.gif

##ジョイントベースでなくボーンベース
そして、Houdiniのボーンを学ぼうとする際にさらに混乱するのが、ジョイントの概念です。
他の3Dソフトでは、スケルトンの焦点を「ジョイント」とする**「ジョイントベース」ですが、Houdiniは「ジョイントベース」ではなく「ボーンベース」**です。

・参考リンク
ボーン vs ジョイント
リグの役立つ情報とガイドライン

###比較して理解するボーンベース
MAYAや3ds Maxなど、ほとんどの3Dソフトのボーンは、ジョイントベースです。しかしHoudiniはジョイントを基本としません。※疑似的に作ることは可能ですが非常にコストがかかります
いやいや、ジョイント無しでどうやってスケルトンの設定するの???と、最初から初学者の精神を折りに来てくれます。

maxbone.png
例:3ds Maxのボーン

ジョイントベースは、ボーンの制御をボーン自身ではなくジョイントが制御します。対して、ボーンベースはボーン自身が制御情報を持ちます。
ジョイント(下図では右側のnullノード)が持つ情報は、各ボーンの支点となる座標です。そのジョイントからボーンは次の子ボーンへの方向にLookAtして、nullノード間の距離まで進みます。
距離の算出方法は、

vlength(vtorigin("../null0","../null1"))

です。

※この例は、あくまでHoudini上でジョイントベースを再現した場合です。各ソフトによってジョイントの処理は異なります。
bo-ne.png

対して、ボーンノードは各ボーンが保持する「方向」と「長さ」の情報をそのまま使います。親ノードを基準として、そのノードが持つ方向と長さの終端を子のボーンの開始点として利用します。なかなか、独特ですね。
というか独特過ぎるよ!!
最初はそう思いましたが、比較してみると、確かに左のほうがシンプルに見えます。そしてHoudiniの設計思想上、以下の点を重視した結果、ボーンベースの方がジョイントベースよりも都合がよいと判断されたのではないでしょうか。

以下引用 ボーン vs ジョイント

ジョイントベースシステムには柔軟性とジョイントを自由に動かせられるコントロールがあります。しかし、不適切なジョイントオリエンテーションは、しばしば予期しない問題を起こし、後処理が必要になってきます。
一方で、ボーンには方向があります。ボーンが向かっている方向が-Z軸で、高さがある方向がY軸平坦な横方向がX軸です。ボーン階層はボーンチェーン全体を解釈することができます。つまり、ボーンと一緒にリグを伸縮することが容易になります。

###補足 Invisible paramters
ジョイントベースの再現時の例ではLookAtにCHOPを使用しました。
image.png

が、一応CHOPを使わない方法もあります。
ひとつは、ほかのジョイントベースのソフトでFBX出力を行った後に、Houdiniにインポートすると、CHOPなしのジョイントベースが生成されます。
対して1からCHOPなしのジョイントベースを作るためには、少し工夫が必要です。なぜなら、普通に呼び出したboneノードは、「Look At」「Look At Up Vector」が無いからです。
※補足:Houdini16でのコンストレイントのCHOP移行により無くなりました。
naiyo.png
右上のギアマークからEdit Paramter Interfaceでパラメータを見ても、対象の項目がありません。
image.png

なぜ無いかというと、項目が隠れているからです。
Show invisible paramtersを押すと、普段隠れているパラメータが姿を現します。
image.png

隠れているパラメータをクリックします。ここからvisible状態に持っていくためにはinvisibleのチェックを外す必要があります。そして確定すると、隠れていたパラメータを出現させることができます。
image.png
宝探しかよ!?

invisible状態のパラメータは、SideFXが余分だと判断したり、下位互換のために残したパラメータ、推奨しないパラメータなどです。理由は様々ですが、積極的に使うか?というと、少々疑問の残るところです。
豆知識として覚えておく程度でよいかと思います。
そして、隠れたパラメータはInvisible parameterだけではありません。

##Pre-Transform
Pre-Transformもまた、隠れたパラメータです。直訳すると「事前のトランスフォーム」ですが、その名の通り、Transformをする前にTransformします。
同じ行為するのに、事前にする意味あるの!?そもそもどこで使われているの!!??

⇒ ボーンチェーンで使われています。
※注意:boneノードだけでなくほかのnullノード、transformなどにも利用されます。
もちろんボーンチェーン以外にも活用されています。

###boneとbonesの違い
まず、boneとbonesの2つを作成します。この2つのボーンの違いはどこにあるでしょうか?
image.png
まず単体のボーンと、ボーンチェーン(nullノード:ルート)の存在があります。
なぜルートが必要かというと、ボーンチェーンを構成するためには「方向」「長さ」のほかに「ノードツリーの従属関係:親から継承されたTranslateの座標」が必要だからです。

###従属関係
ノードツリーの従属関係がある子供の原点は親の座標に追従します。例えば親のTranslateの位置が(2,2,0)だった場合、子供の原点は親の(2,2,0)となります。
e.png
※子のKeep Position When Parentingオプションによって挙動は異なります

従属関係がある状態で子供のボーンが自身のTranslateを使用してしまうと、親と子の接点が途切れてしまいます。
bonetrans.gif
例:子のボーンがY方向にTranslateを使用してしまった場合

つまり、ボーンで従属関係のある状態でTranslateは絶対にしてはなりません。

※以上の理由から、しばしばTranslateの値はLockされています。
image.png

そして、トランスフォームが許されるのは、最上位の親。つまりルートのみです。
なるほど!ではルートのトランスフォームの値を見てみましょう。

image.png

あれ?

Translateが(0,0,0)なのに、なぜかビューポート上では(0.6,0,0)に点があるように見えます。
ノードのどのタブを見ても0.6という値はありません。

なぜかというと、この0.6という値はPre-Transformに格納されているからです。
このPre-transformを見る方法は2種類あります。まずはノードの中クリックを押して見る方法です。下図の中にPre-transformという項目がありますが、このトランスフォーム値を事前に適用した状態が(0,0,0)となります。
image.png

もう一つは、タブから「Extract Pre-Transform」を選択することで、現在のPre-TransformパラメータをTransformパラメータに移すことで値を見る方法です。この方法は、値を編集することもできます。
編集が完了し、もう一度Pre-transformに格納したい場合は、Clean Transformを選択して、Transformのtranslate、rotate値をすべて0、scaleを1にします。
pretra.png

従属関係やPre-transformについての詳しいステータスは、インディゾーンHoudini情報日本語ブログに記載されています。

ルートのnullノードと同じように、boneノードもPre-transformがRotateに仕込まれています。
bonepre.png

###0が欲しい!
なぜ、このような回りくどい方法をとってtransformの値を0にするかというと、後からアニメーションを付けやすくするためです。例えば、体のTポーズに戻したい場合、いちいち腕のrotateを90度など入れるとなると、初期化の手続きが面倒になります。基本の位置、基本の回転は原則として(0,0,0)にする必要があります。

##IK

他のDCCツールを使ったことのある経験者であれば、次に
「なるほど。じゃあIK(インバースキネマティクス)はどうするの?」と思うでしょう。

ボーンのキネマティクス選択は、boneノードを呼び出した後メニュー上に現れます。
image.png

HoudiniのBoneは、デフォルトではNo Kinematicsとなっているため、キネマティクスがありません。
image.png

では、2つめのInverse Kinematicsを選択して、先ほどと同じようにボーンを追加してみます。
すると、謎のnullノードとCHOPノードが現れます!!なんとHoudiniのキネマティクス設定はCHOPで処理されます!!
image.png

###チェーンゴールとCHOPノード
チェーンゴールは、ただのnullノードです。このチェーンゴールを移動させることで、ボーンはこのゴールに追従します。※例としてgoalのnullノードのTransformにsin($F*10)のアニメーションを付与しています。
IK1.gif

この追従を制御しているのがCHOPネットワーク内のInverseKinノードです。
image.png
InverseKinノード内で、Root Bone・End Bone・Goal Affectorを指定します。
これを1文で表すと、「Rootのbone5からEndのbone8までに対して、Goalの影響を与える」ということになります。
invv.png

Kinematicsの種類は7種類あります。
kinematics.png

それぞれの動きを見た方がイメージが湧きやすいかもしれません。

※後日追記

Kinematics ボーン形状 生成されるノード 説明
No Kinematics ソルバなし。元々のパラメータ値を使います。
Inverse Kinematics IKは・・・ 
IK With Twist Affector Twist Affector で・・・
Forward Kinematics FKは・・・ 
IK With Constraints コンストレイント付きIKでは、拘束パラメータによってボーンの角度制限が考慮されます(例:X角度範囲、Y角度範囲、Z角度範囲など)。そのほかは通常のIKと同じです。
FK With Constraints ボーンの角度制限が考慮されます。そのほかは通常のFKと同じです。
Follow Curve カーブにボーンが追従します。

##命名規則
ボーン作成時に、prefixの単語を指定することが可能です。
image.png
デフォルトは「chain」となっているので、今後作成されるボーンの名前は、chain_bone1、chain_bone2…となります。

ボーンを選択して、Alt+Wを押します。
image.png

※後日追記
・・・・

ここで時間切れとなってしまいました。
もうしばらくお待ちください・

54
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?