Unity テクスチャブレンド
解決済
回答 2
投稿
- 評価
- クリップ 0
- VIEW 489

退会済みユーザー
前提・実現したいこと
生成したメッシュのテクスチャを、テクスチャブレンドしたい
該当のソースコード
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
Mesh mesh;
Matrix4x4[][] matrices;
Matrix4x4[][] matrices2;
Matrix4x4[][] matrices3;
int count;
public Material material;
public Material material2;
public Material material3;
public int rows = 100;
public int columns = 100;
public int mapWidth;
public int mapHeight;
public float noiseScale;
public int octaves;
[Range(0, 1)]
public float persistance;
public float lacunarity;
public int seed;
public Vector2 offset;
private void Start()
{
mesh = new Mesh();
mesh.vertices = new Vector3[] {
new Vector3 (-1f, -1f, 0),
new Vector3 (-1f, 1f, 0),
new Vector3 (1f , -1f, 0),
new Vector3 (1f , 1f, 0),
};
mesh.uv = new Vector2[] {
new Vector2 (0, 0),
new Vector2 (0, 1),
new Vector2 (1, 0),
new Vector2 (1, 1),
};
mesh.triangles = new int[] {
0, 1, 2,
1, 3, 2,
};
mesh.RecalculateNormals();
mesh.RecalculateBounds();
count = rows * columns;
var flatMatrices = new Matrix4x4[count];
var flatMatrices2 = new Matrix4x4[count];
var flatMatrices3 = new Matrix4x4[count];
float[,] noiseMap = Noise.GenerateNoiseMap(mapWidth, mapHeight, seed, noiseScale, octaves, persistance, lacunarity, offset);
for (var i = 0; i < rows; i++)
{
for (var j = 0; j < columns; j++)
{
var index = (i * columns) + j;
float height = noiseMap[i,j];
var position = new Vector3(i, j, 0);
var rotation = Quaternion.identity;
var scale = Vector3.one;
if (height < 0.35f)
{
flatMatrices[index] = Matrix4x4.TRS(position, rotation, scale);
}
else if(height < 0.45f)
{
flatMatrices2[index] = Matrix4x4.TRS(position, rotation, scale);
}
else
{
flatMatrices3[index] = Matrix4x4.TRS(position, rotation, scale);
}
}
}
matrices = flatMatrices.Select((m, i) => (m, i / 1023))
.GroupBy(t => t.Item2)
.Select(g => g.Select(t => t.Item1).ToArray()).ToArray();
matrices2 = flatMatrices2.Select((m, i) => (m, i / 1023))
.GroupBy(t => t.Item2)
.Select(g => g.Select(t => t.Item1).ToArray()).ToArray();
matrices3 = flatMatrices3.Select((m, i) => (m, i / 1023))
.GroupBy(t => t.Item2)
.Select(g => g.Select(t => t.Item1).ToArray()).ToArray();
}
void Update()
{
foreach (var m in matrices)
{
Graphics.DrawMeshInstanced(mesh, 0, material, m);
}
foreach (var m in matrices2)
{
Graphics.DrawMeshInstanced(mesh, 0, material2, m);
}
foreach (var m in matrices3)
{
Graphics.DrawMeshInstanced(mesh, 0, material3, m);
}
}
}
using UnityEngine;
using System.Collections;
public static class Noise
{
public static float[,] GenerateNoiseMap(int mapWidth, int mapHeight, int seed, float scale, int octaves, float persistance, float lacunarity, Vector2 offset)
{
float[,] noiseMap = new float[mapWidth, mapHeight];
System.Random prng = new System.Random(seed);
Vector2[] octaveOffsets = new Vector2[octaves];
for (int i = 0; i < octaves; i++)
{
float offsetX = prng.Next(-100000, 100000) + offset.x;
float offsetY = prng.Next(-100000, 100000) + offset.y;
octaveOffsets[i] = new Vector2(offsetX, offsetY);
}
if (scale <= 0)
{
scale = 0.0001f;
}
float maxNoiseHeight = float.MinValue;
float minNoiseHeight = float.MaxValue;
float halfWidth = mapWidth / 2f;
float halfHeight = mapHeight / 2f;
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
float amplitude = 1;
float frequency = 1;
float noiseHeight = 0;
for (int i = 0; i < octaves; i++)
{
float sampleX = (x - halfWidth) / scale * frequency + octaveOffsets[i].x;
float sampleY = (y - halfHeight) / scale * frequency + octaveOffsets[i].y;
float perlinValue = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;
noiseHeight += perlinValue * amplitude;
amplitude *= persistance;
frequency *= lacunarity;
}
if (noiseHeight > maxNoiseHeight)
{
maxNoiseHeight = noiseHeight;
}
else if (noiseHeight < minNoiseHeight)
{
minNoiseHeight = noiseHeight;
}
noiseMap[x, y] = noiseHeight;
}
}
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
noiseMap[x, y] = Mathf.InverseLerp(minNoiseHeight, maxNoiseHeight, noiseMap[x, y]);
}
}
return noiseMap;
}
}
Shader "Unlit/World"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
[IntRange] _Magnification ("Magnification", Range(1, 32)) = 8
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
LOD 100
Pass
{
ZWrite Off
ZTest Always
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
int _Magnification;
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v)
o.vertex = UnityObjectToClipPos(v.vertex);
float3 worldPosition = mul(unity_ObjectToWorld, v.vertex);
float2 blockCoord = worldPosition.xy / _Magnification;
o.uv = TRANSFORM_TEX(blockCoord, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv = frac(i.uv);
// sample the texture
fixed4 col = tex2D(_MainTex, uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
試したこと
一年ほど前にも、テクスチャブレンドについて質問したのですが、複雑で自分のなかで理解できておらず、どのようにスクリプトやシェーダーを書けばいいか、分かりませんでした。
ネットで検索して、同じようなことをやっている記事を見つけました。
ですが、コードが抜粋で書いてあるだけなので、ぼんやりとしか分かっていません。
どのようにすれば、テクスチャブレンドできるでしょうか? 教えていただけるとありがたいです。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
+3
パート2
マテリアルは下記のように変更しました。同じく変更部分にコメントを入れています。
Shader "Unlit/World"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
[IntRange] _Magnification ("Magnification", Range(1, 32)) = 8
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
LOD 100
Pass
{
ZWrite Off
ZTest Always
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertexColor : TEXCOORD2; // vertからfragへ頂点カラーを伝達できるようにする
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
int _Magnification;
// インスタンスごとのプロパティとして_VertexColorsを定義する
// これの各行に左下・右下・左上・右上の順で頂点カラーが格納されている
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4x4, _VertexColors)
UNITY_INSTANCING_BUFFER_END(Props)
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v)
o.vertex = UnityObjectToClipPos(v.vertex);
float3 worldPosition = mul(unity_ObjectToWorld, v.vertex);
float2 blockCoord = worldPosition.xy / _Magnification;
o.uv = TRANSFORM_TEX(blockCoord, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
// 4つの頂点カラーのうちどれを使用するかはv.uvを使って決める
// v.uvが(0.5, 0.5)の点(メッシュ中心)の場合は4点の平均色とする
float4x4 vertexColors = UNITY_ACCESS_INSTANCED_PROP(Props, _VertexColors);
float4 averageColor = mul((float4)0.25, vertexColors);
float vertexIndex = v.uv.x + v.uv.y * 2.0;
o.vertexColor = (vertexIndex % 1.0 > 0.0) ? averageColor : vertexColors[(int)vertexIndex];
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv = frac(i.uv);
// sample the texture
// colに頂点カラーを乗算し透明度を反映させる
fixed4 col = tex2D(_MainTex, uv) * i.vertexColor;
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
各地形レイヤーを描画させてみると下図のようになりました。それぞれの地形が外側に1タイル大きくなり、そこにグラデーションがかかってなめらかにブレンドされます。
「なんかタイルのブロック感が残ってカクカクしてるじゃないか...」とお思いかもしれませんが、この方式だとタイルのメッシュをもっと細かくし、グラデーションをより精密にコントロールしてやらないと自然な表現は難しいんじゃないかと思います。
Plump Helmet Studiosさんのスクリーンショットだと、タイルが斜めに隣接する部分では斜めのグラデーションがかかってブロック感が少ないですが、コードのコメント中でも申し上げたようにサイト上には左下から右上にかけてグラデーションが発生する状況しか図示されていません。
あくまでも想像ですが、グラデーションのかかり方から予想しますに、Plump Helmet Studiosさんのタイルメッシュもご質問者さんと同様の左下と右上の2つの三角形で正方形を構成しているような気がします。だとすると、左下から右上の対角線方向のグラデーションは良好そうに見えても、逆に左上から右下の対角線方向には下図のようなブロック状模様が目立ってしまうんじゃないかと思います...
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
checkベストアンサー
+2
サイトの文章中にある...
So to blend the terrain layers, each layer checks for tiles at it's edge, and, where it finds a tile that is dissimilar, it creates a new quad with vertex-level opacity. For example, if the current tile is water and the tile to the north is sand, then the water layer will create a new quad to the north (tile x,y+1) with four vertices, the "southern" two (x,y + x+1,y) with an opacity of 1 and the "northern" two (x,y+1 + x+1,y+1) with an opacity of 0. These quads are part of the same mesh as the rest of the tiles, and therefore benefit from the same UV mapping & tiling.
というのがキモかと思います。私の解釈したところですと、地形の境界部分にさらにタイルを追加して、そのタイルの四隅の頂点カラーのアルファを調節することで周囲との隣接のしかたに応じて透明度をなめらかに変化させる...ということを意味しているんでしょう。
おっしゃる通りサイトのコードは断片的で、全貌がどうなっているかは読み取れませんでした。ですのでPlump Helmet Studiosさんの作例をぴったり完全に再現しようとするよりも、上記の理屈をヒントに自前で実装するのがいいんじゃないかと思います。
ご質問者さんのご提示いただいたコードではGPUインスタンシング機能を利用しています。その条件で頂点カラーを受け渡すにはどうしようかと考えたところ、まずExampleClass
は下記のようにしてみました。追加・変更した部分にコメントを入れています。
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
Mesh mesh;
Matrix4x4[][] matrices;
Matrix4x4[][] matrices2;
Matrix4x4[][] matrices3;
// 頂点カラー受け渡し用のフィールド
Matrix4x4[][] vertexColors;
Matrix4x4[][] vertexColors2;
Matrix4x4[][] vertexColors3;
// マテリアルへのデータ投入用のMaterialPropertyBlock
private MaterialPropertyBlock materialProperties;
// プロパティアクセス用の識別番号
private static readonly int VertexColorsProperty = Shader.PropertyToID("_VertexColors");
int count;
public Material material;
public Material material2;
public Material material3;
public int rows = 100;
public int columns = 100;
public int mapWidth;
public int mapHeight;
public float noiseScale;
// デバッグ用に、各地形を描画するかどうかを切り替えるフラグを用意する
public bool drawTerrain = true;
public bool drawTerrain2 = true;
public bool drawTerrain3 = true;
public int octaves;
[Range(0, 1)]
public float persistance;
public float lacunarity;
public int seed;
public Vector2 offset;
private void Start()
{
this.mesh = new Mesh();
// 当初のコードではメッシュのサイズが2x2になっており、それをそのまま描画すると
// タイル同士が重なってしまうようだったので1x1になるように変更
// また、メッシュの構造を「4頂点・三角形2枚」から「5頂点・三角形4枚」に変更した
// Plump Helmet Studiosさんの実装がどうなっているかは不明だが、スクリーンショットでは
// 左下から右上にかけてグラデーションが発生するケースしか示されていない
// 頂点単位で透明度を設定する方式だと、三角形2枚方式では左下⇔右上と左上⇔右下の
// 構造が異なるためにグラデーションのかかり方が変化してしまうはずだが...
this.mesh.vertices = new Vector3[] {
Vector3.zero,
new Vector3 (-0.5f, -0.5f, 0),
new Vector3 (-0.5f, 0.5f, 0),
new Vector3 ( 0.5f, -0.5f, 0),
new Vector3 ( 0.5f, 0.5f, 0),
};
this.mesh.uv = new Vector2[]
{
new Vector2(0.5f, 0.5f),
new Vector2(0, 0),
new Vector2(0, 1),
new Vector2(1, 0),
new Vector2(1, 1),
};
this.mesh.triangles = new int[] {
0, 4, 3, // 北
0, 2, 4, // 東
0, 1, 2, // 南
0, 3, 1, // 西
};
this.mesh.RecalculateNormals();
this.mesh.RecalculateBounds();
this.count = this.rows * this.columns;
var flatMatrices = new Matrix4x4[this.count];
var flatMatrices2 = new Matrix4x4[this.count];
var flatMatrices3 = new Matrix4x4[this.count];
float[,] noiseMap = global::Noise.GenerateNoiseMap(this.mapWidth, this.mapHeight, this.seed, this.noiseScale, this.octaves, this.persistance, this.lacunarity, this.offset);
for (var i = 0; i < this.rows; i++)
{
for (var j = 0; j < this.columns; j++)
{
var index = (i * this.columns) + j;
// iとjが逆のようだったので変更
float height = noiseMap[j, i];
var position = new Vector3(j, i, 0);
var rotation = Quaternion.identity;
var scale = Vector3.one;
if (height < 0.35f)
{
flatMatrices[index] = Matrix4x4.TRS(position, rotation, scale);
}
else if (height < 0.45f)
{
flatMatrices2[index] = Matrix4x4.TRS(position, rotation, scale);
}
else
{
flatMatrices3[index] = Matrix4x4.TRS(position, rotation, scale);
}
}
}
// 当初の通りタイルを配置したあと、ここで各地形レイヤーに
// さらに追加の外周タイルを配置する
var flatVertexColors = this.AppendOutlineTiles(flatMatrices);
var flatVertexColors2 = this.AppendOutlineTiles(flatMatrices2);
var flatVertexColors3 = this.AppendOutlineTiles(flatMatrices3);
// タイルの座標変換行列と同様に、頂点カラー配列も1023個ずつに分割しておく
// 結局同じような記述を6回も繰り返すことになってしまったので、この1023個分割操作を
// メソッド化してしまうことにした
this.matrices = this.GroupBy1023(flatMatrices);
this.matrices2 = this.GroupBy1023(flatMatrices2);
this.matrices3 = this.GroupBy1023(flatMatrices3);
this.vertexColors = this.GroupBy1023(flatVertexColors);
this.vertexColors2 = this.GroupBy1023(flatVertexColors2);
this.vertexColors3 = this.GroupBy1023(flatVertexColors3);
}
// 配列要素を1023個ずつに分割して返すメソッド
T[][] GroupBy1023<T>(IEnumerable<T> source)
{
return source.Select((m, i) => (m, i / 1023))
.GroupBy(t => t.Item2)
.Select(g => g.Select(t => t.Item1).ToArray()).ToArray();
}
// 地形レイヤーに対して外周タイルを配置するメソッド
// 併せて頂点カラーを生成して返す
// なお、透明度を操作するだけならシェーダーへ送るデータの型はVector4で間に合うはずだが
// 気まぐれでMatrix4x4とした
// このため、アルファのみならずRGBも伝達することが可能であり、さらに改造を加えれば
// 頂点単位で色を乗せることで地形に色ムラをつけることも可能なはず
Matrix4x4[] AppendOutlineTiles(Matrix4x4[] flatMatrices)
{
var allWhite = (new Matrix4x4(Color.white, Color.white, Color.white, Color.white)).transpose;
var allClear = allWhite;
allClear.SetColumn(3, Vector4.zero);
var colors = Enumerable.Repeat(allWhite, this.rows * this.columns).ToArray();
var needsPlaceTile = new bool[this.rows * this.columns];
for (var i = 0; i < this.rows; i++)
{
for (var j = 0; j < this.columns; j++)
{
var index = (i * this.columns) + j;
var matrix = flatMatrices[index];
if (matrix[0] > 0.0f)
{
// この位置にすでに配置済みならスキップ
continue;
}
var isLeftEdge = j == 0;
var isRightEdge = j == (this.columns - 1);
var isBottomEdge = i == 0;
var isTopEdge = i == (this.rows - 1);
var hasN = !isTopEdge && (flatMatrices[index + this.columns][0] > 0.0f);
var hasNE = !isTopEdge && !isRightEdge && (flatMatrices[index + 1 + this.columns][0] > 0.0f);
var hasE = !isRightEdge && (flatMatrices[index + 1][0] > 0.0f);
var hasSE = !isBottomEdge && !isRightEdge && (flatMatrices[index + 1 - this.columns][0] > 0.0f);
var hasS = !isBottomEdge && (flatMatrices[index - this.columns][0] > 0.0f);
var hasSW = !isBottomEdge && !isLeftEdge && (flatMatrices[index - 1 - this.columns][0] > 0.0f);
var hasW = !isLeftEdge && (flatMatrices[index - 1][0] > 0.0f);
var hasNW = !isTopEdge && !isLeftEdge && (flatMatrices[index - 1 + this.columns][0] > 0.0f);
void MakeOpaque(int vertexIndex)
{
var color = colors[index].GetRow(vertexIndex);
color.w = 1.0f;
colors[index].SetRow(vertexIndex, color);
needsPlaceTile[index] = true;
}
// まず四隅をすべて透明にしておき...
colors[index] = allClear;
if (hasN || hasNE || hasE)
{
// 北~東にかけてタイルが配置されているなら右上を不透明に
MakeOpaque(1 + (1 << 1));
}
if (hasE || hasSE || hasS)
{
// 東~南にかけてタイルが配置されているなら右下を不透明に
MakeOpaque(1 + (0 << 1));
}
if (hasS || hasSW || hasW)
{
// 南~西にかけてタイルが配置されているなら左下を不透明に
MakeOpaque(0 + (0 << 1));
}
if (hasW || hasNW || hasN)
{
// 西~北にかけてタイルが配置されているなら左上を不透明に
MakeOpaque(0 + (1 << 1));
}
}
}
for (var i = 0; i < this.rows; i++)
{
for (var j = 0; j < this.columns; j++)
{
var index = (i * this.columns) + j;
if (needsPlaceTile[index])
{
// 不透明化処理を行った位置に追加のタイルを設置する
var position = new Vector3(j, i, 0);
var rotation = Quaternion.identity;
var scale = Vector3.one;
flatMatrices[index] = Matrix4x4.TRS(position, rotation, scale);
}
}
}
return colors;
}
void Update()
{
if (this.materialProperties == null)
{
this.materialProperties = new MaterialPropertyBlock();
}
void DrawTerrain(Material mat, Matrix4x4[][] mtx, Matrix4x4[][] vCol)
{
for (var i = 0; i < mtx.Length; i++)
{
// プロパティブロックに頂点カラーデータを投入し、DrawMeshInstancedの引数として渡す
var m = mtx[i];
this.materialProperties.SetMatrixArray(VertexColorsProperty, vCol[i]);
Graphics.DrawMeshInstanced(this.mesh, 0, mat, m, m.Length, this.materialProperties);
}
}
// デバッグ用に、各地形レイヤーを描画するかどうかをフラグを見て切り替えさせるようにして
// 特定の地形レイヤーを単独で描画したりできるようにした
// なお、描画順はDrawMeshInstancedを実行した順番とは異なる可能性があるので要注意
// デフォルトではmaterial、material2、material3とも3000だと思うが
// たとえばmaterial2は3001、たとえばmaterial3は3002にでもしておけば
// 意図通りの順序で描画されるはず
if (this.drawTerrain)
{
DrawTerrain(this.material, this.matrices, this.vertexColors);
}
if (this.drawTerrain2)
{
DrawTerrain(this.material2, this.matrices2, this.vertexColors2);
}
if (this.drawTerrain3)
{
DrawTerrain(this.material3, this.matrices3, this.vertexColors3);
}
}
}
(パート2へ続く...)
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.10%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2020/07/12 21:01