OpenGL(三)——着色器
目录
一、前言
二、Shader 2 Shader
2.1 顶点着色器
2.2 片段着色器
三、APP 2 Shader
四、顶点颜色属性
五、着色器类C++
一、前言
着色器Shader是运行在GPU上的小程序,为图形渲染管线的某个特定部分而运行。各阶段着色器之间无法通信,只有输入和输出。着色器程序需要使用GLSL语言编写。GLSL语言转为图形计算量身定制,定义了一些向量和矩阵操作。
着色器开头需要有声明版本,结束输入、输出、uniform变量、main函数。main是着色器入口点,在函数中需要处理输入变量,然后输出到输出变量中。
二、Shader 2 Shader
2.1 顶点着色器
顶点着色器的输的顶点数据直接接收输入。使用location
这一元数据指定输入变量,定义顶点数据该如何管理,才可以在CPU上配置顶点属性,layout (location = 0)表示位置属性。
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0
out vec4 vertexColor; // 为片段着色器指定一个颜色输出
void main()
{
gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色
}
2.2 片段着色器
片段着色器,它需要一个vec4
颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。如果在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)
void main()
{
FragColor = vertexColor;
}
三、APP 2 Shader
APP向Opengl的传参属性有三种:
Attribute:属性通道,传递可变参数,如颜色数据、顶点数据,纹理坐标、光照法线
uniform: 统一变量通道,传递不变的参数,如变换矩阵,RGBA->YUV
Texture Data:纹理数据
Uniform标识符是一种从CPU到GPU的着色器发送数据方式。Uniform是全局变量,可以被任何着色器程序访问。在片段着色器种使用变量Uniform。
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量
void main()
{
FragColor = ourColor;
}
在APP种如何使用联系着色器种的uniform全局变量?
- 找到着色器uniform 属性索引/位置值
- 调用函数更新该变量
while (!glfwWindowShouldClose(window))
{
// input
// -----
processInput(window);
// render
//背景颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//激活着色器
glUseProgram(shaderProgram);
//更新uniform变量
float timeValue = glfwGetTime();//获取秒级时间
float greenValue = sin(timeValue) / 2.0f + 0.5f;
int vertexUniformIndex = glGetUniformLocation(shaderProgram, "ourColor");//索引
glUniform4f(vertexUniformIndex, 0.0, greenValue, 0.0, 1.0);//更新uniform变量
//绘制三角形
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
glGetUniformLocation — Returns the location of a uniform variable
GLint glGetUniformLocation( GLuint program,
const GLchar *name);
program:着色器程序
Specifies the program object to be queried.
name: uniform变量名字
glUniform — Specify the value of a uniform variable for the current program object
void glUniform4f( GLint location,
GLfloat v0,
GLfloat v1,
GLfloat v2,
GLfloat v3);
四、顶点颜色属性
直接使用顶点输入的颜色值来显示:
float vertices[] = {
// 位置 // 颜色
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 // 顶部
};
顶点着色器程序:
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n" //位置属性值0
"layout (location = 1) in vec3 aColor;\n"//颜色属性值1
"out vec3 ourColor;\n" //输出给片段着色器
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0);\n"
" ourColor = aColor;\n" //从顶点数据读取并赋值给输出变量
"}\0";
片段着色器:
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 ourColor;\n"//使用顶点输出的数据
"void main()\n"
"{\n"
" FragColor = vec4(ourColor,1.0);\n"
"}\n\0";
更新VBO内存,并重新配置VAO解释器:
//位置属性
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);
这个图片可能不是你所期望的那种,因为我们只提供了3个颜色,而不是我们现在看到的大调色板。这是在片段着色器中进行的所谓片段插值(Fragment Interpolation)的结果。当渲染一个三角形时,光栅化(Rasterization)阶段通常会造成比原指定顶点更多的片段。光栅会根据每个片段在三角形形状上所处相对位置决定这些片段的位置。
基于这些位置,它会插值(Interpolate)所有片段着色器的输入变量。比如说,我们有一个线段,上面的端点是绿色的,下面的端点是蓝色的。如果一个片段着色器在线段的70%的位置运行,它的颜色输入属性就会是一个绿色和蓝色的线性结合;更精确地说就是30%蓝 + 70%绿。
五、着色器类C++
#pragma once
#include "glad.h"
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
class Shader
{
public:
unsigned int ID;
// constructor generates the shader on the fly
// ------------------------------------------------------------------------
Shader(const char* vertexPath, const char* fragmentPath)
{
// 1. retrieve the vertex/fragment source code from filePath
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// ensure ifstream objects can throw exceptions:
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try
{
// open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// close file handlers
vShaderFile.close();
fShaderFile.close();
// convert stream into string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure& e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
// 2. compile shaders
unsigned int vertex, fragment;
// vertex shader
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// fragment Shader
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// shader Program
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// delete the shaders as they're linked into our program now and no longer necessary
glDeleteShader(vertex);
glDeleteShader(fragment);
}
// activate the shader
// ------------------------------------------------------------------------
void use()
{
glUseProgram(ID);
}
// utility uniform functions
// ------------------------------------------------------------------------
void setBool(const std::string& name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
// ------------------------------------------------------------------------
void setInt(const std::string& name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void setFloat(const std::string& name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
private:
// utility function for checking shader compilation/linking errors.
// ------------------------------------------------------------------------
void checkCompileErrors(unsigned int shader, std::string type)
{
int success;
char infoLog[1024];
if (type != "PROGRAM")
{
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
else
{
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
}
};
类使用:
Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
...
while(...)
{
ourShader.use();
ourShader.setFloat("someUniform", 1.0f);
DrawStuff();
}
参考:
着色器 - LearnOpenGL CN (learnopengl-cn.github.io)