Jump to content
Unity Insider Forum

Farbe eines Terrains an Punkt XY auslesen


Zer0Cool

Recommended Posts

Heute mal wieder eine kleine Helferklasse, die die Farbe des aktuellen Terrains an einem bestimmten Punkt auslesen kann. Ich verwende diese Klassen aktuell für einen Gras-Shader, aber ich denke auch andere Einsatzmöglichkeiten sind denkbar. Der Gras-Shader verwendet die ermittelte Farbe für eine Überblendung der Grasfarbe vom Boden. Da das Auslesen der Terrainfarbe an einem bestimmten Punkt mit einigen Hindernissen verbunden war,  dachte ich mir, ich stelle diese Hilfsklasse der Community zur Verfügung.

Die Klasse bestimmt entweder die Farbe der dominanten Terraintextur (schneller) oder ermittelt den Texturmix über alle Texturen (langsamer) an Punkt XY. Die Klasse ist beim Zugriff auf die Texturen optimiert und verbraucht daher Speicher in Größenordnung der "gefundenen" Terraintexturen. Zudem wurde durchgehend Color32 verwendet.

Beispielverwendung der Klasse:
1) Klasse in die Szene ziehen
2) Mit der Maus auf den Terrainboden clicken
3) Die ermittelte Farbe wird im Inspektor des Skriptes im Feld "currentDominantColor" oder ""currentMixedColor"" angezeigt.

Sollte noch jemand einen Fehler finden, bitte melden. Ein Test der Klasse gestaltet sich schwierig ;)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//
// Klasse zum Auslesen der dominanten Texturfarbe eines Terrains.
// (c) by Zer0Cool for the Unity Insider Forum (forum.unity-community.de)
// Skype: zer0f0rce
// Discord: zer0f0rce #8769
//
// Eckdaten:
// Die Terraintexturen müssen nicht Read/Write enabled sein und wiederholte Zugriffe auf die Texturen werden gecached.
//
// Hintergrund:
// Diese Klasse kann beispielsweise dafür verwendet werden, um kleineren Objekten auf dem Terrain (über einen Shader) 
// eine Farbüberblendung vom Terrainboden zu ermöglichen.
//

public class ProbeTerrainTexture : MonoBehaviour {

    public Color32 currentDominantColor;
    public Color32 currentMixedColor;

    private Terrain terrain;
    private TerrainData terrainData;
    private Vector3 terrainPos;

    struct TextureData
    {
        public Color32[] pixels;
        public int width;
        public int height;
    }

    private Dictionary<string, TextureData> textureCache;

    public void SetTerrain(Terrain terrain)
    {
        this.terrain = terrain;
        terrainData = terrain.terrainData;
        terrainPos = terrain.transform.position;
        textureCache = new Dictionary<string, TextureData>();
    }

    void Start()
    {
        SetTerrain(Terrain.activeTerrain);
    }

