OpenTK教程-2繪製一個三角形(正確的方式)

上一個教程向咱們展現瞭如何在屏幕上畫一個三角形。可是,我說過,那是一種古老的方式,即便它可以正常運行,可是如今這已經不是「正確」的方式。上篇文章中咱們將幾何發送到GPU的方式是所謂的「即時模式」,它很是簡單,可是已經再也不推薦使用。html

在本教程中,咱們將要實現一樣的最終目標,可是咱們將以更復雜的方式來作事情,瘋了麼大哥?git

咱們選擇更麻煩的編寫方式,是爲了更有效率,更快速和可擴展性。github

咱們將像之前的教程同樣開始,我將引用原文幾回,因此若是尚未看過上一篇的話,請抽空看看。c#

Part 1:設置

要開始,咱們須要建立一個新的項目,引用OpenTK和System.Drawing,同上一個教程。將其命名爲OpenTKTutorial2。數組

Part 2:編碼

首先,咱們須要再次作一些基礎工做,就像第一個教程那樣。添加一個名爲「Game」的新類。使它成爲GameWindow的子類(您須要爲OpenTK添加一個using指令才能使用該類)。併發

差很少是這樣:ide

using OpenTK;

namespace OpentkTutorials2
{
    class Game : GameWindow
    {
    }
}

回到Program.cs,添加代碼:函數

namespace OpentkTutorials2
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var game = new Game())
            {
                game.Run(30.0);
            }
        }
    }
}

Onload方法和OnRenderFrame方法參照上一個教程作就好了。編碼

protected override void OnLoad(EventArgs e)
{
         base.OnLoad(e);
         //修改窗口標題
         Title = "Hello OpenTK!";
         //設置背景顏色爲,額,不知道什麼藍(須要添加 OpenTK.Graphics.OpenGL and System.Drawing引用)
         GL.ClearColor(Color.CornflowerBlue);
}
protected override void OnRenderFrame(FrameEventArgs e)
{
         base.OnRenderFrame(e);
 
         GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
         SwapBuffers();
}

好了,從這裏開始,咱們能夠學點新的東西了!spa

咱們首先須要作的是建立咱們的着色器(Shader)。現代OpenGL使用它獲知如何繪製給出的值。咱們將使用兩種着色器:頂點着色器(Vertex Shader)和片斷着色器(Fragment Shader)。 頂點着色器告訴顯卡正在繪製的形狀中的點的信息。片斷着色器決定繪製到屏幕時形狀的每一個像素的顏色。咱們將要使用的代碼很是簡單,可是咱們可使用相似於即時模式的風格操做。OpenGL的着色器以類C語言的腳本語言編寫,稱爲GLSL(DirectX使用稍微不一樣的語言,稱爲HLSL)。

譯者注:有另一篇很是好的文章講GLSL,推薦先閱讀以更深刻了解GLSL:LearnOpenGL CN,該系列教程也很是推薦閱讀。

將一個文本文件添加到您的項目中,名爲「vs.glsl」。 這將存儲咱們的頂點着色器:

#version 330
 
in vec3 vPosition;
in vec3 vColor;
out vec4 color;
uniform mat4 modelview;
 
void
main()
{
    gl_Position = modelview * vec4(vPosition, 1.0);
 
    color = vec4( vColor, 1.0);
}

注意:對於着色器文件,您可能須要告訴IDE將其複製到輸出目錄(設置文件爲始終複製),不然程序將沒法找到它們!

第一行告訴連接器正在使用哪一個版本的GLSL。

「in」行表示每一個頂點具備的不一樣變量。「out」變量被髮送到圖形流水線的下一部分,在其中進行插值,以便跨片斷平滑過渡。咱們一般發送每一個頂點的顏色。 「vec3」類型是指具備三個值的向量,「vec4」是具備四個值的向量。

這裏還有一個「uniform」變量,對於整個被繪製的對象來講,該變量是相同的。 這將有咱們的轉換矩陣,因此咱們能夠一次性改變對象中的頂點。咱們尚未用到它,但咱們很快就會使用它的。

咱們的片斷着色器更簡單。 將如下內容另存爲「fs.glsl」:

#version 330
 
in vec4 color;
out vec4 outputColor;
 
void
main()
{
    outputColor = color;
}

它只是得到上一個着色器輸出的顏色變量(注意它如今是「輸入」的「in」),並將輸出設置爲該顏色。

