如何使用 C++ 软件实际发布 GLSL 着色器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20443560/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:07:58  来源:igfitidea点击:

How to Practically Ship GLSL Shaders with your C++ Software

c++openglglsl

提问by Korchkidu

During OpenGL initialization, the program is supposed to do something like:

在 OpenGL 初始化期间,程序应该执行以下操作:

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

Getting source code could be as simple as putting it in a string like: (Example taken from SuperBible, 6th Edition)

获取源代码就像把它放在一个字符串中一样简单:(例子取自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"
};

The problem is that it is hard to edit, debug and maintain GLSL shaders directly in a string. So getting the source code in a string from a file is easier for development:

问题是很难直接在字符串中编辑、调试和维护 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();

The problem now is how to ship the shaders with your program? Indeed, shipping source code with your application may be a problem. OpenGL supports "pre-compiled binary shaders" but the Open Wikistates that:

现在的问题是如何将着色器与您的程序一起发送?确实,将源代码与您的应用程序一起发送可能是一个问题。OpenGL 支持“预编译的二进制着色器”,但Open Wiki指出:

Program binary formats are not intended to be transmitted. It is not reasonable to expect different hardware vendors to accept the same binary formats. It is not reasonable to expect different hardware from the same vendor to accept the same binary formats. [...]

不打算传输程序二进制格式。期望不同的硬件供应商接受相同的二进制格式是不合理的。期望来自同一供应商的不同硬件接受相同的二进制格式是不合理的。[...]

How to practically ship GLSL shaders with your C++ software?

如何实际使用 C++ 软件发布 GLSL 着色器?

采纳答案by derhass

There is just "store them directly in the executable" or "store them in (a) separate file(s)", with nothing in-between. If you want a self-contained executable, putting them into the binary is a good idea. Note that you can add them as resources or tweak your build system to embed the shader strings from separate development files into source files to make development easier (with the possible addition of being able to directly load the separate files in development builds).

只是“将它们直接存储在可执行文件中”或“将它们存储在(一个)单独的文件中”,中间没有任何内容。如果您想要一个独立的可执行文件,将它们放入二进制文件中是个好主意。请注意,您可以将它们添加为资源或调整您的构建系统,将单独开发文件中的着色器字符串嵌入到源文件中,以使开发更容易(可能还可以在开发构建中直接加载单独的文件)。

Why do you think shipping the shader sources would be a problem? There is simply no other way in the GL. The precompiled binaries are only useful for caching the compilation results on the target machine. With the fast advances of GPU technology, and changing GPU architectures, and different vendors with totally incompatible ISAs, precompiled shader binaries do not make sense at all.

为什么你认为运送着色器源会是一个问题?在 GL 中没有其他方法。预编译的二进制文件仅用于在目标机器上缓存编译结果。随着 GPU 技术的快速进步、不断变化的 GPU 架构以及不同供应商的 ISA 完全不兼容,预编译的着色器二进制文件根本没有意义。

Note that putting your shader sources in the executeable does not "protect" them, even if you encrypt them. A user can still hook into the GL library and intercept the sources you specify to the GL. And the GL debuggers out there do exactly that.

请注意,将着色器源放在可执行文件中并不会“保护”它们,即使您对它们进行了加密。用户仍然可以连接到 GL 库并拦截您指定给 GL 的源。那里的 GL 调试器正是这样做的。

UPDATE 2016

2016 年更新

At SIGGRAPH 2016, the OpenGL Architecture Review Board released the GL_ARB_gl_spirvextension. This will allow a GL inmplementation to use the SPIRVbinary intermediate language. This has some potential benefits:

在 SIGGRAPH 2016 上,OpenGL 架构委员会发布了该GL_ARB_gl_spirv扩展。这将允许 GL 实现使用SPIRV二进制中间语言。这有一些潜在的好处:

  1. Shaders can be pre-"compiled" offline (the final compilation for the target GPU still happens by the driver later). You don't have to ship the shader source code, but only the binary intermediate representation.
  2. There is one standard compiler frontend (glslang) which does the parsing, so differences between the parsers of different implementations can be eliminated.
  3. More shader languages can be added, without the need to change the GL implementations.
  4. It somewhat increases portability to vulkan.
  1. 着色器可以离线预“编译”(目标 GPU 的最终编译仍由驱动程序稍后进行)。您不必提供着色器源代码,而只需提供二进制中间表示。
  2. 有一个标准编译器前端 ( glslang) 进行解析,因此可以消除不同实现的解析器之间的差异。
  3. 可以添加更多着色器语言,而无需更改 GL 实现。
  4. 它在某种程度上增加了对 vulkan 的可移植性。

