着色器-绚丽多彩的世界,从你开始!

Hello,OpenGL World文章中,我们并未过多的提及着色器,只是叫读者将顶点着色器、片段着色器的相关代码进行复制使用。虽然我们通过它们绘制出了三角形,但我们并不知道其中代码的任何含义。那么这篇文章我们便来讲讲这着色器。
着色器是运行在GPU上的把输入转化为输出的独立的小程序。书写着色器的语言我们叫做GLSL,GLSL是一种类C语言写成的。我们通过Hello,OpenGL World中使用的顶点着色器和片段着色器对相关知识进行说明。

// 顶点着色器
#version 300 es
layout (location = 0) in vec4 vPosition;
out vec4 vertexColor;
void main() {
    gl_Position = vPosition;
    vertexColor = vec4(1, 0.8, 0.0, 1.0);
}
// 片段着色器
#version 300 es                               
precision mediump float;
in vec4 vertexColor;
out vec4 fragColor; 
void main() {
    fragColor = vertexColor;
}

从以上代码我们可看出顶点着色器与片段着色器有许多的相似之处也有不同之处。

版本声明

声明了es的着色语言版本,用于检查着色器语言语法,一般来说每个着色器起始于一个版本说明,但是如果着色器不声明版本号,那么该着色器就会被认定为使用OpenGL ES着色语言1.00的版本,着色器语言的1.00版本适用于OpenGL ES 2.0。我们使用OpenGL ES 3.0版本,所以我们一般声明其版本为300即可。(比如GLSL 420版本对应于OpenGL 4.2)

变量和变量类型

在计算机图形中,两个基本的数据类型组成了变换的基础:向量和矩阵。着色器语言包含标量、向量和矩阵的数据类型。
a.标量
标量包含了C语言中集中基本的数据类型。float、int、bool、uint、double
b.向量
向量包含了由标量组成四种向量类型。vecn、ivecn、bvecn、uvecn,dvecn其中n表示的分量个数,例如在上面的顶点着色器中声明的vec4 vertexColor即表示vertexColor是有4个分量的基于浮点的向量类型。

变量构造器

OpenGL ES着色器在类型转换方面有非常严格的规则,变量的赋值、计算都只能是相同类型的变量,在着色器语言中不允许隐含类型转换。(类似于Swift)

a.使用构造器初始化和转换标量值
float myFloat = 1.0;
float myFloat = 1; // 错误, 类型不正确
bool myBool = true;
int myInt = 1;
int myInt1 = 1.0; // 错误, 类型不正确

myFloat = float(myBool); // 将bool类型转为float类型
myFloat = float(myInt); // 将int类型转为float类型
b.构造器初始化、转换向量数据类型

向量构造器参数的传递有两种基本方法:
1.只为向量构造器提供一个标量参数,则该值用于设置向量所有分量的值。
2.提供多个标量或者向量参数,向量的值从左至右使用这些参数设置,因此向量中必须有至少和参数中一样多的分量。

vec4 myVec4 = vec4(1.0); // myVec4 = {1.0, 1.0, 1.0, 1.0};

vec3 myVec3 = vec3(1.0, 0.5, 0.0);
vec3 tempVec3 = vec3(myVec3);  // tempVec3 = {myVec3.x, myVec3.y, myVec3.z};
vec2 myVec2 = vec2(tempVec3);  // myVec2 = {tempVec3.x, tempVec3.y};
myVec4 = vec4(myVec2, tempVec3) // myVec4 = {myVec2.xy, tempVec3.xy};
c.输入与输出

前面我们已经提到过,着色器是将输入转化为输出的一段独立的小程序来实现数据流的传输,恰好GLSL便定义了in和out关键字来实现改目的。每个着色器使用in和out来设定输入和输出的变量,只要输出变量和下一个输入变量的名称类型相同,数据就能正常的往下传递。例如我们前文贴出的两个着色器便可以很容易的找到对应的in和out。

out vec4 vertexColor; // 顶点着色器的输出变量
in vec4 vertexColor;  // 片段着色器的输入变量

顶点着色器可以直接在顶点数据中直接接收输入。在前一章渲染三角形时,我们需要告诉OpenGL如何去解析顶点数据,为了定义顶点数据如何管理,我们使用location这一元数据指定输入变量,当然你也可以使用glGetAttribLocation函数查询属性位置,但为了更好地理解和减少OpenGL的工作量,我们直接在着色器中设置它们。
在片段着色器中,我们有一个颜色输出变量fragColor,因为片段着色器需要生成一个最终输出的颜色,如果在片段着色其中没有定义输出颜色,OpenGL会把你定义的物体渲染成黑色。因此要实现从一个着色器传递数据至另一个着色器,在发送方必须要声明输出,在接收方声明输入。

