現代3D圖形編程學習-設置三角形顏色(譯)

本書系列html

現代3D圖形編程學習 http://www.cnblogs.com/grass-and-moon/category/920962.htmlios

設置顏色

這一章會對上一章中繪製的三角形進行顏色的設定。而不是單純的設置一個單一的顏色,這裏咱們會使用兩種方式來對這個三角形設置顏色的變化。這些方法有使用片斷位點來計算顏色,和前一個頂點數據來計算顏色。c++

片斷位置顯示

正如咱們在引言中提到的,片斷的數據中的一部分包括片斷在屏幕上的位置。所以,若是咱們想要在三角形表面上設定變化的顏色,咱們能夠訪問當前片斷着色器內的數據,並用來計算最終的顏色。編程

在這一章中,咱們對紋理的加載,項目對象的建立進行了封裝。具體代碼以下:數組

// TODO add source code
#ifndef _GLSL_PROGRAM_
#define ___GLSL_PROGRAM_

#include <vector>
#include <fstream>
#include <string>
#include <sstream>

class GLSLProgram
{
public:
    bool AddShader(GLenum shaderType, const std::string& shaderFileName)
    {
        GLuint shader = glCreateShader(shaderType);

        std::ifstream ifile(shaderFileName.c__str());
        std::stringstream buffer;
        buffer << ifile.rdbuf();
        std::string contents(buffer.str());
        const char* pContents =  contents.c_str();

        glShaderSource(shader, 1, &pContents, NULL);

        glCompileShader(shader);
        GLint status;
        STATUS, &status);
        if (status == GL_FALSE)
        {
            GLint infoLogLength;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
            GLchar *strInfoLog = new GLchar[infoLogLength+1];
            glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);
            fprintf(stderr, "Compile failure in %d shader: \n%s\n", shaderType, strInfoLog);
            delete[] strInfoLog;
            return false;
        }

        shaderList_.push_back(shader);
        return true;
    }

    bool LinkProgram()
    {
        bool result(true);
        program_ = glCreateProgram();
        for (size_t i=0; i<shaderList_.size(); ++i)
        {
            glAttachShader(program_, shaderList_[i]);
        }

        glLinkProgram(program_);

        GLint status;
        glGetProgramiv(program_, GL_LINK_STATUS, &status);
        if (status == GL__FALSE)
        {
            GLint infoLogLength;
            glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &infoLogLength);
            printf("Log length: %d\n", infoLogLength);
            GLchar *strInfoLog = new GLchar[infoLogLength+1];
            glGetProgramInfoLog(program_, infoLogLength, NULL, strInfoLog);
            fprintf(stderr, "Linker failure: %s\n", strInfoLog);
            delete[] strInfoLog;
            result = false;
        }

        for (size_t i=0; i<shaderList_.size(); i++)
        {
            glDetachShader(program_, shaderList_[i]);
            glDeleteShader(shaderList_[i]);
        }
        return result;
    }

    void UseProgram()
    {
        glUseProgram(program_);
    }

    GLuint GetProgramID()
    {
        return program_;
    }

private:
    std::vector<GLuint> shaderList_;
    GLuint program_;
};

#endif // _GLSL_PROGRAM_

本章中用到的片斷位點的着色器是FragPosition.vertFragPosition.frag。頂點着色器和上一章用到的着色器相同。片斷着色器顏色輸出的部分有了相應的改動。緩存

#version 330
out vec4 output

void main()
{
    float lerpValue = gl_FragCoord.y / 500.0f;
    outputColor = mix(vec4(1.0f,1.0f,1.0f,1.0f), vec4(0.2f,0.2f,0.2f,1.0f), lerpValue);
}

gl_FragCoord是片斷着色器內建的變量類型。它是一個vec3的變量,具備x,y,z三個組成成分。其中x,y表示的是窗口座標,因此這些值的絕對值會隨着窗口分辨率的改變而改變。該窗口座標系的原點位於坐下方。所以三角形靠近下邊的頂點的y值要比上邊點的y值小。函數

上面的片斷着色器中的代碼是根據窗口位置的y值進行設定的。500表示默認的窗口的高度。在不改變窗口大小的狀況下,lerpValue的值在[0,1]範圍內。1表示在窗口的頂點,0表示在窗口的底部。oop

main函數中的第二行用這個lerpValue"對兩個vec4進行線性插值。mix是衆多着色器語言提供的函數之一。與mix相似的函數能夠對vec中的每一個份量進行相同的處理。所以,參數的維度必須是相同的。學習

