百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

【LearnOpenGL】06.着色器基础(着色器设置)

liuian 2025-05-08 02:44 88 浏览

开发环境

  • Windows 10
  • Visual Studio Community 2022

准备项目

1.新建一个空的C++项目:06_Shader,并设置为启动项目。

2.配置GLEW和GLFW。根据【LearnOpenGL】01.创建窗口【LearnOpenGL】03.配置GLEW,对这2项进行配置,保证编译通过。

简介

OpenGL 着色语言(OpenGL Shading Language,简称为GLSL)是 OpenGL 中使用了一种高级的图形编程语言。其中可以在 OpenGL 可编程处理器上执行的 OpenGL 着色语言代码被称为“着色器(Shader)”。

可编程着色器主要是对顶点和片元进行处理。顶点处理涉及在各个顶点发生的操作,尤其是变换和光照。片元处理是在各个片元(各个像素的数据结构)基础上进行的操作,尤其是从纹理内存读取和在各个片元上应用纹理值的操作组成的。

语法

OpenGL 着色语言是基于 ANSI C 编程语言的语法。使用 OpenGL 着色语言编写的程序的基本结构与使用 C 编写的程序的基本结构相同:

  • 一组着色器的入口点是函数 void main(),而这个函数的主体被界定在括号中。
  • OpenGL 着色语言中的常量、标识符、运算符、表达式和语句与 C 是基本相同的。
  • 用于循环的控制流、if-then-else 以及函数调用与 C 几乎完全相同。

OpenGL 着色语言也具有其专用性。添加了一些与 ANSI C 不同的语言特性:

  • 支持用于浮点数、整数和布尔值的矢量类型。
  • 运算符可以像处理标量一样处理矢量类型。
  • 使用数组语法或者作为一个结构的字段可以访问矢量的单个组分。
  • 增加了浮点矩阵,使用数组语法可以选定矩阵的列,得到一个矢量。
  • 增加了取样器(sampler)的基本类型,可以用来访问纹理内存。
  • 增加了attribute、uniform 和 varying 限定符。

数据类型:标量

类型

描述

float

声明一个单独的浮点数

int

声明一个单独的整数

bool

声明一个单独的布尔数

数据类型:矢量

类型

描述

vec2

包含2个浮点数的矢量

vec3

包含3个浮点数的矢量

vec4

包含4个浮点数的矢量

ivec2

包含2个整数的矢量

ivec3

包含3个整数的矢量

ivec4

包含4个整数的矢量

bvec2

包含2个布尔数的矢量

bvec3

包含3个布尔数的矢量

bvec4

包含4个布尔数的矢量

数据类型:矩阵

mat2 或 mat2x2

mat2x3

mat2x4

mat3x2

mat3 或 mat3x3

mat3x4

mat4x2

mat4x3

mat4 或 mat4x4

数据类型:取样器

类型

描述

sampler1D

访问一个一维纹理

sampler2D

访问一个二维纹理

sampler3D

访问一个三维纹理

samplerCube

访问一个立方贴图纹理

sampler1DShadow

访问一个带对比的一维深度纹理

sampler2DShadow

访问一个带对比的二维深度纹理

限定符:Attribute

属性限定符(或者属性变量)用来使应用程序将经常修改的数据传递到顶点着色器中。

  • 局限于浮点数标量、浮点数矢量和矩阵。
  • 不能在片元着色器中声明。

限定符:Uniform

一致限定变量(或一致变量)只能在着色器外部设置,并且是用于较少更改的数据。

  • 支持所有数据类型和所有数据类型的数组。
  • 一致变量不能被写入着色器。

限定符:Varying

易变限定变量(或者易变变量)是可以让顶点着色器与片元着色器交流结果的唯一方式。用于从顶点着色器传递为片元着色器的插值得到的信息。

调用约定:in、out

