OpenGLで法線ベクトルをバーテックスシェーダーに渡すには

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 2,291

fennec

score 17

現在OpenGLを勉強中で、床井氏のサイトを参考に進めています。
そこで分からない部分がありまして、
http://marina.sys.wakayama-u.ac.jp/~tokoi/?date=20090914
このページでは、position(attribute変数)を頂点法線ベクトルとしてシェーダー内で扱っていますが、
positionではなく3Dモデルデータから取り出した頂点法線ベクトル・面法線ベクトルをシェーダー内で使うには
どのようにすればいいのでしょうか。
また、フラットシェーディングを行いたいのですが、その場合は面法線ベクトルを使用するのでしょうか。
ご教授ください。お願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

頂点単位で渡したいデータが複数ある場合は、必要な分だけattribute変数を足してやれば大丈夫です。
第13回 テクスチャ座標の生成まで読み進められますと、attributeを追加する丁度いい例が載っていましたので、ご参考になるかと思います。
例ではテクスチャ座標の追加ということで、追加した要素は2要素のvec2ですが、法線の場合でも要素数が3個になるだけで大差はありません。
肝になる部分は、

  • バーテックスシェーダーに法線用のattribute変数を追加する
  • シェーダーリンク時にglBindAttribLocationでシェーダー内の変数に一意な番号を付ける(または、シェーダーをリンクした後で、自動的に設定された番号をglGetAttribLocationで調べる)
  • その番号をglEnableVertexAttribArrayを使って使用可能にする(初期状態ではすべての番号は使用不能に設定されているので、明示的に使用可能にします)
  • glVertexAttribPointerで、その番号と対応する属性の要素数(floatやintなら1、vec2やivec2なら2...ここでは法線なので、vec3が適当でしょう。その場合は3です)、要素の成分のデータ型(vec3は3つのfloatからなるのでGL_FLOAT)、整数で与えられたデータを正規化するか(もし法線でなく、例えば色の情報を追加するとして、しかも与える側のデータが0〜255の整数...などという状況の場合、これを設定するとシェーダー側では0.0〜1.0に変換された値として取り出されるので便利)、ストライド(ある頂点の頂点データの先頭から、次の頂点の頂点データの先頭まで何バイト読み進めればいいか...今まで位置しか属性を使用していなかった場合は0を設定していたかもしれませんが、0の場合は自動的に「1つの属性だけが密に詰まっている」と解釈してくれるため正しく指定されますが、今回のように複数の属性を交互に詰める場合、読み進める量を正しく渡してやる必要があります)、オフセット(この属性が頂点データの先頭から何バイト先にあるか...今回の場合、法線データの前には位置データが入っていますので、float3つ分で4*3=12バイトのずれがあります)を指定する

