以前我在《使用GDAL实现DEM的地貌晕渲图(一)》这篇文章里面讲述了DEM晕渲图的生成原理与实现,大致上来说是经过计算DEM格网点的法向量与日照方向的的夹角,来肯定该格网点的晕渲强度值。但其实关于这一点我不是很理解,这样作随着坡面与光源方向的夹角不一样,确实产生了不一样色调明暗效果;但晕渲图同时又有“阴坡面越陡越暗,阳坡面越陡越亮”的特性的,而阴阳坡面的划分又是跟坡度和坡向相关,以前的生成方法能体现出这种特性吗?html
通过查阅资料,却在ArcGIS的帮助文档《山体阴影工具的工做原理》(在线版本可查看这篇文章《ArcGIS教程:山体阴影工做原理》)中查阅到了晕渲图的另一种生成算法。利用直接利用坡度和坡向的关系,算出每一个点的山体阴影值:
而且,在该文档中,还附带了一个具体的计算示例:
具体的算法过程说的很清楚了,惋惜的就是不太明白具体的原理是什么,在帮助中指向了一本1998的英文文献[Burrough, P. A. and McDonell, R. A., 1998. Principles of Geographical Information Systems (Oxford University Press, New York), 190 pp]也实在是无法深刻查阅深究。而在查阅中文论文的时候,关于这一段的描述也是互相抄袭,摘录以下:
这一段的论述反正我是没看明白的,也就很少作论述了,但愿看懂这个算法的大神能指点我一下。ios
虽然更深刻的原理没弄明白,不过做为应用者却足够可以实现其算法了。我这里经过GDAL实现了晕渲图的生成:算法
#include <iostream> #include <algorithm> #include <gdal_priv.h> #include <osg/Vec3d> #include <fstream> using namespace std; using namespace osg; // a b c // d e f // g h i double CalHillshade(float *tmpBuf, double Zenith_rad, double Azimuth_rad, double dx, double dy, double z_factor) { double dzdx = ((tmpBuf[2] + 2 * tmpBuf[5] + tmpBuf[8]) - (tmpBuf[0] + 2 * tmpBuf[3] + tmpBuf[6])) / (8 * dx); double dzdy = ((tmpBuf[6] + 2 * tmpBuf[7] + tmpBuf[8]) - (tmpBuf[0] + 2 * tmpBuf[1] + tmpBuf[2])) / (8 * dy); double Slope_rad = atan(z_factor * sqrt(dzdx*dzdx + dzdy*dzdy)); double Aspect_rad = 0; if (abs(dzdx) > 1e-9) { Aspect_rad = atan2(dzdy, -dzdx); if (Aspect_rad < 0) { Aspect_rad = 2 * PI + Aspect_rad; } } else { if (dzdy > 0) { Aspect_rad = PI / 2; } else if (dzdy < 0) { Aspect_rad = 2 * PI - PI / 2; } else { Aspect_rad = Aspect_rad; } } double Hillshade = 255.0 * ((cos(Zenith_rad) * cos(Slope_rad)) + (sin(Zenith_rad) * sin(Slope_rad) * cos(Azimuth_rad - Aspect_rad))); return Hillshade; } int main() { GDALAllRegister(); //GDAL全部操做都须要先注册格式 CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); //支持中文路径 const char* demPath = "D:/CloudSpace/个人技术文章/素材/DEM的渲染/dst.tif"; //const char* demPath = "D:/Data/imgDemo/K51E001022/k51e001022dem/w001001.adf"; GDALDataset* img = (GDALDataset *)GDALOpen(demPath, GA_ReadOnly); if (!img) { cout << "Can't Open Image!" << endl; return 1; } int imgWidth = img->GetRasterXSize(); //图像宽度 int imgHeight = img->GetRasterYSize(); //图像高度 int bandNum = img->GetRasterCount(); //波段数 int depth = GDALGetDataTypeSize(img->GetRasterBand(1)->GetRasterDataType()) / 8; //图像深度 GDALDriver *pDriver = GetGDALDriverManager()->GetDriverByName("GTIFF"); //图像驱动 char** ppszOptions = NULL; ppszOptions = CSLSetNameValue(ppszOptions, "BIGTIFF", "IF_NEEDED"); //配置图像信息 const char* dstPath = "D:\\dst.tif"; int bufWidth = imgWidth; int bufHeight = imgHeight; int dstBand = 1; int dstDepth = 1; GDALDataset* dst = pDriver->Create(dstPath, bufWidth, bufHeight, dstBand, GDT_Byte, ppszOptions); if (!dst) { printf("Can't Write Image!"); return false; } dst->SetProjection(img->GetProjectionRef()); double padfTransform[6] = { 0 }; if (CE_None == img->GetGeoTransform(padfTransform)) { dst->SetGeoTransform(padfTransform); } //申请buf depth = 4; size_t imgBufNum = (size_t)bufWidth * bufHeight * bandNum; float *imgBuf = new float[imgBufNum]; //读取 img->RasterIO(GF_Read, 0, 0, bufWidth, bufHeight, imgBuf, bufWidth, bufHeight, GDT_Float32, bandNum, nullptr, bandNum*depth, bufWidth*bandNum*depth, depth); if (bandNum != 1) { return 1; } // double startX = padfTransform[0]; //左上角点坐标X double dx = padfTransform[1]; //X方向的分辨率 double startY = padfTransform[3]; //左上角点坐标Y double dy = padfTransform[5]; //Y方向的分辨率 //申请buf size_t dstBufNum = (size_t)bufWidth * bufHeight * dstBand * dstDepth; GByte *dstBuf = new GByte[dstBufNum]; memset(dstBuf, 0, dstBufNum*sizeof(GByte)); //设置方向:平行光 double solarAltitude = 45.0; double solarAzimuth = 315.0; // double Zenith_rad = osg::DegreesToRadians(90 - solarAltitude); double Azimuth_math = 360.0 - solarAzimuth + 90; if (Azimuth_math >= 360.0) { Azimuth_math = Azimuth_math - 360.0; } double Azimuth_rad = osg::DegreesToRadians(Azimuth_math); //a b c //d e f //g h i double z_factor = 1; for (int yi = 1; yi < bufHeight-1; yi++) { for (int xi = 1; xi < bufWidth-1; xi++) { size_t e = (size_t)bufWidth * yi + xi; size_t f = e + 1; size_t d = e - 1; size_t b = e - bufWidth; size_t c = b + 1; size_t a = b - 1; size_t h = e + bufWidth; size_t i = h + 1; size_t g = h - 1; float tmpBuf[9] = { imgBuf[a], imgBuf[b], imgBuf[c], imgBuf[d], imgBuf[e], imgBuf[f], imgBuf[g],imgBuf[h], imgBuf[i] }; double Hillshade = CalHillshade(tmpBuf, Zenith_rad, Azimuth_rad, dx, -dy, z_factor); dstBuf[e] = (GByte)(std::min(std::max(Hillshade, 0.0),255.0)); } } //写入 dst->RasterIO(GF_Write, 0, 0, bufWidth, bufHeight, dstBuf, bufWidth, bufHeight, GDT_Byte, dstBand, nullptr, dstBand*dstDepth, bufWidth*dstBand*dstDepth, dstDepth); //释放 delete[] imgBuf; imgBuf = nullptr; //释放 delete[] dstBuf; dstBuf = nullptr; // GDALClose(dst); dst = nullptr; GDALClose(img); img = nullptr; return 0; }
最终获得的晕渲结果和ArcMap的晕渲结果比较,几乎是如出一辙的: 后续会正式在这个基础之上实现彩色的晕渲图。工具
[1]. ArcGIS帮助:山体阴影工具的工做原理。 [2]. 基于视觉表象的彩色晕渲地图色彩设计.郭礼珍等.2004spa