Jump to content
Unity Insider Forum

Mass Place Trees/Grass/Rocks etc.


MustafGames

Recommended Posts

Gruß,

kennt jemand von euch ein Editor Tool, mit dem ich auf z.b. einer Plane verschiedene Prefabs (Bäume, Felsen, Grass) platzieren kann, ich möchte die Dinge nicht per Terrain platzieren, da ich ihnen dann keinen Script zuweisen kann. Suche praktisch nach der Mass Place Methode vom Terrain nur für alle Prefabs.

 

Mfg Mustaf.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dieses Tool machte für mich einen gute Eindruck, habe aber noch nicht damit gearbeitet:
https://assetstore.unity.com/packages/tools/painting/meshbrush-14453

Ansonsten, wenn es um (das Generieren von) größere Flächen geht und nicht um den "Feinschliff" kann ich nur Gaia empfehlen.

Größere Grassflächen würde ich aber nicht über solch ein Tool platzieren, das zerstört die Performance, daher bin ich ja auch dabei hierfür eine eigene Lösung zu entwickeln (es gibt aber auch schon einige neue Assets im Store die den gleichen Weg gegangen sind). Insofern Bäume gute LODs haben kann man diese damit platzieren, aber auch hier sollte man bei größeren Mengen an Bäumen aufpassen. Unity verbrennt einfach zur viel Performance für ein einzelnes GameObject, hier sollte man dann über die GPU instanziieren.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja, das Tool ist recht umfangreich, bietet dafür aber auch (wenn ich mich recht erinnere) eine Funktion zum Kombinieren der Objektdetails. Das kann man zwar auch per Hand machen, aber so ist es doch bequemer.

Gras solltest du keinesfalls als einzelne GameObjekts platzieren (es sein denn es sind man 10 Grasbüschel und und 10 Grasbüschel da), auch mit LODs nicht, jedes einzelne GameObjekt "konsumiert" (laut meinen Messungen) einiges an CPU-Zeit, ich habe ab 100 Objekten (+ mittelgroßer Meshdaten) erhebliche Einbrüche in der Performance erlebt. 
Man kann das Problem zwar deutlich Abschwächen, indem man die Objekte in einem Mesh kombiniert, aber wie gesagt, ich hatte damit selbst bei nur "50 - 100" kombinierten Meshes bereits deutliche FPS-Einbrüche. Die "Gras-Meshdaten" die das Unityterrain intern erzeugt waren im Gegensatz dazu deutlich performanter, da hier wohl nicht der Umweg über ein Gameobjekt gegangen werden muss. Ich hatte allerdings bei meinem Messungen damals nicht bedacht, daß die Unity "Gras-Meshes" keine Schatten werfen und daher die Performance meiner  kombinierten Grasmeshes um 2/3 schlechter war, daher kann es sein, daß kombinierte Meshdaten noch im Rahmen bleiben. 
Ansonsten war laut meinen Messungen die schnellste Methode ein Instanced Indirect Shader, der die Grasmeshes direkt auf der CPU instanziert. Dieser benötigt quasi 1 DC (je LOD und Material) und erzeugt dann innerhalb der GPU sämtliche Meshinstanzen.
 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Unter meinen Shadern ist GPU Instancing enabled.

https://docs.unity3d.com/Manual/GPUInstancing.html

Wie kann ich das hier verstehen?

"Adding per-instance data"

 

Bräuchte ich so etwas, wenn ich für mein Spiel nur 1 Texture und 5 Materialien mit GPU Instancing brauche?

Die 5 Materialien kommen davon:

- Normal

- Leuchtend

- Metallisch

- Schwenkend

- Transparent (Glas)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Der Haken bedeutet erst einmal nur, daß Unity den Shader für Instancing ansteuert. Der Shader selbst muss aber zudem für Instancing ausgelegt sein. Der Shader muss dabei einzelne Attribute (instanziierte Attribute) wie Position, Farbe speziell verarbeiten, leider kann man dies nicht erkennen ohne in den Shadercode zu schauen. Der Standardshader unterstützt beispielsweise von Haus aus keinerlei Attribute, hierfür muss man dann einen Surface-Shader vom Standardshader ableiten und die Merkmale im Shadercode ausprogrammieren.

Als 2. Voraussetzung, damit das Instancing funktioniert, muss man nun zusätzlich die instanziierten Attribute (des Shaders) per Skriptcode mit Daten befüllen (siehe MaterialPropertyBlock). Damit ist "adding per instance data" gemeint.

Und ja, man kann auch Texturen als instanziiertes Attribut verwenden und damit einem Objekt mit einem Material  5 verschiedene Texturen verpassen (was wieder DCs einspart). Damit hättest du nicht mehr 5 Materialien, sondern 1 Material mit einem Shader der Instancing unterstützt. Voraussetzung ist natürlich immer, daß sich die Objekte den gleichen Mesh teilen...

Ergänzend sei noch gesagt, daß das oben beschriebene "einfache" Instancing immer noch langsamer ist als "Instancing Indirekt", aber das führe ich jetzt nicht weiter aus, daß würde hier den Rahmen sprengen. Dies ist die Technik die ich für meine Grasshaderlösung verwende. Es gibt aber bereits einige Assets im Store die den gleichen Weg gegangen sind.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Kannst du mir da ein Beispiel zeigen mit dem Instancing Indirekt?

 

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

