?* 需求:
* 高程渲染圖 RGB.tif、 山體陰影圖 AMP.tif
*
* 高程渲染圖 rgb波段分別 ?乘以 山體陰影圖r波段, 然后除以255(AI說 ?讀取的紋理就已經歸一化到了 0~1 范圍,不用除以 255)。
本人遙感知識匱乏。
問了AI,以上 需求在許多商業軟件上已實現。在 ArcGIS 和 QGIS 中制作 Hillshade Overlay 的核心邏輯是通過數字高程模型(DEM)生成地形陰影,再將其與其他圖層疊加以增強立體感。
環境:Win10 64bit,? ARM R9 3900X ,Qt 5.15.2, C++,osgEarth3.7.0
1、C++ 結合 OpenCV實現。適合靜態展示;
2、glsl實現。效率高。glsl 我也是抄、描、問,? 零零碎碎 拼湊出來的,這里就想拋磚引玉,希望openGL大佬們來指導哈。
這完全是個筆記,借助AI拼湊出來的代碼,希望遙感數據處理大牛們多多指教,
特別是osgEarth中的著色器編程。
無圖無真相。先圖后碼。
原 彩色浮雕圖:
山體陰影
在Qt 里面使用 C++? OpenCV運行出來的效果,源碼在后面。
osgearth著色器, minLumFactor = 0.125時的效果,明顯偏暗。
minLumFactor = 0.618
好,把之前實現的代碼 和效果做個筆記。
代碼如下:
#include "GenerateColorReliefThread.h"
#include "Global.h"
#include "MyFile/MyRaster.h"
#include "gdal_utils.h"
#include "osg/BlendFunc"
#include "qcoreapplication.h"#include "SettingsManager.h"#include "ProjectManage/MyProject.h"GenerateColorReliefThread::GenerateColorReliefThread( const QString &filePath, GDALDataset *dataset, const std::string &colorFilename, QObject *parent): QThread(parent), m_filePath(filePath), m_colorFilename(colorFilename), m_dataset(dataset)
{}GenerateColorReliefThread::~GenerateColorReliefThread()
{wait();
}bool GenerateColorReliefThread::createColorRelief( std::string &filePathColorRelief )
{CPLSetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS"); // 啟用所有CPU核心并行處理// 3) 調 GDALDEMProcessing 做 color-relief(或 hillshade)const char* dArgvStr = "-alpha -of GTiff";char**papszArgv = CSLTokenizeString(dArgvStr);// 處理選項GDALDEMProcessingOptions *psOptions = GDALDEMProcessingOptionsNew(papszArgv, nullptr);if (psOptions == nullptr){qDebugV5() <<"Failed to create processing options.";return false;}static std::atomic<int> tempFileCounter(0); // 避免內存文件名沖突filePathColorRelief = "/vsimem/color_relief_" + std::to_string(tempFileCounter++) + ".tif";// std::cout<<"memFileName: "<<filePathColorRelief<<std::endl;int pbUsageError = 0;GDALDatasetH hColorRelief = GDALDEMProcessing(filePathColorRelief.c_str(), m_dataset, "color-relief", m_colorFilename.c_str(), psOptions, &pbUsageError);if (!hColorRelief || pbUsageError != 0){qDebugV5() << "Processing failed with error code: " << pbUsageError;GDALDEMProcessingOptionsFree(psOptions);return false;}cv::Mat matHillShade;FileCategory fileCategory = Global::getFileCategory(m_filePath);switch (fileCategory.rasterSubCategory) {case RASTER_OPT:case RASTER_AMP:break;case RASTER_DEM:{switch (fileCategory.demSubSubCategory) {case DEM_COPERNICUS:matHillShade = MyProject::instance()->matHillShadeCopernicus().clone();break;case DEM_RAW:matHillShade = MyProject::instance()->matHillShadeRaw().clone();break;case DEM_CACHE:{double zFactor = SettingsManager::instance().value("HILL_SHADE_ZFACTOR", 0.00001).toDouble();double scale = SettingsManager::instance().value("HILL_SHADE_SCALE", 1.0).toDouble();double azimuth = SettingsManager::instance().value("HILL_SHADE_AZIMUTH", 315.0).toDouble();double altitude = SettingsManager::instance().value("HILL_SHADE_ALTITUDE", 45.0).toDouble();bool combined = SettingsManager::instance().value("HILL_SHADE_COMBINED", false).toBool();Global::generateHillShade(matHillShade, m_filePath, zFactor, scale, azimuth, altitude, combined);if(fileCategory.demSubSubCategory == DEM_CACHE){MyProject::instance()->setMatHillShadeCache( matHillShade );}}break; }}break;case RASTER_MASK:matHillShade = MyProject::instance()->matHillShadeCache().clone();break;}// 獲取圖像尺寸int width = m_dataset->GetRasterXSize();int height = m_dataset->GetRasterYSize();int panBandMap [3]= {1, 2, 3};// 轉換為C++對象GDALDataset* poColorRelief = (GDALDataset*)hColorRelief;if(!poColorRelief){return false;}// 創建三通道矩陣一次性讀取RGB數據cv::Mat matRGB(height, width, CV_8UC3);// 一次性讀取三個波段CPLErr readErr = poColorRelief->RasterIO(GF_Read,0, 0,width, height,(void*)matRGB.data,width, height,GDT_Byte, 3, panBandMap,3, width*3, 1);if (readErr != CE_None) {qDebugV5() << "Failed to read RGB bands!";GDALClose(hColorRelief);GDALDEMProcessingOptionsFree(psOptions);return false;}// 將單通道hillshade轉換為三通道以匹配RGBcv::Mat matHillShade3C;cv::cvtColor(matHillShade, matHillShade3C, cv::COLOR_GRAY2BGR);// 2. 分別對每個通道做 multiply(與你原來的分通道邏輯完全一致)cv::multiply(matRGB, matHillShade3C, matRGB, 1.0 / 255.0, CV_8UC3); // R通道// 一次性寫入三個波段CPLErr writeErr = poColorRelief->RasterIO(GF_Write,0, 0,width, height,(void*)matRGB.data,width, height,GDT_Byte, 3, panBandMap,3, width*3, 1);if (writeErr != CE_None) {std::cerr << "GDAL 寫入失敗: " << CPLGetLastErrorMsg() << std::endl;GDALClose(hColorRelief);GDALDEMProcessingOptionsFree(psOptions);return false;}GDALClose(hColorRelief);GDALDEMProcessingOptionsFree(psOptions);return true;
}void GenerateColorReliefThread::run()
{// 創建并啟動計時器QElapsedTimer timer;timer.start();std::string filePathColorRelief;bool ret = createColorRelief(filePathColorRelief);// qDebugV0() <<m_filePath<< " createColorRelief() 代碼執行時間: " << timer.elapsed() << " 毫秒"; // 計算執行時間(毫秒)if(!ret){return;}osg::ref_ptr<GDALImageLayer> layer = nullptr;if (!filePathColorRelief.empty()) {timer.restart();Global::buildOverviews(filePathColorRelief, -1); // 減少金字塔級數// qDebugV0() <<QString::fromStdString(filePathColorRelief)<< " buildOverviewsTime() 代碼執行時間: " << timer.elapsed() << " 毫秒"; // 計算執行時間(毫秒)osgEarth::GDALImageLayer::Options options;options.set_url(osgEarth::URI(filePathColorRelief));// 基礎優化options.set_async( true ); // 異步加載layer = new GDALImageLayer(options);layer->options().set_name(m_filePath.toStdString());layer->setAsyncLoading(true);}emit sigProcessingFinished(layer);
}
C++ 里面嵌套著色器代碼實現:
/******************************************************************************* 需求:* 高程渲染圖 RGB.tif、 山體陰影圖 AMP.tif** 高程渲染圖 rgb波段分別 乘以 山體陰影圖r波段, 然后除以255(AI說 讀取的紋理就已經歸一化到了 0~1 范圍,不用除以 255)。*/void MyWidget::testGlsl()
{osgEarth::GDALImageLayer::Options ampOpt;// 2. AMP 陰影圖層,開啟 sharedampOpt.url() = "D:/Demo/glsl/AMP.tif";ampOpt.shared() = true; // 關鍵點:讓它暴露共享采樣器和矩陣ampOpt.shareTexUniformName() = "ampTex"; // Uniform 采樣器名ampOpt.shareTexMatUniformName() = "ampTexMatrix"; // Uniform 矩陣名osg::ref_ptr<GDALImageLayer> ampLayer = new GDALImageLayer(ampOpt);ampLayer->setOpacity(0.00f);_map->addLayer(ampLayer.get());Global::buildOverviews("D:/Demo/glsl/RGB.tif", 20, "AVERAGE"); // 減少金字塔級數osgEarth::GDALImageLayer::Options rgbOpt;rgbOpt.tileSize() = 256;rgbOpt.maxLevel() = 20;rgbOpt.url() = "D:/Demo/glsl/RGB.tif";osg::ref_ptr<GDALImageLayer> rgbLayer = new GDALImageLayer(rgbOpt);_map->addLayer(rgbLayer.get());osg::ref_ptr<osg::StateSet> ss = rgbLayer->getOrCreateStateSet();ss->addUniform(new osg::Uniform("minLumFactor", 0.125f));//虛擬程序設置auto vp = osgEarth::VirtualProgram::getOrCreate(ss);// 獲取可執行文件目錄QString appDirQt = QCoreApplication::applicationDirPath();// GLSL 文件路徑(同級 glsl 文件夾)QString glslPathQt = appDirQt + "/glsl/colorRelief_hillShade.glsl";std::string glslFile = glslPathQt.toStdString();// 打印檢查std::cout << "glslFile: " << glslFile << std::endl;// 從文件里加載一個 GLSL 源碼到 osg::Shader 對象osg::ref_ptr<osg::Shader> fragShader = osgDB::readRefShaderFile(osg::Shader::FRAGMENT, glslFile);if (fragShader.valid()) {std::string src = fragShader->getShaderSource();vp->setFunction("customFragment", src , osgEarth::ShaderComp::LOCATION_FRAGMENT_COLORING);}else{qDebugV5()<<"fragShader ×";}
}
以下是著色器glsl代碼:
uniform sampler2D ampTex;
uniform mat4 ampTexMatrix;uniform float minLumFactor;// 新增 uniform,用滑條控制in vec2 oe_layer_texc;void customFragment(inout vec4 color)
{// RGB 原始顏色vec3 baseColor=color.rgb;// 轉到 AMP 圖層坐標vec2 uv_amp=(ampTexMatrix*vec4(oe_layer_texc,0.,1.)).st;// Hillshade 灰度float amp=texture(ampTex,uv_amp).r;// --- 關鍵:用 hillshade 調制亮度 ---// 1. 先算彩色圖的亮度(感知加權公式)float lum=dot(baseColor,vec3(.299,.587,.114));// 2. 用 hillshade 去調制亮度// float lumMod = lum * amp;float lumMod=lum*(minLumFactor+amp*(1.-minLumFactor));// 把 hillshade 映射到 minLumFactor~1.0// 3. 保留原色相和飽和度:縮放 baseColor,使亮度變成 lumModfloat lumBase=max(lum,1e-4);// 防止除零vec3 resultColor=baseColor*(lumMod/lumBase);color.rgb=clamp(resultColor,0.,1.);
}