现在的位置: 首页 > 综合 > 正文

温度场有限容积法程序入门之六:后处理.Contour的绘制.基于Adobe Flash Stage3D技术

2014年02月05日 ⁄ 综合 ⁄ 共 7058字 ⁄ 字号 评论关闭

     如何绘制标量场呢?我们常用诸如商业软件Tecplot,或者基于Python的开源软件包matplotlib中的contour绘制Contour图形(等值线),曾经很好奇如何绘制得到等值图,觉得很神奇,所以就调用Windous的绘图API绘制了等值图,虽然算法实现了,但是觉得不爽,我等工程技术人员还得花时间看你的API函数,笔者认为不应该被这些API所绑架,绘图效率不高,还得考虑重绘(onDraw),不利于向3D扩展。所以基于GPU的绘图才是正道,看WPF的UI绘制几乎都是基于GPU了,这个估计是大势所趋了。

     作为一个Flash爱好者,一直希望Flash能够实现支持原生的3D显示,结果等到号称有3D功能的出来Flash CS4,觉得Adobe不厚道,被忽悠了。不过还好亡羊补牢,终于支持3D了。在官方提供原生3D API之前,有大量基于AS3但非原生的3D引擎,想当年笔者还玩的不亦乐乎,现在却玩不转了。

     显卡是什么,笔者浅薄,觉得显卡也就擅长于画三角形而已(太极端了,如果这样CPU就只有取指令和执行指令的两大功能),对三角形进行着色,渲染,加之诸如灯光、迷雾之类的效果。其实三角形是所有图形的基础,我们看到的大千世界也可以近视为若干三角形无缝拼接而成(貌似在画有限元的三角元网格,其实差不多),而每个三角形的顶点颜色各异,三角形内部的颜色是顶点颜色的线性插值(眼熟?和有限元中形函数似的,的确);也可以看作是将眼前的景象拍照,把照片遮挡在眼前看,这是GPU显示的另一种工作方式:贴图,这里不谈。所以我们如何操纵GPU绘制等值图呢?将离散区域分为有限个无缝连接的小三角形,然后用GPU绘制每个三角形即可得到整个域的等值图。下面给予一段网上(http://www.adobe.com/devnet/flashplayer/articles/hello-triangle.html)抄袭来的顶点着色器(Vertex
Shader)的Demo,用GPU绘制一个三角形。