OpenGL 着色语言使用按值-返回调用作为其调用约定。这里要与按值调用做一下区分。

  • 按值调用:限定为输入参数的参数将被复制到函数中,并且不会作为引用传递。
  • 按值-返回调用:限定为输出参数的参数将会返回到调用程序。

关键字

说明

in

复制进函数但不在返回时复制,在函数内部仍然是可写的。

out

只在返回时复制,是可读的,但是在进入函数时是未定义的。

inout

复制进函数并在返回时复制。

顶点着色器

顶点着色器(Vertex Shader)包含了在提供给 OpenGL 的各个顶点上发生的操作。

#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
out vec3 v_Color;

void main()
{
    gl_Position = vec4(position, 1.0);
    v_Color = color;
}

第1行:版本声明。这里表示适用于 OpenGL 3.3以及更高版本中。

第2行:输入位置顶点属性,以及定义该属性位置值(Location)为0。

第3行:输入颜色顶点属性,以及定义该属性位置值为1。

第4行:为片元着色器指定一个颜色输出。

第8行:将顶点属性输入到顶点着色器。这里表示将位置属性输入到 gl_Position 中。

第9行:将顶点属性中颜色变量赋值给颜色输出。

片元着色器

片元着色器的目的是计算要应用到片元上的颜色或者计算片元的深度值,或者二者都计算。

#version 330 core
out vec4 color;
in vec3 v_Color;

void main()
{
	color = vec4(v_Color, 1.0);
}

第1行:版本声明。这里表示适用于 OpenGL 3.3以及更高版本中。

第2行:声明最终片元上的颜色值。

第3行:从顶点着色器传来的输入变量(名称相同、类型相同)。

第7行:将计算得到的颜色值输出。

着色器的编译

对于着色器程序,我们使用编译器来解析程序,检查是否存在错误,然后将它翻译为目标代码。然后,将一系列目标文件合并,并产生最终的可执行程序。

对于每个着色器对象

1.创建着色器对象

GLunit glCreateShader(GLenum type);

参数:

  • type

指定着色器对象类型。必须是 GL_VERTEX_SHADER、GL_FRAGMENT_SHADER、GL_GEOMETRY_SHADER、GL_COMPUTE_SHADER等中的一个。

  • 返回值:无符号整数。如果为0则说明发生了错误。


2.将着色器源代码附加到着色器对象上

void glShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);

参数:

  • shader

指定要替换源代码的着色器对象的句柄。

  • count

指定 stringlength 数组中元素的个数。

  • string

指定一个指向字符串的指针数组,其中包含加载到着色器中的源代码。

  • length

指定字符串长度的数组。


3.编译着色器源代码

void glCompileShader(GLuint shader);

参数:

  • shader

指定要编译的着色器对象。


对于着色器程序

1.创建一个着色器程序

GLuint glCreateProgram(void);
  • 返回值:无符号整数。如果为0则说明发生了错误。


2.将着色器对象关联到着色器程序

void glAttachShader(GLuint program, GLuint shader);

参数:

  • program

关联到的着色器程序。

  • shader

着色器对象。

着色器对象可以在任何时候关联到着色器程序,但是只有经过程序的成功链接之后,着色器对象的功能才能可用。

着色器对象可以同时关联到多个不同的着色器程序上。


3.链接着色器程序

void glLinkProgram(GLuint program);

参数:

  • program

指定链接的着色器程序。


5.使用着色器来处理顶点和片元

void glUseProgram(GLuint program);

参数:

  • program

指定要运行的着色器程序。

如果 program 为0,那么所有当前使用的着色器都会被清除。


Shader类

着色器源代码编写、编译、加载、管理都是比较麻烦的事,所以构建一个类专门处理这些事情很有必要。

Shader.h

#pragma once
#include <string>

class Shader
{
public:
	/// <summary>
	/// 加载源代码文件,构造着色器
	/// </summary>
	/// <param name="vertexPath">顶点着色器源代码文件路径</param>
	/// <param name="fragmentPath">片元着色器源代码文件路径</param>
	Shader(const std::string& vertexPath, const std::string &fragmentPath);