mix函數執行了現行插值運算。若是第三個參數是0,那麼返回的值就是第一個入參,若是第三個參數是1,那麼返回的就是第二個參數,若是第三個參數是0到1之間的值,那麼返回的值,就在第一個參數和第二個參數之間。ui

Note

第三個參數必須在,[0,1]範圍。可是,GLSL並不會替你進行範圍的檢查。若是這個值不是在[0,1]的範圍內,那麼返回的結果是沒有定義的。在opengl中」沒有定義「的意思是,」返回的值可能不是你想要的「。

咱們能夠獲得以下的結果。

Figrue 2.1 Fragment Position

在這個例子中,越靠近下面的位置更接近白色,越靠近上面的位置,越接近黑色。除了片斷着色器,想對於第一章,並無改變太多的代碼。

頂點屬性

在片斷着色器中使用片斷的位置是很是有用的,可是對於控制三角形的顏色並非最好的選擇。一個更好的方式是,分別設置三角形各個頂點的顏色。這一節中使用了該方式。

咱們想要改變對傳遞給系統的數據。咱們但願事情發生的順序以下:

  1. 對於咱們傳遞給頂點着色器的每個頂點的位置,都傳入特定的顏色值。
  2. 每個頂點着色器的輸出,咱們一樣但願輸出與頂點着色器接受到的相同的顏色。
  3. 在片斷着色器中,咱們將從頂點着色器中輸出的顏色值做爲片斷着色器的輸出顏色。

對與上面的流程,你頗有可能會有一些問題,如第2,3步驟是怎麼工做的。咱們會按照opengl的pipeline進行介紹。

多頂點數組和屬性

爲了完成第一個步驟,咱們須要對咱們的頂點數組進行必定的更改,如今數據看起來是這樣的:

const float vertexData[] = {
    0.0f,    0.5f, 0.0f, 1.0f,
    0.5f, -0.366f, 0.0f, 1.0f,
    -0.5f, -0.366f, 0.0f, 1.0f,
    1.0f,    0.0f, 0.0f, 1.0f,
    0.0f,    1.0f, 0.0f, 1.0f,
    0.0f,    0.0f, 1.0f, 1.0f, 
}

首先,咱們須要從底層理解數組。單個字節是c/c++能夠表示的最小的單位。一個字節有8個位(一個位能夠是0或1),一個字節能夠表示的一個數字的範圍是[0,255]。一個float類型有4個字節的存儲空間。任何一個float都會佔用內存連續的4個字節。

4個字節的組合,在glsl中表示成vec4,確切的是一個4個float值的序列。所以,vec4佔用了16個字節。

vertexData是一個佔用了更多空間的float數組。咱們使用它的方式是將其當成兩個數組。每連續的4個float能夠表示成vec4,前三個vec4表示頂點的位置,後三個vec4表示的是對應頂點位置的顏色。

在內存中,vertexData數組看起來是這個樣子的:

Figure 2.2 Vertex Array Memory Map

圖中上面的兩個表示了基本數據類型的存儲結構,其中每個小格子表示一個字節。最下面一行的圖表顯示了整個數組的存儲結構。每個小格子表示一個float數值。左邊的一半用來表示頂點的位置,右邊的一半用來表示顏色。

這裏咱們擁有的是兩個相互臨近的數組。其中一個數組在內存中的起始位置是&vertexData[0],另外一個數組的起始位置是&vertexData[12]

vertexData中存儲的數據會被存儲到緩存對象中。下面的代碼咱們在以前見過:

Example2.3 Buffer Object Initialization

void InitializeVertexBuffer()
{
    glGenBuffers(1, &vertexBufferObject);
    
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

上面的代碼並無改變,由於數組的大小是經過sizeof直接計算獲得的。所以,當咱們想數組中添加元素時,這個大小會天然而然的增大。

同時,你也許會注意到,三角形的定點和上一節中的代碼不一樣了,上一節中是一個直角等腰三角形,如今的是等邊三角形。

經過上面的代碼,咱們將頂點和顏色都傳到了緩存對象中了。如今咱們須要告訴opengl如何去識別這兩個不一樣類型的數組。

Example 2.4 Rendering the Scene

void dispaly()
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(theProgram);

    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glUseProgram(0);

    glutSwapBuffers();
    glutPostRedisplay();
}

因爲咱們有兩段數據,咱們有兩個頂點屬性。對於每一個頂點屬性,咱們都必須調用glEnableVertexAttribArray來開啓特定的屬性。入參是頂點着色器中經過layout(location)設定的屬性位置。