如今咱們有了這些着色器,接下來咱們須要指示顯卡去使用它們。首先,咱們須要告訴OpenTK建立一個新的程序對象(program)。 它將以可用的形式存儲的這些着色器。

首先,定義程序的ID(它的地址)變量,置於其餘函數以外。咱們在代碼中不存儲程序對象自己,而是存儲一個能夠引用的地址,程序其自己將存儲在顯卡中

int pgmID;

在Game類中建立一個新的函數,稱爲initProgram。在這個函數中,咱們將首先調用GL.CreateProgram()函數,該函數返回一個新程序對象的ID,咱們將它存儲在pgmID中。

void initProgram()
{
    pgmID = GL.CreateProgram();
}

而後咱們須要寫一個加載器來讀取咱們的着色器代碼並添加它們。此函數須要獲取文件名和一些信息,並返回建立的着色器的地址。

它應該看起來像這樣:

void loadShader(String filename,ShaderType type, int program, out int address)
{
    address = GL.CreateShader(type);
    using (StreamReader sr = new StreamReader(filename))
    {
        GL.ShaderSource(address, sr.ReadToEnd());
    }
    GL.CompileShader(address);
    GL.AttachShader(program, address);
    Console.WriteLine(GL.GetShaderInfoLog(address));
}

上面代碼將建立一個新的着色器(使用ShaderType枚舉中的值),爲其加載代碼,編譯,並將其添加到咱們的程序中。它還會在控制檯中將發現的任何錯誤打印出來,當在着色器中發生錯誤時,這是很是好的(若是您使用過期的代碼,它也會警告)。

如今咱們有了這個,咱們來添加咱們的着色器。首先咱們在類上定義兩個變量:

int vsID;
      int fsID;

這些將存儲咱們兩個着色器的地址。 如今,咱們要使用咱們從文件中加載着色器的功能。

將如下代碼添加到initProgram中:

loadShader("vs.glsl", ShaderType.VertexShader, pgmID, out vsID);
    loadShader("fs.glsl", ShaderType.FragmentShader, pgmID, out fsID);

如今,添加了着色器,程序須要連接。像C代碼同樣,代碼首先被編譯,而後被連接,完成從人類可讀的代碼到須要的機器語言的轉變。

而後再添加:

GL.LinkProgram(pgmID);
Console.WriteLine(GL.GetProgramInfoLog(pgmID));

這將連接它,並告訴咱們是否有錯誤。

着色器如今被添加到咱們的程序中,可是咱們須要告訴程序更多的信息才能正常工做。咱們在咱們的頂點着色器上有多個輸入,因此咱們須要告訴它們地址來給出頂點的着色器位置和顏色信息。

將此代碼添加到Game類中:

int attribute_vcol;
      int attribute_vpos;
      int uniform_mview;

咱們在這裏定義三個變量,存儲每一個變量的位置,以供未來引用。日後咱們將須要使用這些值,因此咱們應該保持簡單。要獲取每一個變量的地址,咱們使用GL.GetAttribLocationGL.GetUniformLocation函數。每一個都使用着色器中的程序的ID和變量的名稱。

在initProgram結尾處添加:

attribute_vpos = GL.GetAttribLocation(pgmID, "vPosition");
attribute_vcol = GL.GetAttribLocation(pgmID, "vColor");
uniform_mview = GL.GetUniformLocation(pgmID, "modelview");

if (attribute_vpos == -1 || attribute_vcol == -1 || uniform_mview == -1)
{
    Console.WriteLine("Error binding attributes");
}

上面代碼將存儲咱們須要的值,而且還要作一個簡單的檢查,以確保找到屬性。

譯者注:也能夠不在C#代碼中指定,而在shader代碼中使用layout (location = x)的方式指定。具體用法能夠參見上文中說的

如今咱們的着色器和程序已經創建起來了,可是咱們還須要給他們一些東西繪製。爲此,咱們將使用頂點緩衝區對象(VBO)。 當您使用VBO時,首先須要讓顯卡建立一個,而後綁定到它併發送你的信息。最後,當DrawArrays函數被調用時,緩衝區中的信息將被一次性發送到着色器並繪製到屏幕上。

像着色器的變量同樣,咱們也須要存儲地址以供未來使用:

int vbo_position;
int vbo_color;
int vbo_mview;

建立緩衝區很是簡單。在initProgram中添加:

