2010. 6.24.
石立 喬

Visual C++ 2010 Express の易しい使い方(13)

――― 三次元グラフィックの例題としてのFET静特性の表示 ―――

 三次元グラフィックは、いろいろな関数を分かりやすく見せてくれる。ここでは、電子回路でおなじみのFET(電界効果トランジスタ)の静特性を表示することを例題にして、三次元グラフ(物体でも良い)を画面上に二次元で表示し、見る方向をスクロールバーで可変にする方法を述べる。三次元表示方法としては最も簡単な方法で、遠近効果を出したり、隠線処理をしたりするのは省略している。
  FETの静特性は、Vd(ドレイン電圧)、Vg(ゲート電圧)を変数としてId(ドレイン電流)が求まるので、これをグラフで表すと三次元になる。

FET(電界効果トランジスタ)の静特性
 FETの静特性は、比較的簡単なモデルでは、下記の通りである。ただし、ソース電圧を基準として、Vgはゲート電圧、Vdはドレイン電圧、Vthは閾(しきい)値電圧でトランジスタごとにほぼ一定の値、βは相互コンダクタンスで、大きいと多くの電流が流れる。

    
    
    

 上記のように、FETの静特性はId=F(Vg,Vd)の関係があり、Vgをx軸に、Vdをy軸に、Idをz軸にとると、三次元のグラフで表示できる。教科書などに示されているのは、VdをパラメータにしたVg-Id曲線、VgをパラメータにしたVd-Id曲線などであり、全体像の把握が困難である。

三次元グラフをパソコン画面上に二次元座標で表す方法
 図1に示すように、z軸からφの角度を持ち、x軸からθの角度を持つ無限遠の視点(無限遠とすると式が簡単になる)から三次元絶対座標(x,y,z)で示された座標上の一点を見た(投影した)とする。


図1 座標系と天頂角θと視角φの関係


 そのときの二次元表示画面における座標をdisplay.xとdisplay.yとすると、下式の関係で表すことができる。一般に、θはマイナスの値をとる。

    
 ただし、本文でで紹介するプログラムでは、パソコン画面上の二次元座標をPoint構造体のpoint1などで表し、そのx軸成分はpoint1.Xで、y成分はpoint1.Yなどで表している。

スクロールバーの使い方
 「Form1.h[デザイン]」の画面で「ツールボックス」ウインドウを開き、「すべてのWindowsフォーム」欄から「HScrollBar」と「VScrollBar」をクリックしてForm1上に貼り付ける。
 各スクロールバーの設定は、それぞれの「プロパティ」ウインドウで、下表の通りにする。「Size」は「配置」欄に、「RightToLeft」は「表示」欄に、その他は「動作」欄にある。「Maximum」から9(「LargeChange」が10のとき)を引いたものが採りうる実際の最大値となり、これは90°を意味する。

機能 名称 Size RightToLeft Maximum Minimum Large-
Change
Small-
Change
水平スクロールバー hScrollBar1 120,20 Yes 99 0 10   1
垂直スクロールバー vScrollBar1 20,120 (項目なし) 99 0 10   1
 
 それぞれのスクロールバーは、まず目見当で配置とサイズを決め、後に細かく調整する。LargeChangeはスクロールバーの内部をクリックしたときの変化分、SmallChandeは両端の三角矢印をクリックしたときの変化分で、変更しないでデフォルトのまま使用する。
 スクロールバーからの数値の読み取りは、
    theta=-hScrollBar->Value;
    phi=vScrollBar->Value;
スクロールバーの設定は、
    hScrollBar->Value=theta;
    vSCrollBar->Value=phi;
で行なう。

ラベルの設定方法
 各スクロールバー周辺の文字は、ラベルを使用する。文字が上下二段になっているものは、各ラベルの「プロパティ」ウインドウで、「表示」欄の「Text」をクリックし、右に現れる下向きの矢印をクリックして、四角い入力領域に入力すれば良い。

プログラムの概要

