OpenGL

OpenGl

  • 渲染管线的主要功能:决定在给定虚拟摄像机、三维物体、光源、照明模式,以及纹理等诸多条件的情况下生成或绘制一幅二维图像的过程。
  • GPU渲染流水线(渲染管线)的三个阶段
    • 应用阶段:将需要绘制出来的几何体图元(点、线、矩、阵)输入到绘制管线的下一个阶段
      • 具体包括图元的顶点数据、摄像机位置、光照纹理等参数
    • 几何阶段:将顶点数据最终进行屏幕映射
      1. 将各个图元放入到世界坐标系统,也就是进行模型变换
      2. 根据光照纹理等计算顶点处材质的光照着色效果
      3. 根据摄像机的位置、取景范围进行观察变换和裁剪
      4. 最后进行屏幕映射,将三维模型转换到屏幕坐标系中
    • 光栅化阶段:输出到屏幕的各个像素值
几何阶段
光栅阶段
片元操作
建模坐标系
世界坐标系
观察坐标系
观察坐标系
屏幕坐标系
顶点着色器
几何,曲面着色器
裁剪
屏幕映射
模型变换 视图变换 顶点着色
顶点增删 曲面细分
投影变换 裁剪
屏幕映射
模型变换
视图变换
投影变换
屏幕映射
三角形设置
三角形遍历
片元着色器
片元操作
模板测试
片元
深度测试
颜色混合
帧缓存

OpenGl管线

OpenGl管线

显示设备
交互
输出
计算
输入
图形输入设备
图形输出设备
存储
存储介质

计算机图形系统

  • 计算机图形系统的组成
    • 图形硬件
      • 输入设备
      • 输出设备
      • 显示设备
      • 计算机
    • 图形软件
显卡
操作系统
支撑层
应用层
渲染命令
渲染命令
翻译命令
GPU
显卡驱动
深度缓存 纹理 ...
OpenGL或DirectX
游戏引擎
游戏

程序流程

  1. 初始化:初始化GLFW和GLAD
  2. 数据处理:生成VBO和VAO 发送数据到GPU 设置属性指针 告诉GPU如何解释数据
  3. 着色器:顶点和片段着色器(对数据进行处理)
  4. 渲染
  5. 善后工作
  • EBO:Element Buffer Object 索引缓冲区对象,主要用来存储顶点的索引信息

    • 如果不使用索引直接用点坐标表示,绘制一个四边形需要 [(0,0), (2,0), (1,2), (1,2), (2,0), (3,2)]
    • 如果使用索引 0->(0,0) 1->(2,0) 2->(1,2) 3->(3,2),使用[0,1,2,2,1,3]即可表示
  • VBO:Vertex Buffer Object 顶点缓冲区数据,主要用来存储顶点的各种信息

    • 模型的顶点信息写入VBO,每次绘制模型不用劳烦CPU了,可以直接从GPU的显存中获得
  • VAO:Vertex Array Object 顶点数组对象,保存了所有顶点数据属性的状态集合,存储了顶点数据的格式以及顶点数据所需的VBO对象的引用(VAO相当于对多个VBO的引用)

glut glfw glew freeglut glad gl3w 都是不同的东西
glut 已经废弃
freeGlut 替代glut
glfw 用于台式机的OpenGL,Op​​enGL ES和Vulkan开发的开源,多平台库
glew OpenGL2.0之后的一个工具函数
glad glew的升级版

  • OpenGL相当于一个大的状态机,设置之后整体改变为使用当前的状态
#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>
#include <math.h>
#include <cstring>

#pragma comment(lib,"gltools.lib")

int main(int argc, char* argv[])
{
	glfwInit();		// 初始化glfw
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);					// OpenGL版本为3.3 主版本号为设置为3
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);					// OpenGL版本为3.3 主版本号设置为3
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);	// 使用核心模式 无需后向兼容性
	//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);			// MacOs 加上
	glfwWindowHint(GLFW_RESIZABLE, false);							// 不可更改窗口大小
	// 设置宽高
	int screen_width = 1280;
	int screen_height = 720;
	// 设置窗口
	auto window = glfwCreateWindow(screen_width, screen_height, "Computer", nullptr, nullptr);
	// 如果窗口创建失败 输出Failed to create OpenGL Context
	if (window == nullptr)
	{
		std::cout << "Failed to create OpenGL Context" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);			// 将窗口的上下文设置为当前线程的主上下文
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))		// 初始化GLAD
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	glViewport(0, 0, screen_width, screen_height);			// 创建视口

	/// 上面生成创建 窗口 下面处理数据 设置属性指针

	// 三角形的顶点数据 数据需要在-1~1之间
	const float triangle[] = {
		-0.5f, -0.5f, 0.0f,		// 左下
		0.5f, -0.5f, 0.0f,		// 右下
		0.0f, 0.5f, 0.0f		// 正上
	};

	// 生成和绑定VBO和VAO
	GLuint vertext_array_object;	// VAO 可以一次性发送多个数据 而不用一个个发送
	glGenVertexArrays(1, &vertext_array_object);// 生成
	glBindVertexArray(vertext_array_object);	// 绑定
	GLuint vertext_buffer_object;	// VBO
	glGenBuffers(1, &vertext_buffer_object);	// 生成缓冲区
	glBindBuffer(GL_ARRAY_BUFFER, vertext_buffer_object);	// 绑定
	// 将顶点数据绑定到默认的缓冲中
	glBufferData(GL_ARRAY_BUFFER, sizeof(triangle), triangle, GL_STATIC_DRAW);
	// 设置顶点属性指针 告诉GPU如何解释顶点数据
		// 0 顶点着色器的位置值(这里没有);3 顶点属性是三分量的向量 ;GL_FLOAT 顶点类型 
		// GL_FALSE 表示是否希望数据标准化即映射到0~1 ;3 * sizeof(float) 步长,连续顶点属性之间的间隔,这里表示下一个顶点的数据在3个float之后 ;(void*) 0 数据的偏移量,因为给定数组一开始就是数据,所以偏移量为0,注意需要强转类型
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);	// 开启通道 默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的,意味着数据在着色器端是不可见的

	// 设置属性指针完成后 需要解绑VBO和VAO
		// 1. 防止之后绑定的VAO VBO影响当前的VAO VBO
		// 2. 使代码灵活规范 在需要渲染的时候绑VAO 然后绘制
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// 使用顶点和片段着色器 着色器源码 -> 生成并编译着色器 -> 链接着色器到着色器程序 -> 删除着色器
	const char* vertext_shader_source =	// 顶点着色器这里其实什么都没做
		"#version 330 core\n"
		"layout(location = 0) in vec3 aPos;\n"// 位置变量的属性位置值为0
		"void main()\n"
		"{\n"
		"	gl_Position = vec4(aPos, 1.0);\n"
		"}\n\0";
	const char* fragment_shader_source = // 片段着色器 设置颜色rgba = (1.0f, 0.5f, 0.2f, 1.0f)
		"#version 330 core\n"
		"out vec4 FragColor;\n"	// 输出颜色的向量
		"void main()\n"
		"{\n"
		"	FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
		"}\n\0";
	// 编译着色器
		// 顶点着色器
		int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(vertex_shader, 1, &vertext_shader_source, NULL);
		glCompileShader(vertex_shader);
		int success = -1;
		char info_log[512];
		// 检查着色器是否成功过编译,如果失败打印错误信息
		glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
		if (!success)
		{
			glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
			std::cout << "Error::Shader::Vertext::Compilation_failed\n" << info_log << std::endl;
		}
		// 片段着色器
		int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
		glCompileShader(fragment_shader);
		memset(info_log, 0, sizeof(info_log));
		// 检查着色器是否成功过编译,如果失败打印错误信息
		glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
		if (!success)
		{
			glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
			std::cout << "Error::Shader::Fragnebt::Compilation_failed\n" << info_log << std::endl;
		}
	// 链接顶点和片段着色器至一个着色器程序中
	int shader_program = glCreateProgram();
	glAttachShader(shader_program, vertex_shader);		//添加顶点着色器
	glAttachShader(shader_program, fragment_shader);	//添加片段着色器
	glLinkProgram(shader_program);
	// 检查链接是否成功 不成功打印错误信息
	glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
	if (!success)
	{
		memset(info_log, 0, sizeof(info_log));
		glGetProgramInfoLog(shader_program, 512, NULL, info_log);
		std::cout << "Error::Shader::Program::Compilation_failed\n" << info_log << std::endl;
	}

	// 然后就可以删掉着色器了 只需要使用编译好的program就可以了 着色器就不需要了
	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);

	// 开启线框模式 可以解开注释看看效果
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	while (!glfwWindowShouldClose(window))
	{
		// 清空颜色缓冲
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);	// 使用黑色填充(最顶层的背景颜色)
		glClear(GL_COLOR_BUFFER_BIT);

		// 使用着色器程序
		glUseProgram(shader_program);
		// 绘制三角形
		glBindVertexArray(vertext_array_object);	// 绑定VAO
		glDrawArrays(GL_TRIANGLES, 0, 3);			// 绘制三角形
		glBindVertexArray(0);						// 解除绑定

		// 交换缓冲并检查是否有触发事件(键盘鼠标等)
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	// 删除VAO和VBO
	glDeleteVertexArrays(1, &vertext_array_object);
	glDeleteBuffers(1, &vertext_buffer_object);

	// 清理所有的资源并正确退出程序
	glfwTerminate();
	return 0;
}

