Effizienter Zugriff auf Texturdaten

Informieren Sie sich über die Vor- und Nachteile unterschiedlicher Zugriffsmöglichkeiten auf die zugrunde liegenden Texturpixeldaten in Ihrem Unity-Projekt.
Pixeldaten beschreiben die Farbe einzelner Pixel in einer Textur. Unity bietet Methoden, die es Ihnen ermöglichen, mit C#-Skripten Pixeldaten zu lesen oder in diese zu schreiben.
Sie können diese Methoden verwenden, um eine Textur zu duplizieren oder zu aktualisieren (beispielsweise um dem Profilbild eines Spielers ein Detail hinzuzufügen) oder um die Daten der Textur auf eine bestimmte Weise zu verwenden, beispielsweise um eine Textur zu lesen, die eine Weltkarte darstellt, um zu bestimmen, wo ein Objekt platziert werden soll.
Es gibt mehrere Möglichkeiten, Code zu schreiben, der Pixeldaten liest oder in diese schreibt. Welche Sie auswählen, hängt davon ab, was Sie mit den Daten vorhaben und welche Leistungsanforderungen Ihr Projekt hat.
Dieses Blog und das zugehörige Beispielprojekt sollen Ihnen dabei helfen, die verfügbare API und häufige Leistungsprobleme zu verstehen. Das Verständnis beider Aspekte hilft Ihnen dabei, eine leistungsfähige Lösung zu schreiben oder Leistungsengpässe zu beheben, sobald diese auftreten.
Für die meisten Texturtypen speichert Unity zwei Kopien der Pixeldaten: eine im GPU-Speicher, die für das Rendering erforderlich ist, und die andere im CPU-Speicher. Diese Kopie ist optional und ermöglicht Ihnen, Pixeldaten auf der CPU zu lesen, in sie zu schreiben und sie zu bearbeiten. Eine Textur mit einer Kopie ihrer im CPU-Speicher gespeicherten Pixeldaten wird als lesbare Textur bezeichnet. Zu beachten ist, dass RenderTexture nur im GPU-Speicher vorhanden ist.
Der der CPU zur Verfügung stehende Speicher unterscheidet sich bei den meisten Hardwarekomponenten von dem der GPU. Einige Geräte verfügen über eine Form von teilweise gemeinsam genutztem Speicher, für diesen Blog gehen wir jedoch von der klassischen PC-Konfiguration aus, bei der die CPU nur direkten Zugriff auf den an die Hauptplatine angeschlossenen RAM hat und die GPU auf ihren eigenen Video-RAM (VRAM) angewiesen ist. Alle zwischen diesen verschiedenen Umgebungen übertragenen Daten müssen über den PCI-Bus laufen, was langsamer ist als die Datenübertragung innerhalb des gleichen Speichertyps. Aufgrund dieser Kosten sollten Sie versuchen, die pro Frame übertragene Datenmenge zu begrenzen.