With that scheme, GL is becoming more similar to D3D and Vulkan in that regard. However, it doesn't change the greater picture. The SPIRV bytecode can still be intercepted, disassembled and decompiled. It does make reverse-engineering a little bit harder, but not by much actually. In a shader, you usually can't afford extensive obfuscuation measures, since that dramatically reduces performance - which is contrary to what the shaders are for.

通过该方案,GL 在这方面变得与 D3D 和 Vulkan 更加相似。然而,它并没有改变更大的图景。SPIRV 字节码仍然可以被拦截、反汇编和反编译。它确实使逆向工程变得有点困难,但实际上并没有太多。在着色器中,您通常无法承受大量的混淆措施,因为这会显着降低性能 - 这与着色器的用途相反。

Also keep in mind that this extension is not widely available right now (autumn 2016). And Apple has stopped supporting GL after 4.1, so this extension will probably never come to OSX.

另请记住,此扩展程序目前尚未广泛使用(2016 年秋季)。而且苹果在 4.1 之后就停止支持 GL,所以这个扩展可能永远不会出现在 OSX 上。

MINOR UPDATE 2017

2017 年小更新

GL_ARB_gl_spirvis now official core feature of OpenGL 4.6, so that we can expect growing adoption rate for this feature, but it doesn't change the bigger picture by much.

GL_ARB_gl_spirv现在是OpenGL 4.6 的官方核心功能,因此我们可以预期此功能的采用率会不断提高,但它不会对大局产生太大影响。

回答by Jan Rüegg

With c++11, you can also use the new feature of raw string literals. Put this source code in a separate file named shader.vs:

使用 c++11,您还可以使用原始字符串文字的新功能。将此源代码放在名为 的单独文件中shader.vs

R"(
#version 420 core

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

and then import it as a string like this:

然后将其作为字符串导入,如下所示:

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

The advantage is that its easy to maintain and debug, and you get correct line numbers in case of errors from the OpenGL shader compiler. And you still don't need to ship separate shaders.

优点是它易于维护和调试,并且在 OpenGL 着色器编译器出现错误的情况下您可以获得正确的行号。而且您仍然不需要运送单独的着色器。

The only disadvantage I can see is the added lines on the top and bottom of the file (R")and )") and the syntax that is a little bit strange for getting the string into C++ code.

我能看到的唯一缺点是文件顶部和底部添加的行 ( R")and )") 以及将字符串转换为 C++ 代码的语法有点奇怪。

回答by Andon M. Coleman

OpenGL supports pre-compiled binaries, but not portably. Unlike HLSL, which is compiled into a standard bytcode format by Microsoft's compiler and later translatedinto a GPU's native instruction set by the driver, OpenGL has no such format. You cannot use pre-compiled binaries for anything more than caching compiled GLSL shaders on a single machine to speed-up load time, and even then there is no guarantee that the compiled binary will work if the driver version changes... much less the actual GPU on the machine changes.

OpenGL 支持预编译的二进制文件,但不可移植。HLSL 由微软的编译器编译成标准的字节码格式,然后由驱动程序翻译成 GPU 的本机指令集,而 OpenGL 没有这种格式。除了在单台机器上缓存编译的 GLSL 着色器以加快加载时间之外,您不能使用预编译的二进制文件,即使这样也不能保证编译的二进制文件在驱动程序版本更改时可以工作……更不用说机器上的实际 GPU 发生了变化。

You can always obfuscate your shaders if you are reallyparanoid. The thing is, unless you are doing something truly one-of-a-kind nobody is going to care about your shaders and I mean that genuinely. This industry thrives on openness, all the big players in the industry regularly discuss the newest and most interesting techniques at conferences such as GDC, SIGGRAPH, etc. In fact, shaders are so implementation-specific that often there is not much you can do from reverse engineering them that you could not do just by listening to one of said conferences.

