フィルタースタック、フィルター、プロシージャルマスク
続いて、フィルタースタックを Terrain (地形) ツールに追加します。フィルタースタックを使用すると、ツールの入力や出力に影響するプロシージャルマスクを生成できます。例えば、Slope フィルターを含むフィルタースタックを Smooth ツールに追加すると、Terrain のハイトマップのうち、Slope フィルターで指定した勾配範囲内の領域のみが滑らかになります。
using UnityEngine;
using UnityEditor;
using UnityEditor.TerrainTools;
internal class CustomTerrainToolWithMaskFilters : TerrainPaintTool<CustomTerrainToolWithMaskFilters>
{
private float m_BrushOpacity;
private float m_BrushSize;
private float m_BrushRotation;
Material m_Material;
Material material
{
get
{
if(m_Material != null) return m_Material;
m_Material = new Material(Shader.Find("TerrainTool/BrushMaskFilterExample"));
return m_Material;
}
}
// FilterStack を作成します
FilterStack m_FilterStack;
FilterStack filterStack
{
get
{
if(m_FilterStack != null) return m_FilterStack;
m_FilterStack = ScriptableObject.CreateInstance<FilterStack>();
return m_FilterStack;
}
}
// FilterStack の UI のビューを作成します
FilterStackView m_FilterStackView;
FilterStackView filterStackView
{
get
{
if(m_FilterStackView != null && m_FilterStackView.serializedFilterStack.targetObject != null)
return m_FilterStackView;
m_FilterStackView = new FilterStackView(new GUIContent("Brush Mask Filters"), new SerializedObject( filterStack ) );
m_FilterStackView.FilterContext = filterContext;
return m_FilterStackView;
}
}
// FilterContext を作成します。FilterStack のフィルターで使用されるプロパティバッグの一種です
FilterContext m_FilterContext;
private FilterContext filterContext
{
get
{
if (m_FilterContext != null) return m_FilterContext;
m_FilterContext = new FilterContext(FilterUtility.defaultFormat, Vector3.zero, 1f, 0f);
return m_FilterContext;
}
}
public override string GetName()
{
return "Examples/Custom Terrain Tool With Mask Filters";
}
public override string GetDesc()
{
return "My custom Terrain Tool is amazing!";
}
public override void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext)
{
editContext.ShowBrushesGUI(5, BrushGUIEditFlags.Select);
m_BrushOpacity = EditorGUILayout.Slider("Opacity", m_BrushOpacity, 0, 1);
m_BrushSize = EditorGUILayout.Slider("Size", m_BrushSize, .001f, 100f);
m_BrushRotation = EditorGUILayout.Slider("Rotation", m_BrushRotation, 0, 360);
// FilterStack の UI をレンダリングします。これにより、Terrain ツールの UI を介してフィルターを追加、削除できるようになります
filterStackView.OnGUI();
}
void BlitFilterStackTexture(Terrain terrain, RenderTexture source, RenderTexture dest, Vector3 brushPos)
{
// FilterContext を準備します
filterContext.brushPos = brushPos;
filterContext.brushSize = m_BrushSize;
filterContext.brushRotation = m_BrushRotation;
using(new ActiveRenderTextureScope(null))
{
// FilterStack のフィルターで使用される可能性がある必須プロパティをバインドします。Terrain ツールの一部のフィルターは、くぼみや勾配などの Terrain のサイズに依存します
TerrainData terrainData = terrain.terrainData;
filterContext.floatProperties[FilterContext.Keywords.TerrainScale] = Mathf.Sqrt(terrainData.size.x * terrainData.size.x + terrainData.size.z * terrainData.size.z);
filterContext.vectorProperties["_TerrainSize"] = new Vector4(terrainData.size.x, terrainData.size.y, terrainData.size.z, 0.0f);
// FilterStack のフィルターで使用される可能性がある Terrain のテクスチャデータをバインドします
filterContext.rtHandleCollection.AddRTHandle(0, FilterContext.Keywords.Heightmap, source.graphicsFormat);
filterContext.rtHandleCollection.GatherRTHandles(source.width, source.height);
Graphics.Blit(source, filterContext.rtHandleCollection[FilterContext.Keywords.Heightmap]);
filterStack.Eval(filterContext, source, dest);
}
filterContext.ReleaseRTHandles();
}
private void RenderIntoPaintContext(Terrain terrain, UnityEngine.TerrainTools.PaintContext paintContext, Texture brushTexture, UnityEngine.TerrainTools.BrushTransform brushXform, Vector3 brushPos)
{
Material mat = material;
// 出力の FilterStack RenderTexture を取得します
RTHandle filterTexture = RTUtils.GetTempHandle(paintContext.sourceRenderTexture.width, paintContext.sourceRenderTexture.height, 0, FilterUtility.defaultFormat);
BlitFilterStackTexture(terrain, paintContext.sourceRenderTexture, filterTexture, brushPos);
// FilterStack RenderTexture をツールのマテリアルにバインドします
mat.SetTexture("_FilterTex", filterTexture);
mat.SetTexture("_BrushTex", brushTexture);
var opacity = Event.current.control ? -m_BrushOpacity : m_BrushOpacity;
mat.SetVector("_BrushParams", new Vector4(opacity, 0.0f, 0.0f, 0.0f));
UnityEngine.TerrainTools.TerrainPaintUtility.SetupTerrainToolMaterialProperties(paintContext, brushXform, mat);
Graphics.Blit(paintContext.sourceRenderTexture, paintContext.destinationRenderTexture, mat, (int)UnityEngine.TerrainTools.TerrainPaintUtility.BuiltinPaintMaterialPasses.RaiseLowerHeight);
// FilterStack の RenderTexture をリリースします
RTUtils.Release(filterTexture);
}
public override void OnRenderBrushPreview(Terrain terrain, IOnSceneGUI editContext)
{
if (Event.current.type != EventType.Repaint) return;
if (!editContext.hitValidTerrain) return;
UnityEngine.TerrainTools.BrushTransform brushXform = UnityEngine.TerrainTools.TerrainPaintUtility.CalculateBrushTransform(terrain, editContext.raycastHit.textureCoord, m_BrushSize, m_BrushRotation);
UnityEngine.TerrainTools.PaintContext paintContext = UnityEngine.TerrainTools.TerrainPaintUtility.BeginPaintHeightmap(terrain, brushXform.GetBrushXYBounds(), 1);
Material previewMaterial = TerrainPaintUtilityEditor.GetDefaultBrushPreviewMaterial();
// ブラシのプレビューのレンダリングを続行します
TerrainPaintUtilityEditor.BrushPreview previewTexture = TerrainPaintUtilityEditor.BrushPreview.SourceRenderTexture;
TerrainPaintUtilityEditor.DrawBrushPreview(paintContext, previewTexture, editContext.brushTexture, brushXform, previewMaterial, 0);
RenderIntoPaintContext(terrain, paintContext, editContext.brushTexture, brushXform, editContext.raycastHit.point);
RenderTexture.active = paintContext.oldRenderTexture;
previewMaterial.SetTexture("_HeightmapOrig", paintContext.sourceRenderTexture);
previewTexture = TerrainPaintUtilityEditor.BrushPreview.DestinationRenderTexture;
TerrainPaintUtilityEditor.DrawBrushPreview(paintContext, previewTexture, editContext.brushTexture, brushXform, previewMaterial, 1);
UnityEngine.TerrainTools.TerrainPaintUtility.ReleaseContextResources(paintContext);
}
public override bool OnPaint(Terrain terrain, IOnPaint editContext)
{
UnityEngine.TerrainTools.BrushTransform brushXform = UnityEngine.TerrainTools.TerrainPaintUtility.CalculateBrushTransform(terrain, editContext.uv, m_BrushSize, m_BrushRotation);
UnityEngine.TerrainTools.PaintContext paintContext = UnityEngine.TerrainTools.TerrainPaintUtility.BeginPaintHeightmap(terrain, brushXform.GetBrushXYBounds());
RenderIntoPaintContext(terrain, paintContext, editContext.brushTexture, brushXform, editContext.raycastHit.point);
UnityEngine.TerrainTools.TerrainPaintUtility.EndPaintHeightmap(paintContext, "Terrain Paint - Raise or Lower Height");
return true;
}
}
``
Here is the Shader for the tool that is using a procedural texture from the FilterStack:
``
Shader "TerrainTool/BrushMaskFilterExample"
{
Properties { _MainTex ("Texture", any) = "" {} }
SubShader
{
ZTest Always Cull Off ZWrite Off
HLSLINCLUDE
#include "UnityCG.cginc"
#include "Packages/com.unity.terrain-tools/Shaders/TerrainTools.hlsl"
sampler2D _MainTex;
float4 _MainTex_TexelSize; // 1/width、1/height、width、height
sampler2D _BrushTex;
sampler2D _FilterTex;
float4 _BrushParams;
#define BRUSH_STRENGTH (_BrushParams[0])
#define BRUSH_TARGETHEIGHT (_BrushParams[1])
#define kMaxHeight (32766.0f/65535.0f)
struct appdata_t
{
float4 vertex : POSITION;
float2 pcUV : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 pcUV : TEXCOORD0;
};
v2f vert(appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.pcUV = v.pcUV;
return o;
}
ENDHLSL
Pass
{
Name "CustomTerrainTool"
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 frag(v2f i) : SV_Target
{
float2 brushUV = PaintContextUVToBrushUV(i.pcUV);
// 境界外の乗数
float oob = all(saturate(brushUV) == brushUV) ? 1.0f : 0.0f;
// ソースのハイトマップテクスチャの領域となる MainTex をサンプリングして、指定された UV での現在の高さの値を取得します
// ここでは UnpackHeightmap が必要になります。UnpackHeightmap は、現在のプラットフォームやグラフィックスデバイスが R16_UNorm テクスチャフォーマットに対応していない場合に、高さの値を R および G チャンネルからアンパックするためです。R16_UNorm フォーマットに対応している場合は、R チャンネルのみから読み取りを行います
float height = UnpackHeightmap(tex2D(_MainTex, i.pcUV));
float filter = UnpackHeightmap(tex2D(_FilterTex, i.pcUV));
float brush = UnpackHeightmap(tex2D(_BrushTex, brushUV));
// 合成されたマスクの影響度を計算します
float brushShape = oob * brush * filter;
height = height + BRUSH_STRENGTH * brushShape;
// 書き込み先の RenderTexture に新しい高さを格納します。ハイトマップ自体は符号付きですが、Terrain のレンダリング時に符号なしのテクスチャとして処理されるため、0.0f と 0.5f の間で固定します
// ここでは PackHeightmap が必要です。PackHeightmap は、現在のプラットフォームやグラフィックスデバイスが R16_UNorm テクスチャフォーマットに対応していない場合に、高さの値を R および G チャンネルにパックするためです。R16_UNorm フォーマットに対応している場合、R チャンネルのみに書き込みを行います
return PackHeightmap(clamp(height, 0, kMaxHeight));
}
ENDHLSL
}
}
}