secondlife/viewer#1883: Local-only PBR terrain paintmap with developer tools

master
Cosmic Linden 2024-07-11 15:54:24 -07:00
parent ae2ff22542
commit 6aa9110b2a
26 changed files with 848 additions and 136 deletions

View File

@ -1409,6 +1409,7 @@ void LLShaderMgr::initAttribsAndUniforms()
mReservedUniforms.push_back("detail_3");
mReservedUniforms.push_back("alpha_ramp");
mReservedUniforms.push_back("paint_map");
mReservedUniforms.push_back("detail_0_base_color");
mReservedUniforms.push_back("detail_1_base_color");
@ -1433,6 +1434,8 @@ void LLShaderMgr::initAttribsAndUniforms()
mReservedUniforms.push_back("emissiveColors");
mReservedUniforms.push_back("minimum_alphas");
mReservedUniforms.push_back("region_scale");
mReservedUniforms.push_back("origin");
mReservedUniforms.push_back("display_gamma");

View File

@ -267,6 +267,7 @@ public:
TERRAIN_DETAIL3, // "detail_3"
TERRAIN_ALPHARAMP, // "alpha_ramp"
TERRAIN_PAINTMAP, // "paint_map"
TERRAIN_DETAIL0_BASE_COLOR, // "detail_0_base_color" (GLTF)
TERRAIN_DETAIL1_BASE_COLOR, // "detail_1_base_color" (GLTF)
@ -291,6 +292,8 @@ public:
TERRAIN_EMISSIVE_COLORS, // "emissiveColors" (GLTF)
TERRAIN_MINIMUM_ALPHAS, // "minimum_alphas" (GLTF)
REGION_SCALE, // "region_scale" (GLTF)
SHINY_ORIGIN, // "origin"
DISPLAY_GAMMA, // "display_gamma"

View File

@ -578,6 +578,7 @@ set(viewer_SOURCE_FILES
llsyswellwindow.cpp
llteleporthistory.cpp
llteleporthistorystorage.cpp
llterrainpaintmap.cpp
lltexturecache.cpp
lltexturectrl.cpp
lltexturefetch.cpp
@ -1235,6 +1236,7 @@ set(viewer_HEADER_FILES
lltable.h
llteleporthistory.h
llteleporthistorystorage.h
llterrainpaintmap.h
lltexturecache.h
lltexturectrl.h
lltexturefetch.h

View File

@ -14965,6 +14965,39 @@
<string>F32</string>
<key>Value</key>
<real>0.0</real>
</map>
<key>LocalTerrainPaintEnabled</key>
<map>
<key>Comment</key>
<string>Enables local paintmap if LocalTerrainAsset1, etc are set</string>
<key>Persist</key>
<integer>0</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>TerrainPaintBitDepth</key>
<map>
<key>Comment</key>
<string>Bit depth for future terrain paint map operations. Min: 1. Max: 8. Takes effect when the paint map is created or modified. Modifications to an existing paintmap of different bit depth will have lower precision.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>U32</string>
<key>Value</key>
<integer>5</integer>
</map>
<key>TerrainPaintResolution</key>
<map>
<key>Comment</key>
<string>Resolution of the terrain paint map in pixels. Rounded to a power of two. Min: 16. Max: RenderMaxTextureResolution. Takes effect when the paint map is created.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>U32</string>
<key>Value</key>
<integer>2048</integer>
</map>
<key>PathfindingRetrieveNeighboringRegion</key>
<map>

View File

@ -30,6 +30,9 @@
#define TERRAIN_PBR_DETAIL_NORMAL -2
#define TERRAIN_PBR_DETAIL_METALLIC_ROUGHNESS -3
#define TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE 0
#define TERRAIN_PAINT_TYPE_PBR_PAINTMAP 1
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
#define TerrainCoord vec4[3]
#elif TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 1
@ -48,6 +51,7 @@ struct TerrainMix
};
TerrainMix get_terrain_mix_weights(float alpha1, float alpha2, float alphaFinal);
TerrainMix get_terrain_usage_from_weight3(vec3 weight3);
struct PBRMix
{
@ -97,7 +101,11 @@ PBRMix mix_pbr(PBRMix mix1, PBRMix mix2, float mix2_weight);
out vec4 frag_data[4];
#if TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE
uniform sampler2D alpha_ramp;
#elif TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_PBR_PAINTMAP
uniform sampler2D paint_map;
#endif
// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#additional-textures
uniform sampler2D detail_0_base_color;
@ -133,19 +141,25 @@ uniform vec3[4] emissiveColors;
#endif
uniform vec4 minimum_alphas; // PBR alphaMode: MASK, See: mAlphaCutoff, setAlphaCutoff()
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
in vec4[10] vary_coords;
#elif TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 1
in vec4[2] vary_coords;
#endif
in vec3 vary_position;
in vec3 vary_normal;
#if (TERRAIN_PBR_DETAIL >= TERRAIN_PBR_DETAIL_NORMAL)
in vec3 vary_tangents[4];
flat in float vary_signs[4];
#endif
// vary_texcoord* are used for terrain composition, vary_coords are used for terrain UVs
#if TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE
in vec4 vary_texcoord0;
in vec4 vary_texcoord1;
#elif TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_PBR_PAINTMAP
in vec2 vary_texcoord;
#endif
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
in vec4[10] vary_coords;
#elif TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 1
in vec4[2] vary_coords;
#endif
void mirrorClip(vec3 position);
@ -171,11 +185,16 @@ void main()
// Make sure we clip the terrain if we're in a mirror.
mirrorClip(vary_position);
TerrainMix tm;
#if TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE
float alpha1 = texture(alpha_ramp, vary_texcoord0.zw).a;
float alpha2 = texture(alpha_ramp,vary_texcoord1.xy).a;
float alphaFinal = texture(alpha_ramp, vary_texcoord1.zw).a;
TerrainMix tm = get_terrain_mix_weights(alpha1, alpha2, alphaFinal);
tm = get_terrain_mix_weights(alpha1, alpha2, alphaFinal);
#elif TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_PBR_PAINTMAP
tm = get_terrain_usage_from_weight3(texture(paint_map, vary_texcoord).xyz);
#endif
#if (TERRAIN_PBR_DETAIL >= TERRAIN_PBR_DETAIL_OCCLUSION)
// RGB = Occlusion, Roughness, Metal

View File

@ -51,7 +51,12 @@
#define TERRAIN_PBR_DETAIL_NORMAL -2
#define TERRAIN_PBR_DETAIL_METALLIC_ROUGHNESS -3
#define TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE 0
#define TERRAIN_PAINT_TYPE_PBR_PAINTMAP 1
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
in vec3 vary_vertex_normal;
#endif
vec3 srgb_to_linear(vec3 c);
@ -202,6 +207,45 @@ TerrainMix get_terrain_mix_weights(float alpha1, float alpha2, float alphaFinal)
return tm;
}
// A paintmap weight applier for 4 swatches. The input saves a channel by not
// storing swatch 1, and assuming the weights of the 4 swatches add to 1.
// The components of weight3 should be between 0 and 1
// The sum of the components of weight3 should be between 0 and 1
TerrainMix get_terrain_usage_from_weight3(vec3 weight3)
{
// These steps ensure the output weights add to between 0 and 1
weight3.xyz = max(vec3(0.0), weight3.xyz);
weight3.xyz /= max(1.0, weight3.x + weight3.y + weight3.z);
TerrainMix tm;
// Extract the first weight from the other weights
tm.weight.x = 1.0 - (weight3.x + weight3.y + weight3.z);
tm.weight.yzw = weight3.xyz;
ivec4 usage = max(ivec4(0), ivec4(ceil(tm.weight)));
tm.type = (usage.x * MIX_X) |
(usage.y * MIX_Y) |
(usage.z * MIX_Z) |
(usage.w * MIX_W);
return tm;
}
// Inverse of get_terrain_usage_from_weight3, excluding usage flags
// The components of weight should be between 0 and 1
// The sum of the components of weight should be 1
vec3 get_weight3_from_terrain_weight(vec4 weight)
{
// These steps ensure the input weights add to 1
weight = max(vec4(0.0), weight);
weight.x += 1.0 - sign(weight.x + weight.y + weight.z + weight.w);
weight /= weight.x + weight.y + weight.z + weight.w;
// Then return the input weights with the first weight truncated
return weight.yzw;
}
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
TerrainTriplanar _t_triplanar()
{
float sharpness = TERRAIN_TRIPLANAR_BLEND_FACTOR;
@ -219,6 +263,8 @@ TerrainTriplanar _t_triplanar()
((usage.z) * SAMPLE_Z);
return tw;
}
#endif
// Assume weights add to 1
float terrain_mix(TerrainMix tm, vec4 tms4)

View File

@ -28,31 +28,47 @@
#define TERRAIN_PBR_DETAIL_NORMAL -2
#define TERRAIN_PBR_DETAIL_METALLIC_ROUGHNESS -3
#define TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE 0
#define TERRAIN_PAINT_TYPE_PBR_PAINTMAP 1
uniform mat3 normal_matrix;
uniform mat4 texture_matrix0;
uniform mat4 modelview_matrix;
uniform mat4 modelview_projection_matrix;
#if TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_PBR_PAINTMAP
uniform float region_scale;
#endif
in vec3 position;
in vec3 normal;
in vec4 tangent;
in vec4 diffuse_color;
#if TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE
in vec2 texcoord1;
#endif
out vec3 vary_vertex_normal; // Used by pbrterrainUtilF.glsl
out vec3 vary_position;
out vec3 vary_normal;
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
out vec3 vary_vertex_normal; // Used by pbrterrainUtilF.glsl
#endif
#if (TERRAIN_PBR_DETAIL >= TERRAIN_PBR_DETAIL_NORMAL)
out vec3 vary_tangents[4];
flat out float vary_signs[4];
#endif
// vary_texcoord* are used for terrain composition, vary_coords are used for terrain UVs
#if TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE
out vec4 vary_texcoord0;
out vec4 vary_texcoord1;
#elif TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_PBR_PAINTMAP
out vec2 vary_texcoord;
#endif
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
out vec4[10] vary_coords;
#elif TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 1
out vec4[2] vary_coords;
#endif
out vec3 vary_position;
// *HACK: Each material uses only one texture transform, but the KHR texture
// transform spec allows handling texture transforms separately for each
@ -69,7 +85,9 @@ void main()
vary_position = (modelview_matrix*vec4(position.xyz, 1.0)).xyz;
vec3 n = normal_matrix * normal;
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
vary_vertex_normal = normal;
#endif
vec3 t = normal_matrix * tangent.xyz;
#if (TERRAIN_PBR_DETAIL >= TERRAIN_PBR_DETAIL_NORMAL)
@ -110,9 +128,9 @@ void main()
// Transform and pass tex coords
{
vec4[2] ttt;
#define transform_xy() terrain_texture_transform(position.xy, ttt)
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
// Don't care about upside-down (transform_xy_flipped())
#define transform_xy() terrain_texture_transform(position.xy, ttt)
#define transform_yz() terrain_texture_transform(position.yz, ttt)
#define transform_negx_z() terrain_texture_transform(position.xz * vec2(-1, 1), ttt)
#define transform_yz_flipped() terrain_texture_transform(position.yz * vec2(-1, 1), ttt)
@ -157,26 +175,30 @@ void main()
ttt[0].xyz = terrain_texture_transforms[0].xyz;
ttt[1].x = terrain_texture_transforms[0].w;
ttt[1].y = terrain_texture_transforms[1].x;
vary_coords[0].xy = terrain_texture_transform(position.xy, ttt);
vary_coords[0].xy = transform_xy();
// material 2
ttt[0].xyz = terrain_texture_transforms[1].yzw;
ttt[1].xy = terrain_texture_transforms[2].xy;
vary_coords[0].zw = terrain_texture_transform(position.xy, ttt);
vary_coords[0].zw = transform_xy();
// material 3
ttt[0].xy = terrain_texture_transforms[2].zw;
ttt[0].z = terrain_texture_transforms[3].x;
ttt[1].xy = terrain_texture_transforms[3].yz;
vary_coords[1].xy = terrain_texture_transform(position.xy, ttt);
vary_coords[1].xy = transform_xy();
// material 4
ttt[0].x = terrain_texture_transforms[3].w;
ttt[0].yz = terrain_texture_transforms[4].xy;
ttt[1].xy = terrain_texture_transforms[4].zw;
vary_coords[1].zw = terrain_texture_transform(position.xy, ttt);
vary_coords[1].zw = transform_xy();
#endif
}
vec4 tc = vec4(texcoord1,0,1);
#if TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE
vec2 tc = texcoord1.xy;
vary_texcoord0.zw = tc.xy;
vary_texcoord1.xy = tc.xy-vec2(2.0, 0.0);
vary_texcoord1.zw = tc.xy-vec2(1.0, 0.0);
#elif TERRAIN_PAINT_TYPE == TERRAIN_PAINT_TYPE_PBR_PAINTMAP
vary_texcoord = position.xy / region_scale;
#endif
}

