Jak praktycznie wysyłać shadery GLSL z oprogramowaniem C++

Podczas inicjalizacji OpenGL program powinien zrobić coś takiego:

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

Uzyskanie kodu źródłowego może być tak proste, jak umieszczenie go w łańcuchu jak: (Przykład zaczerpnięty z SuperBible, VI edycja)

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

Problem polega na tym, że trudno jest edytować, debugować i utrzymywać shadery GLSL bezpośrednio w łańcuchu znaków. Tak więc uzyskanie kodu źródłowego w łańcuchu znaków z pliku jest łatwiejsze do opracowania:

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

Problem polega teraz na tym, jak wysłać shadery z Twoim programem? Rzeczywiście, wysyłka kodu źródłowego z aplikacją może być problemem. OpenGL obsługuje "wstępnie skompilowane shadery binarne", ale Open Wiki stwierdza, że:

Formaty binarne programu nie są przeznaczone do przesłane. Nie jest rozsądne oczekiwać od różnych dostawców sprzętu aby zaakceptować te same formaty binarne. Nie jest rozsądne oczekiwać inny sprzęt od tego samego dostawcy, aby zaakceptować ten sam binarny formaty. [...]

Jak praktycznie wysłać shadery GLSL z oprogramowaniem C++?

Author: NathanOliver, 2013-12-07

11 answers

Jest po prostu "przechowuj je bezpośrednio w pliku wykonywalnym" lub "przechowuj je w (a) osobnych plikach", bez niczego pomiędzy. Jeśli potrzebujesz samodzielnego pliku wykonywalnego, umieszczenie go w pliku binarnym jest dobrym pomysłem. Zauważ, że możesz dodać je jako zasoby lub dostosować system kompilacji, aby osadzać ciągi shaderów z oddzielnych plików programistycznych w plikach źródłowych, aby ułatwić rozwój (z możliwością bezpośredniego ładowania oddzielnych plików w rozwoju buduje).

Dlaczego uważasz, że wysyłanie źródeł shaderów byłoby problemem? Po prostu nie ma innej drogi w GL. Wstępnie skompilowane pliki binarne są przydatne tylko do buforowania wyników kompilacji na docelowej maszynie. Dzięki szybkiemu rozwojowi technologii GPU i zmieniającym się architekturom GPU oraz różnym dostawcom z całkowicie niekompatybilnymi Isa, wstępnie skompilowane binaria shaderów nie mają żadnego sensu.

Zauważ, że umieszczenie źródeł shadera w pliku wykonywalnym nie " chroni" nawet jeśli je zaszyfrujesz. Użytkownik może nadal podłączyć się do biblioteki GL i przechwycić źródła, które podasz do GL. A debuggery GL robią dokładnie to.

Aktualizacja 2016

[[2]}na SIGGRAPH 2016, OpenGL Architecture Review Board wydała GL_ARB_gl_spirv przedłużenie. Dzięki temu GL będzie mógł używać binarnego języka pośredniego SPIRV . Ma to pewne potencjalne korzyści:
  1. shadery mogą być wstępnie skompilowane" offline (ostateczna Kompilacja dla docelowego GPU nadal odbywa się przez sterownik później). Nie musisz wysyłać kodu źródłowego shadera, a jedynie binarną reprezentację pośrednią.
  2. istnieje jedna standardowa nakładka kompilatora (glslang ), która parsuje, więc różnice między parserami różnych implementacji mogą zostać wyeliminowane.
  3. można dodać więcej języków shader, bez konieczności zmiany implementacji GL.
  4. nieco zwiększa możliwość przenoszenia na vulkan.

Dzięki temu schematowi GL staje się bardziej podobny do D3D i Vulkana pod tym względem. Nie zmienia to jednak szerszego obrazu. Kod bajtowy SPIRV nadal może być przechwytywany, demontowany i dekompilowany. To sprawia, że inżynieria odwrotna jest trochę trudniejsza, ale nie o wiele. W przypadku shaderów zazwyczaj nie można sobie pozwolić na rozbudowane środki zaciemniania, ponieważ znacznie zmniejsza to wydajność - co jest sprzeczne z tym, do czego służą shadery.

Należy również pamiętać, że to rozszerzenie nie jest obecnie powszechnie dostępne (jesień 2016). A Apple przestało wspierać GL po 4.1, więc to rozszerzenie prawdopodobnie nigdy nie trafi do OSX.

