認識Shader
在計算機圖形學中,Shader(著色器)是一種運行在 GPU(圖形處理單元)上的程序,用于控制圖形渲染過程中頂點和像素的處理。著色器是 OpenGL、Direct3D、Vulkan 等圖形 API 的核心組成部分,它們允許開發者自定義渲染管線的各個階段,從而實現高度自定義的視覺效果。
著色器類型
頂點著色器(Vertex Shader):
處理每個頂點的數據。
可以進行頂點位置的變換、紋理坐標的生成、光照計算等。
通常用于實現 3D 變換、動畫效果等。
片段著色器(Fragment Shader):
處理每個像素(或片段)的數據。
決定每個像素的顏色和透明度。
用于實現光照、陰影、紋理映射、后處理效果等。
幾何著色器(Geometry Shader):
在頂點著色器和片段著色器之間處理整個圖元(如點、線、三角形)。
可以生成新的頂點或圖元。
用于實現高級的幾何變換和效果。
張量著色器(Tessellation Shader):
包括兩個階段:域著色器(Domain Shader)和原語生成著色器(Hull Shader)。
控制細分曲面的生成和處理。
用于實現復雜的曲面細分和建模。
計算著色器(Compute Shader):
執行通用計算任務,不直接參與渲染管線。
可以并行處理大量數據。
用于實現物理模擬、圖像處理、AI 計算等。
著色器編寫
著色器通常使用 GLSL(OpenGL Shading Language)或 HLSL(High-Level Shader Language)等語言編寫。以下是一個簡單的 GLSL 頂點著色器示例:
#version 330 core// in代表輸入
//vec3代表是一個三維向量(xyz)
// aPos 是我們自己取的名字
// layout(location = n) 代表告訴vertexShaser去vao的第n個屬性描述中去取數據。
// gl_Position 是glsl的內置變量,負責向后續階段輸出頂點位置處理的結果。 一般為NDC坐標。
//vec4代表 四維向量。layout(location = 0) in vec3 aPos; // 頂點位置void main() {gl_Position = vec4(aPos, 1.0); // 將頂點位置傳遞給片段著色器
}
以及一個簡單的 GLSL 片段著色器示例:
#version 330 core//out代表輸出變量
//vec4代表四維向量,rgba,紅綠藍,透明度
//FragColor 是最終輸出的變量
out vec4 FragColor; // 輸出顏色void main() {FragColor = vec4(1.0, 0.5, 0.2, 1.0); // 設置像素顏色為橙色
}
著色器編譯和鏈接
在 OpenGL 中,著色器需要經過編譯和鏈接才能使用。以下是一個簡化的流程:
編寫著色器代碼:使用 GLSL 編寫頂點著色器和片段著色器代碼。
創建著色器對象:使用
glCreateShader
創建著色器對象。編譯著色器:使用
glShaderSource
和glCompileShader
編譯著色器代碼。創建程序對象:使用
glCreateProgram
創建程序對象。附加著色器:使用
glAttachShader
將編譯好的著色器附加到程序對象。鏈接程序:使用
glLinkProgram
鏈接程序對象。使用程序:使用
glUseProgram
使用鏈接好的程序對象。
著色器的優勢
高度自定義:開發者可以自定義渲染管線的各個階段,實現復雜的圖形效果。
性能優化:著色器在 GPU 上運行,可以利用 GPU 的并行處理能力,提高渲染性能。
跨平臺兼容性:著色器語言(如 GLSL)在不同的圖形 API 和硬件平臺上具有較好的兼容性。
總之,著色器是現代圖形渲染中不可或缺的一部分,它們為開發者提供了強大的工具來實現高度自定義的視覺效果。
void prepareShader()
{cout << "prepareShader()" << endl;//1.完成vs和fs,并且裝字符串const char* vertexShaderSource ="#version 460 core\n""layout(location = 0) in vec3 aPos;\n""void main()\n""{\n""gl_Position = vec4(aPos.x,aPos.y,aPos.z,1.0);\n""}\n\0";const char* fragmentShaderSource ="#version 330 core\n""out vec4 FragColor;\n""void main()\n""{\n""FragColor = vec4(1.0f,0.5f,0.2f,1.0);\n""}\n\0";//2創建Shader程序(vs和fs)GLuint vertex, fragment;vertex = glCreateShader(GL_VERTEX_SHADER);fragment = glCreateShader(GL_FRAGMENT_SHADER);//3為shader程序輸入Shader代碼glShaderSource(vertex, 1, &vertexShaderSource, NULL); //字符串用\0進行結尾,不需要告訴他長度。glShaderSource(fragment, 1, &fragmentShaderSource, NULL); //字符串用\0進行結尾,不需要告訴他長度。int success = 0;char infoLog[1024];//4 執行Shader編譯glCompileShader(vertex);//5 檢查是否正確編譯glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);if (!success) // 0有問題,非0沒有問題{glGetShaderInfoLog(vertex, 1024, NULL, infoLog); //獲取日志信息cout << "Error Vertex Shaser Complie: " << infoLog << endl;}//4 執行fragment編譯glCompileShader(fragment);//5 檢查是否正確編譯glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);if (!success) // 0有問題,非0沒有問題{glGetShaderInfoLog(vertex, 1024, NULL, infoLog); //獲取日志信息cout << "Error Fragment Shader Complie: " << infoLog << endl;}// 創建Program殼子GLuint program = 0;program = glCreateProgram();//將編譯后的結果放進ProgramglAttachShader(program, vertex);glAttachShader(program, fragment);// 執行program鏈接操作,形成最終的shader程序。glLinkProgram(program);//檢查鏈接錯誤glGetProgramiv(program, GL_LINK_STATUS, &success);if (!success) // 0有問題,非0沒有問題{glGetProgramInfoLog(program, 1024, NULL, infoLog); //獲取日志信息cout << "Error Program Link: " << infoLog << endl;}//清理glDeleteShader(vertex);glDeleteShader(fragment);}
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的頭文件。 用于加載 OpenGL 函數指針
#include "GLFW/glfw3.h" // 用于創建窗口和處理輸入。
#include <assert.h>
#include "wrapper/checkerror.h"
#include "application/application.h"
using namespace std;/*
* 目標:學習DrawArray進行繪制
* 1.采用GL_TRANGLES進行繪制三角形
* 這里可以縮放窗體,實驗NDC坐標的作用。
* prepareVAOForGLTriangles:構建四個頂點的VAO
*
*/GLuint vao, program;void prepareSingleBuffer()
{//1.準備頂點位置數據與顏色數據float positions[] = {-0.5f,-0.5f,0.0f,0.5f,-0.5f,0.0f,0.0f,0.5f,0.0f,};float colors[] = {1.0f,0.0f,0.0f,0.0f,1.0f,0.0f,0.0f,0.0f,1.0f,};//2.為位置&顏色數據各自生成一個VBOGLuint posVbo = 0, colorVbo = 0;GL_CALL(glGenBuffers(1, &posVbo));GL_CALL(glGenBuffers(1, &colorVbo));//3.給兩個分開的VBO各自填充數據//positions填充數據GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, posVbo));GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW));//colors填充數據GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, colorVbo));GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW));//生成VAO并且綁定GLuint vao = 0;glGenVertexArrays(1, &vao);glBindVertexArray(vao);// 分別將數據放入VAO//描述位置屬性glBindBuffer(GL_ARRAY_BUFFER, posVbo);//只有綁定了vbo,下面的屬性描述才于此有關系glEnableVertexAttribArray(0);glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),0);glBindBuffer(GL_ARRAY_BUFFER, colorVbo);//只有綁定了vbo,下面的屬性描述才于此有關系glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);glBindVertexArray(0); //將VAO進行解綁}void prepareInterLeavedBuffer()
{cout << "prepareInterLeavedBuffer()" << endl;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};//2.為位置&顏色數據各自生成一個VBOGLuint vbo = 0;GL_CALL(glGenBuffers(1, &vbo));GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vbo));GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW));//生成VAO并且綁定//GLuint vao = 0;glGenVertexArrays(1, &vao);glBindVertexArray(vao);// 分別將數據放入VAO//描述位置屬性glBindBuffer(GL_ARRAY_BUFFER, vbo);//只有綁定了vbo,下面的屬性描述才于此有關系glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);//顏色屬性//glBindBuffer(GL_ARRAY_BUFFER, vbo);//只有綁定了vbo,下面的屬性描述才于此有關系glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float),(void*)(3*sizeof(float)));glBindVertexArray(0); //將VAO進行解綁
}void prepareVAOForTriangles()
{cout << "prepareVAOForTriangles()" << endl;//目前暫時不用顏色float vertices[] = {-0.5f,-0.5f,0.0f,0.5f,-0.5f,0.0f,0.0f,0.5f,0.0f,0.5f,0.5f,0.0f,0.8f,0.8f,0.0f,0.8f,0.0f,0.0f};//2.生成一個VBOGLuint vbo = 0;GL_CALL(glGenBuffers(1, &vbo));GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vbo));GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW));//生成VAO并且綁定glGenVertexArrays(1, &vao);glBindVertexArray(vao);// 分別將數據放入VAO//描述位置屬性glBindBuffer(GL_ARRAY_BUFFER, vbo);//只有綁定了vbo,下面的屬性描述才于此有關系glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glBindVertexArray(0); //將VAO進行解綁
}void prepareShader()
{cout << "prepareShader()" << endl;//1.完成vs和fs,并且裝字符串const char* vertexShaderSource ="#version 460 core\n""layout(location = 0) in vec3 aPos;\n""void main()\n""{\n""gl_Position = vec4(aPos.x,aPos.y,aPos.z,1.0);\n""}\n\0";const char* fragmentShaderSource ="#version 330 core\n""out vec4 FragColor;\n""void main()\n""{\n""FragColor = vec4(1.0f,0.5f,0.2f,1.0);\n""}\n\0";//2創建Shader程序(vs和fs)GLuint vertex, fragment;vertex = glCreateShader(GL_VERTEX_SHADER);fragment = glCreateShader(GL_FRAGMENT_SHADER);//3為shader程序輸入Shader代碼glShaderSource(vertex, 1, &vertexShaderSource, NULL); //字符串用\0進行結尾,不需要告訴他長度。glShaderSource(fragment, 1, &fragmentShaderSource, NULL); //字符串用\0進行結尾,不需要告訴他長度。int success = 0;char infoLog[1024];//4 執行Shader編譯glCompileShader(vertex);//5 檢查是否正確編譯glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);if (!success) // 0有問題,非0沒有問題{glGetShaderInfoLog(vertex, 1024, NULL, infoLog); //獲取日志信息cout << "Error Vertex Shaser Complie: " << infoLog << endl;}//4 執行fragment編譯glCompileShader(fragment);//5 檢查是否正確編譯glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);if (!success) // 0有問題,非0沒有問題{glGetShaderInfoLog(vertex, 1024, NULL, infoLog); //獲取日志信息cout << "Error Fragment Shader Complie: " << infoLog << endl;}// 創建Program殼子//GLuint program = 0;program = glCreateProgram();//將編譯后的結果放進ProgramglAttachShader(program, vertex);glAttachShader(program, fragment);// 執行program鏈接操作,形成最終的shader程序。glLinkProgram(program);//檢查鏈接錯誤glGetProgramiv(program, GL_LINK_STATUS, &success);if (!success) // 0有問題,非0沒有問題{glGetProgramInfoLog(program, 1024, NULL, infoLog); //獲取日志信息cout << "Error Program Link: " << infoLog << endl;}//清理glDeleteShader(vertex);glDeleteShader(fragment);}void render()
{//GL_CALL( glClear(1) );//加上gl_call如果產生錯誤可以打印出來,雖然vs智能提示有問題GL_CALL(glClear(GL_COLOR_BUFFER_BIT));//渲染操作//1. 使用當前的programglUseProgram(program);//綁定當前的vaoglBindVertexArray(vao);//發出繪制指令//繪制三角形// 如果點數不夠,就會連到0,0點 (123 和456)繪制兩組三角形glDrawArrays(GL_TRIANGLES, 0, 6);//繪制的模式// GL_TRIANGLES 繪制三角形// 默認以三個點繪制一組三角形,不夠三個點就不顯示// GL_TRIANGLE_STRIP:// 末尾點數為偶數【n-2 n-1 n】,基數【n-1 n-2 n】 n從0開始// 并且會復用之前的點// GL_TRIANGLE_FAN:以扇形序列v0為起點,連接三角形。// GL_LINES:繪制直線// GL_LINE_STRIP// }void onResize(int width,int height)
{GL_CALL(glViewport(0, 0, width, height));cout << "onResize " << endl;
}void frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{std::cout << "窗體的最新大小為:" << width << "高度為:" << height << std::endl;//更新窗體的大小glViewport(0, 0, width, height);}void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){// key 字母按鍵碼 // scancode 物理按鍵碼 // action:0抬起1按下2長按// mods:是否有shift(1)或ctrl(2) cout << "key = " << key << " scancode = " << scancode << " action = " << action << " mods = " << mods << endl;if (key == GLFW_KEY_W){//按下了w}else if (action == GLFW_PRESS){//按下了}else if (action == GLFW_RELEASE){//抬起}}int main(int argc,char**argv)
{cout << "===================================" << endl;if (app->init(800,600) == -1){return -1;}//設置監聽幀緩沖窗口大小回調函數。//glfwSetFramebufferSizeCallback(win, frameBufferSizeCallback);//glfwSetKeyCallback(win, keyCallback);app->setResizeCallBack(onResize);app->setkeyCallBack(keyCallback);//設置OpenGL視口以及清理顏色glViewport(0,0,800,600);glClearColor(0.2f,0.3f,0.3f,1.0f);prepareShader();prepareVAOForTriangles();// 3. 執行窗體循環// while (app->update() == 0 ){render();}// 4. 退出程序前做相關清理app->destroy();cout << "===================================" << endl;//const double M_PI = 3.14159265358979323846;//double radians = M_PI / 2; // 90度,轉換為弧度//double sineValue = sin(radians);//std::cout << "sin(" << radians << ") = " << sineValue << std::endl;//system("pause");return 0;
}