OpenGL 画三角形

前言:

看此篇文章请先看上一篇 OpenGL 窗口的创建
三角形的绘制,在其他语言中应该都是很好实现.
但是在OpenGL中 画一个三角形是要理解很多概念
特别是对于初学者看完应该是一头雾水+一脸懵逼
所以说 如果你懵逼了 那么属于正常 哈哈哈

本文参考了一些 learnOpenGL CN 的内容和图片

在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。
图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

着色器(shader)

当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据,图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。
这些小程序叫做着色器(Shader)

有些着色器允许开发者自己配置,这就允许我们用自己写的着色器来替换默认的。这样我们就可以更细致地控制图形渲染管线中的特定部分了,而且因为它们运行在GPU上,所以它们可以给我们节约宝贵的CPU时间。
OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的

图形渲染管线

在这里插入图片描述

这个图形渲染管线是不是很复杂,我们简单的把每个阶段看一下

  • 顶点着色器:输入单独的顶点,把3D坐标转为另一种3D坐标.
  • 图元装配:把所以处理的顶点构成一个图形,本章就是一个三角形
  • 几何着色器:可以输入新的顶点构成新的图形
  • 测试与混合: 把颜色与形状混合
  • 片段着色器:计算每个像素的最终颜色,这也是opengl高级效果的地方,比如光照 阴影等各种特效
  • 光栅化:把图形映射到最终屏幕上的像素

可以看到,图形渲染管线非常复杂,它包含很多可配置的部分。然而,对于大多数场合,我们只需要配置顶点和片段着色器就行了。几何着色器是可选的,通常使用它默认的着色器就行了。

是不是现在已经懵逼了,多看几遍, opengl 就是学起来很困难,每个人刚学都是这样的。

在现代OpenGL中,我们必须定义至少一个顶点着色器和一个片段着色器(因为GPU中没有默认的顶点/片段着色器)
所以下面我们还要定义自己的 顶点着色器和片段着色器

标准化设备坐标(Normalized Device Coordinates, NDC)

OpenGL不是简单地把所有的3D坐标变换为屏幕上的2D像素;
OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。
所有在所谓的标准化设备坐标(Normalized Device Coordinates)范围内的坐标才会最终呈现在屏幕上(在这个范围以外的坐标都不会显示)
在这里插入图片描述

顶点的输入

opengl的坐标系统也看了

开始绘制图形之前,我们必须先给OpenGL输入一些顶点数据.
在这里插入图片描述

组成三角形的三个点 ,每个点都是(x,y,z) 三维的
由于OpenGL是在3D空间中工作的,而我们渲染的是一个2D三角形,我们将它顶点的z坐标设置为0.0。这样子的话三角形每一点的深度(Depth,译注2)都是一样的,从而使它看上去像是2D的。
通常深度可以理解为z坐标,它代表一个像素在空间中和你的距离,如果离你远就可能被别的像素遮挡,你就看不到它了,它会被丢弃,以节省资源。

这个我们会作为输入发送给图形渲染管线的第一个阶段: 顶点着色器

顶点缓冲对象(Vertex Buffer Objects, VBO)

现在顶点着色器已经收到这个数据了,顶点着色器它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。

我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程

创建一个顶点着色器对象

unsigned int VBO;
glGenBuffers(1,&VBO);//根据一个独一无二的缓冲ID 这里为1 生成一个VBO对象(顶点着色器对象)
//然后把VBO 绑定上缓冲对象类型 这里顶点的缓冲对象是GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//然后把刚才的顶点数据 复制到缓冲的内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

第四个参数指定了我们希望显卡如何管理给定的数据
GL_STATIC_DRAW :数据不会或几乎不会改变。
GL_DYNAMIC_DRAW:数据会被改变很多。
GL_STREAM_DRAW :数据每次绘制时都会改变

顶点着色器(Vertex Shader)

如果我们打算做渲染的话,现代OpenGL需要我们至少设置一个顶点和一个片段着色器。

用着色器语言GLSL 编写

#version 330 core  //3.3的版本 并且是核心模式
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

在这里插入图片描述

编译着色器

在这里插入图片描述

先创建着色器对象 glCreateShader
然后把着色器源码添加到着色器对象上 glShaderSource(vertexShader, 1, &vertexShaderSource, NULL)
然后编译
glCompileShader(vertexShader);
下面的是判断是否编译过,并且输出编译错误 因为着色器代码容易写错,所以可能会编译失败

片段着色器(Fragment Shader)

片段着色器(Fragment Shader)是第二个也是最后一个我们打算创建的用于渲染三角形的着色器。
片段着色器所做的是计算像素最后的颜色输出。

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

在这里插入图片描述
下面的步骤和顶点着色器 一样也是编译

着色器程序

两个着色器现在都编译了,剩下的事情是把两个着色器对象链接到一个用来渲染的着色器程序(Shader Program)中。
在这里插入图片描述

先创建一个着色器程序对象 glCreateProgram
然后把刚才编译的 顶点和片段着色器 attach
然后link 
下面一样 检查有没有链接错误然后错误内容
最下面 把着色器对象删除掉 用不到了 节省资源
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

链接顶点属性

顶点着色器允许我们指定任何以顶点属性为形式的输入。这使其具有很强的灵活性的同时,它还的确意味着我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。所以,我们必须在渲染前指定OpenGL该如何解释顶点数据

在这里插入图片描述

在这里插入图片描述

  • 参数1:我们上面的顶点着色器代码写的 location = 0 所以这里填写0
    在这里插入图片描述
  • 参数2: 顶点属性的大小是3 vec3
  • 参数3: 数据类型 :GLSL中 vec的数据类型都是 GL_FLOAT
  • 参数4:是否被标准化 填false就行
  • 参数5:步长 我们这里是3个float 紧密排列的就3*sizeof(float)
  • 参数6:它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0 强制转换一下

绘制三角形

在这里插入图片描述

在这里插入图片描述

完整code:

// testOpenGl_1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//


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




const char * vertexShaderSource = "#version 330 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"
"}\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.0f);\n"
"}\n\0";




inline void frameBuffer_size_callback(GLFWwindow* w, int width, int height)
{
	glViewport(0, 0, width, height);

}

inline void processInput(GLFWwindow* w)
{
	if (glfwGetKey(w, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(w, true);
}

int main()
{
    std::cout << "Hello opengl!\n"; 


	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "learnOpengl", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "opengl window failed" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, frameBuffer_size_callback);

	/*glad:load all opengl function pointer */
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "failed to initalize glad"<<std::endl;
		return -1;
	}

	/*build and compile our shader program */
	int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);

	int isSuccess;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &isSuccess);
	if (!isSuccess)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR:: shader compile failed" << infoLog << std::endl;
	}

	int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &isSuccess);
	if (!isSuccess)
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR:: shader compile failed" << infoLog << std::endl;
	}
	/*link shaders*/
	int shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);

	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &isSuccess);
	if (!isSuccess)
	{
		glGetShaderInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR:: shader LINK failed" << infoLog << std::endl;
	}

	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);





	float vertices[] = {
		-0.5f,-0.5f, 0.0f,
		0.5f,-0.5f,0.0f,
		0.0f,0.5f,0.0f
	};
	unsigned int VBO,VAO;
	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, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);



	while (!glfwWindowShouldClose(window))
	{
		processInput(window);

		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 3);

		glfwSwapBuffers(window);
		glfwPollEvents();

	}

	glfwTerminate();

	system("pause");
}



  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值