HLSLでトゥーンレンダリングをしたかったお話
はじめに
この記事はNCC Advent Calendar 2018の参加記事です。
前日はこんなことをやっていました。 MMDのモデルデータをざっくり読み込む この流れで、今日はトゥーンレンダリングに挑戦してみようと思って作業を始めたのですが、MRT (Multiple render targets) がうまく実装できなくて頓挫してしまったので、その所感のようなものをつらつらと書いていきたいと思います。 少し、何も気にせず文章を書いてみたいという欲求もあったので、好き勝手に書くという意味で今日の分ははてなブログの方に書きました。(せっかくちょっと整えましたしね)
前回のあらすじ
DirectX11のレンダリングパイプラインを組んで、PMXファイルを読み込んで、頂点とテクスチャだけでレンダリングしました。
そこそこ絶望的な所要時間でしたが、なんとか表示できるまでに至ったので、まあここまでは及第点でした。
最初にしていた遊び
法線を定数バッファで渡して表示したり、
法線とディレクショナルライトベクトルの内積を正規化して表示してみたり、(私はこの絵の雰囲気が一番好きです。心が高揚します。)
法線とディレクショナルライトベクトルの内積でシェーディングしてみたり、
上の絵にスペキュラーの値を適当に使って明るくしてみたり、
こういう遊びはかなり楽しくて好きです。パラメータを弄って1分くらい表示結果を見てニマニマしてました。
トゥーンレンダリングをしていく
それから間もなくしてトゥーンレンダリングの実装に入りました。 トゥーンシェーダーをつくる このサイトを参考にして、ハイライトと1号影を実装したまでは良かったのですが、境界線を描くのが思ったより難しく、頓挫の原因となってしまいました。
MRT
エッジを検出するには、ラプラシアンカーネルを使ったフィルタリングをすることで、周辺ピクセルと現在のピクセルを比較する必要があるのですが、エッジを元の絵に重ねて描画するためにポストエフェクトとして実装する必要がありました。
ラプラシアンカーネルに関してざっくり説明すると、中心ピクセルのウェイト(乗算値)を8.0
、周辺の8ピクセルのウェイトを-1.0
とすることで、周辺ピクセルが同一なら演算結果は0.0
に、異なれば異なるほど、演算結果の絶対値が大きくなるというものです。
これ自体はかなり単純な処理なので良かったのですが、前述の通りポストエフェクトとして実装する関係で、「ライティング済みのシェーダリソース」と「テクスチャを貼り付けただけのシェーダーリソース」の2つの、レンダーターゲット兼任のシェーダーリソースが必要でした。それを実現するために、MRTを実装しようとしたのですが、両方のシェーダーリソースが同一の値になってしまい、うまく動作させることができませんでした。
そんなこんなで、苦肉の策として、視線ベクトルと法線ベクトルの内積から「視線に対してのエッジ」を検出する方法を実装してみました。
はい、かなりひどいです。やはり、法線だけではどうにもなりません。その上、この方法では、四角形などの角ばった形状のものは悲惨な描画結果となってしまいます。 本当はこんな感じでエッジを実装するみたいです。とても綺麗に描画されていて驚嘆します。
おわりに
今回は割と大失敗で、とっても辛いお気持ちです。 しかし、明るめに考えれば、2Dの板を画面上に表示するのが結構簡単にできることだというのが今更ながらにわかったので、それはそこそこの収穫だったとお茶を濁すこともできるかなと思います。
明日のアドベントカレンダーはこの2日間で苦闘を強いられたDirectX11のレンダリングパイプラインについてつらつらしたいと思います。