图形学:Bresenham算法绘制直线、圆、椭圆等
X-扫描线思想、Y向连贯性算法

  • 三维几何变换
    • glTranslate*()
    • glRotate*()
    • glScale*()
  • 投影
    • glFrustum()
    • gluPerspective()
    • glOrtho()
  • 窗口裁剪
  • 视口变换
    • 投影的三个函数
    • glViewport()
// display()
// 设置绘制相关的参数,完成绘制
glutDisplayFUnc(display)
// myReshape()
// 设置投影变换和视口变换的参数
glutReshapeFunc(myReshape)
// 键盘的回调函数
glutSpecialFunc(processSpecialKeys)
glutKeyboardFunc(processNormalKeys)

平移

  • 针对各个顶点进行移动即可:调用函数 glTranslate( Δ \Delta Δx, Δ \Delta Δy, Δ \Delta Δz)*
    • 即设置P(x, y, z)
    • 偏移向量T( Δ \Delta Δx, Δ \Delta Δy, Δ \Delta Δz)
    • 平移后的坐标 P`(x`, y`, z`);

[ x ‘ y ‘ z ‘ 1 ] = [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] ∗ [ x y z 1 ] + [ Δ x Δ y Δ z 1 ] \begin{bmatrix} x` \\ y` \\ z` \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} + \begin{bmatrix} {\Delta}x \\ {\Delta}y \\ {\Delta}z \\ 1 \end{bmatrix} xyz1=1000010000100001xyz1+ΔxΔyΔz1

缩放

  • P(X,Y,Z)乘上一个放缩因子( S X , S y , S z S_X, S_y, S_z SX,Sy,Sz),得到P`(X`, Y`, Z`)
    • 注意这里缩放是以锚点为原点进行的缩放,即缩放之后物体中心位置发生变化
    • 为了应对上述的中心位置变化问题,在缩放之之前先将物体中心移动到原点再缩放,缩放之后再将物体移动回原来的位置
      1. Translate( − X p , − Y p , − Z p -X_p, -Y_p, -Z_p Xp,Yp,Zp)
      2. Scale( S x , S y , S z S_x, S_y, S_z Sx,Sy,Sz)
      3. Tranlate( X p , Y p , Z p X_p, Y_p, Z_p Xp,Yp,Zp)
    • 对应OpenGl函数 flScale*(TYPE Sx, TYPE Sy, Type Sz)

[ x ‘ y ‘ z ‘ 1 ] = [ S x 0 0 0 0 S y 0 0 0 0 S z 0 0 0 0 1 ] ∗ [ x y z 1 ] \begin{bmatrix} x` \\ y` \\ z` \\ 1 \end{bmatrix} = \begin{bmatrix} S_x & 0 & 0 & 0 \\ 0 & S_y & 0 & 0 \\ 0 & 0 & S_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} xyz1=Sx0000Sy0000Sz00001xyz1

旋转

  • void glRotate*(TYPE angle, TYPE x, TYPE x, Type y, Type z);
  • P点绕Z轴逆时针旋转 α ° \alpha° α°:使用极坐标运算

