Learn OpenGL 03 着色器

news2024/9/27 5:53:49

GLSL

着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。

一个典型的着色器有下面的结构:

#version version_number
in type in_variable_name;
in type in_variable_name;

out type out_variable_name;

uniform type uniform_name;

int main()
{
  // 处理输入并进行一些图形操作
  ...
  // 输出处理过的结果到输出变量
  out_variable_name = weird_stuff_we_processed;
}

数据类型 

GLSL中包含C等其它语言大部分的默认基础数据类型:intfloatdoubleuintbool。GLSL也有两种容器类型,分别是向量(Vector)和矩阵(Matrix)

向量

GLSL中的向量是一个可以包含有2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n代表分量的数量):

类型含义
vecn包含n个float分量的默认向量
bvecn包含n个bool分量的向量
ivecn包含n个int分量的向量
uvecn包含n个unsigned int分量的向量
dvecn包含n个double分量的向量

大多数时候我们使用vecn,因为float足够满足大多数要求了。

向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:

vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;

你可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;然而,你不允许在一个vec2向量中去获取.z元素。我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:

vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);

输入与输出

顶点着色器从顶点数据中直接接收输入。为了定义顶点数据该如何管理,我们使用location这一元数据指定输入变量,这样我们才可以在CPU上配置顶点属性。

片段着色器,它需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。

如果我们打算从一个着色器向另一个着色器发送数据,我们必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似的输入。当类型和名字都一样的时候,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。

Uniform

Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。

x修改Uniform的值:

首先需要找到着色器中uniform属性的索引/位置值。当我们得到uniform的索引/位置值后,我们就可以更新它的值了。这次我们不去给像素传递单独一个颜色,而是让它随着时间改变颜色:

如果glGetUniformLocation返回-1就代表没有找到这个位置值。最后,我们可以通过glUniform4f函数设置uniform值。注意,查询uniform地址不要求你之前使用过着色器程序,但是更新一个uniform之前你必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置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)

因为OpenGL在其核心是一个C库,所以它不支持类型重载,在函数参数不同的时候就要为其定义新的函数;glUniform是一个典型例子。

uniform对于设置一个在渲染迭代中会改变的属性是一个非常有用的工具,它也是一个在程序和着色器间数据交互的很好工具

更多属性!

顶点数据中新加了颜色属性

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    // 顶部
};

主要的变化就是要重新配置顶点属性指针

glVertexAttribPointer函数的前几个参数比较明了。这次我们配置属性位置值为1的顶点属性。颜色值有3个float那么大,我们不去标准化这些值。

由于我们现在有了两个顶点属性,我们不得不重新计算步长值。为获得数据队列中下一个属性值(比如位置向量的下个x分量)我们必须向右移动6个float,其中3个是位置值,另外3个是颜色值。这使我们的步长值为6乘以float的字节数(=24字节)。
同样,这次我们必须指定一个偏移量。对于每个顶点来说,位置顶点属性在前,所以它的偏移量是0。颜色属性紧随位置数据之后,所以偏移量就是3 * sizeof(float),用字节来计算就是12字节。

// 位置属性
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);

创建自己的着色器类并且从文件读取

创建一个Shader.h和Shader.cpp两个文件

Shader.h文件主要存放Shader类和其中的函数声明,方便随时查看其中函数的功能

Shader.cpp文件主要存放函数的定义

要从文件读取着色器必须使用使用C++文件流

CPU不能直接读取硬盘中的内容,必须先移动到内存中,也就是要经过硬盘-->FileBuffer-->StringBuffer,之后因为OpenGL不操作String,操作的是char数组,所以我们要一个String的缓存把StringBuffer里的东西变成char数组。

Shader.h

#pragma once
#include <string>
class Shader
{
public:
	Shader(const char* vertexPath, const char* fragmentPath); // 构造器读取并构建着色器
	std::string vertexString;
	std::string fragmentString;
	const char* vertexSource;
	const char* fragmentSource;
	unsigned int ID;  //Shader Program ID
	void use();  // 使用/激活程序

	// uniform工具函数
	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;
private:
	void checkCompileErrors(unsigned int ID, std::string type);//检查着色器或者程序是否编译错误
};

Shader.cpp

#include "Shader.h"
#include <iostream>
#include <fstream>
#include <SStream>

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

using namespace std;