View File

@ -0,0 +1,62 @@
/**
* @file terrainBakeF.glsl
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2024, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
/*[EXTRA_CODE_HERE]*/
out vec4 frag_color;
struct TerrainMix
{
vec4 weight;
int type;
};
TerrainMix get_terrain_mix_weights(float alpha1, float alpha2, float alphaFinal);
uniform sampler2D alpha_ramp;
// vary_texcoord* are used for terrain composition
in vec4 vary_texcoord0;
in vec4 vary_texcoord1;
void main()
{
TerrainMix tm;
float alpha1 = texture(alpha_ramp, vary_texcoord0.zw).a;
float alpha2 = texture(alpha_ramp,vary_texcoord1.xy).a;
float alphaFinal = texture(alpha_ramp, vary_texcoord1.zw).a;
tm = get_terrain_mix_weights(alpha1, alpha2, alphaFinal);
// tm.weight.x can be ignored. The paintmap saves a channel by not storing
// swatch 1, and assuming the weights of the 4 swatches add to 1.
// TERRAIN_PAINT_PRECISION emulates loss of precision at lower bit depth
// when a corresponding low-bit image format is not available. Because
// integral values at one depth cannot be precisely represented at another
// bit depth, rounding is required. To maximize numerical stability for
// future conversions, bit depth conversions should round to the nearest
// integer, not floor or ceil.
frag_color = max(vec4(round(tm.weight.yzw * TERRAIN_PAINT_PRECISION) / TERRAIN_PAINT_PRECISION, 1.0), vec4(0));
}

View File