Shader "Custom/Grass/GrassBending" {
	Properties{
		_Color("Main Color", Color) = (1,1,1,1)
		_MainTex("Base (RGB) Trans (A)", 2D) = "white" {}
	_Cutoff("Alpha cutoff", Range(0,1)) = 0.5
		_ShakeDisplacement("Displacement", Range(0, 1.0)) = 1.0
		_ShakeTime("Shake Time", Range(0, 1.0)) = 1.0
		_ShakeWindspeed("Shake Windspeed", Range(0, 1.0)) = 1.0
		_ShakeBending("Shake Bending", Range(0, 1.0)) = 1.0
	}
		SubShader{
		Tags{ "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" }
		LOD 200
		Cull Off

		CGPROGRAM
#pragma target 3.0
#pragma surface surf Lambert alphatest:_Cutoff vertex:vert addshadow
		sampler2D _MainTex;
	fixed4 _Color;
	float _ShakeDisplacement;
	float _ShakeTime;
	float _ShakeWindspeed;
	float _ShakeBending;
	float dist;
	float _GrassAlpha;
	struct Input {
		float2 uv_MainTex;
		float3 worldPos;
	};
	void FastSinCos(float4 val, out float4 s, out float4 c) {
		val = val * 6.408849 - 3.1415927;
		float4 r5 = val * val;
		float4 r6 = r5 * r5;
		float4 r7 = r6 * r5;
		float4 r8 = r6 * r5;
		float4 r1 = r5 * val;
		float4 r2 = r1 * r5;
		float4 r3 = r2 * r5;
		float4 sin7 = { 1, -0.16161616, 0.0083333, -0.00019841 };
		float4 cos8 = { -0.5, 0.041666666, -0.0013888889, 0.000024801587 };
		s = val + r1 * sin7.y + r2 * sin7.z + r3 * sin7.w;
		c = 1 + r5 * cos8.x + r6 * cos8.y + r7 * cos8.z + r8 * cos8.w;
	}
	void vert(inout appdata_full v) {

		float factor = (1 - _ShakeDisplacement - v.color.r) * 0.5;

		const float _WindSpeed = (_ShakeWindspeed + v.color.g);
		const float _WaveScale = _ShakeDisplacement;

		const float4 _waveXSize = float4(0.048, 0.06, 0.24, 0.096);
		const float4 _waveZSize = float4 (0.024, .08, 0.08, 0.2);
		const float4 waveSpeed = float4 (1.2, 2, 1.6, 4.8);
		float4 _waveXmove = float4(0.024, 0.04, -0.12, 0.096);
		float4 _waveZmove = float4 (0.006, .02, -0.02, 0.1);

		float4 waves;
		waves = v.vertex.x * _waveXSize;
		waves += v.vertex.z * _waveZSize;
		waves += _Time.x * (1 - _ShakeTime * 2 - v.color.b) * waveSpeed *_WindSpeed;
		float4 s, c;
		waves = frac(waves);
		FastSinCos(waves, s,c);
		float waveAmount = v.texcoord.y * (v.color.a + _ShakeBending);
		s *= waveAmount;
		s *= normalize(waveSpeed);
		s = s * s;
		float fade = dot(s, 1.3);
		s = s * s;
		float3 waveMove = float3 (0,0,0);
		waveMove.x = dot(s, _waveXmove);
		waveMove.z = dot(s, _waveZmove);
		v.vertex.xz -= mul((float3x3)unity_WorldToObject, waveMove).xz;

	}
	void surf(Input IN, inout SurfaceOutput o) {
		fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color * _Color;
		o.Albedo = c.rgb;
		float dist = distance(_WorldSpaceCameraPos, IN.worldPos);
		dist = clamp(dist / 300, 0.4, 1.0);
		o.Alpha = c.a - dist - _GrassAlpha;
	}
	ENDCG
	}
		Fallback "Transparent/Cutout/VertexLit"
}

Mit diesem Shader, kann ich auch die Enable GPU Instancing Checkbox ausfüllen, es ändert sich auch die Performance dadurch obwohl ich im Shader nichts darüber finden kann?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dieser Shader unterstützt kein Instancing, auch kein Instancing indirekt. Die Performance verändert sich vermutlich aufgrund eines veränderten Renderings. Wenn du beispielsweise vorher einen Transparent-Shader mit Blending verwendet hast verbraucht dieser mehr Performance, der obige Shader macht einen "Cutout" und dieser ist wesentlich schneller.

Einen Shader der "normales" Instancing (oder auch direktes Instancing) unterstützt erkennst du so im Shadercode: 

    UNITY_INSTANCING_BUFFER_START(Props)
       UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)  // <= Diese Zeile definiert eine Property die instanziiert wird! In diesem Fall die Farbe.
    UNITY_INSTANCING_BUFFER_END(Props)


Einen Shader der mit Instancing indirect arbeitet erkennst an diesem Codeschnipsel:

 void setup()
        {
        #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
          ...
          // An dieser Stelle übergibt der Shader entweder Matrizdaten oder er berechnet diese Matrizen aus Position / Scale und Rotation
          // Der Shader benötigt an dieser Stelle die Daten der Matrizen für jede einzelne Instanz. 
          // Die benötigten Matrizen je Instanz sind unity_WorldToObject und unity_ObjectToWorld
          ...
        #endif
        }