    void Update()
    {
        if (!Input.GetMouseButtonDown(0))
            return;

        RaycastHit hit;
        if (!Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
            return;

        currentDominantColor = ReadDominantTerrainColor(hit);
        currentMixedColor = ReadMixedTerrainColor(hit);
    }

    public Color32 ReadMixedTerrainColor(RaycastHit hit)
    {
        float[] allTextures = GetAllTextures(hit.point);

        Color32 mixedDiffuse = new Color32(0, 0, 0, 0);
        // Loop all alphamaps
        for (int i=0; i < terrainData.alphamapLayers; i++)
        {
            Color32 col = ReadTerrainColor(i, hit.textureCoord);
            float weight = allTextures[i];
            mixedDiffuse.r = (byte)(mixedDiffuse.r + (byte)((float)(col.r) * weight));
            mixedDiffuse.g = (byte)(mixedDiffuse.g + (byte)((float)(col.g) * weight));
            mixedDiffuse.b = (byte)(mixedDiffuse.b + (byte)((float)(col.b) * weight));
            mixedDiffuse.a = (byte)(mixedDiffuse.a + (byte)((float)(col.a) * weight));
        }

        return mixedDiffuse;
    }

    public Color32 ReadDominantTerrainColor(RaycastHit hit)
    {
        int dominantTexture = GetMainTexture(hit.point);
        return ReadTerrainColor(dominantTexture, hit.textureCoord);
    }

    public Color32 ReadTerrainColor(int splatPrototypeIndex, Vector2 uv)
    {
        SplatPrototype splatPrototype = terrainData.splatPrototypes[splatPrototypeIndex];
        Texture2D tex = splatPrototype.texture;

        Vector2 terrainUV = uv;
        Vector2 tileSize = splatPrototype.tileSize;
        Vector2 uvTiling = new Vector2(terrainData.size.x / tileSize.x, terrainData.size.z / tileSize.y); // 100 / 15 = 6.666

        Vector2 realUV = new Vector2((terrainUV.x * uvTiling.x) % 1, (terrainUV.y * uvTiling.y) % 1);

        realUV.x *= tex.width;
        realUV.y *= tex.height;

        return ReadTexturePixel(tex, (int)realUV.x, (int)realUV.y);
    }

    private Color32 ReadTexturePixel(Texture2D source, int x, int y)
    {
        TextureData data = new TextureData();
        if (!textureCache.ContainsKey(source.name))
        {
            source.filterMode = FilterMode.Point;
            RenderTexture rt = RenderTexture.GetTemporary(source.width, source.height);
            rt.filterMode = FilterMode.Point;
            RenderTexture.active = rt;
            Graphics.Blit(source, rt);

            Texture2D nTex = new Texture2D(source.width, source.height);
            nTex.ReadPixels(new Rect(0, 0, source.width, source.height), 0, 0);
            nTex.Apply();
            Color32[] pixels = nTex.GetPixels32(0);
            data.pixels = pixels;
            data.width = source.width;
            data.height = source.height;
            textureCache.Add(source.name, data);
            RenderTexture.active = null;
            RenderTexture.ReleaseTemporary(rt);
        }

        data = textureCache[source.name];
        Color32 pix = data.pixels[(data.width * y) + x];
        return pix;
    }

    private float[] GetTextureMix(Vector3 WorldPos)
    {
        // returns an array containing the relative mix of textures
        // on the main terrain at this world position.

        // The number of values in the array will equal the number
        // of textures added to the terrain.

        // calculate which splat map cell the worldPos falls within (ignoring y)
        int mapX = (int)(((WorldPos.x - terrainPos.x) / terrainData.size.x) * terrainData.alphamapWidth);
        int mapZ = (int)(((WorldPos.z - terrainPos.z) / terrainData.size.z) * terrainData.alphamapHeight);

        // get the splat data for this cell as a 1x1xN 3d array (where N = number of textures)
        float[,,] splatmapData = terrainData.GetAlphamaps(mapX, mapZ, 1, 1);

        // extract the 3D array data to a 1D array:
        float[] cellMix = new float[splatmapData.GetUpperBound(2) + 1];

        for (int n = 0; n < cellMix.Length; n++)
        {
            cellMix[n] = splatmapData[0, 0, n];
        }
        return cellMix;
    }

    private int GetMainTexture(Vector3 WorldPos)
    {
        // returns the zero-based index of the most dominant texture
        // on the main terrain at this world position.
        float[] mix = GetTextureMix(WorldPos);

        float maxMix = 0;
        int maxIndex = 0;

        // loop through each mix value and find the maximum
        for (int n = 0; n < mix.Length; n++)
        {
            if (mix[n] > maxMix)
            {
                maxIndex = n;
                maxMix = mix[n];
            }
        }
        return maxIndex;
    }

    private float[] GetAllTextures(Vector3 WorldPos)
    {
        // returns all splatmap textures at world position.
        return GetTextureMix(WorldPos); 
    }

}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe die Klasse noch einmal erweitert. Sie kann nun auch den "Farbmix" über mehrere Texturen des Terrains liefern. Siehe Methode "ReadMixedTerrainColor".
Die Methode "ReadDominantTerrainColor" liefert die Farbe der dominanten (größter Anteil) Textur des Terrains. Diese Methode liefert schneller einen Wert zurück, da sie nur eine Textur auslesen muss.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...