现代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 顶点着色器和片断着色器的代码见文中。

相关文章
相关标签/搜索