如果你真的很偏执,你总是可以混淆你的着色器。问题是,除非你在做一些真正独一无二的事情,否则没人会关心你的着色器,我的意思是真的。这个行业在开放性上蓬勃发展,行业中的所有大玩家都定期在 GDC、SIGGRAPH 等会议上讨论最新、最有趣的技术。 事实上,着色器是如此特定于实现,通常你可以做的并不多对它们进行逆向工程,您无法仅通过听上述会议之一来完成。

If your concern is people modifying your software, then I would suggest you implement a simple hash or checksum test. Many games already do this to prevent cheating, how far you want to take it is up to you. But the bottom line is that binary shaders in OpenGL are meant to reduce shader compile time, not for portable re-distribution.

如果您担心有人修改您的软件,那么我建议您实施简单的哈希或校验和测试。许多游戏已经这样做以防止作弊,您想走多远取决于您。但最重要的是,OpenGL 中的二进制着色器旨在减少着色器编译时间,而不是用于可移植的重新分发。

回答by Jherico

My suggestion would be to make the incorporation of shader into your binary a part of your build process. I use CMake in my code to scan a folder for shader source files and then generate a header with an enum of all the available shaders:

我的建议是将着色器合并到二进制文件中作为构建过程的一部分。我在我的代码中使用 CMake 来扫描一个文件夹中的着色器源文件,然后生成一个包含所有可用着色器枚举的标头:

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

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

Similarly, CMake creates a CPP file which, given a resource, returns the file path to the 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];
}

It wouldn't be too hard (much handwaving here) to make the CMake script alter it's behavior so that in a release build instead of providing the file path it provided the source of the shader, and in the cpp file stored the contents of the shaders themselves (or in the case of a Windows or Apple target make them part of the executable resources / executable bundle).

让 CMake 脚本改变它的行为并不会太难(这里很麻烦),以便在发布版本中而不是提供它提供着色器源的文件路径,并且在 cpp 文件中存储了着色器本身(或者在 Windows 或 Apple 目标的情况下,使它们成为可执行资源/可执行包的一部分)。

The advantage of this approach is that it's much easier to modify the shaders on the fly during debugging if they're not baked into the executable. In fact my GLSL program fetching code actually looks at the compile time of the shader versus the modified timestamps of the source files and will reload the shader if the files have changed since the last time it was compiled (this is still in its infancy, since it means you lose any uniforms that were previously bound to the shader, but I'm working on that).

这种方法的优点是,如果着色器未烘焙到可执行文件中,则在调试期间动态修改着色器要容易得多。事实上,我的 GLSL 程序获取代码实际上查看着色器的编译时间与源文件的修改时间戳,如果文件自上次编译后发生更改,则将重新加载着色器(这仍处于起步阶段,因为这意味着您将丢失以前绑定到着色器的所有制服,但我正在努力)。

This is really less of a shader issue than a generic 'non-C++ resources' issue. The same problem exists with everything you might want to load and process... images for textures, sound files, levels, what have you.

与一般的“非 C++ 资源”问题相比,这实际上不是着色器问题。您可能想要加载和处理的所有内容都存在同样的问题……纹理、声音文件、关卡的图像,您拥有什么。

回答by Podgorskiy

As an alternative of keeping GLSL shaders directly in a string, I would suggest considering this library that I'm developing: ShaderBoiler(Apache-2.0).

作为将 GLSL 着色器直接保存在字符串中的替代方法,我建议考虑我正在开发的这个库:ShaderBoiler(Apache-2.0)。

It is in alpha version and has some limitations that may restrict usage of it.

它是 alpha 版本,有一些限制,可能会限制它的使用。

The main concept is to write in C++ constructs similar to GLSL code, that would construct a computation graph from which the final GLSL code is generated.

主要概念是用类似于 GLSL 代码的 C++ 构造编写,这将构造一个计算图,从中生成最终的 GLSL 代码。

For example, let's consider the following C++ code

例如,让我们考虑以下 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();
}

The output to the console will be:

控制台的输出将是:

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);
}

The created string with GLSL code can be used with OpenGL API to create shader.

使用 GLSL 代码创建的字符串可以与 OpenGL API 一起使用来创建着色器。

回答by Thomas Poole

The problem is that it is hard to edit, debug and maintain GLSL shaders directly in a string.

问题是很难直接在字符串中编辑、调试和维护 GLSL 着色器。

