前言
著色器是運行在GPU上的程序俩檬,為圖形渲染管線特定部分而運行萎胰,從某種意義上來說,著色器是把輸入轉(zhuǎn)化為輸出的程序棚辽。著色器程序是完全獨立的程序技竟,著色器之間不能直接通信,只能通過輸入輸出實現(xiàn)通信屈藐。在前面的幾篇文章中榔组,粗略地介紹了一下著色器,接下來我將詳細的介紹著色器估盘。
GLSL
著色器是使用一種叫GLSL的類C語言寫成的瓷患。GLSL是為圖形計算量身定制的,它包含一些針對向量和矩陣操作的有用特性遣妥。用法與特性可學(xué)習(xí) GLSL 中文手冊
封裝我們自己的著色器類
通過我們幾篇博客了解到著色器需要編寫擅编、編譯、管理著色器箫踩,這是件麻煩事爱态,在著色器主題的最后,我們會寫一個類來讓我們的生活輕松一點境钟,它可以從硬盤讀取著色器锦担,然后編譯并鏈接它們,并對它們進行錯誤檢測慨削,這就變得很好用了洞渔。這也會讓你了解該如何封裝目前所學(xué)的知識到一個抽象對象中。
我們會把著色器類全部放在在頭文件里缚态,主要是為了學(xué)習(xí)用途磁椒,當然也方便移植。類結(jié)構(gòu)設(shè)計如下:
- 聲明一個抽象類 ShaderStreamInterface玫芦,該抽象類代表一個著色器程序的 GLSL 源代碼浆熔。
#define _SHADER_SRC(...) #__VA_ARGS__
#define SHADER_SRC(...) _SHADER_SRC(__VA_ARGS__)
class ShaderStreamInterface {
public:
virtual char *getVertexCode() = 0;
virtual char *getFragmentCode() = 0;
protected:
char *vertextCode;
char *fragmentCode;
};
- 聲明一個著色器類 Shader,勇于編譯桥帆、鏈接医增、管理著色器慎皱。
在Shader.h 文件中代碼如下
#define GLEW_STATIC
#include <GL/glew.h>
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include "ShaderStreamInterface.h"
/** 著色器類 */
class Shader {
public:
// 程序ID
unsigned int ID;
// 構(gòu)造器讀取并構(gòu)建著色器
Shader(ShaderStreamInterface *shaderStream);
// 使用/激活程序
Shader * use();
// uniform工具函數(shù)
Shader * setBool(const std::string &name, bool value);
Shader * setInt(const std::string &name, int value);
Shader * setFloat(const std::string &name, float value);
Shader * setColor(const std::string &name, float red, float green, float blue, float alpha);
};
在Shader.cpp 文件中的代碼如下:
Shader:: Shader(ShaderStreamInterface *shaderStream) {
// 1. 從文件路徑中獲取頂點/片段著色器
const char* vShaderCode = shaderStream->getVertexCode();
const char* fShaderCode = shaderStream->getFragmentCode();
// 2. 編譯著色器
unsigned int vertex, fragment;
int success;
char infoLog[512];
// 頂點著色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// 打印編譯錯誤(如果有的話)
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if(!success) {
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
};
// 片段著色器也類似
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
// 著色器程序
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::LINKING_FAILED\n" << infoLog << std::endl;
}
// 刪除著色器,它們已經(jīng)鏈接到我們的程序中了叶骨,已經(jīng)不再需要了
glDeleteShader(vertex);
glDeleteShader(fragment);
}
Shader * Shader:: use() {
glUseProgram(ID);
return this;
}
Shader * Shader:: setBool(const std::string &name, bool value) {
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
return this;
}
Shader * Shader:: setInt(const std::string &name, int value) {
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
return this;
}
Shader * Shader:: setFloat(const std::string &name, float value) {
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
return this;
}
Shader * Shader:: setColor(const std::string &name, float red, float green, float blue, float alpha) {
glUniform4f(glGetUniformLocation(ID, name.c_str()), red, green, blue, alpha);
return this;
}
上面的設(shè)計方式有如下幾大優(yōu)點:
- 功能職責(zé)分明茫多,ShaderStreamInterface 抽象類專注于著色器代碼的編寫、加載和管理邓萨,地梨,Shader 類專注于著色器的編譯、鏈接缔恳、管理著色器的生命周期宝剖。
- 擴展性強,如果需要編寫新的著色器代碼歉甚,只需要新寫一個類繼承自 ShaderStreamInterface 即可万细。
- 易于調(diào)試,可以分別對 Shader 類以及 ShaderStreamInterface 其子類進行單元測試纸泄。
現(xiàn)在整個著色器類已經(jīng)設(shè)計完成赖钞,接下來我們來完成繪制一個有顏色的三角形
繪制有顏色的三角形
- 定義 ShaderStream 類繼承自 ShaderStreamInterface,并編寫頂點著色器和片段著色器代碼
.hpp
#include <stdio.h>
#include "ShaderStreamInterface.h"
/** 章節(jié)案例 */
class ShaderStream: public ShaderStreamInterface {
public:
ShaderStream();
char *getVertexCode() {
return vertextCode;
}
char *getFragmentCode() {
return fragmentCode;
}
protected:
char *vertexStream();
char *fragmentStream();
};
.cpp
ShaderStream:: ShaderStream() {
// 頂點著色器
vertextCode = vertexStream();
// 片段著色器代碼
fragmentCode = fragmentStream();
}
char * ShaderStream:: vertexStream() {
return SHADER_SRC(
\#version 330 core\n
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
}
);
}
char * ShaderStream:: fragmentStream() {
return SHADER_SRC(
\#version 330 core\n
out vec4 FragColor;
in vec3 ourColor;
void main() {
FragColor = vec4(ourColor, 1.0);
}
);
}
- 來到主應(yīng)用程序 main 函數(shù)中聘裁,引入相關(guān)類
#include "ShaderStream.hpp"
#include "Shader.hpp"
- 初始化glfw雪营,初始化窗口,初始化 glew衡便,前面已經(jīng)講過了献起,這里不再重復(fù)。
- 初始化著色器
// 著色器代碼對象
ShaderStream *shaderCode = new ShaderStream();
// 著色器程序
Shader *shader = new Shader(shaderCode);
- 創(chuàng)建頂點數(shù)組镣陕、頂點緩沖對象谴餐、頂點數(shù)組對象,這塊是跟之前你好 三角形一樣一樣的
// 創(chuàng)建頂點數(shù)組多想 VAO
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 // 頂部
};
unsigned int vao, vbo;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 位置屬性
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);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
- 執(zhí)行渲染循環(huán)
while (!glfwWindowShouldClose(window)) {
procesInput(window);
glClearColor(0.2, 0.2, 0.3, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
shader->use();
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
- 處理退出呆抑,釋放資源
std::cout << "Hello, World!\n";
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);
glDeleteProgram(shader->ID);
glfwTerminate();
運行command + R 運行岂嗓,得到一個五彩斑斕的三角形:
FileSharing.png
如果你運行出來有問題或者出錯了,情仔細檢查一下代碼鹊碍,或者在本文下方下載筆者寫的demo厌殉。