Was sind die Unterschiede zwischen Instancing (direct) und Instancing indirect? Ich habe mal eine kleine Liste erstellt nach meinem Kenntnisstand (eine 100%tige Trennung ist schwierig, da sie beide Methoden teilweise überschneiden):

  • Performance
    Instancing indirect ist schneller, da keine Gameobjekte in der Szene verwendet werden und daher der GameObjekt-Overhead entfällt
  • Instancing direct ist einfacher anzuwenden, da dem Shader keine Matrizen übergeben werden müssen
    die Objekte sind in der Unityszene vorhanden und werden über Unity gebatched und an den Instancing Shader als Batch übergeben (man sieht den Performancevorteil an den sinkenden DCs im Debugfenster)
  • Instancing direct arbeitet mit vorhandenen GameObjekten innerhalb der Unityszene oder mit Graphics.DrawMeshInstanced
    Die Instanzdaten kommen also entweder aus den GameObjekten innerhalb der Szene oder werden über Graphics.DrawMeshInstanced übergeben
  • Instancing indirect arbeitet ohne GameObjekte in der Unityszene
    Die Instanzdaten müssen per Skript erzeugt werden und werden über Graphics.DrawMeshInstancedIndirect gerendert
  • Beim Instancing direct ist die Anzahl der Instanzen pro DC auf 1023 Instanzen beschränkt (siehe Graphics.DrawMeshInstanced)

Beispiel Instancing direct:
https://docs.unity3d.com/Manual/GPUInstancing.html

Beispiel Instancing indirect:
https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstancedIndirect.html

Anmerkung:
Sobald man ohne GameObjekte innerhalb der Szene arbeitet entfallen Frustum Culling (Kamera Culling) und Occlusion Culling. Der Grund ist einfach, beide Features benötigen GameObjekte und sobald diese nicht mehr verwendet werden, muss man sich selbst um das Culling kümmern (dieses macht dann richtig Arbeit bei einer eigenen Grasshaderlösung)

Wenn du unbedingt (wovon ich allerdings abraten würde) jedem Grashalm ein Skript zuweisen musst wäre die beste Methode Instancing direkt mit GameObjekten in der Szene. Warum möchtest du jeder Grasinstanz ein Skript zuweisen?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Es wäre einmal, die Interaktion, da man Gras abbauen können soll.

Für das Gras habe ich oben diesen schwankenden Shader, den der sieht wesentlich besser aus als wenn das Gras nur so rumsteht (Terrain fügt keinen Wind hinzu wenn man das Gras nicht als Quad haben möchte).

 

PS: Was bedeutet das fixed4, wenn ich jetzt die Color-Variable und die MainTex (was eher wichtig ist) instanzieren möchte, kann ich für beide einfach fixed4 setzen?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Leider ist mein Grasshader noch nicht fertig (es fehlt noch ein Tool zum Platzieren von Gras und ich wollte auch noch Occlusion Culling einbauen), sonst könnte ich dir hier leicht sowas einbauen. Wenn du noch etwas warten kannst, ich denke mein Grassshader ist in 1-2 Monaten soweit.

Ansonsten würde ich hier einen kleinen Trick anwenden und erst einmal "leichtere Variante" also Instanzing Direct verwenden (die Performance ist dann vielleicht nicht optimal aber annehmbar).

Ich würde das Gras welches nicht in der Nähe des Spielers ist per Instancing ohne Gameobjekte darstellen und die Positionen per Code an Graphics.DrawMeshInstanced übergeben. Du kannst allerdings hier immer nur 1023 Graspositionen auf einmal übergeben! Das Gras in der Nähe des Spielers würde ich dabei aussparen und dieses Gras dann als GameObjekte darstellen, aber auch mit Instancing. Damit kannst du dann alles mögliche mit diesem Gras in der Nähes des Spieler anstellen wie Collider verpassen, Skripte verpassen oder sonstiges.

Hier ein Beispielcode für das nicht interaktive Gras mit Rendering über Instanzing Direct:

 List<Matrix4x4> transformList = new List<Matrix4x4>();

        Mesh cubeMesh;
        Material cubeMaterial;

        //These for loops create offsets from the position at which you want to draw your cube built from cubes.
        for(int x = -1; x < 1; x++)
        {
            for(int y = -1; y < 1; y++)
            {
                for(int z = -1; x < 1; z++)
                {
                    //We will assume you want to create your cube of cubes at 0,0,0
                    Vector3 position = new Vector3(0, 0, 0);

                    //Take the origin position, and apply the offsets
                    position.x += x;
                    position.y += y;
                    position.z += z;

                    //Create a matrix for the position created from this iteration of the loop
                    Matrix4x4 matrix = new Matrix4x4();

                    //Set the position/rotation/scale for this matrix
                    matrix.SetTRS(position, Quaternion.Euler(Vector3.zero), Vector3.one);

                    //Add the matrix to the list, which will be used when we use DrawMeshInstanced.
                    transformList.Add(matrix);
                }
            }
        }
        //After the for loops are finished, and transformList has several matrices in it, simply pass DrawMeshInstanced the mesh, a material, and the list of matrices containing all positional info.
        Graphics.DrawMeshInstanced(cubeMesh, 0, cubeMaterial, transformList);


Nun musst du nur noch entscheiden, welche Eigenschaften des Grases zusätzlich variieren sollen. Die Postion des Grases wird dabei über die GameObjekte in der Szene bestimmt (Nahgras) oder per Code (siehe oben) für Ferngras.
Normalerweise variiert man die Farbe oder eben auch die Texturen per Grassinstanz. Ich kann dir gerne einen Shader erstellen (aus obigem Code), mit welchen du diese beiden Properties variieren kannst.
 

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 29 Minuten schrieb MustafGames:

Es wäre einmal, die Interaktion, da man Gras abbauen können soll.

Für das Gras habe ich oben diesen schwankenden Shader, den der sieht wesentlich besser aus als wenn das Gras nur so rumsteht (Terrain fügt keinen Wind hinzu wenn man das Gras nicht als Quad haben möchte).

 

PS: Was bedeutet das fixed4, wenn ich jetzt die Color-Variable und die MainTex (was eher wichtig ist) instanzieren möchte, kann ich für beide einfach fixed4 setzen?

Die Farbe wird an das Material so übergeben. "fixed4" steht für einen Vector mit 4 Floats was gleichbedeutend mit einer Color in Unity ist.

MaterialPropertyBlock props = new MaterialPropertyBlock();
MeshRenderer renderer;

foreach (GameObject obj in objects)
{
   float r = Random.Range(0.0f, 1.0f);
   float g = Random.Range(0.0f, 1.0f);
   float b = Random.Range(0.0f, 1.0f);
   props.SetColor("_Color", new Color(r, g, b));
   
   renderer = obj.GetComponent<MeshRenderer>();
   renderer.SetPropertyBlock(props);
}

Ich kann dir gerne einmal ein kleines Demoprojekt erstellen, wenn dir das alles zu verwirrend ist, damit du einen Startpunkt hast.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Danke für die schnellen Antworten,

im Moment bastel ich ein EditorScript mit dem ich dann auf ein Terrain klicke und Bäume/Felsen/Gras dann platziert wird, im Verhältnis welches ich einstelle.

Diese Objekte sind alle static (Occlusion Culling etc.), sie bekommen alle ein LOD, damit ferne Objekte nicht voll geladen werden, der Spieler dürfte dann nicht so viel Gras sehen das die Perfomance den Bach runter geht. Ich bräuchte eigentlich nur das Direkte Instanzieren in meinem Shader oben, hauptsächlich müsste die MainTex und Color (Color habe ich bereits) gemacht werden.

 

Das mit den fixed4 hatte ich gelesen, konnte mir nur dabei nicht erklären wie man das für andere Variablen machen soll.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Cool,

bin gerade soweit mit dem EditorTool (wo man Gras, Bäume...) platzieren kann in einem Bereich (Radius) und die Anzahl bestimmen kann.

Wenn ich etwas weiter bin schreib ich den mal hier rein.

Problem ist, es kommt ab und zu vor das Objekte ineinander platziert werden und größere Objekte (z.b. Felsen welche größer als Gras sind) verdecken das Gras dann.

Muss also noch Prioritäten setzen das an der Stelle wo man einen Felsen platziert, das Gras verschwindet und auch keine Objekte ineinander platziert werden, alles anderes klappt super.

Zufällige Rotationen sind auch dabei.

Zweites aber nebensächliches Problem ist das der Raycast Oben und Unten vertauscht.

Link zu diesem Kommentar
Auf anderen Seiten teilen

So, hier der Shader. Ich habe ihn noch etwas verbessert, daß alle Instanzen nicht gleich schwingen. Der Shader verwendet eigentlich Vertexfarben für die Instanzen, aber scheinbar wurde er umgeschrieben, so daß er auch ohne Vertexfarben funktioniert.