といった事項でしょうか。これでシェーダーに位置に加えて法線も渡せるようになるでしょう。
頂点法線の場合は特に問題ないでしょうが、面法線の場合は、書き出されたデータ形式によっては、頂点の数と法線の数が一対一対応していないかも知れません。OpenGLは頂点の各属性が頂点の数と一対一対応していることを期待しているので、その場合は詰めるデータを加工して一対一対応させた上でVBOに送らなければならないかもしれません。
面法線を送り込むことができれば、あとは頂点法線の場合と同様に光源と法線で内積計算して色を決めれば、フラットシェーディングらしい描画結果が得られるでしょう。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/08 23:09 編集

    いつも丁寧な回答ありがとうございます。返信遅くなって申し訳ないです。
    第11回で止まってました。第13回に例が載っているとは。。
    無事、頂点法線ベクトルはシェーダに渡せたのですが、面法線ベクトルはglDrawElementsを使って描いていると複数のポリゴンで頂点が被る場合があるので、頂点属性として上手く渡せません。glDrawArraysを使うしかないのでしょうか。
    また、面法線ベクトルの場合も、シェーダ内で頂点法線ベクトルと同様なライティング処理を行えば良いのでしょうか。フラットシェーディングについて調べたところ、in out変数の前にflatを付ければ出来ると知ったのですが、これは面法線ベクトルを使って行った場合と結果は同様なのでしょうか。
    今一バーテックスシェーダーの役割というか、シェーダ内でやっていることと、処理の流れが掴めません。
    質問ばかりで申し訳ないですが、よろしくお願いします。

    キャンセル

  • 2017/06/09 04:51 編集

    そこなんですよね...一つの頂点を複数の面が共有していると、位置は同じなのに法線が複数必要なため法線の数だけ頂点を重複させるよう加工しないとならなかったりします。flatを使う方法で見た目的に問題なさそうなら、そちらの方が手軽でしょう。

    バーテックスシェーダは頂点の数だけ(10個の三角形を描画する場合30回)実行され、主な作業は元のデータを画面上の意図した位置へ配置することになります(モデル・ビュー・プロジェクション変換などと呼ばれるような作業をここで行うことになるでしょう)。
    フラグメントシェーダはフラグメントの数だけ(ある三角形に注目すると、その三角形が最終的に画面内を占めるピクセルの数だけ)実行されます。主な作業は最終的なピクセルの色の決定になるでしょう。この時フラグメントシェーダーに送られるvarying変数(in変数)の値は、通常は三角形の各頂点についてバーテックスシェーダーで出力したvarying変数(out変数)が丁度よくブレンドされた値になります。flat指定すると、その変数についてはこの補間処理が行われなくなります。
    WebGLの記事ですが、例えばhttps://sbfl.net/blog/2016/09/04/webgl2-tutorial-basics/などに描画の流れの図がありましたので、ご参考になるかと思います。

    以前ご提示いただいた情報によるとGLSL 1.30が使用可能なようですので、シェーダーを1.30の書式に書き換えればflatを使えるようになるかと思います。法線用のバーテックス側out変数・フラグメント側in変数をflatにすると、フラグメントシェーダ側では補間なしの値が得られるので、フラットシェーディングらしい見た目にはなるかと思いますが、この補間なしの値というのは三角形の最初の頂点、または最後の頂点(glProvokingVertexで切り替えることができます)のものとなるのでご注意ください(もし、厳密に意図通りの描画にするには頂点の法線データ部分、三角形描画のインデックス指定順を工夫しないとならないかもしれません)。描画結果は、旧OpenGLでglShadeModel(GL_FLAT)を使った時のような見た目になると思います。

    その他、別のアプローチとして、フラグメントシェーダでdFdxとdFdyをpositionに対して使用すると、画面水平方向と垂直方向のpositionの変化率...つまり面の傾きを得ることができます。この2つのベクトルの外積が面法線の向きとなりますので、フラグメントシェーダ中でこの面法線と光源の内積を取ってピクセルを塗ればフラットな見た目にできます。
    同じくWebGLの記事で申し訳ないですが、https://wgld.org/d/webgl/w087.htmlにこの手法が紹介されていました。なお、サイト中でOES_standard_derivativesなどに触れられておりますが、通常のOpenGLには微分関数が標準搭載されていますので、特に気にされる必要はないかと思います。

    キャンセル

  • 2017/06/09 22:23 編集

    返信ありがとうございます。
    一つ一つ丁寧に教えてくださって本当に助かっています。シェーダーについてはほぼ理解できました。
    頂点が被る問題についても、とりあえずglDrawElementsのままでいくことにしました。やはり法線と対になるよう頂点配列を揃えないといけないのですね。なにかもっとうまい方法がありそうな気がしますが、一通りの操作が出来るようになってから色々試していきたいと思います。
    フラットシェーディングはコメントの最後の方で教えて下さったdFdx・dFdyを使う方法で実装出来ました。こちらで行ったほうが綺麗に陰影をつけることができました。flatを使う方法だとローポリの場合、面法線と頂点法線との差が大きいからか陰影が不自然になりました。
    ある程度レンダリング出来るようになったので、次はテクスチャの貼り方や座標変換を学んでいこうと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.21%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る