package
{
	import com.adobe.utils.AGALMiniAssembler;
	
	import flash.display.Sprite;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.events.Event;
	import flash.geom.Matrix3D;
	import flash.geom.Rectangle;
	import flash.geom.Vector3D;
	import flash.utils.getTimer;
	
	[SWF(width="800", height="600", frameRate="60", backgroundColor="#FFFFFF")]
	public class HelloTriangleColored extends Sprite
	{
		protected var context3D:Context3D;
		protected var program:Program3D;
		protected var vertexbuffer:VertexBuffer3D;
		protected var indexbuffer:IndexBuffer3D;
		
		public function HelloTriangleColored()
		{			
			stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initMolehill );
			stage.stage3Ds[0].requestContext3D();
			
			addEventListener(Event.ENTER_FRAME, onRender);
			
		}
		
		protected function initMolehill(e:Event):void
		{
			context3D = stage.stage3Ds[0].context3D;			
			context3D.configureBackBuffer(800, 600, 1, true);
			
			var vertices:Vector.<Number> = Vector.<Number>([
				-0.3,-0.3,0, 1, 0, 0, // x, y, z, r, g, b
				-0.3, 0.3, 0, 0, 1, 0,
				0.3, 0.3, 0, 0, 0, 1]);
			
			// Create VertexBuffer3D. 3 vertices, of 6 Numbers each
			vertexbuffer = context3D.createVertexBuffer(3, 6);
			// Upload VertexBuffer3D to GPU. Offset 0, 3 vertices
			vertexbuffer.uploadFromVector(vertices, 0, 3);				
			
			var indices:Vector.<uint> = Vector.<uint>([0, 1, 2]);
			
			// Create IndexBuffer3D. Total of 3 indices. 1 triangle of 3 vertices
			indexbuffer = context3D.createIndexBuffer(3);			
			// Upload IndexBuffer3D to GPU. Offset 0, count 3
			indexbuffer.uploadFromVector (indices, 0, 3);			
			
			var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
			vertexShaderAssembler.assemble( Context3DProgramType.VERTEX,
				"m44 op, va0, vc0\n" + // pos to clipspace
				"mov v0, va1" // copy color
			);			
			
			var fragmentShaderAssembler : AGALMiniAssembler= new AGALMiniAssembler();
			fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT,
				
				"mov oc, v0"
			);
			
			program = context3D.createProgram();
			program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
		}	
		
		
		protected function onRender(e:Event):void
		{
			if ( !context3D ) 
				return;
			
			context3D.clear ( 1, 1, 1, 1 );
			
			// vertex position to attribute register 0
			context3D.setVertexBufferAt (0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
			// color to attribute register 1
			context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_3);
			// assign shader program
			context3D.setProgram(program);
			
			var m:Matrix3D = new Matrix3D();
			m.appendRotation(getTimer()/40, Vector3D.Z_AXIS);
			context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true);
			
			context3D.drawTriangles(indexbuffer);
			
			context3D.present();			
			
		}
		
	}
}

      看不懂吧,正常,第一次接触Flash的3D编程,看不懂太正常了,等你看了他的入门后句会觉得简单,请搜索Molehill Flash。而且这个程序编译后在浏览器中打开后,Flash Player的运行时错误出现的几率很大,不过也正常,请搜索该错误,很容易排除。

      如此我们可以实现绘制等值图,如下是运行结果。说道这里,本文就应当结束了。

        等等,还缺点什么?Legend,这个漂亮的东西困扰了我好久。2010年秋季,我们去宣化一工业现场给老板创造剩余价值以供榨取,几个老外在现场调试程序,隧围观之,不得不说,人家的用户体验确实到位,使用3D技术显示工业测控值(话说老板公司嚷嚷着也做3D显示,数年过去了,不知道搞定没有),目测非奇技淫巧。其中看到那些厮他们的Legend竟然是几段拼接而成,我恍然大悟,原来Legend是几组颜色过渡而成。下面是我曾经写基于Flex SDK有限元前处理和后处理程序包中的一个算法(后台使用C++写网格剖分和求解器,那时候不曾知道GPU可以显示等值图),函数getGradualColor可以得到一组连续的颜色分布,程序不做解释,可意会,不可言传:

package FEModel.util.visual
{
	public class ColorUtil
	{
		public static function combineRGB(r:uint,g:uint,b:uint):uint
		{
			if(r<0) r=0
			if(r>255) r=255;
			
			if(g<0) g=0
			if(g>255) g=255;
			
			if(b<0) b=0
			if(b>255) b=255;
			
			var RGB:uint=(r<<16)|(g<<8)|b;
			return RGB;
		}
		
		public static function getGradualColor(cnt:uint=10):Vector.<uint>
		{
			var colorList:Vector.<uint>=new Vector.<uint>(cnt+1,true);
			
			for(var i:uint=0;i<cnt+1;i++)
			{
				var ratio:Number=i/cnt;
				if(ratio<=0.25)
					colorList[i]=colorInR1(ratio);
				else if(ratio<=0.5)
					colorList[i]=colorInR2(ratio);
				else if(ratio<=0.75)
					colorList[i]=colorInR3(ratio);
				else
					colorList[i]=colorInR4(ratio);
			}
			return colorList.reverse();
		}
		
		public static function colorInR1(ratio:Number):uint
		{
			ratio/=0.25;
			return combineRGB(255,ratio*255,0);
		}
		
		public static function colorInR2(ratio:Number):uint
		{
			ratio=(ratio-0.25)/0.25;
			return combineRGB((1-ratio)*255,255,0);
		}
		
		public static function colorInR3(ratio:Number):uint
		{
			ratio=(ratio-0.5)/0.25;
			return combineRGB(0,255,ratio*255);
		}
		
		public static function colorInR4(ratio:Number):uint
		{
			ratio=(ratio-0.75)/0.25;
			return combineRGB(0,(1-ratio)*255,255);
		}
	}
}

        下图是,本教程后处理运行的一个截图,将就看吧,恕笔者无能,尚无时间丰富界面,更无法实现程序通用性:

         由于显卡绘图颜色分布和Legend颜色分布并非完全一致一致,当温度梯度很大时,显示效果不好(如何解决?相邻节点插入3个颜色节点,3个足够了,那又为什么是3个呢,原因和Legend绘制原理一样);而当梯度较小时,或者网格密度较密时,效果逐渐变好:

          Legend是我在整个程序中最满意的地方。除了Legend可看外,其他都太丑了,这也是为什么商业软件卖钱的原因。也同时促使我们去思考:

1,当你辛辛苦苦写了一个程序,调试好后,有人上嘴唇碰下嘴唇般轻松的和你要源代码时,你是否会慷慨解囊?

2,当你使用了别人破解的软件,你是否会想到,编写这个软件的程序员有老婆孩子需要养活而感到一丝丝愧疚?

恕笔者人品差,目前这两个问题都做不好。

       最后给出本教程的主程序把 ,用到了Flex Framework,笔者2011年底曾经写了一个基于mx:UIComponent的组件,专门通过Stage3D用于显示温度场:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx"
			   xmlns:PostProcess="Soong.PostProcess.*"
			   minWidth="800" minHeight="600" backgroundAlpha="0.0" creationComplete="iniApp(event)">
	
	<fx:Script>
		<![CDATA[
			import Soong.Color.RGBColor;
			import Soong.Solver.TSolverMgr;
			
			import mx.events.FlexEvent;
			
			protected var solution:TSolverMgr=null;
			protected var updateTimer:Timer=null;
			
			protected var iterating:Boolean=false;
			
			protected function iniApp(event:FlexEvent):void
			{
				updateTimer=new Timer(2*1000);
				updateTimer.addEventListener(TimerEvent.TIMER,updateColor);
				
				solution=new TSolverMgr();
				
				solution.SetDim(15,15,1,1);
				
				solution.SetMaterial();
				
				solution.UpdateHeatExchangeFactor();
			}
			
			protected function updateColor(e:TimerEvent):void
			{
				//solution.ExportSetting();
				
				for(var i:uint=0;i<100;i++)
				{
					solution.Step(1);
				}
				
				//solution.Export2Tecplot();
				//solution.ExportColor("Tcontour.dat",5,1538,1550);
				
				var Tlist:Vector.<Number>=solution.GetCurrentT(true);
				
				var Tmax:Number=solution.Tmax;
				var Tmin:Number=solution.Tmin;
				
				var colorList:Vector.<RGBColor>=RGBColor.GetColorList(Tlist,Tmin,Tmax);
				
				viewer.UpdateTemperature(colorList,20,Tmin,Tmax);
				
				Tlist=null;
				colorList=null;
			}
			
			protected function Debugging(event:MouseEvent):void
			{	
				iterating=!iterating;
				
				if(iterating)
				{
					updateTimer.start();
				}
				else
				{
					updateTimer.stop();
				}
			}
			
		]]>
	</fx:Script>
	<fx:Declarations>
		
	</fx:Declarations>
	<s:Button x="518" y="35" label="Click" click="Debugging(event)"/>
	<PostProcess:BilletViewer id="viewer" x="10" y="10" width="500" height="500" sliceNumber="1" billetThickness="16" billetWidth="16" resolution="0.2"/>
</s:Application>

 

        教程先告一段落,作者要努力工作攒钱了。攒钱娶媳妇儿,生娃,娃工作,娃挣钱,娃娶媳妇,娃再生娃……

抱歉!评论已关闭.