MAŁA AKTUALIZACJA 2017

GL_ARB_gl_spirv jest teraz oficjalną podstawową funkcją OpenGL 4.6 , więc możemy spodziewać się rosnącego wskaźnika adopcji dla tej funkcji, ale to nie zmienia szerszego obrazu o wiele.

 47
Author: derhass,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-14 22:55:46

W c++11 Możesz również użyć nowej funkcji surowych liter ciągów. Umieść ten kod źródłowy w osobnym pliku o nazwie shader.vs:

R"(
#version 420 core

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

A następnie zaimportuj go jako ciąg znaków w następujący sposób:

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

Zaletą jest to, że jest łatwy w utrzymaniu i debugowaniu, a w przypadku błędów z kompilatora OpenGL shader otrzymujesz poprawne numery linii. I nadal nie musisz wysyłać oddzielnych shaderów.

Jedyną wadą, jaką widzę, są dodane linie na górze i na dole pliku (R") i )") oraz składnia, która jest trochę dziwna przy wprowadzaniu ciągu znaków do kodu C++.

 54
Author: Jan Rüegg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-16 14:27:06

OpenGL obsługuje wstępnie skompilowane binaria, ale nie przenośnie. W przeciwieństwie do HLSL, który jest kompilowany do standardowego formatu bajtowego przez kompilator Microsoftu i Później przetłumaczone w natywnej instrukcji GPU ustawionej przez sterownik, OpenGL nie ma takiego formatu. Nie można używać wstępnie skompilowanych binariów do niczego więcej niż buforowania skompilowanych shaderów GLSL na jednej maszynie, aby przyspieszyć czas ładowania, a nawet wtedy nie ma gwarancji, że skompilowany plik binarny będzie działał, jeśli Wersja Sterownika zmiany... znacznie mniej zmienia się rzeczywisty GPU na maszynie.

Zawsze możesz zaciemnić swoje shadery, jeśli jesteśnaprawdę paranoikiem. Chodzi o to, że jeśli nie robisz czegoś naprawdę jedynego w swoim rodzaju, nikt nie będzie się przejmował Twoimi shaderami i mówię to szczerze. Ta branża rozwija się na otwartości, wszyscy najwięksi gracze w branży regularnie omawiają najnowsze i najciekawsze techniki na konferencjach takich jak GDC, SIGGRAPH itp. W rzeczywistości shadery są tak implementacja-specyficzne, że często nie ma wiele można zrobić z inżynierii odwrotnej je, że nie można zrobić tylko słuchając jednej z wymienionych konferencji.

Jeśli twoim problemem są ludzie modyfikujący Twoje oprogramowanie, sugerowałbym zaimplementowanie prostego testu hash lub sumy kontrolnej. Wiele gier już to zrobić, aby zapobiec oszukiwaniu, jak daleko chcesz wziąć to zależy od Ciebie. Ale najważniejsze jest to, że binarne shadery w OpenGL mają na celu skrócenie czasu kompilacji shaderów, a nie przenośnych re-Dystrybucja.

 22
Author: Andon M. Coleman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-07 20:50:15

Moją sugestią byłoby włączenie shader ' a do Twojego pliku binarnego jako części procesu budowania. Używam CMake w kodzie do skanowania folderu w poszukiwaniu plików źródłowych shaderów, a następnie generuję nagłówek z liczbą wszystkich dostępnych shaderów:

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

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

Podobnie, CMake tworzy plik CPP, który, biorąc pod uwagę zasób, zwraca ścieżkę do shadera.

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

Nie byłoby zbyt trudne (dużo tu ręcznego oszczędzania), aby skrypt CMake zmienił jego zachowanie, tak aby w wydaniu buduj zamiast podawać ścieżkę do pliku, podało źródło shadera, a w pliku cpp przechowywała zawartość samych shaderów (lub w przypadku systemu Windows lub Apple docelowego uczynić je częścią zasobów wykonywalnych / pakietu wykonywalnego).

