Jump to content
Unity Insider Forum

TileMap AlphaBlending


StefanD

Recommended Posts

Hallo zusammen,

ich versuche mich gerade daran meine Tilemap etwas zu verschönern. Diese wird aktuell mit einem eigenen Shader mit einem TextureArray gezeichnet. Leider sehen damit die Übergänge zwischen den Tiles ziemlich "bescheiden" aus. Daher würde ich gerne immer die benachbarten Tiles mit überblenden um einen sauberen Übergang zwischen den Typen zu haben. Ich habe hier aber das Problem das mir nicht ganz klar ist wie ich das Umsetzen kann.

Hier der Shader Code ohne Anpassungen:

Shader "Custom/TileMap" {
	Properties{
		_MainTex("Texture", 2DArray) = "white" {}
	}
		SubShader{
		Pass{
			CGPROGRAM
				#pragma vertex MyVertexProgram
				#pragma fragment MyFragmentProgram
				#pragma target 3.5
				#include "UnityCG.cginc"

				UNITY_DECLARE_TEX2DARRAY(_MainTex);

				struct VertexData {
					float4 position : POSITION;
					float3 uv : TEXCOORD0;
				};

				struct Interpolators {
					float4 position : SV_POSITION;
					float3 uv : TEXCOORD0;
				};

				Interpolators MyVertexProgram(VertexData v) {
					Interpolators i;
					i.position = UnityObjectToClipPos(v.position);
					i.uv = v.uv;
					return i;
				}

				fixed4 MyFragmentProgram(Interpolators i) : SV_TARGET {
					return UNITY_SAMPLE_TEX2DARRAY(_MainTex, i.uv);
				}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

 

Meine Idee wäre gewesen zum einen jeweils die Texture ID der benachbarten Tiles sowie eine BlendMap an den Shader zu übergeben und die benachbarten Tiles damit zu überblenden. Ich habe das schon für den geraden Übergang von links nach rechts gemacht. Wenn ich das so mache, würde ich aber jeweils 9 Texturen (Haupt Texture + 8 Nachbarn) und 8 BlendMaps für jeden Nachbarn benötigen. Das kommt mir aber etwas viel vor, unabhängig davon ob es überhaupt möglich ist.

 

Shader "Custom/TileMap" {
	Properties{
		_MainTex("Texture", 2DArray) = "white" {}
		_BlendTex("Blend", 2D) = "White" {}
	}
		SubShader{
		Pass{
			CGPROGRAM
				#pragma vertex MyVertexProgram
				#pragma fragment MyFragmentProgram
				#pragma target 3.5
				// #include "UnityCG.cginc"

				UNITY_DECLARE_TEX2DARRAY(_MainTex);
				sampler2D _BlendTex;

				struct VertexData {
					float4 position : POSITION;
					float3 uv : TEXCOORD0;
					float3 left : TEXCOORD1;
				};

				struct Interpolators {
					float4 position : SV_POSITION;
					float3 uv : TEXCOORD0;
					float3 left : TEXCOORD1;
				};

				Interpolators MyVertexProgram(VertexData v) {
					Interpolators i;
					i.position = UnityObjectToClipPos(v.position);
					i.uv = v.uv;
					i.left = v.left;
					return i;
				}

				fixed4 MyFragmentProgram(Interpolators i) : SV_TARGET {
					fixed4 main = UNITY_SAMPLE_TEX2DARRAY(_MainTex, i.uv);
					fixed4 left = UNITY_SAMPLE_TEX2DARRAY(_MainTex, i.left);

					fixed4 blend = tex2D(_BlendTex, i.uv);

					return main.rgba * blend.a + (1 - blend.a) * left.rgba;
				}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

 

Hat jemand ein paar Tipps wie das ganze sonst Umgesetzt werden kann? Oder bin ich mit meinem Versuch prinzipiell schon auf dem richtigen Weg?

Vielen Dank und noch einen schönen Sonntag

Gruß Stefan

Link zu diesem Kommentar
Auf anderen Seiten teilen

Meinst du das hier?:
https://docs.unity3d.com/Manual/Tilemap.html

Solltest du obiges meinen, ist der Shader nur ein Teil des Ganzen, da die Tilemap über den Tile-Renderer erzeugt wird. Dem Shader selbst fehlen die Informationen über die umgebenden Tiles (dieser rendert ja nur ein Tile). Der Tile-Renderer sollte diese Information allerdings kennen. Ich habe es nur überfolgen, aber ich denke man kann hiermit den Tile-Renderer erweitern:
https://docs.unity3d.com/Manual/Tilemap-ScriptableTiles.html

Nachdem du den Tile-Renderer angezapft hast, könntest du die umgebenen Tiles auslesen und sie als Parameter an deinen Shader übergeben:
https://docs.unity3d.com/ScriptReference/Material.SetTexture.html

Ist aber ein ziemlicher Aufwand.

Hier ist auch noch einiges an Quellcode und Beispielen zu finden:
https://github.com/Unity-Technologies/2d-extras

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo Zer0Cool,

nein das meine ich nicht. Ich habe den Shader und die Tiles selbst geschrieben. Aber ich erkläre noch einmal kurz was ich genau versuche zu tun. Ich habe eine Karte auf der sich Gebäude, Einheiten usw. befinden. Mir geht es aktuell um das zeichnen der Karte. Mit dem oberen Shader habe ich angefangen. Darin übergabe ich an das Probertie "MainTex" ein Array mit Texturen (Alle Texturen, welche auf der Karte gezeichnet werden sollen). Hier ist das beschrieben: Unity3D - Texture Arrays

Dabei wird nun aber das gesamte Tile mit der jeweiligen Texture gezeichnet, was theoretisch ja richtig ist aber einfach nicht gut aussieht. Welche Texture gezeichnet wird steht in der Z-Koordinate der uv. Daher wollte ich nun mit einer Alpha Map die benachbarten Tiles überblenden, damit das ganze schöne Übergänge hat und die Tiles nicht mehr ganz so zu sehen sind. Das ganze wird auch in dem Beitrag (Game Map in HTML Canvas) beschrieben. Hier habe ich die Idee her, schaffe es aber nicht wirklich das zu implementieren.

Im unteren Shader übergebe ich dann eine zweite UV "left", in dem ebenfalls in der Z-Koordinate der Type des Linken Nachbarn steht. Nur mit dem linken Nachbarn funktioniert das Vorgehen auch. Jedoch habe ich jetzt das Problem das mir nicht ganz klar ist wie ich das für alle Ränder und Ecken machen kann. 

Theoretisch könnte ich das vorgehen wie für die Linke Seite für jede Seite und Ecke machen, was aber zur Folge hat, das ich nur für die Texture schon 9 Textkoordinaten an den Shader übergeben muss. Zusätzlich dann natürlich auch für die BlendMap. Meine Frage bezieht sich darauf ob das eine Lösung ist die man so umsetzen kann, bzw. wie andere Wege wären das ganze mit maximaler Flexibilität zu implementieren. 

Kann man hier eigentlich irgendwie Bilder hochladen, dann könnte ich mal zeigen was die beiden Shader genau machen. Dann kann man sich das vielleicht noch besser vorstellen.

Danke und Grüße

Stefan

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hmm, ok, wie setzt du denn die Z-Koordinate der UVs? Im Shader selbst wird diese ja scheinbar nur übergeben? Es wäre hilfreich zu wissen, wie die UVs deines Meshes sind den du zeichnen willst., davon könnte ja auch abhängen, wie du überblenden musst (und wie dein Mesh aussieht). Ich stelle mir aktuell vor, daß sich die "normalen" UVs einfach von 0..1 über die gesamte Face deines Modells aufspannen und du setzt jeweils die Z-Koordinate pro Quadrant? Ich frag mich halt immer noch, wie du die Z-Koordinate überhaupt setzt?

Ich vermute dein Mesh besteht dann aus Quadranten (= 2 Faces), wobei jeweils ein Quadrant ein Tile darstellt?

PS:
Bild hochladen würde ich mit imgur und dann den Link hier einstellen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Okay hier einmal zwei Bilder die zeigen wie das ganze aktuell aussieht.

Original (1. Shader):

r1JGhtim.png

Mit Alpha Map und Blending (2.Shader)

qsxzvUfm.png

Wie man sieht sind in dem 2 Bild die Übergänge zwischen den Typen schon schöner. Das verhalten würde ich gerne für alle 8 Nachbarn hinbekommen, das es überall gut aussieht. Natürlich sind die Texturen und die AlphaMap noch nicht wirklich gut.

Genau das was du geschrieben hast ist korrekt. Mein Mesh besteht aus lauter Dreiecken, wobei immer 2 Dreiecke ein Tile entsprechen. Diesem gebe ich dann immer die UVs von 0 -1 mit um die Gesamte Texture auf die Dreiecke zu rendern. Ursprünglich war mal jedes Tile ein GameObject, was ich aber in dem selben Zug geändert habe. 

Hier mal der Code wie ich aktuell das Mesh und die Texture Koordinaten erzeuge. Ist leider nicht so richtig gut kommentiert, da ich aktuell am testen bin wie ich das am besten lösen kann. Ich hoffe das man das trotzdem versteht. Sonst bei Fragen einfach Fragen. 

public class TileMesh : MonoBehaviour
{
    Mesh mesh;
    MeshCollider meshCollider;
    MeshRenderer meshRenderer;

    List<Vector3> vertices;
    List<int> triangles;

    Dictionary<ITileType, int> textureMap;
    Texture2DArray textureArray;
    List<Vector3> uv;
    List<Vector3> left;
  
    public void Triangulate(ITile[] tiles)
    {
        // Cleare old data
        mesh.Clear();
        vertices.Clear();
        triangles.Clear();
        uv.Clear();
        left.Clear();

        // Calculate new data
        for (int i = 0; i < tiles.Length; i++)
        {
            Triangulate(tiles[i]);
        }

        // Apply data to mesh.
        mesh.vertices = vertices.ToArray();
        mesh.triangles = triangles.ToArray();
        mesh.SetUVs(0, uv);
        mesh.SetUVs(1, left);
 
        mesh.RecalculateNormals();
        meshCollider.sharedMesh = mesh;
    }

    void Triangulate(ITile tile)
    {
        // Gets the center point and calculaate triangles.
        var tileCenter = new Vector3(tile.Position.X, tile.Position.Y);
        var tileTopLeft = tileCenter + new Vector3(-0.5f, 0.5f);
        var tileTopRigth = tileCenter + new Vector3(0.5f, 0.5f);
        var tileBottomLeft = tileCenter + new Vector3(-0.5f, -0.5f);
        var tileBottomRight = tileCenter + new Vector3(0.5f, -0.5f);

        AddTriangle(tileBottomLeft, tileTopLeft, tileTopRigth);
        AddTriangle(tileBottomLeft, tileTopRigth, tileBottomRight);

        // Get  terrain typ and calculate uvs for it.
        var terrainID = textureMap[tile.TileType];
        var terrainTopLeft = new Vector3(0, 1, terrainID);
        var terrainTopRight = new Vector3(1, 1, terrainID);
        var terrainBottomLeft = new Vector3(0, 0, terrainID);
        var terrainBottomRight = new Vector3(1, 0, terrainID);

        AddUV(terrainBottomLeft, terrainTopLeft, terrainTopRight);
        AddUV(terrainBottomLeft, terrainTopRight, terrainBottomRight);

        // Get  terrain typ of the left tile and calculate uvs for it.
        var leftTile = tile;
        if(tile.Position.X > 0)
            leftTile = WorldController.Instance.World.GetTileAt(tile.Position.Offset(-1, 0));

        terrainID = textureMap[leftTile.TileType];
        terrainTopLeft = new Vector3(0, 1, terrainID);
        terrainTopRight = new Vector3(1, 1, terrainID);
        terrainBottomLeft = new Vector3(0, 0, terrainID);
        terrainBottomRight = new Vector3(1, 0, terrainID);

        AddLeftUV(terrainBottomLeft, terrainTopLeft, terrainTopRight);
        AddLeftUV(terrainBottomLeft, terrainTopRight, terrainBottomRight);
    }

    void AddTriangle(Vector3 v1, Vector3 v2, Vector3 v3)
    {
        int vertexIndex = vertices.Count;
        vertices.Add(v1);
        vertices.Add(v2);
        vertices.Add(v3);
        triangles.Add(vertexIndex);
        triangles.Add(vertexIndex + 1);
        triangles.Add(vertexIndex + 2);
    }

    void AddUV(Vector3 v1, Vector3 v2, Vector3 v3)
    {
        uv.Add(v1);
        uv.Add(v2);
        uv.Add(v3);
    }

    void AddLeftUV(Vector3 v1, Vector3 v2, Vector3 v3)
    {
        left.Add(v1);
        left.Add(v2);
        left.Add(v3);
    }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich vermute du kommst mit dem Überblenden so nicht weiter. Ich denke auch du musst das Überblenden auf den Vertex beziehen und nicht auf das Tile selbst.
Jetzt stellt sich die Frage, woher bekommst du die Information im Shader, welche Texturen die umgebenden Vertices verwenden. Eine mögliche Lösung wäre, daß du die Vertexcolors des Meshes verwendest. In deinem Skript könnten beispielsweise die Vertexcolors des Meshes so gesetzt werden:

vertex_xy:        nachbar + texturnummer 
linker Nachbar:   00 + 001 (Textur1) = Vertexcolor = 00001 = 1
rechter Nachbar:  01 + 010 (Textur2) = Vertexcolor = 01010 = 10
oberer Nachbar:   10 + 011 (Textur3) = Vertexcolor = 10011 = 19
unterer Nachbar:  11 + 100 (Textur4) = Vertexcolor = 11100 = 28

https://docs.unity3d.com/ScriptReference/Mesh-colors.html

Jetzt musst du dem Shader noch die Vertexcolor übergeben und dann über eine Bitoperation die jeweiligen Texturen der Nachbarn auslesen und dann überblenden.

struct VertexData {
  float4 position : POSITION;
  float3 uv : TEXCOORD0;
  fixed4 color : COLOR; //  is the per-vertex color, typically a float4
};

Wenn ich richtig liege, brauchst du so nur die MainTex, weil darin sind alle Texturen enthalten die du zum Überblenden brauchst. Die Überblendung selbst kannst du mit lerp() berechnen. Wird noch ein wenig Aufwand bis der Shadercode steht, aber sollte machbar sein.

// lerp between texture1 and texture2
fixed3 mixedColor = lerp(texture1.rgb, texture2.rgb, 0.5);

Kann sein, daß du die 0.5 noch über die aktuelle UV berechnen musst, um einen weichen Übergang zu erhalten, da du dich ja im Fragmentshader befindest, an dieser Stelle wird es leider kompliziert, da bin ich mir auch gerade unsicher.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich bin mir nicht ganz sicher ob ich das richtig verstanden habe. Glaube eher nicht, weil ich bei mir die Nachbar Nummer nicht brauchen würde. Hier einmal skizziert wie ich mir das jetzt gerade vorstelle.

su5Cr2u.png

Also jedes Vertex (Eckpunkt eines Dreiecks) hat die folgenden Daten.

  • TextureArray mit allen Texturen.
  • BlendMask zum überblenden der Texturen.
  • UV Position der Texturen im Dreieck. 
  • Color Jeder Kanal als ID für die jeweilige Textur.

Mithilfe des "TextureArray" und der Farbinformation in "Color" können dann die einzelnen 4 Texturen im VertexShader bestimmt werden.
Mit "UV" kann die Position der Texture auf dem Vertex ermittelt werden. Z.B. Oben links => UV = 0 / 1
Damit können dann die 4 TexCoords berechnet werden. Diese ergibt sich immer aus "UV" und in der Koordinate Z die TextureID.
Diese 4 TexCoords werden an den Fragment Shader übergeben. Hier wird zusammen mit der BlendMask für jedes Pixel die Farbe bestimmt.

Ist das so richtig und umsetzbar? Oder habe ich etwas falsch verstanden?
Werde ich gleich mal versuchen so umzusetzen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich würde es so probieren:

// lerp between texture left and texture right
fixed3 mixedColorU = lerp(texture_left.rgb, texture_right.rgb, i.uv.x);
// lerp between texture top and bottom
fixed3 mixedColorV = lerp(texture_top.rgb, texture_bottom.rgb, i.uv.y);
fixed3 mixedColor = lerp(mixedColorU, mixedColorV, 0.5);


Bei deinem Pfeil hätte ich folgende Werte:
left: 2  (liegt auf dem Rand)
right: 1 
top: 1
bottom: 2 (liegt auf dem Rand)

Die Überblendung wäre so natürlich relativ breit. Man müsste halt schauen, wie das Ergebnis aussieht.
Ansonsten hängt es auch davon ab, mit welcher Logik die Nachbarn gesetzt werden, die sind ja nicht "eindeutig", aber sie sollten immer so gesetzt werden, daß Übergänge entstehen. Ich denke man bestimmt zuerst einen "Rand" und dieser Rand bestimmt die Werte der umgebenden Texturen. Bei deinem Beispiel wäre der Rand TID2. Dabei bekommen alle Vertices auf dem Rand die Textur innerhalb des Randes.

Wenn du ein kleines Exampleprojekt hochlädst, kann ich es mir mal anschauen, oben die Klasse ist ja nur ein Teil des Ganzen wenn ich das richtig sehe.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Danke für die ganze Hilfe Zer0Cool. Ich habe jetzt mal eine Test Applikation geschrieben.
Da ich nicht weiß wo ich die hier hochladen kann habe ich die bei Bitbucket als GIT Repository hochgeladen.
Link: https://bitbucket.org/StefanDu/tilemap_example

Ich bin zwar selber auch noch am ausprobieren und habe auch schon alle Möglichen ungewollten Effekte geschafft aber nicht das was ich eigentlich wollte.
Das hier find ich z.B. ganz gut gelungen, obwohl hier eigentlich nur schwarz und dunkelblau drauf sein soll....

o2uovOBm.png

Warum dort die ganzen anderen Farben droben sind bin ich noch am rätseln. Aber wie es aussieht funktioniert das mit den Farben als Index nicht. 

Edit: Was ich noch sagen wollte ist, der Code in dem Repository ist stark vereinfacht was alles außen Rum angeht. ich habe hier nur die Teile hochgeladen die für das Problem relevant sind, falls noch etwas benötig werden sollte einfach schreiben.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Habe das Projekt geladen und bin gerade dabei die Vertexcolors in den Shader einzubauen, erst wenn das klappt, kann man mal das Überblenden testen.

AddColor(Color.blue, Color.yellow, Color.white);
AddColor(Color.blue, Color.yellow, Color.white);


Ok, er zeigt sie zumindest schon einmal an:
xukRSNI.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich konnte die Vertexfarben nun nach unserem geplanten Algorithmus setzen. Die Texturindices werden dabei "einfach" in die RGBA-Farbkanäle gesetzt:

        // 0 = blue
        // 1 = green
        // 2 = red
        // r(left) / g(right) / b(top) / a(bottom)
        float colorScale = 0.1f;

        Color colorCodeV0 = new Color(textureMap[left.TileType] * colorScale, textureMap[tile.TileType] * colorScale, textureMap[tile.TileType] * colorScale, textureMap[bottom.TileType] * colorScale);
        Color colorCodeV1 = new Color(textureMap[left.TileType] * colorScale, textureMap[tile.TileType] * colorScale, textureMap[top.TileType] * colorScale, textureMap[tile.TileType] * colorScale);
        Color colorCodeV2 = new Color(textureMap[tile.TileType] * colorScale, textureMap[right.TileType] * colorScale, textureMap[top.TileType] * colorScale, textureMap[tile.TileType] * colorScale);
        Color colorCodeV3 = colorCodeV0;
        Color colorCodeV4 = colorCodeV2;
        Color colorCodeV5 = new Color(textureMap[tile.TileType] * colorScale, textureMap[right.TileType] * colorScale, textureMap[tile.TileType] * colorScale, textureMap[bottom.TileType] * colorScale);

Die Vertexfarben sehen dann so aus:
OCUzgYM.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hier noch einmal das Kontrollbild (unverändert):
h2KkfZk.png

So nun bin ich mit meinem Latein auch am Ende, mehr bekomme ich mit dieser Lösung nicht zusammen.
Ich musste die Meshsegmente verdoppeln, damit ein Übergang zu sehen ist. Was ich auch nicht verstehe, warum der blaue Anteil ständig so dominant ist.
z83nItq.png


Ebenfalls scheint ein Überblenden zwischen Vertikal und Horizontal nicht wie gewollt zu funktionieren, schaltet man beides ein, dann sieht es so aus:
HzzPA8F.png

Wenn man sich das Ergebnis anschaut, wird dies allerdings verständlich, daß das Überblenden von Links nach Rechts beispielsweise immer von rot nach rot verläuft und dadurch wird die Farbe rot ins Tile gemixed, welches eigentlich gerade von blau nach rot überblendet und damit entsteht wieder ein harter Übergang.

So sieht es dann mit Texturen aus:
Kontrollbild:
4KXH4bM.png
7JoZCfX.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hab die Ursache gefunden, der Shader interpoliert die Vertexfarben und dadurch läuft die Bestimmung des Texturindex im Shader schief ...

So hier ein letztes Ergebnis, mehr kann ich aus dem Ganzen nicht mehr rausholen:
RfY3BgX.png

Ich befürchte viel mehr ist aus der Technik nicht mehr rauszuholen. Die Überblendung über die Vertices war zwar eine nette Idee, aber durch die Interpolation der Vertexfarben werden die Texturen nicht sauber getrennt. Vielleicht braucht man eine Routine, die die Texturen zusätzlich anhand der Vertexfarben mischt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Puh, ich habe es letztendlich doch hinbekommen, ich habe die Interpolation der Vertexfarben mit einbezogen und einen Texturmix damit berechnet:

0K7ORJm.jpg

Render mit den original Farbtexturen:
hlaqcZy.png

Das einzige was nun noch stört sind die "Kanten", aber ich befürchte, daß liegt an der aktuell verwendeten "Technik".
Zudem schaue ich mir jeweils nur die 4 umgebenden Tiles an und ermittle damit einen Texturmix, ich vermute wenn man 8 Seiten einbezieht wird der Mix "runder" und die Kanten vermischen sich besser.

So sehen die "Kanten" aus der Nähe aus:
lxKeJfW.png


PS:
Würde Unity diese Technik beim Terrainshader verwenden, dann würde es kaum noch ein sichtbares Tiling bei den Terraintexturen geben, aber ihr sehr ja selbst, nicht ganz so einfach das Ganze umzusetzen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Oha, das sieht ja so aus als hätte ich dich mit meinem Problem den ganzen Tag beschäftigt. Vielen Danke erst mal für die Unterstützung. Das Ergebniss ist ja auch schon beeindruckend. ich habe gestern auch ewig versucht das hinzubekommen,  bin aber immer schon daran gescheitert, das ich die Indizies nicht als Farbe übergeben bekommen habe.

Alleine damit, dass das ganze so umsetztbar ist hast du mir schon viel geholfen. Jetzt muss ich mich dann hinsetzen und das ganze auch noch soweit hinbekommen. :-)
Was ist den aktuell deine Einschätzung. Ist das Vorgehen so brauchbar oder ist das eher zu umständlich / kompliziert und ressourcenintensiv (aus Performancesicht)?

Gibt es deine aktuelle Version irgendwo zum runterladen? Zumindest den Shader, bzw. wie du aus der Farbe den Index gebildet hast?
Falls ich dich mal bei irgendetwas unterstützen kann, dann schreib einfach, wobei meine Kentnisse hier bei weitem nicht so gut sind wie deine.

Gruß Stefan

Link zu diesem Kommentar
Auf anderen Seiten teilen

So ganz zu 100% ist der Shader immer noch nicht korrekt. Man kann es sehen, wenn man nur in eine Richtung überblendet. Dabei entsteht beispielsweise eine Überblendung von oben nach unten und beim darüberliegenden Vertex entsteht die selbe Überblendung nochmal.
fY889ai.png

Wie man sehen kann, überblenden die Vertices 2x. Ich habe nun versucht dies zu beheben, leider verschlechterte dies wiederum die Überblendung. Ich habe in den Shader nun beide Varianten eingebaut, so daß man Testen kann.Ich lade dir die Version mal hoch... siehe EMail

Am Shader kann man nun Version 1 und Version 2 einstellen. In Version 2 kann man zusätzlich noch einmal die Überblendung für Vertikal und Horizontal getrennt nachregeln.

Also die allgemeine Einschätzung:
Die Ressourcen sind minimal und der Performanceimpact ist minimal.
Das Hauptproblem habe ich oben beschrieben, bei einer kompletten Überblendung (Version 1) wird über 2 Zeilen überblendet. Über den Versuch dies zu beheben verschwindet zwar die doppelte Zeile, aber die Überblendung wird schwächer (verschlechtert).

PS:
Ich habe die Größe des Grids vervierfacht, ist ein Aufruf in der World class.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Nach einiger Überlegung, ich denke das Problem liegt nicht am Shader, sondern am Setzen der Ränder (Version 1 des Shaders wäre somit richtig).
Dafür müssen die Übergange allerdings von der Breite und von der Höhe her aus mehreren Tiles bestehen! Daher hatte ich auch das Grid vergrößert.

Nun müsste man an der Stelle wo wir die Vertexfarben setzen die Ränder beachten. Am Rand müssten jeweils die Farben auf den gleichen Wert gesetzt werden:

        Color colorCodeV0 = new Color(textureMap[left.TileType] * colorScale, textureMap[tile.TileType] * colorScale, textureMap[tile.TileType] * colorScale, textureMap[bottom.TileType] * colorScale);
        Color colorCodeV1 = new Color(textureMap[left.TileType] * colorScale, textureMap[tile.TileType] * colorScale, textureMap[top.TileType] * colorScale, textureMap[tile.TileType] * colorScale);
        Color colorCodeV2 = new Color(textureMap[tile.TileType] * colorScale, textureMap[right.TileType] * colorScale, textureMap[top.TileType] * colorScale, textureMap[tile.TileType] * colorScale);
        Color colorCodeV3 = colorCodeV0;
        Color colorCodeV4 = colorCodeV2;
        Color colorCodeV5 = new Color(textureMap[tile.TileType] * colorScale, textureMap[right.TileType] * colorScale, textureMap[tile.TileType] * colorScale, textureMap[bottom.TileType] * colorScale);

Der horizontale Rand besteht dabei aus:
V1 & V2/V4
und
V0/V3 & V5

Der vertikale Rand besteht dabei aus:
V0/V3 & V1
und
V2/V4 & V5

Soll ein Rand definiert werden, dann müssen diese Ränder den gleichen Farbcode bekommen.
Da wird momentan Tiles mit nur einer Vertexkantenlänge haben, können wir keine Ränder definieren.
Weil aktuell der Anfang und das Ende einer "Kante" zusammenfallen, bekommen wir quasi 2 Ränder und keinen sauberen Übergang.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Okay, dann sage ich nochmal vielen Dank. :-)
Ich habe leider gerade kein Unity dabei, kann daher erst am Freitag mit deinem Beispiel spielen. Du bist mir aber mit deinen Beiträgen zu schnell XD, da komm ich ja gar nicht mehr mit.
Habe mir aber jetzt den Code schon mal angeschaut. Hab das ganze aber noch nicht so wirklich verstanden. Ich glaube auch nur anhand des Codes komme ich da auch nicht wirklich dahinter. Ich muss das ganze dann mal sehen und an paar Werten drehen. Bin aber auch erst ganz am Anfang mit den ganzen Shader Sachen.

Wo kommen den in dem Shader die Variablen (_border1, border2, ...) her? Müssten die nicht als Property definiert sein?
Mit der Farbe sehe ich das du das genauso wie ich versuchst und den Wert auf 0 ... 1 normierst. Ich habe durch 100 geteilt und du multplizierst mit 0,1. Wie aber dann die Farbe im Shader wieder zu einer Texture ID umgewandelt wird finde ich nicht. Oder soll das immer dieser Teil sein und mit _border wird bestimmt im welchem Bereich die Texture ID = 0,1,2 oder 3 ist?

uvTop.z = 3;
if (i.color.b <= 0.2) uvTop.z = 2;
if (i.color.b > _border1 && i.color.b < _border2) uvTop.z = 1;
if (i.color.b < _border3) uvTop.z = 0;

Wie gesagt im Detail kann ich mir das leider erst am Wochenende anschauen.

Gruß Stefan

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe das ganze System noch einmal auf den Kopf gestellt, da die Technik wegen der Interpolation der Vertexfarben (im Fragmentshader) so überhaupt nicht funktionierte, da über die Interpolation ebenfalls andere Texturen in den Übergang hinein interpoliert wurden.
Als Beispiel:
- Farbe 0.2 für Textur 2
- Farbe 0,1 für Textur 1 und
- Farbe 0.0 für Textur 0
Wenn nun von Textur 2 nach Textur 0 überblendet werden sollte, dann wurde über den den Shader die Textur 1 mit hinein interpoliert, obwohl der Übergang eigentlich nur zwischen den Texturen 2 und 0 stattfinden sollte...

Ich habe das Projekt und den Shader nun so umgestellt, daß alle Vertexfarben eines Tiles auf die gleiche Farbe gesetzt werden, damit wird im Fragmentshader nur zwischen 2 gleichen Werten interpoliert (was egal ist). Ein Tile hält dabei die aktuelle Farbe und ein angrenzendes Teil definiert quasi den Übergang.

Die Programmlogik der C#-Klasse und der Shader ist einfacher als vorher.

Ein Tile muss mindestens 8 Faces (2x2) haben, damit ein Übergang gezeichnet werden kann. 

Ein Problem bleibt immer noch bestehen, der Übergang zwischen Horizontal und Vertical.

Hier ist das Ergebnis:

Variante 1) Hier werden die vertikalen und horizontalen Texturen einfach zusammengemischt: lerp(mixedColorU, mixedColorV, 0.5);

0ERQE8d.png

 

Variante 2: "Dependent Mix" (je nachdem ob Vertikal oder Horizontal überhaupt Texturänderungen vorhanden sind)

YxUh5pc.png

Beide Varianten haben immer ein Problem mit Kanten, da beim Überblenden nur in 4 Richtungen geschaut wird. Ich vermute, wenn man in alle 8 Richtungen schauen würde, dann könnte man diese Kanten zumindest abschwächen. Allerdings haben wir für die 4 Richtungen bereits die 4 Farbkanäle der Vertexfarben verbraucht. Man müsste die 8 Richtungen nun in diese 4 Farbkanäle verpacken.

Aber ich sehe dieses Ergebnis zumindest als Teilerfolg, da nun zumindest die Texturen extrem sauber überblendet werden.

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

So letztes Ergebnis, ich glaube die Grenzen dieser Technik sind erreicht. Ich habe noch einmal die Kanten verbessert über die Zuweisungen der Vertexfarben der Tiles, aber wie man sieht macht sich die Überblendung der Faces als "Muster" bemerkbar. Der "diagonale" Übergang wird nun aber beachtet. Ich denke man sollte auch noch einmal nach einer Lösung forschen, die die Überblendung über die UVs der Texturen realisiert. Ich dachte das Überblenden über die Kanten der Faces wäre ein idealer Weg, aber bei zu vielen verschiedenen Nachbarn wird die Überblendung etwas "kantig", da eben entlang der Facevertices überblendet wird.

Np5xlqP.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo zusammen, 

hier ist mal ein kurzes Update von mir was ich aktuell hinbekomme. 
T0cHU40m.png

Die vertikalen Balken kommen noch durch eine nicht optimale BlendMaske. Aber das soll ja hier nicht das Problem sein. 
Jetzt geht es ans optimieren und verbessern von dem ganzen. Folgende Punkte gilt es noch zu betrachten:

  • Aktuell nur 1 BlendMaske für die ganze Tile Map möglich.
  • Es werden aktuell nur die Nachbarn (Rechts, links, oben und unten) betrachtet, jedoch nicht die diagonalen.
  • Der Ansatz benötigt noch zu viel Ressourcen ( Alle 4 UV Kanäle). Da dafür 3 Kanäle (2 für UVs, 1 für Nachbartexturen) für die Berechnung der richtigen Blendmaske benötigt werden, hoffe ich das man diese Berechnungen evtl. irgendwie im Shader durchführen kann. Da bin ich aber noch am experimentieren.

Sobald ich eine Lösung für die Punkte gefunden habe schreibe ich hier wieder. Falls jemand dazu Ideen hat gerne einen Kommentar hier lassen.
Danke und einen schönen Sonntag.

Stefan

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Die Probleme gingen bei mir erst los, sobald man Treppen und Ecken im Testmuster hat. Nur die vertikalen und horizontalen Übergange zu betrachten führt zu falschen Lösungsansätzen. Ich hab dir ja eine Mail geschrieben, wie man das Ganze umsetzen kann. Man benötigt entweder eine Blendmaske je Textur (diese Maske geht dann über alle Tiles) oder man macht es ohne Blendmasken und verwendet einen Blendshader der über die Vertexcolors (und UV2, UV3) geht.

So hier nun beispielhaft das Ergebnis des Lösungsansatzes 2. Die Texturen der Tiles werden als Vertexfarben codiert. Zusätzlich wurde der UV2-Kanal mit verwendet. Insgesamt kann man so nun 6(7) Texturen überblenden. Diese Technik kann die Probleme mit den Treppen und Kanten elegant lösen. Jeder Pixel des Originalbildes ist ein Tile und wird gleichmäßig überblendet. Man kann diese Lösung auf beliebig viele Texturen erweitern, dazu muss man allerdings ein Array an den Shader mit Texturdaten je Vertex übergeben.

S3AOjYz.png

Folgende Eckdaten der Technik:

  • keinerlei Blendmasken
  • es werden alle 8 Nachbarn beachtet
  • Verwendung der Vertexfarben des Meshes und UV2
  • der Shader ließt alle Texturen im Fragmentshader aus und vermischt diese anhand der Vertexfarben und UV2 (Performanceimpact sehr gering)

Nachteile:

  • Überblendung erfolgt immer Linear
  • keine spezielle Überblendungsmaske zwischen 2 Texturen möglich


Ergebnis mit "echten Texturen":

decXAuH.jpg


Aus der Nähe:

ymCOT5I.jpg

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...