而後,咱們針對每一個咱們想使用的屬性調用glVertexAttribPointer。兩個該函數的調用惟一的區別是設置的是那個屬性位置,以及最後一個入參。最後一個入參是該屬性的參數開始的數組的偏移量。在這個例子中,這個偏移量是float的字節數4,乘以vec4中有的float個數,乘以頂點的個數3,爲48。

Note

若是你好奇爲何是(void*)48,而不是48,由於這是接口遺留問題。glVertexAttrib"Pointer"之因此以Pointer結尾,由於最後一個參數是指針。或者說在之前至少是這樣的。所以,咱們須要顯式的將48轉換成指針類型。

在這以後,咱們使用glDrawArrays來進行渲染,而後使得數組不起做用,使用接口glDisableVertexAttribArray

關於繪製,更詳細的介紹

在上一章中,咱們跳過了glDrawArrays的詳細介紹。這裏,讓咱們更進一步來看一下。

設定到opengl不一樣的屬性數組是在渲染的時候被讀取的。在咱們的示例中,咱們有兩個數組。每個數組有一個緩存對象和一個這個數組在緩存對象中的偏移量,可是這些數組的大小尚未被設定。若是你看c++的僞代碼,會有以下的形式:

GLbyte *bufferObject = (void*){0.0f,0.5f,0.0f,1.0f,0.5f,-0.366f, ...};
float* positionAttribArray[4] = (float *[4])(&(bufferObject+0));
float *colorAttribArray[4] = (float *[4])(&(bufferObject+48));

positionAttribArray中的每一個元素還有4個成分,這個大小和頂點着色器中的vec4大小相等。glVertexAttribPointer第二個參數是4的緣由正是這個。沒一個成分都是float數據;這也決定了第三個參數是GL_FLOAT。這個數組從bufferObject獲取數據,這個也正是glVertexAttriPointer調用的時候綁定的緩存區對象。偏移量0,正好是glVertexAttribPointer的最後一個參數。

對於colorAttribArray也是相似的說明,除了偏移量是48。

使用上述僞代碼中的表述方式,glDrawArrays能夠表達成:

void glDrawArrays(GLenum type, GLint start, GLint count)
{
    for (GLint element=start; element<start+count; element++)
    {
        VertexSahder(positionAttribArray[element], colorAttribArray[element]);
    }
}

這意味着頂點着色器執行的次數是count,而且對應的數據是從start開始的count個元素。

從緩存對象到頂點着色器的數據流相似以下:

Multiple Vertex Attributes

和原來同樣,每三個頂點能夠組成一個三角形。

頂點着色器

Example 2.7 Multi-input Vertex Shader

#version 330
layout (location=0) in vec4 position;
layout (location=1) in vec4 color;

smooth out vec4 theColor;

void main()
{
    gl_Position = position;
    theColor = color;
}

這裏有三行是新出現的內容。

定義了一個全局的color變量,做爲頂點着色器的輸入。所以,這個着色器,除了輸入了position之外,還將color做爲了輸入參數。position屬性被賦予0的屬性位置。color被賦予1的屬性位置。

上面兩行僅僅將參數輸入到頂點着色器,咱們還須要將特定數據輸出。爲了完成這個目的,咱們首先須要定義一個輸出參數,這個使用out關鍵字實現的,這裏輸出參數theColor的類型是vec4.

smooth是與插值相關的關鍵詞。在後續的內容中,咱們會詳細的討論他。

固然,除了簡單的定義輸出變量以外,咱們還在main函數中對這個輸出變量進行了賦值。在着色器代碼中,咱們一般須要一些複雜的計算纔可以肯定特定位點的顏色,這裏,爲了簡化問題,咱們僅僅使用了輸入做爲了顏色的輸出。用戶定義的輸出變量對於系統而言是沒有嚴格的意義的。僅僅當接下去的着色器使用他們的時候,他們纔是有意義的。

片斷着色器

新的片斷着色器代碼以下:

#version 330
smooth in vec4 theColor;

out vec4 outputColor;

void main()
{
    outputColor = theColor;
}

這個片斷着色器咱們定義了一個輸入變量。這個輸入參數的名字並和上一個着色器中輸出參數的名字保持一致,類型也一樣保持一致,這並非一種巧合,而是特殊的規定。咱們嘗試將頂點着色器中的信息傳遞給片斷着色器。爲了達成這個目的,咱們須要保證着色器輸出階段的名字和類型必須和輸入階段的名字和類型保持一致。而且還須要使用相同的插值限定詞(smooth)。

