Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

18.03.2020, 19:25

Aufbau einer 3D-Engine mit WebGPU

Hallo,

momentan schreibe ich meine Bachelorarbeit über das (grobe) Thema "3D-Rendering im Browser mit WebGPU und emscripten" und bin dabei eine "3D-Engine" zu erstellen.

Mithilfe der Tutorials von emscripten und Vulkan Tutorial, der API Spezifikation von WebGPU und Demo Implementationen, habe ich es geschafft ein rotierendes Viereck, wie im Vulkan Tutorial, anzuzeigen. (s. Anhang 1)
Der Code dazu: Github (master branch) (Alles wichtige eigentlich in der src/main.c)


Nun wollte ich das alles ein bisschen aufräumen und abstrahieren um eine "einfache" API zum Erstellen von 3D-Szenen zu haben. Dafür habe ich mich ein bisschen an der WebGPU-Implementation von sokol orientiert (soweit ich es verstanden habe).
Meine Idee war es die Daten in Meshes (Vertex- und Index-Daten), Materials (Rendering-Pipeline) und RenderObjects (Kombination eines Meshs, eines Materials und weitere Daten (z.B. Transform)) zu teilen.
Der Code dazu: Github (spider branch) (src/spider.h und src/example.c sind hier die wichtigen Dateien) (ist nicht lauffähig)
Nun habe ich aber Probleme dabei, die Ressourcen (z.B. Uniform Buffer) richtig aufzuteilen:
  • Ein Uniform Buffer pro RenderObject mit der dazugehörigen MVP-Matrix?
  • Ein Uniform Buffer pro Material, der für jedes RenderObject mit diesem Material aktualisiert wird?
  • Ein Uniform Buffer, der entsprechend immer aktualisiert wird?
  • Ich habe auch gelesen, dass man einen Uniform Buffer benutzen kann, der mehrere UBOs beinhaltet, und dann für jedes Objekt mit einem Offset auf die jeweiligen Daten zugreift.
Das ist jetzt nur eins der Probleme, die ich aktuell habe und nicht wirklich weiß wie ich sie lösen soll.
Mir ist auch gar nicht so die konkrete Lösung (wenn es hier überhaupt die goldene Lösung gibt) wichtig, sondern das Verständnis des Gesamtkonstruktes.

TLDR: Mir fehlt, glaube ich, noch ein bisschen der generelle Überblick, wie das mit den 3D-APIs (Vulkan ist ja z.B. relativ ähnlich wie WebGPU) so funktioniert. Die einfachen Demos mit einem bunten Dreieck bekomme ich hin und verstehe da auch größtenteils, wie das zusammenhängt. Mir fällt hierbei der Schritt momentan noch schwer, das auszubauen und zu "abstrahieren".

Ich wäre froh wenn sich jemand, der sich damit auskennt, mir da ein bisschen weiterhelfen kann (gerne direkt, aber auch gerne durch Verweise auf Tutorials etc.)
»poorsider« hat folgendes Bild angehängt:
  • webgpu_demo.jpg

Schrompf

Alter Hase

Beiträge: 1 470

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

2

18.03.2020, 20:40

Ich glaube, Du suchst da die "richtige" Lösung, wo es nur jede Menge "könnte man so machen" gibt. Ich finde Deine Aufteilung in Meshes, Materials und RenderObjects (bei mir heißen die Instances) schon sehr gut. Die GPU kann bis zu 16 Konstantenbuffer mappen - Du könntest pro Zweck einen Buffer machen und fröhlich für jeden DrawCall kombinieren. Dann musst Du nur festlegen, in welchem Slot der Material-Buffer liegt, in welchem der Buffer mit den Matrizen usw. Du kannst aber auch oldschool den Buffer vor jedem DrawCall mappen, mit allen Details füllen und unmappen und wirst trotzdem kaum einen Unterschied benchmarken können. Weil die Spiele der letzten 20 Jahre so gearbeitet haben und die Treiberhersteller deswegen diesen Pfad gut optimiert haben.

Probier's aus. Aber für Dein eigenes Wohlbefinden würde ich Dir empfehlen, erstmal eine minimal nötige Implementation anzustreben, so dass Du was aufm Bildschirm siehst. Von da aus bastelt und erprobt es sich dann viel motivierter. Und jedes Design, was Du Dir hier überlegst, ist ja nicht in Stein gemeißelt, Du kannst es ja refactoren und ausprobieren.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

3

19.03.2020, 12:44

Ich habe das jetzt nun mal mit einem einzigen UniformBuffer probiert, den ich vor jedem DrawCall aktualisieren würde. Jedoch ist der einzige schreibende Zugriff auf einen existierenden Buffer asynchron, da er erst gemappt werden muss. Ich habe nun sowas in der Art (Pseudocode):

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
callback(buffer_data, user_data) {
  memcpy(buffer_data, user_data)
  unmap(state.uniform_staging_buffer)
  copyBufferToBuffer(uniform_staging_buffer, uniform_buffer)
}

updateUniformBuffer(obj) {
  mapWriteAsync(state.uniform_staging_buffer, callback, obj.uniformData)
}

render() {
  for obj : renderObjects {
    updateUniformBuffer(obj)
    beginRenderPass
    ...
    endRenderPass
  }
  submitQueue
}

Der echte Code ist hier zu finden: GitHub

Dadurch habe ich jetzt aber das Problem, dass ich nicht davon ausgehen kann, dass die richtigen Daten im uniform_buffer liegen, wenn ich den RenderPass starte. Also müsste ich theoretisch das rendern des Objektes erst starten, wenn die Daten im uniform_buffer liegen, also am Ende des Callbacks. Um das nächste Objekt zu rendern, müsste ich das dann danach wieder im Callback anstoßen. Und das hört sich nicht richtig an (Rekursion / Callback-Hell).

Ich könnte alternativ auch vor jedem DrawCall den Buffer im mapped Status komplett neu kreieren, wobei ich dann aber auch die Bindings und die Pipeline jeweils komplett neu kreieren müsste.

Irgendwie habe ich das Gefühl, dass ich hier ein Puzzlestück übersehe.

Werbeanzeige