{ X = r ∗ cos ⁡ ρ Y = r ∗ sin ⁡ ρ 1 { X ‘ = r ∗ cos ⁡ ( ρ + α ) = r ∗ cos ⁡ ρ ∗ cos ⁡ α − r ∗ sin ⁡ ρ ∗ cos ⁡ α Y ‘ = r ∗ sin ⁡ ( ρ + α ) = r ∗ cos ⁡ ρ ∗ sin ⁡ α − r ∗ sin ⁡ ρ ∗ cos ⁡ α 2 将 1 式 带 入 2 式 得 到 { X ‘ = X ∗ cos ⁡ α − Y ∗ sin ⁡ α Y ‘ = X ∗ sin ⁡ α + Y ∗ cos ⁡ α \left\{ \begin{array}{c} X = r*\cos\rho \\ Y = r*\sin\rho \end{array} \right. 1 \\ \left\{ \begin{array}{c} X` = r*\cos(\rho+\alpha) = r*\cos\rho*\cos\alpha - r*\sin\rho*\cos\alpha \\ Y` = r*\sin(\rho+\alpha) = r*\cos\rho*\sin\alpha - r*\sin\rho*\cos\alpha \end{array} \right.2 \\ 将1式带入2式得到 \\ \left\{ \begin{array}{c} X` = X*\cos\alpha - Y*\sin\alpha \\ Y` = X*\sin\alpha + Y*\cos\alpha \end{array} \right. {X=rcosρY=rsinρ1{X=rcos(ρ+α)=rcosρcosαrsinρcosαY=rsin(ρ+α)=rcosρsinαrsinρcosα212{X=XcosαYsinαY=Xsinα+Ycosα

其他还有绕X轴、绕Y轴旋转

绕 Z 轴 旋 转 的 矩 阵 表 示 [ x ‘ y ‘ z ‘ 1 ] = [ cos ⁡ α − sin ⁡ α 0 0 sin ⁡ α cos ⁡ α 0 0 0 0 1 0 0 0 0 1 ] ∗ [ x y z 1 ] 绕 Y 轴 旋 转 的 矩 阵 表 示 [ x ‘ y ‘ z ‘ 1 ] = [ cos ⁡ α 0 sin ⁡ α 0 0 1 0 0 − sin ⁡ α 0 cos ⁡ α 0 0 0 0 1 ] ∗ [ x y z 1 ] 绕 X 轴 旋 转 的 矩 阵 形 式 [ x ‘ y ‘ z ‘ 1 ] = [ 1 0 0 0 0 cos ⁡ α − sin ⁡ α 0 0 sin ⁡ α cos ⁡ α 0 0 0 0 1 ] ∗ [ x y z 1 ] 绕Z轴旋转的矩阵表示\\ \begin{bmatrix} x` \\ y` \\ z` \\ 1 \end{bmatrix} = \begin{bmatrix} \cos\alpha & -\sin\alpha & 0 & 0 \\ \sin\alpha & \cos\alpha & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}\\ 绕Y轴旋转的矩阵表示\\ \begin{bmatrix} x` \\ y` \\ z` \\ 1 \end{bmatrix} = \begin{bmatrix} \cos\alpha & 0 & \sin\alpha & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\alpha & 0 & \cos\alpha & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} \\ 绕X轴旋转的矩阵形式\\ \begin{bmatrix} x` \\ y` \\ z` \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\alpha & -\sin\alpha & 0 \\ 0 & \sin\alpha & \cos\alpha & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} Zxyz1=cosαsinα00sinαcosα0000100001xyz1Yxyz1=cosα0sinα00100sinα0cosα00001xyz1Xxyz1=10000cosαsinα00sinαcosα00001xyz1

  • 如何计算延任意向量( A x , A y , A z A_x, A_y, A_z Ax,Ay,Az)旋转
    • 构建新的坐标系统 O X ‾ . Y ‾ . Z ‾ O\overline{X}.\overline{Y}.\overline{Z} OX.Y.Z并且以向量( A x , A y , A z A_x, A_y, A_z Ax,Ay,Az)为Z轴
    • 将物体的点从原 O X Y Z OXYZ OXYZ坐标系统变到新的 O X ‾ . Y ‾ . Z ‾ O\overline{X}.\overline{Y}.\overline{Z} OX.Y.Z坐标系统中
    • 变换坐标系统 O X ‾ . Y ‾ . Z ‾ O\overline{X}.\overline{Y}.\overline{Z} OX.Y.Z之后延Z轴旋转
    • 旋转之后变换会原坐标系统 O X Y Z OXYZ OXYZ

[ x ‘ y ‘ z ‘ 1 ] = A − 1 [ cos ⁡ α − sin ⁡ α 0 0 sin ⁡ α cos ⁡ α 0 0 0 0 1 0 0 0 0 1 ] ∗ A ∗ [ x y z 1 ] \begin{bmatrix} x` \\ y` \\ z` \\ 1 \end{bmatrix} = A^{-1} \begin{bmatrix} \cos\alpha & -\sin\alpha & 0 & 0 \\ \sin\alpha & \cos\alpha & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * A * \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} xyz1=A1cosαsinα00sinαcosα0000100001Axyz1

其中A为从原坐标系变为 O X ‾ . Y ‾ . Z ‾ O\overline{X}.\overline{Y}.\overline{Z} OX.Y.Z坐标系的变换矩阵
A − 1 A^{-1} A1为A的逆矩阵,将坐标系统变回来,这里 A − 1 A^{-1} A1也可以写为 A T A^{T} AT,因为 A A A是一个正交矩阵
GPU的配置就是适合矩阵运算,使用矩阵运算可以减少计算次数
glLoadIdentity(); 加载单位矩阵 随后可以进行后续变换 最后再作用到图元上

矩阵变换


// P1 经过变换T1,T2;P2经过变换T3,T4

glLoadIdentity();
  Transformation T1;
  Transformation T2;
  Primitive P1;
glLoadIdentity();
  Transformation T3;
  Transformation T4;
  Primitive P2;

  • 使用堆栈管理矩阵
    • glLoadIdentity(); 初始化单位矩阵,使栈顶矩阵为单位矩阵
      • 其他的平移、旋转、缩放操作也是修改栈顶矩阵
    • glPushMatrix(); 保存当前环境,将栈顶矩阵复制一份,入栈
    • glPopMatrix(); 回复环境,普通退栈
// 假设p1,p2有相同的变换Tc; p1又经过变换T1,T2;P2经过T3,T4变换
glLoadIdentity()
  Transformation Tc;
glPushMatrix();
  Transformation T1;
  Transformation T2;
  Primitive p1;
glPopMatrix();
glPushMatrix();
  Transformation T3;
  Transformation T4;
  Primitive p2;
glPopMatrix();

所谓glPushMatrixglPopMatrix需要搭配出现,也可以进行嵌套


glPushMatrix();
  glTranslatef(-1.0, 0.0, 0.0);
  glRotatef((GLfloat)shoulder, 0.0, 0.0, 1.0);
  glTranslatef(1.0, 1.0, 1.0);

  glPushMatrix();
    glScalef(2.0, 0.4, 1.0);
    glutWireCube(1.0);
  glPopMatrix();

  glPushMatrix();
    glTranslatef(1.0, 0.0, 0.0);
    glRotatef((GLfloat)elbow, 0.0, 0.0, 1.0);
    glTranslatef(1.0, 0.0, 0.0);
    glScalef(2.0, 0.4, 1.0);
    glutWireCube(1.0);
  glPopMatrix();
glPopMatrix();

像这样搭配出现,保证被glPushMatrixglPopMatrix包裹的计算可以不用担心对其他矩阵有所影响

模型变换与视点变换

  • Model transformation 视点不变,变物体

  • View transformation 物体不变,变视点(视点可以理解为你的头,视点变换就是左右移动等)

  • glMatrixMode(GL_MODELVIEW)设置矩阵模式,告诉GL_MODELVIEW程序后面要修改的是模型-视点变换要先申明变换

  • 一般视点不变,模型变(移动,旋转,缩放)

  • void gluLookAt(eyeX, eyeZ, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)

    • 这个是专门进行视点变换的函数
    • eye* 表示设置视点坐标;center* 表示设置观察点的坐标;up* 表示视点(头)向上的向量

全局变换与局部变换

  • 每个物体都有一个自己的坐标系,跟着物体移动
    • 比如机器人的手臂要旋转时,通过全局变换来计算是很麻烦的,但是通过手臂关节的局部坐标来变换就简单很多
  • OpenGL中实现的
    • 如果是单一旋转平移,是全局变换
    • 如果是组合变换,是局部变换(全局变换的相反顺序就是局部变换)这里不好表述…得自己查了

投影

  • 人眼就是透视投影,近大远小
  • 平行投影
  • 正投影(平行投影特殊情况)
// 三个跟投影相关的函数

// 透视投影 可以不对称
// 六个参数就是棱台体的左面、右面、上面、下面、靠近的前面、远距离的背面 棱台体处于Z轴的负方向 需要百度图片 
void glFrustum(GLdouble left, GLdouble right, Gldouble bottom, GLdouble top, GLdouble near, GLdouble far);

// 透视投影 跟glFrustum一样,这个参数比较直观 glu库中的 只能是对称的
// fovy 张角视角  aspect 投影面比例 宽高比例 zNear 靠近的前面 zFar 远距离的背面
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);

// 正投影
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);


// 当用户改变窗口大笑的时候调用的回调
void myReshape(GLsizei w, GLsizei h){
  // 设置视区
  glViewport(0, 0, w, h);

  // 设定透视方式
  glMatrixMode(GL_PROJECTION);    // 改变矩阵模式为投影矩阵变换
  glLoadIdentity();
  gluPerspective(60.0, 1.0*(GLfloat)w/(GLfloat)h, 1.0, 30.0);
  // gluPerspective(60.0, 1.0, 1.0, 30.0);
  // glFrustum(-1.0, 1.0, -1.0, 1.0, 1.0, 30.0);
}

OpenGL中的使用

光照

  • 通过打光给物体明暗变化,给物体立体感

  • 光照明模型

    • 环境光:光源发出的光在场景中经过多次反射,折射后还是会对物体产生影响。环境光就是反映这种周环境对物体的光照影响
    • 漫反射光
    • 高光
  • Phong光照模型:经验模型而非真实物理模型 I = K a ∗ I a + K d ∗ I l ∗ cos ⁡ θ + K s ∗ L l ∗ cos ⁡ n ϕ I=K_a*I_a+K_d*I_l*\cos\theta+K_s*L_l*\cos^n\phi I=KaIa+KdIlcosθ+KsLlcosnϕ

    • Diffuse reflection 漫反射
    • Specular reflection 镜面反射
    • Ambient light 环境光
    • 环境光很复杂,Phong模型干脆只用一个常数表示 I e = K a ∗ I a I_e = K_a*I_a Ie=KaIa
      • I a I_a Ia环境光的亮度
      • K a K_a Ka物体表面的环境光反射系数( K a ∈ [ 0 , 1 ] K_a\in[0,1] Ka[0,1])
    • 漫反射默认粗糙表面的光会向四周均匀反射 I = K d ∗ I l ∗ cos ⁡ θ I = K_d*I_l*\cos\theta I=KdIlcosθ
      • K d K_d Kdd表示diffuse,漫射光的反射系数或反射率 K d ∈ [ 0 , 1 ] K_d\in[0,1] Kd[0,1]
      • I l I_l Ill表示light
      • 入射光角度变化时发射光变换不是根据 cos ⁡ θ \cos\theta cosθ变化的,但是大致是一个递减的变换,所以为了简化计算所以使用 cos ⁡ θ \cos\theta cosθ
    • 镜面反射与视点位置有关
      • I = K s ∗ I l ∗ cos ⁡ n ϕ I=K_s*I_l*\cos^n\phi I=KsIlcosnϕ
      • K s K_s Ks高光的反射率
      • I l I_l Il表示light
      • cos ⁡ n ϕ \cos^n\phi cosnϕ中n表示高光指数, ϕ \phi ϕ表示的不是入射角度,具体细节需要查
  • Blinn光照模型

  • 三原色:红绿蓝(RGB): 对于不同的分量都可以设置不同的RGB即 K a R , K d R , K s R , I l R K_{aR},K_{dR}, K_{sR}, I_{lR} KaR,KdR,KsR,IlR

  • OpenGL speciffication中的公式本质是Blinn模型公式内容懒得敲

  • 设置光照参数

    1. 设置好物体的法向 I = K a ∗ I a + K d ∗ I l ∗ ( N ⋅ L ) + K s ∗ I l ∗ ( N ⋅ H ) n I = K_a*I_a + K_d*I_l*(N\cdot L) + K_s*I_l*(N\cdot H)^n I=KaIa+KdIl(NL)+KsIl(NH)n
      • glNormal3f(Nx, Ny, Nz)
      • 片面的法向:两个向量的叉称就是垂直于他们的方向
      • 点的法向:点周围面片的法向相加平均得到的法向就是点的法向
    2. 打开光照
      • glEnable(GL_LIGHTING); 总的灯的开关
      • glEnable(GL_LIGHT0); OpenGL提供多个灯从0~7
    3. 设置光照参数
      • glLightfv(GL_LIGHT0, GL_AMBIENT, vLitAmbient);设置 I a I_a Ia
      • glLightfv(GL_LIGHT0, GL_DIFFUSE, vLitDiffuse);设置 I l I_l Il
      • glLightfv(GL_LIGHT0, GL_SPECULAR, vLitSpecular);设置 I l I_l Il
      • glLightfv(GL_LIGHT0, GL_POSITION, vLitPosition);设置光源的位置
    4. 设置材质
      • glMaterialfv(GL_FRONT, GL_AMBIENT, vMatAmb) 设置前向面的环境光反射系数 K a K_a Ka
      • glMaterialfv(GL_FRONT, GL_DIFFUSE, vDiffuse) 设置前向面的漫反射系数 K d K_d Kd
      • glMaterialfv(GL_FRONT, GL_SPECULAR, vMatSpe) 设置前向面的高光反射系数 K s K_s Ks
      • glMaterialfv(GL_FRONT, GL_SHININESS, vShininess) 设置前向面的高光指数 n n n
      • glMaterialfv(GL_FRONT, GL_EMISSION, vEmission) 设置前向面的自发光
      • 第一个参数除了GL_FRONT还有GL_BACK,GL_FRONT_AND_BACK
    5. 其他效果
      • 激光灯
        • glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, vSpotDir);聚光灯的方向
        • glLightfv(GL_LIGHT0, GL_SPOT_CUTOFF, vLitCutoff);聚光灯的角度(聚光灯的光像一个扇形)
        • glLightfv(GL_LIGHT0, GL_SPOR_EXPONENT, vSpotExp);聚光灯的偏离角度的衰减(偏离角度越多 光线越差)
      • 光的衰减
        • glLightfv(GL_LIGHT0, GL_CONSTANT_ATTENUATION, kc);常数衰减,默认为1
        • glLightfv(GL_LIGHT0, GL_LINEAR_ATTENUATION, kl);线性衰减,默认为0
        • glLightfv(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, kq);二次方衰减,默认为0
        • 衰 减 因 子 = 1 k c + k l ∗ d + k q ∗ d 2 衰减因子 = \frac{1}{k_c+k_l*d+k_q*d^2} =kc+kld+kqd21 d就是光到点的距离,衰减因子越小光纤约暗

GLSL着色器

代码片段

// 声明GLSL的版本为3.3
#version 330 core
// 传入参数
in vec4 vPosition;
in vec4 vColor;
// 传出数据
out vec4 color;
// 程序控制输入数据
uniform mat4 ModelViewProjectionMaxtrix;

void 
main()
{
    color = vColor;        // 颜色不变
    gl_Position = ModelViewProjectionMaxtrix * vPosition; // 位置根据传入矩阵进行修改
    // 无需返回值
}
/*
    多行注释写法
*/

GLSL着色器内置变量

基础数据类型

基本数据类型

类型描述
float32位浮点值
double64位浮点值
int有符号二进制补码的32位整数
uint无符号32位整数
bool布尔值
  • 所有变量必须在声明的同时进行初始化
int i, num = 1500;
float force, g= -9.5;
bool falling = true;
double pi = 3.1415926535;
  • 作用域

    1. 几乎跟C语言一样
    2. 在任何函数之外定义的是全局变量
    3. 在一组大括号之内定义的,只在打括号内有效
    4. for(int i=0; i<cout; i++) i只在这个for循环中有效
  • 隐式转换

    • GLSL的隐式转换比C语言少,int f = flase 会报错
    • GLSL更注重类型安全
目标类型可以隐式转换到目标类型
uintint
floatint, uint
doubleint, uint, float

聚合类型

矩阵

基本类型2D向量3D向量4D向量矩阵类型
floatvec2vec3vec4mat2 mat3 mat4
mat2x2 mat2x3 mat2x4
mat3x2 mat3x3 mat3x4
mat4x2 mat4x3 mat4x4
doubledvec2dvec3dvec4dmat2 dmat3 dmat4
dmat2x2 dmat2x3 dmat2x4
dmat3x2 dmat3x3 dmat3x4
dmat4x2 dmat4x3 dmat4x4
intivec2ivec3ivec4——————
uintuvec2uvec3uvec4——————
boolbvec2bvec3bvec4——————
  • 构建聚合类型
vec3 vel = vec3(0.0, 2.0, 3.0);
ivec3 step = ivec3(vel);

vec4 color;
vec3 RGB = vec3(color);     // 只包含color的前四个分量

vec3 whtie = vec3(1.0f);    // white = (1.0, 1.0, 1.0)
vec4 trans = vec4(whtie, 0.5);  // trans = (1.0, 1.0, 1.0, 0.5)

mat3 m = mat3(4.0);     // 3x3的矩阵中的主对角线值为4.0,其他位置值为0
mat3 m = mat3(1.0, 2.0, 3.0,
              4.0, 5.0, 6.0,
              7.0, 8.0, 9.0);
vec3 row1 = vec3(1.0, 2.0, 3.0);
vec3 row2 = vec3(4.0, 5.0, 6.0);
vec3 row3 = vec3(7.0, 8.0, 9.0);
mat3 m = mat3(row1, row2, row3);
  • 访问聚合类型
分量访问符符号描述
(x,y,z,w)与位置相关的分量
(r,g,b,a)与颜色相关的分量
(s,t,p,d)与纹理坐标相关的分量
vec3 lum = color.rrr; 
color = color.abgr; // 反转color的每个分量

// 一条语句中,只能使用一种类型的访问符
vec4 color = otherColor.rgz;      // 错误 z不是(rgba)访问符中的
// 访问元素不要超过变量类型范围
vec2 pos;
float oPos = pos.z;               // 错误 z超过2D向量(x,y)

结构体

struct Particle{
    float lifeTime;
    vec3 position;
    vec3 vecl;
};
Particle p = Particle(1.0, vec3(1.0, 2.0, 3.0), vec3(1.0, 2.0, 3.0));

数组

  • GLSL 4.3 之后可以数组套数组,之前不可
  • 使用 [ ] 来进行索引
  • 不允许使用负数索引或者超出范围的索引
  • 数组具有构造函数
float coff[3];      // 3个float元素的数组
float[3] coff;      // 同上
int intdic[];       // 未定义维数,可以后面重新声明

float coff[3] = float[3](2.22, 3.14, 4.2);  // 通过数组的构造函数创建数组
int len = coff.length();                    // 获得数组长度

mat3x4 m;
m.length(); // 列数为3
m[0].length();  // 行数为4

float digitl[m.length()];       // 设置数组大小与矩阵列数相同
float digitl[gl_in.length()];   // 设置数组大小与几何着色器的输入定点数相同

存储限制符

类型修饰符描述
const将一个变量定义为只读形式。如果它初始化时使用的是一个编译时的常量,那么它本身会称为编译时的常量
in设置这个变量为着色器阶段的输入变量
out设置这个变量为着色器阶段的输出变量
uniform设置这个变量为用户应用程序传递给着色器的数据,它对于给定的图元而言是一个常量
buffer设置应用程序共享的一块可读写的内存。这块内存也作为着色器中的存储缓存(storage buffer)使用
shared设置变量是本地工作组(local work group)中共享的。它之恩那个用于计算着色器中
  • const 存储限制符
  • in 存储限制符:可以是顶点属性(对于顶点着色器),或者是前一个着色器阶段的输出变量
  • out 存储限制符:定义着色器阶段的输出变量,例如着色器中输出变换后的其次坐标,或者片元着色器中输出的最终片元颜色
  • buffer 存储限制符:需要在应用程序中共享一大块缓存给着色器时使用buffer,着色器对缓存可以读和写
  • shared 存储限制符:只能用于计算着色器中,可以建立本地工作组共享的内存
  • uniform 存储限制符:是应用程序设置好的,在图元处理中不会发生变化,所有可用的着色阶段共享,必须定义为全局变量

uniform使用

应用程序在着色器运行之前可以,可以设置着色器中uniform变量的值,且该变量在着色器计算中不可更改。

uniform vec4 BaseColor; // 着色器中定义

着色器中可以直接通过BaseColor变量名来使用,但是在应用程序中不能直接通过变量名来设置它的值。
GLSL编译器会在链接着色器程序时创建一个uniform列表,在OpenGL中通过GLint glGetUniformLocation(GLuint program, const char* name)函数来获得uniform列表

GLint glGetUniformLocation(GLuint program, const char* name)
// name为着色器中uniform变量名,name是以NULL结尾的字符串,不存在空格
// 如果name与启用的着色器中的素有uniform变量民都不想符或者与内部保留的着色器变量名相同返回-1
void glUniform****(GLint location, ...) // 以glUniform开头的函数 来设置unifrom的值
  • 样例代码
GLint timeLoc;      // 着色器中uniform变量time的索引
GLfloat timeValue;  // 程序运行时间
ShaderInfo shaders[] = {
    {GL_VERTEX_SHADER, "triangles.vert"},	// 顶点着色器
    {GL_FRAGMENT_SHADER, "triangles.frag"}, // 片段着色器
    {GL_NONE, NULL}
};
GLuint program = LoadShaders(shaders);
glUseProgram(program);
timeLoc = glGetUniformLocation(program, "time");
glUniformlf(timeLoc, timeValue);

语句

算数运算符

优先级操作符可用类型描述
1( )——成组操作
2[ ]
f()
.(句点)
++ –
数组、矩阵、向量
函数
结构体
算数类型
数组的下标
函数调用与构造函数
访问结构体的域变量或者方法
后置递增/递减
3++ –
+ -
~
~
算数类型
算数类型
整型
布尔型
前置递增/缔结
一元正/负
一元按位 非
一元逻辑非
4* / %算术类型乘法运算
5+ -算数类型相加运算
6<< >>整型按位操作
7<> <= >=算数操作关系比较操作
8== !=任意相等操作
9&整型按位与
10^整型按位异或
11|整型按位或
12&&布尔型逻辑与
13^^布尔型逻辑异或
14||布尔型逻辑或
15a?b:c布尔型?任意:任意三元操作符
16=
+= -=
*= /=
%= <<= >>=
&= |= ^=
任意
算数类型
算数类型
整型
整型
赋值
算数赋值
17,(逗号)任意操作符序列

操作符重载

  • GLSL中大部分操作符都是经过重载的
  • 如果需要进行向量和矩阵之间的乘法(操作数和顺序非常重要,必须符合规则)
    • 基本限制条件是要求矩阵和向量的维度必须是匹配的
    • 两个向量相乘得到的是一个逐分量相乘的新向量(即按位相乘,而不会生成矩阵)
    • 两个矩阵相乘得到的是通常矩阵相乘的结果(按照矩阵乘法相乘)

流控制

  • if-else
  • switch
if(truth){

} else{

}

switch(int_value){
    case b:
    break;

    case d:
    break;

    default:
    break;
}

循环

  • for
  • while
  • do…while
for(int i=0; i<10; i++>){

}
while(n<10){

}
do{

}while(n < 10);

流控制语句

  • break
  • continue
  • return
  • discard:丢弃当前的片元,终止着色器的执行。discard语句只能用于片元着色器中。

函数

returnType functionName([accessModifier] type1 variable1, [accessModifier] type1 variable12, ...)
{
    // 函数体
    return returnValue;
}

参数限制符

访问修饰符描述
in将数据拷贝到函数中(如果没有指定修饰符,默认该形式)
const in将只读数据拷贝到函数中
out从函数中获取数值(因此输入函数的值是未定义的)
inout将数据拷贝到函数中,并且返回函数中修改的数据
  • 在函数中写入一个in类型的变量,相当于对变量的局部拷贝进行了修改,只在函数自身范围内产生作用

计算的不确定性

  • GLSL无法保证在不同的着色器中,两个完全相同的计算会得到完全一样的结果。这一问题与CPU对应用程序进行计算时的问题相同,即不同的优化方式可能会导致结果非常细微的差异。这细微的差异对于多通道的算法会产生问题,因为各个着色器阶段可能需要计算得到完全一致的结果。
  • GLSL保证着色器之间的计算不变性:invariantprecise关键字
  • 这两个关键字都需要在图形设备上完成计算过程,来确保同一表达式的结果可以保证重复(不变)性
  • 但是对于宿主计算机和图形硬件各自的计算,这两种方法都无法保证结果的完全一致性
uniform float ten;          // 假设应用程序设置该值为10.0
const float f = sin(10.0);  // 宿主机的编译器负责计算
float g = sin(ten);         // 图形硬件负责计算
void main(){
    if(f == g);         // 无法保证该值相等
}

invariant

invariant gl_Position;                  // 内置的输出变量
invariant centroid out vec3 Color;      // 自定义输出变量

invariant 可以设置任何着色器的输出变量。它可以确保如果两个着色器的输出变量使用了同样的表达式,并且表达式中的变量也是相同值,那么产生的结果也是相同的。
输出变量的作用是将一个着色器的数据从一个阶段传递到下一个。可以在着色器中用到某个变量或者内置变量之前的任何位置,对该变量设置关键字invariant

// 如果需要将着色器中的所有可变量都设置为invariant
#param STDGL invariant(all)

precise

precise可以设置任何计算中的变量或者函数的返回值。它的作用不是增加数据精度而是增加计算的可复用性。

这段还没弄懂 后面写代码注意一下

数据块接口

着色器和应用程序之间,着色器各个阶段之间共享的变量可以组织为变量快的形式,并且有时候必须采用这种形式。uniform变量可以使用uniform块,输入和输出变量可以使用in和out块,着色器的存储缓存可以使用buffer块

// uniform块的写法
uniform b{      // 限定访问符可以是uniform in out 或者 buffer
    vec4 v1;    // 块中的变量列表
    bool v2;
};             // 访问块成员时使用v1,v2

uniform b{
    vec4 v1;
    bool v2;
}name;          // 访问块成员时使用name.v1 name.v2

上述代码中,块开始的部分(代码中为 b)对应于外部访问时的接口名称,结尾部分的名称(代码中为name)用于在着色器代码中访问具体成员变量
如果着色器程序变得复杂,那么用到unifrom变量的数量会上升,通常多个着色器程序会用到同一个uniform变量。
由于uniform变量的索引位置是在glLinkProgram()的时候产生的,所以应用程序获得的索引有可能发生变化。
uniform缓存对象就是优化对uniform变量的访问,以及在不同着色器程序之间共享uniform数据的方法

着色器编译

字符串1
着色器源代码1
着色器对象1
着色器程序
可执行的着色器程序
字符串2
着色器源代码2
着色器对象2
...
  • 为什么创建多个着色器对象
    • 可能在不同的程序中复用同一个GLSL程序
  1. 调用glCreateShader来创建着色器对象
  2. 将着色器的源代码关联到这个对象上,使用glShaderSource
  3. 如果要编译着色器对象的源代码,使用glCompileShader
    1. 如果编译错误,可以通过调取编译日志来判断错误的原因
    2. glGetShaderInfoLog函数会返回一个具体实现相关问题的信息
  4. 当创建并编译了所有必要的着色器对象之后,创建可执行程序,glCreateProgram(创建空的着色器程序)
  5. 将空的着色器程序关联到必要的着色器对象上glAttachShader
  6. 如果要从程序中移除一个着色器对象glDetachShader
  7. 当所有必要的着色器惯量到着色器程序之后,可以链接对象来生成可执行程序glLinkProgram
    1. 由于着色器对象中可能存在问题,因此链接过程可能失败
    2. 调用glGetProgramiv来查询连接操作的结果,如果返回GL_TRUE则链接成功,GL_FALSE链接失败
    3. 如果链接失败,调用glGetProgramInfoLog来获取程序链接的日志信息
  8. 如果链接成功,可以使用glUseProgram来启用顶点或者片元程序
  9. 当着色器任务完成之后,可以通过glDeleteShader来删除
  10. 当不再需要着色器程序之后调用glDeleteProgram来删除着色器程序
  11. 最后,调用glIsShader来判断某个着色器对象是否存在,或者调用glIsProgram来判断着色器程序是否存在
#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>
#include <math.h>
#include <cstring>


int main(int argc, char* argv[])
{
	glfwInit();		// 初始化glfw
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);					// OpenGL版本为3.3 主版本号为设置为3
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);					// OpenGL版本为3.3 主版本号设置为3
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);	// 使用核心模式 无需后向兼容性
																	//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);			// MacOs 加上
	glfwWindowHint(GLFW_RESIZABLE, true);							// 不可更改窗口大小
																	// 设置宽高
	int screen_width = 1280;
	int screen_height = 720;
	// 设置窗口
	auto window = glfwCreateWindow(screen_width, screen_height, "Computer", nullptr, nullptr);
	// 如果窗口创建失败 输出Failed to create OpenGL Context
	if (window == nullptr)
	{
		std::cout << "Failed to create OpenGL Context" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);			// 将窗口的上下文设置为当前线程的主上下文
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))		// 初始化GLAD
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	glViewport(0, 0, screen_width, screen_height);			// 创建视口

															// 上面生成创建 窗口 下面处理数据 设置属性指针

															// 三角形的顶点数据 数据需要在-1~1之间
	const float triangle[] = {
		-0.5f, -0.5f, 0.0f,		// 左下
		0.5f, -0.5f, 0.0f,		// 右下
		0.0f, 0.5f, 0.0f		// 正上
	};

	// 生成和绑定VBO和VAO
	GLuint vertext_array_object;	// VAO 可以一次性发送多个数据 而不用一个个发送
	glGenVertexArrays(1, &vertext_array_object);// 生成
	glBindVertexArray(vertext_array_object);	// 绑定
	GLuint vertext_buffer_object;	// VBO
	glGenBuffers(1, &vertext_buffer_object);	// 生成缓冲区
	glBindBuffer(GL_ARRAY_BUFFER, vertext_buffer_object);	// 绑定
															// 将顶点数据绑定到默认的缓冲中
	glBufferData(GL_ARRAY_BUFFER, sizeof(triangle), triangle, GL_STATIC_DRAW);
	// 设置顶点属性指针 告诉GPU如何解释顶点数据
	// 0 顶点着色器的位置值(这里没有);3 顶点属性是三分量的向量 ;GL_FLOAT 顶点类型 
	// GL_FALSE 表示是否希望数据标准化即映射到0~1 ;3 * sizeof(float) 步长,连续顶点属性之间的间隔,这里表示下一个顶点的数据在3个float之后 ;(void*) 0 数据的偏移量,因为给定数组一开始就是数据,所以偏移量为0,注意需要强转类型
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);	// 开启通道 默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的,意味着数据在着色器端是不可见的

									// 设置属性指针完成后 需要解绑VBO和VAO
									// 1. 防止之后绑定的VAO VBO影响当前的VAO VBO
									// 2. 使代码灵活规范 在需要渲染的时候绑VAO 然后绘制
	glBindVertexArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// 使用顶点和片段着色器 着色器源码 -> 生成并编译着色器 -> 链接着色器到着色器程序 -> 删除着色器
	const char* vertext_shader_source =	// 顶点着色器这里其实什么都没做
		"uniform float time;\n"
		"uniform vec4 Fragcol;\n"
		"in vec4 vertCol;\n"
		"out vec4 col;"
		"void main()\n"
		"{\n"
		"	vec4 v = gl_Vertex;\n"
		"	v.y += time*0.1;\n"
		"	col = Fragcol;\n"
		"	gl_Position = gl_ModelViewProjectionMatrix * v;\n"
		"}\n\0";
	const char* fragment_shader_source = // 片段着色器 设置颜色rgba = (1.0f, 0.5f, 0.2f, 1.0f)
		"in vec4 col;"
		"void main()\n"
		"{\n"
		"	gl_FragColor = col;\n"
		"}\n\0";
	// 编译着色器
	// 顶点着色器
	int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex_shader, 1, &vertext_shader_source, NULL);
	glCompileShader(vertex_shader);
	int success = -1;
	char info_log[512];
	// 检查着色器是否成功过编译,如果失败打印错误信息
	glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
		std::cout << "Error::Shader::Vertext::Compilation_failed\n" << info_log << std::endl;
	}
	// 片段着色器
	int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
	glCompileShader(fragment_shader);
	memset(info_log, 0, sizeof(info_log));
	// 检查着色器是否成功过编译,如果失败打印错误信息
	glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
		std::cout << "Error::Shader::Fragnebt::Compilation_failed\n" << info_log << std::endl;
	}
	// 链接顶点和片段着色器至一个着色器程序中
	int shader_program = glCreateProgram();
	glAttachShader(shader_program, vertex_shader);		//添加顶点着色器
	glAttachShader(shader_program, fragment_shader);	//添加片段着色器
	glLinkProgram(shader_program);
	// 检查链接是否成功 不成功打印错误信息
	glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
	if (!success)
	{
		memset(info_log, 0, sizeof(info_log));
		glGetProgramInfoLog(shader_program, 512, NULL, info_log);
		std::cout << "Error::Shader::Program::Compilation_failed\n" << info_log << std::endl;
	}

	// 然后就可以删掉着色器了 只需要使用编译好的program就可以了 着色器就不需要了
	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);

	// 开启线框模式 可以解开注释看看效果
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	while (!glfwWindowShouldClose(window))
	{
		static float shaderTime = 0.0f;
		static float moveOrnament = 0.01f;
		shaderTime += moveOrnament;
		if (shaderTime > 5.0f)
		{
			moveOrnament = -0.01f;
		}
		else if (shaderTime < -5.0f)
		{
			moveOrnament = 0.01f;
		}
		// 清空颜色缓冲
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);	// 使用黑色填充(最顶层的背景颜色)
		glClear(GL_COLOR_BUFFER_BIT);

		// 使用着色器程序
		glUseProgram(shader_program);
		GLint time_location = glGetUniformLocation(shader_program, "time");	// 获得time在shader中索引
		glUniform1f(time_location, shaderTime);

		GLint col_location = glGetUniformLocation(shader_program, "Fragcol");	// 设置方框的颜色
		glUniform4f(col_location, 122.0f / 255.0f, 122.0f / 255.0f, 122.0f / 255.0f, 1.0f);
		// 绘制三角形
		glBindVertexArray(vertext_array_object);	// 绑定VAO
		glDrawArrays(GL_TRIANGLES, 0, 3);			// 绘制三角形
		glBindVertexArray(0);						// 解除绑定

													// 交换缓冲并检查是否有触发事件(键盘鼠标等)
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	// 删除VAO和VBO
	glDeleteVertexArrays(1, &vertext_array_object);
	glDeleteBuffers(1, &vertext_buffer_object);

	// 清理所有的资源并正确退出程序
	glfwTerminate();
	return 0;
}