這裏對於opengl須要將頂點着色器和片斷着色器連接到一起有個比較好的理由,就是若是名字,類型或插值限定詞不一致,那麼opengl在連接的時候就會拋出錯誤。

這裏片斷着色器中,將從頂點着色器中傳入的顏色直接設置給輸出參數。

片斷着色器插值

這裏有個基礎的交互問題。咱們的頂點着色其會被運行三次。這個執行能夠產生3個輸出點和3個輸出顏色。這三個輸出點被用來構建和光柵化三角形,產生一系列的片斷。

片斷着色器的運行次數不是3。對於每一個生成的片斷都會運行。一個三角形可以產生的片斷着色器的數量,依賴於視線的分辨率,以及在當前屏幕上三角形覆蓋了多少的面積。一個變長爲1的等邊三角形的面積大約爲0.433。整個屏幕的面積(x,y in [-1,1])的面積爲4,所以三角形的面積大概佔了屏幕面積的10分之一。若是窗口的大小爲500x500,擁有的像素爲250000個像素,那麼它的十分之一就是25000。所以,咱們的片斷着色器大概會被執行2500次。

這裏有一些不一樣的地方。若是,頂點着色器徹底和片斷着色器對應,那麼頂點着色器僅僅有三個顏色值,其它的24997個顏色從什麼地方來的呢?

這個答案就是片斷插值。

經過使用smooth的插值方式,咱們告訴opengl要對smooth標識的變量進行特殊處理。除了沒一個片斷着色器接收從頂點着色器傳過來的值以外,每一個片斷着色器還接收到了三個輸出值之間的混合值。越接近三個頂點的片斷,受到越多的頂點輸出值的貢獻。

到目前爲止,這樣的插值方式是頂點着色器和片斷着色器之間最爲經常使用的方式。若是你沒有提供插值關鍵詞,那麼smooth就是默認的方式。除了smooth以外還有:noperspective和flat。

若是在這一節的練習中,將smooth改爲noperspective,你並不會看到差異。這並非說明這兩個方式之間沒有區別,而是咱們這個練習過爲簡單,沒法使他們差別化。這兩種方式之間的差異是微妙的,在後續的內容中咱們會有更詳細的說明。

flat插值方式,實際上是不使用插值。他就是說,每一個片斷只是簡單的獲取三個頂點着色器輸出的第一個值,而不是使用插值。這種方式獲得的三角形的顏色是均一的,平面的,所以被稱爲flat

每一個光柵話的三角形都有屬於本身的三個輸出用來計算三角形片斷的值。所以,若是咱們渲染兩個三角形,一個三角形的渲染並不會影響到另外一個三角形的渲染。也就是說,兩個三角形之間是獨立的。

從相同的頂點數據,來繪製多個三角形也是有可能的。在後續的內容中咱們會進行討論。

最後的圖

figure2_4_interpolated_vertex_colors.png

回顧

在這一章中,咱們學到了如下內容:

  • 數據是經過緩存對象和屬性數組傳遞給頂點着色器的。這些數據構成了三角形。
  • 這個gl_FragCoord是glsl內建變量,用來得到當前窗口座標系中該片斷的位置。
  • 在頂點着色器中使用output變量,能夠將輸入參數傳遞給片斷着色器。
  • 基於頂點着色器和片斷着色器設定的插值關鍵詞,數據能夠三角形表面插值的方式從頂點着色器傳遞到片斷着色器。

進一步的學習

  • 在FragPosition例子中,改變viewport。將視口改變成窗口的上半部分,或下半部分。看一下,這將如何影響到三角形。
  • 將FragPosition和VertexColor例子結合到一塊兒,使用插值獲得的顏色和FragPosition肯定的顏色的乘積做爲最後的顏色。

GLSL函數

vec min(vec initial, vec final, float alpha);

對initial,final進行插值操做。alpha爲0時,結果是initial,alpha爲1時,結果爲final。用公式進行表示就是result=initial+(final-initial)*alpha

代碼

通用代碼

爲了更少的更改每一章的代碼,將須要更具每一個章節定製的函數提取到了common.h中,以下:

#ifndef _COMMON_H_
#define ___COMMON_H_

void init();
void display();
void reshape(int w, int h);
void mouseClick(int button, int state, int x, int y);
void mouseMotion(int x, int y);
void keyboard(unsigned char key, int x, int y);
extern int windowWidth;
extern int windowHeight;

