着色器 :
1.开头总要声明版本
2.接着是输入和输出变量, uniform和main函数
典型的着色器结构
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
int main()
{
//处理一些输入并处理一些图形操作
....
out_variable_name = weird_stuff_we_processed
}
回过头来看前面的
char *vertexShaderStr = SHADER(
\#version 330 core\n
layout (location = 0) in vec3 position;
void main()
{
gl_Position = vec4(position.x, position.y, position.z, 1.0);
}
);
着色器写在了宏里面, 把宏移除掉之后
#version 330 core 版本
layout (location = 0) in vec3 position; 输入和输出
void main()
{
//处理一些输入并处理一些图形操作
//out_variable_name
gl_Position = vec4(position.x, position.y, position.z, 1.0);
}
整体的大概框架就是这样. 细节往下走.
数据类型 :
默认的基础数据类型 : int, float, double, uint 和 bool
向量:
vecn --- 包含n个float分量的默认向量
bvecn --- 包含n个bool分量的默认向量
ivecn --- 包含n个int分量的默认向量
uvecn --- 包含n个uint分量的默认向量
dvecn --- 包含n个double分量的默认向量
基本上和上面对应的基础类型一直.
n代表的是多少维.
比如 vec2, vec3 , vec4.
vec2 = (1.0, 1.0); //x , y
vec3 = (1.0, 1.0, 1.0); // x , y , z
vec4 = (1.0, 1.0, 1.0, 1.0); // x , y , z , w
输入与输出
打个比如 , 比如类似我们的CPU编程.
int a = 1;
int b = a;
数据的传递, 把1传给a, 把a传给b.
因为着色器是各自独立的小程序, 但是对于整个GPU来说,它们是这个整体的一部分, 所以需要数据的传递.在GLSL里面定义了in和out关键字来实现这个目的.
每个着色器使用者和两个关键字(in 和 out)设定输出和输入.
只要一个输出变量与下一个着色器阶段的输入匹配, 他就会传递下去. 但是在顶点和片元着色器中会有点不同.
顶点着色器
顶点着色器接受一种特殊的形式的输入. 否则就会效率低下. 顶点着色器的输入特殊在, 他从顶点数据中直接接受输入. 为了定义顶点数据改如何管. 我们使用 location 这一元数据指定输入变量. 这样我们才可以在CPU上配置顶点属性.
回看最前面的顶点着色器. 定义完版本之后.
layout (location = 0) in vec3 position; 输入和输出
顶点着色器需要为它的输入提供一个额外的layout标识. 这样我们才能把它连接到顶点数据. 也可以通过OpenGl代码的方式
glGetAttribLocation 查询属性位置值
简单粗暴理解 : 顶点着色器的输入也是in ,但需要打特殊标记layout(location = 0)
片元着色器
顶点着色器处理数据的问题. 那么片元着色器处理真个图像的渲染问题. 现在这个片元着色器需要一个vec4的颜色输出变量. 因为片段着色器需要生成一个最终输出的颜色. 如果你在片段周色漆没有定义输出颜色, OpenGL会把你的物体渲染为黑色.(或白色)
这里要结合顶点着色器一起看, 回到前面的代码.
//顶点着色器程序
char *vertexShaderStr = SHADER(
\#version 330 core\n
layout (location = 0) in vec3 position;
void main()
{
gl_Position = vec4(position.x, position.y, position.z, 1.0);
}
);
//片元着色器程序
char *fragmentShaderSrc = SHADER(
\#version 330 core\n
out vec4 color;
void main()
{
color = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
);
片元着色器因为输出的color是vec4(1.0f, 0.5f, 0.2f, 1.0f), 已经写好了. 所以这里就会呈现出我们需要的颜色.
我们这里是需要做一个数据的传递, 所以需要稍微改一改这里面的方式.
首先我们先确定我们要做的事情就是. 片元着色器的颜色的值从哪里来, 从顶点着色器中来.
修改后.
char *vertexShaderStr = SHADER(
\#version 330 core\n
layout (location = 0) in vec3 position;
out vec4 vertexColor;//把片元着色器的颜色从这里输出
void main()
{
gl_Position = vec4(position.x, position.y, position.z, 1.0);
vertexColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
);
//片元着色器程序
char *fragmentShaderSrc = SHADER(
\#version 330 core\n
in vec4 vertexColor;//从顶点着色器中拿color的值
out vec4 color;
void main()
{
color = vertexColor;//获取值
}
);
程序运行看看效果 :
![](https://img.haomeiwen.com/i1638260/584c6893ce53e129.png)
从这里看到的效果是跟前面的是一样的.
比如再换一种颜色, 红色吧.
![](https://img.haomeiwen.com/i1638260/1e56c028110e877b.png)
.
简单总结一下. 其实这里就很好理解了整个GLSL的大体的框架.
//定义着色器的版本.
#version version_number
//定义一些输入和输出的变量
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
//处理一些输入并处理一些图形操作, 这些变量的逻辑关系, 取值问题等等
int main()
{
//处理一些输入并处理一些图形操作
....
out_variable_name = weird_stuff_we_processed
}
初次接触会有点懵 , 因为这里的编程方式和CPU的编程方式不太相同. 硬件结构导致吧. 所以学起来有点绕.
还有一些细节 :
- 顶点着色器有layout(location) 标识符, 为特殊的输入结构
- 只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去. 这里只用了2个着色器程序, 所以先简单理解.
- 着色器程序的main里面, 对输出赋值 , 对输入做取值操作.
- 因为在项目里面写在了同一个文件里面, 其实把它单独分开到一个文件里面,也是一样的效果.
因为这里比较简单, 先这么帮助理解. 就是着色器之间的数据传输方式就有一个简单的认识理解.
Uniform
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式, 但Uniform和顶点属性有些不同.
- 首先, uniform是全局的, 全局意味着uniform变量必须在每个着色器对象中都是独一无二的. 而且它可以被着色器程序的任意着色器在任意阶段访问.
- 第二, 无论你把uniform设置成什么, uniform会一直保存它们的数据. 直到他们被重置或者更新.
从这个解释来说, 是不是像极了在属性面前加了static.
#version 330 core
uniform vec4 ourColor; //在OpenGL程序代码中设定这个变量
void main()
{
color = ourColor;
}
我们在片段着色器中声明了一个Uniform vec4的ourColor, 并把片元着色器的输出颜色设置为uniform值得内容. 因为uniform是全局变量, 我们可以在任何着色器中定义他们, 而无需通过顶点着色器作为中介. 顶点着色器中不需要这个uniform, 所以我们不用在哪里定义它.
PS : 如果你声明了一个uniform却在GLSL代码中没用过, 编译器会静默移除这个变量, 导致最后编译出来的版本并不会包含它, 这可能导致几个非常麻烦的错误. 这个要注意.
我们现在定义了uniform vec4 ourColor, 赋值给color, 但是ourColor还是空的, 没有给ourColor添加任何数据, 所以下面我们就做这件事情.
首先找到着色器中的uniform属性的索引/位置值. 当我们得到索引/位置值之后, 就可以更新它了.
新修改的Shader
char *vertexShaderStr = SHADER(
\#version 330 core\n
layout (location = 0) in vec3 position;
void main()
{
gl_Position = vec4(position.x, position.y, position.z, 1.0);
}
);
//片元着色器程序
char *fragmentShaderSrc = SHADER(
\#version 330 core\n
uniform vec4 ourColor;
out vec4 color;
void main()
{
color = ourColor;//获取值
}
);
在渲染流程中加入.
GLfloat timeValue = glfwGetTime();
GLfloat greenValue = (sin(timeValue) / 2 ) + 0.5;
GLint vertexColorLocation = glGetUniformLocation(myProgram.program, "ourColor");
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
glUseProgram(myProgram.program);
PS : 代码中用glad_glGetUniformLocation, 其实和glGetUniformLocation是一个函数, 一个别名.忘记改了.
![](https://img.haomeiwen.com/i1638260/e5e75c20b7d50752.gif)
总结 :
- Uniform是把着色器里面的属性变成了全局变量. 不管是什么样的着色器.
- 我们可以通过OpenGL函数获取这个属性,然后更新它的值.
- 这里也是一个值传递的过程. 可以理解为在着色器之外做值传递
在这里整理一下Shader数据输入输出的方式 :
- 1.直接在Shader里面写死了对应的数据. (没有值传递处理)
- 2.通过in和out编写相同属性名称的方式处理. (有值传递处理)
- 3.通过Uniform编写全局属性来处理. (有值传递处理).
Layout (location)
在着色器中我们定义了 layout (location = 0) in vec3 position;
这个怎么理解, 这个要根据你的顶点数据 和 绑定方式.
前面说了 , layout是顶点数据的特殊标识符. 所以这个根据前面的理解就好, 这个location怎么理解呢?
比如
char *vertexShaderStr = SHADER(
\#version 330 core\n
layout (location = 0) in vec3 position;
void main()
{
gl_Position = vec4(position.x, position.y, position.z, 1.0);
}
);
这里设置了location为0. 那么处理VAO数据的时候
//顶点数据(location = 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
如果location为1, 那么
//顶点数据(location = 1)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
更多练习
在 LearnOpenGL 完整画一个三角形or正方形最后有一个想法, 我们可以设置一个纯色的三角形和正方形. 那么我们能不能做一个渐变的三角形或者是正方形.
以shader为准, 修改三角形
PS : 这要确定的是, 要以shader为准还是以顶点数据为准来修改. 根据个人调解.
///
/// 着色器程序之间的数据传递
char *vertexShaderStr = SHADER(
\#version 330 core\n
layout (location = 0) in vec3 position; //顶点数据源输入
layout (location = 1) in vec3 color; //颜色数据源输入
out vec4 vertexColor;//把片元着色器的颜色从这里输出
void main()
{
gl_Position = vec4(position, 1.0f);
vertexColor = vec4(color, 1.0f); //输出给片元着色器
}
);
//片元着色器程序
char *fragmentShaderSrc = SHADER(
\#version 330 core\n
in vec4 vertexColor;//从顶点着色器中拿color的值
out vec4 color;
void main()
{
color = vertexColor;//获取值
}
);
稍微要注意的是用的是vec3,但是要传给vec4,记得后面补1.0f.
修改顶点数据
//(旧)三角形
//GLfloat triangleVertices[] = {
// -0.5f, -0.5f, 0.0f, //左下
// 0.5f, -0.5f, 0.0f, //右下
// 0.0f, 0.5f, 0.0f, //中上
//};
//(新修改)三角形
GLfloat triangleVertices[] = {
//顶点数据 //颜色数据
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, //左下 红
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, //右下 绿
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, //中上 蓝
};\
更新VBO
因为更新了数据源的格式, 所以这里也要更新下VBO的内存
//创建VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangleVertices), triangleVertices, GL_STATIC_DRAW);
//创建VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
//顶点数据(location = 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
//颜色数据 (location = 1)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
//解绑VAO
glBindVertexArray(0);
因为layout输入location是0和1, 所以glEnableVertexAttribArray 分别 对应0和1.
![](https://img.haomeiwen.com/i1638260/8ddbca594e714164.png)
复杂点 : 以正方形来
这次如果shader不变, 顶点数据怎么处理
修改顶点数据
////正方形
//GLfloat squareVertices[] = {
// -0.5f, -0.5f, 0.0f, //左下
// 0.5f, -0.5f, 0.0f, //右下
// 0.5f, 0.5f, 0.0f, //右上
// -0.5f, 0.5f, 0.0f, //左上
//};
//正方形
GLfloat squareVertices[] = {
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, //左下 红
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, //右下 绿
0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, //右上 绿
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, //左上 红
};
![](https://img.haomeiwen.com/i1638260/c1b67adee6b22b43.png)
最后总结 , 到这里应该对于整个的OpenGL和GLSL有一个初步的认识了.
接下来得任务, 还是很多 : 纹理, 变化, 坐标系统, 摄像机, 广告颜色, 材质, 贴图, 多光源, 高级OpenGL, 高级光照等等等.....
自己的Demo在:LearnOpengl .
里面有很多修修改改,注释什么的, 很多细节都在从第一篇到最后一步一步的修改了. 所有的修改的代码在这里我觉得也很详细了.
网友评论