Prologue
In this article I narrate a process, I have been exploring lately, for designing a 3D model in ArcGIS Pro. My goal is to avoid as much as I can interacting with other software – even ones dedicated to 3D modeling – and to exploit the possibilities that Pro can offer.
This short journey has three distinct phases: preparation, assembling and illumination. We will have to prepare certain layers using some Layout possibilities, as well as a couple of geoprocessing tools, and then proceeding to designing a Local Scene.
I am not a 3D expert, nor a Blender or other similar software skillful user. I am just exploring new ways and share them with you with the anticipation of some positive feedback and suggestions for improvement.
If you want to follow along with my project (or skip the entire article and take a shortcut), I have uploaded it as a Map Package on my ArcGIS Online account. You can download it from here and use it under a CC BY-NC-SA 4.0 license.
Preparation
To get started I have created an ArcGIS Pro project, I have loaded the World Imagery as basemap, I have defined a coordinate system at the map properties and I have created a Feature Dataset in the default geodatabase of my project, where I will store the layers I will create, as shown in Picture 1.
To have it all working properly, I have, from the early beginning, to define the area of interest. Perhaps this is the most important step, because this establishes the frame within which everything else is about to happen.
To do this I create a new Layout, I insert a Map Frame and I focus on the area of interest, as shown in Picture 2.
Four things are very important here:
- the shape of the Map Frame, which in my example here is just a square,
- the size of the Map Frame, which in this simple shape it is controlled by the size of the square’s sides,
- the scale of the map, which should be wisely combined with the size of the Map Frame, but also with the resolution of the DEM which will be used as the Ground elevation layer,
- the extent of the map, defined by the coordinates of the Map Frame’s four corners.
For the purposes of this article, I have created a square Map Frame whose sides have dimensions 20cm by 20cm, the map has a scale of 1:10,000 and I have defined the map extent by panning to my area of interest. The Layout view will help me to produce some layers.
Firstly, I need to create a new polyline feature layer which will follow the edges of the Map Frame shape. Since the Map Frame is a square, I have to copy the coordinates of its extent, which will help me create the polyline feature layer.
To do this, I right click on the Map Frame, at the Contents pane and I select Properties to open its properties at the Element pane (Picture 2).
At the Display Options of the Element pane I click on the Extent button to open the Map Frame Extent pop up window. From there, I can copy the Top, Left, Right and Bottom coordinates of the Map Frame extent (Picture 2).
I have to save these coordinates to use them later, so I open a text editor or a notepad (I use Visual Studio Code) and I paste them there, as shown in Picture 3.
As you can see, I have pasted the coordinates with their correspondent name in the text editor, but I have also transformed them in an array, which I will use at the following step.
Before proceeding, I will export the Layout view in TIFF format, as shown in Picture 4. This will export the World Imagery as a local GeoTIFF file, clipped in the area of interest, which I will use later as the texture of my 3D model. I give to it the name Aerial.tif.
If I don’t want the exported image to have the Service Layer Credits, down below, I can always remove them, following John Nelson’s instructions!
It is also important to save the current extent as a Bookmark, so it will be easy to come back here, if I accidentally move the map (Picture 4).
Now, back to the Map view!
As already said, I need to create a polyline feature layer that will match the shape of the Map Frame extent. The easy way to do this is by following the editing process, where I have to create an empty polyline feature class in the project’s database, then edit and create new features by using the coordinates, saved at the previous steps. The Absolute X,Y,Z will help me to precisely input these coordinates, as I create the feature.
However, I prefer to use a simple ArcPy script at the Python window, which will create the desired polyline feature layer for me, as shown in Picture 5, below.
The script, shown in the text editor and at the Python window of Picture 5, goes like this:
import arcpy
from arcpy import env
env.workspace = "C:/Dropbox/SG_BLOG/3D_Model/3D_Model.gdb/Data_3857"
extent = [
-1609813.6168723013,
-19007708.81018684,
-19005708.81018684,
-1611813.6168723013
]
Top = extent[0]
Left = extent[1]
Right = extent[2]
Bottom = extent[3]
pnt1 = arcpy.Point(Left,Bottom)
pnt2 = arcpy.Point(Left,Top)
pnt3 = arcpy.Point(Right,Top)
pnt4 = arcpy.Point(Right,Bottom)
array = arcpy.Array()
array.add(pnt1)
array.add(pnt2)
array.add(pnt3)
array.add(pnt4)
array.add(pnt1)
shape = arcpy.Polyline(array)
arcpy.CopyFeatures_management(shape,"extent_polyline")
If you want to use my ArcPy script, just replace the path at the third line, with the path on your computer, where you want the produced feature layer to be saved and also replace its name at the last line (in case you don’t want to name it “extent_polyline”, as I do). And of course, replace the extent coordinates at the extent array with your own.
If you are not familiar with ArcPy, just follow the classic editing process with the Absolute X,Y,Z mentioned above.
In any case, I have to end up with a square polyline feature layer whose corners have the coordinates of the Map Frame extent, as shown in Picture 6.
I have named the polyline feature layer “extent_polyline” and I have symbolized it with a thick red stroke, so it is clearly visible. This will serve certain purposes in the following steps, but firstly it will be used to create the Ground elevation layer.
Next, I need some elevation data!
Provided I have downloaded the DEM tiles that correspond to my area of interest, I add them to the Map, as shown in Picture 7.
Raster resolution here is key. For the purpose of this article I have downloaded tiles with 1m cell size, which is very suitable for the scale I am about to work (1:10,000). I have used the same elevation data source with those I had used in my Shadowplay article.
If I had a medium scale to work with, say 1:50,000 or 1:100,000, then 1m cell size would be too much, so I should resample or get another source with larger cell size.
Now I have to mosaic these tiles and for this I use the Mosaic Rasters function from the Raster Functions pane, as shown in Picture 8 below. The Mosaic Rasters function will produce a new raster layer, named “Mosaic Rasters“, out of the four tiles and it will add it at the Contents pane.
Then, I will use the Clip function from the Raster Functions pane to clip the Mosaic Rasters layer within the “extent_polyline” layer – the area of interest.
As shown in Picture 9, I open the Clip function where I select the Mosaic Rasters layer as input at the Raster field, I select Outside for the Clipping Type, I select the “extent_polyline” layer for the Clipping Geometry / Raster field and I click on the Create new layer button. Another new layer is created and added on the map; its name is “Clip_Mosaic Rasters” and it is clipped within the extent of the area of interest.
The Clip_Mosaic Rasters layer will be the Ground elevation layer, which will be used later at the assembling phase, at the Local Scene view.
Having set the Ground layer, it is now time to create the layer with which I will build the sides of my model. To do this I have somehow to densify the vertices of the “extent_polyline” layer, because at the moment it only has four vertices, which are the four corners of its square shape.
The easiest way to do this, is to select the “extent_polyline” layer on the Map view, then at the Edit tab, select the Generalize tool from the Tools group, as shown in Picture 10. The Modify Features pane opens with the Generalize options of the selected feature, where I select Densify as Method and 1m for the value of Distance (Picture 10).
Then I click on the Generalize button, down below, and new vertices every 1m are added along the “extent_polyline” layer. Since this is an Editing tool, I have to click on the Save Edits button on the Edit tab ribbon to make these new vertices permanent.
It is important to note that I added vertices every 1m, because I have an elevation source with 1m cell size. If I had another elevation source with larger resolution then I should have selected the equivalent distance among the vertices. For example, if my elevation source had 5m or 30m cell size, then the distance of the new vertices should be 5m or 30m, respectively.
An alternative way to add vertices is to use the Densify (Editing) geoprocessing tool, provided you have a Standard or Advanced License.
Next, I have somehow to add elevation information on each vertex of the “extent_polyline” layer. This can be done with the Interpolate Shape Geoprocessing tool, available with a 3D Analyst license, or a Spatial Analyst license.
As shown in Picture 11, at the Interpolate Shape tool, I select the Mosaic Rasters at the Input Surface field, I select the “extent_polyline” at the Input Features field and I choose where to save the Output Feature Class, which I name “sides_3d_vector“.
When I click on the Run button, the newly created “sides_3d_vector” feature layer is added on the Contents pane and on the Map. This is a 3D vector feature layer and it is the one that I will use for the sides of my model.
So far, I have not found an alternative way to produce 3D features by interpolating z-values from a surface, without one of the aforementioned licenses.
If you don’t possess any of these, then another way would be to use the Drape (set Z value from raster) tool in QGIS for this step. As a matter of fact, this is how I do it in my production line, since I only possess a Basic ArcGIS Pro license with no extensions.
The final step for the Preparation phase is to clean up the Contents pane. As shown in Picture 12, I have removed all unnecessary layers and I have added the Aerial.tif image, created in a previous step.
In the Contents pane I must end up with:
- The “sides_3d_vector” 3D feature layer, which will be used to build the sides of the model,
- The “extent_polyline” 2D feature layer, which will be used to create the base of the model,
- The Aerial.tif image layer, which will be used as the texture of the model,
- The Clip_Mosaic Rasters layer, which will be used as the Ground elevation layer of the model.
Assembling
The beginning of the assembling phase is as easy as converting the Map to a Local Scene. As shown in Picture 12 above, at the View tab I click on the Convert button at the View group and from the dropdown list I select the To Local Scene option.
The Map is being converted to a Local Scene, as shown in Picture 13, below. Note that by default the Scene has the WorldElevation3D/Terrain3D as the Ground layer, and Pro automatically recognizes and separates the 3D layers from the 2D layers in the Contents pane.
Since I have produced my own elevation layer, I remove the WorldElevation3D/Terrain3D layer from the Elevation Surfaces group, at the Contents pane, where I add the Clip_Mosaic Rasters layer (by drag and drop), as shown in the Picture 14, below. Now this is being used as the Ground elevation layer.
Next, I select the Ground group at the Elevation Surfaces at the Contents pane and the Elevation Surface Layer contextual tab appears. There, I change the Vertical Exaggeration to 2, I change the Surface Color to No Color and I select the Shade Relative to Light Position by checking the checkbox before it (Picture 14).
I also use the Explore tool to Navigate the Scene and change its viewing angle, so I will no longer see it from a top view (Picture 14).
Now it’s time to build the sides!
At the 3D Layers group, at the Contents pane, I select the “sides_3d_vector” and I right-click to open its Layer Properties window, as shown in Picture 15, below. At the layer properties window I select the Elevation tab, where I select the At an absolute height from the dropdown list at the Features are option, I select the Geometry z-values radio button at the Additional feature elevation using option and I write 2 for the Vertical Exaggeration (Picture 15).
It is important to pay attention to two things here. One, the “sides_3d_vector” has z-values, meaning the elevation information on each vertex, because it is a 3D feature layer. We created it in a previous step with the Interpolate Shape geoprocessing tool. Two, the Vertical Exaggeration must be exactly the same with the one set at the Elevation Surface Layer (see Picture 14). In my example here, I have set both to 2.
Now, I close the Layer Properties window and having selected the “sides_3d_vector” layer I go to the Feature Layer contextual tab, where at the Extrusion group I click on the Type button to open the Feature Extrusion Type list, as shown in Picture 16, below. From the list I select the Min Height option, which will add extrusion to each feature’s minimum height. As you can see in Picture 16, the extrusion is already building the sides of the model!
After selecting the Min Height option as the Feature Extrusion Type for the “sides_3d_vector” layer, I can also add additional extrusion by clicking on the expression button to open the Expression Builder window, as shown in Picture 17, below. This will control how tall I want the sides to be. For this example, I type -50, which will extrude the sides 50m below the base height of the model (which actually is the minimum z-value of the “sides_3d_vector” layer).
The final result is shown in Picture 18. If I want taller sides, I can open again the Expression Builder and type another number, say -100 or -200. How tall I want my sides to be depends on the size of the model and the scale.
Apart from the sides, I also want to have a base for the model. With a suitable symbology, the “extent_polyline” layer can serve as the base. So, firstly, I drag it from the 2D Layers group to the 3D Layers group, at the Contents pane, as shown in Picture 19.
Then, I right click on the “extent_polyline” layer to open its Layer Properties window. At the layer properties window I select the Elevation tab, where I select the At an absolute height from the dropdown list at the Features are option and I select the A field radio button at the Additional feature elevation using option. I click on the expression button to open the Expression Builder window where I type how much its absolute height will be. Even though some trial-and-error would work I have found that a simple formula can give the desired result. This formula is:
Elevation Surface Layer’s minimum height + (“sides_3d_vector” layer’s additional extrusion / 2)
To find the Elevation Surface Layer’s minimum height I can go back to the Map view and see the minimum value of the Clip_Mosaic Rasters layer, which in my example is 20.5389 (see Picture 19). The additional extrusion of the “sides_3d_vector” layer is -50 (defined a couple of steps above), so the half of it is -50 / 2 = -25.
So the absolute height of the “extent_polyline” layer will be the result of the simple math equation 20.5389 – 25, as seen in Picture 19. What this actually does is pushing the “extent_polyline” layer exactly at the bottom of the model, to serve as its base.
Now I have to change its symbology to make it look like a base!
So, I open the Symbology properties and at the Structure tab I delete the default Stroke symbol layer and I add a Fill symbol layer with two symbol effects, the Enclosing polygon effect and the Offset effect, as shown in Picture 20.
The Enclosing polygon effect will create a dynamic polygon from the spatial extent of the “extent_polyline” layer and the Offset effect will offset this dynamic polygon in a specified distance. Order of the symbol effects is important. I need firstly to define the Enclosing polygon and then its offset.
On the Layers tab of the Symbology pane, I select the Close path as Method for the Enclosing polygon effect and then I type 10 for Offset at the Offset effect with Miter as Method, as shown in Picture 21, below. I click apply, et voilà! The model’s base is ready!
It would be better to give some height to the base. To do this, I will have to extrude it to a specified height, so I select the “extent_polyline” layer at the Content pane and from the Extrusion group at the Feature Layer contextual tab I click on the Type button to open the Feature Extrusion Type list. From the list I select the Absolute Height option, as shown in Picture 22.
Then, I click on the expression button to open the Expression Builder, where I type -25, as shown in picture 23, below. This means that the base will have a thickness of 25m. If I want a thicker base, then I should type a smaller number, like -50 or -100. The thickness of the base is a matter of taste, but it should match the height of the sides and the size of the model.
Final touch is to change a few colors to produce a more aesthetically pleasing composition. I right click on the Map_3D at the Contents pane, to open the Map Properties, where at the General tab I change the Scene’s Background color to #777777, as shown in Picture 24.
Then at the Symbology pane of the “sides_3d_vector” layer, at the Appearance group, I type #555555 for Color, as seen in Picture 25.
I repeat the same process for the “extent_polyline” layer, where I also type #555555 for Color.
And finally I reached the end of the Assembling phase. I have created the Scene, where I placed the model, composed from the Ground layer and the Aerial image, I built its sides and its base and I adjusted the colors. But to make it look more realistic, I need to also adjust the illumination.
Illumination
Time to light things up! At the Scene group of the View tab I click on the arrow that is next to the Illumination button, as shown in Picture 26, below. From the dropdown list I click on the Illumination Settings button.
The Map Properties window opens with the Illumination tab selected, where I can configure the Illumination Settings of the Scene, as shown in Picture 27.
In the Illumination Settings I check the checkbox before the Display shadows in 3D option at the Shadow group and I type 30 for the Light contribution at the Illumination group (Picture 27).
The enabled Display shadows in 3D option will create cast shadows, not only on the model but also around the model. Pay attention to the shadow that falls on the base of the model. Keep in mind that without a base, there will be no shadow around the model.
The light contribution value is also important. The lower the value the more dramatic the Scene will be but also more dark and vice versa. So for this, a bit of trial-and-error will indicate the suitable value.
Next, I have a number of different options at the Illumination defined by group, where I choose to light my Scene with the Absolute sun position option defined with an Azimuth and an Altitude (Picture 27).
As shown in Picture 27, I have chosen a value of 45 for the Azimuth, meaning that light comes from northeast, and a value of 30 for the Altitude, meaning that light comes from a low angle, so shadows will be longer. The final result is shown in Picture 28, below.
I have found that it is better to adhere to the light conditions of the aerial imagery, especially the sun direction (Azimuth). If I go back to Picture 4 and take a thorough look at the Aerial.tif image (derived from the World Imagery), I can understand that the time of its capture, the sun most probably was coming from northeast. So, to make the model look realistic, it is a good idea to define the illumination Azimuth in the same angle.
If I use satellite captured imagery, like Landsat or Sentinel, I can know exactly the capture date and time. So in these cases, I can go to the SunCalc app and take the Azimuth and Altitude values with great precision.
Final touch is to adjust the appearance of the Aerial.tif image. As shown in Picture 29, I open the Symbology pane, where I select Minimum Maximum for the Stretch type and then I adjust the histograms for its Red, Green and Blue bands, until I make it brighter and warmer.
Having selected the Aerial.tif image, at the Raster Layer contextual tab, at the Enhancement group, I can also adjust the Brightness, Contrast and Gamma values (Picture 29).
Finally, I can expand the area of the model’s base to allow more room for the cast shadows and perhaps other elements I might want to add later. To do this, I open the Symbology pane for the “extent_polyline” layer and I change the Offset to a large number. In my example, I have given a value of 500pt, as shown in Picture 30, below.
Depending on the computer’s system and since I have densified the “extent_polyline” layer, a large offset number at the symbol effect may slow down performance. So in that case, it is better to duplicate this layer, but with the minimum number of vertices (which in the case of a square, it is just four, one for every corner). This can be achieved with many different ways, including the reverse process of the Generalize tool, where I go with the Simplify option instead of the Densify one, or by creating a new feature class in the geodatabase and digitizing to create a new feature.
It also very useful to open the Camera properties at the Navigation group of the View tab, as shown in Picture 31, where I can obtain full control of the view of the model, by adjusting the Pitch and Heading and other useful parameters.
When I am happy with how my model looks, the angles, the perspective, the illumination and the aerial image, I go to the Export pane to export it in a convenient format.
Epilogue
Reaching the end of this article, I want to thank you for reading and I really hope you enjoyed it! I am open to feedback and suggestions for improvement on my approach.
You can always download the project as a Map Package from here and use it under a CC BY-NC-SA 4.0 license.
Kindest regards from Crete, Greece!
Spiros