OpenGL——使用幾何着色器繪製平滑(bezier)曲線

效果圖:

頂點數據

    float vertices[]={
        -0.6f, -0.8f,  0.0f,        //首尾填充
        -0.6f, -0.8f,  0.0f,
        -0.4f, -0.3f,  0.0f,
        -0.2f, -0.5f,  0.0f,
         0.0f,  0.4f,  0.0f,
         0.2f,  0.4f,  0.0f,
         0.3f,  0.1f,  0.0f,
         0.3f,  0.1f,  0.0f
    };

關於幾何着色器:

其作用大致是:根據已有頂點數據,構建出新的頂點數據,甚至修改圖元。

關於Bezier曲線,可以看這篇文章:傳送門

這裏我們使用二階Bezier方程

因此我們需要三個點(起點、一個控制點、終點)才能繪製一段Bezier曲線,我們需要考慮兩個問題:

如何選取控制點?

這裏我的策略是,每三個連續點(p0,p1,p2),取兩條線(p0p1,p1p2)的中點作爲起點和終點,中間點(p1)作爲控制點,也就是下圖的效果:

這三個點從哪來?

由於我們的頂點數據本意是用來構建GL_LINE_STRIP的,但GL_LINE_STRIP圖元只會傳遞兩個頂點數據給幾何着色器,而我們繪製bezier曲線需要三個頂點。很顯然GL_LINE_STRIP已經無法滿足我們了,因此我們需要使用GL_TRIANGLE_STRIP。真奇怪,我們現在正使用三角形圖元來畫線。

還沒完,使用GL_TRIANGLE_STRIP有一個細節,假設有一系列頂點分別爲(0,1,2,3,4,5)。使用GL_TRIANGLE_STRIP第一個繪製的三角形頂點是(0,1,2), 重點來了,第二個三角形的頂點是(1,2,3)嗎?

既然我這麼問了,那肯定不是了,其實第二個三角形的頂點是(2,1,3),爲什麼會這樣?

這個順序是爲了保證所有的三角形都是按照相同的方向繪製的,使這個三角形串能夠正確形成表面的一部分。對於某些操作,維持方向是很重要的,比如剔除。

具體規律請看這篇文章:傳送門

爲了避免這一問題,我們需要讓幾何着色器能夠識別出頂點的處理順序。我們需要藉助頂點着色器輸出頂點的序號。

頂點着色器

#version 330 core

layout (location = 0) in vec3 pos;

out VS_OUT{
    int id;
} vs_out;

void main()
{
     gl_Position = vec4(pos,1.0f);
     vs_out.id = gl_VertexID;
}

幾何着色器

#version 330 core

in VS_OUT {
    int id;
}gs_in[];
layout (triangles) in;                      //以三角形爲單位進行擴張,每次從VertexShader往GeomShader傳進三個點進行數據處理
layout (line_strip, max_vertices = 32) out; //將一個點變爲最多32個可連成線條的點 交給FragShader

void creatBezier(){

    const int max_len=20;                   //使用20條線來繪製一段bezier曲線
    
    vec4 p0=mix(gl_in[0].gl_Position,gl_in[1].gl_Position,0.5);     //取前兩個點的中點
    vec4 p1;
    vec4 p2;
    if(gs_in[2].id%2==0){
        p1=gl_in[1].gl_Position;                                    //調整點
        p2=mix(gl_in[1].gl_Position,gl_in[2].gl_Position,0.5);      //取後兩個點的中點
    }
    else{
        p1=gl_in[0].gl_Position;                                    //同上
        p2=mix(gl_in[0].gl_Position,gl_in[2].gl_Position,0.5);
    }
    for(int i=0;i<=max_len;i++){                                    //根據公式生成頂點,並提交
        float t=i*0.05;
        gl_Position=p0*(1.0-t)*(1.0-t)+p1*2*t*(1.0-t)+p2*t*t;
        EmitVertex();
    }
    EndPrimitive();
}


void main(){
    creatBezier();
}

端點bug

使用上方的方式無法繪製兩端的端點連線,比較好的解決方法是,把第一個點再添加到開始,末尾的點再添加到末尾

    float vertices[]={
        -0.6f, -0.8f,  0.0f,
        -0.4f, -0.3f,  0.0f,
        -0.2f, -0.5f,  0.0f,
         0.0f,  0.4f,  0.0f,
         0.2f,  0.4f,  0.0f,
         0.3f,  0.1f,  0.0f,
    };
    float vertices[]={
        -0.6f, -0.8f,  0.0f,        //首尾填充
        -0.6f, -0.8f,  0.0f,
        -0.4f, -0.3f,  0.0f,
        -0.2f, -0.5f,  0.0f,
         0.0f,  0.4f,  0.0f,
         0.2f,  0.4f,  0.0f,
         0.3f,  0.1f,  0.0f,
         0.3f,  0.1f,  0.0f
    };

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章