GL.GenBuffers(1, out vbo_position);
GL.GenBuffers(1, out vbo_color);
GL.GenBuffers(1, out vbo_mview);

這將生成3個單獨的緩衝區並將其地址存儲在咱們的變量中。對於像這樣的多個緩衝區,有一個能夠生成多個緩衝區並將它們存儲在數組中的選項,可是爲了簡單起見,在這裏咱們將它們保留在單獨的int中。

這些緩衝區將須要一些數據。位置和顏色都爲Vector3類型,模型視圖爲Matrix4類型。咱們須要將它們存儲在一個數組中,這樣能夠更有效地將數據發送到緩衝區。

向Game類添加三個變量:

Vector3[] vertdata;
Vector3[] coldata;
Matrix4[] mviewdata;

這個例子中,咱們將在onLoad中設置這些值,並調用initProgram():

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    initProgram();

    vertdata = new Vector3[] { new Vector3(-0.8f, -0.8f, 0f),
        new Vector3( 0.8f, -0.8f, 0f),
        new Vector3( 0f,  0.8f, 0f)};


    coldata = new Vector3[] { new Vector3(1f, 0f, 0f),
        new Vector3( 0f, 0f, 1f),
        new Vector3( 0f,  1f, 0f)};


    mviewdata = new Matrix4[]{
        Matrix4.Identity
    };



    Title = "Hello OpenTK!";
    GL.ClearColor(Color.CornflowerBlue);
    GL.PointSize(5f);
}

數據存儲完畢,咱們就能夠發送到緩衝區了。咱們須要爲OnUpdateFrame函數添加另外一個重載。首先是綁定到緩衝區:

GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_position);

這就告訴OpenTK,若是咱們發送任何數據,咱們將使用該緩衝區。接下來,咱們會發送數據:

GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(vertdata.Length * Vector3.SizeInBytes), vertdata, BufferUsageHint.StaticDraw);

這段代碼告訴咱們,咱們發送的長度爲(vertdata.Length * Vector3.SizeInBytes)的vertdata到緩衝區。最後,咱們須要告訴它使用這個緩衝區(最後一個綁定到)vPosition變量,這將須要3個float值:

GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0);

因此,最後合起來:

GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_position);
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(vertdata.Length * Vector3.SizeInBytes), vertdata, BufferUsageHint.StaticDraw);
            GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0);

            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo_color);
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(coldata.Length * Vector3.SizeInBytes), coldata, BufferUsageHint.StaticDraw);
            GL.VertexAttribPointer(attribute_vcol, 3, VertexAttribPointerType.Float, true, 0, 0);

咱們還須要發送模型視圖矩陣(Model-View Matrix):

GL.UniformMatrix4(uniform_mview, false, ref mviewdata[0]);

最後,咱們要清除緩衝區綁定,並將其設置爲與咱們的着色器一塊兒使用該程序:

GL.UseProgram(pgmID);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

快要大功告成了! 如今咱們將數據、着色器發送到顯卡,可是咱們還須要繪製他們。在咱們的OnRenderFrame函數中,首先咱們須要告訴它使用咱們想要的變量:

GL.EnableVertexAttribArray(attribute_vpos);
            GL.EnableVertexAttribArray(attribute_vcol);

而後咱們告訴它如何繪製它們:

GL.DrawArrays(PrimitiveType.Triangles, 0, 3);

最後是清理工做:

GL.DisableVertexAttribArray(attribute_vpos);
GL.DisableVertexAttribArray(attribute_vcol);
 
GL.Flush();

最終看起來是這樣子:

protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            GL.Viewport(0, 0, Width, Height);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.Enable(EnableCap.DepthTest);<
 
 
            GL.EnableVertexAttribArray(attribute_vpos);
            GL.EnableVertexAttribArray(attribute_vcol);
 
            GL.DrawArrays(BeginMode.Triangles, 0, 3);
 
 
            GL.DisableVertexAttribArray(attribute_vpos);
            GL.DisableVertexAttribArray(attribute_vcol);
 
 
            GL.Flush();
            SwapBuffers();
        }

若是你運行這些代碼,效果是否是很熟悉?


本系列教程翻譯自Neo Kabuto's Blog。已經取得做者受權。

本文原文地址http://neokabuto.blogspot.com/2013/03/opentk-tutorial-2-drawing-triangle.html

原文代碼能夠在github上找到。

相關文章
相關標籤/搜索