d.Uniform

Uniform是CPU中的应用向GPU中的着色器发送数据的方式,它声明的变量是一个全局的变量,也就是说该变量会保存其最新数据,并可被着色器程序的任意着色器在任意阶段访问。我们适当修改一下绘制三角形所用到的片段着色器,使用Uniform来设置三角形的颜色。

#version 300 es
precision mediump float;
out vec4 fragColor;
uniform vec4 ourColor;

void main() {
    fragColor = ourColor;
}

需要注意的是,更新一个Uniform需要在调用glUseProgram之后,因此,我们可以如下设置Uniform声明的变量。

int vertexColorLoaction = glGetUniformLocation(self.program, "ourColor");
glUniform4f(vertexColorLoaction, 0.4, 0.3, 0.6, 1);

glUniform函数有特定的后缀,用于标识设定的Uniform类型。在这里我们是为了设置一个RGBA的颜色,所以我们使用glUniform4f函数。glGetUniformLocation函数是用于查询Uniform变量ourColor的位置值,当返回的数值为-1时,便代表没有找到这个位置值,所以为了安全起见,我们可以在此多做一个判断并打印相关错误,为我们调试提供更多的信息。该案例在Learn_OpenGLES_Demo中的TwoTriangles中。

e.main函数

main函数是每一个着色器的入口点,在这个函数中处理所有的输入变量,并将结果输出到输出变量中。(可联想C语言的main函数)

多属性的顶点数据

在绘制三角形时,我们只在vertices数组中配置了位置坐标,那么在这里,我们打算把各个顶点的颜色也配置在vertices数组中。

    float vertices[] = {
    // 位置              // 颜色
     0.0,  0.5, 0.0,  1.0, 0.0, 0.0,
    -0.5, -0.5, 0.0,  0.0, 1.0, 0.0,
     0.5, -0.5, 0.0,  0.0, 0.0, 1.0
    };

由于我们现在还有颜色的相关信息在顶点数据中,所以我们可以思考该如何书写我们的顶点着色器。
按照前文的讲述,一个着色器是由版本信息开始的,所以我们声明对应的版本信息。

#version 300 es

并且片段着色器需要一个输入来知道最终的颜色,所以在顶点着色器中理应包含一个输入用于告知片段着色器绘制的最终颜色。为了更好地熟悉变量构造器的相关知识点。我们这里使用vec3类型来声明输出的颜色。

out vec3 ourColor;

并且在前面我们也介绍过,顶点着色器与片段着色器的一些不同点,顶点着色器可以使用location来指定输入变量,因为我们的顶点数据的坐标包含三个分量,颜色也含有三个分量,所以我们使用vec3来声明输入的值。但要注意的是我们需要设定一下location的值。

layout (location = 0) in vec3 aPos; // 声明位置输入,位置变量的属性位置值为 0 
layout (location = 1) in vec3 aColor; // 声明颜色输入,颜色变量的属性位置为1

最后便是main函数,将处理完的输入变量的结果输出到输出变量中。

gl_Position = vec4(aPos, 1);
ourColor = aColor;

到这里,我们自己书写的一个顶点着色器就完成了,具体如下:

#version 300 es
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

out vec3 ourColor;

void main() {
    gl_Position = vec4(aPos, 1);
    ourColor = aColor;
}

在片段着色器方面,因为最终物体绘制的颜色是通过顶点着色器传入的,所以片段着色器必然有一个输入用于接收顶点着色器的输出。

in vec3 ourColor;

在此之前,我们还是一样声明版本并设置精度。

#version 300 es
precision mediump float;

最后声明一个输出的变量,将接收到的ourColor的值赋值给输出变量。

out vec4 fragColor;

最后实现main函数就完成了一个简单地片段着色器。

void main() {
    fragColor = vec4(ourColor, 1.0);
}

在着色器书写完成后,我们实现上下文的创建,着色器的创建,编译,链接后,还需要告诉OpenGL如何解析顶点数据。
VBO内存中的数据排列(图片来源于LearnOpenGL CN)

所以调用顶点数组指针函数应该如下:

// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
glEnableVertexAttribArray(1);

小总结:将多个属性写入顶点数据中需要注意的是在告诉OpenGL如何解析顶点数据时的步进值以及偏差时,并在书写顶点着色器时注意location的位置(也可通过函数获取)。
运行程序,得到以下效果:

多个属性的顶点数据

该案例的具体实现LearnShadersSMLearnShader.fsh
SMLearnShader.vsh
学习OpenGL的所有Demo均可在Github下载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343