Shader::Shader(const char* vertexPath, const char* fragmentPath)
{
	//从文件路径中获取顶点/片段着色器
	ifstream vertexFile;
	ifstream fragmentFile;
	stringstream vertexSStream;
	stringstream fragmentSStream;
	//打开文件
	vertexFile.open(vertexPath);//与硬盘文件建立链接
	fragmentFile.open(fragmentPath);
	//保证ifstream对象可以抛出异常:
	vertexFile.exceptions(ifstream::failbit || ifstream::badbit);//检查是否打开失败或者读取到了坏文件
	fragmentFile.exceptions(ifstream::failbit || ifstream::badbit);

	try
	{
		if (!vertexFile.is_open() || !fragmentFile.is_open())
		{
			throw exception("open file error");
		}

		//读取文件缓冲内容到数据流
		vertexSStream << vertexFile.rdbuf();//硬盘-->FileBuffer-->StringBuffer
		fragmentSStream << fragmentFile.rdbuf();

		//转换数据流到string
		vertexString = vertexSStream.str();
		fragmentString = fragmentSStream.str();

		vertexSource = vertexString.c_str();
		fragmentSource = fragmentString.c_str();

		// 编译着色器
		unsigned int vertex, fragment;
		// 顶点着色器
		vertex = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(vertex, 1, &vertexSource, NULL);
		glCompileShader(vertex);
		checkCompileErrors(vertex, "VERTEX");

		// 片段着色器
		fragment = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(fragment, 1, &fragmentSource, NULL);
		glCompileShader(fragment);
		checkCompileErrors(fragment, "FRAGMENT");

		// 着色器程序
		ID = glCreateProgram();
		glAttachShader(ID, vertex);
		glAttachShader(ID, fragment);
		glLinkProgram(ID);
		checkCompileErrors(ID, "PROGRAM");

		// 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
		glDeleteShader(vertex);
		glDeleteShader(fragment);

	}
	catch (const std::exception& ex)
	{
		printf(ex.what());
	}
}

void Shader::use()
{
	glUseProgram(ID);
}

void Shader::checkCompileErrors(unsigned int ID, std::string type) {
	int sucess;
	char infolog[512];

	if (type != "PROGRAM")
	{
		glGetShaderiv(ID, GL_COMPILE_STATUS, &sucess);
		if (!sucess)
		{
			glGetShaderInfoLog(ID, 512, NULL, infolog);
			cout << "shader compile error:" << infolog << endl;
		}
	}
	else
	{
		glGetProgramiv(ID, GL_LINK_STATUS, &sucess);
		if (!sucess)
		{
			glGetProgramInfoLog(ID, 512, NULL, infolog);
			cout << "program linking error:" << infolog << endl;
		}
		cout << "编译成功!" << endl;
	}
}

main.cpp

只需要加上两句代码就可以了,这里的两个txt文件需要自己创建一个,之后就可以循环使用了

  Shader* myshader = new Shader("vertexSource.txt", "fragmentSource.txt");
[...]

  myshader->use();

练习

1.修改顶点着色器让三角形上下颠倒:参考解答

将顶点着色器输出的y坐标取反即可

