( 本文对应学习章节:https://learnopengl-cn.github.io/01%20Getting%20started/05%20Shaders/ )
0.前言在之前绘制三角形的章节中已经初步了解了着色器和着色器语言。在本章节中,教程进一步讲解着色器和着色器语言GLSL,及相关的数据类型,参数传递等。在最后,封装了一个简易的着色器类,简化主体流程。
1.了解着色器与着色器语言着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。
着色器是使用一种叫GLSL的类C语言写成的,典型的着色器代码结构如下:
#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;
}
数据类型
GLSL中包含常见的基础数据类型:int、float、double、uint和bool。也有两种容器类型:向量(Vector)和矩阵(Matrix)。
向量是一个可以包含至多4个分量的容器,可以看作基本类型的复合形式:vec3就是3个分量的float向量(首字母不带类型默认是float),bvec2表示2个分量的bool向量,ivec4表示4个分量的int向量。
对于向量中的分量,可以通过.xyzw或是.rgba或是.stpq来访问,而且可以灵活的重组和构造:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
输入与输出
GLSL定义了in和out关键字,每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。但在顶点和片段着色器中会有点不同。
顶点着色器从顶点数据中直接接收输入。为了指定输入变量,需要为它的输入提供一个额外的layout标识,这样我们才能把它链接到顶点数据。而layout的location偏移是通过glVertexAttribPointer函数的第一个参数指定的:
glVertexAttribPointer定义通用顶点属性数据的数组:
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer);
参数1指定顶点属性的索引;参数2指定顶点属性的分量数,可以是[1-4];参数3指定数据类型;参数4设置是否归一化;参数5指定连续的顶点属性间的字节偏移;参数6指定第一个值的偏移量。可以对照上面的代码示例。
另一个例外是片段着色器,它需要一个vec4颜色输出变量 ,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。
UniformUniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
假设我们在片段着色器声明了一个uniform的颜色向量:
uniform vec4 ourColor;
我们可以通过着色器程序对象来动态设置这个颜色:
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(vertexColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
glGetUniformLocation找到uniform变量的位置, glUniform指定当前程序对象的uniform变量的值。glGetUniformLocation返回-1就表示没有找到这个变量。glUniform函数名后面有分量数及类型,如glUniform4f就表示需要4个float作为它的值。
2.封装一个简易的着色器类代码Github地址:https://github.com/gongjianbo/LearnTheOpenGL
整体比较简单,就是把着色器的初始化流程封装了下,以及对uniform变量操作的封装。教程是读取文件来初始化类的,我为了方便直接使用的字符串。
#include
#include
#include
#include
class MyShader
{
public:
//根据着色器代码来初始化
MyShader(const std::string& vertexCode, const std::string& fragmentCode)
{
init(vertexCode, fragmentCode);
}
//激活程序
void useProgram()
{
glUseProgram(programID);
}
// uniform工具函数
void setInt(const std::string& name, int value)
{
glUniform1i(glGetUniformLocation(programID, name.c_str()), value);
}
void setFloat(const std::string& name, float value)
{
glUniform1f(glGetUniformLocation(programID, name.c_str()), value);
}
private:
void init(const std::string& vertexCode, const std::string& fragmentCode)
{
const char* vertex_shader_code = vertexCode.c_str();
const char* fragment_shader_code = fragmentCode.c_str();
//顶点着色器
unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_code, NULL);
glCompileShader(vertex_shader);
checkError(vertex_shader, "vertex");
//片段着色器
unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_code, NULL);
glCompileShader(fragment_shader);
checkError(fragment_shader,"fragment");
//着色器程序
programID = glCreateProgram();
glAttachShader(programID, vertex_shader);
glAttachShader(programID, fragment_shader);
glLinkProgram(programID);
checkError(programID, "program");
// 删除着色器,它们已经链接到我们的程序中了
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
}
void checkError(unsigned int name,const std::string &type)
{
int check_flag;
char check_info[1024];
if (type != "program") {
glGetShaderiv(name, GL_COMPILE_STATUS, &check_flag);
if (!check_flag) {
glGetShaderInfoLog(name, 1024, NULL, check_info);
std::cout
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?