前面几章的内容都只用到了顶点着色器,最终的图像是底色+黑色,不是很美观,使用片元着色器给程序加颜色,实际上是非常简单的,前面几章为了不引入太多的新内容,避免增加程序复杂性,没有增加片元着色器的使用。本章介绍片元着色器的使用方法,包括以下内容:
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. 顶点着色器position与color的输入,并不一定要通过两个变量输入,你可以只用一个变量如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图实现出来,显示图片的方式不少,这里采用最笨的方法,使用顶点着色器描述图片的坐标,每个顶点指定对应位置的颜色,这样就可以显示出完整的图片,还可以自己添加通过键盘控制对图片进行缩放、移动等操作,可以放大查看图片细节,注意插值效果。
为载入简单,直接使用bmp,bmp格式比较简单,可以查看:
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. 向着色器传送颜色数据时,type为GL_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"
本文介绍片元着色器的基本使用方法,包括片元着色器与顶点着色器的关系、关闭颜色插值方式及使用片元着色器显示BMP图片等内容。
3886

被折叠的 条评论
为什么被折叠?