Shader "Custom/Grass/GrassBending" {
	Properties{
		_Color("Main Color", Color) = (1,1,1,1)
		//_MainTex("Base (RGB) Trans (A)", 2D) = "white" {}
		_MyArr("Tex", 2DArray) = "" {}

	_Cutoff("Alpha cutoff", Range(0,1)) = 0.5
		_ShakeDisplacement("Displacement", Range(0, 1.0)) = 1.0
		_ShakeTime("Shake Time", Range(0, 1.0)) = 1.0
		_ShakeWindspeed("Shake Windspeed", Range(0, 1.0)) = 1.0
		_ShakeBending("Shake Bending", Range(0, 1.0)) = 1.0
	}
		SubShader{
		Tags{ "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" }
		LOD 200
		Cull Off

		CGPROGRAM

// to use texture arrays we need to target DX10/OpenGLES3 which
// is shader model 3.5 minimum
#pragma target 3.5
#pragma surface surf Lambert alphatest:_Cutoff vertex:vert addshadow

    UNITY_DECLARE_TEX2DARRAY(_MyArr);

//	sampler2D _MainTex;
//	fixed4 _Color;
	float _ShakeDisplacement;
	float _ShakeTime;
	float _ShakeWindspeed;
	float _ShakeBending;
	float dist;
	float _GrassAlpha;
	
	struct Input {
		//float2 uv_MainTex;
		float2 uv_MyArr;
		float3 worldPos;
	};
	
	void FastSinCos(float4 val, out float4 s, out float4 c) {
		val = val * 6.408849 - 3.1415927;
		float4 r5 = val * val;
		float4 r6 = r5 * r5;
		float4 r7 = r6 * r5;
		float4 r8 = r6 * r5;
		float4 r1 = r5 * val;
		float4 r2 = r1 * r5;
		float4 r3 = r2 * r5;
		float4 sin7 = { 1, -0.16161616, 0.0083333, -0.00019841 };
		float4 cos8 = { -0.5, 0.041666666, -0.0013888889, 0.000024801587 };
		s = val + r1 * sin7.y + r2 * sin7.z + r3 * sin7.w;
		c = 1 + r5 * cos8.x + r6 * cos8.y + r7 * cos8.z + r8 * cos8.w;
	}
	
	// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
	// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
	// #pragma instancing_options assumeuniformscaling
	UNITY_INSTANCING_CBUFFER_START(Props)
		UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
		UNITY_DEFINE_INSTANCED_PROP(float, _TextureIndex)
		// put more per-instance properties here
	UNITY_INSTANCING_CBUFFER_END

	void vert(inout appdata_full v) {

		float factor = (1 - _ShakeDisplacement - v.color.r) * 0.5;

		const float _WindSpeed = (_ShakeWindspeed + v.color.g);
		const float _WaveScale = _ShakeDisplacement;

		const float4 _waveXSize = float4(0.048, 0.06, 0.24, 0.096);
		const float4 _waveZSize = float4 (0.024, .08, 0.08, 0.2);
		const float4 waveSpeed = float4 (1.2, 2, 1.6, 4.8);
		float4 _waveXmove = float4(0.024, 0.04, -0.12, 0.096);
		float4 _waveZmove = float4 (0.006, .02, -0.02, 0.1);

		float4 waves;
		waves = v.vertex.x * _waveXSize;
		waves += v.vertex.z * _waveZSize;
		
		// Worldkoordinaten berechen, um eine Zufallsfrequenz zu erzeugen
		float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
		
		waves += (_Time.x + worldPos.x + worldPos.y + worldPos.z) * (1 - _ShakeTime * 2 - v.color.b) * waveSpeed *_WindSpeed;
		float4 s, c;
		waves = frac(waves);
		FastSinCos(waves, s,c);
		float waveAmount = v.texcoord.y * (v.color.a + _ShakeBending);
		s *= waveAmount;
		s *= normalize(waveSpeed);
		s = s * s;
		float fade = dot(s, 1.3);
		s = s * s;
		float3 waveMove = float3 (0,0,0);
		waveMove.x = dot(s, _waveXmove);
		waveMove.z = dot(s, _waveZmove);
		v.vertex.xz -= mul((float3x3)unity_WorldToObject, waveMove).xz;

	}
	
	void surf(Input IN, inout SurfaceOutput o) {
		//fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(_Color);
		fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_MyArr, float3(IN.uv_MyArr, UNITY_ACCESS_INSTANCED_PROP(_TextureIndex))) * UNITY_ACCESS_INSTANCED_PROP(_Color);
		
		o.Albedo = c.rgb;
		float dist = distance(_WorldSpaceCameraPos, IN.worldPos);
		dist = clamp(dist / 300, 0.4, 1.0);
		o.Alpha = c.a - dist - _GrassAlpha;
	}
	ENDCG
	}
		Fallback "Transparent/Cutout/VertexLit"
}

Dieser Shader wird nun auf einem Material angewendet mit einem Textur-Array-Asset.

Das TexturArray-Asset kannst du überfolgende Klasse erzeugen.
Wichtig:
- die Arraygröße ist im Code auf 1024 festgelegt, d.h. alle Texturen brauchen eine Größe von 1024x1024
- die Anzahl der Texturen habe ich aktuell auf 2 festgelegt 
- alle Texturen müssen Read-Write-Enabled werden!
 

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

public class CreateTexture2DArray : EditorWindow{

    [MenuItem("GameObject/Create Texture Array")]
    static void Create()
    {
        string filename;

        // CHANGEME: Number of textures you want to add in the array
        int slices = 2;

        // See Texture2DArray in unity scripting API.
        Texture2DArray textureArray = new Texture2DArray(1024, 1024, slices, TextureFormat.RGBA32, false);

        for (int i = 0; i < slices; i++)
        {
            filename = EditorUtility.OpenFilePanel("Select Texture for Array", Application.dataPath, "png");
            filename = filename.Replace(Application.dataPath, "Assets");
            if (filename.Length != 0)
            {
                Texture2D tex = (Texture2D)AssetDatabase.LoadAssetAtPath(filename, typeof(Texture2D));
                Debug.Log("Loading " + filename);
                Debug.Log(tex.width);
                Debug.Log(tex.height);
                textureArray.SetPixels(tex.GetPixels(0), i, 0);
            }
        }
        textureArray.Apply();

        filename = EditorUtility.SaveFilePanel("Select name for texture array asset", Application.dataPath, "TextureArray", "asset");
        filename = filename.Replace(Application.dataPath, "Assets");
        if (filename.Length != 0)
        {
            AssetDatabase.CreateAsset(textureArray, filename);
            Debug.Log("Saved asset to " + filename);
        }
    }
}


Und zu guter Letzt noch ein Stück Code, wo du sehen kannst, wie man den Shader sowohl für Instancing mit GOs und ohne GOs ansteuert:

using System.Collections.Generic;
using UnityEngine;

public class InstancingTester : MonoBehaviour
{
    #region Public properties
    public bool withGOs = false;

    public bool debug = false;

    public GameObject prefab;

    public int instanceCount = 10000; // grass detail number

    public int seed;
    public Vector2 size;

    public float startHeight = 1000;
    public float grassOffset = 0.0f;
    #endregion

    #region Private properties
    private Mesh instanceMesh;
    private Material instanceMaterial;