1) FETの静特性の計算は視角に関係なく、直接画面に表示する必要がないので、あらかじめ計算し、data[i,j]としてメモリに格納しておく。これにより、視角のパラメータを変換して画面を書き換えるたびに、静特性の計算をする必要がなく、高速化できる。
  具体的には、プログラムの起動時に一度だけ呼び出されるForm1_Load()メソッドで、data[i,j]の計算を行なう。data[i,j]は、Form1.hのグローバル変数として、あらかじめ宣言しておく。 メモリ内の配列をアクセスするので、VgやVdの値を直接使用できない。forループをiとjで回し、Vg=0.1*i; Vd=0.1*j; などとしても良いが、ここでは、VgやVdを意識するためにforループをVgとVdで直接回し、その都度iとjをインクリメントした。Vgは0.0Vから5.0Vまでの0.2Vおきにしたので、iは0から50までの26個、Vdは0.0Vから12.0Vまでの0.4Vおきにしたので、jは0から30までの31個の値をとる。
2) Form1_Paint()では、
  ・theta、phiから、何回も使われるsinθ、cosθ、sinφ、cosφを計算して準備しておく
  ・三次元の座標軸と軸の名称を描く
  ・display3D()メソッドを呼び出し、data[i,j]により、FETの三次元グラフを描く
  ・使用したtheta、phiの値を表示する
  を順次行なう。
3)三次元データをパソコン画面上の二次元座標に変換するdisplay3D()メソッドを記述する。
4)水平スクロールバーを動かしたときのハンドラ-(hScrollBar1_Scroll)でthetaを読み取る。
5)垂直スクロールバーのハンドラ-(vScrollBar_Scroll)でphiを読み取る。


プログラム

    array<float,2>^ data;
    int theta,phi;
    float sint,sinp,cost,cosp;
    static const float PI180=(float)Math::PI/180.0f;

    private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) {

        float BETA=4.0f,VTH=1.5f;
        theta=-45;
        phi=60;
        hScrollBar1->Value=45;
        vScrollBar1->Value=60;

        data=gcnew array<float,2>(26,31);

        int i,j;
        float vd,vg,id;

        i=0;
        for(vg=0.0f;vg<=5.0f;vg+=0.2f){
            j=0;
            for(vd=0.0f;vd<=12.0f;vd+=0.4f){
                if(vg-VTH<=0)       id=0.0f;
                else if(vd>vg-VTH)    id=BETA/2.0f*(vg-VTH)*(vg-VTH);
                else             id=BETA*((vg-VTH)*vd-0.5f*vd*vd);
                data[i,j]=id;
                j++;
            }
            i++;
        }

    }


    private: System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e) {

        int i,j;
        int X0=40,Y0=320;      //グラフの原点
        int X1=30,Y1=20;      //条件の表示位置
        Point point1=Point();
        Point point2=Point();
        Point old_point=Point();

        //三角関数の計算
        sint=Math::Sin(theta*PI180);
        sinp=Math::Sin(phi*PI180);
        cost=Math::Cos(theta*PI180);
        cosp=Math::Cos(phi*PI180);

        Graphics^ g=e->Graphics;

        //座標軸を描く
        point1=display3D(0,0,0);    //原点
        point2=display3D(200,0,0);   //X軸上のX=200
        g->DrawLine(Pens::Black,X0+point1.X,Y0-point1.Y,X0+point2.X,Y0-point2.Y);
        point2=display3D(0,240,0);   //Y軸上のY=240
        g->DrawLine(Pens::Black,X0+point1.X,Y0-point1.Y,X0+point2.X,Y0-point2.Y);
        point2=display3D(0,0,250);   //Z軸上のZ=250
        g->DrawLine(Pens::Black,X0+point1.X,Y0-point1.Y,X0+point2.X,Y0-point2.Y);

        //軸の名称を描く
        point1=display3D(220,0,0);
        g->DrawString("Vg",Font,Brushes::Black,X0+point1.X,Y0-point1.Y);
        point1=display3D(0,260,0);
        g->DrawString("Vd",Font,Brushes::Black,X0+point1.X,Y0-point1.Y);
        point1=display3D(0,0,270);
        g->DrawString("Id",Font,Brushes::Black,X0+point1.X,Y0-point1.Y);

        //赤色でグラフを描く
        for(i=0;i<=25;i++)
            for(j=0;j<=30;j++){
                point1=display3D(8*i,8*j,10*data[i,j]);
                if(j==0) old_point=point1;
                else{
                    g->DrawLine(Pens::Red,X0+old_point.X,Y0-old_point.Y,X0+point1.X,Y0-point1.Y);
                    old_point=point1;
                }
            }

        //緑色でグラフを描く
        for(j=0;j<=30;j++)
            for(i=0;i=<25;i++){
            point1=display3D(8*i,8*j,10*data[i,j]);
            if(i==0) old_point=point1;
            else{
                g->DrawLine(Pens::Green,X0+old_point.X,Y0-old_point.Y,X0+point1.X,Y0-point1.Y);
                old_point=point1;
            }
        }

        //設定条件を表示する
        String^ string1=String::Format("theta={0}, phi={1}",theta,phi);
        g->DrawString(string1,Font,Brushes::Black,X1,Y1);

    }

    private:Point display3D(float x,float y,float z){

        int xx=(int)(-sint*x+cost*y+0.5f);
        int yy=(int)(-cost*cosp*x-sint*cosp*y+sinp*z+0.5f);
        return Point(xx,yy);

    }

    private: System::Void hScrollBar1_Scroll(System::Object^ sender, System::Windows::Forms::ScrollEventArgs^ e) {

        theta=-hScrollBar1->Value;
        Invalidate();

    }

    private: System::Void vScrollBar1_Scroll(System::Object^ sender, System::Windows::Forms::ScrollEventArgs^ e) {

        phi=90-vScrollBar1->Value;
        Invalidate();

    }

