著色器(shaders)
-
簡單的理解澡刹,著色器就是將輸入轉(zhuǎn)換為輸出的程序篓像,同時著色器也是非常獨立的程序动遭,它們之間的通訊只能通過它們的輸入和輸出善茎。
OpenGL著色器主要內(nèi)容
1. GLSL
- 著色器典型的結(jié)構(gòu):
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
void main()
{
// 處理輸入明垢,做一些圖形操作
// 將處理結(jié)果設置到輸出變量
out_variable_name = werid_stuff_we_processed;
}
1. 聲明版本(和指定渲染模式)蚣常。
2. 定義輸入變量。
3. 定義輸出變量痊银。
4. 在main函數(shù)中設置輸出變量的值抵蚊。
- 當針對頂點著色器時,輸入變量也稱為頂點屬性(vertex attribute)溯革。我們可以聲明的頂點屬性的最大數(shù)量由硬件限制贞绳,但是OpenGL保證我們至少可以使用16個4維頂點屬性變量。硬件具體支持的數(shù)量可以通過查詢
GL_MAX_VERTEX_ATTRIBS
獲得致稀。
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
2. 類型
-
矢量(vector):在GLSL中矢量是一個由1,2,3或4個基本類型組件組成的容器冈闭。可以有如下的形式(其中n代表組件的數(shù)量):
-
vecn
:n個float的矢量(默認)豺裆。 -
bvecn
:n個boolean的矢量拒秘。 -
ivecn
:n個整數(shù)的矢量。 -
uvecn
:n個無符號整數(shù)的矢量臭猜。 -
dvecn
:n個double的矢量躺酒。
-
- 矢量通過
vec.x, vec.y, vec.z, vec.w
訪問相應的組件。在GLSL中蔑歌,顏色還可以通過rgba
訪問羹应,紋理(texture)可以通過stpq
訪問。 - 矢量允許我們進行一些靈活的組件選擇次屠,叫做swizzling园匹,語法類似下面的代碼:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
// 傳入到構(gòu)造函數(shù)中
vec2 vect = vec2(0.5, 0.7);
vec4 = result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
3. 輸入和輸出
- 當一個輸出變量與下一著色器階段的輸入變量匹配時,變量將在兩個著色器間傳遞劫灶。
- 在輸入輸出方面裸违,比較特殊的兩個著色器是頂點著色器和片元著色器。
- 頂點著色器:直接從頂點數(shù)據(jù)接收輸入本昏,使用
location
元數(shù)據(jù)來指定輸入變量以便我們可以在CPU側(cè)配置頂點屬性供汛。
- 頂點著色器:直接從頂點數(shù)據(jù)接收輸入本昏,使用
- 片元著色器:因為片元著色器需要生成一個最終輸出顏色,因此該著色器需要一個
vec4
的顏色輸出變量。如果沒有指定一個輸出顏色怔昨,那么片元的顏色將是不確定的雀久。
- 片元著色器:因為片元著色器需要生成一個最終輸出顏色,因此該著色器需要一個
- 如果我們要在兩個著色器之間傳遞變量,那么我們需要在發(fā)送的著色器中聲明一個輸出變量趁舀,在接收的著色器中聲明一個匹配的輸入變量赖捌。這樣,在鏈接著色器程序時矮烹,兩個著色器的變量會鏈接到一起越庇。下面的著色器顯示從頂點著色器傳遞顏色值到片元著色器。
// 頂點著色器
#version 330 core
layout (location = 0) in vec3 aPos;
out vec4 vertexColor; // 指定一個顏色輸出給片元著色器
void main()
{
gl_Position = vec4(aPos, 1.0);
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 設置輸出變量值 dark-red
}
// 片元著色器
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // 從頂點著色器獲取值的輸入變量(相同的名稱和數(shù)據(jù)類型)
void main()
{
FragColor = vertexColor; // 設置輸出變量值
}
-
將上述著色器應用到我們前一篇博客的代碼中擂送,運行效果如下:
著色器傳遞變量效果
4. Uniforms
- Uniforms是另一種從CPU側(cè)悦荒,即我們的OpenGL程序傳遞數(shù)據(jù)到GPU中的著色器的方式。
- Uniforms與頂點屬性的區(qū)別:
- Uniforms是全局的嘹吨,意味著每個著色器程序?qū)ο笾邪嵛叮瑄niform變量是唯一的,且可以從著色器程序中任何階段的任何著色器進行訪問蟀拷。
- 不管你將uniforms值設置為什么碰纬,值會一直保持直到被重置或更新。
- 使用
uniform
關鍵字聲明Uniform變量问芬。
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 該變量的值在我們的OpenGL代碼中設置
void main()
{
FragColor = ourColor; // 設置輸出變量值
}
- 注意:如果你聲明了一個uniform悦析,但是沒有在你的GLSL代碼使用,那么編譯器將會在編譯版本中移除此衅,這可能導致一些不確定的問題强戴。
- 設置uniform變量的值
float timeValue = glfwGetTime();
float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
1. glfwGetTime()函數(shù)獲取程序運行時間,使用`sin()`函數(shù)轉(zhuǎn)換為一個[0.0, 1.0]之間的顏色值挡鞍。
2. glGetUniformLocation()函數(shù)查詢uniform變量的位置(示例中為ourColor)骑歹。
3. 使用getUniform4f()函數(shù)設置uniform變量的值。
-
將上述片元著色器應用到我們前一篇博客的代碼中墨微,運行效果如下道媚,矩形將在綠色和黑色之間變化:
Uniform設置效果圖1
Uniform設置效果圖2
5. 更多屬性(attributes)
- 為頂點數(shù)據(jù)添加顏色屬性:
// 頂點數(shù)據(jù)
float vertices[] = {
// positions // colors
-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
};
- 調(diào)整頂點著色器以接收頂點數(shù)據(jù)中的顏色屬性:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
ourColor = aColor; // 將頂點數(shù)據(jù)輸入的顏色設置為輸出
}
- 調(diào)整片元著色器接收頂點著色器的顏色輸出:
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor, 1.0f);
}
- 包含位置和顏色屬性的VBO內(nèi)存可能如下圖所示:(圖片取自書中)
VBO內(nèi)存示意圖 - 使用
glVertexAttribPointer
函數(shù)更新頂點數(shù)據(jù)的格式,參數(shù)的值設置對比上圖的內(nèi)存情形:
// 1. 設置頂點位置屬性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 設置頂點顏色屬性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
-
運行結(jié)果:
添加顏色屬性運行效果
6. 編寫我們自己的著色器類
- 編寫一個著色器類將頂點著色器和片元著色器的創(chuàng)建和編譯翘县,以及著色器程序的鏈接封裝起來最域。
// 著色器頭文件
#pragma once
#include <glad/glad.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
class Shader
{
public:
// the program ID
unsigned int ID;
// constructor reads and builds the shader
Shader(const char* vertexPath, const char* fragmentPath);
// use/active the shader
void use();
// utility uniform functions
void setBool(const std::string& name, bool value) const;
void setInt(const std::string& name, int value) const;
void setFloat(const std::string& name, float value) const;
};
// 著色器類實現(xiàn)文件
#include <iostream>
#include <fstream>
#include "shader.h"
Shader::Shader(const char* vertexPath, const char* fragmentPath)
{
//------1. 從指定路徑讀取頂點/片元著色器程序內(nèi)容
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 file
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// read file's buffer contents into stream
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 << "EEROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
//------2. 創(chuàng)建和編譯著色器
unsigned int vertex, fragment;
int success;
char infoLog[512];
//---------2.1 創(chuàng)建和編譯頂點著色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// print compile errors if any
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
//---------2.2 創(chuàng)建和編譯片元著色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragment, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
//------3. 鏈接著色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(ID, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINK_FAILED\n" << infoLog << std::endl;
}
//------4. 鏈接著色器程序后刪除著色器對象
glDeleteShader(vertex);
glDeleteShader(fragment);
}
// 激活著色器程序
void Shader::use()
{
glUseProgram(ID);
}
// 一些輔助函數(shù),用于設置unfirom的值
void Shader::setBool(const std::string& name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
void Shader::setInt(const std::string& name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
void Shader::setFloat(const std::string& name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
- 著色器類的調(diào)用
// 聲明著色器類(路徑取決于shader文件的存放位置锈麸,文件后綴無關緊要)
Shader ourShader("./shaders/VertexShader.vs", "./shaders/FragmentShader.fs");
// 在渲染循環(huán)中調(diào)用
ourShader.use();
ourShader.setFloat("ourColor", 0.2f);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);