	/// <summary>
	/// 析构函数
	/// </summary>
	~Shader();

	/// <summary>
	/// 启用着色器程序
	/// </summary>
	void Bind() const;

	/// <summary>
	/// 取消使用着色器程序
	/// </summary>
	void Unbind() const;

private:
	/// <summary>
	/// 着色器程序ID
	/// </summary>
	unsigned int m_ID;

	/// <summary>
	/// 根据着色器源代码字符串,构建着色器对象
	/// </summary>
	/// <param name="vertexSource">顶点着色器源代码字符串</param>
	/// <param name="fragmentSource">片元着色器源代码字符串</param>
	void CreateShader(const std::string& vertexSource, const std::string& fragmentSource);

	/// <summary>
	/// 把着色器源代码字符串,根据指定的着色器类型,编译成着色器对象。
	/// </summary>
	/// <param name="type">着色器类型</param>
	/// <param name="source">着色器源代码字符串</param>
	/// <returns>返回着色器对象</returns>
	unsigned int CompileShader(unsigned int type, const std::string& source);
};

Shader.cpp

#include "Shader.h"
#include <GL/glew.h>
#include <fstream>
#include <sstream>
#include <iostream>

Shader::Shader(const std::string& vertexPath, const std::string &fragmentPath) :m_ID(0)
{
	// 着色器源代码字符串
	std::string vertexCode;
	std::string fragmentCode;

	std::ifstream vertexFile;
	std::ifstream fragmentFile;
	// 保证ifstream对象可以抛出异常:
	vertexFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	fragmentFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);

	try
	{
		// 打开文件
		vertexFile.open(vertexPath);
		fragmentFile.open(fragmentPath);

		std::stringstream vertexStream, fragmentStream;
		// 读取文件的缓冲内容到数据流中
		vertexStream << vertexFile.rdbuf();
		fragmentStream << fragmentFile.rdbuf();
		// 关闭文件处理器
		vertexFile.close();
		fragmentFile.close();
		// 转换数据流到string
		vertexCode = vertexStream.str();
		fragmentCode = fragmentStream.str();
	}
	catch (const std::exception&)
	{
		std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
	}

	CreateShader(vertexCode, fragmentCode);
}

Shader::~Shader()
{
	glDeleteProgram(m_ID);
}

void Shader::CreateShader(const std::string& vertexSource, const std::string& fragmentSource)
{
	unsigned int vertexShader, fragmentShader;

	// 顶点着色器对象
	vertexShader = CompileShader(GL_VERTEX_SHADER, vertexSource);

	// 片元着色器对象
	fragmentShader = CompileShader(GL_FRAGMENT_SHADER, fragmentSource);

	if (vertexShader > 0 || fragmentShader > 0)
	{
		// 创建着色器程序
		m_ID = glCreateProgram();
		// 关联着色器对象
		if (vertexShader > 0) glAttachShader(m_ID, vertexShader);
		if (fragmentShader > 0) glAttachShader(m_ID, fragmentShader);
		// 链接着色器程序
		glLinkProgram(m_ID);
	}

	// 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
	if (vertexShader > 0) glDeleteShader(vertexShader);
	if (fragmentShader > 0) glDeleteShader(fragmentShader);
}

unsigned int Shader::CompileShader(unsigned int type, const std::string& source)
{
	unsigned int shader;
	// 创建着色器对象
	shader = glCreateShader(type);
	const char* src = source.c_str();
	// 附加着色器源码
	glShaderSource(shader, 1, &src, NULL);
	// 编译着色器对象
	glCompileShader(shader);
	return shader;
}

void Shader::Bind() const
{
	glUseProgram(m_ID);
}

void Shader::Unbind() const
{
	glUseProgram(0);
}


顶点数据

有了着色器,我们就可以把每个顶点上的颜色属性展示出来了。