通过设置着色器让物体动起来

OpenGL绘制方式

图元

  • OpenGL支持很多不同的图元类型
    • 点,线,或者三角形(可以再组合为条带、循环体、扇面等,大多数设备都支持的基础图元类型)
    • 作为细分器输入的Patch类型
    • 作为几何着色器输入的临街图元

  • 通过单一的顶点来表示,一个点就是一个四维的其次坐标值,因此点不存在面积

  • OpenGl通过显示屏幕上的一个四边形区域来模拟点

  • 渲染点图元的时候,OpenGL会通过一系列光栅化规则来判断点所覆盖的像素位置

  • 设置点的大小

    • 默认的点大小为1.0
    • glPointSize(GLfloat size)
    • 可以在顶点、细分、几何着色器中设置gl_PointSize来设置

线、条带与循环

  • OpenGL中的线指的是线段,独立的线用一对顶点来表达,每个顶点为线段端点

    • 多线段链接首位闭合的叫做 循环线
    • 开放的多线段叫做 条带线
  • 设置线段的宽度

    • void glLineWidth(GLfloat width)
    • width表示线的宽度,必须是一个大于0.0的值

三角形、条带和扇面

  • 三角形:独立的三角形
  • 条带:相邻三角形共享一条边
  • 扇面:第一个点作为共享点存在

