/** * @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> 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 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 index_array(region_indices); std::vector positions(region_vertices); std::vector 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 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; }