Zaletą tego podejścia jest to, że znacznie łatwiej jest modyfikować shadery w locie podczas debugowania, jeśli nie są one wypalone w pliku wykonywalnym. W rzeczywistości mój program GLSL pobierający kod faktycznie wygląda na czas kompilacji shader kontra zmodyfikowane znaczniki czasu plików źródłowych i przeładuje shader, jeśli pliki uległy zmianie od czasu ostatniego skompilowania (jest to wciąż w powijakach, ponieważ oznacza to utratę mundurów, które były wcześniej związane z shader, ale pracuję nad tym).

Jest to naprawdę mniej problem shader niż ogólny problem 'non-c++ resources'. Ten sam problem istnieje ze wszystkim, co chcesz załadować i przetworzyć... obrazy dla tekstur, plików dźwiękowych, poziomów, co masz.

 13
Author: Jherico,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-08 22:50:15

Jako alternatywę trzymania shaderów GLSL bezpośrednio w łańcuchu, sugerowałbym rozważenie tej biblioteki, którą rozwijam: ShaderBoiler (Apache-2.0).

Jest w wersji alpha i ma pewne ograniczenia, które mogą ograniczać korzystanie z niego.

Główną koncepcją jest pisanie w C++ konstrukcji podobnych do kodu GLSL, które konstruowałyby Wykres obliczeniowy, z którego generowany jest końcowy kod GLSL.

Na przykład, rozważmy następujące C++ kod

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

Wyjście do konsoli będzie następujące:

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

Utworzony ciąg znaków z kodem GLSL może być używany z API OpenGL do tworzenia shaderów.

 7
Author: Podgorskiy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-30 03:22:32

Problem polega na tym, że trudno jest edytować, debugować i utrzymywać GLSL shadery bezpośrednio w łańcuchu.

To dziwne, że to zdanie zostało całkowicie zignorowane przez wszystkie "odpowiedzi" do tej pory, podczas gdy powracający motyw tych odpowiedzi brzmiał :" nie możesz rozwiązać problemu, po prostu sobie z nim poradzić."

Odpowiedź na ułatwienie ich edycji, podczas ładowania bezpośrednio z łańcucha znaków, jest prosta. Rozważmy następujący ciąg znaków:

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

Wszystkie pozostałe komentarze są poprawne. Rzeczywiście, jak mówią, najlepszym dostępnym zabezpieczeniem jest ciemność, ponieważ GL można przechwycić. Ale aby uczciwi ludzie byli uczciwi, i aby umieścić jakiś blok na drodze do przypadkowego uszkodzenia programu, można zrobić jak wyżej w C++, i nadal łatwo utrzymać swój kod.

Oczywiście, jeśli chciałeś chronić najbardziej rewolucyjny shader świata przed kradzieżą, ciemność mogłaby być wzięta do raczej skutecznych skrajności. Ale to kolejne pytanie do innego wątku.

 5
Author: Thomas Poole,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-25 20:02:57

Możesz także połączyć wiele źródeł shaderów w jeden plik (lub łańcuch znaków) używając dyrektyw preprocesora, jeśli nie chcesz ich oddzielać. Pozwala to również uniknąć powtórzeń (np. wspólnych deklaracji) – nieużywane zmienne są optymalizowane przez kompilator przez większość czasu.

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

 3
Author: UXkQEZ7,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-09 02:26:27

Sugestia:

W twoim programie dodaj shader:

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

In shader_code.dane tam powinien być kod źródłowy shader jako lista o liczby szesnastkowe oddzielone przecinkami. Pliki te powinny być utworzone przed kompilacją przy użyciu kodu shader zapisanego normalnie w pliku. W Linuksie umieściłbym instrukcje w Makefile, aby uruchomić kod:

cat shader_code.glsl | xxd -i > shader_code.data
 2
Author: Thiago Harry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-05 13:46:44

Nie wiem, czy to zadziała, ale mógłbyś wstawić .vs plik do pliku wykonywalnego z binutils jak program jak g2bin, i możesz zadeklarować swoje programy shader jako zewnętrzne, a następnie uzyskać do nich dostęp jako normalne zasoby osadzone w pliku wykonywalnym. Zobacz qrc w Qt, lub możesz zobaczyć mój mały program do osadzania rzeczy w plikach wykonywalnych tutaj: https://github.com/heatblazer/binutil, która jest wywoływana jako polecenie pre-build do IDE.

 1
Author: Ilian Zapryanov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-26 10:59:24

