OpenGL: Gdzie powinienem umieszczać shadery?

17

Próbuję nauczyć się OpenGL ES 2.0 i zastanawiam się, jaka jest najczęstsza praktyka „zarządzania” modułami cieniującymi.
Zadaję to pytanie, ponieważ w przykładach, które znalazłem (takich jak ten zawarty w Demo API dostarczonym wraz z Androidem SDK), zwykle widzę wszystko w klasie GLRenderer i wolę rozdzielić rzeczy, aby móc, na przykład obiekt GLImage, którego mogę użyć ponownie, gdy chcę narysować teksturowany kwadrat (w tej chwili koncentruję się na 2D), tak jak w moim kodzie OpenGL ES 1.0. W prawie każdym znalezionym przykładzie shadery są po prostu zdefiniowane jako atrybuty klasy. Na przykład:

public class Square {

public final String vertexShader =
        "uniform mat4 uMVPMatrix;\n" +
        "attribute vec4 aPosition;\n" +
        "attribute vec4 aColor;\n" +
        "varying vec4 vColor;\n" +
        "void main() {\n" +
        "  gl_Position = uMVPMatrix * aPosition;\n" +
        "  vColor = aColor;\n" +
        "}\n";

public final String fragmentShader =
        "precision mediump float;\n" +
        "varying vec4 vColor;\n" +
        "void main() {\n" +
        "  gl_FragColor = vColor;\n" +
        "}\n";
// ...
}

Z góry przepraszam, jeśli niektóre z tych pytań są głupie, ale nigdy wcześniej nie pracowałem z shaderami.

1) Czy powyższy kod jest powszechnym sposobem definiowania shaderów (właściwości publicznej klasy końcowej)?
2) Czy powinienem mieć osobną klasę Shader?
3) Jeśli moduły cieniujące są zdefiniowane poza klasą, która ich używa, skąd mam znać nazwy ich atrybutów (np. „AColor” w poniższym fragmencie kodu), aby móc je powiązać?

colorHandle = GLES20.glGetAttribLocation(program, "aColor");
miviclin
źródło

Odpowiedzi:

16

Zawsze nie lubiłem tego sposobu definiowania shaderów (w postaci ciągu). Wolę zrobić mój w pliku tekstowym i czytać go podczas ładowania. Zdefiniowanie go w ciągu jest denerwujące podczas debugowania i wydaje mi się po prostu bałaganiarskie. O wiele łatwiej jest napisać go i zobaczyć, jak jest sformatowany tak, jak powinien, zamiast w ciągu.

Mam również osobną klasę, która ma wspólną funkcję modułu cieniującego, taką jak czytanie w modułach cieniujących i drukowanie informacji o logowaniu do debugowania modułu cieniującego.

Gdziekolwiek shadery są zdefiniowane, możesz uzyskać dostęp do nazw tak, jak robisz to w przykładzie. Używanie literału ciągu jest dopuszczalne dla programów cieniujących, ponieważ jest mało prawdopodobne, aby te wartości uległy zmianie po ich zaimplementowaniu.

Jednak ostatecznie to zależy od ciebie. Sposób, w jaki obecnie to robisz, działa świetnie, jeśli nie powoduje żadnych problemów.

MichaelHouse
źródło
2
Posiadanie twoich shaderów w osobnych plikach oznacza również, że możesz używać swoich poręcznych edytorów shaderów i mieć pełne podświetlanie składni, a nawet podglądać je przed przetestowaniem w programie, jeśli to obsługuje.
Raceimaztion,
6
Jedynym prawdziwym powodem, dla którego shadery są w łańcuchu, są szybkie testy i samouczki ... gdzie obsługa plików dodaje masę „niepotrzebnego kodu”.
Jari Komppa,
2
Możesz także zaimplementować hot-reload na plikach shaderów - umożliwiając debugowanie zmian bez ponownego uruchamiania gry. Produktywność ++
Liosan,
Podoba mi się pomysł, aby je odczytać z pliku i mieć osobną klasę Shader. Widzenie ich zawsze w Sznurku wprawiało mnie w zakłopotanie, ponieważ nie wiedziałem, czy w taki sposób są one zazwyczaj definiowane, czy tylko w celach edukacyjnych. Dzięki!
miviclin,
8

Zarządzanie modułami cieniującymi (a tym samym materiałowymi) jest dość trudnym problemem, na który natrafisz, gdy twój system graficzny staje się bardziej złożony i zauważasz, że ciężkie kodowanie każdego modułu cieniującego prowadziłoby do ogromnego powielania kodu. Oto kilka alternatywnych sposobów rozwiązania tego problemu:

  • Małe przykłady, w których jest tylko kilka shaderów, zwykle kodują je jako ciągi znaków, aby uniknąć obsługi plików, jak komentował Jari Komppa
  • Lepiej jest używać osobnych plików, w których można wyróżnić składnię i odpowiednie formatowanie. Możesz także zakodować prosty system debugowania, który śledzi zmiany w tych plikach i zastosować zmodyfikowany moduł cieniujący w locie, gdy gra jest uruchomiona.
  • Gdy liczba materiałów wzrasta, generator shaderów staje się niemal koniecznością. Zasadniczo definiujesz typowe fragmenty, takie jak „normalny fragment odwzorowujący moduł cieniujący fragmenty” i komponujesz kompletne moduły cieniujące, włączając pożądane komponenty.
    • Możesz użyć zakodowanych fragmentów łańcucha, ale robi się to naprawdę szybko, więc pliki są dobrym pomysłem.
    • Możesz mieć fragmenty kodu w osobnych plikach (lub w tym samym pliku) lub alternatywnie użyć ubershader / supershader, gdzie używasz preprocesora (lub jednolitych flag) do włączania / wyłączania rzeczy.

Jeśli chodzi o nazwy atrybutów i mundurów, wystarczy zastosować spójne nazewnictwo we wszystkich modułach cieniujących.

Tapio
źródło
Na pewno przeniosę moje shadery do osobnego pliku. Dzięki!
miviclin,