From c669fedbe2722652a22eb2cefeaa64a4f63bc08e Mon Sep 17 00:00:00 2001 From: Joaquim Date: Thu, 24 Sep 2026 13:27:21 +0100 Subject: [PATCH 1/6] Add grdimage pbr test --- test/grdview/pbr.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 test/grdview/pbr.sh diff --git a/test/grdview/pbr.sh b/test/grdview/pbr.sh new file mode 100644 index 00000000000..7ca89c42622 --- /dev/null +++ b/test/grdview/pbr.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Test physically based shading (-F) of a 3-D surface: cast shadows, ambient occlusion and tone +# mapping with a vertical exaggeration of 3, draped over the relief it was computed from + +ps=pbr.ps + +gmt grdmath -R-15/15/-15/15 -I0.1 X Y HYPOT DUP 2 MUL PI MUL 8 DIV COS EXCH NEG 10 DIV EXP MUL = somb.nc +gmt makecpt -Cjet -T-0.5/1 > pal.cpt + +gmt grdview somb.nc -Cpal.cpt -F225/30+o+s+t+v3 -JX12c -JZ3c -p135/35 -Baf -Bzaf -BWSneZ -P -Xc > $ps + +rm -f somb.nc pal.cpt From e762230f74a3cd7b8da1bdbae15eccbba2ca928f Mon Sep 17 00:00:00 2001 From: Joaquim Date: Thu, 24 Sep 2026 13:29:55 +0100 Subject: [PATCH 2/6] Add PBR shading fter the shading methos used by the VTL library --- doc/rst/source/grdimage.rst | 30 ++++ doc/rst/source/grdview.rst | 32 ++++ src/gmt_prototypes.h | 5 + src/gmt_support.c | 323 ++++++++++++++++++++++++++++++++++ src/gmt_types.h | 19 ++ src/grdimage.c | 33 +++- src/grdview.c | 50 +++++- src/longopt/grdimage_inc.h | 6 +- src/longopt/grdview_inc.h | 6 +- test/grdimage/grdimage-l2s.sh | 4 + test/grdimage/pbr.sh | 14 ++ test/grdview/grdview-l2s.sh | 4 + 12 files changed, 518 insertions(+), 8 deletions(-) create mode 100644 test/grdimage/pbr.sh diff --git a/doc/rst/source/grdimage.rst b/doc/rst/source/grdimage.rst index a4773b4f03b..f6f9a11d6a2 100644 --- a/doc/rst/source/grdimage.rst +++ b/doc/rst/source/grdimage.rst @@ -18,6 +18,7 @@ Synopsis [ |-C|\ [*section*/]\ *master*\|\ *cpt*\|\ *color*\ :math:`_1`,\ *color*\ :math:`_2`\ [,\ *color*\ :math:`_3`\ ,...]\ [**+h**\ [*hinge*]][**+i**\ *dz*][**+u**\|\ **U**\ *unit*][**+s**\ *fname*] ] [ |-D|\ [**r**] ] [ |-E|\ [**i**\|\ *dpi*] ] +[ |-F|\ *azim*/*elev*\ [**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] ] [ |-G|\ *color*\ [**+b**\|\ **f**] ] [ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*] ] [ |-J|\ *parameters* ] @@ -126,6 +127,16 @@ Optional Arguments columns) as the input file. Specify **i** to use the PostScript image operator to interpolate the image at the device resolution. +.. _-F: + +**-F**\ *azim*/*elev*\ [**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] + Shade the grid with physically based lighting instead of illuminating it with |-I| (the two + cannot be combined). The shaded image is made on the grid's own nodes and then projected and + plotted as any image, so |-A|, |-E| and |-J| work as usual. Use **-I+f** as a short form that + selects the default settings, which are the same as **-F**\ 315/45+o+t. + + .. include:: explain_pbr.rst_ + .. _-G: **-G**\ *color*\ [**+b**\|\ **f**] @@ -333,8 +344,27 @@ To create a sinusoidal projection of a remotely located Jessica Rabbit:: gmt grdimage -JI15c -Rd http://larryfire.files.wordpress.com/2009/07/untooned_jessicarabbit.jpg -pdf jess +To shade the Island of Hawaii and the surrounding sea floor with physically based lighting, with a low sun +from the northwest that makes Mauna Kea and Mauna Loa cast their shadows, ambient occlusion, tone mapping, +a threefold vertical exaggeration and a brighter sun and fill light than the defaults:: + + gmt grdcut @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Ghawaii.nc + gmt grdimage hawaii.nc -Cgeo -F315/35+o+s+t+v3+l1.4+f0.5 -JM15c -B -pdf hawaii + .. include:: cpt_notes.rst_ +References +---------- + +Heitz, E., Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs, Journal of +Computer Graphics Techniques, Vol. 3, No. 2, 2014, pp. 48-107. + +Schlick, C., An Inexpensive BRDF Model for Physically-based Rendering, Computer Graphics Forum, +Vol. 13, No. 3, 1994, pp. 233-246. + +Walter, B., Marschner, S. R., Li, H., and Torrance, K. E., Microfacet Models for Refraction through +Rough Surfaces, Proceedings of the Eurographics Symposium on Rendering, 2007, pp. 195-206. + See Also -------- diff --git a/doc/rst/source/grdview.rst b/doc/rst/source/grdview.rst index d8c3d05ef86..4d3c6f21778 100644 --- a/doc/rst/source/grdview.rst +++ b/doc/rst/source/grdview.rst @@ -15,6 +15,7 @@ Synopsis **gmt grdview** *reliefgrid* |-J|\ *parameters* [ |SYN_OPT-B| ] [ |-C|\ [*section*/]\ *master*\|\ *cpt*\|\ *color*\ :math:`_1`,\ *color*\ :math:`_2`\ [,\ *color*\ :math:`_3`\ ,...]\ [**+h**\ [*hinge*]][**+i**\ *dz*][**+u**\|\ **U**\ *unit*][**+s**\ *fname*] ] +[ |-F|\ *azim*/*elev*\ [**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] ] [ |-G|\ *drapegrid*\|\ *drapeimage* ] [ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*] ] [ |-Jz|\ \|\ **Z**\ *parameters* ] @@ -70,6 +71,17 @@ Optional Arguments .. include:: use_cpt_grd.rst_ +.. _-F: + +**-F**\ *azim*/*elev*\ [**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] + Shade the *reliefgrid* with physically based lighting instead of illuminating it with |-I| (the + two cannot be combined, nor can |-G| or |-T| be used). The shaded colors are computed on the grid's + own nodes and draped over the relief as an image, so |-F| implies **-Qi** unless **-Qc**\|\ **i** + is given. Use **-I+f** as a short form that selects the default settings, which are the same as + **-F**\ 315/45+o+t. The shading itself is identical to that of :doc:`grdimage` **-F**. + + .. include:: explain_pbr.rst_ + .. _-G: |-G|\ *drapegrid*\|\ *drapeimage* @@ -243,6 +255,14 @@ topo_intens.nc, and looking from the SE, run:: gmt grdview topography.nc -JM6i -Gmagnetics.nc -Cmag_intens.cpt -Qs -p140/30 -Itopo_intens.nc -pdf draped3D +To view the Island of Hawaii and the surrounding sea floor in perspective, shaded with physically based +lighting: a low sun from the northwest that makes Mauna Kea and Mauna Loa cast their shadows, ambient +occlusion, tone mapping, a threefold vertical exaggeration of the shading and a brighter sun and fill +light than the defaults:: + + gmt grdcut @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Ghawaii.nc + gmt grdview hawaii.nc -Cgeo -F315/35+o+s+t+v3+l1.4+f0.5 -JM15c -JZ3c -p150/35 -B -pdf hawaii3D + .. module_note_begins Notes @@ -261,6 +281,18 @@ variations within polygons by using scanline conversion to image the polygons. .. module_note_ends +References +---------- + +Heitz, E., Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs, Journal of +Computer Graphics Techniques, Vol. 3, No. 2, 2014, pp. 48-107. + +Schlick, C., An Inexpensive BRDF Model for Physically-based Rendering, Computer Graphics Forum, +Vol. 13, No. 3, 1994, pp. 233-246. + +Walter, B., Marschner, S. R., Li, H., and Torrance, K. E., Microfacet Models for Refraction through +Rough Surfaces, Proceedings of the Eurographics Symposium on Rendering, 2007, pp. 195-206. + See Also -------- diff --git a/src/gmt_prototypes.h b/src/gmt_prototypes.h index 41475ce6483..e8139237fe2 100644 --- a/src/gmt_prototypes.h +++ b/src/gmt_prototypes.h @@ -747,6 +747,11 @@ EXTERN_MSC void gmt_RI_prepare (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h) EXTERN_MSC struct GMT_DATASEGMENT * gmt_prepare_contour (struct GMT_CTRL *GMT, double *x, double *y, uint64_t n, double z); EXTERN_MSC void gmt_get_plot_array (struct GMT_CTRL *GMT); EXTERN_MSC void gmt_illuminate (struct GMT_CTRL *GMT, double intensity, double *rgb); +EXTERN_MSC void gmt_pbr_defaults(struct GMT_PBR *P); +EXTERN_MSC unsigned int gmt_pbr_parse(struct GMT_CTRL *GMT, char option, char *arg, struct GMT_PBR *P); +EXTERN_MSC unsigned int gmt_pbr_check(struct GMT_CTRL *GMT, char option, struct GMT_PBR *P); +EXTERN_MSC void gmt_pbr_syntax(struct GMTAPI_CTRL *API, char option); +EXTERN_MSC struct GMT_IMAGE *gmt_pbr_image(struct GMT_CTRL *GMT, struct GMT_PBR *P, struct GMT_GRID *G, struct GMT_PALETTE *CPT); EXTERN_MSC int gmt_colorname2index (struct GMT_CTRL *GMT, char *name); EXTERN_MSC void gmt_list_custom_symbols (struct GMT_CTRL *GMT); EXTERN_MSC void gmt_smart_justify (struct GMT_CTRL *GMT, int just, double angle, double dx, double dy, double *x_shift, double *y_shift, unsigned int mode); diff --git a/src/gmt_support.c b/src/gmt_support.c index 35b7a5b8389..a43f6f1df5a 100644 --- a/src/gmt_support.c +++ b/src/gmt_support.c @@ -10125,6 +10125,329 @@ void gmt_illuminate (struct GMT_CTRL *GMT, double intensity, double rgb[]) { gmt_hsv_to_rgb (rgb, hsv); } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * Physically based (PBR) shading of a grid through its CPT, used by grdimage -S and grdview. + * Each node's CPT color is the albedo of a microfacet material lit by the sun and a headlight fill, + * seen from straight above. The maths is VTK's PBR fragment shader (vtkOpenGLPolyDataMapper.cxx, + * vtkPBRFunctions.glsl) and its NeutralPBR tone mapping pass (vtkToneMappingPass.cxx), evaluated + * on the CPU: Cook-Torrance specular with the GGX distribution (Walter et al., 2007), the + * height-correlated Smith visibility (Heitz, 2014) and Schlick's (1994) Fresnel, plus the + * Lambertian diffuse. The relief is shaded as a 3-D view draws it: x and y at one scale (x shrunk + * by cos(mid-latitude) for geographic grids) and the z range drawn as 0.1 of the larger horizontal + * side, times the vertical exaggeration. Occlusion and cast shadows are measured on that relief. + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +void gmt_pbr_defaults(struct GMT_PBR *P) { + /* The settings when nothing is given */ + gmt_M_memset(P, 1, struct GMT_PBR); + P->azimuth = 315.0; P->elevation = 45.0; + P->light = 1.0; P->fill = 0.35; + P->roughness = 0.3; P->metallic = 0.0; P->ior = 1.5; + P->radius = 0.1; P->ve = 1.0; +} + +unsigned int gmt_pbr_parse(struct GMT_CTRL *GMT, char option, char *arg, struct GMT_PBR *P) { + /* Parse /[+f][+i][+l][+m][+o[]][+r][+s][+t][+v] */ + unsigned int n_errors = 0, pos = 0; + char p[GMT_BUFSIZ] = {""}, *c = NULL; + + if ((c = gmt_first_modifier(GMT, arg, "filmorstv"))) { /* Process any modifiers */ + while (gmt_getmodopt(GMT, option, c, "filmorstv", &pos, p, &n_errors) && n_errors == 0) { + switch (p[0]) { + case 'f': P->fill = atof(&p[1]); break; + case 'i': P->ior = atof(&p[1]); break; + case 'l': P->light = atof(&p[1]); break; + case 'm': P->metallic = atof(&p[1]); break; + case 'o': P->occlusion = true; if (p[1]) P->radius = atof(&p[1]); break; + case 'r': P->roughness = atof(&p[1]); break; + case 's': P->shadow = true; break; + case 't': P->tone = true; break; + case 'v': P->ve = atof(&p[1]); break; + default: break; /* These are caught in gmt_getmodopt so break is just for Coverity */ + } + } + c[0] = '\0'; /* Chop off all modifiers so azimuth/elevation can be determined */ + } + if (sscanf(arg, "%lf/%lf", &P->azimuth, &P->elevation) != 2) { + GMT_Report(GMT->parent, GMT_MSG_ERROR, "Option -%c: Must give azimuth and elevation\n", option); + n_errors++; + } + if (c) c[0] = '+'; /* Restore modifiers */ + return (n_errors); +} + +unsigned int gmt_pbr_check(struct GMT_CTRL *GMT, char option, struct GMT_PBR *P) { + /* Check the settings against their bounds */ + unsigned int n_errors = 0; + char msg[GMT_LEN128] = {""}; + if (P->elevation < 0.0 || P->elevation > 90.0) { sprintf(msg, "Use 0-90 degree range for elevation"); n_errors++; } + else if (P->roughness < 0.0 || P->roughness > 1.0) { sprintf(msg, "Roughness must be in the 0-1 range"); n_errors++; } + else if (P->metallic < 0.0 || P->metallic > 1.0) { sprintf(msg, "Metallic must be in the 0-1 range"); n_errors++; } + else if (P->ior < 1.0) { sprintf(msg, "Index of refraction must be >= 1"); n_errors++; } + else if (P->light < 0.0 || P->fill < 0.0) { sprintf(msg, "Light intensities must be >= 0"); n_errors++; } + else if (P->occlusion && P->radius <= 0.0) { sprintf(msg, "Occlusion radius must be > 0"); n_errors++; } + else if (P->ve <= 0.0) { sprintf(msg, "Vertical exaggeration must be > 0"); n_errors++; } + if (n_errors) GMT_Report(GMT->parent, GMT_MSG_ERROR, "Option -%c: %s\n", option, msg); + return (n_errors); +} + +void gmt_pbr_syntax(struct GMTAPI_CTRL *API, char option) { + /* The usage lines shared by every option that takes the PBR settings */ + GMT_Usage(API, 1, "\n-%c/[+f][+i][+l][+m][+o[]][+r][+s][+t][+v]", option); + GMT_Usage(API, -2, "Shade the grid with physically based (PBR) lighting: each node's CPT color is lit as a microfacet " + "material by the sun at (0-360) and (0-90) and by a headlight fill, seen from above. The relief is lit " + "as a 3-D view draws it: x and y at one scale and the z range drawn as 0.1 of the larger horizontal side. Modifiers:"); + GMT_Usage(API, 3, "+f Set the (headlight) intensity (>= 0) [0.35]."); + GMT_Usage(API, 3, "+i Set the index of refraction of the surface (>= 1) [1.5]."); + GMT_Usage(API, 3, "+l Set the (sun) intensity (>= 0) [1]."); + GMT_Usage(API, 3, "+m Set the value (0-1) [0]."); + GMT_Usage(API, 3, "+o Add ambient occlusion; optionally append the sampling as a fraction of the " + "diagonal of the relief as drawn (> 0) [0.1]."); + GMT_Usage(API, 3, "+r Set the (0-1) [0.3]."); + GMT_Usage(API, 3, "+s Cast shadows along the sun's azimuth and elevation."); + GMT_Usage(API, 3, "+t Apply the Khronos PBR Neutral tone mapping."); + GMT_Usage(API, 3, "+v Set the vertical exaggeration of the relief (> 0) [1]."); +} + +GMT_LOCAL double gmtsupport_pbr_z(struct GMT_GRID *G, int ix, int iy) { /* z at column ix and row iy counted from the south */ + return (G->data[gmt_M_ijp(G->header, G->header->n_rows - 1 - iy, ix)]); +} + +GMT_LOCAL void gmtsupport_pbr_drawn(struct GMT_PBR *P, double *nv, double *o) { + /* The data-space unit normal nv turned into the normal of the relief as drawn */ + double len; + o[0] = nv[0] * P->fx; + o[1] = nv[1]; + o[2] = nv[2] * P->fz; + len = sqrt(o[0] * o[0] + o[1] * o[1] + o[2] * o[2]); + if (len > 0.0) { o[0] /= len; o[1] /= len; o[2] /= len; } +} + +GMT_LOCAL void gmtsupport_pbr_light(struct GMT_PBR *P, double NdH, double NdL, double NdV, double HdL, double radiance, double *alb, double *F0, double *F90, double *Lo) { + /* Add one light's reflected radiance to Lo[3] */ + unsigned int k; + double d, D, gv, gl, V, fw, F; + if (!(radiance > 0.0)) return; + d = (NdH * P->a2 - NdH) * NdH + 1.0; + D = P->a2 / (M_PI * d * d); + gv = NdL * sqrt(P->a + NdV * (NdV - P->a * NdV)); + gl = NdV * sqrt(P->a + NdL * (NdL - P->a * NdL)); + V = 0.5 / (gv + gl); + fw = pow(1.0 - HdL, 5.0); + for (k = 0; k < 3; k++) { + F = F0[k] + (F90[k] - F0[k]) * fw; + Lo[k] += radiance * (D * V * F + (1.0 - P->metallic) * (1.0 - F) * alb[k] * (1.0 / M_PI)) * NdL; + } +} + +GMT_LOCAL void gmtsupport_pbr_shade(struct GMT_PBR *P, double *nv, double ao, double sun, double *rgb) { + /* rgb[0-2] is the albedo (0-1) on input and the shaded, gamma encoded color on output. nv is the + * data-space unit normal, ao the occlusion factor (1 = open) and sun 0 if the sun is blocked */ + unsigned int k; + double N[3], alb[3], F0[3], F90[3], Lo[3] = {0.0, 0.0, 0.0}, t[3]; + double NdV, NdL, NdH, f90, x, offset, peak, d, new_peak, g, a; + + gmtsupport_pbr_drawn(P, nv, N); + NdV = MAX(1.0e-5, MIN(1.0, N[2])); /* The viewer is straight above: V = +z */ + for (k = 0; k < 3; k++) { + alb[k] = MAX(0.0, MIN(1.0, rgb[k])); + F0[k] = P->f0 + (alb[k] - P->f0) * P->metallic; /* A metal reflects its own color */ + } + f90 = MAX(0.0, MIN(1.0, (F0[0] + F0[1] + F0[2]) * (50.0 * 0.33))); /* Specular occlusion of a very low F0 */ + for (k = 0; k < 3; k++) F90[k] = f90 + (1.0 - f90) * P->metallic; + NdL = MAX(1.0e-5, MIN(1.0, N[0] * P->L[0] + N[1] * P->L[1] + N[2] * P->L[2])); + NdH = MAX(1.0e-5, MIN(1.0, N[0] * P->H[0] + N[1] * P->H[1] + N[2] * P->H[2])); + gmtsupport_pbr_light(P, NdH, NdL, NdV, P->HdL, P->light * sun, alb, F0, F90, Lo); /* The sun */ + gmtsupport_pbr_light(P, NdV, NdV, NdV, 1.0, P->fill, alb, F0, F90, Lo); /* The headlight: L = H = V */ + ao = MAX(0.0, MIN(1.0, ao)); + if (!P->tone) { /* Encode, then occlude */ + for (k = 0; k < 3; k++) rgb[k] = MAX(0.0, MIN(1.0, pow(MAX(0.0, MIN(1.0, Lo[k])), 1.0 / 2.2) * ao)); + return; + } + if (ao < 1.0) { /* Occlusion acts before the tone mapping linearises the frame again */ + a = pow(ao, 2.2); + for (k = 0; k < 3; k++) Lo[k] *= a; + } + x = MIN(Lo[0], MIN(Lo[1], Lo[2])); /* Khronos PBR Neutral tone mapping */ + offset = (x < 0.08) ? x - 6.25 * x * x : 0.04; + for (k = 0; k < 3; k++) t[k] = Lo[k] - offset; + peak = MAX(t[0], MAX(t[1], t[2])); + if (peak >= 0.8 - 0.04) { + d = 1.0 - (0.8 - 0.04); + new_peak = 1.0 - d * d / (peak + d - (0.8 - 0.04)); + g = 1.0 - 1.0 / (0.15 * (peak - new_peak) + 1.0); + for (k = 0; k < 3; k++) { + t[k] *= new_peak / peak; + t[k] = t[k] + (new_peak - t[k]) * g; + } + } + for (k = 0; k < 3; k++) rgb[k] = pow(MAX(0.0, MIN(1.0, t[k])), 1.0 / 2.2); +} + +GMT_LOCAL double gmtsupport_pbr_height(struct GMT_CTRL *GMT, struct GMT_GRID *G, struct GMT_PBR *P, double fx, double fy) { + /* Bilinear drawn height at fractional column fx and row fy (from the south); NaN off the grid */ + int nx = (int)G->header->n_columns, ny = (int)G->header->n_rows, x0, y0, x1, y1; + double tx, ty, a, b, c, d; + if (!(fx >= 0.0 && fy >= 0.0 && fx <= nx - 1 && fy <= ny - 1)) return (GMT->session.d_NaN); + x0 = (int)fx; y0 = (int)fy; + x1 = MIN(x0 + 1, nx - 1); y1 = MIN(y0 + 1, ny - 1); + tx = fx - x0; ty = fy - y0; + a = gmtsupport_pbr_z(G, x0, y0); b = gmtsupport_pbr_z(G, x1, y0); + c = gmtsupport_pbr_z(G, x0, y1); d = gmtsupport_pbr_z(G, x1, y1); + return (((a * (1.0 - tx) + b * tx) * (1.0 - ty) + (c * (1.0 - tx) + d * tx) * ty) / P->fz); +} + +GMT_LOCAL void gmtsupport_pbr_occlusion(struct GMT_CTRL *GMT, struct GMT_GRID *G, struct GMT_PBR *P, int ix, int iy, double *nv, double *ao, double *sun) { + /* Ambient occlusion and cast shadow at node (ix,iy), measured on the relief as drawn. + * Shadow: march toward the sun, one cell at first and growing slowly, until the ray is above the + * highest point of the relief or leaves the grid; the sun is blocked if the terrain rises above it. + * Occlusion: horizon based, 8 directions x 6 samples within the radius. Each direction contributes + * how far the terrain rises above the node's tangent plane (sine of the horizon angle minus sine of + * the tangent angle), weighted down toward the radius; ao = 1 - the mean over the directions. */ + int nx = (int)G->header->n_columns, ny = (int)G->header->n_rows, k, d, j; + double dx = G->header->inc[GMT_X], dy = G->header->inc[GMT_Y], z0, h0, kx, ky, cell, hl, ux, uy, tan_el, bias; + double t, ray, fx, fy, h, N[3], ang, cx, cy, tan_t, sin_t, best, r, dh, sin_h, w, occ = 0.0; + + *ao = *sun = 1.0; + if ((!P->occlusion && !P->shadow) || nx < 2 || ny < 2) return; + z0 = gmtsupport_pbr_z(G, ix, iy); + if (gmt_M_is_dnan(z0)) return; + h0 = z0 / P->fz; + kx = P->fx / dx; ky = 1.0 / dy; /* Drawn distance -> grid index */ + cell = MIN(fabs(dx) / P->fx, fabs(dy)); + if (P->shadow && P->L[2] > 0.0) { + hl = sqrt(P->L[0] * P->L[0] + P->L[1] * P->L[1]); + if (hl > 1.0e-6) { + ux = P->L[0] / hl; uy = P->L[1] / hl; tan_el = P->L[2] / hl; + bias = 0.25 * cell * tan_el; /* Keeps a lit slope from shadowing itself */ + for (k = 0, t = cell; k < 512; k++) { + ray = h0 + t * tan_el; + if (ray > P->h_top) break; + fx = ix + ux * t * kx; fy = iy + uy * t * ky; + if (!(fx >= 0.0 && fy >= 0.0 && fx <= nx - 1 && fy <= ny - 1)) break; + h = gmtsupport_pbr_height(GMT, G, P, fx, fy); + if (!gmt_M_is_dnan(h) && h > ray + bias) { *sun = 0.0; break; } + t += cell * (1.0 + k / 64.0); + } + } + } + if (P->occlusion && P->box_radius > cell) { + gmtsupport_pbr_drawn(P, nv, N); + for (d = 0; d < 8; d++) { + ang = 2.0 * M_PI * d / 8; + cx = cos(ang); cy = sin(ang); + tan_t = (N[2] > 1.0e-6) ? -(N[0] * cx + N[1] * cy) / N[2] : 0.0; /* Tangent plane slope along d */ + sin_t = tan_t / sqrt(1.0 + tan_t * tan_t); + for (j = 0, best = 0.0; j < 6; j++) { + r = P->box_radius * (j + 0.5) / 6; + h = gmtsupport_pbr_height(GMT, G, P, ix + cx * r * kx, iy + cy * r * ky); + if (gmt_M_is_dnan(h)) continue; + dh = h - h0; + sin_h = dh / sqrt(dh * dh + r * r); + w = 1.0 - (r / P->box_radius) * (r / P->box_radius); + best = MAX(best, (sin_h - sin_t) * w); + } + occ += best; + } + *ao = MAX(0.0, MIN(1.0, 1.0 - occ / 8)); + } +} + +struct GMT_IMAGE *gmt_pbr_image(struct GMT_CTRL *GMT, struct GMT_PBR *P, struct GMT_GRID *G, struct GMT_PALETTE *CPT) { + /* Shade grid G through the CPT into a new RGB image on G's nodes: padded by 2, pixel interleaved, and in + * the row order GMT's own image reader delivers (north row first), which it labels BRPa */ + int nx, ny, ix, iy, ixm, ixp, iym, iyp; + openmp_int row, col; + unsigned int band; + uint64_t node, dim[3] = {0, 0, 3}; + double zspan, z_top, x_fac, z_fac, W, H, Z, r, dx, dy, zc, za, zb, zu, zd, dzdx, dzdy, len; + double nv[3], rgb[4] = {0.0, 0.0, 0.0, 0.0}, ao, sun; + struct GMT_IMAGE *I = NULL; + + /* The relief as a 3-D view draws it */ + nx = (int)G->header->n_columns; ny = (int)G->header->n_rows; + dx = G->header->inc[GMT_X]; dy = G->header->inc[GMT_Y]; + x_fac = (gmt_M_is_geographic(GMT, GMT_IN)) ? MAX(1.0e-6, cosd(0.5 * (G->header->wesn[YLO] + G->header->wesn[YHI]))) : 1.0; + W = fabs(G->header->wesn[XHI] - G->header->wesn[XLO]) * x_fac; + H = fabs(G->header->wesn[YHI] - G->header->wesn[YLO]); + zspan = G->header->z_max - G->header->z_min; + z_fac = (zspan > 0.0 && MAX(W, H) > 0.0) ? 0.1 * MAX(W, H) / zspan : 1.0; + P->fx = 1.0 / x_fac; + P->fz = 1.0 / (z_fac * P->ve); + P->L[0] = sind(P->azimuth) * cosd(P->elevation); + P->L[1] = cosd(P->azimuth) * cosd(P->elevation); + P->L[2] = sind(P->elevation); + P->H[0] = P->L[0]; P->H[1] = P->L[1]; P->H[2] = P->L[2] + 1.0; /* Halfway between L and V = +z */ + len = sqrt(P->H[0] * P->H[0] + P->H[1] * P->H[1] + P->H[2] * P->H[2]); + if (len > 0.0) { P->H[0] /= len; P->H[1] /= len; P->H[2] /= len; } + P->HdL = MAX(1.0e-5, MIN(1.0, P->H[0] * P->L[0] + P->H[1] * P->L[1] + P->H[2] * P->L[2])); + P->a = MAX(P->roughness, 0.05); /* Keep the GGX lobe finite */ + P->a *= P->a; P->a2 = P->a * P->a; + r = (P->ior - 1.0) / (P->ior + 1.0); + P->f0 = r * r; + Z = zspan * z_fac * P->ve; + P->box_radius = P->radius * sqrt(W * W + H * H + Z * Z); + z_top = -DBL_MAX; + if (P->shadow) { /* Highest point, pad excluded: only the shadow march reads it. Each thread keeps its own + * maximum and merges it at the end, since reduction(max:) is not in OpenMP 2.0 (MSVC) */ +#ifdef _OPENMP +#pragma omp parallel private(row,col,node) shared(G,z_top) +#endif + { + double z_max = -DBL_MAX; +#ifdef _OPENMP +#pragma omp for +#endif + for (row = 0; row < (openmp_int)G->header->n_rows; row++) { + for (col = 0; col < (openmp_int)G->header->n_columns; col++) { + node = gmt_M_ijp(G->header, row, col); + if (!gmt_M_is_fnan(G->data[node]) && G->data[node] > z_max) z_max = G->data[node]; + } + } +#ifdef _OPENMP +#pragma omp critical +#endif + if (z_max > z_top) z_top = z_max; + } + } + P->h_top = z_top / P->fz; + + /* Padded like any image a module may have to project, and pixel interleaved, which is how it is filled */ + if ((I = GMT_Create_Data(GMT->parent, GMT_IS_IMAGE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA, dim, G->header->wesn, G->header->inc, G->header->registration, 2, NULL)) == NULL) + return (NULL); + strncpy(I->header->mem_layout, "BRPa", 4); + /* Every node is independent: it only reads the grid and writes its own pixel. Rows are handed out + * dynamically because the shadow march and the occlusion make a node's cost vary widely with the terrain */ +#ifdef _OPENMP +#pragma omp parallel for schedule(dynamic) private(row,col,ix,iy,ixm,ixp,iym,iyp,zc,za,zb,zu,zd,dzdx,dzdy,len,nv,rgb,ao,sun,node,band) shared(GMT,P,G,I,CPT,nx,ny,dx,dy) +#endif + for (row = 0; row < (openmp_int)G->header->n_rows; row++) { + iy = ny - 1 - (int)row; /* Row counted from the south */ + iym = (iy > 0) ? iy - 1 : iy; iyp = (iy < ny - 1) ? iy + 1 : iy; + for (col = 0; col < (openmp_int)G->header->n_columns; col++) { + ix = (int)col; + zc = gmtsupport_pbr_z(G, ix, iy); + (void)gmt_get_rgb_from_z(GMT, CPT, zc, rgb); + if (!gmt_M_is_dnan(zc)) { /* Central differences, edge clamped; a NaN neighbour gives a flat direction */ + ixm = (ix > 0) ? ix - 1 : ix; ixp = (ix < nx - 1) ? ix + 1 : ix; + za = gmtsupport_pbr_z(G, ixp, iy); zb = gmtsupport_pbr_z(G, ixm, iy); + zu = gmtsupport_pbr_z(G, ix, iyp); zd = gmtsupport_pbr_z(G, ix, iym); + dzdx = (ixp == ixm || gmt_M_is_dnan(za) || gmt_M_is_dnan(zb)) ? 0.0 : (za - zb) / ((ixp - ixm) * dx); + dzdy = (iyp == iym || gmt_M_is_dnan(zu) || gmt_M_is_dnan(zd)) ? 0.0 : (zu - zd) / ((iyp - iym) * dy); + nv[0] = -dzdx; nv[1] = -dzdy; nv[2] = 1.0; + len = sqrt(nv[0] * nv[0] + nv[1] * nv[1] + nv[2] * nv[2]); + nv[0] /= len; nv[1] /= len; nv[2] /= len; + gmtsupport_pbr_occlusion(GMT, G, P, ix, iy, nv, &ao, &sun); + gmtsupport_pbr_shade(P, nv, ao, sun, rgb); + } + node = gmt_M_ijpgi(I->header, row, col); + for (band = 0; band < 3; band++) + I->data[node + band] = (unsigned char)MIN(255.0, rgb[band] * 255.0 + 0.5); + } + } + return (I); +} + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * gmtlib_akima computes the coefficients for a quasi-cubic hermite spline. * Same algorithm as in the IMSL library. diff --git a/src/gmt_types.h b/src/gmt_types.h index 869cf07443a..8247c82a9fb 100644 --- a/src/gmt_types.h +++ b/src/gmt_types.h @@ -480,6 +480,25 @@ struct GMT_CTRL { struct GMT_INTERNAL hidden; /* Internal global variables that are not to be changed directly by users */ }; +/* Physically based (PBR) shading of a grid through its CPT (grdimage -S, grdview): the settings a user + * gives, and what gmt_pbr_image derives from them and from the grid (see gmt_support.c) */ +struct GMT_PBR { + bool occlusion, shadow, tone; /* Ambient occlusion, cast shadows, tone mapping */ + double azimuth, elevation; /* Direction of the sun, in degrees */ + double light, fill; /* Intensities of the sun and of the headlight */ + double roughness, metallic, ior; /* The material */ + double radius; /* Occlusion radius, as a fraction of the diagonal of the relief as drawn */ + double ve; /* Vertical exaggeration */ + /* Derived, set by gmt_pbr_image */ + double L[3]; /* Unit vector toward the sun */ + double H[3], HdL; /* Unit halfway vector between the sun and the viewer (+z), and H.L */ + double a, a2; /* GGX alpha (roughness squared) and its square */ + double f0; /* Dielectric reflectance at normal incidence, from the IOR */ + double fx, fz; /* 1 / x scale and 1 / z scale of the relief as drawn */ + double box_radius; /* Occlusion radius, drawn units */ + double h_top; /* Highest point of the relief, drawn units */ +}; + /* p_to_io_func is used as a pointer to functions such as GMT_read_d in assignments * and is used to declare gmtlib_get_io_ptr in gmt_io.c and gmt_prototypes.h */ typedef int (*p_to_io_func) (struct GMT_CTRL *, FILE *, uint64_t, double *); diff --git a/src/grdimage.c b/src/grdimage.c index eef56fc1106..7f824d730c1 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -105,6 +105,10 @@ struct GRDIMAGE_CTRL { double rgb[4]; /* Pixel value for transparency in images */ double value; /* If +z is used this z-value will give us the r/g/b via CPT */ } Q; + struct GRDIMAGE_F { /* -F/[+f][+i][+l][+m][+o[]][+r][+s][+t][+v] */ + bool active; + struct GMT_PBR P; /* Physically based shading settings (gmt_support.c) */ + } F; struct GRDIMAGE_T { /* -T[s][o[]] */ bool active; bool skip; @@ -154,6 +158,7 @@ static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new C->I.azimuth = strdup ("-45.0"); /* Default azimuth for shading when -I+d is used */ C->I.method = strdup ("t1"); /* Default normalization for shading when -I+d is used */ C->I.ambient = strdup ("0"); /* Default ambient light for shading when -I+d is used */ + gmt_pbr_defaults(&C->F.P); /* Physically based shading defaults (-F, or -I+f) */ C->Q.value = GMT->session.f_NaN; /* If -Q is used with a grid then NaNs are made transparent by default. */ C->Q.rgb[0] = C->Q.rgb[1] = C->Q.rgb[2] = 1.0; /* White transparent pixel is default if -Q is given without a color */ return (C); @@ -178,6 +183,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) { const char *extra[2] = {A, " [-A]"}; if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR); GMT_Usage (API, 0, "usage: %s %s %s%s [%s] [-C%s] [-D[r]] [-Ei|] " + "[-F/[+f][+i][+l][+m][+o[]][+r][+s][+t][+v]] " "[-G[+b|f]] [-I[||]] %s[-M] [-N] %s%s[-Q[][+i][+z]] " "[%s] [-T[+o[]][+s]] [%s] [%s] [%s] [%s] %s[%s] [%s] [%s] [%s]%s[%s]\n", name, GMT_INGRID, GMT_J_OPT, extra[API->external], GMT_B_OPT, CPT_OPT_ARGS, API->K_OPT, API->O_OPT, API->P_OPT, GMT_Rgeo_OPT, GMT_U_OPT, @@ -215,6 +221,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, -2, "Set dpi for the projected grid which must be constructed [100] " "if -J implies a nonlinear graticule [Default gives same size as input grid]. " "Alternatively, append i to do the interpolation in PostScript at device resolution (invalid with -Q)."); + gmt_pbr_syntax(API, 'F'); /* Replaces -I */ gmt_rgb_syntax (API->GMT, 'G', "Set transparency color for images that otherwise would result in 1-bit images. "); GMT_Usage (API, 3, "+b Set background color."); GMT_Usage (API, 3, "+f Set foreground color [Default]."); @@ -226,7 +233,8 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, 3, "+n Set the and to use [t1]."); GMT_Usage (API, 3, "+m Set light to add [0]."); GMT_Usage (API, -2, "Alternatively, use -I+d to accept the default values (see grdgradient for more details). " - "To derive intensities from another grid than , give the alternative data grid with suitable modifiers."); + "To derive intensities from another grid than , give the alternative data grid with suitable modifiers. " + "Use -I+f to shade physically based (see -F) with its default settings, i.e., -F315/45+o+t."); GMT_Option (API, "J-"); GMT_Option (API, "K"); GMT_Usage (API, 1, "\n-M Force a monochrome (gray-scale) image."); @@ -359,6 +367,10 @@ static int parse(struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_OP else Ctrl->E.dpi = atoi (opt->arg); break; + case 'F': /* Physically based shading -F/[+f][+i][+l][+m][+o[]][+r][+s][+t][+v] */ + n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); + n_errors += gmt_pbr_parse(GMT, 'F', opt->arg, &Ctrl->F.P); + break; case 'G': /* -G[+b|f] 1-bit fore- or background color for transparent masks (was -G[f|b]) */ n_errors += gmt_M_repeated_module_option (API, Ctrl->G.active); if ((c = strstr (opt->arg, "+b"))) { /* Background color */ @@ -385,6 +397,12 @@ static int parse(struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_OP break; case 'I': /* Use intensity from grid or constant or auto-compute it */ n_errors += gmt_M_repeated_module_option (API, Ctrl->I.active); + if (!strcmp(opt->arg, "+f")) { /* -I+f: physically based shading (-F) with its default settings */ + n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); + Ctrl->F.P.occlusion = Ctrl->F.P.tone = true; + Ctrl->I.active = false; /* No intensities: -F replaces -I */ + break; + } if ((c = strstr (opt->arg, "+d"))) { /* Gave +d, so derive intensities from the input grid using default settings */ Ctrl->I.derive = true; c[0] = '\0'; /* Chop off modifier */ @@ -542,6 +560,10 @@ static int parse(struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_OP "Option -A: Cannot draw base frame (-B) when creating just a raster image\n"); n_errors += gmt_M_check_condition (GMT, Ctrl->Q.transp_color && Ctrl->Q.z_given, "Option Q: Cannot both specify a r/g/b and a grid z-value\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->F.active && Ctrl->I.active, "Option -F: Cannot be combined with -I\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->F.active && (Ctrl->T.active || Ctrl->D.active || n_files == 3), + "Option -F: Requires a single grid and cannot be used with -T\n"); + if (Ctrl->F.active) n_errors += gmt_pbr_check(GMT, 'F', &Ctrl->F.P); return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR); } @@ -1950,6 +1972,15 @@ EXTERN_MSC int GMT_grdimage(void *V_API, int mode, void *args) { for (node = 0; !has_content && node < Grid_orig->header->size; node++) if (!gmt_M_is_dnan (Grid_orig->data[node])) has_content = true; } + if (Ctrl->F.active) { /* Physically based shading: from here on the grid, shaded through its CPT, is an image input */ + GMT_Report(API, GMT_MSG_INFORMATION, "Physically based shading of the grid into an image\n"); + if ((I = gmt_pbr_image(GMT, &Ctrl->F.P, Grid_orig, P)) == NULL) Return (API->error); + got_z_grid = gray_only = false; + Ctrl->D.active = true; /* So it is projected as an image */ + grid_registration = I->header->registration; + Conf->n_columns = I->header->n_columns; + Conf->n_rows = I->header->n_rows; + } } if (Ctrl->T.active) { /* Plot colored graticules instead */ diff --git a/src/grdview.c b/src/grdview.c index fe4ce14f870..569b8135089 100644 --- a/src/grdview.c +++ b/src/grdview.c @@ -74,6 +74,10 @@ struct GRDVIEW_CTRL { char *file; char *savecpt; /* For when we want to save the automatically generated CPT */ } C; + struct GRDVIEW_F { /* -F/[+f][+i][+l][+m][+o[]][+r][+s][+t][+v] */ + bool active; + struct GMT_PBR P; /* Physically based shading settings (gmt_support.c) */ + } F; struct GRDVIEW_G { /* -G| */ /* Also handle deprecated triples -Gred.grd -Ggreen.grd -Gblue.grd or -Gred.grd,green.grd,blue.grd */ bool active; @@ -474,6 +478,7 @@ static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new /* Initialize values whose defaults are not 0/false/NULL */ gmt_init_fill (GMT, &C->N.fill, -1.0, -1.0, -1.0); /* Default is no fill of facade */ + gmt_pbr_defaults(&C->F.P); /* Physically based shading defaults (-F, or -I+f) */ C->I.azimuth = strdup ("-45.0"); /* Default azimuth for shading when -I is used */ C->I.method = strdup ("t1"); /* Default normalization for shading when -I is used */ C->T.pen = C->W.pen[0] = C->W.pen[1] = C->W.pen[2] = GMT->current.setting.map_default_pen; /* Tile and mesh pens */ @@ -503,7 +508,8 @@ static int usage (struct GMTAPI_CTRL *API, int level) { const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE); if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR); - GMT_Usage (API, 0, "usage: %s %s [%s] [-C%s] [-G|] " + GMT_Usage (API, 0, "usage: %s %s [%s] [-C%s] " + "[-F/[+f][+i][+l][+m][+o[]][+r][+s][+t][+v]] [-G|] " "[-I[||]] [%s] %s[-N[][+g]] %s%s[-Qc|g[m|a]|i|m[x|y]|s[][+m]] [%s] [-S] " "[-T[+o[]][+s]] [%s] [%s] [-W] [%s] [%s] %s[%s] [%s] [%s] [%s] [%s]\n", name, GMT_J_OPT, GMT_B_OPT, CPT_OPT_ARGS, GMT_Jz_OPT, API->K_OPT, API->O_OPT, API->P_OPT, GMT_Rgeoz_OPT, GMT_U_OPT, GMT_V_OPT, @@ -517,6 +523,9 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Message (API, GMT_TIME_NONE, "\n OPTIONAL ARGUMENTS:\n"); GMT_Option (API, "B-"); gmt_explain_cpt_input (API, 'C'); + gmt_pbr_syntax(API, 'F'); + GMT_Usage(API, -2, "The shaded colors are draped over as an image, so -F implies -Qi [or use -Qc|i] and cannot be " + "combined with -G or -I."); GMT_Usage (API, 1, "\n-G|"); GMT_Usage (API, -2, "Specify how to color the 3-D surface geometry defined by :"); GMT_Usage (API, 3, "%s Provide a grid () and colors will be determined from it and the cpt.", GMT_LINE_BULLET); @@ -531,7 +540,8 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, 3, "+n Set the and to use [t1]."); GMT_Usage (API, 3, "+m Set light to add [0]."); GMT_Usage (API, -2, "Alternatively, use -I+d to accept the default values (see grdgradient for more details). " - "To derive intensities from another grid than , give the alternative data grid with suitable modifiers."); + "To derive intensities from another grid than , give the alternative data grid with suitable modifiers. " + "Use -I+f to shade physically based (see -F) with its default settings, i.e., -F315/45+o+t."); GMT_Option (API, "K"); GMT_Usage (API, 1, "\n-N[][+g]"); GMT_Usage (API, -2, "Draw a horizontal plane at z = [minimum grid (or -R) value]. For rectangular projections, append +g " @@ -618,6 +628,10 @@ static int parse (struct GMT_CTRL *GMT, struct GRDVIEW_CTRL *Ctrl, struct GMT_OP } gmt_cpt_interval_modifier (GMT, &(Ctrl->C.file), &(Ctrl->C.dz)); break; + case 'F': /* Physically based shading -F/[+f][+i][+l][+m][+o[]][+r][+s][+t][+v] */ + n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); + n_errors += gmt_pbr_parse(GMT, 'F', opt->arg, &Ctrl->F.P); + break; case 'G': /* One grid or image or three separate r,g,b grids */ Ctrl->G.active = true; for (k = 0, n_commas = 0; opt->arg[k]; k++) if (opt->arg[k] == ',') n_commas++; @@ -642,6 +656,12 @@ static int parse (struct GMT_CTRL *GMT, struct GRDVIEW_CTRL *Ctrl, struct GMT_OP break; case 'I': /* Use intensity from grid or constant or auto-compute it */ n_errors += gmt_M_repeated_module_option (API, Ctrl->I.active); + if (!strcmp(opt->arg, "+f")) { /* -I+f: physically based shading (-F) with its default settings */ + n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); + Ctrl->F.P.occlusion = Ctrl->F.P.tone = true; + Ctrl->I.active = false; /* No intensities: -F replaces -I */ + break; + } if ((c = strstr (opt->arg, "+d"))) { /* Gave +d, so derive intensities from the input grid using default settings */ Ctrl->I.derive = true; c[0] = '\0'; /* Chop off modifier */ @@ -890,6 +910,13 @@ static int parse (struct GMT_CTRL *GMT, struct GRDVIEW_CTRL *Ctrl, struct GMT_OP "Option -Qs: Must also specify a cpt via -C\n"); n_errors += gmt_M_check_condition(GMT, Ctrl->T.active && GMT->current.proj.JZ_set, "Option -T: Cannot specify -JZ|z\n"); + if (Ctrl->F.active) { /* The shaded colors are draped as an image */ + n_errors += gmt_M_check_condition(GMT, Ctrl->I.active, "Option -F: Cannot be combined with -I\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->G.active, "Option -F: Cannot be combined with -G\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->T.active, "Option -F: Cannot be combined with -T\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->Q.active && Ctrl->Q.mode != GRDVIEW_IMAGE, "Option -F: Requires -Qc|i\n"); + n_errors += gmt_pbr_check(GMT, 'F', &Ctrl->F.P); + } return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR); } @@ -949,6 +976,13 @@ EXTERN_MSC int GMT_grdview(void *V_API, int mode, void *args) { /*---------------------------- This is the grdview main code ----------------------------*/ + if (Ctrl->F.active) { /* Physically based shading: the topo grid is shaded through its CPT (gmt_pbr_image) into an + * image that is then draped over it exactly as a -G would be */ + Ctrl->C.active = Ctrl->G.active = true; + Ctrl->G.n = 1; + if (!Ctrl->Q.active) Ctrl->Q.mode = GRDVIEW_IMAGE; /* A drape needs an image mode */ + } + gmt_grd_set_datapadding (GMT, true); /* Turn on gridpadding when reading a subset */ GMT->current.plot.mode_3D = 1; /* Only do background axis first; do foreground at end */ @@ -1067,7 +1101,7 @@ EXTERN_MSC int GMT_grdview(void *V_API, int mode, void *args) { t_reg = gmt_change_grdreg (GMT, Topo->header, GMT_GRID_NODE_REG); /* Ensure gridline registration */ if (Ctrl->C.active) { - char *dataset_cpt = (Ctrl->G.active && Ctrl->G.n == 1 && !gmt_M_file_is_image (Ctrl->G.file[0])) ? Ctrl->G.file[0] : Ctrl->In.file; + char *dataset_cpt = (!Ctrl->F.active && Ctrl->G.active && Ctrl->G.n == 1 && !gmt_M_file_is_image (Ctrl->G.file[0])) ? Ctrl->G.file[0] : Ctrl->In.file; char *cpt = gmt_cpt_default (API, Ctrl->C.file, dataset_cpt, Topo->header); if ((P = gmt_get_palette (GMT, cpt, GMT_CPT_OPTIONAL, Topo->header->z_min, Topo->header->z_max, Ctrl->C.dz)) == NULL) { Return (API->error); @@ -1082,11 +1116,17 @@ EXTERN_MSC int GMT_grdview(void *V_API, int mode, void *args) { get_contours = (Ctrl->Q.mode == GRDVIEW_MESH && Ctrl->W.contour) || (Ctrl->Q.mode == GRDVIEW_SURF && !Ctrl->Q.gouraud && P && P->n_colors > 1) || (Ctrl->Q.gouraud && Ctrl->W.contour); if (Ctrl->G.active) { /* Draping wanted */ - if (Ctrl->G.n == 1 && gmt_M_file_is_image (Ctrl->G.file[0])) { + if (Ctrl->F.active || (Ctrl->G.n == 1 && gmt_M_file_is_image (Ctrl->G.file[0]))) { double inc[2]; /* Want to drape an image on top of surface. Do so by converting the image to r, g, b grids */ struct GMT_IMAGE *I = NULL; - if ((I = GMT_Read_Data (API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA | GMT_IMAGE_NO_INDEX | GMT_GRID_NEEDS_PAD2, NULL, Ctrl->G.file[0], NULL)) == NULL) { + if (Ctrl->F.active) { /* The image is the topo grid itself, shaded physically based through its CPT */ + GMT_Report(API, GMT_MSG_INFORMATION, "Physically based shading of the grid into a drape image\n"); + if ((I = gmt_pbr_image(GMT, &Ctrl->F.P, Topo, P)) == NULL) { + Return (API->error); + } + } + else if ((I = GMT_Read_Data (API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA | GMT_IMAGE_NO_INDEX | GMT_GRID_NEEDS_PAD2, NULL, Ctrl->G.file[0], NULL)) == NULL) { Return (API->error); } /* Compute the x,y increment in the Topo x/y units that the image will have give its dimensions and pixel registration */ diff --git a/src/longopt/grdimage_inc.h b/src/longopt/grdimage_inc.h index 29dc61e49df..1f213cd3160 100644 --- a/src/longopt/grdimage_inc.h +++ b/src/longopt/grdimage_inc.h @@ -35,13 +35,17 @@ static struct GMT_KEYWORD_DICTIONARY module_kw[] = { /* Local options for this m "i", "psdeviceres", "", "", GMT_TP_STANDARD }, + { 0, 'F', "pbr|physical", + "", "", + "f,i,l,m,o,r,s,t,v", "fill,ior,light,metallic,occlusion,roughness,shadows,tonemap,exaggeration", + GMT_TP_STANDARD }, { 0, 'G', "bitcolor", "", "", "b,f", "background,foreground", GMT_TP_STANDARD }, { 0, 'I', "intensity", "", "", - "a,d,m,n", "azimuth,default,ambient,intensity", + "a,d,f,m,n", "azimuth,default,pbr,ambient,intensity", GMT_TP_STANDARD }, { 0, 'M', "monochrome", "", "", "", "", GMT_TP_STANDARD }, { 0, 'N', "noclip", "", "", "", "", GMT_TP_STANDARD }, diff --git a/src/longopt/grdview_inc.h b/src/longopt/grdview_inc.h index 4e3c579a674..5a1e0a5528a 100644 --- a/src/longopt/grdview_inc.h +++ b/src/longopt/grdview_inc.h @@ -26,10 +26,14 @@ static struct GMT_KEYWORD_DICTIONARY module_kw[] = { short_modifiers, long_modifiers, transproc_mask */ GMT_C_CPT_KW, + { 0, 'F', "pbr|physical", + "", "", + "f,i,l,m,o,r,s,t,v", "fill,ior,light,metallic,occlusion,roughness,shadows,tonemap,exaggeration", + GMT_TP_STANDARD }, { 0, 'G', "drapegrid|drape", "", "", "", "", GMT_TP_STANDARD }, { 0, 'I', "illumination|shading|intensity", "", "", - "a,d,m,n", "azimuth,default,ambient,args", + "a,d,f,m,n", "azimuth,default,pbr,ambient,args", GMT_TP_STANDARD }, { 0, 'N', "plane", "", "", diff --git a/test/grdimage/grdimage-l2s.sh b/test/grdimage/grdimage-l2s.sh index 4ab0bed268b..b042716f69c 100755 --- a/test/grdimage/grdimage-l2s.sh +++ b/test/grdimage/grdimage-l2s.sh @@ -19,6 +19,8 @@ cat << EOF > $a --l2stranstest -M --l2stranstest -N --l2stranstest -Qred -Q+z-999 +--l2stranstest -F315/30+f0.2+i1.8+l1.2+m0.5 -I+f +--l2stranstest -F45/20+o0.2+r0.6+s+t+v2 EOF # module-specific longopts @@ -32,5 +34,7 @@ gmt $m $l2s --intensity+azimuth:10+ambient:0+intensity:t1 >> $b gmt $m $l2s --monochrome >> $b gmt $m $l2s --noclip >> $b gmt $m $l2s --alphacolor=red --alphacolor+gridvalue:-999 >> $b +gmt $m $l2s --pbr=315/30+fill:0.2+ior:1.8+light:1.2+metallic:0.5 --intensity+pbr >> $b +gmt $m $l2s --physical=45/20+occlusion:0.2+roughness:0.6+shadows+tonemap+exaggeration:2 >> $b diff $a $b --strip-trailing-cr > fail diff --git a/test/grdimage/pbr.sh b/test/grdimage/pbr.sh new file mode 100644 index 00000000000..6bcce4a52f5 --- /dev/null +++ b/test/grdimage/pbr.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Test physically based shading (-F): cast shadows, ambient occlusion and tone mapping on the +# left, a rough metal with a vertical exaggeration of 3 on the right, and the -I+f defaults below + +ps=pbr.ps + +gmt grdmath -R-15/15/-15/15 -I0.1 X Y HYPOT DUP 2 MUL PI MUL 8 DIV COS EXCH NEG 10 DIV EXP MUL = somb.nc +gmt makecpt -Cjet -T-0.5/1 > pal.cpt + +gmt grdimage somb.nc -Cpal.cpt -F225/30+o+s+t -JX8c -Baf -BWSne -P -K -Y14c > $ps +gmt grdimage somb.nc -Cpal.cpt -F225/30+m0.8+r0.6+v3 -J -Baf -BwSne -O -K -X9c >> $ps +gmt grdimage somb.nc -Cpal.cpt -I+f -J -Baf -BWSne -O -X-9c -Y-10c >> $ps + +rm -f somb.nc pal.cpt diff --git a/test/grdview/grdview-l2s.sh b/test/grdview/grdview-l2s.sh index 508362b0250..d34e153f9d9 100755 --- a/test/grdview/grdview-l2s.sh +++ b/test/grdview/grdview-l2s.sh @@ -10,6 +10,8 @@ rm -f $a $b ; touch $b cat << EOF > $a --l2stranstest -Cfile.cpt -Cfile.cpt +--l2stranstest -F315/30+f0.2+i1.8+l1.2+m0.5 -I+f +--l2stranstest -F45/20+o0.2+r0.6+s+t+v2 --l2stranstest -Gthis/file.grd -Gthat/file.grd --l2stranstest -Ilighting/file+a --l2stranstest -I+d -I6+m6+nsome.args @@ -22,6 +24,8 @@ EOF # module-specific longopts gmt $m $l2s --cpt=file.cpt --cmap=file.cpt >> $b +gmt $m $l2s --pbr=315/30+fill:0.2+ior:1.8+light:1.2+metallic:0.5 --intensity+pbr >> $b +gmt $m $l2s --physical=45/20+occlusion:0.2+roughness:0.6+shadows+tonemap+exaggeration:2 >> $b gmt $m $l2s --drapegrid=this/file.grd --drape=that/file.grd >> $b gmt $m $l2s --illumination=lighting/file+azimuth >> $b gmt $m $l2s --shading+default --intensity=6+ambient:6+args:some.args >> $b From 387925914e9cb01cefb5526259a45ccfb499a9f8 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Fri, 25 Sep 2026 23:49:47 +0100 Subject: [PATCH 3/6] add them --- test/baseline/grdimage.dvc | 6 +++--- test/baseline/grdview.dvc | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/baseline/grdimage.dvc b/test/baseline/grdimage.dvc index c412664f5d3..e277a58332c 100644 --- a/test/baseline/grdimage.dvc +++ b/test/baseline/grdimage.dvc @@ -1,6 +1,6 @@ outs: -- md5: e21fa5a85cbf106cc8d027da81d49db8.dir - nfiles: 36 +- md5: 84e7053917f84f01ca65140933cc0624.dir + nfiles: 37 path: grdimage hash: md5 - size: 19831244 + size: 20441999 diff --git a/test/baseline/grdview.dvc b/test/baseline/grdview.dvc index 91a6ae0057b..7d6cdfe9d7e 100644 --- a/test/baseline/grdview.dvc +++ b/test/baseline/grdview.dvc @@ -1,5 +1,5 @@ outs: -- md5: 29e78028799ee60a1c27fa8d2d53fb83.dir - nfiles: 12 +- md5: 61968f7f451c62d8b6d7e28f7f5574e9.dir + nfiles: 13 path: grdview hash: md5 From 623d0379c24d4c5cd4cb6fdca03237b17c029c01 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Fri, 25 Sep 2026 23:58:20 +0100 Subject: [PATCH 4/6] Simplify PBR example shading in man pages examples. --- doc/rst/source/grdimage.rst | 3 +-- doc/rst/source/grdview.rst | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/rst/source/grdimage.rst b/doc/rst/source/grdimage.rst index f6f9a11d6a2..287106afa74 100644 --- a/doc/rst/source/grdimage.rst +++ b/doc/rst/source/grdimage.rst @@ -348,8 +348,7 @@ To shade the Island of Hawaii and the surrounding sea floor with physically base from the northwest that makes Mauna Kea and Mauna Loa cast their shadows, ambient occlusion, tone mapping, a threefold vertical exaggeration and a brighter sun and fill light than the defaults:: - gmt grdcut @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Ghawaii.nc - gmt grdimage hawaii.nc -Cgeo -F315/35+o+s+t+v3+l1.4+f0.5 -JM15c -B -pdf hawaii +gmt grdimage @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Cgeo -F315/35+o+s+t+v3+l1.4+f0.5 -JM15c -B -png hawaii .. include:: cpt_notes.rst_ diff --git a/doc/rst/source/grdview.rst b/doc/rst/source/grdview.rst index 4d3c6f21778..f614a34c4bc 100644 --- a/doc/rst/source/grdview.rst +++ b/doc/rst/source/grdview.rst @@ -260,8 +260,7 @@ lighting: a low sun from the northwest that makes Mauna Kea and Mauna Loa cast t occlusion, tone mapping, a threefold vertical exaggeration of the shading and a brighter sun and fill light than the defaults:: - gmt grdcut @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Ghawaii.nc - gmt grdview hawaii.nc -Cgeo -F315/35+o+s+t+v3+l1.4+f0.5 -JM15c -JZ3c -p150/35 -B -pdf hawaii3D +gmt grdimage @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Cgeo -F315/35+o+s+t+v3+l1.4+f0.5 -JM15c -JZ3c -p150/35 -B -png hawaii .. module_note_begins From 80ff2ba6869ea748a5430e90a24218cd163c5995 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Tue, 29 Sep 2026 01:33:28 +0100 Subject: [PATCH 5/6] Expand the PBR shading to grd2kml.c Let the full -F be also set via -I+P[...] --- doc/rst/source/explain_intense.rst_ | 25 ++++ doc/rst/source/grd2kml.rst | 8 +- doc/rst/source/grdimage.rst | 10 +- doc/rst/source/grdview.rst | 11 +- src/gmt_support.c | 28 +++- src/grd2kml.c | 192 +++++++++++++++++++++++++--- src/grdimage.c | 15 ++- src/grdview.c | 13 +- src/longopt/grd2kml_inc.h | 2 +- src/longopt/grdimage_inc.h | 2 +- src/longopt/grdview_inc.h | 2 +- 11 files changed, 257 insertions(+), 51 deletions(-) diff --git a/doc/rst/source/explain_intense.rst_ b/doc/rst/source/explain_intense.rst_ index ddec4ae539f..757d8b69523 100644 --- a/doc/rst/source/explain_intense.rst_ +++ b/doc/rst/source/explain_intense.rst_ @@ -1,4 +1,7 @@ **-I**\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*] + Or + +**-I**\ **+P**\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] Apply illumination for the input grid. Several methods are available: - Give the *file* name of a co-registered grid with intensities in the ±1 range. @@ -14,3 +17,25 @@ modifiers [Default is no illumination]. **Note**: If the input grid actually is an image then *file* or constant *intens* must be provided since derivatives are not available. + + Instead of intensities, give **+P**\ [*azim*\ [/*elev*]][*modifiers*] to shade the input grid with + physically based lighting, i.e., the CPT colors are lit as a microfacet material by the sun at + azimuth *azim* (degrees clockwise from north, taken modulo 360) [315] and elevation *elev* (degrees + above the horizon, 0-90) [45], and by a headlight fill, seen from straight above. Give just *azim* + to keep the default *elev* (but *elev* cannot be given without *azim*). Plain **+P**, or **+f**, + selects all the default settings, which are the same as **+P**\ 315/45+o+t. See :doc:`grdimage` + **-F** for more details. Optionally, append modifiers (the allowed range of each value is given in + parentheses, the default in brackets): + + - **+f** Set the *fill* (headlight) intensity (>= 0) [0.35]. + - **+i** Set the index of refraction *ior* of the surface (>= 1) [1.5]. Values from about 1.3 + (water) to 2.4 (diamond) cover most materials. + - **+l** Set the *light* (sun) intensity (>= 0) [1]. + - **+m** Set the *metallic* value, from 0 (dielectric) to 1 (metal) (0-1) [0]. + - **+o** Add ambient occlusion, which darkens creases and valley floors [no occlusion]. Optionally + append the sampling *radius* as a fraction of the diagonal of the relief as drawn (> 0) [0.1]. + - **+r** Set the *roughness*, from 0 (mirror) to 1 (matte) (0-1) [0.3]. Values below 0.05 are + treated as 0.05. + - **+s** Cast shadows along the sun's azimuth and elevation [no shadows]. + - **+t** Apply the Khronos PBR Neutral tone mapping, which compresses the highlights [no tone mapping]. + - **+v** Set the vertical exaggeration *ve* of the relief (> 0) [1]. diff --git a/doc/rst/source/grd2kml.rst b/doc/rst/source/grd2kml.rst index 6e4c2c7ce30..cb711db3662 100644 --- a/doc/rst/source/grd2kml.rst +++ b/doc/rst/source/grd2kml.rst @@ -19,7 +19,7 @@ Synopsis [ |-E|\ *URL* ] [ |-F|\ *filtercode* ] [ |-H|\ *scale* ] -[ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*] ] +[ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*]\|\ **+f**\|\ **+P**\ [*azim*\ [/*elev*]][*modifiers*] ] [ |-L|\ *tilesize* ] [ |-S|\ [*extra*] ] [ |-T|\ *title* ] @@ -110,6 +110,12 @@ Optional Arguments .. include:: explain_intense.rst_ + With **+P** or **+f**, each tile is shaded together with a border of its neighbouring nodes, so slopes, + occlusion and shadows continue across the tile edges. The border is the occlusion radius on all sides + plus the shadow length (at most 256 nodes) on the sun's side. The relief is drawn at the vertical scale + of the full grid on every level, while the occlusion *radius* is a fraction of the tile's horizontal + diagonal, so the occlusion looks the same at every zoom level. + .. _-L: **-L**\ *tilesize* diff --git a/doc/rst/source/grdimage.rst b/doc/rst/source/grdimage.rst index 287106afa74..66d4150ec5b 100644 --- a/doc/rst/source/grdimage.rst +++ b/doc/rst/source/grdimage.rst @@ -18,7 +18,7 @@ Synopsis [ |-C|\ [*section*/]\ *master*\|\ *cpt*\|\ *color*\ :math:`_1`,\ *color*\ :math:`_2`\ [,\ *color*\ :math:`_3`\ ,...]\ [**+h**\ [*hinge*]][**+i**\ *dz*][**+u**\|\ **U**\ *unit*][**+s**\ *fname*] ] [ |-D|\ [**r**] ] [ |-E|\ [**i**\|\ *dpi*] ] -[ |-F|\ *azim*/*elev*\ [**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] ] +[ |-F|\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] ] [ |-G|\ *color*\ [**+b**\|\ **f**] ] [ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*] ] [ |-J|\ *parameters* ] @@ -129,11 +129,13 @@ Optional Arguments .. _-F: -**-F**\ *azim*/*elev*\ [**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] +**-F**\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] Shade the grid with physically based lighting instead of illuminating it with |-I| (the two cannot be combined). The shaded image is made on the grid's own nodes and then projected and - plotted as any image, so |-A|, |-E| and |-J| work as usual. Use **-I+f** as a short form that - selects the default settings, which are the same as **-F**\ 315/45+o+t. + plotted as any image, so |-A|, |-E| and |-J| work as usual. The sun position is optional + [315/45]; give just *azim* to keep the default *elev* (but *elev* cannot be given without *azim*). + Plain |-F| (or **-I+f**) selects all the default settings, which are the same as **-F**\ 315/45+o+t. + **-I+P**\ [*azim*\ [/*elev*]][*modifiers*] is an alternative form that works exactly as |-F|. .. include:: explain_pbr.rst_ diff --git a/doc/rst/source/grdview.rst b/doc/rst/source/grdview.rst index f614a34c4bc..b21f8150d30 100644 --- a/doc/rst/source/grdview.rst +++ b/doc/rst/source/grdview.rst @@ -15,7 +15,7 @@ Synopsis **gmt grdview** *reliefgrid* |-J|\ *parameters* [ |SYN_OPT-B| ] [ |-C|\ [*section*/]\ *master*\|\ *cpt*\|\ *color*\ :math:`_1`,\ *color*\ :math:`_2`\ [,\ *color*\ :math:`_3`\ ,...]\ [**+h**\ [*hinge*]][**+i**\ *dz*][**+u**\|\ **U**\ *unit*][**+s**\ *fname*] ] -[ |-F|\ *azim*/*elev*\ [**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] ] +[ |-F|\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] ] [ |-G|\ *drapegrid*\|\ *drapeimage* ] [ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*] ] [ |-Jz|\ \|\ **Z**\ *parameters* ] @@ -73,12 +73,15 @@ Optional Arguments .. _-F: -**-F**\ *azim*/*elev*\ [**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] +**-F**\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] Shade the *reliefgrid* with physically based lighting instead of illuminating it with |-I| (the two cannot be combined, nor can |-G| or |-T| be used). The shaded colors are computed on the grid's own nodes and draped over the relief as an image, so |-F| implies **-Qi** unless **-Qc**\|\ **i** - is given. Use **-I+f** as a short form that selects the default settings, which are the same as - **-F**\ 315/45+o+t. The shading itself is identical to that of :doc:`grdimage` **-F**. + is given. The sun position is optional [315/45]; give just *azim* to keep the default *elev* (but + *elev* cannot be given without *azim*). Plain |-F| (or **-I+f**) selects all the default settings, + which are the same as **-F**\ 315/45+o+t. **-I+P**\ [*azim*\ [/*elev*]][*modifiers*] is an + alternative form that works exactly as |-F|. The shading itself is identical to that of + :doc:`grdimage` **-F**. .. include:: explain_pbr.rst_ diff --git a/src/gmt_support.c b/src/gmt_support.c index a43f6f1df5a..9325ae02c58 100644 --- a/src/gmt_support.c +++ b/src/gmt_support.c @@ -10147,10 +10147,17 @@ void gmt_pbr_defaults(struct GMT_PBR *P) { } unsigned int gmt_pbr_parse(struct GMT_CTRL *GMT, char option, char *arg, struct GMT_PBR *P) { - /* Parse /[+f][+i][+l][+m][+o[]][+r][+s][+t][+v] */ + /* Parse [[/]][+f][+i][+l][+m][+o[]][+r][+s][+t][+v]. + * An empty arg selects all the defaults, i.e., /+o+t. A missing or keeps its default, + * but cannot be given without . */ + int n; unsigned int n_errors = 0, pos = 0; char p[GMT_BUFSIZ] = {""}, *c = NULL; + if (!arg[0]) { /* Nothing given: all the defaults */ + P->occlusion = P->tone = true; + return (0); + } if ((c = gmt_first_modifier(GMT, arg, "filmorstv"))) { /* Process any modifiers */ while (gmt_getmodopt(GMT, option, c, "filmorstv", &pos, p, &n_errors) && n_errors == 0) { switch (p[0]) { @@ -10168,10 +10175,17 @@ unsigned int gmt_pbr_parse(struct GMT_CTRL *GMT, char option, char *arg, struct } c[0] = '\0'; /* Chop off all modifiers so azimuth/elevation can be determined */ } - if (sscanf(arg, "%lf/%lf", &P->azimuth, &P->elevation) != 2) { - GMT_Report(GMT->parent, GMT_MSG_ERROR, "Option -%c: Must give azimuth and elevation\n", option); + if (arg[0] == '/') { /* Gave / but no */ + GMT_Report(GMT->parent, GMT_MSG_ERROR, "Option -%c: Cannot give elevation without azimuth\n", option); n_errors++; } + else if (arg[0]) { /* Gave or / */ + n = sscanf(arg, "%lf/%lf", &P->azimuth, &P->elevation); + if (n < 1 || (n == 1 && strchr(arg, '/'))) { + GMT_Report(GMT->parent, GMT_MSG_ERROR, "Option -%c: Must give or /\n", option); + n_errors++; + } + } if (c) c[0] = '+'; /* Restore modifiers */ return (n_errors); } @@ -10193,10 +10207,12 @@ unsigned int gmt_pbr_check(struct GMT_CTRL *GMT, char option, struct GMT_PBR *P) void gmt_pbr_syntax(struct GMTAPI_CTRL *API, char option) { /* The usage lines shared by every option that takes the PBR settings */ - GMT_Usage(API, 1, "\n-%c/[+f][+i][+l][+m][+o[]][+r][+s][+t][+v]", option); + GMT_Usage(API, 1, "\n-%c[[/]][+f][+i][+l][+m][+o[]][+r][+s][+t][+v]", option); GMT_Usage(API, -2, "Shade the grid with physically based (PBR) lighting: each node's CPT color is lit as a microfacet " - "material by the sun at (0-360) and (0-90) and by a headlight fill, seen from above. The relief is lit " - "as a 3-D view draws it: x and y at one scale and the z range drawn as 0.1 of the larger horizontal side. Modifiers:"); + "material by the sun at (0-360) [315] and (0-90) [45] and by a headlight fill, seen from above. The relief is lit " + "as a 3-D view draws it: x and y at one scale and the z range drawn as 0.1 of the larger horizontal side. " + "Give just to keep the default . Plain -%c selects all the defaults, i.e., -%c315/45+o+t. " + "Same as -I+P[[/]][]. Modifiers:", option, option); GMT_Usage(API, 3, "+f Set the (headlight) intensity (>= 0) [0.35]."); GMT_Usage(API, 3, "+i Set the index of refraction of the surface (>= 1) [1.5]."); GMT_Usage(API, 3, "+l Set the (sun) intensity (>= 0) [1]."); diff --git a/src/grd2kml.c b/src/grd2kml.c index 3e4bc001f7e..9549421f435 100644 --- a/src/grd2kml.c +++ b/src/grd2kml.c @@ -75,11 +75,13 @@ struct GRD2KML_CTRL { bool active; char *prefix; } N; - struct GRD2KML_I { /* -I[||] */ + struct GRD2KML_I { /* -I[||] or -I+f|+P[[/]][] */ bool active; bool constant; bool derive; + bool pbr; /* Physically based shading (-I+f or -I+P) instead of intensities */ double value; + struct GMT_PBR P; /* Physically based shading settings (gmt_support.c) */ char *azimuth; /* Default azimuth(s) for shading */ char *file; char *method; /* Default scaling method */ @@ -122,6 +124,7 @@ static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new /* Initialize values whose defaults are not 0/false/NULL */ C->F.filter = 'g'; C->I.method = strdup ("t1"); /* Default normalization for shading when -I is used */ + gmt_pbr_defaults(&C->I.P); /* Physically based shading defaults (-I+f or -I+P) */ C->L.size = 512; /* Default tile size unless global grids [360] */ C->W.scale = M_SQRT2; C->W.cutoff = 0.1; @@ -145,7 +148,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) { const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE); if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR); GMT_Usage (API, 0, "usage: %s %s -N [-Aa|g|s[]] [-C%s] [-E] [-F] " - "[-H] [-I[||]] [-L] [-S[]] [-T] [%s] " + "[-H<scale>] [-I[<intensgrid>|<value>|<modifiers>|+f|+P[<azim>[/<elev>]][<pbr_modifiers>]]] [-L<size>] [-S[<extra>]] [-T<title>] [%s] " "[-W<contfile>|<pen>[+s<scl>/<limit>]] [%s] [%s] [%s]\n", name, GMT_INGRID, CPT_OPT_ARGS, GMT_V_OPT, GMT_f_OPT, GMT_n_OPT, GMT_PAR_OPT); if (level == GMT_SYNOPSIS) return (GMT_MODULE_SYNOPSIS); @@ -182,6 +185,20 @@ static int usage (struct GMTAPI_CTRL *API, int level) { "To derive intensities from <grid> instead, use -I+d to accept the default values (see grdgradient for details) or be specific:"); GMT_Usage (API, 3, "+a Append <azimuth> of illumination [-45]"); GMT_Usage (API, 3, "+n Append <method> pf intensity calculation [t1]"); + GMT_Usage(API, -2, "Alternatively, use -I+P[<azim>[/<elev>]][<pbr_modifiers>] to shade <grid> physically based " + "instead (see grdimage -F), or -I+f to do so with its default settings, i.e., -I+P315/45+o+t. " + "Each level is shaded as a whole and with the relief scale of <grid>, so tiles match across their edges. " + "The <pbr_modifiers> are:"); + GMT_Usage(API, 3, "+f Set the <fill> (headlight) intensity (>= 0) [0.35]."); + GMT_Usage(API, 3, "+i Set the index of refraction <ior> of the surface (>= 1) [1.5]."); + GMT_Usage(API, 3, "+l Set the <light> (sun) intensity (>= 0) [1]."); + GMT_Usage(API, 3, "+m Set the <metallic> value (0-1) [0]."); + GMT_Usage(API, 3, "+o Add ambient occlusion; optionally append the sampling <radius> as a fraction of the " + "diagonal of the relief as drawn (> 0) [0.1]."); + GMT_Usage(API, 3, "+r Set the <roughness> (0-1) [0.3]."); + GMT_Usage(API, 3, "+s Cast shadows along the sun's azimuth and elevation."); + GMT_Usage(API, 3, "+t Apply the Khronos PBR Neutral tone mapping."); + GMT_Usage(API, 3, "+v Set the vertical exaggeration <ve> of the relief (> 0) [1]."); GMT_Usage (API, 1, "\n-L<size>"); GMT_Usage (API, -2, "Set tile size as a power of 2 [512; for global grids, we instead select 360].n"); GMT_Usage (API, 1, "\n-S[<extra>]"); @@ -266,8 +283,12 @@ static int parse (struct GMT_CTRL *GMT, struct GRD2KML_CTRL *Ctrl, struct GMT_OP n_errors += gmt_get_required_int (GMT, opt->arg, opt->option, 0, &Ctrl->H.factor); break; case 'I': /* Here, intensity must be a grid file since we need to filter it */ - n_errors += gmt_M_repeated_module_option (API, Ctrl->I.active); - if (!strcmp (opt->arg, "+d")) /* Gave +d only, so derive intensities from input grid using default settings */ + n_errors += gmt_M_repeated_module_option(API, Ctrl->I.active); + if (!strcmp(opt->arg, "+f") || !strncmp(opt->arg, "+P", 2)) { /* -I+f or -I+P[<azim>[/<elev>]][<modifiers>]: physically based shading */ + Ctrl->I.pbr = true; + n_errors += gmt_pbr_parse(GMT, 'I', &opt->arg[2], &Ctrl->I.P); /* For +f, &opt->arg[2] is "" and selects the defaults */ + } + else if (!strcmp(opt->arg, "+d")) /* Gave +d only, so derive intensities from input grid using default settings */ Ctrl->I.derive = true; else if ((c = gmt_first_modifier (GMT, opt->arg, "an"))) { /* Want to control how grdgradient is run */ unsigned int pos = 0; @@ -341,8 +362,12 @@ static int parse (struct GMT_CTRL *GMT, struct GRD2KML_CTRL *Ctrl, struct GMT_OP n_errors += gmt_M_check_condition (GMT, Ctrl->N.prefix == NULL, "Option -N: Must specify a prefix for naming usage.\n"); n_errors += gmt_M_check_condition (GMT, Ctrl->H.active && Ctrl->H.factor <= 1, "Option -H: Must specify an integer factor > 1.\n"); n_errors += gmt_M_check_condition (GMT, Ctrl->E.active && Ctrl->E.url == NULL, "Option -E: Must specify an URL.\n"); - n_errors += gmt_M_check_condition (GMT, Ctrl->I.active && !Ctrl->I.constant && !Ctrl->I.file && !Ctrl->I.derive, + n_errors += gmt_M_check_condition(GMT, Ctrl->I.active && !Ctrl->I.pbr && !Ctrl->I.constant && !Ctrl->I.file && !Ctrl->I.derive, "Option -I: Must specify intensity file, value, or modifiers\n"); + if (Ctrl->I.pbr) { /* No intensity grid: the shaded colors replace it */ + n_errors += gmt_pbr_check(GMT, 'I', &Ctrl->I.P); + Ctrl->I.active = false; + } return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR); } @@ -474,6 +499,87 @@ int grd2kml_coarsen_grid (struct GMT_CTRL *GMT, unsigned int level, char filter, return (GMT_NOERROR); } +GMT_LOCAL double grd2kml_pbr_zfac(struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h, double *x_fac, double *diag2) { + /* The z scale gmt_pbr_image gives the relief of a grid with header h, plus the x shrink factor it uses + * and the squared horizontal diagonal of the relief as drawn */ + double W, H, zspan; + *x_fac = (gmt_M_is_geographic(GMT, GMT_IN)) ? MAX(1.0e-6, cosd(0.5 * (h->wesn[YLO] + h->wesn[YHI]))) : 1.0; + W = fabs(h->wesn[XHI] - h->wesn[XLO]) * (*x_fac); + H = fabs(h->wesn[YHI] - h->wesn[YLO]); + zspan = h->z_max - h->z_min; + *diag2 = W * W + H * H; + return ((zspan > 0.0 && MAX(W, H) > 0.0) ? 0.1 * MAX(W, H) / zspan : 1.0); +} + +GMT_LOCAL struct GMT_IMAGE *grd2kml_pbr_tile(struct GMT_CTRL *GMT, struct GMT_PBR *P_in, struct GMT_GRID_HEADER *h_ref, struct GMT_GRID_HEADER *h_level, + char *Zgrid, struct GMT_GRID *T, bool alpha, struct GMT_PALETTE *CPT) { + /* Physically based shading of tile T. T plus a border of neighbouring nodes is read from the level grid Zgrid and + * shaded, so slopes, occlusion and shadows see across the tile edges, and then only the tile's own pixels are kept. + * The border is 2 nodes, plus the occlusion radius on all sides and the shadow length (at most 256 nodes) on the + * sun's side. The relief is drawn at the z scale of the full grid (header h_ref) and the occlusion radius is a + * fraction of the tile's horizontal diagonal, so all tiles of all levels match. If alpha then a separate alpha + * array makes the NaN nodes of T transparent */ + int pad[4], lrow, lcol; + unsigned int row, col, band, n_bands = (alpha) ? 4 : 3; /* 4 asks GMT_Create_Data for 3 bands plus the alpha array */ + uint64_t dim[3] = {0, 0, 0}, node, lnode; + double inc = T->header->inc[GMT_X], x_fac, x_ref, d_tile, d_ref, d_pad, z_ref, z_pad, Z, nodes, sx, sy, wesn[4]; + struct GMT_PBR P = *P_in; + struct GMT_GRID *Tp = NULL; + struct GMT_IMAGE *Ip = NULL, *I = NULL; + + z_ref = grd2kml_pbr_zfac(GMT, h_ref, &x_ref, &d_ref); /* z scale of the full grid */ + (void)grd2kml_pbr_zfac(GMT, T->header, &x_fac, &d_tile); /* x shrink and horizontal diagonal of the tile */ + pad[XLO] = pad[XHI] = pad[YLO] = pad[YHI] = 2; /* Central differences need one node, plus one for safety */ + if (P.occlusion) { /* Add the occlusion radius */ + nodes = P.radius * sqrt(d_tile) / inc; + pad[XLO] += (int)ceil(nodes / x_fac); pad[XHI] = pad[XLO]; + pad[YLO] += (int)ceil(nodes); pad[YHI] = pad[YLO]; + } + if (P.shadow && P.elevation < 90.0) { /* Add the shadow length of the full relief on the sun's side, at most 256 nodes */ + nodes = (h_ref->z_max - h_ref->z_min) * z_ref * P.ve / tand(MAX(P.elevation, 1.0)) / inc; + sx = sind(P.azimuth); sy = cosd(P.azimuth); + if (fabs(sx) > 1.0e-6) pad[(sx > 0.0) ? XHI : XLO] += (int)ceil(MIN(256.0, nodes * fabs(sx) / x_fac)); + if (fabs(sy) > 1.0e-6) pad[(sy > 0.0) ? YHI : YLO] += (int)ceil(MIN(256.0, nodes * fabs(sy))); + } + wesn[XLO] = MAX(h_level->wesn[XLO], T->header->wesn[XLO] - pad[XLO] * inc); + wesn[XHI] = MIN(h_level->wesn[XHI], T->header->wesn[XHI] + pad[XHI] * inc); + wesn[YLO] = MAX(h_level->wesn[YLO], T->header->wesn[YLO] - pad[YLO] * inc); + wesn[YHI] = MIN(h_level->wesn[YHI], T->header->wesn[YHI] + pad[YHI] * inc); + if ((Tp = GMT_Read_Data(GMT->parent, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA, wesn, Zgrid, NULL)) == NULL) + return (NULL); + + /* gmt_pbr_image scales the relief from the grid it gets, so set ve and radius to undo that for the padded tile */ + z_pad = grd2kml_pbr_zfac(GMT, Tp->header, &x_fac, &d_pad); + P.ve = P_in->ve * z_ref / z_pad; + Z = (Tp->header->z_max - Tp->header->z_min) * z_ref * P_in->ve; + P.radius = P_in->radius * sqrt(d_tile) / sqrt(d_pad + Z * Z); + Ip = gmt_pbr_image(GMT, &P, Tp, CPT); + GMT_Destroy_Data(GMT->parent, &Tp); + if (Ip == NULL) return (NULL); + + /* Keep the pixels of the tile itself */ + dim[GMT_Z] = n_bands; + if ((I = GMT_Create_Data(GMT->parent, GMT_IS_IMAGE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA, dim, T->header->wesn, T->header->inc, T->header->registration, 2, NULL)) == NULL) { + GMT_Destroy_Data(GMT->parent, &Ip); + return (NULL); + } + strncpy(I->header->mem_layout, Ip->header->mem_layout, 4); + for (row = 0; row < T->header->n_rows; row++) { + lrow = (int)gmt_M_grd_y_to_row(GMT, gmt_M_grd_row_to_y(GMT, row, T->header), Ip->header); + for (col = 0; col < T->header->n_columns; col++) { + lcol = (int)gmt_M_grd_x_to_col(GMT, gmt_M_grd_col_to_x(GMT, col, T->header), Ip->header); + node = gmt_M_ijpgi(I->header, row, col); + if (lrow >= 0 && lcol >= 0 && lrow < (int)Ip->header->n_rows && lcol < (int)Ip->header->n_columns) { + lnode = gmt_M_ijpgi(Ip->header, lrow, lcol); + for (band = 0; band < 3; band++) I->data[node + band] = Ip->data[lnode + band]; + } + if (alpha) I->alpha[gmt_M_ijp(I->header, row, col)] = (gmt_M_is_fnan(T->data[gmt_M_ijp(T->header, row, col)])) ? 0 : 255; + } + } + GMT_Destroy_Data(GMT->parent, &Ip); + return (I); +} + #define bailout(code) {gmt_M_free_options (mode); return (code);} #define Return(code) {Free_Ctrl (GMT, Ctrl); gmt_end_module (GMT, GMT_cpy); bailout (code);} @@ -505,7 +611,9 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { struct GMT_DATASET *C = NULL; struct GMT_QUADTREE **Q = NULL; struct GRD2KML_CTRL *Ctrl = NULL; - struct GMT_GRID *G = NULL, *T = NULL, *I = NULL; + struct GMT_GRID *G = NULL, *T = NULL, *I = NULL, *Zh = NULL; + struct GMT_PALETTE *PBR_cpt = NULL; + struct GMT_IMAGE *TI = NULL; struct GMT_CTRL *GMT = NULL, *GMT_cpy = NULL; struct GMT_OPTION *options = NULL; struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API); /* Cast from void to GMTAPI_CTRL pointer */ @@ -751,7 +859,19 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { /* Set up the constant parts of the grdimage command */ sprintf (grdimage, "-JX%3.2lfi -X0 -Y0%s -W -Ve --PS_MEDIA=%3.2lfix%3.2lfi", dim, K, dim, dim); - if (Ctrl->C.active) { strcat (grdimage, " -C"); strcat (grdimage, Ctrl->C.file); } + if (Ctrl->I.pbr) { /* The tiles are shaded here, so we need the CPT here instead of in grdimage */ + if ((PBR_cpt = GMT_Read_Data(API, GMT_IS_PALETTE, GMT_IS_FILE, GMT_IS_NONE, GMT_READ_NORMAL, NULL, Ctrl->C.file, NULL)) == NULL) { + error = API->error; goto end_times; + } + /* We write the tiles ourselves, so turn off the automatic creation of aux files by GDAL as grdimage -A does */ +#ifdef WIN32 + if (_putenv("GDAL_PAM_ENABLED=NO")) +#else + if (setenv("GDAL_PAM_ENABLED", "NO", 0)) +#endif + GMT_Report(API, GMT_MSG_WARNING, "Unable to set GDAL_PAM_ENABLED to prevent writing of auxiliary files\n"); + } + else if (Ctrl->C.active) { strcat (grdimage, " -C"); strcat (grdimage, Ctrl->C.file); } /* Set up the constant parts of the grdcontour command */ if (Ctrl->W.active) /* Overlay contours */ sprintf (grdcontour, "-JX%3.2lfi -O -C%s -Ve", dim, contour_file); @@ -792,6 +912,9 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { step = Ctrl->L.size * G->header->inc[GMT_X]; if (Ctrl->I.active) strcpy (Igrid, IntensGrid); } + if (Ctrl->I.pbr && (Zh = GMT_Read_Data(API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_ONLY, NULL, Zgrid, NULL)) == NULL) { /* Level extent, which limits the tile borders */ + error = API->error; goto end_times; + } /* Loop over all rows at this level */ row = col = n_skip = 0; wesn[YLO] = ext_wesn[YLO]; @@ -841,7 +964,18 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { use_tile = (n_NaN < n_NM); im_type = (n_NaN) ? 1 : 0; if (use_tile) { /* Found data inside this tile, make plot and rasterize (if PostScript) */ - char z_data[GMT_VF_LEN] = {""}, i_data[GMT_VF_LEN] = {""}, psfile[PATH_MAX] = {""}; + char z_data[GMT_VF_LEN] = {""}, i_data[GMT_VF_LEN] = {""}, p_data[GMT_VF_LEN] = {""}, psfile[PATH_MAX] = {""}, *img_in = z_data; + if (Ctrl->I.pbr) { /* Shade the tile physically based; it is written as is, or plotted by grdimage if -W */ + if ((TI = grd2kml_pbr_tile(GMT, &Ctrl->I.P, G->header, Zh->header, Zgrid, T, im_type, PBR_cpt)) == NULL) { + GMT_Report(API, GMT_MSG_ERROR, "Unable to shade grid tile!\n"); + error = GMT_RUNTIME_ERROR; goto end_times; + } + if (GMT_Open_VirtualFile(API, GMT_IS_IMAGE, GMT_IS_SURFACE, GMT_IN|GMT_IS_REFERENCE, TI, p_data) == GMT_NOTSET) { + GMT_Report(API, GMT_MSG_ERROR, "Unable to open shaded tile as virtual file!\n"); + error = GMT_RUNTIME_ERROR; goto end_times; + } + img_in = p_data; + } if (Ctrl->I.active) { /* Also get the intensity tile */ if ((I = GMT_Read_Data (API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA, wesn, Igrid, NULL)) == NULL) { GMT_Report (API, GMT_MSG_ERROR, "Unable to read in intensity tile!\n"); @@ -866,12 +1000,21 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { sprintf (imagefile, "%s/L%2.2dR%3.3dC%3.3d.%s", Ctrl->N.prefix, level, row, col, ext[im_type]); else sprintf (imagefile, "%s/R%3.3dC%3.3d.%s", level_dir, row, col, ext[im_type]); - if (Ctrl->I.active) /* Must pass two grids */ - sprintf (cmd, "%s %s -I%s -R%s/%s/%s/%s -A%s", grdimage, z_data, i_data, W, E, S, N, imagefile); - else - sprintf (cmd, "%s %s -R%s/%s/%s/%s -A%s", grdimage, z_data, W, E, S, N, imagefile); - if (im_type) strcat (cmd, transp); - error = GMT_Call_Module (API, "grdimage", GMT_MODULE_CMD, cmd); + if (Ctrl->I.pbr) { /* The shaded tile already holds the tile's pixels, and grdimage -A would drop its alpha array */ + if (GMT_Write_Data(API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA, NULL, imagefile, TI) != GMT_NOERROR) { + GMT_Report(API, GMT_MSG_ERROR, "Unable to write shaded tile %s\n", imagefile); + error = GMT_RUNTIME_ERROR; goto end_times; + } + error = GMT_NOERROR; + } + else { + if (Ctrl->I.active) /* Must pass two grids */ + sprintf(cmd, "%s %s -I%s -R%s/%s/%s/%s -A%s", grdimage, z_data, i_data, W, E, S, N, imagefile); + else + sprintf(cmd, "%s %s -R%s/%s/%s/%s -A%s", grdimage, z_data, W, E, S, N, imagefile); + if (im_type) strcat(cmd, transp); + error = GMT_Call_Module(API, "grdimage", GMT_MODULE_CMD, cmd); + } if (!(error == GMT_NOERROR || error == GMT_IMAGE_NO_DATA)) { GMT_Report (API, GMT_MSG_ERROR, "Unable to create a direct PNG from grid\n"); error = GMT_RUNTIME_ERROR; goto end_times; @@ -883,12 +1026,12 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { if (Ctrl->I.active) /* Must pass two grids */ sprintf (cmd, "%s %s -I%s -R%s/%s/%s/%s ->%s", grdimage, z_data, i_data, W, E, S, N, psfile); else - sprintf (cmd, "%s %s -R%s/%s/%s/%s ->%s", grdimage, z_data, W, E, S, N, psfile); - if (im_type) strcat (cmd, transp); + sprintf(cmd, "%s %s -R%s/%s/%s/%s ->%s", grdimage, img_in, W, E, S, N, psfile); + if (im_type && !Ctrl->I.pbr) strcat (cmd, transp); /* A shaded tile has its NaNs in an alpha band instead */ error = GMT_Call_Module (API, "grdimage", GMT_MODULE_CMD, cmd); if (error == GMT_NOERROR && Ctrl->W.active) { /* Overlay contours */ - sprintf (cmd, "%s %s -R%s/%s/%s/%s %s ->>%s", grdcontour, z_data, W, E, S, N, scalepen_arg, psfile); - GMT_Init_VirtualFile (API, 0, z_data); /* Read the same grid again */ + sprintf(cmd, "%s %s -R%s/%s/%s/%s %s ->>%s", grdcontour, z_data, W, E, S, N, scalepen_arg, psfile); + if (!Ctrl->I.pbr) GMT_Init_VirtualFile (API, 0, z_data); /* Read the same grid again (grdimage got the shaded image instead) */ GMT_Init_VirtualFile (API, 0, contour_file); /* Read the same contours again */ if ((error = GMT_Call_Module (API, "grdcontour", GMT_MODULE_CMD, cmd))) { GMT_Report (API, GMT_MSG_ERROR, "Unable to overlay contours!\n"); @@ -898,6 +1041,13 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { } /* Relinquish the tile memory */ GMT_Close_VirtualFile (API, z_data); + if (Ctrl->I.pbr) { /* Same for the shaded tile */ + GMT_Close_VirtualFile(API, p_data); + if (GMT_Destroy_Data(API, &TI) != GMT_NOERROR) { + GMT_Report(API, GMT_MSG_ERROR, "Unable to free memory of shaded tile!\n"); + error = GMT_RUNTIME_ERROR; goto end_times; + } + } if (GMT_Destroy_Data (API, &T) != GMT_NOERROR) { GMT_Report (API, GMT_MSG_ERROR, "Unable to free memory of grid tile!\n"); error = GMT_RUNTIME_ERROR; goto end_times; @@ -959,6 +1109,7 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { else sprintf (box, "%g x %g m", 60*step, 60*step); GMT_Report (GMT->parent, GMT_MSG_NOTICE, "Level %2.2d: Tile size: %18s Tiles: %3d by %3d = %5d %5d mapped %3d empty%s\n", level, box, row, col, row*col, row*col - n_skip, n_skip, filt_report); + if (Ctrl->I.pbr) GMT_Destroy_Data(API, &Zh); if (level < max_level) { /* Delete the temporary filtered grid(s) */ gmt_remove_file (GMT, Zgrid); if (Ctrl->I.active) gmt_remove_file (GMT, Igrid); @@ -1113,8 +1264,9 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { GMT_Report (API, GMT_MSG_NOTICE, "Done: %d tiles (%d JPG and %d PNG) written to directory %s\n", n_tiles, n_img[0], n_img[1], Ctrl->N.prefix); end_times: - gmt_M_free (GMT, Q); - if (Ctrl->W.active) GMT_Destroy_Data (API, &C); + gmt_M_free(GMT, Q); + if (Ctrl->W.active) GMT_Destroy_Data(API, &C); + if (PBR_cpt) GMT_Destroy_Data(API, &PBR_cpt); Return (error); } diff --git a/src/grdimage.c b/src/grdimage.c index 7f824d730c1..ae1e240b66e 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -105,7 +105,7 @@ struct GRDIMAGE_CTRL { double rgb[4]; /* Pixel value for transparency in images */ double value; /* If +z is used this z-value will give us the r/g/b via CPT */ } Q; - struct GRDIMAGE_F { /* -F<azim>/<elev>[+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ + struct GRDIMAGE_F { /* -F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ bool active; struct GMT_PBR P; /* Physically based shading settings (gmt_support.c) */ } F; @@ -183,7 +183,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) { const char *extra[2] = {A, " [-A]"}; if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR); GMT_Usage (API, 0, "usage: %s %s %s%s [%s] [-C%s] [-D[r]] [-Ei|<dpi>] " - "[-F<azim>/<elev>[+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>]] " + "[-F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>]] " "[-G<rgb>[+b|f]] [-I[<intensgrid>|<value>|<modifiers>]] %s[-M] [-N] %s%s[-Q[<color>][+i][+z<value>]] " "[%s] [-T[+o[<pen>]][+s]] [%s] [%s] [%s] [%s] %s[%s] [%s] [%s] [%s]%s[%s]\n", name, GMT_INGRID, GMT_J_OPT, extra[API->external], GMT_B_OPT, CPT_OPT_ARGS, API->K_OPT, API->O_OPT, API->P_OPT, GMT_Rgeo_OPT, GMT_U_OPT, @@ -234,7 +234,8 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, 3, "+m Set <ambient> light to add [0]."); GMT_Usage (API, -2, "Alternatively, use -I+d to accept the default values (see grdgradient for more details). " "To derive intensities from another grid than <grid>, give the alternative data grid with suitable modifiers. " - "Use -I+f to shade physically based (see -F) with its default settings, i.e., -F315/45+o+t."); + "Use -I+P[<azim>[/<elev>]][<modifiers>] to shade physically based instead, exactly as -F does, " + "or -I+f to do so with its default settings, i.e., -F315/45+o+t."); GMT_Option (API, "J-"); GMT_Option (API, "K"); GMT_Usage (API, 1, "\n-M Force a monochrome (gray-scale) image."); @@ -367,7 +368,7 @@ static int parse(struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_OP else Ctrl->E.dpi = atoi (opt->arg); break; - case 'F': /* Physically based shading -F<azim>/<elev>[+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ + case 'F': /* Physically based shading -F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); n_errors += gmt_pbr_parse(GMT, 'F', opt->arg, &Ctrl->F.P); break; @@ -397,9 +398,9 @@ static int parse(struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_OP break; case 'I': /* Use intensity from grid or constant or auto-compute it */ n_errors += gmt_M_repeated_module_option (API, Ctrl->I.active); - if (!strcmp(opt->arg, "+f")) { /* -I+f: physically based shading (-F) with its default settings */ + if (!strcmp(opt->arg, "+f") || !strncmp(opt->arg, "+P", 2)) { /* -I+f or -I+P[<azim>[/<elev>]][<modifiers>]: same as -F */ n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); - Ctrl->F.P.occlusion = Ctrl->F.P.tone = true; + n_errors += gmt_pbr_parse(GMT, 'I', &opt->arg[2], &Ctrl->F.P); /* For +f, &opt->arg[2] is "" and selects the defaults */ Ctrl->I.active = false; /* No intensities: -F replaces -I */ break; } @@ -567,7 +568,7 @@ static int parse(struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_OP return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR); } -GMT_LOCAL unsigned int grdimage_clean_global_headers (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h) { +GMT_LOCAL unsigned int grdimage_clean_global_headers(struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h) { /* Problem is that many global grids are reported with misleading -R or imprecise * -R or non-global -R yet nx * xinc = 360, etc. We fix these here for GMT use. */ unsigned int flag = 0; diff --git a/src/grdview.c b/src/grdview.c index 569b8135089..5cd3fb140b8 100644 --- a/src/grdview.c +++ b/src/grdview.c @@ -74,7 +74,7 @@ struct GRDVIEW_CTRL { char *file; char *savecpt; /* For when we want to save the automatically generated CPT */ } C; - struct GRDVIEW_F { /* -F<azim>/<elev>[+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ + struct GRDVIEW_F { /* -F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ bool active; struct GMT_PBR P; /* Physically based shading settings (gmt_support.c) */ } F; @@ -509,7 +509,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) { const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE); if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR); GMT_Usage (API, 0, "usage: %s <topogrid> %s [%s] [-C%s] " - "[-F<azim>/<elev>[+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>]] [-G<drapegrid>|<drapeimage>] " + "[-F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>]] [-G<drapegrid>|<drapeimage>] " "[-I[<intensgrid>|<value>|<modifiers>]] [%s] %s[-N[<level>][+g<fill>]] %s%s[-Qc|g[m|a]|i|m[x|y]|s[<color>][+m]] [%s] [-S<smooth>] " "[-T[+o[<pen>]][+s]] [%s] [%s] [-W<type><pen>] [%s] [%s] %s[%s] [%s] [%s] [%s] [%s]\n", name, GMT_J_OPT, GMT_B_OPT, CPT_OPT_ARGS, GMT_Jz_OPT, API->K_OPT, API->O_OPT, API->P_OPT, GMT_Rgeoz_OPT, GMT_U_OPT, GMT_V_OPT, @@ -541,7 +541,8 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, 3, "+m Set <ambient> light to add [0]."); GMT_Usage (API, -2, "Alternatively, use -I+d to accept the default values (see grdgradient for more details). " "To derive intensities from another grid than <topogrid>, give the alternative data grid with suitable modifiers. " - "Use -I+f to shade physically based (see -F) with its default settings, i.e., -F315/45+o+t."); + "Use -I+P[<azim>[/<elev>]][<modifiers>] to shade physically based instead, exactly as -F does, " + "or -I+f to do so with its default settings, i.e., -F315/45+o+t."); GMT_Option (API, "K"); GMT_Usage (API, 1, "\n-N[<level>][+g<fill>]"); GMT_Usage (API, -2, "Draw a horizontal plane at z = <level> [minimum grid (or -R) value]. For rectangular projections, append +g<fill> " @@ -628,7 +629,7 @@ static int parse (struct GMT_CTRL *GMT, struct GRDVIEW_CTRL *Ctrl, struct GMT_OP } gmt_cpt_interval_modifier (GMT, &(Ctrl->C.file), &(Ctrl->C.dz)); break; - case 'F': /* Physically based shading -F<azim>/<elev>[+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ + case 'F': /* Physically based shading -F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); n_errors += gmt_pbr_parse(GMT, 'F', opt->arg, &Ctrl->F.P); break; @@ -656,9 +657,9 @@ static int parse (struct GMT_CTRL *GMT, struct GRDVIEW_CTRL *Ctrl, struct GMT_OP break; case 'I': /* Use intensity from grid or constant or auto-compute it */ n_errors += gmt_M_repeated_module_option (API, Ctrl->I.active); - if (!strcmp(opt->arg, "+f")) { /* -I+f: physically based shading (-F) with its default settings */ + if (!strcmp(opt->arg, "+f") || !strncmp(opt->arg, "+P", 2)) { /* -I+f or -I+P[<azim>[/<elev>]][<modifiers>]: same as -F */ n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); - Ctrl->F.P.occlusion = Ctrl->F.P.tone = true; + n_errors += gmt_pbr_parse(GMT, 'I', &opt->arg[2], &Ctrl->F.P); /* For +f, &opt->arg[2] is "" and selects the defaults */ Ctrl->I.active = false; /* No intensities: -F replaces -I */ break; } diff --git a/src/longopt/grd2kml_inc.h b/src/longopt/grd2kml_inc.h index 6966eb59f6b..3883906ddc7 100644 --- a/src/longopt/grd2kml_inc.h +++ b/src/longopt/grd2kml_inc.h @@ -38,7 +38,7 @@ static struct GMT_KEYWORD_DICTIONARY module_kw[] = { { 0, 'H', "subpixel", "", "", "", "", GMT_TP_STANDARD }, { 0, 'I', "illumination|intensity|shading", "", "", - "a,d,m,n", "azimuth,default,ambient,nargs", + "a,d,f,m,n,P", "azimuth,default,pbr,ambient,nargs,physical", GMT_TP_STANDARD }, { 0, 'L', "tilesize|tile_size", "", "", "", "", GMT_TP_STANDARD }, { 0, 'N', "prefix", "", "", "", "", GMT_TP_STANDARD }, diff --git a/src/longopt/grdimage_inc.h b/src/longopt/grdimage_inc.h index 1f213cd3160..d33f4754f75 100644 --- a/src/longopt/grdimage_inc.h +++ b/src/longopt/grdimage_inc.h @@ -45,7 +45,7 @@ static struct GMT_KEYWORD_DICTIONARY module_kw[] = { /* Local options for this m GMT_TP_STANDARD }, { 0, 'I', "intensity", "", "", - "a,d,f,m,n", "azimuth,default,pbr,ambient,intensity", + "a,d,f,m,n,P", "azimuth,default,pbr,ambient,intensity,physical", GMT_TP_STANDARD }, { 0, 'M', "monochrome", "", "", "", "", GMT_TP_STANDARD }, { 0, 'N', "noclip", "", "", "", "", GMT_TP_STANDARD }, diff --git a/src/longopt/grdview_inc.h b/src/longopt/grdview_inc.h index 5a1e0a5528a..699e0543511 100644 --- a/src/longopt/grdview_inc.h +++ b/src/longopt/grdview_inc.h @@ -33,7 +33,7 @@ static struct GMT_KEYWORD_DICTIONARY module_kw[] = { { 0, 'G', "drapegrid|drape", "", "", "", "", GMT_TP_STANDARD }, { 0, 'I', "illumination|shading|intensity", "", "", - "a,d,f,m,n", "azimuth,default,pbr,ambient,args", + "a,d,f,m,n,P", "azimuth,default,pbr,ambient,args,physical", GMT_TP_STANDARD }, { 0, 'N', "plane", "", "", From af7f78b4fd834169dc3f103b0da3b4a6c800eeec Mon Sep 17 00:00:00 2001 From: Joaquim <jluis@ualg.pt> Date: Wed, 30 Sep 2026 20:35:19 +0100 Subject: [PATCH 6/6] Remove the option -F and overload -I with it --- doc/rst/source/explain_intense.rst_ | 34 ++++++++++++------ doc/rst/source/grd2kml.rst | 8 +---- doc/rst/source/grdimage.rst | 17 ++------- doc/rst/source/grdview.rst | 19 ++-------- src/gmt_support.c | 10 +++--- src/grd2kml.c | 51 +++++++++++--------------- src/grdimage.c | 43 ++++++++++------------ src/grdview.c | 56 +++++++++++++---------------- src/longopt/grd2kml_inc.h | 2 +- src/longopt/grdimage_inc.h | 6 +--- src/longopt/grdview_inc.h | 6 +--- test/grdimage/grdimage-l2s.sh | 6 ++-- test/grdimage/pbr.sh | 10 +++--- test/grdview/grdview-l2s.sh | 6 ++-- test/grdview/pbr.sh | 4 +-- 15 files changed, 112 insertions(+), 166 deletions(-) diff --git a/doc/rst/source/explain_intense.rst_ b/doc/rst/source/explain_intense.rst_ index 757d8b69523..f82aa691806 100644 --- a/doc/rst/source/explain_intense.rst_ +++ b/doc/rst/source/explain_intense.rst_ @@ -1,7 +1,4 @@ **-I**\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*] - Or - -**-I**\ **+P**\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] Apply illumination for the input grid. Several methods are available: - Give the *file* name of a co-registered grid with intensities in the ±1 range. @@ -18,14 +15,27 @@ actually is an image then *file* or constant *intens* must be provided since derivatives are not available. +**Or** + +**-I**\ **+P**\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] Instead of intensities, give **+P**\ [*azim*\ [/*elev*]][*modifiers*] to shade the input grid with - physically based lighting, i.e., the CPT colors are lit as a microfacet material by the sun at - azimuth *azim* (degrees clockwise from north, taken modulo 360) [315] and elevation *elev* (degrees - above the horizon, 0-90) [45], and by a headlight fill, seen from straight above. Give just *azim* - to keep the default *elev* (but *elev* cannot be given without *azim*). Plain **+P**, or **+f**, - selects all the default settings, which are the same as **+P**\ 315/45+o+t. See :doc:`grdimage` - **-F** for more details. Optionally, append modifiers (the allowed range of each value is given in - parentheses, the default in brackets): + physically based lighting (the two cannot be combined). Each node's CPT color is used as the albedo + of a microfacet material that is lit by the sun, at azimuth *azim* (degrees clockwise from north, + taken modulo 360) [315] and elevation *elev* (degrees above the horizon, 0-90) [45], and by a + headlight fill, and seen from straight above. Give just *azim* to keep the default *elev* (but *elev* + cannot be given without *azim*). Plain **+P** selects all the default settings, which are the + same as **+P**\ 315/45+o+t. + + This is physically based rendering (PBR) as done by the shader of the Visualization Toolkit (VTK): + the Cook-Torrance specular term with the GGX distribution (Walter et al., 2007), the + height-correlated Smith visibility (Heitz, 2014) and Schlick's Fresnel approximation (Schlick, + 1994), plus the Lambertian diffuse term that the specular leaves over. Unlike intensities, which + blend each color toward white or gray, the light here multiplies the color, so hues are kept in + both the lit and the shaded parts. The relief is lit as a 3-D view would draw it: *x* and *y* at + the same scale (for a geographic grid, *x* is shortened by the cosine of the middle latitude) and + the *z* range drawn as 0.1 of the larger horizontal side, times the vertical exaggeration (see + **+v**). Optionally, append modifiers (the allowed range of each value is given in parentheses, + the default in brackets): - **+f** Set the *fill* (headlight) intensity (>= 0) [0.35]. - **+i** Set the index of refraction *ior* of the surface (>= 1) [1.5]. Values from about 1.3 @@ -34,8 +44,10 @@ - **+m** Set the *metallic* value, from 0 (dielectric) to 1 (metal) (0-1) [0]. - **+o** Add ambient occlusion, which darkens creases and valley floors [no occlusion]. Optionally append the sampling *radius* as a fraction of the diagonal of the relief as drawn (> 0) [0.1]. + Occlusion is only computed when the radius is larger than one grid node spacing. - **+r** Set the *roughness*, from 0 (mirror) to 1 (matte) (0-1) [0.3]. Values below 0.05 are treated as 0.05. - - **+s** Cast shadows along the sun's azimuth and elevation [no shadows]. + - **+s** Cast shadows: the terrain shades itself along the sun's azimuth and elevation, which the + hillshade methods of :doc:`grdgradient` do not do [no shadows]. - **+t** Apply the Khronos PBR Neutral tone mapping, which compresses the highlights [no tone mapping]. - **+v** Set the vertical exaggeration *ve* of the relief (> 0) [1]. diff --git a/doc/rst/source/grd2kml.rst b/doc/rst/source/grd2kml.rst index cb711db3662..5904f15a266 100644 --- a/doc/rst/source/grd2kml.rst +++ b/doc/rst/source/grd2kml.rst @@ -19,7 +19,7 @@ Synopsis [ |-E|\ *URL* ] [ |-F|\ *filtercode* ] [ |-H|\ *scale* ] -[ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*]\|\ **+f**\|\ **+P**\ [*azim*\ [/*elev*]][*modifiers*] ] +[ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*]\|\ **+P**\ [*modifiers*] ] [ |-L|\ *tilesize* ] [ |-S|\ [*extra*] ] [ |-T|\ *title* ] @@ -110,12 +110,6 @@ Optional Arguments .. include:: explain_intense.rst_ - With **+P** or **+f**, each tile is shaded together with a border of its neighbouring nodes, so slopes, - occlusion and shadows continue across the tile edges. The border is the occlusion radius on all sides - plus the shadow length (at most 256 nodes) on the sun's side. The relief is drawn at the vertical scale - of the full grid on every level, while the occlusion *radius* is a fraction of the tile's horizontal - diagonal, so the occlusion looks the same at every zoom level. - .. _-L: **-L**\ *tilesize* diff --git a/doc/rst/source/grdimage.rst b/doc/rst/source/grdimage.rst index 66d4150ec5b..527d76e1612 100644 --- a/doc/rst/source/grdimage.rst +++ b/doc/rst/source/grdimage.rst @@ -18,9 +18,8 @@ Synopsis [ |-C|\ [*section*/]\ *master*\|\ *cpt*\|\ *color*\ :math:`_1`,\ *color*\ :math:`_2`\ [,\ *color*\ :math:`_3`\ ,...]\ [**+h**\ [*hinge*]][**+i**\ *dz*][**+u**\|\ **U**\ *unit*][**+s**\ *fname*] ] [ |-D|\ [**r**] ] [ |-E|\ [**i**\|\ *dpi*] ] -[ |-F|\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] ] [ |-G|\ *color*\ [**+b**\|\ **f**] ] -[ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*] ] +[ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*]\|\ **+P**\ [*modifiers*] ] [ |-J|\ *parameters* ] [ |-M| ] [ |-N| ] @@ -127,18 +126,6 @@ Optional Arguments columns) as the input file. Specify **i** to use the PostScript image operator to interpolate the image at the device resolution. -.. _-F: - -**-F**\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] - Shade the grid with physically based lighting instead of illuminating it with |-I| (the two - cannot be combined). The shaded image is made on the grid's own nodes and then projected and - plotted as any image, so |-A|, |-E| and |-J| work as usual. The sun position is optional - [315/45]; give just *azim* to keep the default *elev* (but *elev* cannot be given without *azim*). - Plain |-F| (or **-I+f**) selects all the default settings, which are the same as **-F**\ 315/45+o+t. - **-I+P**\ [*azim*\ [/*elev*]][*modifiers*] is an alternative form that works exactly as |-F|. - - .. include:: explain_pbr.rst_ - .. _-G: **-G**\ *color*\ [**+b**\|\ **f**] @@ -350,7 +337,7 @@ To shade the Island of Hawaii and the surrounding sea floor with physically base from the northwest that makes Mauna Kea and Mauna Loa cast their shadows, ambient occlusion, tone mapping, a threefold vertical exaggeration and a brighter sun and fill light than the defaults:: -gmt grdimage @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Cgeo -F315/35+o+s+t+v3+l1.4+f0.5 -JM15c -B -png hawaii + gmt grdimage @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Cgeo -I+P315/35+o+s+t+v3+l1.4+f0.5 -JM15c -B -png hawaii .. include:: cpt_notes.rst_ diff --git a/doc/rst/source/grdview.rst b/doc/rst/source/grdview.rst index b21f8150d30..469e0ec472b 100644 --- a/doc/rst/source/grdview.rst +++ b/doc/rst/source/grdview.rst @@ -15,9 +15,8 @@ Synopsis **gmt grdview** *reliefgrid* |-J|\ *parameters* [ |SYN_OPT-B| ] [ |-C|\ [*section*/]\ *master*\|\ *cpt*\|\ *color*\ :math:`_1`,\ *color*\ :math:`_2`\ [,\ *color*\ :math:`_3`\ ,...]\ [**+h**\ [*hinge*]][**+i**\ *dz*][**+u**\|\ **U**\ *unit*][**+s**\ *fname*] ] -[ |-F|\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] ] [ |-G|\ *drapegrid*\|\ *drapeimage* ] -[ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*] ] +[ |-I|\ [*file*\|\ *intens*\|\ **+a**\ *azimuth*][**+d**][**+m**\ *ambient*][**+n**\ *args*]\|\ **+P**\ [*modifiers*] ] [ |-Jz|\ \|\ **Z**\ *parameters* ] [ |-N|\ [*level*]\ [**+g**\ *fill*] ] [ |-Q|\ **c**\|\ **g**\ [**m**]\ **i**\|\ **m**\ [**x**\|\ **y**]\|\ **s**\ [**m**]\|\ [*color*][**+m**]\ **t** ] @@ -71,20 +70,6 @@ Optional Arguments .. include:: use_cpt_grd.rst_ -.. _-F: - -**-F**\ [*azim*\ [/*elev*]][**+f**\ *fill*][**+i**\ *ior*][**+l**\ *light*][**+m**\ *metallic*][**+o**\ [*radius*]][**+r**\ *roughness*][**+s**][**+t**][**+v**\ *ve*] - Shade the *reliefgrid* with physically based lighting instead of illuminating it with |-I| (the - two cannot be combined, nor can |-G| or |-T| be used). The shaded colors are computed on the grid's - own nodes and draped over the relief as an image, so |-F| implies **-Qi** unless **-Qc**\|\ **i** - is given. The sun position is optional [315/45]; give just *azim* to keep the default *elev* (but - *elev* cannot be given without *azim*). Plain |-F| (or **-I+f**) selects all the default settings, - which are the same as **-F**\ 315/45+o+t. **-I+P**\ [*azim*\ [/*elev*]][*modifiers*] is an - alternative form that works exactly as |-F|. The shading itself is identical to that of - :doc:`grdimage` **-F**. - - .. include:: explain_pbr.rst_ - .. _-G: |-G|\ *drapegrid*\|\ *drapeimage* @@ -263,7 +248,7 @@ lighting: a low sun from the northwest that makes Mauna Kea and Mauna Loa cast t occlusion, tone mapping, a threefold vertical exaggeration of the shading and a brighter sun and fill light than the defaults:: -gmt grdimage @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Cgeo -F315/35+o+s+t+v3+l1.4+f0.5 -JM15c -JZ3c -p150/35 -B -png hawaii + gmt grdview @earth_gebco_15s -R-156.2/-154.7/18.8/20.35 -Cgeo -I+P315/35+o+s+t+v3+l1.4+f0.5 -JM15c -JZ3c -p150/35 -B -png hawaii .. module_note_begins diff --git a/src/gmt_support.c b/src/gmt_support.c index 9325ae02c58..9476630b76e 100644 --- a/src/gmt_support.c +++ b/src/gmt_support.c @@ -10206,13 +10206,13 @@ unsigned int gmt_pbr_check(struct GMT_CTRL *GMT, char option, struct GMT_PBR *P) } void gmt_pbr_syntax(struct GMTAPI_CTRL *API, char option) { - /* The usage lines shared by every option that takes the PBR settings */ - GMT_Usage(API, 1, "\n-%c[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>]", option); - GMT_Usage(API, -2, "Shade the grid with physically based (PBR) lighting: each node's CPT color is lit as a microfacet " + /* The usage lines of the +P directive, shared by every option (-I) that takes the PBR settings */ + GMT_Usage(API, -2, "Or, use -%c+P[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] " + "to shade the grid with physically based (PBR) lighting instead of intensities: each node's CPT color is lit as a microfacet " "material by the sun at <azim> (0-360) [315] and <elev> (0-90) [45] and by a headlight fill, seen from above. The relief is lit " "as a 3-D view draws it: x and y at one scale and the z range drawn as 0.1 of the larger horizontal side. " - "Give just <azim> to keep the default <elev>. Plain -%c selects all the defaults, i.e., -%c315/45+o+t. " - "Same as -I+P[<azim>[/<elev>]][<modifiers>]. Modifiers:", option, option); + "Give just <azim> to keep the default <elev>. Plain -%c+P selects all the defaults, i.e., -%c+P315/45+o+t. " + "Modifiers:", option, option, option); GMT_Usage(API, 3, "+f Set the <fill> (headlight) intensity (>= 0) [0.35]."); GMT_Usage(API, 3, "+i Set the index of refraction <ior> of the surface (>= 1) [1.5]."); GMT_Usage(API, 3, "+l Set the <light> (sun) intensity (>= 0) [1]."); diff --git a/src/grd2kml.c b/src/grd2kml.c index 9549421f435..a8db0ea4869 100644 --- a/src/grd2kml.c +++ b/src/grd2kml.c @@ -75,11 +75,11 @@ struct GRD2KML_CTRL { bool active; char *prefix; } N; - struct GRD2KML_I { /* -I[<intensfile>|<value>|<modifiers>] or -I+f|+P[<azim>[/<elev>]][<pbr_modifiers>] */ + struct GRD2KML_I { /* -I[<intensfile>|<value>|<modifiers>] or -I+P[<azim>[/<elev>]][<modifiers>] */ bool active; bool constant; bool derive; - bool pbr; /* Physically based shading (-I+f or -I+P) instead of intensities */ + bool pbr; /* Physically based shading (-I+P) instead of intensities */ double value; struct GMT_PBR P; /* Physically based shading settings (gmt_support.c) */ char *azimuth; /* Default azimuth(s) for shading */ @@ -124,7 +124,7 @@ static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new /* Initialize values whose defaults are not 0/false/NULL */ C->F.filter = 'g'; C->I.method = strdup ("t1"); /* Default normalization for shading when -I is used */ - gmt_pbr_defaults(&C->I.P); /* Physically based shading defaults (-I+f or -I+P) */ + gmt_pbr_defaults(&C->I.P); /* Physically based shading defaults (-I+P) */ C->L.size = 512; /* Default tile size unless global grids [360] */ C->W.scale = M_SQRT2; C->W.cutoff = 0.1; @@ -148,7 +148,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) { const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE); if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR); GMT_Usage (API, 0, "usage: %s %s -N<name> [-Aa|g|s[<altitude>]] [-C%s] [-E<url>] [-F<filter>] " - "[-H<scale>] [-I[<intensgrid>|<value>|<modifiers>|+f|+P[<azim>[/<elev>]][<pbr_modifiers>]]] [-L<size>] [-S[<extra>]] [-T<title>] [%s] " + "[-H<scale>] [-I[<intensgrid>|<value>|<modifiers>|+P[<modifiers>]]] [-L<size>] [-S[<extra>]] [-T<title>] [%s] " "[-W<contfile>|<pen>[+s<scl>/<limit>]] [%s] [%s] [%s]\n", name, GMT_INGRID, CPT_OPT_ARGS, GMT_V_OPT, GMT_f_OPT, GMT_n_OPT, GMT_PAR_OPT); if (level == GMT_SYNOPSIS) return (GMT_MODULE_SYNOPSIS); @@ -179,26 +179,15 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, 3, "m: Median - median (50%% quantile) value of all points."); GMT_Usage (API, 1, "\n-H<scale>"); GMT_Usage (API, -2, "Do sub-pixel smoothing using factor <scale> [no sub-pixel smoothing]. Ignored if -W not set."); - GMT_Usage (API, 1, "\n-I[<intensgrid>|<value>|<modifiers>]"); + GMT_Usage(API, 1, "\n-I[<intensgrid>|<value>|<modifiers>|+P[<modifiers>]]"); GMT_Usage (API, -2, "Apply directional illumination. Append name of intensity grid file. " " For a constant intensity (i.e., change the ambient light), append a single value. " "To derive intensities from <grid> instead, use -I+d to accept the default values (see grdgradient for details) or be specific:"); GMT_Usage (API, 3, "+a Append <azimuth> of illumination [-45]"); GMT_Usage (API, 3, "+n Append <method> pf intensity calculation [t1]"); - GMT_Usage(API, -2, "Alternatively, use -I+P[<azim>[/<elev>]][<pbr_modifiers>] to shade <grid> physically based " - "instead (see grdimage -F), or -I+f to do so with its default settings, i.e., -I+P315/45+o+t. " - "Each level is shaded as a whole and with the relief scale of <grid>, so tiles match across their edges. " - "The <pbr_modifiers> are:"); - GMT_Usage(API, 3, "+f Set the <fill> (headlight) intensity (>= 0) [0.35]."); - GMT_Usage(API, 3, "+i Set the index of refraction <ior> of the surface (>= 1) [1.5]."); - GMT_Usage(API, 3, "+l Set the <light> (sun) intensity (>= 0) [1]."); - GMT_Usage(API, 3, "+m Set the <metallic> value (0-1) [0]."); - GMT_Usage(API, 3, "+o Add ambient occlusion; optionally append the sampling <radius> as a fraction of the " - "diagonal of the relief as drawn (> 0) [0.1]."); - GMT_Usage(API, 3, "+r Set the <roughness> (0-1) [0.3]."); - GMT_Usage(API, 3, "+s Cast shadows along the sun's azimuth and elevation."); - GMT_Usage(API, 3, "+t Apply the Khronos PBR Neutral tone mapping."); - GMT_Usage(API, 3, "+v Set the vertical exaggeration <ve> of the relief (> 0) [1]."); + gmt_pbr_syntax(API, 'I'); + GMT_Usage(API, -2, "Each tile is shaded with a border of its neighbouring nodes and at the relief scale of <grid>, so tiles " + "match across their edges; here the occlusion <radius> is a fraction of the tile's horizontal diagonal."); GMT_Usage (API, 1, "\n-L<size>"); GMT_Usage (API, -2, "Set tile size as a power of 2 [512; for global grids, we instead select 360].n"); GMT_Usage (API, 1, "\n-S[<extra>]"); @@ -284,9 +273,9 @@ static int parse (struct GMT_CTRL *GMT, struct GRD2KML_CTRL *Ctrl, struct GMT_OP break; case 'I': /* Here, intensity must be a grid file since we need to filter it */ n_errors += gmt_M_repeated_module_option(API, Ctrl->I.active); - if (!strcmp(opt->arg, "+f") || !strncmp(opt->arg, "+P", 2)) { /* -I+f or -I+P[<azim>[/<elev>]][<modifiers>]: physically based shading */ + if (!strncmp(opt->arg, "+P", 2)) { /* -I+P[<azim>[/<elev>]][<modifiers>]: physically based shading */ Ctrl->I.pbr = true; - n_errors += gmt_pbr_parse(GMT, 'I', &opt->arg[2], &Ctrl->I.P); /* For +f, &opt->arg[2] is "" and selects the defaults */ + n_errors += gmt_pbr_parse(GMT, 'I', &opt->arg[2], &Ctrl->I.P); /* Plain +P gives "" and selects all the defaults */ } else if (!strcmp(opt->arg, "+d")) /* Gave +d only, so derive intensities from input grid using default settings */ Ctrl->I.derive = true; @@ -302,7 +291,7 @@ static int parse (struct GMT_CTRL *GMT, struct GRD2KML_CTRL *Ctrl, struct GMT_OP } } } - else if (!opt->arg[0] || strstr (opt->arg, "+")) /* No argument or just +, so derive intensities from input grid using default settings */ + else if (!opt->arg[0] || !strcmp(opt->arg, "+")) /* No argument or just +, so derive intensities from input grid using default settings */ Ctrl->I.derive = true; else if (!gmt_access (GMT, opt->arg, R_OK)) /* Got a file */ Ctrl->I.file = strdup (opt->arg); @@ -764,7 +753,7 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { error = GMT_RUNTIME_ERROR; goto end_times; } z_extend = true; /* We made a temp file we need to zap later */ - if (Ctrl->I.active) { /* Also extend the intensity grid */ + if (Ctrl->I.active && !Ctrl->I.constant) { /* Also extend the intensity grid */ sprintf (IntensGrid, "%s/grd2kml_extended_intens_%6.6d.grd", API->tmp_dir, uniq); sprintf (cmd, "%s -R%.16g/%.16g/%.16g/%.16g -N -G%s", Ctrl->I.file, ext_wesn[XLO], ext_wesn[XHI], ext_wesn[YLO], ext_wesn[YHI], IntensGrid); GMT_Report (API, GMT_MSG_INFORMATION, "Extend intensity grid to multiple of largest grid spacing\n"); @@ -777,8 +766,8 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { } else { /* No need to extend, use the input files as is */ strcpy (DataGrid, Ctrl->In.file); - if (Ctrl->I.active) - strcpy (IntensGrid, Ctrl->I.file); + if (Ctrl->I.active && !Ctrl->I.constant) + strcpy(IntensGrid, Ctrl->I.file); } if (!Ctrl->C.active || gmt_is_cpt_master (GMT, Ctrl->C.file)) { /* If no cpt given or just a master then we must compute a scaled one from the full-size grid and use it throughout */ @@ -899,7 +888,7 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { if (grd2kml_coarsen_grid (GMT, level, Ctrl->F.filter, registration, G->header->inc[GMT_X], inc, DataGrid, Zgrid, filt_report)) { error = GMT_RUNTIME_ERROR; goto end_times; } - if (Ctrl->I.active) { /* Also filter the intensity grid */ + if (Ctrl->I.active && !Ctrl->I.constant) { /* Also filter the intensity grid */ sprintf (Igrid, "%s/grd2kml_I_L%d_tmp_%6.6d.grd", API->tmp_dir, level, uniq); if (grd2kml_coarsen_grid (GMT, level, Ctrl->F.filter, registration, G->header->inc[GMT_X], inc, IntensGrid, Igrid, filt_report)) { error = GMT_RUNTIME_ERROR; goto end_times; @@ -910,7 +899,7 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { sprintf (filt_report, " [Original grid used]"); strcpy (Zgrid, DataGrid); step = Ctrl->L.size * G->header->inc[GMT_X]; - if (Ctrl->I.active) strcpy (Igrid, IntensGrid); + if (Ctrl->I.active && !Ctrl->I.constant) strcpy(Igrid, IntensGrid); } if (Ctrl->I.pbr && (Zh = GMT_Read_Data(API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_ONLY, NULL, Zgrid, NULL)) == NULL) { /* Level extent, which limits the tile borders */ error = API->error; goto end_times; @@ -976,7 +965,9 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { } img_in = p_data; } - if (Ctrl->I.active) { /* Also get the intensity tile */ + if (Ctrl->I.constant) /* Constant intensity: pass the value to grdimage instead of a tile */ + snprintf(i_data, GMT_VF_LEN, "%.16g", Ctrl->I.value); + else if (Ctrl->I.active) { /* Also get the intensity tile */ if ((I = GMT_Read_Data (API, GMT_IS_GRID, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA, wesn, Igrid, NULL)) == NULL) { GMT_Report (API, GMT_MSG_ERROR, "Unable to read in intensity tile!\n"); error = GMT_RUNTIME_ERROR; goto end_times; @@ -1052,7 +1043,7 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { GMT_Report (API, GMT_MSG_ERROR, "Unable to free memory of grid tile!\n"); error = GMT_RUNTIME_ERROR; goto end_times; } - if (Ctrl->I.active) { /* Same for intensity tile */ + if (Ctrl->I.active && !Ctrl->I.constant) { /* Same for intensity tile */ GMT_Close_VirtualFile (API, i_data); if (GMT_Destroy_Data (API, &I) != GMT_NOERROR) { GMT_Report (API, GMT_MSG_ERROR, "Unable to free memory of intensity tile!\n"); @@ -1112,7 +1103,7 @@ EXTERN_MSC int GMT_grd2kml (void *V_API, int mode, void *args) { if (Ctrl->I.pbr) GMT_Destroy_Data(API, &Zh); if (level < max_level) { /* Delete the temporary filtered grid(s) */ gmt_remove_file (GMT, Zgrid); - if (Ctrl->I.active) gmt_remove_file (GMT, Igrid); + if (Ctrl->I.active && !Ctrl->I.constant) gmt_remove_file(GMT, Igrid); } grd2kml_halve_dimensions (&inc, &step); } diff --git a/src/grdimage.c b/src/grdimage.c index ae1e240b66e..3bd6324005a 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -105,10 +105,10 @@ struct GRDIMAGE_CTRL { double rgb[4]; /* Pixel value for transparency in images */ double value; /* If +z is used this z-value will give us the r/g/b via CPT */ } Q; - struct GRDIMAGE_F { /* -F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ + struct GRDIMAGE_PBR { /* -I+P[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ bool active; struct GMT_PBR P; /* Physically based shading settings (gmt_support.c) */ - } F; + } PBR; struct GRDIMAGE_T { /* -T[s][o[<pen>]] */ bool active; bool skip; @@ -158,7 +158,7 @@ static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new C->I.azimuth = strdup ("-45.0"); /* Default azimuth for shading when -I+d is used */ C->I.method = strdup ("t1"); /* Default normalization for shading when -I+d is used */ C->I.ambient = strdup ("0"); /* Default ambient light for shading when -I+d is used */ - gmt_pbr_defaults(&C->F.P); /* Physically based shading defaults (-F, or -I+f) */ + gmt_pbr_defaults(&C->PBR.P); /* Physically based shading defaults (-I+P) */ C->Q.value = GMT->session.f_NaN; /* If -Q is used with a grid then NaNs are made transparent by default. */ C->Q.rgb[0] = C->Q.rgb[1] = C->Q.rgb[2] = 1.0; /* White transparent pixel is default if -Q is given without a color */ return (C); @@ -183,8 +183,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) { const char *extra[2] = {A, " [-A]"}; if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR); GMT_Usage (API, 0, "usage: %s %s %s%s [%s] [-C%s] [-D[r]] [-Ei|<dpi>] " - "[-F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>]] " - "[-G<rgb>[+b|f]] [-I[<intensgrid>|<value>|<modifiers>]] %s[-M] [-N] %s%s[-Q[<color>][+i][+z<value>]] " + "[-G<rgb>[+b|f]] [-I[<intensgrid>|<value>|<modifiers>|+P[<modifiers>]]] %s[-M] [-N] %s%s[-Q[<color>][+i][+z<value>]] " "[%s] [-T[+o[<pen>]][+s]] [%s] [%s] [%s] [%s] %s[%s] [%s] [%s] [%s]%s[%s]\n", name, GMT_INGRID, GMT_J_OPT, extra[API->external], GMT_B_OPT, CPT_OPT_ARGS, API->K_OPT, API->O_OPT, API->P_OPT, GMT_Rgeo_OPT, GMT_U_OPT, GMT_V_OPT, GMT_X_OPT, GMT_Y_OPT, API->c_OPT, GMT_f_OPT, GMT_n_OPT, GMT_p_OPT, GMT_t_OPT, GMT_x_OPT, GMT_PAR_OPT); @@ -221,11 +220,10 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, -2, "Set dpi for the projected grid which must be constructed [100] " "if -J implies a nonlinear graticule [Default gives same size as input grid]. " "Alternatively, append i to do the interpolation in PostScript at device resolution (invalid with -Q)."); - gmt_pbr_syntax(API, 'F'); /* Replaces -I */ gmt_rgb_syntax (API->GMT, 'G', "Set transparency color for images that otherwise would result in 1-bit images. "); GMT_Usage (API, 3, "+b Set background color."); GMT_Usage (API, 3, "+f Set foreground color [Default]."); - GMT_Usage (API, 1, "\n-I[<intensgrid>|<value>|<modifiers>]"); + GMT_Usage(API, 1, "\n-I[<intensgrid>|<value>|<modifiers>|+P[<modifiers>]]"); GMT_Usage (API, -2, "Apply directional illumination. Append name of an intensity grid, or " "for a constant intensity (i.e., change the ambient light), just give a scalar. " "To derive intensities from <grid> instead, append desired modifiers:"); @@ -233,9 +231,10 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, 3, "+n Set the <method> and <scale> to use [t1]."); GMT_Usage (API, 3, "+m Set <ambient> light to add [0]."); GMT_Usage (API, -2, "Alternatively, use -I+d to accept the default values (see grdgradient for more details). " - "To derive intensities from another grid than <grid>, give the alternative data grid with suitable modifiers. " - "Use -I+P[<azim>[/<elev>]][<modifiers>] to shade physically based instead, exactly as -F does, " - "or -I+f to do so with its default settings, i.e., -F315/45+o+t."); + "To derive intensities from another grid than <grid>, give the alternative data grid with suitable modifiers."); + gmt_pbr_syntax(API, 'I'); + GMT_Usage(API, -2, "The shaded image is made on the grid's own nodes and then projected and plotted as any image. " + "Requires a single grid and cannot be used with -T."); GMT_Option (API, "J-"); GMT_Option (API, "K"); GMT_Usage (API, 1, "\n-M Force a monochrome (gray-scale) image."); @@ -368,10 +367,6 @@ static int parse(struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_OP else Ctrl->E.dpi = atoi (opt->arg); break; - case 'F': /* Physically based shading -F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ - n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); - n_errors += gmt_pbr_parse(GMT, 'F', opt->arg, &Ctrl->F.P); - break; case 'G': /* -G<color>[+b|f] 1-bit fore- or background color for transparent masks (was -G[f|b]<color>) */ n_errors += gmt_M_repeated_module_option (API, Ctrl->G.active); if ((c = strstr (opt->arg, "+b"))) { /* Background color */ @@ -398,10 +393,10 @@ static int parse(struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_OP break; case 'I': /* Use intensity from grid or constant or auto-compute it */ n_errors += gmt_M_repeated_module_option (API, Ctrl->I.active); - if (!strcmp(opt->arg, "+f") || !strncmp(opt->arg, "+P", 2)) { /* -I+f or -I+P[<azim>[/<elev>]][<modifiers>]: same as -F */ - n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); - n_errors += gmt_pbr_parse(GMT, 'I', &opt->arg[2], &Ctrl->F.P); /* For +f, &opt->arg[2] is "" and selects the defaults */ - Ctrl->I.active = false; /* No intensities: -F replaces -I */ + if (!strncmp(opt->arg, "+P", 2)) { /* -I+P[<azim>[/<elev>]][<modifiers>]: physically based shading */ + n_errors += gmt_M_repeated_module_option(API, Ctrl->PBR.active); + n_errors += gmt_pbr_parse(GMT, 'I', &opt->arg[2], &Ctrl->PBR.P); /* Plain +P gives "" and selects all the defaults */ + Ctrl->I.active = false; /* No intensities: the shaded colors replace them */ break; } if ((c = strstr (opt->arg, "+d"))) { /* Gave +d, so derive intensities from the input grid using default settings */ @@ -561,10 +556,10 @@ static int parse(struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_OP "Option -A: Cannot draw base frame (-B) when creating just a raster image\n"); n_errors += gmt_M_check_condition (GMT, Ctrl->Q.transp_color && Ctrl->Q.z_given, "Option Q: Cannot both specify a r/g/b and a grid z-value\n"); - n_errors += gmt_M_check_condition(GMT, Ctrl->F.active && Ctrl->I.active, "Option -F: Cannot be combined with -I\n"); - n_errors += gmt_M_check_condition(GMT, Ctrl->F.active && (Ctrl->T.active || Ctrl->D.active || n_files == 3), - "Option -F: Requires a single grid and cannot be used with -T\n"); - if (Ctrl->F.active) n_errors += gmt_pbr_check(GMT, 'F', &Ctrl->F.P); + n_errors += gmt_M_check_condition(GMT, Ctrl->PBR.active && Ctrl->I.active, "Option -I: Given more than once (+P or +f cannot be combined with intensities)\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->PBR.active && (Ctrl->T.active || Ctrl->D.active || n_files == 3), + "Option -I+P: Requires a single grid and cannot be used with -T\n"); + if (Ctrl->PBR.active) n_errors += gmt_pbr_check(GMT, 'I', &Ctrl->PBR.P); return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR); } @@ -1973,9 +1968,9 @@ EXTERN_MSC int GMT_grdimage(void *V_API, int mode, void *args) { for (node = 0; !has_content && node < Grid_orig->header->size; node++) if (!gmt_M_is_dnan (Grid_orig->data[node])) has_content = true; } - if (Ctrl->F.active) { /* Physically based shading: from here on the grid, shaded through its CPT, is an image input */ + if (Ctrl->PBR.active) { /* Physically based shading: from here on the grid, shaded through its CPT, is an image input */ GMT_Report(API, GMT_MSG_INFORMATION, "Physically based shading of the grid into an image\n"); - if ((I = gmt_pbr_image(GMT, &Ctrl->F.P, Grid_orig, P)) == NULL) Return (API->error); + if ((I = gmt_pbr_image(GMT, &Ctrl->PBR.P, Grid_orig, P)) == NULL) Return (API->error); got_z_grid = gray_only = false; Ctrl->D.active = true; /* So it is projected as an image */ grid_registration = I->header->registration; diff --git a/src/grdview.c b/src/grdview.c index 5cd3fb140b8..76ba7d041af 100644 --- a/src/grdview.c +++ b/src/grdview.c @@ -74,10 +74,10 @@ struct GRDVIEW_CTRL { char *file; char *savecpt; /* For when we want to save the automatically generated CPT */ } C; - struct GRDVIEW_F { /* -F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ + struct GRDVIEW_PBR { /* -I+P[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ bool active; struct GMT_PBR P; /* Physically based shading settings (gmt_support.c) */ - } F; + } PBR; struct GRDVIEW_G { /* -G<drapefile>|<drapeimage> */ /* Also handle deprecated triples -Gred.grd -Ggreen.grd -Gblue.grd or -Gred.grd,green.grd,blue.grd */ bool active; @@ -478,7 +478,7 @@ static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new /* Initialize values whose defaults are not 0/false/NULL */ gmt_init_fill (GMT, &C->N.fill, -1.0, -1.0, -1.0); /* Default is no fill of facade */ - gmt_pbr_defaults(&C->F.P); /* Physically based shading defaults (-F, or -I+f) */ + gmt_pbr_defaults(&C->PBR.P); /* Physically based shading defaults (-I+P) */ C->I.azimuth = strdup ("-45.0"); /* Default azimuth for shading when -I is used */ C->I.method = strdup ("t1"); /* Default normalization for shading when -I is used */ C->T.pen = C->W.pen[0] = C->W.pen[1] = C->W.pen[2] = GMT->current.setting.map_default_pen; /* Tile and mesh pens */ @@ -509,8 +509,8 @@ static int usage (struct GMTAPI_CTRL *API, int level) { const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE); if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR); GMT_Usage (API, 0, "usage: %s <topogrid> %s [%s] [-C%s] " - "[-F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>]] [-G<drapegrid>|<drapeimage>] " - "[-I[<intensgrid>|<value>|<modifiers>]] [%s] %s[-N[<level>][+g<fill>]] %s%s[-Qc|g[m|a]|i|m[x|y]|s[<color>][+m]] [%s] [-S<smooth>] " + "[-G<drapegrid>|<drapeimage>] " + "[-I[<intensgrid>|<value>|<modifiers>|+P[<modifiers>]]] [%s] %s[-N[<level>][+g<fill>]] %s%s[-Qc|g[m|a]|i|m[x|y]|s[<color>][+m]] [%s] [-S<smooth>] " "[-T[+o[<pen>]][+s]] [%s] [%s] [-W<type><pen>] [%s] [%s] %s[%s] [%s] [%s] [%s] [%s]\n", name, GMT_J_OPT, GMT_B_OPT, CPT_OPT_ARGS, GMT_Jz_OPT, API->K_OPT, API->O_OPT, API->P_OPT, GMT_Rgeoz_OPT, GMT_U_OPT, GMT_V_OPT, GMT_X_OPT, GMT_Y_OPT, API->c_OPT, GMT_f_OPT, GMT_n_OPT, GMT_p_OPT, GMT_t_OPT, GMT_PAR_OPT); @@ -523,16 +523,13 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Message (API, GMT_TIME_NONE, "\n OPTIONAL ARGUMENTS:\n"); GMT_Option (API, "B-"); gmt_explain_cpt_input (API, 'C'); - gmt_pbr_syntax(API, 'F'); - GMT_Usage(API, -2, "The shaded colors are draped over <topogrid> as an image, so -F implies -Qi [or use -Qc|i<dpu>] and cannot be " - "combined with -G or -I."); GMT_Usage (API, 1, "\n-G<drapegrid>|<drapeimage>"); GMT_Usage (API, -2, "Specify how to color the 3-D surface geometry defined by <topogrid>:"); GMT_Usage (API, 3, "%s Provide a grid (<drapegrid>) and colors will be determined from it and the cpt.", GMT_LINE_BULLET); GMT_Usage (API, 3, "%s Provide an image (<drapeimage>) and it will be draped over the surface.", GMT_LINE_BULLET); GMT_Usage (API, -2, "Notes: 1) -JZ|z and -N always refer to the data in the <topogrid>. " "2) The -G option requires the -Qc|i[<dpu>] option."); - GMT_Usage (API, 1, "\n-I[<intensgrid>|<value>|<modifiers>]"); + GMT_Usage(API, 1, "\n-I[<intensgrid>|<value>|<modifiers>|+P[<modifiers>]]"); GMT_Usage (API, -2, "Apply directional illumination. Append name of an intensity grid, or " "for a constant intensity (i.e., change the ambient light), just give a scalar. " "To derive intensities from <topogrid> instead, append desired modifiers:"); @@ -540,9 +537,10 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, 3, "+n Set the <method> and <scale> to use [t1]."); GMT_Usage (API, 3, "+m Set <ambient> light to add [0]."); GMT_Usage (API, -2, "Alternatively, use -I+d to accept the default values (see grdgradient for more details). " - "To derive intensities from another grid than <topogrid>, give the alternative data grid with suitable modifiers. " - "Use -I+P[<azim>[/<elev>]][<modifiers>] to shade physically based instead, exactly as -F does, " - "or -I+f to do so with its default settings, i.e., -F315/45+o+t."); + "To derive intensities from another grid than <topogrid>, give the alternative data grid with suitable modifiers."); + gmt_pbr_syntax(API, 'I'); + GMT_Usage(API, -2, "The shaded colors are draped over <topogrid> as an image, so -I+P implies -Qi [or use -Qc|i<dpu>] and cannot be " + "combined with -G or -T."); GMT_Option (API, "K"); GMT_Usage (API, 1, "\n-N[<level>][+g<fill>]"); GMT_Usage (API, -2, "Draw a horizontal plane at z = <level> [minimum grid (or -R) value]. For rectangular projections, append +g<fill> " @@ -629,10 +627,6 @@ static int parse (struct GMT_CTRL *GMT, struct GRDVIEW_CTRL *Ctrl, struct GMT_OP } gmt_cpt_interval_modifier (GMT, &(Ctrl->C.file), &(Ctrl->C.dz)); break; - case 'F': /* Physically based shading -F[<azim>[/<elev>]][+f<fill>][+i<ior>][+l<light>][+m<metallic>][+o[<radius>]][+r<roughness>][+s][+t][+v<ve>] */ - n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); - n_errors += gmt_pbr_parse(GMT, 'F', opt->arg, &Ctrl->F.P); - break; case 'G': /* One grid or image or three separate r,g,b grids */ Ctrl->G.active = true; for (k = 0, n_commas = 0; opt->arg[k]; k++) if (opt->arg[k] == ',') n_commas++; @@ -657,10 +651,10 @@ static int parse (struct GMT_CTRL *GMT, struct GRDVIEW_CTRL *Ctrl, struct GMT_OP break; case 'I': /* Use intensity from grid or constant or auto-compute it */ n_errors += gmt_M_repeated_module_option (API, Ctrl->I.active); - if (!strcmp(opt->arg, "+f") || !strncmp(opt->arg, "+P", 2)) { /* -I+f or -I+P[<azim>[/<elev>]][<modifiers>]: same as -F */ - n_errors += gmt_M_repeated_module_option(API, Ctrl->F.active); - n_errors += gmt_pbr_parse(GMT, 'I', &opt->arg[2], &Ctrl->F.P); /* For +f, &opt->arg[2] is "" and selects the defaults */ - Ctrl->I.active = false; /* No intensities: -F replaces -I */ + if (!strncmp(opt->arg, "+P", 2)) { /* -I+P[<azim>[/<elev>]][<modifiers>]: physically based shading */ + n_errors += gmt_M_repeated_module_option(API, Ctrl->PBR.active); + n_errors += gmt_pbr_parse(GMT, 'I', &opt->arg[2], &Ctrl->PBR.P); /* Plain +P gives "" and selects all the defaults */ + Ctrl->I.active = false; /* No intensities: the shaded colors replace them */ break; } if ((c = strstr (opt->arg, "+d"))) { /* Gave +d, so derive intensities from the input grid using default settings */ @@ -911,12 +905,12 @@ static int parse (struct GMT_CTRL *GMT, struct GRDVIEW_CTRL *Ctrl, struct GMT_OP "Option -Qs: Must also specify a cpt via -C\n"); n_errors += gmt_M_check_condition(GMT, Ctrl->T.active && GMT->current.proj.JZ_set, "Option -T: Cannot specify -JZ|z\n"); - if (Ctrl->F.active) { /* The shaded colors are draped as an image */ - n_errors += gmt_M_check_condition(GMT, Ctrl->I.active, "Option -F: Cannot be combined with -I\n"); - n_errors += gmt_M_check_condition(GMT, Ctrl->G.active, "Option -F: Cannot be combined with -G\n"); - n_errors += gmt_M_check_condition(GMT, Ctrl->T.active, "Option -F: Cannot be combined with -T\n"); - n_errors += gmt_M_check_condition(GMT, Ctrl->Q.active && Ctrl->Q.mode != GRDVIEW_IMAGE, "Option -F: Requires -Qc|i\n"); - n_errors += gmt_pbr_check(GMT, 'F', &Ctrl->F.P); + if (Ctrl->PBR.active) { /* The shaded colors are draped as an image */ + n_errors += gmt_M_check_condition(GMT, Ctrl->I.active, "Option -I: Given more than once (+P or +f cannot be combined with intensities)\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->G.active, "Option -I+P: Cannot be combined with -G\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->T.active, "Option -I+P: Cannot be combined with -T\n"); + n_errors += gmt_M_check_condition(GMT, Ctrl->Q.active && Ctrl->Q.mode != GRDVIEW_IMAGE, "Option -I+P: Requires -Qc|i\n"); + n_errors += gmt_pbr_check(GMT, 'I', &Ctrl->PBR.P); } return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR); @@ -977,7 +971,7 @@ EXTERN_MSC int GMT_grdview(void *V_API, int mode, void *args) { /*---------------------------- This is the grdview main code ----------------------------*/ - if (Ctrl->F.active) { /* Physically based shading: the topo grid is shaded through its CPT (gmt_pbr_image) into an + if (Ctrl->PBR.active) { /* Physically based shading: the topo grid is shaded through its CPT (gmt_pbr_image) into an * image that is then draped over it exactly as a -G<image> would be */ Ctrl->C.active = Ctrl->G.active = true; Ctrl->G.n = 1; @@ -1102,7 +1096,7 @@ EXTERN_MSC int GMT_grdview(void *V_API, int mode, void *args) { t_reg = gmt_change_grdreg (GMT, Topo->header, GMT_GRID_NODE_REG); /* Ensure gridline registration */ if (Ctrl->C.active) { - char *dataset_cpt = (!Ctrl->F.active && Ctrl->G.active && Ctrl->G.n == 1 && !gmt_M_file_is_image (Ctrl->G.file[0])) ? Ctrl->G.file[0] : Ctrl->In.file; + char *dataset_cpt = (!Ctrl->PBR.active && Ctrl->G.active && Ctrl->G.n == 1 && !gmt_M_file_is_image(Ctrl->G.file[0])) ? Ctrl->G.file[0] : Ctrl->In.file; char *cpt = gmt_cpt_default (API, Ctrl->C.file, dataset_cpt, Topo->header); if ((P = gmt_get_palette (GMT, cpt, GMT_CPT_OPTIONAL, Topo->header->z_min, Topo->header->z_max, Ctrl->C.dz)) == NULL) { Return (API->error); @@ -1117,13 +1111,13 @@ EXTERN_MSC int GMT_grdview(void *V_API, int mode, void *args) { get_contours = (Ctrl->Q.mode == GRDVIEW_MESH && Ctrl->W.contour) || (Ctrl->Q.mode == GRDVIEW_SURF && !Ctrl->Q.gouraud && P && P->n_colors > 1) || (Ctrl->Q.gouraud && Ctrl->W.contour); if (Ctrl->G.active) { /* Draping wanted */ - if (Ctrl->F.active || (Ctrl->G.n == 1 && gmt_M_file_is_image (Ctrl->G.file[0]))) { + if (Ctrl->PBR.active || (Ctrl->G.n == 1 && gmt_M_file_is_image (Ctrl->G.file[0]))) { double inc[2]; /* Want to drape an image on top of surface. Do so by converting the image to r, g, b grids */ struct GMT_IMAGE *I = NULL; - if (Ctrl->F.active) { /* The image is the topo grid itself, shaded physically based through its CPT */ + if (Ctrl->PBR.active) { /* The image is the topo grid itself, shaded physically based through its CPT */ GMT_Report(API, GMT_MSG_INFORMATION, "Physically based shading of the grid into a drape image\n"); - if ((I = gmt_pbr_image(GMT, &Ctrl->F.P, Topo, P)) == NULL) { + if ((I = gmt_pbr_image(GMT, &Ctrl->PBR.P, Topo, P)) == NULL) { Return (API->error); } } diff --git a/src/longopt/grd2kml_inc.h b/src/longopt/grd2kml_inc.h index 3883906ddc7..7b9e647a4f1 100644 --- a/src/longopt/grd2kml_inc.h +++ b/src/longopt/grd2kml_inc.h @@ -38,7 +38,7 @@ static struct GMT_KEYWORD_DICTIONARY module_kw[] = { { 0, 'H', "subpixel", "", "", "", "", GMT_TP_STANDARD }, { 0, 'I', "illumination|intensity|shading", "", "", - "a,d,f,m,n,P", "azimuth,default,pbr,ambient,nargs,physical", + "a,d,m,n,P", "azimuth,default,ambient,nargs,physical", GMT_TP_STANDARD }, { 0, 'L', "tilesize|tile_size", "", "", "", "", GMT_TP_STANDARD }, { 0, 'N', "prefix", "", "", "", "", GMT_TP_STANDARD }, diff --git a/src/longopt/grdimage_inc.h b/src/longopt/grdimage_inc.h index d33f4754f75..11873d3dc1a 100644 --- a/src/longopt/grdimage_inc.h +++ b/src/longopt/grdimage_inc.h @@ -35,17 +35,13 @@ static struct GMT_KEYWORD_DICTIONARY module_kw[] = { /* Local options for this m "i", "psdeviceres", "", "", GMT_TP_STANDARD }, - { 0, 'F', "pbr|physical", - "", "", - "f,i,l,m,o,r,s,t,v", "fill,ior,light,metallic,occlusion,roughness,shadows,tonemap,exaggeration", - GMT_TP_STANDARD }, { 0, 'G', "bitcolor", "", "", "b,f", "background,foreground", GMT_TP_STANDARD }, { 0, 'I', "intensity", "", "", - "a,d,f,m,n,P", "azimuth,default,pbr,ambient,intensity,physical", + "a,d,m,n,P", "azimuth,default,ambient,intensity,physical", GMT_TP_STANDARD }, { 0, 'M', "monochrome", "", "", "", "", GMT_TP_STANDARD }, { 0, 'N', "noclip", "", "", "", "", GMT_TP_STANDARD }, diff --git a/src/longopt/grdview_inc.h b/src/longopt/grdview_inc.h index 699e0543511..646f356ff30 100644 --- a/src/longopt/grdview_inc.h +++ b/src/longopt/grdview_inc.h @@ -26,14 +26,10 @@ static struct GMT_KEYWORD_DICTIONARY module_kw[] = { short_modifiers, long_modifiers, transproc_mask */ GMT_C_CPT_KW, - { 0, 'F', "pbr|physical", - "", "", - "f,i,l,m,o,r,s,t,v", "fill,ior,light,metallic,occlusion,roughness,shadows,tonemap,exaggeration", - GMT_TP_STANDARD }, { 0, 'G', "drapegrid|drape", "", "", "", "", GMT_TP_STANDARD }, { 0, 'I', "illumination|shading|intensity", "", "", - "a,d,f,m,n,P", "azimuth,default,pbr,ambient,args,physical", + "a,d,m,n,P", "azimuth,default,ambient,args,physical", GMT_TP_STANDARD }, { 0, 'N', "plane", "", "", diff --git a/test/grdimage/grdimage-l2s.sh b/test/grdimage/grdimage-l2s.sh index b042716f69c..862f8efbd71 100755 --- a/test/grdimage/grdimage-l2s.sh +++ b/test/grdimage/grdimage-l2s.sh @@ -19,8 +19,7 @@ cat << EOF > $a --l2stranstest -M --l2stranstest -N --l2stranstest -Qred -Q+z-999 ---l2stranstest -F315/30+f0.2+i1.8+l1.2+m0.5 -I+f ---l2stranstest -F45/20+o0.2+r0.6+s+t+v2 +--l2stranstest -I+P -I+P315/30 EOF # module-specific longopts @@ -34,7 +33,6 @@ gmt $m $l2s --intensity+azimuth:10+ambient:0+intensity:t1 >> $b gmt $m $l2s --monochrome >> $b gmt $m $l2s --noclip >> $b gmt $m $l2s --alphacolor=red --alphacolor+gridvalue:-999 >> $b -gmt $m $l2s --pbr=315/30+fill:0.2+ior:1.8+light:1.2+metallic:0.5 --intensity+pbr >> $b -gmt $m $l2s --physical=45/20+occlusion:0.2+roughness:0.6+shadows+tonemap+exaggeration:2 >> $b +gmt $m $l2s --intensity+physical --intensity+physical:315/30 >> $b diff $a $b --strip-trailing-cr > fail diff --git a/test/grdimage/pbr.sh b/test/grdimage/pbr.sh index 6bcce4a52f5..6876d375d88 100644 --- a/test/grdimage/pbr.sh +++ b/test/grdimage/pbr.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash -# Test physically based shading (-F): cast shadows, ambient occlusion and tone mapping on the -# left, a rough metal with a vertical exaggeration of 3 on the right, and the -I+f defaults below +# Test physically based shading (-I+P): cast shadows, ambient occlusion and tone mapping on the +# left, a rough metal with a vertical exaggeration of 3 on the right, and the -I+P defaults below ps=pbr.ps gmt grdmath -R-15/15/-15/15 -I0.1 X Y HYPOT DUP 2 MUL PI MUL 8 DIV COS EXCH NEG 10 DIV EXP MUL = somb.nc gmt makecpt -Cjet -T-0.5/1 > pal.cpt -gmt grdimage somb.nc -Cpal.cpt -F225/30+o+s+t -JX8c -Baf -BWSne -P -K -Y14c > $ps -gmt grdimage somb.nc -Cpal.cpt -F225/30+m0.8+r0.6+v3 -J -Baf -BwSne -O -K -X9c >> $ps -gmt grdimage somb.nc -Cpal.cpt -I+f -J -Baf -BWSne -O -X-9c -Y-10c >> $ps +gmt grdimage somb.nc -Cpal.cpt -I+P225/30+o+s+t -JX8c -Baf -BWSne -P -K -Y14c > $ps +gmt grdimage somb.nc -Cpal.cpt -I+P225/30+m0.8+r0.6+v3 -J -Baf -BwSne -O -K -X9c >> $ps +gmt grdimage somb.nc -Cpal.cpt -I+P -J -Baf -BWSne -O -X-9c -Y-10c >> $ps rm -f somb.nc pal.cpt diff --git a/test/grdview/grdview-l2s.sh b/test/grdview/grdview-l2s.sh index d34e153f9d9..c71171dc073 100755 --- a/test/grdview/grdview-l2s.sh +++ b/test/grdview/grdview-l2s.sh @@ -10,8 +10,7 @@ rm -f $a $b ; touch $b cat << EOF > $a --l2stranstest -Cfile.cpt -Cfile.cpt ---l2stranstest -F315/30+f0.2+i1.8+l1.2+m0.5 -I+f ---l2stranstest -F45/20+o0.2+r0.6+s+t+v2 +--l2stranstest -I+P -I+P315/30 --l2stranstest -Gthis/file.grd -Gthat/file.grd --l2stranstest -Ilighting/file+a --l2stranstest -I+d -I6+m6+nsome.args @@ -24,8 +23,7 @@ EOF # module-specific longopts gmt $m $l2s --cpt=file.cpt --cmap=file.cpt >> $b -gmt $m $l2s --pbr=315/30+fill:0.2+ior:1.8+light:1.2+metallic:0.5 --intensity+pbr >> $b -gmt $m $l2s --physical=45/20+occlusion:0.2+roughness:0.6+shadows+tonemap+exaggeration:2 >> $b +gmt $m $l2s --intensity+physical --intensity+physical:315/30 >> $b gmt $m $l2s --drapegrid=this/file.grd --drape=that/file.grd >> $b gmt $m $l2s --illumination=lighting/file+azimuth >> $b gmt $m $l2s --shading+default --intensity=6+ambient:6+args:some.args >> $b diff --git a/test/grdview/pbr.sh b/test/grdview/pbr.sh index 7ca89c42622..c29f9cebd16 100644 --- a/test/grdview/pbr.sh +++ b/test/grdview/pbr.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Test physically based shading (-F) of a 3-D surface: cast shadows, ambient occlusion and tone +# Test physically based shading (-I+P) of a 3-D surface: cast shadows, ambient occlusion and tone # mapping with a vertical exaggeration of 3, draped over the relief it was computed from ps=pbr.ps @@ -7,6 +7,6 @@ ps=pbr.ps gmt grdmath -R-15/15/-15/15 -I0.1 X Y HYPOT DUP 2 MUL PI MUL 8 DIV COS EXCH NEG 10 DIV EXP MUL = somb.nc gmt makecpt -Cjet -T-0.5/1 > pal.cpt -gmt grdview somb.nc -Cpal.cpt -F225/30+o+s+t+v3 -JX12c -JZ3c -p135/35 -Baf -Bzaf -BWSneZ -P -Xc > $ps +gmt grdview somb.nc -Cpal.cpt -I+P225/30+o+s+t+v3 -JX12c -JZ3c -p135/35 -Baf -Bzaf -BWSneZ -P -Xc > $ps rm -f somb.nc pal.cpt