web-dev-qa-db-ja.com

C ++ソフトウェアでGLSLシェーダーを実際に出荷する方法

OpenGLの初期化中、プログラムは次のようなことを行うことになっています。

<Get Shader Source Code>
<Create Shader>
<Attach Source Code To Shader>
<Compile Shader>

ソースコードの取得は、次のような文字列に入れるだけで簡単にできます: SuperBible、6th Edition の例

static const char * vs_source[] =
{
    "#version 420 core                             \n"
    "                                              \n"
    "void main(void)                               \n"
    "{                                             \n"
    "    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);   \n"
    "}                                             \n"
};

問題は、GLSLシェーダーを文字列で直接編集、デバッグ、および保守することが難しいことです。したがって、ファイルから文字列のソースコードを取得する方が開発が簡単です。

std::ifstream vertexShaderFile("vertex.glsl");
std::ostringstream vertexBuffer;
vertexBuffer << vertexShaderFile.rdbuf();
std::string vertexBufferStr = vertexBuffer.str();
// Warning: safe only until vertexBufferStr is destroyed or modified
const GLchar *vertexSource = vertexBufferStr.c_str();

問題は、プログラムでシェーダーをどのように出荷するかです。実際、アプリケーションと一緒にソースコードを出荷するのは問題かもしれません。 OpenGLは「プリコンパイル済みバイナリシェーダー」をサポートしていますが、 Open Wiki は次のように述べています。

プログラムのバイナリ形式は、送信されることを意図していません。異なるハードウェアベンダーが同じバイナリ形式を受け入れることを期待するのは合理的ではありません。同じベンダーの異なるハードウェアが同じバイナリ形式を受け入れることを期待するのは合理的ではありません。 [...]

C++ソフトウェアでGLSLシェーダーを実際に出荷する方法は?

46
Korchkidu

「実行可能ファイルに直接保存する」または「(a)別のファイルに保存する」だけで、間に何もありません。自己完結型の実行可能ファイルが必要な場合は、それらをバイナリに入れることをお勧めします。リソースとして追加するか、ビルドシステムを調整して、シェーダー文字列を個別の開発ファイルからソースファイルに埋め込み、開発を容易にすることができることに注意してください(開発ビルドで個別のファイルを直接ロードできる可能性があります)。

シェーダーソースの配送が問題になると思うのはなぜですか? GLには他に方法はありません。プリコンパイルされたバイナリは、コンパイル結果をターゲットマシンにキャッシュする場合にのみ役立ちます。 GPUテクノロジーの急速な進歩、GPUアーキテクチャの変更、および完全に互換性のないISAを持つさまざまなベンダーにより、プリコンパイルされたシェーダーバイナリはまったく意味がありません。

シェーダーソースを実行可能ファイルに配置しても、暗号化しても「保護」されないことに注意してください。ユーザーはGLライブラリにフックし、GLに指定したソースをインターセプトできます。また、そこにあるGLデバッガーはまさにそれを行います。

2016年更新