// 顶点数据
float vertices[] = {
 // --- position ---     --- color --- 
   -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下角
   -0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 左上角
    0.5f,  0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // 右上角
    0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f  // 右下角
};

因为我们添加了另一个顶点属性,并且更新了VBO的内存,我们就必须重新配置顶点属性指针。更新后的VBO内存中的数据现在看起来像这样:

// --- 位置属性 ---
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT), 0);
glEnableVertexAttribArray(0);

// --- 颜色属性 ---
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT), (void*)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);

由于我们现在有了两个顶点属性,我们不得不重新计算步长值:6*sizeof(GL_FLOAT)。对于每个顶点,位置属性的偏移值不变,还是0;而颜色属性的偏移值是位置属性的字节长度,即:3*sizeof(GL_FLOAT)。

完整项目

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "src/Shader.h"

unsigned int VBO;
unsigned int IBO;

void init() {
    // 顶点数据
    float vertices[] = {
     // --- position ---     --- color --- 
       -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下角
       -0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 左上角
        0.5f,  0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // 右上角
        0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f  // 右下角
    };

    // 绘制顶点的顺序
    unsigned int indices[] = {
        0, 1, 2,    // 第一个三角形的顶点数组下标
        2, 0, 3     // 第二个三角形的顶点数组下标
    };

    // 创建一个缓存对象名,并存储到VBO
    glGenBuffers(1, &VBO);
    // 将缓存对象绑定到GL_ARRAY_BUFFER
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 把顶点数据复制到当前绑定的缓存对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 创建一个缓存对象名,并存储到IBO
    glGenBuffers(1, &IBO);
    // 将缓存对象绑定到GL_ELEMENT_ARRAY_BUFFER
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
    // 把顶点索引数据复制到当前绑定的缓存对象
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // --- 位置属性 ---
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT), 0);
    glEnableVertexAttribArray(0);

    // --- 颜色属性 ---
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT), (void*)(3 * sizeof(GL_FLOAT)));
    glEnableVertexAttribArray(1);

    // 着色器
    Shader shader("res/Basic.vert", "res/Basic.frag");
    shader.Bind();
}

void display() {
    // 绘制
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

void clear() {
    // 删除缓存对象
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &IBO);
}

// main
int main(void)
{
    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Shader", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    // glew init
    if (glewInit() != GLEW_OK) {
        std::cout << "glew init error!" << std::endl;
    }

    // print opengl version
    std::cout << "OpenGL version :" << glGetString(GL_VERSION) << std::endl;

    /* init */
    init();


    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        /* display */
        display();

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }

    glfwTerminate();

    /* clear */
    clear();
    return 0;
}

运行结果:


GitHub

https://github.com/zGameDeveloper/LearnOpenGL/tree/main/Project/06_Shader

相关推荐

无法连接到这个网络是怎么回事

有可能是网络本身有问题,需要联系运营商解决。也有可能是因为网卡驱动问题,首先鼠标右击开始按钮,然后点击设备管理器,双击网络适配器,最后查看网卡驱动有没有出现黄色的感叹号,如果有的话,右击选择更新驱动程...

刷机精灵怎么解除锁屏密码(刷机精灵怎么解除锁屏密码设置)

刷机精灵解锁手机锁屏密码方法:下载好刷机精灵。打开链接手机,之后在刷机精灵页面里能看到“实用工具”的选项。解除手机解锁图案要获取root权限,若没有获取的可以在这里点击获取root权限的选项。获取了...

联想云服务官网(联想云服务官网查找手机)

华为手机也是可以下载云服务软件安装然后使用联想账号登陆云服务的。部分云服务功能将无法使用。登录联想云服务方法:点开云服务软件,选择立即使用,即出现:手机号码登入,邮箱登入,第三方登入;手机号码登入,邮...

宏基笔记本系统重装快捷键(宏基笔记本重装系统步骤)

