openGL之glsl入门4--片元着色器显示bmp图片

本文介绍片元着色器的基本使用方法,包括片元着色器与顶点着色器的关系、关闭颜色插值方式及使用片元着色器显示BMP图片等内容。

    前面几章的内容都只用到了顶点着色器,最终的图像是底色+黑色,不是很美观,使用片元着色器给程序加颜色,实际上是非常简单的,前面几章为了不引入太多的新内容,避免增加程序复杂性,没有增加片元着色器的使用。本章介绍片元着色器的使用方法,包括以下内容

1. 片元着色器与顶点关系

2. 关闭颜色插值方式

3. 使用片元着色器显示bmp图片

1. 片元着色器与顶点关系

    片元着色器为模型上色,片元着色器和顶点着色器的main函数都只处理一个点(多个点会并行处理),顶点着色器画一个点,片元着色器就为这个点上色,颜色信息一般都通过客户端经过顶点着色器传过去的,当然着色器也可以自己参数数据,最终都表现在的定义为out的四维向量上。

    先看一个使用片元着色器的例子,源码如下:

#include <stdlib.h>
#include <stdio.h>
#include <GL/glew.h>
#include <GL/glut.h>
static const GLchar * vertex_source =
    "#version 330 core\n"
    "layout (location = 0) in vec2 position;\n"
    "layout (location = 1) in vec3 color;\n"
    "out vec3 vertex_color;\n"
    "void main()\n"
    "{\n"
    "gl_Position = vec4(position.x,position.y,0.0,1.0);\n"
    "vertex_color = color;\n"
    "}\0";
static const GLchar * frag_source =
    "#version 330 core\n"
    "in vec3 vertex_color;\n"
    "out vec4 color;\n"
    "void main()\n"
    "{\n"
    "color = vec4(vertex_color,1.0);\n"
    "}\n\0";
void loadShader(GLuint program, GLuint type, const GLchar * source)
{
    const GLchar * shaderSource[] = {source};
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, shaderSource, 0);
    glCompileShader(shader);
    glAttachShader(program, shader);
}
void init()
{
    GLuint program = glCreateProgram();
    /* 同时加载了顶点着色器和片元着色器*/
    loadShader(program, GL_VERTEX_SHADER, vertex_source);
    loadShader(program, GL_FRAGMENT_SHADER, frag_source);
    glLinkProgram(program);
    glUseProgram(program);
    glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
}
void display()
{
    glClear(GL_COLOR_BUFFER_BIT);
    GLfloat vertices[] =
    {
        -0.8, -0.8,
        0.8, -0.8,
        0.0, 0.8
    };
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid *)vertices);
    /* 3个点,每个点给一种颜色*/
    GLfloat colors[] =
    {
        1.0, 0.0, 0.0,
        0.0, 1.0, 0.0,
        0.0, 0.0, 1.0,
    };
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid *)colors);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glFlush();
}
int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA);
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(300, 300);
    glutCreateWindow("color");
    glewInit();
    init();
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;
}

效果如下:

 

    相比第二章的helloworld程序,程序作了以下几个改变:

1. init的时候加载了片元着色器,用的是GL_FRAGMENT_SHADER类型。

2. display的时候设置了每个点的颜色信息,注意glVertexAttribPointer第一个参数(索引位置),使用的是1,需要与顶点着色器区分开,与着色器中的layout (location = 1) in vec3 color;对应。

3. 顶点着色器多定义了一个in变量,用来接收color,同时也定义了一个out变量,用来给片元着色器传递颜色信息。

    代码并不复杂,了解数据流程可以更好的理解:

1. 顶点和颜色数据都是先从客户端传送到顶点着色器

2. 顶点着色器经过处理后传送到片元着色器

3. 片元着色器经过处理后,再次传入到后面的着色使用,最终显示出来。

    顶点着色器main函数里面,直接把接收到的颜色信息,通过out参数传递出去。片元着色器直接把收到的顶点颜色信息通过out变量传递出去。

 

注意:

1. 片元着色器的in变量的名称,必须和顶点着色器out变量保持一致,否则收不到顶点着色器传过来的颜色信息。

2. 片元着色器的in变量和out变量都不是必须的(片元着色器本身都不是必须的),会有默认值,out可以写成固定值,如color = vec4(0.5,0,0,1.0);则所有点显示为同样的红色。

3. 顶点着色器positioncolor的输入,并不一定要通过两个变量输入,你可以只用一个变量如4维变量position,把前面的2维作为坐标,后面的两维作为颜色,也可以通过多个变量输入,再组合起来用,通常情况下,顶点和颜色各使用一个变量来输入。

4. 实际编程,点序列,最好使用结构体,使用浮点数组,太容易出错,逗号和点很容易搞混,这个地方被坑了好几次。

5. 上面程序3个顶点,给了3种颜色,假如只定义了2个顶点的颜色信息,第三个点则使用默认值着色(黑色)。

    可以把填充三角形改成线三角形(默认线太细,设置了线宽),可以看到改变的只是线的颜色,其他的不受影响。