@ -0,0 +1,42 @@
/**
* @file pbrTerrainBakeV.glsl
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2024, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
uniform mat4 modelview_projection_matrix;
in vec3 position;
in vec2 texcoord1;
out vec4 vary_texcoord0;
out vec4 vary_texcoord1;
void main()
{
gl_Position = modelview_projection_matrix * vec4(position.xyz, 1.0);
vec2 tc = texcoord1.xy;
vary_texcoord0.zw = tc.xy;
vary_texcoord1.xy = tc.xy-vec2(2.0, 0.0);
vary_texcoord1.zw = tc.xy-vec2(1.0, 0.0);
}

View File

@ -219,7 +219,9 @@ void LLDrawPoolTerrain::renderFullShader()
else
{
// Use materials
sShader = &gDeferredPBRTerrainProgram;
U32 paint_type = use_local_materials ? gLocalTerrainMaterials.getPaintType() : compp->getPaintType();
paint_type = llclamp(paint_type, 0, TERRAIN_PAINT_TYPE_COUNT);
sShader = &gDeferredPBRTerrainProgram[paint_type];
sShader->bind();
renderFullShaderPBR(use_local_materials);
}
@ -326,7 +328,7 @@ void LLDrawPoolTerrain::renderFullShaderTextures()
}
// *TODO: Investigate use of bindFast for PBR terrain textures
void LLDrawPoolTerrain::renderFullShaderPBR(bool local_materials)
void LLDrawPoolTerrain::renderFullShaderPBR(bool use_local_materials)
{
// Hack! Get the region that this draw pool is rendering from!
LLViewerRegion *regionp = mDrawFace[0]->getDrawable()->getVObj()->getRegion();
@ -339,7 +341,7 @@ void LLDrawPoolTerrain::renderFullShaderPBR(bool local_materials)
llassert(shader_material_count == terrain_material_count);
#endif
if (local_materials)
if (use_local_materials)
{
// Override region terrain with the global local override terrain
fetched_materials = &gLocalTerrainMaterials.mDetailRenderMaterials;
@ -351,6 +353,9 @@ void LLDrawPoolTerrain::renderFullShaderPBR(bool local_materials)
if (!materials[i]) { materials[i] = &LLGLTFMaterial::sDefault; }
}
U32 paint_type = use_local_materials ? gLocalTerrainMaterials.getPaintType() : compp->getPaintType();
paint_type = llclamp(paint_type, 0, TERRAIN_PAINT_TYPE_COUNT);
S32 detail_basecolor[terrain_material_count];
S32 detail_normal[terrain_material_count];
S32 detail_metalrough[terrain_material_count];
@ -481,11 +486,31 @@ void LLDrawPoolTerrain::renderFullShaderPBR(bool local_materials)
LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater();
//
// Alpha Ramp
// Alpha Ramp or paint map
//
S32 alpha_ramp = sShader->enableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
gGL.getTexUnit(alpha_ramp)->bind(m2DAlphaRampImagep);
gGL.getTexUnit(alpha_ramp)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
S32 alpha_ramp = -1;
S32 paint_map = -1;
if (paint_type == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE)
{
alpha_ramp = sShader->enableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
gGL.getTexUnit(alpha_ramp)->bind(m2DAlphaRampImagep);
gGL.getTexUnit(alpha_ramp)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
}
else if (paint_type == TERRAIN_PAINT_TYPE_PBR_PAINTMAP)
{
paint_map = sShader->enableTexture(LLViewerShaderMgr::TERRAIN_PAINTMAP);
LLViewerTexture* tex_paint_map = use_local_materials ? gLocalTerrainMaterials.getPaintMap() : compp->getPaintMap();
// If no paintmap is available, fall back to rendering just material slot 1 (by binding the appropriate image)
if (!tex_paint_map) { tex_paint_map = LLViewerTexture::sBlackImagep.get(); }
// This is a paint map for four materials, but we save a channel by
// storing the paintmap as the "difference" between slot 1 and the
// other 3 slots.
llassert(tex_paint_map->getComponents() == 3);
gGL.getTexUnit(paint_map)->bind(tex_paint_map);
gGL.getTexUnit(paint_map)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
shader->uniform1f(LLShaderMgr::REGION_SCALE, regionp->getWidth());
}
//
// GLTF uniforms
@ -534,11 +559,22 @@ void LLDrawPoolTerrain::renderFullShaderPBR(bool local_materials)
// Disable multitexture
sShader->disableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
if (paint_type == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE)
{
sShader->disableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
gGL.getTexUnit(alpha_ramp)->unbind(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(alpha_ramp)->disable();
gGL.getTexUnit(alpha_ramp)->activate();
gGL.getTexUnit(alpha_ramp)->unbind(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(alpha_ramp)->disable();
gGL.getTexUnit(alpha_ramp)->activate();
}
else if (paint_type == TERRAIN_PAINT_TYPE_PBR_PAINTMAP)
{
sShader->disableTexture(LLViewerShaderMgr::TERRAIN_PAINTMAP);
gGL.getTexUnit(paint_map)->unbind(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(paint_map)->disable();
gGL.getTexUnit(paint_map)->activate();
}
for (U32 i = 0; i < terrain_material_count; ++i)
{

View File

@ -38,7 +38,6 @@ public:
VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX |
LLVertexBuffer::MAP_NORMAL |
LLVertexBuffer::MAP_TANGENT | // Only PBR terrain uses this currently
LLVertexBuffer::MAP_TEXCOORD0 |
LLVertexBuffer::MAP_TEXCOORD1
};
@ -80,7 +79,7 @@ protected:
void renderFull4TU();
void renderFullShader();
void renderFullShaderTextures();
void renderFullShaderPBR(bool local_materials = false);
void renderFullShaderPBR(bool use_local_materials = false);
void drawLoop();
private:

View File

@ -110,6 +110,7 @@ public:
LLSurfacePatch *resolvePatchRegion(const F32 x, const F32 y) const;
LLSurfacePatch *resolvePatchRegion(const LLVector3 &position_region) const;
LLSurfacePatch *resolvePatchGlobal(const LLVector3d &position_global) const;
LLSurfacePatch *getPatch(const S32 x, const S32 y) const;
// Update methods (called during idle, normally)
template<bool PBR>
@ -176,8 +177,6 @@ protected:
void createPatchData(); // Allocates memory for patches.
void destroyPatchData(); // Deallocates memory for patches.
LLSurfacePatch *getPatch(const S32 x, const S32 y) const;
protected:
LLVector3d mOriginGlobal; // In absolute frame
LLSurfacePatch *mPatchList; // Array of all patches

View File

@ -201,13 +201,13 @@ LLVector2 LLSurfacePatch::getTexCoords(const U32 x, const U32 y) const
void LLSurfacePatch::eval(const U32 x, const U32 y, const U32 stride, LLVector3 *vertex, LLVector3 *normal,
LLVector2 *tex0, LLVector2 *tex1)
LLVector2 *tex1) const
{
if (!mSurfacep || !mSurfacep->getRegion() || !mSurfacep->getGridsPerEdge() || !mVObjp)
{
return; // failsafe
}
llassert_always(vertex && normal && tex0 && tex1);
llassert_always(vertex && normal && tex1);
U32 surface_stride = mSurfacep->getGridsPerEdge();
U32 point_offset = x + y*surface_stride;
@ -220,12 +220,6 @@ void LLSurfacePatch::eval(const U32 x, const U32 y, const U32 stride, LLVector3
pos_agent.mV[VZ] = *(mDataZ + point_offset);
*vertex = pos_agent-mVObjp->getRegion()->getOriginAgent();
LLVector3 rel_pos = pos_agent - mSurfacep->getOriginAgent();
// *NOTE: Only PBR terrain uses the UVs right now. Texture terrain just ignores it.
// *NOTE: In the future, UVs and horizontal position will no longer have a 1:1 relationship for PBR terrain
LLVector3 tex_pos = rel_pos;
tex0->mV[0] = tex_pos.mV[0];
tex0->mV[1] = tex_pos.mV[1];
tex1->mV[0] = mSurfacep->getRegion()->getCompositionXY(llfloor(mOriginRegion.mV[0])+x, llfloor(mOriginRegion.mV[1])+y);
const F32 xyScale = 4.9215f*7.f; //0.93284f;

View File

@ -116,7 +116,7 @@ public:
void calcNormalFlat(LLVector3& normal_out, const U32 x, const U32 y, const U32 index /* 0 or 1 */);
void eval(const U32 x, const U32 y, const U32 stride,
LLVector3 *vertex, LLVector3 *normal, LLVector2 *tex0, LLVector2 *tex1);
LLVector3 *vertex, LLVector3 *normal, LLVector2 *tex1) const;
@ -146,6 +146,8 @@ public:
void dirty(); // Mark this surface patch as dirty...
void clearDirty() { mDirty = false; }
bool isHeightsGenerated() const { return mHeightsGenerated; }
void clearVObj();
public:

View File

@ -0,0 +1,285 @@
/**
* @file llterrainpaintmap.cpp
* @brief Utilities for managing terrain paint maps
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2024, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llterrainpaintmap.h"
#include "llviewerprecompiledheaders.h"
// library includes
#include "llglslshader.h"
#include "llrendertarget.h"
#include "llvertexbuffer.h"
// newview includes
#include "llrender.h"
#include "llsurface.h"
#include "llsurfacepatch.h"
#include "llviewercamera.h"
#include "llviewerregion.h"
#include "llviewershadermgr.h"
#include "llviewertexture.h"
// static
bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion& region, LLViewerTexture& tex)
{
llassert(tex.getComponents() == 3);
llassert(tex.getWidth() > 0 && tex.getHeight() > 0);
llassert(tex.getWidth() == tex.getHeight());
llassert(tex.getPrimaryFormat() == GL_RGB);
llassert(tex.getGLTexture());
const LLSurface& surface = region.getLand();
const U32 patch_count = surface.getPatchesPerEdge();
// *TODO: mHeightsGenerated isn't guaranteed to be true. Assume terrain is
// loaded for now. Would be nice to fix the loading issue or find a better
// heuristic to determine that the terrain is sufficiently loaded.
#if 0
// Don't proceed if the region heightmap isn't loaded
for (U32 rj = 0; rj < patch_count; ++rj)
{
for (U32 ri = 0; ri < patch_count; ++ri)
{
const LLSurfacePatch* patch = surface.getPatch(ri, rj);
if (!patch->isHeightsGenerated())
{
LL_WARNS() << "Region heightmap not fully loaded" << LL_ENDL;
return false;
}
}
}
#endif
// Bind the debug shader and render terrain to tex
// Use a scratch render target because its dimensions may exceed the standard bake target, and this is a one-off bake
LLRenderTarget scratch_target;
const S32 dim = llmin(tex.getWidth(), tex.getHeight());
scratch_target.allocate(dim, dim, GL_RGB, false, LLTexUnit::eTextureType::TT_TEXTURE,
LLTexUnit::eTextureMipGeneration::TMG_NONE);
if (!scratch_target.isComplete())
{
llassert(false);
LL_WARNS() << "Failed to allocate render target" << LL_ENDL;
return false;
}
gGL.getTexUnit(0)->disable();
stop_glerror();
scratch_target.bindTarget();
glClearColor(0, 0, 0, 0);
scratch_target.clear();
// Render terrain heightmap to paint map via shader
// Set up viewport, camera, and orthographic projection matrix. Position
// the camera such that the camera points straight down, and the region
// completely covers the "screen". Since orthographic projection does not
// distort, we arbitrarily choose the near plane and far plane to cover the
// full span of region heights, plus a small amount of padding to account
// for rounding errors.
const F32 region_width = region.getWidth();
const F32 region_half_width = region_width / 2.0f;
const F32 region_camera_height = surface.getMaxZ() + DEFAULT_NEAR_PLANE;
LLViewerCamera camera;
const LLVector3 region_center = LLVector3(region_half_width, region_half_width, 0.0) + region.getOriginAgent();
const LLVector3 camera_origin = LLVector3(0.0f, 0.0f, region_camera_height) + region_center;
camera.lookAt(camera_origin, region_center, LLVector3::y_axis);
camera.setAspect(F32(scratch_target.getHeight()) / F32(scratch_target.getWidth()));
const LLRect texture_rect(0, scratch_target.getHeight(), scratch_target.getWidth(), 0);
glViewport(texture_rect.mLeft, texture_rect.mBottom, texture_rect.getWidth(), texture_rect.getHeight());
// Manually get modelview matrix from camera orientation.
glh::matrix4f modelview((GLfloat *) OGL_TO_CFR_ROTATION);
GLfloat ogl_matrix[16];
camera.getOpenGLTransform(ogl_matrix);
modelview *= glh::matrix4f(ogl_matrix);
gGL.matrixMode(LLRender::MM_MODELVIEW);
gGL.loadMatrix(modelview.m);
// Override the projection matrix from the camera
gGL.matrixMode(LLRender::MM_PROJECTION);
gGL.pushMatrix();
gGL.loadIdentity();
llassert(camera_origin.mV[VZ] >= surface.getMaxZ());
const F32 region_high_near = camera_origin.mV[VZ] - surface.getMaxZ();
constexpr F32 far_plane_delta = 0.25f;
const F32 region_low_far = camera_origin.mV[VZ] - surface.getMinZ() + far_plane_delta;
gGL.ortho(-region_half_width, region_half_width, -region_half_width, region_half_width, region_high_near, region_low_far);
// No need to call camera.setPerspective because we don't need the clip planes. It would be inaccurate due to the perspective rendering anyway.
// Need to get the full resolution vertices in order to get an accurate
// paintmap. It's not sufficient to iterate over the surface patches, as
// they may be at lower LODs.
// The functionality here is a subset of
// LLVOSurfacePatch::getTerrainGeometry. Unlike said function, we don't
// care about stride length since we're always rendering at full
// resolution. We also don't care about normals/tangents because those
// don't contribute to the paintmap.
// *NOTE: The actual getTerrainGeometry fits the terrain vertices snugly
// under the 16-bit indices limit. For the sake of simplicity, that has not
// been replicated here.
std::vector<LLPointer<LLDrawInfo>> infos;
// Vertex and index counts adapted from LLVOSurfacePatch::getGeomSizesMain,
// with additional vertices added as we are including the north and east
// edges here.
const U32 patch_size = (U32)surface.getGridsPerPatchEdge();
constexpr U32 stride = 1;
const U32 vert_size = (patch_size / stride) + 1;
const U32 n = vert_size * vert_size;
const U32 ni = 6 * (vert_size - 1) * (vert_size - 1);
const U32 region_vertices = n * patch_count * patch_count;
const U32 region_indices = ni * patch_count * patch_count;
if (LLGLSLShader::sCurBoundShaderPtr == nullptr)
{ // make sure a shader is bound to satisfy mVertexBuffer->setBuffer
gDebugProgram.bind();
}
LLPointer<LLVertexBuffer> buf = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD1);
{
buf->allocateBuffer(region_vertices, region_indices*2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used
buf->setBuffer();
U32 vertex_total = 0;
std::vector<U32> index_array(region_indices);
std::vector<LLVector4a> positions(region_vertices);
std::vector<LLVector2> texcoords1(region_vertices);
auto idx = index_array.begin();
auto pos = positions.begin();
auto tex1 = texcoords1.begin();
for (U32 rj = 0; rj < patch_count; ++rj)
{
for (U32 ri = 0; ri < patch_count; ++ri)
{
const U32 index_offset = vertex_total;
for (U32 j = 0; j < (vert_size - 1); ++j)
{
for (U32 i = 0; i < (vert_size - 1); ++i)
{
// y
// 2....3
// ^ . .
// | 0....1
// |
// -------> x
//
// triangle 1: 0,1,2
// triangle 2: 1,3,2
// 0: vert0
// 1: vert0 + 1
// 2: vert0 + vert_size
// 3: vert0 + vert_size + 1
const U32 vert0 = index_offset + i + (j*vert_size);
*idx++ = vert0;
*idx++ = vert0 + 1;
*idx++ = vert0 + vert_size;
*idx++ = vert0 + 1;
*idx++ = vert0 + vert_size + 1;
*idx++ = vert0 + vert_size;
}
}
const LLSurfacePatch* patch = surface.getPatch(ri, rj);
for (U32 j = 0; j < vert_size; ++j)
{
for (U32 i = 0; i < vert_size; ++i)
{
LLVector3 scratch3;
LLVector3 pos3;
LLVector2 tex1_temp;
patch->eval(i, j, stride, &pos3, &scratch3, &tex1_temp);
(*pos++).set(pos3.mV[VX], pos3.mV[VY], pos3.mV[VZ]);
*tex1++ = tex1_temp;
vertex_total++;
}
}
}
}
buf->setIndexData(index_array.data(), 0, (U32)index_array.size());
buf->setPositionData(positions.data(), 0, (U32)positions.size());
buf->setTexCoord1Data(texcoords1.data(), 0, (U32)texcoords1.size());
buf->unmapBuffer();
buf->unbind();
}
// Draw the region in agent space at full resolution
{
LLGLSLShader::unbind();
// *NOTE: A theoretical non-PBR terrain bake program would be
// *slightly* different, due the texture terrain shader not having an
// alpha ramp threshold (TERRAIN_RAMP_MIX_THRESHOLD)
LLGLSLShader& shader = gPBRTerrainBakeProgram;
shader.bind();
LLGLDisable stencil(GL_STENCIL_TEST);
LLGLDisable scissor(GL_SCISSOR_TEST);
LLGLEnable cull_face(GL_CULL_FACE);
LLGLDepthTest depth_test(GL_FALSE, GL_FALSE, GL_ALWAYS);
S32 alpha_ramp = shader.enableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
LLPointer<LLViewerTexture> alpha_ramp_texture = LLViewerTextureManager::getFetchedTexture(IMG_ALPHA_GRAD_2D);
gGL.getTexUnit(alpha_ramp)->bind(alpha_ramp_texture);
gGL.getTexUnit(alpha_ramp)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
buf->setBuffer();
for (U32 rj = 0; rj < patch_count; ++rj)
{
for (U32 ri = 0; ri < patch_count; ++ri)
{
const U32 patch_index = ri + (rj * patch_count);
const U32 index_offset = ni * patch_index;
const U32 vertex_offset = n * patch_index;
llassert(index_offset + ni <= region_indices);
llassert(vertex_offset + n <= region_vertices);
buf->drawRange(LLRender::TRIANGLES, vertex_offset, vertex_offset + n - 1, ni, index_offset);
}
}
shader.disableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
gGL.getTexUnit(alpha_ramp)->unbind(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(alpha_ramp)->disable();
gGL.getTexUnit(alpha_ramp)->activate();
shader.unbind();
}
gGL.matrixMode(LLRender::MM_PROJECTION);
gGL.popMatrix();
gGL.flush();
LLVertexBuffer::unbind();
// Final step: Copy the output to the terrain paintmap
const bool success = tex.getGLTexture()->setSubImageFromFrameBuffer(0, 0, 0, 0, dim, dim);
if (!success)
{
LL_WARNS() << "Failed to copy framebuffer to paintmap" << LL_ENDL;
}
glGenerateMipmap(GL_TEXTURE_2D);
stop_glerror();
scratch_target.flush();
LLGLSLShader::unbind();
return success;
}

View File

@ -0,0 +1,42 @@
/**
* @file llterrainpaintmap.h
* @brief Utilities for managing terrain paint maps
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2024, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#pragma once
class LLViewerRegion;
class LLViewerTexture;
class LLTerrainPaintMap
{
public:
// Convert a region's heightmap and composition into a paint map texture which
// approximates how the terrain would be rendered with the heightmap.
// In effect, this allows converting terrain of type TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE
// to type TERRAIN_PAINT_TYPE_PBR_PAINTMAP.
// Returns true if successful
static bool bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion& region, LLViewerTexture& tex);
};

View File

@ -719,6 +719,8 @@ void handleLocalTerrainChanged(const LLSD& newValue)
{
gLocalTerrainMaterials.setMaterialOverride(i, mat_override);
}
const bool paint_enabled = gSavedSettings.getBOOL("LocalTerrainPaintEnabled");
gLocalTerrainMaterials.setPaintType(paint_enabled ? TERRAIN_PAINT_TYPE_PBR_PAINTMAP : TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE);
}
}
////////////////////////////////////////////////////////////////////////////
@ -909,6 +911,7 @@ void settings_setup_listeners()
setting_setup_signal_listener(gSavedSettings, "AutoTuneImpostorByDistEnabled", handleUserImpostorByDistEnabledChanged);
setting_setup_signal_listener(gSavedSettings, "TuningFPSStrategy", handleFPSTuningStrategyChanged);
{
setting_setup_signal_listener(gSavedSettings, "LocalTerrainPaintEnabled", handleLocalTerrainChanged);
const char* transform_suffixes[] = {
"ScaleU",
"ScaleV",
@ -927,6 +930,7 @@ void settings_setup_listeners()
}
}
}
setting_setup_signal_listener(gSavedSettings, "TerrainPaintBitDepth", handleSetShaderChanged);
setting_setup_signal_listener(gSavedPerAccountSettings, "AvatarHoverOffsetZ", handleAvatarHoverOffsetChanged);
}

View File

@ -42,6 +42,7 @@
#include "llnotifications.h"
#include "llnotificationsutil.h"
#include "llviewereventrecorder.h"
#include "v4coloru.h"
// newview includes
#include "llagent.h"
@ -105,6 +106,7 @@
#include "llsidepanelappearance.h"
#include "llspellcheckmenuhandler.h"
#include "llstatusbar.h"
#include "llterrainpaintmap.h"
#include "lltextureview.h"
#include "lltoolbarview.h"
#include "lltoolcomp.h"
@ -122,6 +124,7 @@
#include "llviewerparcelmgr.h"
#include "llviewerstats.h"
#include "llviewerstatsrecorder.h"
#include "llvlcomposition.h"
#include "llvoavatarself.h"
#include "llvoicevivox.h"
#include "llworld.h"
@ -1387,6 +1390,65 @@ class LLAdvancedResetInterestLists : public view_listener_t
};
/////////////
// TERRAIN //
/////////////
class LLAdvancedRebuildTerrain : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
{
gPipeline.rebuildTerrain();
return true;
}
};
class LLAdvancedTerrainCreateLocalPaintMap : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
{
LLViewerRegion* region = gAgent.getRegion();
if (!region)
{
LL_WARNS() << "Agent not in a region" << LL_ENDL;
return false;
}
U16 dim = (U16)gSavedSettings.getU32("TerrainPaintResolution");
// Ensure a reasonable image size of power two
const U32 max_resolution = gSavedSettings.getU32("RenderMaxTextureResolution");
dim = llclamp(dim, 16, max_resolution);
dim = 1 << U32(std::ceil(std::log2(dim)));
LLPointer<LLImageRaw> image_raw = new LLImageRaw(dim,dim,3);
LLPointer<LLViewerTexture> tex = LLViewerTextureManager::getLocalTexture(image_raw.get(), true);
const bool success = LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(*region, *tex);
// This calls gLocalTerrainMaterials.setPaintType
gSavedSettings.setBOOL("LocalTerrainPaintEnabled", true);
// If baking the paintmap failed, set the paintmap to nullptr. This
// causes LLDrawPoolTerrain to use a blank paintmap instead.
if (!success) { tex = nullptr; }
gLocalTerrainMaterials.setPaintMap(tex);
return true;
}
};
class LLAdvancedTerrainDeleteLocalPaintMap : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
{
// This calls gLocalTerrainMaterials.setPaintType
gSavedSettings.setBOOL("LocalTerrainPaintEnabled", false);
gLocalTerrainMaterials.setPaintMap(nullptr);
return true;
}
};
/////////////
class LLAdvancedBuyCurrencyTest : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
@ -2242,20 +2304,6 @@ class LLAdvancedPurgeShaderCache : public view_listener_t
}
};
/////////////////////
// REBUILD TERRAIN //
/////////////////////
class LLAdvancedRebuildTerrain : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
{
gPipeline.rebuildTerrain();
return true;
}
};
////////////////////
// EVENT Recorder //
///////////////////
@ -9851,7 +9899,6 @@ void initialize_menus()
view_listener_t::addMenu(new LLAdvancedClickGLTFEdit(), "Advanced.ClickGLTFEdit");
view_listener_t::addMenu(new LLAdvancedClickResizeWindow(), "Advanced.ClickResizeWindow");
view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache");
view_listener_t::addMenu(new LLAdvancedRebuildTerrain(), "Advanced.RebuildTerrain");
#ifdef TOGGLE_HACKED_GODLIKE_VIEWER
view_listener_t::addMenu(new LLAdvancedHandleToggleHackedGodmode(), "Advanced.HandleToggleHackedGodmode");
@ -9868,6 +9915,11 @@ void initialize_menus()
view_listener_t::addMenu(new LLAdvancedCheckInterestList360Mode(), "Advanced.CheckInterestList360Mode");
view_listener_t::addMenu(new LLAdvancedResetInterestLists(), "Advanced.ResetInterestLists");
// Develop > Terrain
view_listener_t::addMenu(new LLAdvancedRebuildTerrain(), "Advanced.RebuildTerrain");
view_listener_t::addMenu(new LLAdvancedTerrainCreateLocalPaintMap(), "Advanced.TerrainCreateLocalPaintMap");
view_listener_t::addMenu(new LLAdvancedTerrainDeleteLocalPaintMap(), "Advanced.TerrainDeleteLocalPaintMap");
// Advanced > UI
commit.add("Advanced.WebBrowserTest", boost::bind(&handle_web_browser_test, _2)); // sigh! this one opens the MEDIA browser
commit.add("Advanced.WebContentTest", boost::bind(&handle_web_content_test, _2)); // this one opens the Web Content floater

View File

@ -100,6 +100,7 @@ LLGLSLShader gBenchmarkProgram;
LLGLSLShader gReflectionProbeDisplayProgram;
LLGLSLShader gCopyProgram;
LLGLSLShader gCopyDepthProgram;
LLGLSLShader gPBRTerrainBakeProgram;
//object shaders
LLGLSLShader gObjectPreviewProgram;
@ -226,7 +227,7 @@ LLGLSLShader gDeferredSkinnedPBROpaqueProgram;
LLGLSLShader gHUDPBRAlphaProgram;
LLGLSLShader gDeferredPBRAlphaProgram;
LLGLSLShader gDeferredSkinnedPBRAlphaProgram;
LLGLSLShader gDeferredPBRTerrainProgram;
LLGLSLShader gDeferredPBRTerrainProgram[TERRAIN_PAINT_TYPE_COUNT];
LLGLSLShader gGLTFPBRMetallicRoughnessProgram;
@ -432,7 +433,10 @@ void LLViewerShaderMgr::finalizeShaderList()
mShaderList.push_back(&gGLTFPBRMetallicRoughnessProgram);
mShaderList.push_back(&gDeferredAvatarProgram);
mShaderList.push_back(&gDeferredTerrainProgram);
mShaderList.push_back(&gDeferredPBRTerrainProgram);
for (U32 paint_type = 0; paint_type < TERRAIN_PAINT_TYPE_COUNT; ++paint_type)
{
mShaderList.push_back(&gDeferredPBRTerrainProgram[paint_type]);
}
mShaderList.push_back(&gDeferredDiffuseAlphaMaskProgram);
mShaderList.push_back(&gDeferredNonIndexedDiffuseAlphaMaskProgram);
mShaderList.push_back(&gDeferredTreeProgram);
@ -1129,7 +1133,10 @@ bool LLViewerShaderMgr::loadShadersDeferred()
gDeferredSkinnedPBROpaqueProgram.unload();
gDeferredPBRAlphaProgram.unload();
gDeferredSkinnedPBRAlphaProgram.unload();
gDeferredPBRTerrainProgram.unload();
for (U32 paint_type = 0; paint_type < TERRAIN_PAINT_TYPE_COUNT; ++paint_type)
{
gDeferredPBRTerrainProgram[paint_type].unload();
}
return true;
}
@ -1443,25 +1450,31 @@ bool LLViewerShaderMgr::loadShadersDeferred()
S32 detail = gSavedSettings.getS32("RenderTerrainPBRDetail");
detail = llclamp(detail, TERRAIN_PBR_DETAIL_MIN, TERRAIN_PBR_DETAIL_MAX);
const S32 mapping = clamp_terrain_mapping(gSavedSettings.getS32("RenderTerrainPBRPlanarSampleCount"));
gDeferredPBRTerrainProgram.mName = llformat("Deferred PBR Terrain Shader %d %s",
detail,
(mapping == 1 ? "flat" : "triplanar"));
gDeferredPBRTerrainProgram.mFeatures.hasSrgb = true;
gDeferredPBRTerrainProgram.mFeatures.isAlphaLighting = true;
gDeferredPBRTerrainProgram.mFeatures.calculatesAtmospherics = true;
gDeferredPBRTerrainProgram.mFeatures.hasAtmospherics = true;
gDeferredPBRTerrainProgram.mFeatures.hasGamma = true;
gDeferredPBRTerrainProgram.mFeatures.hasTransport = true;
gDeferredPBRTerrainProgram.mFeatures.isPBRTerrain = true;
for (U32 paint_type = 0; paint_type < TERRAIN_PAINT_TYPE_COUNT; ++paint_type)
{
LLGLSLShader* shader = &gDeferredPBRTerrainProgram[paint_type];
shader->mName = llformat("Deferred PBR Terrain Shader %d %s %s",
detail,
(paint_type == TERRAIN_PAINT_TYPE_PBR_PAINTMAP ? "paintmap" : "heightmap-with-noise"),
(mapping == 1 ? "flat" : "triplanar"));
shader->mFeatures.hasSrgb = true;
shader->mFeatures.isAlphaLighting = true;
shader->mFeatures.calculatesAtmospherics = true;
shader->mFeatures.hasAtmospherics = true;
shader->mFeatures.hasGamma = true;
shader->mFeatures.hasTransport = true;
shader->mFeatures.isPBRTerrain = true;
gDeferredPBRTerrainProgram.mShaderFiles.clear();
gDeferredPBRTerrainProgram.mShaderFiles.push_back(make_pair("deferred/pbrterrainV.glsl", GL_VERTEX_SHADER));
gDeferredPBRTerrainProgram.mShaderFiles.push_back(make_pair("deferred/pbrterrainF.glsl", GL_FRAGMENT_SHADER));
gDeferredPBRTerrainProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED];
gDeferredPBRTerrainProgram.addPermutation("TERRAIN_PBR_DETAIL", llformat("%d", detail));
gDeferredPBRTerrainProgram.addPermutation("TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT", llformat("%d", mapping));
success = gDeferredPBRTerrainProgram.createShader();
llassert(success);
shader->mShaderFiles.clear();
shader->mShaderFiles.push_back(make_pair("deferred/pbrterrainV.glsl", GL_VERTEX_SHADER));
shader->mShaderFiles.push_back(make_pair("deferred/pbrterrainF.glsl", GL_FRAGMENT_SHADER));
shader->mShaderLevel = mShaderLevel[SHADER_DEFERRED];
shader->addPermutation("TERRAIN_PBR_DETAIL", llformat("%d", detail));
shader->addPermutation("TERRAIN_PAINT_TYPE", llformat("%d", paint_type));
shader->addPermutation("TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT", llformat("%d", mapping));
success = success && shader->createShader();
llassert(success);
}
}
if (success)
@ -2957,6 +2970,25 @@ bool LLViewerShaderMgr::loadShadersInterface()
success = gCopyDepthProgram.createShader();
}
if (success)
{
LLGLSLShader* shader = &gPBRTerrainBakeProgram;
U32 bit_depth = gSavedSettings.getU32("TerrainPaintBitDepth");
// LLTerrainPaintMap currently uses an RGB8 texture internally
bit_depth = llclamp(bit_depth, 1, 8);
shader->mName = llformat("Terrain Bake Shader RGB%o", bit_depth);
shader->mFeatures.isPBRTerrain = true;
shader->mShaderFiles.clear();
shader->mShaderFiles.push_back(make_pair("interface/pbrTerrainBakeV.glsl", GL_VERTEX_SHADER));
shader->mShaderFiles.push_back(make_pair("interface/pbrTerrainBakeF.glsl", GL_FRAGMENT_SHADER));
shader->mShaderLevel = mShaderLevel[SHADER_INTERFACE];
const U32 value_range = (1 << bit_depth) - 1;
shader->addPermutation("TERRAIN_PAINT_PRECISION", llformat("%d", value_range));
success = success && shader->createShader();
llassert(success);
}
if (success)
{
gAlphaMaskProgram.mName = "Alpha Mask Shader";

View File

@ -174,6 +174,7 @@ extern LLGLSLShader gBenchmarkProgram;
extern LLGLSLShader gReflectionProbeDisplayProgram;
extern LLGLSLShader gCopyProgram;
extern LLGLSLShader gCopyDepthProgram;
extern LLGLSLShader gPBRTerrainBakeProgram;
//output tex0[tc0] - tex1[tc1]
extern LLGLSLShader gTwoTextureCompareProgram;
@ -304,5 +305,13 @@ enum TerrainPBRDetail : S32
TERRAIN_PBR_DETAIL_BASE_COLOR = -4,
TERRAIN_PBR_DETAIL_MIN = -4,
};
extern LLGLSLShader gDeferredPBRTerrainProgram;
enum TerrainPaintType : U32
{
// Use LLVLComposition::mDatap (heightmap) generated by generateHeights, plus noise from TERRAIN_ALPHARAMP
TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE = 0,
// Use paint map if PBR terrain, otherwise fall back to TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE
TERRAIN_PAINT_TYPE_PBR_PAINTMAP = 1,
TERRAIN_PAINT_TYPE_COUNT = 2,
};
extern LLGLSLShader gDeferredPBRTerrainProgram[TERRAIN_PAINT_TYPE_COUNT];
#endif

View File

@ -42,6 +42,7 @@
#include "llstl.h"
#include "message.h"
#include "lltimer.h"
#include "v4coloru.h"
// viewer includes
#include "llimagegl.h"

View File

@ -312,6 +312,17 @@ bool LLTerrainMaterials::makeMaterialsReady(bool boost, bool strict)
return one_ready;
}
LLViewerTexture* LLTerrainMaterials::getPaintMap()
{
return mPaintMap.get();
}
void LLTerrainMaterials::setPaintMap(LLViewerTexture* paint_map)
{
llassert(!paint_map || mPaintType == TERRAIN_PAINT_TYPE_PBR_PAINTMAP);
mPaintMap = paint_map;
}
// Boost the texture loading priority
// Return true when ready to use (i.e. texture is sufficiently loaded)
// static

View File

@ -28,6 +28,8 @@
#define LL_LLVLCOMPOSITION_H
#include "llviewerlayer.h"
#include "llviewershadermgr.h"
#include "llviewertexture.h"
#include "llpointer.h"
#include "llimage.h"
@ -44,6 +46,7 @@ public:
virtual const LLGLTFMaterial* getMaterialOverride(S32 asset) const = 0;
};
// The subset of the composition used by local terrain debug materials (gLocalTerrainMaterials)
class LLTerrainMaterials : public LLModifyRegion
{
public:
@ -79,6 +82,12 @@ public:
// strict = false -> at least one material must be loaded
bool makeMaterialsReady(bool boost, bool strict);
// See TerrainPaintType
U32 getPaintType() const { return mPaintType; }
void setPaintType(U32 paint_type) { mPaintType = paint_type; }
LLViewerTexture* getPaintMap();
void setPaintMap(LLViewerTexture* paint_map);
protected:
void unboost();
static bool makeTextureReady(LLPointer<LLViewerFetchedTexture>& tex, bool boost);
@ -93,6 +102,9 @@ protected:
LLPointer<LLGLTFMaterial> mDetailMaterialOverrides[ASSET_COUNT];
LLPointer<LLFetchedGLTFMaterial> mDetailRenderMaterials[ASSET_COUNT];
bool mMaterialTexturesSet[ASSET_COUNT];
U32 mPaintType = TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE;
LLPointer<LLViewerTexture> mPaintMap;
};
// Local materials to override all regions

View File

@ -245,7 +245,6 @@ bool LLVOSurfacePatch::updateLOD()
void LLVOSurfacePatch::getTerrainGeometry(LLStrider<LLVector3> &verticesp,
LLStrider<LLVector3> &normalsp,
LLStrider<LLVector2> &texCoords0p,
LLStrider<LLVector2> &texCoords1p,
LLStrider<U16> &indicesp)
{
@ -260,21 +259,18 @@ void LLVOSurfacePatch::getTerrainGeometry(LLStrider<LLVector3> &verticesp,
updateMainGeometry(facep,
verticesp,
normalsp,
texCoords0p,
texCoords1p,
indicesp,
index_offset);
updateNorthGeometry(facep,
verticesp,
normalsp,
texCoords0p,
texCoords1p,
indicesp,
index_offset);
updateEastGeometry(facep,
verticesp,
normalsp,
texCoords0p,
texCoords1p,
indicesp,
index_offset);
@ -283,7 +279,6 @@ void LLVOSurfacePatch::getTerrainGeometry(LLStrider<LLVector3> &verticesp,
void LLVOSurfacePatch::updateMainGeometry(LLFace *facep,
LLStrider<LLVector3> &verticesp,
LLStrider<LLVector3> &normalsp,
LLStrider<LLVector2> &texCoords0p,
LLStrider<LLVector2> &texCoords1p,
LLStrider<U16> &indicesp,
U32 &index_offset)
@ -322,10 +317,9 @@ void LLVOSurfacePatch::updateMainGeometry(LLFace *facep,
{
x = i * render_stride;
y = j * render_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
}
@ -387,7 +381,6 @@ void LLVOSurfacePatch::updateMainGeometry(LLFace *facep,
void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep,
LLStrider<LLVector3> &verticesp,
LLStrider<LLVector3> &normalsp,
LLStrider<LLVector2> &texCoords0p,
LLStrider<LLVector2> &texCoords1p,
LLStrider<U16> &indicesp,
U32 &index_offset)
@ -421,10 +414,9 @@ void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep,
x = i * render_stride;
y = 16 - render_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -433,10 +425,9 @@ void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep,
{
x = i * render_stride;
y = 16;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -469,10 +460,9 @@ void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep,
x = i * render_stride;
y = 16 - render_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -482,10 +472,9 @@ void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep,
x = i * render_stride;
y = 16;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -525,10 +514,9 @@ void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep,
x = i * north_stride;
y = 16 - render_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -538,10 +526,9 @@ void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep,
x = i * north_stride;
y = 16;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -577,7 +564,6 @@ void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep,
void LLVOSurfacePatch::updateEastGeometry(LLFace *facep,
LLStrider<LLVector3> &verticesp,
LLStrider<LLVector3> &normalsp,
LLStrider<LLVector2> &texCoords0p,
LLStrider<LLVector2> &texCoords1p,
LLStrider<U16> &indicesp,
U32 &index_offset)
@ -606,10 +592,9 @@ void LLVOSurfacePatch::updateEastGeometry(LLFace *facep,
x = 16 - render_stride;
y = i * render_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -618,10 +603,9 @@ void LLVOSurfacePatch::updateEastGeometry(LLFace *facep,
{
x = 16;
y = i * render_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -654,10 +638,9 @@ void LLVOSurfacePatch::updateEastGeometry(LLFace *facep,
x = 16 - render_stride;
y = i * render_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
// Iterate through the east patch's points
@ -666,10 +649,9 @@ void LLVOSurfacePatch::updateEastGeometry(LLFace *facep,
x = 16;
y = i * render_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -708,10 +690,9 @@ void LLVOSurfacePatch::updateEastGeometry(LLFace *facep,
x = 16 - render_stride;
y = i * east_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
// Iterate through the east patch's points
@ -720,10 +701,9 @@ void LLVOSurfacePatch::updateEastGeometry(LLFace *facep,
x = 16;
y = i * east_stride;
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get());
mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords1p.get());
verticesp++;
normalsp++;
texCoords0p++;
texCoords1p++;
}
@ -992,8 +972,8 @@ void gen_terrain_tangents(U16 strider_vertex_count,
LLStrider<LLVector3> &verticesp,
LLStrider<LLVector3> &normalsp,
LLStrider<LLVector4a> &tangentsp,
LLStrider<LLVector2> &texCoords0p,
LLStrider<U16> &indicesp)
LLStrider<U16> &indicesp,
F32 region_width)
{
LL_PROFILE_ZONE_SCOPED;
@ -1010,7 +990,10 @@ void gen_terrain_tangents(U16 strider_vertex_count,
F32 *n = normalsp[v].mV;
normals[v] = LLVector4a(n[0], n[1], n[2], 1.f);
tangents[v] = tangentsp[v];
texcoords[v] = texCoords0p[v];
// Calculate texcoords on-the-fly using the terrain positions
texcoords[v].mV[VX] = verticesp[v].mV[VX] / region_width;
texcoords[v].mV[VY] = verticesp[v].mV[VY] / region_width;
}
for (U32 i = 0; i < strider_index_count; ++i)
{
@ -1039,14 +1022,12 @@ void LLTerrainPartition::getGeometry(LLSpatialGroup* group)
LLStrider<LLVector3> vertices_start;
LLStrider<LLVector3> normals_start;
LLStrider<LLVector4a> tangents_start;
LLStrider<LLVector2> texcoords_start;
LLStrider<LLVector2> texcoords2_start;
LLStrider<U16> indices_start;
llassert_always(buffer->getVertexStrider(vertices_start));
llassert_always(buffer->getNormalStrider(normals_start));
llassert_always(buffer->getTangentStrider(tangents_start));
llassert_always(buffer->getTexCoord0Strider(texcoords_start));
llassert_always(buffer->getTexCoord1Strider(texcoords2_start));
llassert_always(buffer->getIndexStrider(indices_start));
@ -1056,7 +1037,6 @@ void LLTerrainPartition::getGeometry(LLSpatialGroup* group)
{
LLStrider<LLVector3> vertices = vertices_start;
LLStrider<LLVector3> normals = normals_start;
LLStrider<LLVector2> texcoords = texcoords_start;
LLStrider<LLVector2> texcoords2 = texcoords2_start;
LLStrider<U16> indices = indices_start;
@ -1069,7 +1049,7 @@ void LLTerrainPartition::getGeometry(LLSpatialGroup* group)
facep->setVertexBuffer(buffer);
LLVOSurfacePatch* patchp = (LLVOSurfacePatch*) facep->getViewerObject();
patchp->getTerrainGeometry(vertices, normals, texcoords, texcoords2, indices);
patchp->getTerrainGeometry(vertices, normals, texcoords2, indices);
indices_index += facep->getIndicesCount();
index_offset += facep->getGeomCount();
@ -1082,10 +1062,20 @@ void LLTerrainPartition::getGeometry(LLSpatialGroup* group)
LLStrider<LLVector3> vertices = vertices_start;
LLStrider<LLVector3> normals = normals_start;
LLStrider<LLVector4a> tangents = tangents_start;
LLStrider<LLVector2> texcoords = texcoords_start;
LLStrider<U16> indices = indices_start;
gen_terrain_tangents(index_offset, indices_index, vertices, normals, tangents, texcoords, indices);
F32 region_width = 256.0f;
if (mFaceList.empty())
{
llassert(false);
}
else
{
const LLViewerRegion* regionp = mFaceList[0]->getViewerObject()->getRegion();
llassert(regionp == mFaceList.back()->getViewerObject()->getRegion()); // Assume this spatial group is confined to one region
region_width = regionp->getWidth();
}
gen_terrain_tangents(index_offset, indices_index, vertices, normals, tangents, indices, region_width);
}
buffer->unmapBuffer();

View File

@ -41,14 +41,6 @@ class LLVOSurfacePatch : public LLStaticViewerObject
public:
static F32 sLODFactor;
enum
{
VERTEX_DATA_MASK = (1 << LLVertexBuffer::TYPE_VERTEX) |
(1 << LLVertexBuffer::TYPE_NORMAL) |
(1 << LLVertexBuffer::TYPE_TEXCOORD0) |
(1 << LLVertexBuffer::TYPE_TEXCOORD1)
};
LLVOSurfacePatch(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp);
/*virtual*/ void markDead();
@ -65,7 +57,6 @@ public:
/*virtual*/ void updateFaceSize(S32 idx);
void getTerrainGeometry(LLStrider<LLVector3> &verticesp,
LLStrider<LLVector3> &normalsp,
LLStrider<LLVector2> &texCoords0p,
LLStrider<LLVector2> &texCoords1p,
LLStrider<U16> &indicesp);
@ -118,21 +109,18 @@ protected:
void updateMainGeometry(LLFace *facep,
LLStrider<LLVector3> &verticesp,
LLStrider<LLVector3> &normalsp,
LLStrider<LLVector2> &texCoords0p,
LLStrider<LLVector2> &texCoords1p,
LLStrider<U16> &indicesp,
U32 &index_offset);
void updateNorthGeometry(LLFace *facep,
LLStrider<LLVector3> &verticesp,
LLStrider<LLVector3> &normalsp,
LLStrider<LLVector2> &texCoords0p,
LLStrider<LLVector2> &texCoords1p,
LLStrider<U16> &indicesp,
U32 &index_offset);
void updateEastGeometry(LLFace *facep,
LLStrider<LLVector3> &verticesp,
LLStrider<LLVector3> &normalsp,
LLStrider<LLVector2> &texCoords0p,
LLStrider<LLVector2> &texCoords1p,
LLStrider<U16> &indicesp,
U32 &index_offset);