Inną alternatywą dla przechowywania plików tekstowych glsl lub wstępnie skompilowanych plików glsl jest generator shader, który pobiera drzewo cieni jako wejście i wyjście glsl (lub hlsl, ...) kod, który jest następnie kompilowany i linkowany w czasie wykonywania... Dzięki takiemu podejściu można łatwiej dostosować się do wszelkich możliwości sprzętu gfx. Możesz również obsługiwać hlsl, jeśli masz dużo czasu, nie potrzebujesz języka cieniowania cg. Jeśli pomyślisz o glsl/hlsl wystarczająco głęboko, zobaczysz, że przekształcanie drzew cienia kod źródłowy znajdował się z tyłu umysłów projektantów języka.

 1
Author: user1095108,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-09 18:27:02

W C99 / C11 można to zrobić w 2 prostych krokach.

## Bash build script:

## STEP #1: Convert your [C99/C11] code to GLSL by quoting it:

    awk 'NF { print "\""$0"\\""n""\""}' GLSL_AS_C99.C11 > QUOTED.TXT

## STEP #2: Compile your project:

    gcc -x c -c MY_PROJECT_FILE.C11 -o object_file.o -std=c11 -m64
    gcc -o EXE.exe object_file.o
    rm object_file.o
    ./EXE.exe
    rm EXE.exe
Tak. To nie wszystko. Musisz napisać swój C99 w generycznym stylu, który będzie również skompilować jako GLSL. Na przykład:
#ifdef THIS_IS_BEING_COMPILED_AS_OPEN_GL_SHADER_CODE
    #define I32 int
#endif
#ifdef THIS_IS_BEING_COMPILED_AS_C99_CODE
    #define I32 uint32_t
#endif

Kod napisany w taki sposób w C99 można wyciąć i wkleić do shadera GLSL kod bez problemów. Pozwala również na jednostkowe testowanie kodu shadera GLSL. Aby załączyć kod, który został stringowany przez polecenie AWK, zrób coś takiego:

//:AAC2020_PAINT5D_DEFAULT_001:==============================://
const char* AAC2020_PAINT5D_DEFAULT_001=( //:////////////////://
//://////////////////////////////////////////////////////////://
"#version 330 core                           \n"//://////////://
"#define AAC2020_MACRO_THIS_IS_OPEN_GL (1)   \n"//://////////://
//://////////////////////////////////////////////////////////://
//|Everything Below Is Cut+Pasted From       |////://////////://
//|The C99 File: P5D_OGL._                   |////://////////://
//://////////////////////////////////////////////////////////://

    #include "../QUOTED.TXT"
                                                
); //:///////////////////////////////////////////////////////://
//:==============================:AAC2020_PAINT5D_DEFAULT_001://

Jeśli znasz kod C i skrypt bash. To powinno wystarczyć do wyjaśnij to. Ale jeśli potrzebujesz więcej wyjaśnień, nakręciłem 30-minutową demonstrację. i Wyjaśnienie wideo na youtube.

Https://www.youtube.com/watch?v=kQfSL4kv5k0&list=PLN4rUakF78aCdRxjMU8_JBGAKIrtt_7N5&index=115

Jeśli wolisz działający kod... Oto mój silnik gry, który używa tego system. Otwórz AAC2020.SH zbuduj skrypt, aby znaleźć polecenie " awk " i działać wstecz od tam.

Https://github.com/KanjiCoder/AAC2020

Inne pliki o szczególnym znaczeniu dla omawianego problemu to:

  1. P5D1OGL.FRA._
  2. P5D1OGL.FRA.Sznurek._
  3. P5D_001._
  4. POLIOGL.D. _
  5. POLIOGL.F._

Alternatywnie, jeśli dostroisz się do mojej transmisji na Twitchu i poprosisz o osobisty przegląd o tym, jak to robię, mogę dać ci demo na żywo i dalsze wyjaśnienia.

Www.twitch.com/kanjicoder

Możesz również wysłać do mnie e-mail na adres: [email protected] Obecnie mam 35 lat i jest rok 2021. Jeśli nie odpowiem to znaczy, że albo nie żyję, albo jestem zbyt sławny, by odpowiedzieć, albo jedno i drugie. Zostawię to jako ćwiczenie dla czytelnika, aby dowiedzieć się które to jeden z nich.

-John Mark

 -1
Author: twitchdotcom slash KANJICODER,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-29 13:23:58