glDrawArrays(GL_TRIANGLES, 0, 3);

改成

glLineWidth(8.0);
glDrawArrays(GL_LINE_LOOP, 0, 3);

效果如下:

 

2. 关闭颜色插值方式

    由上例可以看到,默认情况下颜色是渐变的,默认使用了插值方式,例子中3个顶点颜色分别为纯色(1.0)红、绿、蓝,老的程序可以使用 glShadeModel(GL_FLAT); 函数来关闭插值模式,这个是1.0版本的函数,现在不适用了,可以通过flat方式设置为同样的效果,需与glProvokingVertex函数配合使用 flat类型声明方式如下:

1. 顶点着色器:

"out vec3 vertex_color;\n"

改为

"flat out vec3 vertex_color;\n"

2. 片元着色器:

"in vec3 vertex_color;\n"

改为

"flat in vec3 vertex_color;\n"

3. 客户端init函数:

glClearColor(0.5f, 0.5f, 1.0f, 1.0f);

改为

glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
glClearColor(0.5f, 0.5f, 1.0f, 1.0f);

    glProvokingVertex函数参数有两个值,GL_FIRST_VERTEX_CONVENTION表示使用第一个颜色,    GL_LAST_VERTEX_CONVENTION表示使用最后一个点的颜色作为整体颜色,通过以上修改,两个参数输出的效果分别为红色和蓝色。效果如下:

 

3. 使用片元着色器显示bmp图片

    片元着色器可以输出颜色,为进一步加深了解,我们可以通过shader来加载一张bmp图实现出来,显示图片的方式不少,这里采用最笨的方法,使用顶点着色器描述图片的坐标,每个顶点指定对应位置的颜色,这样就可以显示出完整的图片,还可以自己添加通过键盘控制对图片进行缩放、移动等操作,可以放大查看图片细节,注意插值效果。

    为载入简单,直接使用bmpbmp格式比较简单,可以查看:

http://blog.youkuaiyun.com/t3swing/article/details/76546402

 

加载显示图片的源码为:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <GL/glew.h>
#include <GL/glut.h>
#pragma pack(1)
#define BMP_FILE                "1920x1080.bmp"
typedef unsigned char U8;
typedef unsigned short int U16;
typedef unsigned long int U32;
typedef struct
{
    U16 bfType;         /* windows下该值必需是x4D42,即字符'BM'*/
    U32 bfSize;         /* bmp文件大小,包含bmp文件头,信息头,调色板大小,数据大小*/
    U16 bfReserved1;    /* 保留,必须设置为*/
    U16 bfReserved2;    /* 保留,必须设置为*/
    U32 bfOffBits;      /* rgb数据相对文件头偏移量*/
} BitmapFileHeader;
typedef struct
{
    U32 biSize;         /* 信息头大小sizeof(BitmapInfoHeader) */
    U32 biWidth;        /* 图象的宽度,以象素为单位*/
    U32 biHeight;       /* 图象的高度,以象素为单位,正值倒向位图,负值正向位图*/
    U16 biPlanes;       /* 位面数,设为*/
    U16 biBitCount;     /* 位图位数,可以为,24,16,8,4,1 */
    U32 biCompression;  /* 说明图象数据压缩的类型,BI_RGB(0) BI_BITFIELDS(3)等*/
    U32 biSizeImage;    /* 图像数据大小,包括位图信息大小和数据大小*/
    U32 biXPelsPerMeter;/* 水平分辨率,一般可填*/
    U32 biYPelsPerMeter;/* 垂直分辨率,一般可填*/
    U32 biClrUsed;      /* 颜色索引使用数,一般填,表示都使用*/
    U32 biClrImportant; /* 重要颜色索引数,一般填,表示都重要*/
} BitmapInfoHeader;
typedef struct
{
    BitmapFileHeader bfHeader;
    BitmapInfoHeader biHeader;
} BitmapInfo;
typedef struct
{
    int width;
    int height;
    char * data;
} BitmapLoad;
typedef struct
{
    GLfloat x;
    GLfloat y;
} Point;
typedef struct
{
    GLbyte r;
    GLbyte g;
    GLbyte b;
} RGB;
static const GLchar * vertex_source =
    "#version 330 core\n"
    "layout (location = 0) in vec2 position;\n"
    "layout (location = 1) in vec3 color;\n"
    "out vec3 vertex_color;\n"
    "void main()\n"
    "{\n"
    "gl_Position = vec4(position.x,position.y,0.0,1.0);\n"
    "vertex_color = color;\n"
    "}\0";
static const GLchar * frag_source =
    "#version 330 core\n"
    "in vec3 vertex_color;\n"
    "out vec4 color;\n"
    "void main()\n"
    "{\n"
    "color = vec4(vertex_color.b,vertex_color.g,vertex_color.r,1.0);\n"
    "}\n\0";
