How to Optimizing the drawing sequence
蒋践 20070709 MSN:[email protected] [summary] 本文对ArcGlobe/Globecontrol 中的重画方法的优化提出了四种解决方案。对各 自方案的提出背景及其原理进行了简短的解释,同时给出了实例,值得仔细研究! Implementation of custom layers for ArcGlobe/GlobeControl would probably be done for real-time applications. Usually, such applications are required to match a very high standard of performance, whether it is the ability to display thousands of elements that pertain to one data feed or update objects in a very high frequency. Although efficient, caching your symbol inside a display list is not always enough to get the best performance. The following are the major techniques to a display list. optimize the drawing sequence: 1、 Minimizing the computation load inside DrawImmediate 2 、Scaling and aggregating the drawn symbology 3、 Screening out objects outside of the display viewport 4、 Using a global timer to redraw the display 一一、、Minimizing the computation load inside DrawImmediate 一一、、 Tip: 1、 一个对象可能在一个绘制周期中需要重画多次,如果每一次都去计算地心坐标和 方向将会影响效率。 2、 解决:当我们对某一个对象进行更新时,将其地心坐标和方向保存。 In most cases, the layer’s drawing frequency does not match the object’s update rate. An object is usually subjected to update every several drawing cycles. For that reason, calculating the object’s geocentric coordinate and orientation only when the element gets updated significantly decreases the computations that must be done on each drawing cycle.(引 出背景) To draw the object, you will have to cache its calculated geocentric coordinate and orientation so it can be used inside the DrawImmediate method. 为了去画出某个对象,你将首先保存其计算出的地心坐标及其方向,使它能够调 用内部的DrawImmediate 方法。 The following code shows how to minimize the computation load: [C#] public bool AddItem(long zipCode, double lat, double lon) { double X = 0.0, Y = 0.0, Z = 0.0; . . . DataRow r = m_table.Rows.Find(zipCode); if (null != r) //If the record with this ZIP Code already exists. { //Cache the item’s geocentric coordinate to save the calculation inside method DrawImmediate (). globeViewUtil.GeographicToGeocentric(lon, lat, 1000.0, out X, out Y, out Z); //Update the record. r[3] = lat; r[4] = lon; r[5] = X; r[6] = Y; r[7] = Z; lock (m_table) { r.AcceptChanges(); } } . . . } 二二、、Scaling and aggregating symbols 二二、、 Tip: 1、可以通过一定方法计算每一个 symbol 的当前显示 scale、 2、当某一个符号在当前显示小于一定比例的时候,我们将一样简单的符号如点来表示它! Usually, one of the first techniques learned in a geographic information system (GIS) class for drawing spatially enabled data is to use aggregated symbology (as scale ratio becomes small when zooming out可伸缩性符号). 3D drawing is no exception. However, in a 3D environment, the scale constantly varies since there is no reference surface that can calculate a scale accordingly. Therefore, the distance from the globe’s camera of each object determines a reference scale.(相对比例由全球幕布 globe camera的距离来决定) The distance from the object to the camera can be calculated both in geographical units and in geocentric units.(对象到全球幕布的距离在地心坐标和地理坐标下都可以计算) The following shows calculating the scale (magnitude) according to the distance from the camera to the object using geocentric units and taking into consideration the elevation exaggeration: 这个例子 show通过从幕布到对象的距离(地心坐标下)来计算放大比例,同是考虑到高程的 发达! [C#] double dblObsX, dblObsY, dblObsZ, dMagnitude; //获取当前全球幕布 ICamera camera = pGlobeViewer.GlobeDisplay.ActiveViewer.Camera; //Get the camera location in a geocentric coordinate (OpenGL coordsystem). 得到地心坐标系用于OpenGL camera.Observer.QueryCoords(out dblObsX, out dblObsY); dblObsZ = camera.Observer.Z; //QA IGlobeDisplayRendering globeDisplayRendering = IGlobeDisplayRendering)m_globeDisplay; //得到基本的起始放大点 double baseExaggeration = globeDisplayRendering.BaseExaggeration; // double X = 0.0, Y = 0.0, Z = 0.0; X = Convert.ToDouble(rec[5]); Y = Convert.ToDouble(rec[6]); Z = Convert.ToDouble(rec[7]); // m_ipVector3D.SetComponents(dblObsX - X, dblObsY - Y, dblObsZ - Z); // dMagnitude = m_ipVector3D.Magnitude; // double scale = dblMagnitude * baseExagFactor; //当得到的比例比较小时,可以用一个符号表示,只有到达一定比例是才在幕布上 draw一个 full symbol。 Setting the scale threshold, which determines whether to draw a full symbol or an aggregated symbol is up to the developer, usually according to a requirement defined as a geographical distance. In many cases, setting the threshold is done empirically to get the best balance between the aggregated symbols and the full symbol that looks best and gives the best performance. The aggregated symbol can be any type of symbol, cached as a display list or a simple OpenGL geometry. There is no limit to the number of aggregated symbol levels that can be set to an object. The following code shows how to set the scale threshold: [C#] //If far away from the object, draw it as a simple OpenGL point. if(scale > 2.5) { GL.glPointSize(5.0f); GL.glColor3ub(0, 255, 0); GL.glBegin(GL.GL_POINTS); GL.glVertex3f(Convert.ToSingle(X), Convert.ToSingle(Y), Convert.ToSingle(Z)); GL.glEnd(); } else //When close enough, draw the full symbol. { GL.glPushMatrix(); { //Translate and orient the object into place. TranslateAndRotate(X,Y, Z, wksSymbolOrientation, dSymbolAngle); //Scale the object. GL.glScaled(dObjScale, dObjScale, dObjScale); //Draw the object. GL.glCallList(m_intMainSymbolDisplayList); } GL.glPopMatrix(); } 三三。。Screening out objects outside the display viewport 三三。。 Tip: 1、可以计算某一个对象是否在我们的展示幕布中、 2、当不在幕布中时,我们就不让他显示 Drawing dynamic objects usually involves a large amount of computation. In addition, drawing a complex full-resolution model that contains a large amount of triangles can take a considerable amount of resources. The camera’s field of view is limited to a certain degree. Therefore, many objects get drawn despite the fact they are not visible inside the viewport. Although OpenGL will not draw these objects, the computation required to draw an object might be quite expensive. For that reason, screening out objects outside the viewport is a good practice to preserve resources and make your code more efficient.(为什么要这样做?) To screen out objects, you need to convert the object’s coordinate into the window’s coordinate and test whether it is inside the viewport. There are two ways to convert a coordinate into a window coordinate. You can use the IGlobeViewUtil interface to convert a coordinate from each of the other coordinate systems used by globe (geographic or geocentric) into the window system or you can use OpenGL to convert from a geocentric coordinate into a window coordinate.(怎么 去计算得到是否在当前可是窗体中) 通过 OpenGL 的方法从地理坐标到地心坐标的转换需要你去获取如下 feed:OpenGL 的投影 matrix,模型 matrix ,及其视角matrix,这些feed 只能在 DrawImmediate方 法 (globe’s custom layer 情况下)及其 IGlobeDisplayEvents::BeforeDraw and IGlobeDisplayEvents::AfterDraw方法下才能获取。虽然,这种方法比较快速同时精度比 较高并且可以用来估计外部的快速展示面,但是当你转变到选择模式下时,投影 matrix将改 变同时结果将出现计算错误。 所以:建议尽可能的使用model of ArcObjects和 OpenGL 模式相结合。 使用方法:当 OpenGL 处于选择模式下时,我们就使用 IGlobeViewUtil 接口来把结果转 变到窗体坐标。其他情况下通通用 OpenGL的模式。 Calling OpenGL to project a coordinate from geocentric into a window coordinate system requires you to get from OpenGL the projection matrix, model matrix, and the viewport matrix. This can only be done inside the DrawImmediate method (in the case of a globe’s custom layer) or inside IGlobeDisplayEvents::BeforeDraw and IGlobeDisplayEvents::AfterDraw. Although, using OpenGL to convert from geocentric coordinates into window coordinates is very fast and accurate (it also allows you to screen out objects outside the clipping planes), when switched into selection mode, the projection matrix changes and results in erroneous calculations. For that reason, it is possible to use a mixed model of ArcObjects together with OpenGL to test if an object is inside the viewport. When OpenGL is in selection mode, use IGlobeViewUtil to convert into a window coordinate and the rest of the time, use OpenGL. The following code shows testing an object inside the viewport: [C#] private bool InsideViewport(double x, double y, double z, double clipNear, uint mode) { bool inside = true; //In selection mode, the projection matrix is changed. //Therefore, use the GlobeViewUtil because calling gluProject gives unexpected results. if (GL.GL_SELECT == mode) { int winX, winY; m_globeViewUtil.GeocentricToWindow(x, y, z, out winX, out winY); inside = (winX >= m_viewport[0] && winX <= m_viewport[2]) && (winY >= m_viewport[1] && winY <= m_viewport[3]); } else { //Use gluProject to convert into the window’s coordinate. unsafe { double winx, winy, winz; GLU.gluProject(x, y, z, m_modelViewMatrix, m_projMatrix, m_viewport, &winx, &winy, &winz); inside = (winx >= m_viewport[0] && winx <= m_viewport[2]) && (winy >= m_viewport[1] && winy <= m_viewport[3] && (winz >= clipNear && winz <= 1.0)); } } return inside; } The following code filters out objects outside of the viewport: [C#] //Get the OpenGL matrices. GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX, m_modelViewMatrix); GL.glGetIntegerv(GL.GL_VIEWPORT, m_viewport); GL.glGetDoublev(GL.GL_PROJECTION_MATRIX, m_projMatrix); //Get the OpenGL rendering mode. uint mode; unsafe { int m; GL.glGetIntegerv(GL.GL_RENDER_MODE, &m); mode = (uint)m; } //Get the globe’s near clipping plane. The ClipNear value is required for the viewport filtering (since we don’t want to draw an item beyond the clipping planes). IGlobeAdvancedOptions advOpt = m_globeDisplay.AdvancedOptions; double clipNear = advOpt.ClipNear; //Ensure the object is within the viewport. if (!InsideViewport(X, Y, Z, clipNear, mode)) continue; //Continue with the drawings here. . . . 四四、、Using a single timer to redraw the display 四四、、 Tip: 1、动态效果的制作方法。使用 timer来控制 2、及其需要注意的问题:所以的 customer layer 公用一个timer Since the data managed by a custom layer or in custom drawings is usually dynamic, you need to constantly redraw the globe display to reflect changes to that data. The redraw frequency should be determined by the amount of time it takes each object to update, as well as the amount of objects that you have to address in the application.(引出问题,需要动态效果) Since Window’s operating system cannot support a true real-time application, it is usually acceptable to have a delay of a few milliseconds between the time that an object gets updated and the time that it gets drawn in the globe display. Therefore, having a timer in your application that constantly redraws the display is usually the best solution when your data is dynamic. (解决方法:使用 timer) The timer's elapsed event 通过进程池来引发,所以没有在主线程下执行。 The timer's elapsed event is raised on a ThreadPool’s thread. This means it is executed on another thread that is not the main thread. The solution is to treat an ArcObjects component like a user interface (UI) control by using the Invoke method to delegate the call to the main thread (where the ArcObjects component was created) and prevent making cross apartment calls.(原理) To support the Invoke method, your class needs to implement the ISynchronizeInvoke .NET interface. Alternatively, your class can inherit from the System.Windows.Forms.Control .NET interface. This way, your class automatically supports the Invoke method.(怎么做) The following shows a class inheriting the System.Windows.Forms.Control interface: [C#] //Class definition. public abstract class GlobeCustomLayerBase : Control, ILayer, IGeoDataset, . . . //Class constructor. public GlobeCustomLayerBase() { //Call CreateControl to create the handle. this.CreateControl(); } . . . The following code shows using the timer to redraw the GlobeDisplay: [C#] //Redraw delegate invokes the timer's event handler on the main thread. private delegate void RedrawEventHandler(); //Set the layer’s redraw timer. private System.Timers.Timer m_redrawTimer = null; //Initialize the redraw timer. m_redrawTimer = new System.Timers.Timer(200); m_redrawTimer.Enabled = false; //Wire the timer’s elapsed event handler. m_redrawTimer.Elapsed += new ElapsedEventHandler(OnRedrawUpdateTimer); //Redraw the timer’s elapsed event handler. private void OnRedrawUpdateTimer(object sender, ElapsedEventArgs e) { //Since this is the timer’s event handler, it gets executed on a different thread than the main one. Therefore, the Invoke call is required to force the call on the main thread and prevent cross apartment calls. if(m_bTimerIsRunning) base.Invoke(new RedrawEventHandler(OnRedrawEvent)); } //This method invoked by the timer’s elapsed event handler and gets executed on the main thread. void OnRedrawEvent() { m_globeDisplay.RefreshViewers(); } 译: 另外一种可选方案是当元素更新时,在一次重新画,弊端:当对象很多是不停的重画 将可能把资源全部耗掉。 Another alternative is to redraw the globe display each time an element gets updated. However, this approach can lead to a nonstop redraw of the display when there are a large number of objects and could consume all of the system resources. 总结:当有很多动态图层时,如果每一个图层都给一个 timer,那么,可以会失去控制,所以, 所有的图层公用一个timer。 When there is more than one dynamic layer, having multiple timers for each layer can result in a constructive interference of the timers, which may lead to excessive uncontrolled redrawing of the globe display. For that reason, the best solution is to implement a common singleton object that serves all layers that require constant drawing. This way, no matter how many dynamic layers get added to the globe, the drawing rate remains constant. The following C++ active template library (ATL) class declaration code shows how the global timer singleton is declared: ((to look for help9.2for (( engine net)) )) 但是没有找到相关的接口但是没有找到相关的接口 Engine9.2 中中 但是没有找到相关的接口但是没有找到相关的接口 中中 The following code to use the global timer class is straightforward: [C#] //Declare the global timer. private IGlobalTimer m_globalTimer = null; public void DrawImmediate(IGlobeViewer pGlobeViewer) { if(!m_bVisible || !m_bValid) return; . . . if(null == m_globalTimer) { m_globalTimer = new GlobalTimerClass(); m_globalTimer.GlobeDisplay = pGlobeViewer.GlobeDisplay; if(m_globalTimer.RedrawInterval > 200) m_globalTimer.RedrawInterval = 200; m_globalTimer.Start(); } . . . Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1763362 |