#version 330 core									  
layout(location = 0) in vec3 aPos;   // 位置变量的属性位置值为 0
layout(location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1
out vec4 vertexColor;								   
void main(){										   
   gl_Position = vec4(aPos.x, -aPos.y, aPos.z, 1.0);   
   vertexColor = vec4(aColor,1.0);					   
}

2.使用uniform定义一个水平偏移量,在顶点着色器中使用这个偏移量把三角形移动到屏幕右侧:参考解答

顶点着色器的x坐标加上一个随时间增加的偏移量即可

//main.cpp
myshader->setFloat("offset", 0.01+glfwGetTime());


#version 330 core									  
layout(location = 0) in vec3 aPos;   // 位置变量的属性位置值为 0
layout(location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1
out vec4 vertexColor;	
uniform float offset;
void main(){										   
   gl_Position = vec4(aPos.x+offset, aPos.y, aPos.z, 1.0);   
   vertexColor = vec4(aColor,1.0);					   
}

3.使用out关键字把顶点位置输出到片段着色器,并将片段的颜色设置为与顶点位置相等(来看看连顶点位置值都在三角形中被插值的结果)。做完这些后,尝试回答下面的问题:为什么在三角形的左下角是黑的?:参考解答

三角形的左下角是黑的,是因为左下角顶点(-0.5,-0.5,0),因为颜色值是0到1,负值会被截断到0,就是黑色。插值以后靠近左下角的地方就是黑色的

vertex 

#version 330 core									  
layout(location = 0) in vec3 aPos;   // 位置变量的属性位置值为 0
layout(location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1
out vec4 vertexColor;	
out vec3 pos;
uniform float offset;
void main(){										   
   gl_Position = vec4(aPos.x+offset, aPos.y, aPos.z, 1.0);   
   vertexColor = vec4(aColor,1.0);		
   pos = aPos;
}

fragment

#version 330 core			
in vec4 vertexColor;			
out vec4 FragColor;	
in vec3 pos;
void main(){				
	FragColor = vec4(pos,1);

	}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1507260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[java入门到精通] 10 常用API , 正则表达式 , Collection集合

今日目标 BigInteger类BigDecimal类Arrays类包装类String类的常用方法正则表达式Collection集合 1 BigInteger类 1.1 概述 概述 : java.math.BigInteger类是一个引用数据类型 , 可以用于计算一些大的整数 , 当超出基本数据类型数据范围的整数运算时就可以使用BigInteger了。…

Arduino Uno使用Mind+实现图形化编程

文章目录&#xff1a; 一&#xff1a;软件下载安装 1.下载安装 1.1 开发软件 2.辅助软件 2.主控板 二&#xff1a;基础 1.LED 2.传感器 3.智能小车 三&#xff1a;学习资源 一&#xff1a;软件下载安装 1.下载安装 1.1 开发软件 Arduino IDE代码编程软件&#…

集合和数组的相关操作

目录 1.数组转集合(引用类型数组) 2.数组转集合(基础类型数组) 3.集合转数组 4.集合之间是否相交 5.获取两个集合的交集 6.集合转为字符串 1.数组转集合(引用类型数组) (1)Arrays.asList 示例&#xff1a; String[] colArr new String[6];colArr[0] "1";co…

Fastgithub

上Github太慢、打不开怎么办&#xff1f; 选择之一是Fastgithub工具&#xff0c;同时支持win, linux, mac。 1. 工作原理 从公共dns服务器拿到github的大量ip数据&#xff0c;检测哪些ip可用&#xff0c;哪些ip访问速度最佳&#xff0c;然后编写一个本地版的dns服务&#xff0…

小巧设备,大能量:探索口袋中的远程控制神器

在这个科技日新月异的时代&#xff0c;我们的生活被各种手机软件所包围。几乎每个人都有一个甚至多个手机&#xff0c;你是否也有遇到过需要远程操作自己某一台手机的场景呢&#xff1f;今天&#xff0c;我要向大家推荐一款神奇的手机远程操作神器&#xff0c;让你可以随时随地…

【EtherCAT实践篇】十、SSC工具使用说明

EtherCAT Slave Stack Code&#xff08;SSC&#xff09;是倍福提供的EtherCAT从站源代码生成工具&#xff0c;基于SSC工具&#xff0c;可以大大降低EtherCAT数据通讯程序及xml设计难度。 本操作参考SSC软件包中的EtherCAT Slave Design Quick Guide.pdf文档。 1、创建一个SSC工…

项目解决方案:视频监控接入和录像系统设计方案(下)

目 录 1.概述 2. 建设目标及需求 2.1建设总目标 2.2 需求描述 ​2.3 需求分析 3.设计依据与设计原则 3.1设计依据 3.2 设计原则 4.建设方案设计 4.1系统方案设计 4.2组网说明 5.产品介绍 5.1视频监控综合资源管理平台介绍 5.2视频录像服务器和存储 5.2.…

后勤管理系统|基于SSM 框架+vue+ Mysql+Java+B/S架构技术的后勤管理系统设计与实现(可运行源码+数据库+设计文档+部署说明+视频演示)

目录 文末获取源码 前台首页功能 员工注册、员工登录 个人中心 公寓信息 员工功能模块 员工积分管理 管理员登录 ​编辑管理员功能模块 个人信息 ​编辑员工管理 公寓户型管理 ​编辑公寓信息管理 系统结构设计 数据库设计 luwen参考 概述 源码获取 文末获取源…

bug总结(1)--变量取错

a c t i v i t y [ ′ t a g n a m e ′ ] 应为 activity[tag_name]应为 activity[′tagn​ame′]应为couponActivitList[0][‘name’] .隐藏的bug&#xff0c;在测试中竟然测不出来&#xff0c;而且上线了好久。为啥会出现这种低级错误呢&#xff1f;第一是写的时候不够仔细认…

C语言:基于单链表实现的泊车管理系统

一、需求 &#xff08;1&#xff09;管理员方账号登录&#xff1b; &#xff08;2&#xff09;车位管理显示&#xff1a;车位状态&#xff1b; &#xff08;3&#xff09;收费管理&#xff1a;小轿车 5元/小时&#xff0c;面包车6元/小时&#xff0c;大货车或客车7元/小时&a…

算法(6种思想、7种查找)、与数据结构(数组/链表/栈与队列/树)整理总结

算法 除了这里提到的算法思想和查找算法&#xff0c;算法还有别的类型&#xff1a; 排序算法&#xff1a; 对一组元素进行排序的算法。常见的排序算法包括冒泡排序、快速排序、归并排序等。 图算法&#xff1a; 解决图结构相关问题的算法&#xff0c;例如最短路径问题、最小…

Zookeeper详解

1.Zookeeper概述 1.Zookeeper概念 Zookeeper是 Apache Hadoop 项目下的一个子项目&#xff0c;是一个树形目录服务 Zookeeper 翻译过来就是动物园管理员&#xff0c;他是用来管 Hadoop&#xff08;大象&#xff09;、Hive(蜜蜂)、Pig(小猪)的管理员。简称zk Hadoop: 存储海…

【周总结周末日常】

周总结 完成任务开发并且与前端联调通过 完成已开发功能的冒烟测试 修复测试中出现的一些数据显示问题 2024/3/10 晴 温度适宜 这周天气比上周好多了&#xff0c;最起码见到好几次太阳 周六在世纪公园溜达一会儿&#xff0c;偶尔呼吸下大自然&#xff0c;挺棒的…

力扣hot100:152.乘积最大子数组(动态规划)

一个子数组问题&#xff0c;我们要使用线性dp&#xff0c;最好先考虑以i结尾&#xff0c;如果定义dp[i]为前i个数最大子数组乘积值 那么dp[i-1]就无法转移到dp[i]。因此我们先考虑dp[i]定义为以第i个数结尾的最大子数组乘积值。 53. 最大子数组和 最大子数组和是一个动态规划问…

重要通告 | 公司更名为“浙江实在智能科技有限公司”

更名公告 升级蜕变、砥砺前行 因业务快速发展和战略升级&#xff0c;经相关政府机构批准&#xff0c;自2024年3月1日起&#xff0c;原“杭州实在智能科技有限公司”正式更名为“浙江实在智能科技有限公司”。 更名后&#xff0c;公司统一社会信用代码不变&#xff0c;业务主体…

蓝桥杯单片机---第十二届省赛题目解析

文章目录 比赛题目一、代码相关定义、声明1.头文件声明2.变量声明 二、主要函数1.main函数2.按键扫描3.数码管显示4.电压模式1、2输出 & LED显示5.定时器中断6.消除85C显示 三、次要函数1.初始化函数Init2.按键函数Key3.LED函数Led4.数码管函数Seg5.iic函数中6.onewire函数…

python(5)之处理数组

上次代码结果如下&#xff1a; 1、处理数组的缺失值 1、isnan&#xff08;&#xff09;函数 isnan&#xff08;&#xff09;函数是Numpy模块里的一个可以标记数组中缺失值的位置 代码示例如下&#xff1a; import numpy as np ac np.array([1,2,3,2,3,4,5,9,np.nan,1]) p…

分布式CAP原理详解

引言 随着互联网的飞速发展&#xff0c;越来越多的应用开始采用分布式系统来提高系统的可用性和扩展性。在分布式系统中&#xff0c;CAP原理是一个非常重要的理论&#xff0c;它描述了分布式系统在面临网络分区时&#xff0c;如何在一致性、可用性和分区容错性之间进行权衡。本…

PDF24 Creator PDF工具箱 v11.17.0

软件介绍 可将大部分文件转成pdf格式的免费软件&#xff0c;安装好后会在你的打印机里看到一个叫PDF24的虚拟打印机&#xff0c;你可将要转成pdf格式的文件打印时选虚拟打印机PDF24&#xff0c;也可以直接将文件以拖拉方式拉进这软件的主视窗编辑区里&#xff0c;它会自动转成…

信息系统项目管理师006:车联网(1信息化发展—1.2现代化基础设施—1.2.3车联网)

文章目录 1.2.3 车联网1.体系框架2.链接方式3.场景应用 记忆要点总结 1.2.3 车联网 车联网是新一代网络通信技术与汽车、电子、道路交通运输等领域深度融合的新兴产业形态。智能网联汽车是搭载先进的车载传感器、控制器、执行器等装置&#xff0c;并融合现代通信与网络技术&…