两个三角形的共享边上的像素值因为同时被两者所覆盖,因此不可能不收到光照计算的影响
两个三角形的共享边上的像素值,不可能受到多于一个三角形的光照计算影响
上面两句话总结就是:OpenGL对于模型三角形共享边的光栅化过程不会产生任何裂缝,也不会产生重复的绘制

OpenGL图元的模式标识

图元模型OpenGL枚举量
GL_POINTS
线GL_LINES
条带线GL_LINESTRIP
循环线GL_LINE_LOOP
独立三角形GL_TRIANGLES
三角形条带GL_TRIANGLE_STRIP
三角形扇面GL_TRIANGLE_FAN

将多变形渲染为点集、轮廓线、实体

  • 一个多变形有 正面和背面,不同的面朝向观察者时可能会有不同的渲染结果
  • 默认情况下正面和背面的绘制方法是一样的,如果要修改这个属性需要设置glPolygonMode
void glPolygonMode(GLenum face, Glenum mode);
// face 必须是 GL_FRONT_ANDBACK
// mode 可以是 GL_POINT、GL_LINE、GL_FILL
//  绘制模式是 点集、轮廓线、填充模式

多变形的反转和裁剪

  • 一般来说,多变形的顶点在屏幕上应该是逆时针方向排列的(法线向上,右手比划一下大拇指向上时其余手指弯曲方向是逆时针)

    • 对于球体、环形体、茶壶等都是有向的
    • 对于莫比乌斯环、克林瓶不是逆时针
  • 如果我们要描述一个有向的模型表面,但是它的外侧需要使用逆时针方向进行描述,使用glFrontFace来反转