Das Sampling von Texturen in Shadern ist die häufigste GPU-Pixeldatenoperation. Um diese Daten zu ändern, können Sie zwischen Texturen kopieren oder mithilfe eines Shaders in eine Textur rendern. Alle diese Vorgänge können schnell von der GPU ausgeführt werden.
In manchen Fällen ist es möglicherweise vorzuziehen, Ihre Texturdaten auf der CPU zu bearbeiten, da dies mehr Flexibilität beim Datenzugriff bietet. CPU-Pixeldatenoperationen wirken sich nur auf die CPU-Kopie der Daten aus und erfordern daher lesbare Texturen. Wenn Sie die aktualisierten Pixeldaten in einem Shader abtasten möchten, müssen Sie diese zunächst durch einen Aufruf von Applyvon der CPU auf die GPU kopieren. Abhängig von der betreffenden Textur und der Komplexität der Vorgänge kann es schneller und einfacher sein, sich an CPU-Vorgänge zu halten (beispielsweise beim Kopieren mehrerer 2D-Texturen in ein Texture2DArray-Asset).
Die Unity-API bietet mehrere Methoden für den Zugriff auf oder die Verarbeitung von Texturdaten. Einige Vorgänge wirken sich sowohl auf die GPU- als auch auf die CPU-Kopie aus, wenn beide vorhanden sind. Daher variiert die Leistung dieser Methoden je nachdem, ob die Texturen lesbar sind. Zum Erreichen der gleichen Ergebnisse können unterschiedliche Methoden verwendet werden, jede Methode weist jedoch ihre eigenen Merkmale in Bezug auf Leistung und Benutzerfreundlichkeit auf.
Beantworten Sie die folgenden Fragen, um die optimale Lösung zu ermitteln:
- Kann die GPU Ihre Berechnungen schneller durchführen als die CPU?
- Welchen Druck übt der Prozess auf die Textur-Caches aus? (Beispielsweise verlangsamt das Sampling vieler hochauflösender Texturen ohne Verwendung von Mipmaps wahrscheinlich die GPU.)
- Erfordert der Vorgang eine zufällige Schreibtextur oder kann die Ausgabe in einen Farb- oder Tiefenanhang erfolgen? (Das Schreiben in zufällige Pixel einer Textur erfordert häufiges Leeren des Cache, was den Vorgang verlangsamt.)
- Gibt es bei meinem Projekt bereits einen GPU-Engpass? Selbst wenn die GPU einen Prozess schneller ausführen kann als die CPU, kann die GPU es sich leisten, mehr Arbeit zu übernehmen, ohne ihr Frame-Time-Budget zu überschreiten?
- Wenn sich sowohl der GPU- als auch der CPU-Hauptthread ihrem Frame-Zeitlimit nähern, könnte der langsame Teil eines Prozesses möglicherweise von CPU-Arbeitsthreads ausgeführt werden.
- Wie viele Daten müssen zur Berechnung oder Verarbeitung der Ergebnisse auf die GPU hoch- oder von der GPU heruntergeladen werden?
- Könnte ein Shader- oder C#-Job die Daten in ein kleineres Format packen, um die erforderliche Bandbreite zu reduzieren?
- Könnte eine RenderTexture auf eine Version mit niedrigerer Auflösung herunterskaliert werden, die stattdessen heruntergeladen wird?
- Kann der Vorgang in Abschnitten durchgeführt werden? (Wenn viele Daten gleichzeitig verarbeitet werden müssen, besteht das Risiko, dass der GPU nicht genügend Speicher dafür zur Verfügung steht.)
- Wie schnell werden die Ergebnisse benötigt? Können Berechnungen oder Datenübertragungen asynchron durchgeführt und später bearbeitet werden? (Wenn in einem einzelnen Frame zu viel Arbeit erledigt wird, besteht das Risiko, dass die GPU nicht genügend Zeit hat, die eigentliche Grafik für jedes Frame zu rendern.)
Standardmäßig sind Textur-Assets, die Sie in Ihr Projekt importieren, nicht lesbar, während aus einem Skript erstellte Texturen lesbar sind.
Lesbare Texturen verwenden doppelt so viel Speicher wie nicht lesbare Texturen, da sie eine Kopie ihrer Pixeldaten im CPU-RAM benötigen. Sie sollten eine Textur nur dann lesbar machen, wenn es nötig ist, und sie unlesbar machen, wenn Sie mit der Arbeit mit den Daten auf der CPU fertig sind.
Um zu sehen, ob ein Textur-Asset in Ihrem Projekt lesbar ist, und Änderungen vorzunehmen, verwenden Sie die Option „Lesen/Schreiben aktiviert“ in den Texture-Importeinstellungenoder die API „ TextureImporter.isReadable “.
Um eine Textur unlesbar zu machen, rufen Sie ihre Apply-Methode mit dem auf „true“ gesetzten Parameter makeNoLongerReadable auf (z. B. Texture2D.Apply oder Cubemap.Apply). Eine nicht lesbare Textur kann nicht wieder lesbar gemacht werden.
Alle Texturen sind für den Editor im Bearbeitungs- und Wiedergabemodus lesbar. Wenn Sie „Apply“ aufrufen, um die Textur unlesbar zu machen, wird der Wert von „isReadable“aktualisiert und Sie können nicht mehr auf die CPU-Daten zugreifen. Einige Unity-Prozesse funktionieren jedoch so, als ob die Textur lesbar wäre, da sie erkennen, dass die internen CPU-Daten gültig sind.