    private bool updatePositions = false;
    private List<Matrix4x4> matrices;
    private MaterialPropertyBlock properties;
    private Vector4[] colors;
    private float[] textures;

    #endregion

    void Start()
    {
        instanceMesh = prefab.GetComponent<MeshFilter>().sharedMesh;
        instanceMaterial = prefab.GetComponent<Renderer>().sharedMaterial;
        if (withGOs) DrawMeshesWithGO();

    }

    void Update()
    {
        if (!withGOs)
        {
            // Update starting position buffer
            if (!updatePositions) UpdateBuffersDrawMeshInstanced();

            Graphics.DrawMeshInstanced(instanceMesh, 0, instanceMaterial, matrices, properties);
        }
    }
    
    void UpdateBuffersDrawMeshInstanced()
    {
        Random.InitState(seed);
        properties = new MaterialPropertyBlock();
        matrices = new List<Matrix4x4>(instanceCount);
        colors = new Vector4[instanceCount];
        textures = new float[instanceCount];
        for (int i = 0; i < instanceCount; i++)
        {
            Vector3 origin = transform.position;
            origin.y = startHeight;
            origin.x += size.x * Random.Range(-0.5f, 0.5f);
            origin.z += size.y * Random.Range(-0.5f, 0.5f);
            Ray ray = new Ray(origin, Vector3.down);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                origin = hit.point;
                origin.y += grassOffset;

                matrices.Add(Matrix4x4.TRS(origin, Quaternion.identity, Vector3.one));

                // Setze Random Farbe
                colors[i] = new Color(Random.value, Random.value, Random.value);

                // Setze Random Texturindex
                textures[i] = (float)Random.Range(0, 2);
            }
        }

        properties.SetVectorArray("_Color", colors);
        properties.SetFloatArray("_TextureIndex", textures);

        updatePositions = true;
    }

    void DrawMeshesWithGO()
    {
        properties = new MaterialPropertyBlock();
        for (int i = 0; i < instanceCount; i++)
        {
            Vector3 origin = transform.position;
            origin.y = startHeight;
            origin.x += size.x * Random.Range(-0.5f, 0.5f);
            origin.z += size.y * Random.Range(-0.5f, 0.5f);
            Ray ray = new Ray(origin, Vector3.down);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                origin = hit.point;
                origin.y += grassOffset;

                GameObject t = Instantiate(prefab);
                t.transform.localPosition = origin;

                t.transform.SetParent(transform);

                // Setze Random Farbe
                properties.SetColor("_Color", new Color(Random.value, Random.value, Random.value));

                // Setze Random Texturindex
                properties.SetFloat("_TextureIndex", (float)Random.Range(0,2));

                t.GetComponent<MeshRenderer>().SetPropertyBlock(properties);
            }
        }
    }

}


Der Testcode braucht einen Collider unter dem GameObjekt in der Szene. Dies kann ein Terrain oder ein Mesh sein. Die Instanzen werden per Zufall gesetzt und bekommen eine Zufallsfarbe und eine Zufallstextur. Der Vector size definiert die Größe der Fläche beispielsweise 10x10.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja habs jetzt erst verstanden wie das funktioniert, so ähnlich wie ein SpritePackage.

Kann man so etwas auch auf ein Terrain anwenden und wäre das sinnvoll?

 

Kann man die Texture Größe beliebig ändern, den meine ist 256x1 (sind nur Farben, die den UV zugeordnet werden, per externen Programm)?

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 36 Minuten schrieb MustafGames:

Ja habs jetzt erst verstanden wie das funktioniert, so ähnlich wie ein SpritePackage.

Kann man so etwas auch auf ein Terrain anwenden und wäre das sinnvoll?

Ich habe oben das Skript noch einmal verändert, da das Übergeben der Properties im Falle "ohne GOs" noch falsch war.

Du kannst das Ganze anwenden wie du lustig bist. Das Problem im Falle ohne GOs ist (also die nicht interaktiven Grasinstanzen), hier sind die Instanzen auf 1023 limitiert und daher müsstest du das obige Beispielskript erweitern, so das es die Gras-Details in Chunks von 1023 gerendert werden. Das bedeutet 1 DC für 1023 Instanzen. "Graphics.DrawMeshInstanced" kann leider nur 1023 Instanzen auf einmal rendern. 
"Normale Grasmengen" bewegen sich so um die 100000 Instanzen..., d.h. du würdest letztendlich 100 DCs für das Gras benötigen...

Das interaktive Gras würdest du dann mit GOs erzeugen. Hier würde ich einem quadratischen Bereich um den Spieler definierten.

Also Zusammenfassung:
- du berechnest Positionen für alle Grasinstanzen, diese können auch über ein Tool erzeugt werden
- diese Positionen werden entweder mit GO oder ohne GO gerendert (siehe Skript oben)
- die GOs keinesfalls jedes Frame neu erzeugen, sondern nur die Positionen umsetzen
- die Spielerposition definiert die Anzahl und Positionen der interaktiven Instanzen je Frame!
- interaktive Instanzen mit GOs werden um den Spieler herum gerendert (beispielsweise 5x5 Meter)
- alle anderen nicht interaktiven Grasinstanzen werden ohne GOs in Chunks von 1023 gerendert
- die interaktiven Grasinstanzen werden jedes Frame von nicht interaktiven Instanzen abgezogen

