🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

297閲覧

Unity2D テクスチャブレンド

退会済みユーザー

退会済みユーザー

総合スコア0

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2019/04/11 15:13

前提・実現したいこと

メッシュに適応しているテクスチャをブレンドするシェーダーを作り、自然な見た目の地形表現をしたい。

該当のソースコード

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class World : MonoBehaviour 6{ 7 8 public int width; 9 public int height; 10 11 public Tile[,] tiles; 12 13 public Material material; 14 15 Mesh mesh; 16 17 public string seed; 18 public bool randomSeed; 19 20 public float frequency; 21 public float amplitude; 22 23 public float lacunarity; 24 public float persistance; 25 26 public int octaves; 27 28 Noise noise; 29 30 private void Awake() 31 { 32 if(randomSeed == true) 33 { 34 int value = Random.Range(-1000, 10000); 35 seed = value.ToString(); 36 } 37 38 noise = new Noise(seed.GetHashCode(), frequency, amplitude, lacunarity, persistance, octaves); 39 } 40 41 // Use this for initialization 42 void Start() 43 { 44 CreateTiles(); 45 GenerateMesh(); 46 } 47 48 // Update is called once per frame 49 void Update() 50 { 51 Graphics.DrawMesh(mesh, Vector3.zero, Quaternion.identity, material, 0); 52 } 53 54 void CreateTiles() 55 { 56 tiles = new Tile[width, height]; 57 58 float[,] noiseValues = noise.GetNoiseValues(width, height); 59 60 for (int i = 0; i < width; i++) 61 { 62 for (int j = 0; j < height; j++) 63 { 64 if(noiseValues[i,j] > 0.5f) 65 { 66 tiles[i, j] = new Tile(Tile.Type.Grass); 67 } 68 else 69 { 70 tiles[i, j] = new Tile(Tile.Type.Dirt); 71 } 72 } 73 } 74 } 75 76 void GenerateMesh() 77 { 78 MeshData data = new MeshData(tiles); 79 mesh = new Mesh(); 80 81 // 頂点インデックスのフォーマットを32ビット整数に変更する 82 mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; 83 84 mesh.vertices = data.vertices.ToArray(); 85 mesh.triangles = data.triangles.ToArray(); 86 mesh.uv = data.UVs.ToArray(); 87 mesh.RecalculateNormals(); 88 mesh.RecalculateBounds(); 89 } 90}

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class MeshData{ 6 7 public List<Vector3> vertices; 8 public List<int> triangles; 9 public List<Vector2> UVs; 10 11 public MeshData(Tile[,] data) 12 { 13 vertices = new List<Vector3>(); 14 triangles = new List<int>(); 15 UVs = new List<Vector2>(); 16 17 for(int i = 0; i < data.GetLength(0); i++) 18 { 19 for(int j = 0;j < data.GetLength(1); j++) 20 { 21 CreateSquare(data[i, j], i, j); 22 } 23 } 24 } 25 26 void CreateSquare(Tile tile,int x,int y) 27 { 28 vertices.Add(new Vector3(x + 0, y + 0)); 29 vertices.Add(new Vector3(x + 1, y + 0)); 30 vertices.Add(new Vector3(x + 0, y + 1)); 31 vertices.Add(new Vector3(x + 1, y + 1)); 32 33 triangles.Add(vertices.Count - 1); 34 triangles.Add(vertices.Count - 3); 35 triangles.Add(vertices.Count - 4); 36 37 triangles.Add(vertices.Count - 2); 38 triangles.Add(vertices.Count - 1); 39 triangles.Add(vertices.Count - 4); 40 41 UVs.AddRange(SpriteLoader.instance.GetTileUVs(tile)); 42 } 43 44} 45

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class Tile{ 6 7 public enum Type { Dirt,Grass } 8 public Type type; 9 10 public Tile(Type type) 11 { 12 this.type = type; 13 } 14} 15

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4 5public class Noise { 6 7 int seed; 8 9 float frequency; 10 float amplitude; 11 12 float lacunarity; 13 float persistance; 14 15 int octaves; 16 17 public Noise(int seed,float frequency,float amplitude,float lacunarity,float persistance,int octaves) 18 { 19 this.seed = seed; 20 this.frequency = frequency; 21 this.amplitude = amplitude; 22 this.lacunarity = lacunarity; 23 this.persistance = persistance; 24 this.octaves = octaves; 25 } 26 27 public float [,] GetNoiseValues(int width,int height) 28 { 29 float[,] noiseValues = new float[width, height]; 30 31 float max = 0f; 32 float min = float.MaxValue; 33 34 for(int i = 0; i < width; i++) 35 { 36 for(int j =0; j < height; j++) 37 { 38 noiseValues[i, j] = 0f; 39 40 float tempA = amplitude; 41 float tempF = frequency; 42 43 for(int k = 0;k < octaves; k++) 44 { 45 noiseValues[i, j] += Mathf.PerlinNoise((i + seed) / (float)width * frequency, j / (float)height * frequency) * amplitude; 46 frequency *= lacunarity; 47 amplitude *= persistance; 48 } 49 50 amplitude = tempA; 51 frequency = tempF; 52 53 if(noiseValues[i,j] > max) 54 { 55 max = noiseValues[i, j]; 56 } 57 58 if(noiseValues[i,j] < min) 59 { 60 min = noiseValues[i, j]; 61 } 62 } 63 } 64 65 for (int i = 0; i < width; i++) 66 { 67 for (int j = 0; j < height; j++) 68 { 69 70 noiseValues[i, j] = Mathf.InverseLerp(max, min, noiseValues[i, j]); 71 } 72 } 73 74 return noiseValues; 75 } 76} 77

C#

1using System.Collections; 2using System.Collections.Generic; 3using UnityEngine; 4using System.Linq; 5 6public class SpriteLoader : MonoBehaviour 7{ 8 9 public static SpriteLoader instance; 10 11 Dictionary<string, Vector2[]> tileUVMap; 12 13 // Use this for initialization 14 void Awake() 15 { 16 17 instance = this; 18 19 tileUVMap = new Dictionary<string, Vector2[]>(); 20 21 Sprite[] sprites = Resources.LoadAll<Sprite>("World Tiles"); 22 23 float imageWidth = 0f; 24 float imageHeight = 0f; 25 26 foreach (Sprite s in sprites) 27 { 28 29 if (s.rect.x + s.rect.width > imageWidth) 30 imageWidth = s.rect.x + s.rect.width; 31 32 if (s.rect.y + s.rect.height > imageHeight) 33 imageHeight = s.rect.y + s.rect.height; 34 } 35 36 foreach (Sprite s in sprites) 37 { 38 39 Vector2[] uvs = new Vector2[4]; 40 41 uvs[0] = new Vector2(s.rect.x / imageWidth, s.rect.y / imageHeight); 42 uvs[1] = new Vector2((s.rect.x + s.rect.width) / imageWidth, s.rect.y / imageHeight); 43 uvs[2] = new Vector2(s.rect.x / imageWidth, (s.rect.y + s.rect.height) / imageHeight); 44 uvs[3] = new Vector2((s.rect.x + s.rect.width) / imageWidth, (s.rect.y + s.rect.height) / imageHeight); 45 46 tileUVMap.Add(s.name, uvs); 47 48 //Debug.Log(s.name + " :" + uvs[0] + "," + uvs[1] + "," + uvs[2] + "," + uvs[3]); 49 } 50 51 } 52 53 // Update is called once per frame 54 void Update() 55 { 56 57 } 58 59 public Vector2[] GetTileUVs(Tile tile) 60 { 61 62 string key = tile.type.ToString(); 63 64 if (tileUVMap.ContainsKey(key) == true) 65 { 66 67 return tileUVMap[key]; 68 } 69 else 70 { 71 72 Debug.LogError("There is no UV map for tile type: " + key); 73 return tileUVMap["Void"]; 74 } 75 } 76} 77

試したこと

テクスチャのあるメッシュを生成しましたが、どうやれば目的のシェーダーを作れるのかが分かりませんでした。

イメージ説明
イメージ説明

理想↓
イメージ説明

回答お願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

たとえば、タイルおよびメッシュに前景・背景情報とそれらのブレンド率を追加して、描画時にはそれに基づいて前景と背景のグラフィックを混ぜ合わせて表示してやる方法が考えられるでしょう。

まず、Tileは下記のようにして...

C#

1public class Tile{ 2 3 public enum Type { Dirt, Grass } 4 public Type type; // ゲームロジックのための地形タイプ 5 public Type foregroundType; // 描画のための前景の地形タイプ 6 public Type backgroundType; // 描画のための背景の地形タイプ 7 public Vector4 blendWeights; // 描画のための前景・背景混合率(x、y、z、wが左下、右下、左上、右上の角に対応) 8 9 public Tile(Type type, Type foreground, Type background, Vector4 weights) 10 { 11 this.type = type; 12 this.foregroundType = foreground; 13 this.backgroundType = background; 14 this.blendWeights = weights; 15 } 16}

WorldCreateTilesは下記のように変え...

C#

1void CreateTiles() 2{ 3 this.tiles = new Tile[this.width, this.height]; 4 5 // ノイズのサイズを縦横1だけ広げて、それをタイル中心ではなくタイル四隅における値として利用する 6 float[,] noiseValues = this.noise.GetNoiseValues(this.width + 1, this.height + 1); 7 8 for (int i = 0; i < this.width; i++) 9 { 10 for (int j = 0; j < this.height; j++) 11 { 12 // 4点のノイズ値を取得して、それを四隅における前景・背景混合率とし... 13 Vector4 weights = new Vector4(noiseValues[i, j], noiseValues[i + 1, j], noiseValues[i, j + 1], noiseValues[i + 1, j + 1]); 14 15 // 4点の平均値を中心の値として、それをもとに地形タイプを決定し... 16 float averageWeight = Vector4.Dot(weights, Vector4.one) * 0.25f; 17 Tile.Type type = (averageWeight > 0.5f) ? Tile.Type.Grass : Tile.Type.Dirt; 18 19 // 前景を草地、背景を裸地としてタイルを生成する 20 this.tiles[i, j] = new Tile(type, Tile.Type.Grass, Tile.Type.Dirt, weights); 21 } 22 } 23}

SpriteLoaderGetTileUVsは、引数としてタイルではなく地形タイプを受け取るようにし...

C#

1public Vector2[] GetTileUVs(Tile.Type type) 2{ 3 4 string key = type.ToString(); 5 6 if (this.tileUVMap.ContainsKey(key) == true) 7 { 8 9 return this.tileUVMap[key]; 10 } 11 else 12 { 13 14 Debug.LogError("There is no UV map for tile type: " + key); 15 return this.tileUVMap["Void"]; 16 } 17}

MeshDataにも追加情報を持たせ...

C#

1public class MeshData 2{ 3 4 public List<Vector3> vertices; 5 public List<int> triangles; 6 public List<Vector4> UVs; // Vector4に変更し、xyに前景、zwに背景のUVを格納することにする 7 public List<Color> colors; // 混合率は色として表現する(UV2以降を使ってもいいと思います) 8 9 public MeshData(Tile[,] data) 10 { 11 vertices = new List<Vector3>(); 12 triangles = new List<int>(); 13 UVs = new List<Vector4>(); 14 colors = new List<Color>(); 15 16 for (int i = 0; i < data.GetLength(0); i++) 17 { 18 for (int j = 0;j < data.GetLength(1); j++) 19 { 20 CreateSquare(data[i, j], i, j); 21 } 22 } 23 } 24 25 void CreateSquare(Tile tile, int x, int y) 26 { 27 vertices.Add(new Vector3(x + 0, y + 0)); 28 vertices.Add(new Vector3(x + 1, y + 0)); 29 vertices.Add(new Vector3(x + 0, y + 1)); 30 vertices.Add(new Vector3(x + 1, y + 1)); 31 32 triangles.Add(vertices.Count - 1); 33 triangles.Add(vertices.Count - 3); 34 triangles.Add(vertices.Count - 4); 35 36 triangles.Add(vertices.Count - 2); 37 triangles.Add(vertices.Count - 1); 38 triangles.Add(vertices.Count - 4); 39 40 // 前景・背景についてそれぞれUVを取得し、合成してUVsに追加する 41 // (Zipで合成しましたが、この場合「using System.Linq;」が必要になります) 42 Vector2[] foregroundUVs = SpriteLoader.instance.GetTileUVs(tile.foregroundType); 43 Vector2[] backgroundUVs = SpriteLoader.instance.GetTileUVs(tile.backgroundType); 44 UVs.AddRange(foregroundUVs.Zip(backgroundUVs, (f, b) => new Vector4(f.x, f.y, b.x, b.y))); 45 46 // 混合率はA成分に埋め込む 47 // RGBに他の情報を埋め込む余地があります 48 // 例えばランダムな色を入れてやり、描画時にそれを利用することで地形に色ムラをつけるとか... 49 for (int i = 0; i < 4; i++) 50 { 51 colors.Add(new Color(0.0f, 0.0f, 0.0f, tile.blendWeights[i])); 52 } 53 } 54}

そしてWorldGenerateMeshも変更します。

C#

1void GenerateMesh() 2{ 3 MeshData data = new MeshData(this.tiles); 4 this.mesh = new Mesh(); 5 6 // 頂点インデックスのフォーマットを32ビット整数に変更する 7 this.mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; 8 9 this.mesh.vertices = data.vertices.ToArray(); 10 this.mesh.triangles = data.triangles.ToArray(); 11 12 // uvへの代入だとVector2しか受け付けてくれないので、代わりにSetUVsを使う 13 this.mesh.SetUVs(0, data.UVs); 14 15 // 混合率もセットする 16 this.mesh.colors = data.colors.ToArray(); 17 18 this.mesh.RecalculateNormals(); 19 this.mesh.RecalculateBounds(); 20}

Worldにセットするマテリアルについては、単純には下記のような合成でいいかと思います。

ShaderLab

1Shader "Unlit/World" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 _EdgeSoftness ("Edge Softness", Range(0.0, 1.0)) = 0.05 7 } 8 9 SubShader 10 { 11 Tags { "RenderType"="Opaque" } 12 13 Pass 14 { 15 CGPROGRAM 16 #pragma vertex vert 17 #pragma fragment frag 18 #include "UnityCG.cginc" 19 20 struct appdata 21 { 22 float4 vertex : POSITION; 23 float4 uv : TEXCOORD0; 24 float4 color : COLOR; 25 }; 26 27 struct v2f 28 { 29 float4 vertex : SV_POSITION; 30 float4 uv : TEXCOORD0; 31 float weight : TEXCOORD1; 32 }; 33 34 sampler2D _MainTex; 35 float4 _MainTex_ST; 36 float _EdgeSoftness; 37 38 v2f vert(appdata v) 39 { 40 v2f o; 41 o.vertex = UnityObjectToClipPos(v.vertex); 42 o.uv = float4(TRANSFORM_TEX(v.uv.xy, _MainTex), TRANSFORM_TEX(v.uv.zw, _MainTex)); 43 o.weight = v.color.a; 44 return o; 45 } 46 47 fixed4 frag(v2f i) : SV_Target 48 { 49 fixed4 foreColor = tex2D(_MainTex, i.uv.xy); 50 fixed4 backColor = tex2D(_MainTex, i.uv.zw); 51 float smoothingWidth = _EdgeSoftness * 0.5; 52 float ratio = smoothstep(0.5 - smoothingWidth, 0.5 + smoothingWidth, i.weight); 53 return lerp(backColor, foreColor, ratio); 54 } 55 ENDCG 56 } 57 } 58}

結果1

ですがご提示の理想図を見ますと、境界部分にフラクタルノイズっぽい乱れがあるようにも見えます。一例としては、下記のようにするとそれに近づけられるのではないでしょうか?

ShaderLab

1Shader "Unlit/World" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 _EdgeSoftness ("Edge Softness", Range(0.0, 1.0)) = 0.025 7 _EdgeDist ("Edge Disturbance", Range(0.0, 1.0)) = 0.05 8 _EdgeDistFreq ("Edge Disturbance Frequency", Range(0.0, 16.0)) = 4.0 9 } 10 11 SubShader 12 { 13 Tags { "RenderType"="Opaque" } 14 15 Pass 16 { 17 CGPROGRAM 18 #pragma vertex vert 19 #pragma fragment frag 20 #include "UnityCG.cginc" 21 22 struct appdata 23 { 24 float4 vertex : POSITION; 25 float4 uv : TEXCOORD0; 26 float4 color : COLOR; 27 }; 28 29 struct v2f 30 { 31 float4 vertex : SV_POSITION; 32 float4 uv : TEXCOORD0; 33 float weight : TEXCOORD1; 34 float2 modelPos : TEXCOORD2; 35 }; 36 37 // https://thebookofshaders.com/11/ の方法によるバリューノイズの生成 38 inline float random(float2 st) { 39 return frac(sin(dot(st, float2(12.9898, 78.233))) * 43758.5453123); 40 } 41 42 float noise(float2 st) 43 { 44 float2 i = floor(st); 45 float2 f = frac(st); 46 float a = random(i); 47 float b = random(i + float2(1.0, 0.0)); 48 float c = random(i + float2(0.0, 1.0)); 49 float d = random(i + float2(1.0, 1.0)); 50 float2 u = f * f * (3.0 - 2.0 * f); 51 52 return dot(float2(lerp(a, b, u.x), lerp(c - a, d - b, u.x)), float2(1.0, u.y)); 53 } 54 55 // バリューノイズを適当に重ね合わせてフラクタルっぽくする 56 float fractal(float2 st) 57 { 58 float4 amp = float4(1.0, 0.5, 0.25, 0.125); 59 float4 v; 60 61 v.x = noise(st); 62 st = st * 2.0 + float2(14.1421356237, 17.3205080757); 63 v.y = noise(st); 64 st = st * 2.0 + float2(22.360679775, 26.4575131106); 65 v.z = noise(st); 66 st = st * 2.0 + float2(31.4159265359, 27.1828182846); 67 v.w = noise(st); 68 69 return dot(v, amp) / dot(1.0, amp); 70 } 71 72 sampler2D _MainTex; 73 float4 _MainTex_ST; 74 float4 _MainTex_TexelSize; 75 float _EdgeSoftness; 76 float _EdgeDist; 77 float _EdgeDistFreq; 78 79 v2f vert(appdata v) 80 { 81 v2f o; 82 o.uv = float4(TRANSFORM_TEX(v.uv.xy, _MainTex), TRANSFORM_TEX(v.uv.zw, _MainTex)); 83 o.weight = v.color.a; 84 o.modelPos = v.vertex.xy; 85 o.vertex = UnityObjectToClipPos(v.vertex); 86 return o; 87 } 88 89 fixed4 frag(v2f i) : SV_Target 90 { 91 fixed4 foreColor = tex2D(_MainTex, i.uv.xy); 92 fixed4 backColor = tex2D(_MainTex, i.uv.zw); 93 float smoothingWidth = _EdgeSoftness * 0.5; 94 float f = dot(float2(_EdgeDist, 1.0 - _EdgeDist), float2(fractal(i.modelPos * _EdgeDistFreq), 0.5)); 95 float upper = 1.0 - 2.0 * (1.0 - i.weight) * (1.0 - f); 96 float lower = 2.0 * i.weight * f; 97 float weight = lerp(lower, upper, step(0.5, i.weight)); 98 float ratio = smoothstep(0.5 - smoothingWidth, 0.5 + smoothingWidth, weight); 99 return lerp(backColor, foreColor, ratio); 100 } 101 ENDCG 102 } 103 } 104}

結果2

※実験中に気付いたのですが、シードの値によってはノイズパターンの水平方向周波数が極端に小さくなってしまうことがあるようでしたので、さらに下記のような変更を加えています。

C#

1public float [,] GetNoiseValues(int width,int height) 2{ 3 float[,] noiseValues = new float[width, height]; 4 5 float max = 0f; 6 float min = float.MaxValue; 7 8 // seedに極端に大きな値が入ると、(i + seed) / (float)width * frequencyの部分で 9 // 精度不足となるようだったため、適当に小さくしました 10 seed %= 1024; 11 12 // 省略 13 14 return noiseValues; 15}

投稿2019/04/13 03:26

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/04/13 12:16

丁寧な回答ありがとうございます。本当に勉強になります。 コードの内容はまだ完全に理解出来ていませんが、理解出来るように勉強しようと思います。
退会済みユーザー

退会済みユーザー

2019/04/15 12:20

ベストアンサーを選んでから質問して申し訳ありません。 TileのTypeを二種類以上に増やす場合は、どうコードを書けばよいのでしょうか? 二種類以上の時、foregroundTypeとbackgroundTypeに、なにを指定すればいいか分かりません。 もしよければ教えていただきたいです。
Bongo

2019/04/15 21:39

なるほど、ちょっとやっかいそうな問題ですね... 今回の2地形選択だとノイズ値に対する地形を [0.0←土→0.5←草→1.0] という具合に割り当てましたが、もしこれを単純に多段階化して、たとえば [0.0←水→0.25←土→0.5←草→0.75←岩→1.0] という風に増やすのであれば、隣り合った地形...水・土、土・草、草・岩をそれぞれ背景・前景とすることである程度は対応できそうに思います(多段階対応のために少々コードを修正することになるでしょうが、おそらく大きくは変わらないでしょう)。 ですがもっと複雑な地形割り当てを行いたい場合は、どの地形とどの地形が隣り合うかわからないので描画戦略を見直すことになりそうです。 それら複数種類の地形をどのように割り当てるのかご説明いただければ(可能であれば、当初ご提示いただいたようなタイルがきっちり分かれた状態でかまいませんので、スクリプトをご提示いただけるとありがたいです)、もしいい案が思いつきましたら追記いたします。
Bongo

2019/04/15 21:44

おっとすみません、新しい関連質問(https://teratail.com/questions/184671 )をご投稿いただいているのを見逃していました。そちらも見てみようと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問