浏览代码

Make Density blend an option on the TerrainLayer.

/main
Yao Xiaoling 6 年前
当前提交
bf0d8504
共有 4 个文件被更改,包括 110 次插入127 次删除
  1. 123
      com.unity.render-pipelines.high-definition/HDRP/Editor/Material/TerrainLit/TerrainLitUI.cs
  2. 4
      com.unity.render-pipelines.high-definition/HDRP/Material/TerrainLit/TerrainLit.shader
  3. 108
      com.unity.render-pipelines.high-definition/HDRP/Material/TerrainLit/TerrainLitSplatCommon.hlsl
  4. 2
      com.unity.render-pipelines.high-definition/HDRP/Material/TerrainLit/TerrainLit_Basemap_Gen.shader

123
com.unity.render-pipelines.high-definition/HDRP/Editor/Material/TerrainLit/TerrainLitUI.cs


{
private class StylesLayer
{
public readonly GUIContent layersText = new GUIContent("Inputs");
public readonly GUIContent layerMapMaskText = new GUIContent("Layer Mask", "Layer mask");
public readonly GUIContent layerBlendMode = new GUIContent("Layer Blend Mode", "Controls how terrain layers are blended.");
public readonly GUIContent enableHeightBlend = new GUIContent("Enable Height-based Blend", "Blend terrain layers based on height values.");
public readonly GUIContent diffuseTexture = new GUIContent("Diffuse", "Alpha: Density");
public readonly GUIContent tint = new GUIContent("Tint");
public readonly GUIContent enableDensity = new GUIContent("Enable Density");
public readonly GUIContent diffuseTextureWithoutDensity = new GUIContent("Diffuse");
public readonly GUIContent diffuseTexture = new GUIContent("Diffuse");
public readonly GUIContent colorTint = new GUIContent("Color Tint");
public readonly GUIContent opacityAsDensity = new GUIContent("Opacity as Density", "Enable Density Blend");
public readonly GUIContent normalMapTexture = new GUIContent("Normal Map");
public readonly GUIContent normalScale = new GUIContent("Normal Scale");
public readonly GUIContent maskMapTexture = new GUIContent("Mask", "R: Metallic\nG: AO\nB: Height\nA: Smoothness");

{
}
MaterialProperty layerBlendMode;
const string kLayerBlendMode = "_LayerBlendMode";
MaterialProperty enableHeightBlend;
const string kEnableHeightBlend = "_EnableHeightBlend";
// Height blend
MaterialProperty heightTransition = null;

protected override void FindMaterialProperties(MaterialProperty[] props)
{
layerBlendMode = FindProperty(kLayerBlendMode, props, false);
enableHeightBlend = FindProperty(kEnableHeightBlend, props, false);
// We use the user data to save a string that represent the referenced lit material
// so we can keep reference during serialization
void DoLayeringInputGUI()
{
EditorGUI.indentLevel++;
GUILayout.Label(styles.layersText, EditorStyles.boldLabel);
if (layerBlendMode != null)
{
m_MaterialEditor.ShaderProperty(layerBlendMode, styles.layerBlendMode);
if (layerBlendMode.floatValue == 2)
{
EditorGUI.indentLevel++;
m_MaterialEditor.ShaderProperty(heightTransition, styles.heightTransition);
EditorGUI.indentLevel--;
}
}
EditorGUI.indentLevel--;
}
bool DoLayersGUI(AssetImporter materialImporter)
{
bool layerChanged = false;
GUI.changed = false;
DoLayeringInputGUI();
EditorGUILayout.Space();
layerChanged |= GUI.changed;
GUI.changed = false;
return layerChanged;
}
protected override bool ShouldEmissionBeEnabled(Material mat)
{
return false;

// TODO: planar/triplannar supprt
//SetupLayersMappingKeywords(material);
int layerBlendMode = (int)material.GetFloat(kLayerBlendMode);
CoreUtils.SetKeyword(material, "_TERRAIN_BLEND_DENSITY", layerBlendMode == 1);
CoreUtils.SetKeyword(material, "_TERRAIN_BLEND_HEIGHT", layerBlendMode == 2);
bool enableHeightBlend = material.HasProperty(kEnableHeightBlend) && material.GetFloat(kEnableHeightBlend) > 0;
CoreUtils.SetKeyword(material, "_TERRAIN_BLEND_HEIGHT", enableHeightBlend);
bool enableInstancedPerPixelNormal = material.GetFloat(kEnableInstancedPerPixelNormal) > 0.0f;
CoreUtils.SetKeyword(material, "_TERRAIN_INSTANCED_PERPIXEL_NORMAL", enableInstancedPerPixelNormal);

EditorGUI.BeginChangeCheck();
{
BaseMaterialPropertiesGUI();
if (enableHeightBlend != null)
{
EditorGUI.indentLevel++;
m_MaterialEditor.ShaderProperty(enableHeightBlend, styles.enableHeightBlend);
if (enableHeightBlend.floatValue > 0)
{
EditorGUI.indentLevel++;
m_MaterialEditor.ShaderProperty(heightTransition, styles.heightTransition);
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
}
EditorGUILayout.Space();
}
if (EditorGUI.EndChangeCheck())

bool layerChanged = DoLayersGUI(materialImporter);
bool enablePerPixelNormalChanged = false;
EditorGUILayout.Space();

}
EditorGUI.indentLevel--;
if (layerChanged || optionsChanged || enablePerPixelNormalChanged)
if (optionsChanged || enablePerPixelNormalChanged)
{
foreach (var obj in m_MaterialEditor.targets)
{

};
private HeightParametrization m_HeightParametrization = HeightParametrization.Amplitude;
private static bool DoesTerrainUseMaskMaps(Terrain terrain)
private static bool DoesTerrainUseMaskMaps(TerrainLayer[] terrainLayers)
var terrainLayers = terrain.terrainData.terrainLayers;
for (int i = 0; i < terrainLayers.Length; ++i)
{
if (terrainLayers[i].maskMapTexture != null)

bool ITerrainLayerCustomUI.OnTerrainLayerGUI(TerrainLayer terrainLayer, Terrain terrain)
{
if (!DoesTerrainUseMaskMaps(terrain))
var terrainLayers = terrain.terrainData.terrainLayers;
if (!DoesTerrainUseMaskMaps(terrainLayers))
var layerBlendMode = terrain.materialTemplate.GetFloat("_LayerBlendMode"); // Don't use the member field layerBlendMode as ShaderGUI.OnGUI might not be called if the material is folded.
bool densityUsed = layerBlendMode == 1;
bool heightUsed = layerBlendMode == 2;
terrainLayer.diffuseTexture = EditorGUILayout.ObjectField(densityUsed ? styles.diffuseTexture : styles.diffuseTextureWithoutDensity, terrainLayer.diffuseTexture, typeof(Texture2D), false) as Texture2D;
// Don't use the member field enableHeightBlend as ShaderGUI.OnGUI might not be called if the material UI is folded.
bool heightBlend = terrain.materialTemplate.HasProperty(kEnableHeightBlend) && terrain.materialTemplate.GetFloat(kEnableHeightBlend) > 0;
terrainLayer.diffuseTexture = EditorGUILayout.ObjectField(styles.diffuseTexture, terrainLayer.diffuseTexture, typeof(Texture2D), false) as Texture2D;
TerrainLayerUtility.ValidateDiffuseTextureUI(terrainLayer.diffuseTexture);
var diffuseRemapMin = terrainLayer.diffuseRemapMin;

bool enableDensity = false;
rect.height = 16;
rect = EditorGUI.PrefixLabel(rect, styles.tint);
rect.width = 64;
rect.height = 16;
var tintRect = EditorGUI.PrefixLabel(rect, styles.colorTint);
tintRect.width = 64;
diffuseTint = EditorGUI.ColorField(rect, GUIContent.none, diffuseTint, true, false, false);
diffuseTint = EditorGUI.ColorField(tintRect, GUIContent.none, diffuseTint, true, false, false);
}
if (densityUsed)
{
bool enableDensity = EditorGUILayout.Toggle(styles.enableDensity, diffuseRemapMax.w == 1);
diffuseRemapMax.w = enableDensity ? 1 : 0;
diffuseRemapMin.w = terrainLayer.diffuseTexture == null && enableDensity ? 1 : 0;
if (!heightBlend)
{
rect.y += 16;
++EditorGUI.indentLevel;
var densityToggleRect = EditorGUI.PrefixLabel(rect, styles.opacityAsDensity);
--EditorGUI.indentLevel;
densityToggleRect.width = 64;
enableDensity = EditorGUI.Toggle(densityToggleRect, diffuseRemapMin.w > 0);
}
diffuseRemapMax.w = 1;
diffuseRemapMin.w = enableDensity ? 1 : 0;
if (EditorGUI.EndChangeCheck())
{

EditorGUILayout.Space();
}
terrainLayer.maskMapTexture = EditorGUILayout.ObjectField(heightUsed ? styles.maskMapTexture : styles.maskMapTextureWithoutHeight, terrainLayer.maskMapTexture, typeof(Texture2D), false) as Texture2D;
terrainLayer.maskMapTexture = EditorGUILayout.ObjectField(heightBlend ? styles.maskMapTexture : styles.maskMapTextureWithoutHeight, terrainLayer.maskMapTexture, typeof(Texture2D), false) as Texture2D;
TerrainLayerUtility.ValidateMaskMapTextureUI(terrainLayer.maskMapTexture);
var maskMapRemapMin = terrainLayer.maskMapRemapMin;

EditorGUILayout.MinMaxSlider(s_Styles.ao, ref min, ref max, 0, 1);
maskMapRemapMin.y = min; maskMapRemapMax.y = max;
if (heightUsed)
if (heightBlend)
{
EditorGUILayout.LabelField(styles.height);
++EditorGUI.indentLevel;

// (height - heightBase) * amplitude
float amplitude = maskMapRemapMax.z - maskMapRemapMin.z;
float amplitude = Mathf.Max(maskMapRemapMax.z - maskMapRemapMin.z, Mathf.Epsilon); // to avoid divide by zero
float heightBase = -maskMapRemapMin.z / amplitude;
amplitude = EditorGUILayout.FloatField(styles.heightAmplitude, amplitude * 100) / 100;
heightBase = EditorGUILayout.FloatField(styles.heightBase, heightBase);

{
maskMapRemapMin.x = maskMapRemapMax.x = EditorGUILayout.Slider(s_Styles.metallic, maskMapRemapMin.x, 0, 1);
maskMapRemapMin.y = maskMapRemapMax.y = EditorGUILayout.Slider(s_Styles.ao, maskMapRemapMin.y, 0, 1);
if (heightUsed)
if (heightBlend)
maskMapRemapMin.z = maskMapRemapMax.z = EditorGUILayout.FloatField(s_Styles.heightCm, maskMapRemapMin.z * 100) / 100;
maskMapRemapMin.w = maskMapRemapMax.w = EditorGUILayout.Slider(s_Styles.smoothness, maskMapRemapMin.w, 0, 1);
}

4
com.unity.render-pipelines.high-definition/HDRP/Material/TerrainLit/TerrainLit.shader


{
Properties
{
[HideInInspector] [Enum(Alpha, 0, Density, 1, Height, 2)] _LayerBlendMode("Layer Blend Mode", Int) = 0
[HideInInspector] [ToggleUI] _EnableHeightBlend("EnableHeightBlend", Float) = 0.0
_HeightTransition("Height Transition", Range(0, 1.0)) = 0.0
// TODO: support tri-planar?

#pragma only_renderers d3d11 ps4 xboxone vulkan metal
#pragma shader_feature _TERRAIN_8_LAYERS
#pragma shader_feature _ _TERRAIN_BLEND_DENSITY _TERRAIN_BLEND_HEIGHT
#pragma shader_feature _ _TERRAIN_BLEND_HEIGHT
#pragma shader_feature _NORMALMAP
#pragma shader_feature _MASKMAP
// Sample normal in pixel shader when doing instancing

108
com.unity.render-pipelines.high-definition/HDRP/Material/TerrainLit/TerrainLitSplatCommon.hlsl


float weights[_LAYER_COUNT];
ZERO_INITIALIZE_ARRAY(float, weights, _LAYER_COUNT);
#if defined(_TERRAIN_BLEND_HEIGHT) && defined(_MASKMAP)
// Modify blendMask to take into account the height of the layer. Higher height should be more visible.
float maxHeight = masks[0].z;
maxHeight = max(maxHeight, masks[1].z);
maxHeight = max(maxHeight, masks[2].z);
maxHeight = max(maxHeight, masks[3].z);
#ifdef _TERRAIN_8_LAYERS
maxHeight = max(maxHeight, masks[4].z);
maxHeight = max(maxHeight, masks[5].z);
maxHeight = max(maxHeight, masks[6].z);
maxHeight = max(maxHeight, masks[7].z);
#endif
// Make sure that transition is not zero otherwise the next computation will be wrong.
// The epsilon here also has to be bigger than the epsilon in the next computation.
float transition = max(_HeightTransition, 1e-5);
#ifdef _MASKMAP
#ifdef _TERRAIN_BLEND_HEIGHT
// Modify blendMask to take into account the height of the layer. Higher height should be more visible.
float maxHeight = masks[0].z;
maxHeight = max(maxHeight, masks[1].z);
maxHeight = max(maxHeight, masks[2].z);
maxHeight = max(maxHeight, masks[3].z);
#ifdef _TERRAIN_8_LAYERS
maxHeight = max(maxHeight, masks[4].z);
maxHeight = max(maxHeight, masks[5].z);
maxHeight = max(maxHeight, masks[6].z);
maxHeight = max(maxHeight, masks[7].z);
#endif
// The goal here is to have all but the highest layer at negative heights, then we add the transition so that if the next highest layer is near transition it will have a positive value.
// Then we clamp this to zero and normalize everything so that highest layer has a value of 1.
float4 weightedHeights0 = { masks[0].z, masks[1].z, masks[2].z, masks[3].z };
weightedHeights0 = weightedHeights0 - maxHeight.xxxx;
// We need to add an epsilon here for active layers (hence the blendMask again) so that at least a layer shows up if everything's too low.
weightedHeights0 = (max(0, weightedHeights0 + transition) + 1e-6) * blendMasks0;
// Make sure that transition is not zero otherwise the next computation will be wrong.
// The epsilon here also has to be bigger than the epsilon in the next computation.
float transition = max(_HeightTransition, 1e-5);
#ifdef _TERRAIN_8_LAYERS
float4 weightedHeights1 = { masks[4].z, masks[5].z, masks[6].z, masks[7].z };
weightedHeights1 = weightedHeights1 - maxHeight.xxxx;
weightedHeights1 = (max(0, weightedHeights1 + transition) + 1e-6) * blendMasks1;
#else
float4 weightedHeights1 = { 0, 0, 0, 0 };
#endif
// The goal here is to have all but the highest layer at negative heights, then we add the transition so that if the next highest layer is near transition it will have a positive value.
// Then we clamp this to zero and normalize everything so that highest layer has a value of 1.
float4 weightedHeights0 = { masks[0].z, masks[1].z, masks[2].z, masks[3].z };
weightedHeights0 = weightedHeights0 - maxHeight.xxxx;
// We need to add an epsilon here for active layers (hence the blendMask again) so that at least a layer shows up if everything's too low.
weightedHeights0 = (max(0, weightedHeights0 + transition) + 1e-6) * blendMasks0;
// Normalize
float sumHeight = GetSumHeight(weightedHeights0, weightedHeights1);
blendMasks0 = weightedHeights0 / sumHeight.xxxx;
#ifdef _TERRAIN_8_LAYERS
blendMasks1 = weightedHeights1 / sumHeight.xxxx;
#endif
#ifdef _TERRAIN_8_LAYERS
float4 weightedHeights1 = { masks[4].z, masks[5].z, masks[6].z, masks[7].z };
weightedHeights1 = weightedHeights1 - maxHeight.xxxx;
weightedHeights1 = (max(0, weightedHeights1 + transition) + 1e-6) * blendMasks1;
#else
float4 weightedHeights1 = { 0, 0, 0, 0 };
#endif
#elif defined(_TERRAIN_BLEND_DENSITY) && defined(_MASKMAP)
// Denser layers are more visible.
float4 opacityAsDensity0 = saturate((float4(albedo[0].a, albedo[1].a, albedo[2].a, albedo[3].a) - (float4(1.0, 1.0, 1.0, 1.0) - blendMasks0)) * 20.0); // 20.0 is the number of steps in inputAlphaMask (Density mask. We decided 20 empirically)
float4 useOpacityAsDensityParam0 = { _DiffuseRemapScale0.w, _DiffuseRemapScale1.w, _DiffuseRemapScale2.w, _DiffuseRemapScale3.w };
blendMasks0 = lerp(blendMasks0, opacityAsDensity0, useOpacityAsDensityParam0);
#ifdef _TERRAIN_8_LAYERS
float4 opacityAsDensity1 = saturate((float4(albedo[4].a, albedo[5].a, albedo[6].a, albedo[7].a) - (float4(1.0, 1.0, 1.0, 1.0) - blendMasks1)) * 20.0); // 20.0 is the number of steps in inputAlphaMask (Density mask. We decided 20 empirically)
float4 useOpacityAsDensityParam1 = { _DiffuseRemapScale4.w, _DiffuseRemapScale5.w, _DiffuseRemapScale6.w, _DiffuseRemapScale7.w };
blendMasks1 = lerp(blendMasks1, opacityAsDensity1, useOpacityAsDensityParam1);
#endif
#endif
// Normalize
float sumHeight = GetSumHeight(weightedHeights0, weightedHeights1);
blendMasks0 = weightedHeights0 / sumHeight.xxxx;
#ifdef _TERRAIN_8_LAYERS
blendMasks1 = weightedHeights1 / sumHeight.xxxx;
#endif
#else
// Denser layers are more visible.
float4 opacityAsDensity0 = saturate((float4(albedo[0].a, albedo[1].a, albedo[2].a, albedo[3].a) - (float4(1.0, 1.0, 1.0, 1.0) - blendMasks0)) * 20.0); // 20.0 is the number of steps in inputAlphaMask (Density mask. We decided 20 empirically)
float4 useOpacityAsDensityParam0 = { _DiffuseRemapScale0.w, _DiffuseRemapScale1.w, _DiffuseRemapScale2.w, _DiffuseRemapScale3.w }; // 1 is off
blendMasks0 = lerp(opacityAsDensity0, blendMasks0, useOpacityAsDensityParam0);
#ifdef _TERRAIN_8_LAYERS
float4 opacityAsDensity1 = saturate((float4(albedo[4].a, albedo[5].a, albedo[6].a, albedo[7].a) - (float4(1.0, 1.0, 1.0, 1.0) - blendMasks1)) * 20.0); // 20.0 is the number of steps in inputAlphaMask (Density mask. We decided 20 empirically)
float4 useOpacityAsDensityParam1 = { _DiffuseRemapScale4.w, _DiffuseRemapScale5.w, _DiffuseRemapScale6.w, _DiffuseRemapScale7.w };
blendMasks1 = lerp(opacityAsDensity1, blendMasks1, useOpacityAsDensityParam1);
#endif
#endif // if _TERRAIN_BLEND_HEIGHT
#endif // if _MASKMAP
weights[0] = blendMasks0.x;
weights[1] = blendMasks0.y;

weights[7] = blendMasks1.w;
#endif
#if defined(_TERRAIN_BLEND_DENSITY) && defined(_MASKMAP)
#if defined(_MASKMAP) && !defined(_TERRAIN_BLEND_HEIGHT)
bool densityBlendEnabled = any(useOpacityAsDensityParam0 < 1);
#ifdef _TERRAIN_8_LAYERS
densityBlendEnabled = densityBlendEnabled || any(useOpacityAsDensityParam1 < 1);
#endif
// calculate weight of each layers
// Algorithm is like this:
// Top layer have priority on others layers

UNITY_UNROLL for (int i = _LAYER_COUNT - 1; i >= 0; --i)
if (densityBlendEnabled)
weights[i] = min(weights[i], (1.0 - weightsSum));
weightsSum = saturate(weightsSum + weights[i]);
UNITY_UNROLL for (int i = _LAYER_COUNT - 1; i >= 0; --i)
{
weights[i] = min(weights[i], (1.0 - weightsSum));
weightsSum = saturate(weightsSum + weights[i]);
}
}
#endif

2
com.unity.render-pipelines.high-definition/HDRP/Material/TerrainLit/TerrainLit_Basemap_Gen.shader


#pragma shader_feature _TERRAIN_8_LAYERS
#pragma shader_feature _NORMALMAP
#pragma shader_feature _MASKMAP
#pragma shader_feature _ _TERRAIN_BLEND_DENSITY _TERRAIN_BLEND_HEIGHT
#pragma shader_feature _ _TERRAIN_BLEND_HEIGHT
#ifdef _MASKMAP
// Needed because unity tries to match the name of the used textures to samplers. Masks can be used without splats in Metallic pass.

正在加载...
取消
保存