void glFrontFace(GLenum mode);
// 控制多变形正面的判断方式
//    默认模式是GL_CCW:多变形投影到窗口坐标系之后,顶点按照逆时针排列的面作为正面
//        模式是GL_CW: 采取顺时针方向的面将被认为是正面
  • 对于一个由不透明的且方向一致的多变形组成、完全封闭的模型表面来看说,它的所有背面多变形都是不可见的——会背正面多边形遮挡
    • 如果位于模型外侧,开启裁剪可来直接抛弃OpenGL中的背面多变形
    • 如果位于模型内存,需要指示OpenGL自动抛弃正面或者背面的多变形
void glCullFace(Glenum mode);
// 在转换到屏幕空间渲染之前,设置需要抛弃(裁剪)哪一类多变形
// mode 可以是:GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK
//    分别表示:正面、背面或者所有多变形
void glEnable() 
// GL_CULL_FACE参数来开启裁剪
void glDisable()
// 同样的 GL_CULL_FACE来关闭

OpenGL缓存数据

  • 几乎所有使用OpenGL完成的事情都用到的了缓存buffer中的数据
  • OpenGL的缓存表示为缓存对象

创建与分配内存

void glGenBuffers(GLsize n, GLuint *buffers);
// 返回n个当前未使用的缓存对象名称,并保存到buffers数组中