Besser wäre es evtl. zu tricksen, du kannst auch alle Grasinstanzen einfach ohne GOs rendern! und die interaktiven Grasinstanzen als leere GOs in die Scene packen.
Damit musst du das Array für die nicht interaktiven Instanzen nicht jedes Frame ändern  und das spart viel FPS!
Sobald der Spieler beispielsweise Grasdetails entfernt hat (modifiziert) hat,  werden diese aus dem Array für die nicht interaktiven Grasdetails entfernt.

Das Ganze ist leider nicht optimal, da eine Änderung am Array immer eine CPU Belastung auslöst, es wäre also günstig, die Änderung des Arrays für die Grasinstanzen nicht jedes Frame zu ändern.

Ein anderes Problem ist noch die pure Menge an Grasdetails. Bei "Graphics.DrawMeshInstanced"  werden immer alle Grasinstanzen übergeben, die gerendert werden sollen. Diese werden nicht über Distanz oder der Kamera gecullt. Dafür muss man selbst sorgen.
Eine Mögliche Lösung ohne Computeshader wäre sein Terrain in Chunks aufzuteilen und Chunks zu rendern die in Spielernähe sind. Jedes Chunk würde dabei seine Grasdetails kennen und diese Rendern. Entfernt sich ein Spieler zu weit vom Chunk wird dieser deaktiviert.

Aber wie du siehst, ist das Ganze dennoch recht komplex (auch wenn ich den Weg dargestellt habe), weshalb ich auch mein eigenes Gras-Shader-System gestartet. Zumal man das Ganze mit Computeshadern kombinieren kann und eben auch Instancing Indirekt verwenden kann.
 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das mit den Chunks hätte ich mir so auch gedacht.

Nochmal zum Shader selbst, einmal wäre die Grasbewegung jetzt anders als vorher, leider zu meinen Nachteil (das Gras besteht aus kleinen Würfeln), das verzerrt sich jetzt, vorher hat es sich nur grob bewegt, das hat aber ausgereicht (für meine Zwecke).

Die Texture muss ich irgendwie hinbekommen, den die UV's nutzen eine 256x1 Texture.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hab am Shader nur die Schwingfrequenz geändert, verzerren tut sich da eigentlich nix, daß muss dann ein anderes Problem sein, hast du den Mesh geändert? Hier der Shader ohne die Schwingfrequenzänderung, aber ich vermute das dies nicht das Problem war,

Vielleicht hattest du auch an den Variablen des Shaders gedreht und diese haben sich nun durch das erneute Einspielen des Shader zurückgesetzt ?

Shader "Custom/Grass/GrassBending" {
	Properties{
		_Color("Main Color", Color) = (1,1,1,1)
		//_MainTex("Base (RGB) Trans (A)", 2D) = "white" {}
		_MyArr("Tex", 2DArray) = "" {}

	_Cutoff("Alpha cutoff", Range(0,1)) = 0.5
		_ShakeDisplacement("Displacement", Range(0, 1.0)) = 1.0
		_ShakeTime("Shake Time", Range(0, 1.0)) = 1.0
		_ShakeWindspeed("Shake Windspeed", Range(0, 1.0)) = 1.0
		_ShakeBending("Shake Bending", Range(0, 1.0)) = 1.0
	}
		SubShader{
		Tags{ "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" }
		LOD 200
		Cull Off

		CGPROGRAM

// to use texture arrays we need to target DX10/OpenGLES3 which
// is shader model 3.5 minimum
#pragma target 3.5
#pragma surface surf Lambert alphatest:_Cutoff vertex:vert addshadow

    UNITY_DECLARE_TEX2DARRAY(_MyArr);

//	sampler2D _MainTex;
//	fixed4 _Color;
	float _ShakeDisplacement;
	float _ShakeTime;
	float _ShakeWindspeed;
	float _ShakeBending;
	float dist;
	float _GrassAlpha;
	
	struct Input {
		//float2 uv_MainTex;
		float2 uv_MyArr;
		float3 worldPos;
	};
	
	void FastSinCos(float4 val, out float4 s, out float4 c) {
		val = val * 6.408849 - 3.1415927;
		float4 r5 = val * val;
		float4 r6 = r5 * r5;
		float4 r7 = r6 * r5;
		float4 r8 = r6 * r5;
		float4 r1 = r5 * val;
		float4 r2 = r1 * r5;
		float4 r3 = r2 * r5;
		float4 sin7 = { 1, -0.16161616, 0.0083333, -0.00019841 };
		float4 cos8 = { -0.5, 0.041666666, -0.0013888889, 0.000024801587 };
		s = val + r1 * sin7.y + r2 * sin7.z + r3 * sin7.w;
		c = 1 + r5 * cos8.x + r6 * cos8.y + r7 * cos8.z + r8 * cos8.w;
	}
	
	// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
	// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
	// #pragma instancing_options assumeuniformscaling
	UNITY_INSTANCING_CBUFFER_START(Props)
		UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
		UNITY_DEFINE_INSTANCED_PROP(float, _TextureIndex)
		// put more per-instance properties here
	UNITY_INSTANCING_CBUFFER_END

	void vert(inout appdata_full v) {

		float factor = (1 - _ShakeDisplacement - v.color.r) * 0.5;

		const float _WindSpeed = (_ShakeWindspeed + v.color.g);
		const float _WaveScale = _ShakeDisplacement;

		const float4 _waveXSize = float4(0.048, 0.06, 0.24, 0.096);
		const float4 _waveZSize = float4 (0.024, .08, 0.08, 0.2);
		const float4 waveSpeed = float4 (1.2, 2, 1.6, 4.8);
		float4 _waveXmove = float4(0.024, 0.04, -0.12, 0.096);
		float4 _waveZmove = float4 (0.006, .02, -0.02, 0.1);

		float4 waves;
		waves = v.vertex.x * _waveXSize;
		waves += v.vertex.z * _waveZSize;
			
		waves += _Time.x * (1 - _ShakeTime * 2 - v.color.b) * waveSpeed *_WindSpeed;
		float4 s, c;
		waves = frac(waves);
		FastSinCos(waves, s,c);
		float waveAmount = v.texcoord.y * (v.color.a + _ShakeBending);
		s *= waveAmount;
		s *= normalize(waveSpeed);
		s = s * s;
		float fade = dot(s, 1.3);
		s = s * s;
		float3 waveMove = float3 (0,0,0);
		waveMove.x = dot(s, _waveXmove);
		waveMove.z = dot(s, _waveZmove);
		v.vertex.xz -= mul((float3x3)unity_WorldToObject, waveMove).xz;

	}
	
	void surf(Input IN, inout SurfaceOutput o) {
		//fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(_Color);
		fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_MyArr, float3(IN.uv_MyArr, UNITY_ACCESS_INSTANCED_PROP(_TextureIndex))) * UNITY_ACCESS_INSTANCED_PROP(_Color);
		
		o.Albedo = c.rgb;
		float dist = distance(_WorldSpaceCameraPos, IN.worldPos);
		dist = clamp(dist / 300, 0.4, 1.0);
		o.Alpha = c.a - dist - _GrassAlpha;
	}
	ENDCG
	}
		Fallback "Transparent/Cutout/VertexLit"
}