如果用系统u盘、光盘安装:1、需要在Bios中设置从u盘或光盘启动。2、启动电脑,dcer一般默认按Del键(有些型号F2、F12)进入Bios设置界面。F2键。宏碁笔记本重装系统按F2键,进入BIO...

windows10官网打不开(win10系统官网打不开)

你可以通过以下步骤在Windows10官网上更新操作系统:1.打开windows官网,进入“下载和工具”页面。2.单击“立即下载工具”按钮,将下载“Windows10更新助手”。3.运行“...

win7无线网卡插上没反应(win7无线网卡插上没反应怎么回事)

1、如果是路由器的问题,如果原来可以用,暂时不能用了,在有就是恢复出厂设置,从新设置就可以用了(这是在物理连接正确的前提下)。2、如果是宽带本身的问题,首先直接联接宽带网线测试,如果是宽带的问题,联系...

下载爱奇艺安装(下载爱奇艺安装包)

如果你的电脑无法安装爱奇艺,可能有以下原因,第一种原因可能是你的电脑系统版本太低,升级你的电脑操作系统,可以促进爱奇艺的下载,第二种情况是你下载的爱奇艺可能捆绑一些病毒软件,系统的杀毒软件识别有霸王软...

5000元左右的电脑配置单(5000左右的电脑配置推荐2021)

五千元至六千元价位电脑主机,如果组装机,可以配置配置很高的档次,电脑主机主板可以配置不低于十二代产品,可以设四个内存条插槽,相应的内存可以配置128GB内存条2至四根,电脑处理器也同样不低于十二代产品...

快速关机(快速关机按什么键)
快速关机(快速关机按什么键)

1、我们直接长按手机右侧的电源键,大概5秒的时间,这时候手机页面会直接显示是否关机,选择关机就可以直接关机了。2、找到手机一侧的音量“+”键,再找到电源按键,之后只需同时按住音量“+”键和电源按钮,直到手机屏幕关闭即可强制关机。3、点击【设...

2025-12-25 08:05 liuian

云电脑免登录破解版(“云电脑破解版”)

虎牙YOWA云游戏平台便是一款完全免费的产品,只要玩家在自己的账号上购买过相关的产品即可通过云游戏平台直接登陆。但云游戏平台终归只是改变玩家的游戏方式,用户最终还是要回归于游戏中,如果难以保证游戏体验...

联想家庭版win7(联想家庭版笔记本电脑)

1、开机到欢迎界面时,按Ctrl+Alt+Delete,跳出帐号窗口,输入用户名:administrator,回车。2、如果这个帐号也有密码采用开机启动时按F8选“带命令行的安全模式”。3、选“Ad...

两台电脑怎么传文件最快(两台电脑怎么传文件比较快)

两台电脑之间传递文件可以有很多种方法。如果两台电脑同时在1栋楼或者一间办公室内,可以用U盘拷贝的方法传递文件。另外最快的方法还可以用通过邮箱、微信、QQ传送文件,那样速度更快,节省时间,又节省距离。将...

win7计算机图标怎么弄出来(win7怎么设置计算机图标)

您好,如果您的Win7桌面图标不见了,可以尝试以下方法:1.右键点击桌面的空白处,点击查看之后点击显示桌面图标。2.如果第一种方法不起作用,可以使用组合键“ctrl键+alt键+delete键”,...

usb打印机改wifi打印机(usb打印机改无线网络打印机)

首先要把打印机通过USB端口连接到路由器上,连接成功后路由器上的USB指示灯会亮。然后在需要使用网络打印机的电脑上安装打印机的驱动程序,这样才能够正常使用打印服务器连接的打印机。登录路由器,在左侧的系...

windows7没pdf打印机(win7系统自带的打印pdf找不到了)

建议安装Acrobat9,并安装9.1.3的AdobeReader/Acrobat的更新,去官网搜索即可,如果现有版本是9.1.0,则9.1.2和9.1.3的更新均需要安装.我实验的结果时9.0...