#endif

以及通用的main.cpp以下:

#include <GL/glew.h>
#include <GL/glut.h>
#include <iostream>

#include "common.h"


int main(int argc, char*argv[])
{
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(windowWidth, windowHeight);
    glutInitWindowPosition(300,200);
    glutCreateWindow(argv[0]);

    GLenum err = glewInit();
    if (err != GLEW_OK)
    {
        std::cout << "glewInit failed: " << glewGetErrorString(err) << std::endl;
    }

    init();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouseClick);
    glutMotionFunc(mouseMotion);
    glutKeyboardFunc(keyboard);
    glutMainLoop();
}

FragPosition代碼

fragPosition例子的代碼以下:

#include <GL/glew.h>
#include <GL/glut.h>

#include "glsl_program.h"
#include "common.h"

int windowWidth=500;
int windowHeight=500;

GLuint positionBufferObject;
const float vertexPositions[] = {
    0.75f, 0.75f, 0.0f, 1.0f,
    0.75f, -0.75f, 0.0f, 1.0f,
    -0.75f, -0.75f, 0.0f, 1.0f,
};

GLSLProgram program;

void InitializeVertexBuffer()
{
    glGenBuffers(1, &positionBufferObject);

    glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void InitializeProgram()
{
    program.AddShader(GL_VERTEX_SHADER, "./FragPosition.vert");
    program.AddShader(GL_FRAGMENT_SHADER, "./FragPosition.frag");
    program.LinkProgram();
}

void init()
{
    InitializeProgram();
    InitializeVertexBuffer();
}

void display()
{
    glClearColor(0.0f,0.0f,0.0f,0.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    program.UseProgram();

    glBindBuffer(GL__ARRAY_BUFFER, positionBufferObject);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glDisableVertexAttribArray(0);
    ARRAY_BUFFER, 0);
    glUseProgram(0);
    glutSwapBuffers();
}

void reshape(int w, int h)
{
    glViewport(0, 0, w, h);
}

void mouseClick(int button, int state, int x, int y)
{

}

void mouseMotion(int x, int y)
{

}

void keyboard(unsigned char key, int x, int y)
{

}

fragPosition頂點着色器和片斷着色器分別以下:

/// FragPosition.vert
#version 450
layout(location = 0) in vec4 position;
void main()
{
    gl_Position = position;
}

/// FragPosition.frag
#version 450

out vec4 outputColor;

void main()
{
    float lerpValue = gl_FragCoord.y/500.f;
    outputColor = mix(vec4(1.0f,1.0f,1.0f,1.0f), vec4(0.2f,0.2f,0.2f,1.0f), lerpValue);
}

Vertex Color 代碼

vertex_color.cpp代碼

#include <GL/glew.h>
#include <GL/glut.h>

#include "glsl_program.h"
#include "common.h"

int windowWidth=500;
int windowHeight=500;

GLuint positionBufferObject;
const float vertexData[] = {
    0.0f,    0.5f, 0.0f, 1.0f,
    0.5f, -0.366f, 0.0f, 1.0f,
    -0.5f, -0.366f, 0.0f, 1.0f,
    1.0f,    0.0f, 0.0f, 1.0f,
    0.0f,    1.0f, 0.0f, 1.0f,
    0.0f,    0.0f, 1.0f, 1.0f, 
};

GLSLProgram program;

void InitializeVertexBuffer()
{
    glGenBuffers(1, &positionBufferObject);

    glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void InitializeProgram()
{
    program.AddShader(GL_VERTEX_SHADER, "./VertexColor.vert");
    program.AddShader(GL_FRAGMENT_SHADER, "./VertexColor.frag");
    program.LinkProgram();
}

void init()
{
    InitializeProgram();
    InitializeVertexBuffer();
}

void display()
{
    glClearColor(0.0f,0.0f,0.0f,0.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    program.UseProgram();

    glBindBuffer(GL__ARRAY_BUFFER, positionBufferObject);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);
    glVertexAttribPointer(1,4,GL_FLOAT,GL_FALSE,0,(void*)48);
    TRIANGLES, 0, 3);
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glUseProgram(0);
    glutSwapBuffers();
}

void reshape(int w, int h)
{
    glViewport(0, 0, w, h);
}

void mouseClick(int button, int state, int x, int y)
{

}

void mouseMotion(int x, int y)
{

}

void keyboard(unsigned char key, int x, int y)
{

}

color vertex 頂點着色器和片斷着色器的代碼見文中。

相關文章
相關標籤/搜索