View File

@ -3435,13 +3435,6 @@ function="World.EnvPreset"
<menu_item_call.on_click
function="Advanced.ClearShaderCache" />
</menu_item_call>
<menu_item_call
enabled="true"
label="Rebuild Terrain"
name="Rebuild Terrain">
<menu_item_call.on_click
function="Advanced.RebuildTerrain" />
</menu_item_call>
<menu_item_separator />
<menu_item_call
enabled="true"
@ -3662,6 +3655,37 @@ function="World.EnvPreset"
function="Advanced.ResetInterestLists" />
</menu_item_call>
</menu>
<!-- terrain -->
<menu
create_jump_keys="true"
label="Terrain"
name="DevelopTerrain"
tear_off="true">
<menu_item_call
enabled="true"
label="Rebuild Terrain"
name="Rebuild Terrain">
<menu_item_call.on_click
function="Advanced.RebuildTerrain" />
</menu_item_call>
<menu_item_separator/>
<menu_item_call
enabled="true"
label="Create Local Paintmap"
name="Create Local Paintmap">
<menu_item_call.on_click
function="Advanced.TerrainCreateLocalPaintMap" />
</menu_item_call>
<menu_item_call
enabled="true"
label="Delete Local Paintmap"
name="Delete Local Paintmap">
<menu_item_call.on_click
function="Advanced.TerrainDeleteLocalPaintMap" />
</menu_item_call>
</menu>
<menu
create_jump_keys="true"
label="UI"