Die Leistung unterscheidet sich bei den verschiedenen Zugriffsmethoden auf Texturdaten erheblich, insbesondere auf der CPU (bei niedrigeren Auflösungen ist dies jedoch weniger der Fall). Das Beispiel-Repository der Unity Texture Access API auf GitHub enthält zahlreiche Beispiele, die Leistungsunterschiede zwischen verschiedenen APIs zeigen, die den Zugriff auf Texturdaten oder deren Bearbeitung ermöglichen. Die Benutzeroberfläche zeigt nur die CPU-Zeiten des Haupt-Threads an. In einigen Fällen werden DOTS-Funktionen wie Burst und das Jobsystem verwendet, um die Leistung zu maximieren.
Hier sind die im GitHub-Repository enthaltenen Beispiele:
- SimpleCopy: Kopieren aller Pixel von einer Textur in eine andere
- PlasmaTexture: Eine Plasmatextur, die pro Frame auf der CPU aktualisiert wird
- TransferGPUTexture: Übertragen (Kopieren in eine andere Größe oder ein anderes Format) aller Pixel auf der GPU von einer Textur in eine RenderTexture
Nachfolgend sind Leistungsmessungen aus den Beispielen auf GitHub aufgeführt. Diese Zahlen werden zur Unterstützung der folgenden Empfehlungen verwendet. Die Messungen stammen von einem Player-Build auf einem System mit einer 3,7 GHz 8-Core Xeon® W-2145 CPU und einer RTX 2080.
Dies sind die mittleren CPU-Zeiten für SimpleCopy.UpdateTestCase mit einer Texturgröße von 2.048.
Beachten Sie, dass die Grafikmethoden im Hauptthread nahezu augenblicklich abgeschlossen werden, da sie die Arbeit einfach auf den RenderThread verschieben, der später von der GPU ausgeführt wird. Ihre Ergebnisse stehen zur Verfügung, wenn das nächste Frame gerendert wird.
Ergebnisse
- 1.326 ms – foreach(mip) for(x in der Breite) for(y in der Höhe) SetPixel(x, y, GetPixel(x, y, mip), mip)
- 32,14 ms – für jeden (mip) SetPixels (Quelle.GetPixels (mip), mip)
- 6,96 ms – foreach(mip) SetPixels32(Quelle.GetPixels32(mip), mip)
- 6,74 ms – LoadRawTextureData(Quelle.GetRawTextureData())
- 3,54 ms – Graphics.CopyTexture(lesbareQuelle, lesbaresZiel)
- 2,87 ms – für jeden (mip) SetPixelData<byte>(mip, GetPixelData<byte>(mip))
- 2,87 ms – LoadRawTextureData(Quelle.GetRawTextureData<byte>())
- 0,00 ms – Graphics.ConvertTexture(Quelle, Ziel)
- 0,00 ms – Graphics.CopyTexture (nicht lesbare Quelle, Ziel)
Dies sind die mittleren CPU-Zeiten für PlasmaTexture.UpdateTestCase mit einer Texturgröße von 512.
Sie werden feststellen, dass SetPixels32 unerwartet langsamer ist als SetPixels. Dies liegt daran, dass das Float-basierte Farbergebnis aus der Plasmapixelberechnung übernommen und in die Byte-basierte Color32-Struktur konvertiert werden muss. SetPixels32NoConversion überspringt diese Konvertierung und weist dem Color32-Ausgabearray einfach einen Standardwert zu, was zu einer besseren Leistung als SetPixels führt. Um die Leistung von SetPixels und der zugrunde liegenden Farbkonvertierung von Unity zu übertreffen, muss die Pixelberechnungsmethode selbst überarbeitet werden, um direkt einen Color32-Wert auszugeben. Eine einfache Implementierung mit SetPixelData liefert fast garantiert bessere Ergebnisse als sorgfältige SetPixels- und SetPixels32-Ansätze.
Ergebnisse
- 126,95 ms – SetPixel
- 113,16 ms – SetPixels32
- 88,96 ms – SetPixels
- 86,30 ms – SetPixels32NoConversion
- 16,91 ms – SetPixelDataBurst
- 4,27 ms – SetPixelDataBurstParallel
Dies sind die Editor-GPU-Zeiten für TransferGPUTexture.UpdateTestCase mit einer Texturgröße von 8.196:
- Blit – 1.584 ms
- CopyTexture – 0,882 ms
Auf die Pixeldaten können Sie auf verschiedene Weise zugreifen. Allerdings unterstützen nicht alle Methoden jedes Format, jeden Texturtyp oder jeden Anwendungsfall, und die Ausführung einiger Methoden dauert länger als die anderer. In diesem Abschnitt werden die empfohlenen Methoden beschrieben. Im folgenden Abschnitt werden die Methoden beschrieben, bei deren Verwendung Vorsicht geboten ist.
CopyTexture ist der schnellste Weg, GPU-Daten von einer Textur in eine andere zu übertragen. Es wird keine Formatkonvertierung durchgeführt. Sie können Daten teilweise kopieren, indem Sie zusätzlich zur Breite und Höhe der Region eine Quell- und Zielposition angeben. Wenn beide Texturen lesbar sind, wird der Kopiervorgang auch für die CPU-Daten ausgeführt, wodurch die Gesamtkosten dieser Methode näher an die einer reinen CPU-Kopie mit SetPixelData und dem Ergebnis von GetPixelData aus einer Quelltextur heranrücken.
Blit ist eine schnelle und leistungsstarke Methode, GPU-Daten mithilfe eines Shaders in eine RenderTexture zu übertragen. In der Praxis muss dadurch der API-Status der Grafikpipeline so eingerichtet werden, dass das Rendering in die Ziel-Rendertextur erfolgt. Im Vergleich zu CopyTexture fallen geringe, auflösungsunabhängige Einrichtungskosten an. Der von der Methode verwendete Standard-Blit-Shader nimmt eine Eingabetextur und rendert sie in die Ziel-Rendertextur. Indem Sie ein benutzerdefiniertes Material oder einen benutzerdefinierten Shader bereitstellen, können Sie komplexe Textur-zu-Textur-Renderingprozesse definieren.
GetPixelData und SetPixelData (zusammen mit GetRawTextureData) sind die schnellsten Methoden, wenn nur CPU-Daten berührt werden. Bei beiden Methoden müssen Sie einen Strukturtyp als Vorlagenparameter angeben, der zur Neuinterpretation der Daten verwendet wird. Die Methoden selbst benötigen diese Struktur nur, um die richtige Größe abzuleiten. Sie können also einfach Byte verwenden, wenn Sie keine benutzerdefinierte Struktur zum Darstellen des Texturformats definieren möchten.
Beim Zugriff auf einzelne Pixel empfiehlt es sich, zur einfacheren Verwendung eine benutzerdefinierte Struktur mit einigen Hilfsmethoden zu definieren. Beispielsweise könnte eine Struktur im R5G5B5A1-Format aus einem ushort-Datenelement und einigen Get/Set-Methoden bestehen, um auf die einzelnen Kanäle als Bytes zuzugreifen.
Unbekannter Blocktyp „codeBlock“, bitte geben Sie einen Serialisierer dafür in der Eigenschaft „serializers.types“ an
Der obige Code ist ein Beispiel aus der Implementierung eines Objekts, das ein Pixel im Format R5G5B5A5A1 darstellt; die entsprechenden Eigenschaftensetter wurden der Kürze halber weggelassen.
Mit SetPixelData kann eine vollständige MIP-Datenebene in die Zieltextur kopiert werden. GetPixelData gibt ein NativeArray zurück, das tatsächlich auf eine MIP-Ebene der internen CPU-Texturdaten von Unity verweist. Dadurch können Sie die Daten direkt lesen/schreiben, ohne dass Kopiervorgänge erforderlich sind. Der Haken besteht darin, dass die Gültigkeit des von GetPixelData zurückgegebenen NativeArrays nur so lange garantiert ist, bis der Benutzercode, der GetPixelData aufruft, die Kontrolle an Unity zurückgibt, z. B. wenn MonoBehaviour.Update zurückgegeben wird. Anstatt das Ergebnis von GetPixelData zwischen den Frames zu speichern, müssen Sie für jeden Frame, von dem Sie auf diese Daten zugreifen möchten, das richtige NativeArray von GetPixelData abrufen.
Die Apply- Methode kehrt zurück, nachdem die CPU-Daten auf die GPU hochgeladen wurden. Der Parameter makeNoLongerReadable sollte nach Möglichkeit auf „true“ gesetzt werden, um den Speicher der CPU-Daten nach dem Upload freizugeben.
Die Methoden RequestIntoNativeArray und RequestIntoNativeSlice laden GPU-Daten asynchron aus der angegebenen Textur in (einen Ausschnitt) eines vom Benutzer bereitgestellten NativeArrays herunter.
Durch Aufrufen der Methoden wird ein Anforderungs-Handle zurückgegeben, der anzeigen kann, ob der Download der angeforderten Daten abgeschlossen ist. Die Unterstützung ist auf nur eine Handvoll Formate beschränkt. Verwenden Sie daher SystemInfo.IsFormatSupported mit FormatUsage.ReadPixels, um die Formatunterstützung zu überprüfen. Die Klasse AsyncGPUReadback verfügt auch über eine Request- Methode, die ein NativeArray für Sie zuweist. Wenn Sie diesen Vorgang wiederholen müssen, erzielen Sie eine bessere Leistung, wenn Sie stattdessen ein NativeArray zuordnen, das Sie wiederverwenden.
Bei zahlreichen Methoden ist aufgrund der möglicherweise erheblichen Auswirkungen auf die Leistung Vorsicht geboten. Schauen wir sie uns genauer an.
Diese Methoden führen Pixelformatkonvertierungen von unterschiedlicher Komplexität durch. Die Pixels32-Varianten sind die leistungsstärksten von allen, aber selbst sie können Formatkonvertierungen durchführen, wenn das zugrunde liegende Format der Textur nicht perfekt mit der Color32-Struktur übereinstimmt. Beachten Sie bei der Verwendung der folgenden Methoden, dass deren Auswirkungen auf die Leistung mit zunehmender Pixelanzahl deutlich und in unterschiedlichem Ausmaß zunehmen:
GetRawTextureData und LoadRawTextureData sind reine Texture2D-Methoden, die mit Arrays arbeiten, die nacheinander die Rohpixeldaten aller MIP-Ebenen enthalten. Das Layout verläuft vom größten zum kleinsten Mip, wobei jeder Mip die Menge an „Höhe“ und „Breite“ der Pixelwerte darstellt. Diese Funktionen ermöglichen schnellen CPU-Datenzugriff. GetRawTextureData weist einen Haken auf, da die nicht auf Vorlagen basierende Variante eine Kopie der Daten zurückgibt. Dies ist etwas langsamer und ermöglicht keine direkte Manipulation des zugrunde liegenden, von Unity verwalteten Puffers. GetPixelData hat diese Eigenart nicht und kann nur ein NativeArray zurückgeben, das auf den zugrunde liegenden Puffer zeigt, der gültig bleibt, bis der Benutzercode die Kontrolle an Unity zurückgibt.
ConvertTexture ist eine Möglichkeit, GPU-Daten von einer Textur auf eine andere zu übertragen, wenn die Quell- und Zieltexturen nicht dieselbe Größe oder dasselbe Format haben. Dieser Konvertierungsprozess ist unter den gegebenen Umständen so effizient wie möglich, aber nicht billig. Dies ist der interne Prozess:
Weisen Sie eine temporäre RenderTexture zu, die der Zieltextur entspricht.
Führen Sie einen Blit von der Quelltextur zur temporären RenderTexture durch.
Kopieren Sie das Blit-Ergebnis aus der temporären RenderTexture in die Zieltextur.
Um zu ermitteln, ob diese Methode für Ihren Anwendungsfall geeignet ist, beantworten Sie die folgenden Fragen:
- Muss ich diese Konvertierung durchführen?
- Kann ich sicherstellen, dass die Quelltextur beim Importieren in der gewünschten Größe/im gewünschten Format für die Zielplattform erstellt wird?
- Kann ich meine Prozesse so ändern, dass sie dieselben Formate verwenden, sodass das Ergebnis eines Prozesses direkt als Eingabe für einen anderen Prozess verwendet werden kann?
- Kann ich stattdessen eine RenderTexture als Ziel erstellen und verwenden? Dadurch würde der Konvertierungsprozess auf ein einzelnes Blit zur Ziel-Rendertextur reduziert.
Die Methode ReadPixels lädt GPU-Daten synchron von der aktiven RenderTexture (RenderTexture.active) in die CPU-Daten eines Texture2D herunter. Auf diese Weise können Sie die Ausgabe eines Rendering-Vorgangs speichern oder verarbeiten. Die Unterstützung ist auf nur eine Handvoll Formate beschränkt. Verwenden Sie daher SystemInfo.IsFormatSupported mit FormatUsage.ReadPixels, um die Formatunterstützung zu überprüfen.
Das Zurückladen von Daten von der GPU ist ein langsamer Prozess. Bevor es beginnen kann, muss ReadPixels warten, bis die GPU alle vorhergehenden Arbeiten abgeschlossen hat. Diese Methode sollten Sie am besten vermeiden, da sie erst zurückkehrt, wenn die angeforderten Daten verfügbar sind, was die Leistung beeinträchtigt. Auch die Benutzerfreundlichkeit ist ein Problem, da GPU-Daten in einer RenderTexture vorhanden sein müssen, die als die aktuell aktive konfiguriert sein muss. Sowohl die Benutzerfreundlichkeit als auch die Leistung sind besser, wenn Sie die zuvor besprochenen AsyncGPUReadback- Methoden verwenden.
Die Klasse ImageConversion verfügt über Methoden zur Konvertierung zwischen Texture2D und mehreren Bilddateiformaten. LoadImage kann JPG-, PNG- oder EXR-Daten (seit 2023.1) in ein Texture2D laden und diese für Sie auf die GPU hochladen. Die geladenen Pixeldaten können je nach Originalformat von Texture2D im laufenden Betrieb komprimiert werden. Andere Methoden können ein Texture2D- oder Pixeldaten-Array in ein Array aus JPG-, PNG-, TGA- oder EXR-Daten konvertieren.
Diese Methoden sind nicht besonders schnell, können aber nützlich sein, wenn Ihr Projekt Pixeldaten über gängige Bilddateiformate weitergeben muss. Typische Anwendungsfälle sind das Laden des Avatars eines Benutzers von der Festplatte und das Teilen mit anderen Spielern über ein Netzwerk.
Es stehen zahlreiche Ressourcen zur Verfügung, um mehr über Grafikoptimierung, verwandte Themen und Best Practices in Unity zu erfahren. Der Abschnitt zur Grafikleistung und Profilerstellung in der Dokumentation ist ein guter Ausgangspunkt.
Sie können sich auch mehrere technische E-Books für fortgeschrittene Benutzer ansehen, darunter „Der ultimative Leitfaden zum Profilieren von Unity-Spielen“,„Optimieren Sie die Leistung Ihrer Handyspiele“ und „Optimieren Sie die Leistung Ihrer Konsolen- und PC-Spiele“.
Viele weitere fortgeschrittene Best Practices finden Sie im Unity-How-to-Hub.
Hier ist eine Zusammenfassung der wichtigsten Punkte, die Sie sich merken sollten:
- Bei der Manipulation von Texturen besteht der erste Schritt darin, zu beurteilen, welche Vorgänge für eine optimale Leistung auf der GPU ausgeführt werden können. Die vorhandene CPU-/GPU-Arbeitslast und die Größe der Eingabe-/Ausgabedaten sind wichtige Faktoren, die berücksichtigt werden müssen.
- Die Verwendung von Low-Level-Funktionen wie GetRawTextureData zum Implementieren eines bestimmten Konvertierungspfads kann bei Bedarf eine bessere Leistung bieten als die bequemeren Methoden, die (oft redundante) Kopien und Konvertierungen durchführen.
- Komplexere Vorgänge wie umfangreiche Rücklesevorgänge und Pixelberechnungen sind auf der CPU nur dann durchführbar, wenn sie asynchron oder parallel ausgeführt werden. Die Kombination aus Burst und dem Jobsystem ermöglicht es C#, bestimmte Vorgänge auszuführen, die sonst nur auf einer GPU ausgeführt werden könnten.
- Häufiges Profilieren: Während der Entwicklung können Ihnen viele Fallstricke begegnen, von unerwarteten und unnötigen Konvertierungen bis hin zu Verzögerungen durch das Warten auf einen anderen Prozess. Einige Leistungsprobleme treten erst zutage, wenn das Spiel skaliert wird und bestimmte Teile Ihres Codes stärker beansprucht werden. Das Beispielprojekt zeigt, wie scheinbar kleine Erhöhungen der Texturauflösung dazu führen können, dass bestimmte APIs zu einem Leistungsproblem werden.
Teilen Sie uns Ihr Feedback zu Texturdaten in den Foren „Scripting“ oder „Allgemeine Grafiken“ mit. Achten Sie auf neue technische Blogs von anderen Unity-Entwicklern im Rahmen der laufenden Technik aus der Trenches- Reihe.