SIGGRAPH 2016で、OpenGLアーキテクチャレビュー委員会は GL_ARB_gl_spirv 拡張。これにより、GL実装が [〜#〜] spirv [〜#〜] バイナリ中間言語を使用できるようになります。これにはいくつかの潜在的な利点があります。

  1. シェーダーはオフラインで事前に「コンパイル」できます(ターゲットGPUの最終的なコンパイルは、後でドライバーによって行われます)。シェーダーのソースコードを出荷する必要はなく、バイナリ中間表現のみを出荷する必要があります。
  2. 解析を行う1つの標準コンパイラフロントエンド( glslang )があるため、異なる実装のパーサー間の違いを排除できます。
  3. GL実装を変更することなく、シェーダー言語を追加できます。
  4. Vulkanへの移植性がいくらか向上します。

そのスキームでは、GLはその点でD3DおよびVulkanにより類似しています。ただし、全体像は変わりません。SPIRVバイトコードは、インターセプト、逆アセンブル、逆コンパイルできます。シェーダーでは、パフォーマンスが劇的に低下するため、通常、広範囲の難読化対策を行う余裕はありません-これはシェーダーの目的とは反対です。

また、この拡張機能は現在広く利用可能ではないことに注意してください(2016年秋)。そしてAppleは4.1以降でGLのサポートを停止したため、この拡張機能はおそらくOSXには登場しません。

2017年マイナーアップデート

GL_ARB_gl_spirv は現在、 OpenGL 4.6 の公式のコア機能であるため、この機能の採用率の向上が期待できますが、全体像は大きく変わりません。

41
derhass

C++ 11では、生の文字列リテラルの新しい機能も使用できます。このソースコードをshader.vsという名前の別のファイルに配置します。

R"(
#version 420 core

void main(void)
{
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
)"

次に、次のような文字列としてインポートします。

const std::string vs_source =
#include "shader.vs"
;

利点は、メンテナンスとデバッグが簡単であり、OpenGLシェーダーコンパイラからエラーが発生した場合に正しい行番号を取得できることです。そして、あなたはまだ別々のシェーダーを出荷する必要はありません。

私が見ることができる唯一の欠点は、ファイルの上下に追加された行(R")および)")と、文字列をC++コードに入れるのに少し奇妙な構文です。

43
Jan Rüegg

OpenGLはプリコンパイル済みバイナリをサポートしていますが、移植性はありません。 HLSLとは異なり、Microsoftのコンパイラーによって標準のbytcode形式にコンパイルされ、後でtranslatedによってGPUのネイティブ命令セットにコンパイルされます。ドライバ、OpenGLにはそのような形式はありません。コンパイル済みのGLSLシェーダーを1台のマシンにキャッシュしてロード時間を短縮する以外の目的でプリコンパイル済みバイナリを使用することはできません。その場合でも、ドライバーのバージョンが変更されてもコンパイル済みバイナリが機能するという保証はありません...マシン上の実際のGPUが変更されます。

really paranoidであれば、シェーダーをいつでも難読化できます。本当にユニークなことをしているのでなければ、誰もあなたのシェーダーを気にかけないでしょう。この業界はオープンで繁栄しており、業界のすべての大企業はGDCやSIGGRAPHなどの会議で最新かつ最も興味深い技術について定期的に話し合っています。実際、シェーダーは実装固有であるため、多くの場合、できることはあまりありません。前述の会議のいずれかを聞くだけではできないことをリバースエンジニアリングします。

あなたの懸念があなたのソフトウェアを変更する人々であるなら、単純なハッシュまたはチェックサムテストを実装することをお勧めします。不正行為を防ぐために多くのゲームがすでにこれを行っています。どの程度までそれをしたいかはあなた次第です。しかし、結論としては、OpenGLのバイナリシェーダーは、ポータブルな再配布のためではなく、シェーダーのコンパイル時間を短縮するためのものです。

19

私の提案は、シェーダーをバイナリに組み込むことをビルドプロセスの一部にすることです。コードでCMakeを使用してフォルダーをスキャンし、シェーダーソースファイルを探してから、使用可能なすべてのシェーダーの列挙型を含むヘッダーを生成します。

#pragma once
enum ShaderResource {
    LIT_VS,
    LIT_FS,
    // ... 
    NO_SHADER
};

const std::string & getShaderPath(ShaderResource shader);

同様に、CMakeはCPPファイルを作成し、リソースが指定されると、ファイルパスをシェーダーに返します。

const string & getShaderPath(ShaderResource res) {
  static map<ShaderResource, string> fileMap;
  static bool init = true;
  if (init) {
   init = false;
   fileMap[LIT_VS] =
    "C:/Users/bdavis/Git/OculusRiftExamples/source/common/Lit.vs";
   // ...
  }
  return fileMap[res];
}

CMakeスクリプトがその動作を変更するのはそれほど難しくありません(ここではかなり手を振っています)。そのため、リリースビルドではファイルパスを提供する代わりにシェーダーのソースを提供し、cppファイルにはシェーダー自体(またはWindowsまたはAppleターゲットの場合、それらを実行可能リソース/実行可能バンドルの一部にします)。

このアプローチの利点は、シェーダーが実行可能ファイルに焼き付けられていない場合、デバッグ中にその場でシェーダーを簡単に変更できることです。実際、コードをフェッチするGLSLプログラムは、シェーダーのコンパイル時とソースファイルの変更されたタイムスタンプを実際に見て、最後にコンパイルされてからファイルが変更された場合、シェーダーをリロードします(これはまだ初期段階です。以前シェーダーにバインドされていた制服を失うことを意味しますが、私はそれに取り組んでいます)。

これは、一般的な「非C++リソース」の問題よりも、実際にはシェーダーの問題ではありません。同じ問題は、ロード、処理するすべてのものに存在します...テクスチャ、サウンドファイル、レベル、あなたが持っているものの画像。

11
Jherico

GLSLシェーダーを文字列に直接保持する代わりに、開発中のこのライブラリを検討することをお勧めします。 ShaderBoiler (Apache-2.0)。

これはアルファ版であり、使用を制限する可能性のあるいくつかの制限があります。

主な概念は、GLSLコードに似たC++コンストラクトを記述することです。これにより、最終的なGLSLコードの生成元となる計算グラフが構築されます。

たとえば、次のC++コードを考えてみましょう

#include <shaderboiler.h>
#include <iostream>

void main()
{
    using namespace sb;

    context ctx;
    vec3 AlbedoColor           = ctx.uniform<vec3>("AlbedoColor");
    vec3 AmbientLightColor     = ctx.uniform<vec3>("AmbientLightColor");
    vec3 DirectLightColor      = ctx.uniform<vec3>("DirectLightColor");
    vec3 LightPosition         = ctx.uniform<vec3>("LightPosition");

    vec3 normal   = ctx.in<vec3>("normal");
    vec3 position = ctx.in<vec3>("position");
    vec4& color   = ctx.out<vec4>("color");

    vec3 normalized_normal = normalize(normal);

    vec3 fragmentToLight = LightPosition - position;

    Float squaredDistance = dot(fragmentToLight, fragmentToLight);

    vec3 normalized_fragmentToLight = fragmentToLight / sqrt(squaredDistance);

    Float NdotL = dot(normal, normalized_fragmentToLight);

    vec3 DiffuseTerm = max(NdotL, 0.0) * DirectLightColor / squaredDistance;

    color = vec4(AlbedoColor * (AmbientLightColor + DiffuseTerm), 1.0);

    std::cout << ctx.genShader();
}

コンソールへの出力は次のようになります。

uniform vec3 AlbedoColor;
uniform vec3 AmbientLightColor;
uniform vec3 LightPosition;
uniform vec3 DirectLightColor;

in vec3 normal;
in vec3 position;

out vec4 color;

void main(void)
{
        vec3 sb_b = LightPosition - position;
        float sb_a = dot(sb_b, sb_b);
        color = vec4(AlbedoColor * (AmbientLightColor + max(dot(normal, sb_b / sqrt(sb_a)), 0.0000000) * DirectLightColor / sb_a), 1.000000);
}

GLSLコードで作成された文字列をOpenGL APIで使用して、シェーダーを作成できます。

6
Podgorskiy

問題は、GLSLシェーダーを文字列で直接編集、デバッグ、および保守することが難しいことです。

この文がこれまでのすべての「回答」で完全に無視されているのは奇妙ですが、それらの回答の繰り返しのテーマは「問題を解決することはできません。対処するだけです」。

それらを文字列から直接ロードしながら、それらを編集しやすくするための答えは簡単です。次の文字列リテラルを検討してください。

    const char* gonFrag1 = R"(#version 330
// Shader code goes here
// and newlines are fine, too!)";

他のコメントはすべて、正しい限り正しいです。確かに、彼らが言うように、GLは傍受される可能性があるため、利用可能な最良のセキュリティは不明瞭です。しかし、正直な人々を正直に保ち、偶発的なプログラムの損傷を防ぐために、上記のようにC++で実行しても、コードを簡単に維持できます。

もちろん、もしあなたがDID世界で最も革命的なシェーダーを盗難から保護したいなら、あいまいさはかなり効果的な極端に連れて行かれます。しかし、それは別のスレッドの別の質問です。

4
Thomas Poole

また、プリプロセッサディレクティブを使用して、別々にしたくない場合は、複数のシェーダーソースを1つのファイル(または文字列)に結合することもできます。これにより、繰り返し(例:共有宣言)を回避することもできます。ほとんどの場合、未使用の変数はコンパイラーによって最適化されます。

http://www.gamedev.net/topic/651404-shaders-glsl-in-one-file-is-it-practical/ を参照してください

2
UXkQEZ7

それが機能するかどうかはわかりませんが、g2binのようなプログラムのようなbinutilsを使用して.vsファイルを実行可能ファイルに埋め込むことができ、シェーダープログラムを外部として宣言し、実行可能ファイルに埋め込まれた通常のリソースとしてそれらにアクセスできます。 Qtのqrcを参照するか、実行可能ファイルにコンテンツを埋め込むための私の小さなプログラムを表示できます: https://github.com/heatblazer/binutil これはIDEのビルド前コマンドとして呼び出されます。

1
Ilian Zapryanov

提案:

プログラムで、シェーダーを次の場所に配置します。

const char shader_code = {
#include "shader_code.data"
, 0x00};

Shader_code.dataには、シェーダーのソースコードがコンマで区切られた16進数のリストとして含まれている必要があります。これらのファイルは、通常ファイルに記述されたシェーダーコードを使用してコンパイルする前に作成する必要があります。 Linuxでは、コードを実行するための指示をMakefileに入れます。

cat shader_code.glsl | xxd -i > shader_code.data
1
Thiago Harry

Glslテキストファイルまたはプリコンパイル済みglslファイルを保存する別の方法は、シェーダージェネレーターです。シェーダージェネレーターは、入力としてシェードツリーを取得し、glsl(またはhlsl、...)コードを出力します。 gfxハードウェアが持つあらゆる機能により簡単に適応できます。 hlslをサポートすることもできます。時間があれば、cgシェーディング言語は必要ありません。 glsl/hlslについて十分に深く考えると、シェードツリーをソースコードに変換することは、言語設計者の心の奥底にありました。

1
user1095108