Die Textur kannst du hier ändern, 256x1 gibt es nicht, aber du kannst 256x256 einstellen:

/ See Texture2DArray in unity scripting API.
Texture2DArray textureArray = new Texture2DArray(256, 256, slices, TextureFormat.RGBA32, false);

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das mit der Texture konnte ich lösen indem ich sie einfach wiederholt habe (ergibt solche Längsstreifen), klappt, die Verzehrung überprüfe ich jetzt noch einmal an den Werten.

Die Performance ist um einiges angestiegen durch den neuen Shader :) Jetzt ist das letzte "normale" Material, das vom Terrain und die Skybox welche 4 Texturen nutzt.

 

Edit:

Vorher war Bending auf 0.5 jetzt hab ich es auf 0.1 damit es genauso aussieht.

 

Beim Tool sollte man erst die großen Objekte platzieren, dann kann man keine kleineren mehr verdecken, wenn man wenige Objekte in einem bestimmten Bereich platziert, ist die Wahrscheinlichkeit das sich welche ineinander platzieren sehr gering.

PS (Mein EditorTool zum platzieren von mehreren Dingen in einem bestimmten Bereich):

 

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

public class SpawnStructures : EditorWindow {

    [MenuItem("Custom/TerrainEditor")]
    static void Init () {
        SpawnStructures window = (SpawnStructures) EditorWindow.GetWindow(typeof(SpawnStructures));
        window.Show();
    }

    [Range(1, 100)]
    public int amount;

    [Range(1, 100)]
    public int list;

    public GameObject obj;
    public bool place;

    void OnEnable () {
        SceneView.onSceneGUIDelegate += OnSceneGUI;
    }
    void OnDisable () {
        SceneView.onSceneGUIDelegate -= OnSceneGUI;
    }

    public void OnSceneGUI (SceneView sceneView) {
        if (place) {
            if (Event.current.keyCode == KeyCode.Space && Event.current.type == EventType.KeyDown) {
                Ray ray = Camera.current.ScreenPointToRay(Event.current.mousePosition);
                RaycastHit hit = new RaycastHit();
                if (Physics.Raycast(ray, out hit, Mathf.Infinity)) {
                    for (int i = 0; i < amount; i++) {
                        Vector2 circle = Random.insideUnitCircle * list;
                        Vector3 pos = new Vector3(circle.x + hit.point.x, hit.point.y + 500, circle.y + hit.point.z);
                        Ray ray2 = new Ray(pos, Vector3.down);
                        RaycastHit hit2 = new RaycastHit();
                        if (Physics.Raycast(ray2, out hit2, Mathf.Infinity)) {
                            if (hit.transform.name == "Terrain") {
                                Quaternion rot = Quaternion.Euler(new Vector3(obj.transform.rotation.x, Random.Range(0, 360), obj.transform.rotation.z));
                                Instantiate(obj, hit2.point, rot, hit.transform);
                            }
                        }
                    }
                }
            }
        }
    }

    void OnGUI () {
        EditorGUILayout.Toggle("Placeing", place);
        amount = Mathf.Max(0, EditorGUILayout.DelayedIntField("Spawnamount", amount));

        list = Mathf.Max(0, EditorGUILayout.DelayedIntField("Spawnrange", list));
            obj = (GameObject) EditorGUILayout.ObjectField(obj, typeof(GameObject), true);
        if (GUILayout.Button("Place", GUILayout.Width(500))) {
            place = !place;
        }
    }
}

[System.Serializable]
public class TObject {
    public GameObject obj;
    public float prozent;
}
Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

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

×
×
  • Neu erstellen...