BitmapLoad * loadBmp(BitmapLoad * load)
{
    int pos = 0, dataSize = 0;
    FILE * fp = NULL;
    BitmapInfo bmpInfo;
    fp = fopen(BMP_FILE, "rb");
    if (fp == NULL)
    {
        perror("fopen");
        return NULL;
    }
    memset(&bmpInfo, 0, sizeof(BitmapInfo));
    fread(&bmpInfo, 1, sizeof(BitmapInfo), fp);
    printf("width:%lu height:%lu dataSize:%lu\n", bmpInfo.biHeader.biWidth, bmpInfo.biHeader.biHeight, bmpInfo.biHeader.biSizeImage);
    if (bmpInfo.biHeader.biSizeImage != bmpInfo.biHeader.biWidth * bmpInfo.biHeader.biHeight * 3)
    {
        printf("biSizeImage size error! header:%d bmp size:%d\n", sizeof(BitmapInfo), bmpInfo.bfHeader.bfSize);
    }
    dataSize = bmpInfo.biHeader.biSizeImage;
    load->data = (char *)malloc(dataSize);
    if (load->data == NULL)
    {
        fclose(fp);
        return NULL;
    }
    fread(load->data, 1, dataSize, fp);
    load->width = bmpInfo.biHeader.biWidth;
    load->height = bmpInfo.biHeader.biHeight;
    pos = ftell(fp);
    printf("bmp pos:%d\n", pos);
    fclose(fp);
    return load;
}
void loadShader(GLuint program, GLuint type, const GLchar * source)
{
    const GLchar * shaderSource[] = {source};
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, shaderSource, 0);
    glCompileShader(shader);
    glAttachShader(program, shader);
}
BitmapLoad bmpLoad;
Point * bmpVertices = NULL;
RGB * bmpColors = NULL;
GLfloat scale = 1.0;
void init()
{
    GLuint program = glCreateProgram();
    loadShader(program, GL_VERTEX_SHADER, vertex_source);
    loadShader(program, GL_FRAGMENT_SHADER, frag_source);
    glLinkProgram(program);
    glUseProgram(program);
    /* 这里只处理了位色bitmap且高宽字节对齐的情况 */
    if (loadBmp(&bmpLoad))
    {
                int cnt = 0;
                bmpColors = (RGB * )bmpLoad.data;
        bmpVertices = (Point *)calloc(bmpLoad.width * bmpLoad.height * sizeof(Point),1);
                for(int i = 0;i<bmpLoad.height;i++)
                {
                        for(int j = 0;j<bmpLoad.width;j++)
                        {
                                bmpVertices[cnt].x = (j*2.0/bmpLoad.width - 1.0)  * scale;
                                bmpVertices[cnt].y = (i*2.0/bmpLoad.height - 1.0) * scale;
                                cnt++;
                        }
                }
        }
}
void display()
{
    glClear(GL_COLOR_BUFFER_BIT);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid *)bmpVertices);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_UNSIGNED_BYTE, GL_TRUE, 3 * sizeof(GLbyte), (GLvoid *)bmpColors);
    glDrawArrays(GL_POINTS, 0, bmpLoad.width*bmpLoad.height);
    glFlush();
}
int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA);
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(960, 540);
    glutCreateWindow("loadBmp");
    glewInit();
    init();
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;
}

    这里没有使用库加载bmp(避免依赖引入),程序里的实现比较简单,直接把bmp数据当成了片元着色器的数据使用,所以只能识别24位色bmp图片。

    大家可以把上图保存一下,用画图打开,改成24位色BMP格式,或者自己找一张1080P大小24位的bmp图片来验证。

程序运行的效果与用画图直接打开并没有什么区别,当scale = 4.0时,效果如下:

 

    可以看到,图像的效果比较差,这个程序只是让大家对片元着色器编程有直观的认识,实际上一般显示图片不会这么用,上述的程序,数据运算有部分在cpu,完全可以放到shader里面做,可以看到,使用点的方式画出的图形放大后基本不能看,用线的方式画出来后也不好看,大家可以尝试改成用面的方式(三角形、四边形等),这种方式放大的效果理论上应该好不少,但坐标点的传送方式需重新设计一下,有兴趣的自己验证。

 

    程序有几个细节解释一下:

1. 向着色器传送颜色数据时,typeGL_UNSIGNED_BYTE,是因为RGB数据一个分量只占用一个字节,归一化为GL_TRUE,因为传入的数据范围是0~255,而shader用的RGBA范围是[0-1],所以要使用归一化,否则需自己先转成[0-1]范围再传进去。

glVertexAttribPointer(1, 3, GL_UNSIGNED_BYTE, GL_TRUE, 3 * sizeof(GLbyte), (GLvoid *)bmpColors);

2. 片元着色器,RGB分量倒置了一下,因为bmp数据时BGR顺序排布的,所以需调一下顺序。

"color = vec4(vertex_color.b,vertex_color.g,vertex_color.r,1.0);\n"


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值