buffers得到一个缓存对象名称的而数组,需要将名称绑定到系统环境中的一个结合点,这样缓存对象才会真正创建出来

目标用途
GL_ARRAY_BUFFER这个结合点可以用来保存glVertexAttribPointer()设置的顶点数组数据
CL_COPY_READ_BUFFER 和 GL_COPY_WRITE_BUFFER这两个目标是一堆互相匹配的结合点,用于拷贝缓存之间的数据,并且不会引起OpenGL的状态变化,也不会产生任何特殊形式的OpenGL调用
GL_DRAW_INDIRECT_BUFFER如果采用间接绘制的方法,那么这个缓存目标用于存储绘制命令的参数
GL_ELEMENT_ARRAY_BUFFER绑定到这个目标的缓存中可以包含顶点索引数据,以便用于glDrawElements()等索引形式的绘制命令
GL_PIXEL_PACK_BUFFER这一缓存目标用于从图像中读取数据了,相关OpenGL命令包括 glGetTexImage 和 glReadPixels
GL_TEXTURE_BUFFER与GL_PIXEL_PACK_BUFFER相反,它可以作为glTexImage2D等命令的数据源使用
GL_TRANSFORM_FEEDBACK_BUFFERtransform feedback 是OpenGL提供的一种便捷方案,它可以在管线的顶点处理部分结束时(即经过顶点着色,可能还有集合着色阶段),将经过变换的顶点重新捕获,并且将部分属性写入到缓存对象中。
GL_UNIFORM_BUFFER用于创建uniform缓存对象的缓存数据
void glBindBuffer(GLenum target, GLuint buffer);
// 将名称为buffer的缓存对象绑定到target所指定的缓存结合点
// target必须是OpenGL支持的缓存帮绑定目标直以,buffer必须是通过glGenBuffers分配的缓冲对象
// 如果buffer是第一次被绑定,那么所对应的缓存对象也将被同时创建
  • 样例代码
GLuint vbo[3];
glGenBuffers(3,vbo);
glBindBuffer(GL_ARRAY_BUFFER, VBO); 

缓存输入和输出数据

void glBufferData(GLenum target, GLsizeiptr size, GLvoid* data, GLenum usage);
// 为绑定到target的缓存对象分配size大小(单位为字节)的存储空间
// 如果参数data不是NULL,那么将使用data所在的内存区域的内容来初始化整个空间
// usage允许应用程序向OpenGL端发送一个提示,指示缓存中的数据可能具备一定特定的用途

  • usage必须是内置标准标识符中的一个
    • GL_STATIC_DRAW
    • GL_DYNAMIC_COPY
    • 等等…… 下面有分解标识符的含义
分解的usage标识符意义
GL_STATIC_数据集存储内容只写入一次,然后多次使用
GL_DYNAMIC_数据存储内容会背反复写入和反复使用
GL_STREAM_数据存储内容只写入一次,然后也不会被频繁使用
_DRAW数据存储内容有应用程序负责写入,并且作为OpenGL绘制和图像命令的数据源
_READ数据存储内容通过OpenGL反馈的数据写入,然后再应用程序进行查询时返回这些数据
_COPY数据存储内容通过OpenGL反馈的数据写入,并且作为OpenGL绘制和图像命令的数据源
  • 缓存的部分初始化
void glBufferSubData(Glenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
// 使用新的数据替换缓存对象中的部分数据
// 绑定到target的缓存对象要从offset字节处开始需要使用地址data、大小为size的数据块来进行更新
// 如果offset和size的综合超出缓存对象绑定数据的范围,那么将产生一个错误
  • 样例代码
// 顶点位置
static const GLfloat position[] = {
  -1.0f, -1.0f, 0.0f, 1.0f,
  1.0f, -1.0f, 0.0f, 1.0f,
  1.0f, 1.0f, 0.0f, 1.0f,
  -1.0f, 1.0f, 0.0f, 1.0f
};
// 顶点颜色
static const GLfloat colors[] = {
  1.0f, 0.0f, 0.0f,
  0.0f, 1.0f, 0.0f,
  0.0f, 0.0f, 1.0f,
  1.0f, 1.0f, 1.0f,
};

// 缓存对象
GLuint buffer;

// 为缓存对象生成一个名称
glGenBuffer(1, &buffer);
// 将它绑定到GL_ARRAY_BUFFER目标
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// 分配足够的空间(sizeof(position) + sizeof(colors))
glBufferData(GL_ARRAY_BUFFER,                   // 目标
             sizeof(position) + sizeof(colors), // 总计大小
             NULL,                              // 无数据
             GL_STATIC_DRAW);                   // 用途

glBufferSubData(GL_ARRAY_BUFFER,                // 目标
                0,                              // 偏移地址
                sizeof(positions),              // 大小
                positions);                     // 数据
glBufferSubData(GL_ARRAY_BUFFER,                // 目标
                sizeof(positions),              // 偏移地址
                sizeof(colors),                 // 大小
                colors);                        // 数据

  • 将缓存对象的数据清楚为一个已知的值
    • glClearBufferData
    • glClearBufferSubData
void glClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data);
void glClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLintptr size, GLenum format, GLenum type, const void* data);

// 清楚缓存对象中的所有或者部分数据
// 绑定到target的缓存存储空间将使用data中存储的数据进行填充。format和type分别制定了data对象数据的格式和类型
// 首先将数据转换到internalformat所指定的格式,然后填充缓存数据的指定区域范围