It's strange that this sentence has been totally ignored by all 'answers' so far, while the recurring theme of those answers has been, "You can't solve the problem; just deal with it."

奇怪的是,到目前为止,所有“答案”都完全忽略了这句话,而这些答案反复出现的主题是“您无法解决问题;只需处理它。”

The answer to making them easier to edit, while loading them directly from a string, is simple. Consider the following string literal:

使它们更易于编辑,同时直接从字符串加载它们的答案很简单。考虑以下字符串文字:

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

All other comments are correct as far as they go. Indeed, as they say, the best security available is obscurity, since GL can be intercepted. But to keep honest people honest, and to put some block in the way of accidental program damage, you can do as above in C++, and still easily maintain your code.

就它们而言,所有其他评论都是正确的。事实上,正如他们所说,可用的最佳安全性是默默无闻,因为 GL 可以被拦截。但是为了让诚实的人保持诚实,并防止意外损坏程序,您可以在 C++ 中执行上述操作,并且仍然可以轻松维护您的代码。

Of course if you DID want to protect the world's most revolutionary shader from theft, obscurity could be taken to rather effective extremes. But that's another question for another thread.

当然,如果您确实想保护世界上最具革命性的着色器免遭盗窃,那么默默无闻可能会达到相当有效的极端。但这是另一个线程的另一个问题。

回答by UXkQEZ7

You can also combine multiple shader sources into one file (or string) using preprocessor directives if you don't want to keep them separate. This also lets you avoid repetition (e.g. shared declarations) – unused variables are optimized by the compiler most of the time.

如果您不想将多个着色器源分开,您还可以使用预处理器指令将多个着色器源合并到一个文件(或字符串)中。这也可以让您避免重复(例如共享声明)——大多数时候编译器会优化未使用的变量。

See http://www.gamedev.net/topic/651404-shaders-glsl-in-one-file-is-it-practical/

http://www.gamedev.net/topic/651404-shaders-glsl-in-one-file-is-it-practical/

回答by Ilian Zapryanov

I don`t know if that will work, but you could embed the .vs file into your executable with binutils like program like g2bin, and you can declare your shader programs as externals then you access them as normal resources embedded in the executable. See qrc in Qt, or you can view my small program for embedding stuff in executables here: https://github.com/heatblazer/binutilwhich is invoked as pre-build command to the IDE.

我不知道这是否可行,但是您可以使用 binutils 像 g2bin 这样的程序将 .vs 文件嵌入到您的可执行文件中,并且您可以将着色器程序声明为外部程序,然后您可以将它们作为嵌入在可执行文件中的普通资源来访问。请参阅 Qt 中的 qrc,或者您可以在此处查看我的小程序,用于在可执行文件中嵌入内容:https: //github.com/heatblazer/binutil,它作为 IDE 的预构建命令调用。

回答by Thiago Harry

A suggestion:

一条建议:

In your program, put the shader in:

在您的程序中,将着色器放入:

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

In shader_code.data there should be the shader source code as a list o hex numbers separated by commas. These files should be created before compilation using your shader code written normally in a file. In Linux I would put instructions at Makefile to run the code:

在 shader_code.data 中应该有着色器源代码作为一个列表 o 用逗号分隔的十六进制数字。这些文件应该在编译之前使用正常编写在文件中的着色器代码创建。在 Linux 中,我会在 Makefile 中放置指令来运行代码:

cat shader_code.glsl | xxd -i > shader_code.data

回答by user1095108

Another alternative to storing glsl text files or precompiled glsl files is a shader generator, which takes a shade tree as input and outputs glsl (or hlsl, ...) code, that is then compiled and linked at runtime... Following this approach you can more easily adapt to whatever capabilities the gfx hardware has. You can also support hlsl, if you have lots of time, no need for the cg shading language. If you think about glsl/hlsl deeply enough you will see, that transforming shade trees into source code was at the back of the language designers minds.

存储 glsl 文本文件或预编译的 glsl 文件的另一种替代方法是着色器生成器,它将着色树作为输入并输出 glsl(或 hlsl,...)代码,然后在运行时编译和链接...遵循这种方法您可以更轻松地适应 gfx 硬件具有的任何功能。你也可以支持hlsl,如果你有很多时间,不需要cg着色语言。如果您对 glsl/hlsl 进行深入思考,您就会发现,将树荫树转换为源代码是语言设计者的想法。