美文网首页
LearnOpenGL 着色器Shader

LearnOpenGL 着色器Shader

作者: li_礼光 | 来源:发表于2020-09-16 00:48 被阅读0次

着色器 :
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;//获取值
    }
);

程序运行看看效果 :


效果图

从这里看到的效果是跟前面的是一样的.

比如再换一种颜色, 红色吧.


红色的效果

.


简单总结一下. 其实这里就很好理解了整个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是一个函数, 一个别名.忘记改了.

效果图

总结 :

  • 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.

效果图

复杂点 : 以正方形来

这次如果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,  //左上     红
};
效果图

最后总结 , 到这里应该对于整个的OpenGL和GLSL有一个初步的认识了.

接下来得任务, 还是很多 : 纹理, 变化, 坐标系统, 摄像机, 广告颜色, 材质, 贴图, 多光源, 高级OpenGL, 高级光照等等等.....

自己的Demo在:LearnOpengl .
里面有很多修修改改,注释什么的, 很多细节都在从第一篇到最后一步一步的修改了. 所有的修改的代码在这里我觉得也很详细了.

相关文章

网友评论

      本文标题:LearnOpenGL 着色器Shader

      本文链接:https://www.haomeiwen.com/subject/nhkxyktx.html