実行結果
 プログラムを起動した直後の画面を図2に示す。目盛りが無いので、少しさびしいが、関心のある人は、追加して欲しい。θ(theta)やφ(phi)のデフォルト値は、三次元画面として比較的見やすい値に設定されている。陰線処理をすると、もっと見やすい画面になると思われる。


図2 プログラム実行直後の画面


 スクロールバーを動かしてθ(theta)=-90°(y軸を原点側から見る)、φ(phi)=90°(水平に真横から見る)にすると、図3のように見慣れたVg―Id曲線が得られる。


図3 Vg―Id曲線


 同様に、θ=0°(x軸と同じ方向から見る)、φ=90°(水平に真横から見る)に設定した場合を図4に示す。これも、よく見慣れたVd―Id曲線である。


図4 Vd―Id曲線


付録
  ここでは、三次元の座標をパソコン画面上の二次元座標に変換する方法を、図を用いて解説する。
1)パソコン画面のx座標が display.x=-sinθ・x+cosθ・y となることの説明 (図A参照)
  この場合は、天頂角φを考える必要はない。また、zを考える必要もない。三次元座標(以後、原座標と呼ぶ)上のP点(x、y、z)を原座標のx、y平面に原座標のまま投影したパソコン画面上の位置をD点とする。D点の x 軸方向成分を display.x とする。 -x・sinθは、視線が垂直に交わる投影平面への原座標の x 成分の投影であり、視線より左側にあるので、マイナス符号を付けてある。一方、 y・cosθは、投影平面への原座標のy成分の投影である。D点の投影 display.x を求めるには、x 成分と y 成分の投影を線形的に加算した、display.x=-x・sinθ+y・cosθ を用いれば良い。



2)パソコン画面のy座標が display.y=-cosθ・cosφ・x-sinθ・cosφ・y+sinφ・zとなることの説明 (図B、C参照)
   display.yは、display.y=-cosφ(x・cosθ+y・sinθ)+z・sinφ と書き換えることができる。この式で、括弧の中は、天頂角φやzを考えない、先ほどのD点の縦方向の距離(これをdとする)である。x・cosθは、その時の原座標のx成分の投影であり、y・sinθは y成分の投影である。D点の距離 d は、それらを合計して求まるので、d=x・cosθ+y・sinθ となる。(図B参照)



  実際には、天頂角φを考え、かつzを考慮して投影平面への投影を考えると、原座標の xy 平面に対しては -cosφ を乗じ、z軸に対しては sinφを乗じて加算すればよいので、display.y=-cosθ・d+z・sinφの関係が得られる。(図C参照) d はすでに求めてあるので、それを代入すればよい。



「Visual C++ の勉強部屋」(目次)へ