对于glClearBufferData()来说,整个区域都会被指定的数据所填充
对于ClearBufferSubData()来说,填充区域时通过offset和size来指定,分别指定以字节为单位的起始偏移地址和大小

  • 缓存对象中的数据也可以使用glCopyBufferSubData()函数互相进行拷贝
    • 我们可以使用glBufferData将数据更新到独立的缓存中,然后将这些缓存直接用glCopyBufferSubData拷贝到一个较大的缓存中
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintprr writeoffset, GLsizeiptr size);
// 将绑定到readtarget的缓存对象的一部分存储数据拷贝到与writetarget相绑定的缓存对象的数据区域中
// readtarget对应的数据从readoffset位置开始赋值size个字节,然后拷贝到writetarget对应数据的writeoffset位置
// 如果readoffset或者writeoffset与szie的和超出了绑定的缓存对象的范围,那么OpenGL产生GL_INVALID_VALUE错误
  • 读取缓存的内容: glGetBufferSubData()可以绑定到某个目标的缓存中回读数据,然后将它放置到应用程序保有的一处内存当中
void glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid* data);
// 返回当前绑定到target的缓存对象中的部分或者全部数据
// 起始数据的偏移字节位置为offset,回读的数据大小为size个字节,他们将从缓存的数据区域拷贝到data所指向的内存区域中
// 如果缓存对象当前已经被映射,或者offset和size的和超出缓存对象数据区域的范围,那么将提示错误
  • 访问缓存的内容
    • 上述的glBufferDataglBufferSubDataglCopyBufferSubDataglGetBufferSubData都会进行一次数据拷贝
    • glBufferDataglBufferSubData将数据从应用程序内存拷贝到OpenGL内存中,glCopyBufferSubDataOpenGL内部拷贝到内部,glGetBufferSubDataOpenGL内存拷贝到应用程序中
    • glMapBuffer可以直接在应用程序中对OpenGL管理的内存进行访问
void* glMapBuffer(GLenum target, Glenum access);
// 将当前绑定到target的缓存对象的整个数据区域映射到客户端的地址空间中
// 根据给定的access策略,通过返回的指针对数据进行直接读或者写的操作
// 如果OpenGL无法将缓存对象的数据映射出来,那么glMapBuffer将产生一个错误并且返回NULL
标识符意义
GL_READ_ONLY应用程序仅对OpenGL映射的内存区域执行读操作
GL_WRITE_ONLY应用程序仅对OpenGL映射的内存区域执行写操作
GL_READ_WRITE应用程序对OpenGL映射的内存区域可能执行读或者写操作

access相当于程序与用户的约定,如果违反约定可能出现操作被忽略数据被破坏程序崩溃

  • 如果使用glMapBuffer获得指针,操作了数据之后,需要使用glUnmapBuffer来解除映射操作
GLboolean glUnmapBuffer(GLenum target);
// 解除glMapBuffer创建的映射
// 如果对象数据没有损坏,返回GL_TRUE
// 如果对象数据损坏,返回GL_FALSE

可以执行一个如下操作:
可以先使用glBufferData分配空间,之后进行映射glMapBuffer,直接写入数据,最后解除映射

GLuint buffer;
FILE* f;
size_t filesize;

// 打开文件确定大小
f = fopen("data.dat", "rb");
fseek(f, 0, SEEK_END);
filesize = ftell(f);
fseek(f, 0, SEEK_SET);

// 生成缓存名字并将它绑定到缓存绑定点上
// 创建缓存
glGenBuffers(1, &buffer);
glBindBuffer(GL_COPY_WRITE_BUFFER, buffer);

// 分配缓存中存储的数据空间,向data参数传入NULL即可
glBufferData(GL_COPY_WRITE_BUFFER, (GLsizei)filesize, NULL, GL_STATIC_DRAW);

// 映射缓存
void* data = glMapBUffer(GL_COPY_WRITE_BUFFER, GL_WRITE_ONLY);

// 将文件读入缓存
fread(data, 1, filesize, f);

// 关闭文件 解除映射
glUnmapBuffer(GL_COPY_WRITE_BUFFER);
fclose(f);

顶点规范

void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer);
// 设置顶点属性在index位置可访问的数据值,pointer的起始位置也就是数组中的第一组数据值,由绑定到GL_ARRAY_BUFFER目标的缓存对象中的地址偏移量确定的
// size表示每个定点中需要更新的元素个数,type表示每个元素的数据类型,normalized表示顶点数据是否需要在传递到顶点数组之前进行归一化处理
// stride表示数组中两个连续元素之间的偏移字节数,如果stride为0,那么内存中各个数据就是紧密贴合的

glVertexAttribPointer所设置的状态会保存到当前绑定的数组对象(VAO)中
size表示属性向量的元素个数(1,2,3,4)或者一个特殊的标识符GL_BGRA
type设置缓存对象中存储的数据类型

type标识符OpenGL类型
GL_BYTEGLbyte(有符号8位整型)
GL_UNSIGNED_BYTEGLubyte(无符号8位整型)
GL_SHORTGLshort(有符号16位整型)
GL_UNSIGNED_SHORTGLushort(无符号16位整型)
GL_INTGLint(有符号32位整型)
GL_UNSIGNED_INTGLuint(无符号32位整型)
GL_FIXEDGLfixed(有符号16位定点型)
GL_FLOATGLfloat(32位IEEE单精度浮点型)
GL_HALF_FLOATGLhalf(16位S1E5M10半精度浮点型)
GL_DOUBLEGLdouble(64位IEEE双精度浮点型)
GL_INT_2_10_10_10_REVGLuint(压缩数据类型)
GL_UNSIGNED_INT_2_10_10_10_REVGLuint(压缩数据类型)

如果type为GL_INT等整型,而normalize位GL_FALSE,那么如果整数将被强制转换成浮点数的形式,然后再传入顶点着色器

void glVertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer);
// 与glVertexAttribPointer相似,不过它专用于向顶点着色器中传递整型的顶点属性
// type 包括 GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT以及GL_UNSIGNED_INT

glVertexAttribIPointerglVertexAttribPointer完全等价,只是不用再传normalize参数

void glVertexAttribLPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer);
// 与glVertexAttribPointer相似,不过它专用于向顶点着色器中传递双精度浮点型的顶点属性
// 使用glVertexAttribPointer时即使指定type为GL_DOUBLE类型,也会转到单精度浮点型
void glEnableVertexAttribArray(0);	// 开启通道 默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的,意味着数据在着色器端是不可见的
void glDisableVertexAttribArray();

在OpenGL从顶点缓存中读取数据之前,必须使用glEnableVertexAttribArray启用对应的顶点属性数组
如果没启用顶点属性对应的属性数组的话,OpenGL会使用静态顶点属性(使用一个常数来填充模型中所有顶点的数据缓存)

  • 设置静态顶点属性:每个属性的静态顶点属性可以通过glVertexAttrib*()系列函数来设置
void glVertexAttrib{1234}{fds}(GLuint index, TYPE values);
void glVertexAttrib{1234}{fds}v(GLuint intdex, const TYPE* values);
void glVertexAttrib4{bdidf ub us ui}v(GLuint index, const TYPE* values);
// 设置索引为index的顶点属性的静态值。
// 如果函数名称尾部没有v,那么最多可以指定4个参数值,即x、y、z、w参数
// 如果函数尾部由v,呢么最多有4个参数值时保存在一个数组中传入的,它的地址通过value来指定,顺序依次为x、y、z、w

这些函数会自动将传入参数转换为浮点数,然后传递到顶点着色器中

  • 如果函数中需要传入整型数值,可以用其他函数,将数据归一化到[0,1]或者[-1, 1]的范围内
void glVertexAttrib4Nub(GLuint index, GLubyte x, Glubyte y, GLubyte z, GLubyte w);
void glVertexAttrib4N{bsi ub us ui}v(GLuint index, const TYPE* v);

在转换过程中将无符号参数归一化到[0,1]的范围,将有符号参数归一化到[-1, 1]的范围

  • 如果顶点属性必须声明为整数或者双精度浮点数的话,可以使用下面的函数
void glVertexAttribI{1234}{i ui}(GLuint index, TYPE values)void glVertexAttribI{123}{i ui}v(GLuint index, const TYPE* values)void glVertexAttribI4{bsi ub us ui}v(GLuint index, const TYPE* values)
// 设置一个或多个静态整型顶点属性值
// index表示顶点着色器中指定索引的顶点属性。
// values表示静态数据。
void glVertexAttribL{1234}(GLuint index, TYPE values)void glVertexAttribL{1234}v(GLuint index, const TYPE* values)// 设置一个或多个静态双精度浮点顶点属性值
// index表示顶点着色器中指定索引的顶点属性。
// values表示静态数据。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值