曲面细分着色器使用-程序员宅基地

技术标签: openGL曲面细分着色器  openGL  

openGL系列文章目录

提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

前言

术语Tessellation(镶嵌)是指一大类设计活动,通常是指在平坦的表面上,用各种几何
形状的瓷砖相邻排列以形成图案。它的目的可以是艺术性的或实用性的,很多例子可以追
溯到几千年前[TS16]。
在3D 图形学中,Tessellation 指的是有点不同的东西(曲面细分),但显然是由它的经典
对应物(镶嵌)启发而成的。在这里,曲面细分指的是生成并且操控大量三角形以渲染复
杂的形状和表面,尤其是使用硬件进行渲染。曲面细分是OpenGL 核心近期才增加的新功
能,在2010 年的4.0 版本中出现。①

一、OpenGL 中的曲面细分

OpenGL 对硬件曲面细分的支持,通过3 个管线阶段提供:
(1)曲面细分控制着色器;
(2)曲面细分器;
(3)曲面细分评估着色器。
第(1)和第(3)阶段是可编程的;而中间的第(2)阶段不是。为了使用曲面细分,
程序员通常会提供控制着色器和评估着色器。
曲面细分器(其全名是曲面细分图元生成器,或TPG)是硬件支持的引擎,可以生成固
定的三角形网格。②控制着色器允许我们配置曲面细分器要构建什么样的三角形网格。然后,
评估着色器允许我们以各种方式操控网格。然后,被操控过的三角形网格,会作为通过管
线前进的顶点的源数据。回想一下图2.2,在管线上,曲面细分着色器位于顶点着色器和几
何着色器阶段之间。
让我们从一个简单的应用程序开始,该应用程序只使用曲面细分器创建顶点的三角形网
格,然后在不进行任何操作的情况下显示它。为此,我们需要以下模块。
(1)C++/OpenGL 应用程序:
创建一个摄像机和相关的MVP 矩阵,视图(v)和投影(p)矩阵确定摄像机朝向,模
型(m)矩阵可用于修改网格的位置和方向。
(2)顶点着色器:
在这个例子中基本上什么都不做,顶点将在曲面细分器中生成。
(3)曲面细分控制着色器:
指定曲面细分器要构建的网格。
(4)曲面细分评估着色器:
将MVP 矩阵应用于网格中的顶点。
(5)片段着色器:
只需为每个像素输出固定颜色。
程序12.1 显示了整个应用程序的代码。即使像这样的简单示例也相当复杂,因此许多代
码元素都需要解释。请注意,这是我们第一次使用除顶点和片段着色器之外的组件构建
GLSL 渲染程序。因此,我们实现了createShaderProgram()的4 参数重载版本。
在这里插入图片描述

二、代码

1.c++主程序

#include "glew/glew.h"
#include "glfw/glfw3.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "camera.h"
#include "Utils.h"
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

static const float pai = 3.1415926f;
float toRadians(float degrees) {
     return degrees * 2.f * pai / (float)360.f; }

static const int screenWidth = 1920;
static const int screenHeight = 1080;

static const int numVAOs = 1;
static const int numVBOs = 4;

//-------------------------------------------Utils util = Utils();
float cameraX = 0.f, cameraY = 0.f, cameraZ = 0.f;     //相机位置
float terLocX = 0.f, terLocY = 0.f, terLocZ = 0.f;     //曲面细分矩形位置
GLuint renderingProgram = 0;
GLuint vao[numVAOs] = {
     0 };

// variable allocation for display
GLuint mvpLoc = 0;
int width = 0, height = 0;
float aspect = 0.f;
glm::mat4 mMat(1.f), vMat(1.f), pMat(1.f), mvMat(1.f), mvpMat(1.f);

float tessInner = 30.f;   //网格内边缘的大小?
float tessOuter = 20.f;   //网格外边缘的大小?

void init(GLFWwindow* window)
{
    
	renderingProgram = Utils::createShaderProgram("vertShader.vert", "tessCShader.glsl", "tessEShader.glsl", "fragShader.frag");
	cameraX = 0.5f, cameraY = -0.5f, cameraZ = 2.f;
	terLocX = 0.f, terLocY = 0.f, terLocZ = 0.f;

	glfwGetFramebufferSize(window, &width, &height);
	aspect = (float)width / (float)height;
	pMat = glm::perspective(toRadians(45.f), aspect, 0.1f, 1000.f);

	glGenVertexArrays(numVAOs, vao);
	glBindVertexArray(vao[0]);

	//不需要顶点缓冲区吗?
	//从C++ / OpenGL 应用程序发送到管线(即在VBO 中)的顶点不会被渲染,但通常会被当作控制点
	//在这个特定示例中,没有任何控制点被发送,
}

void display(GLFWwindow* window, double currentTime)
{
    
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glClearColor(0.1f, 0.2f, 0.5f, 1.f);

	glUseProgram(renderingProgram);

	vMat = glm::translate(glm::mat4(1.f), glm::vec3(-cameraX, -cameraY, -cameraZ));
	
	mMat = glm::translate(glm::mat4(1.f), glm::vec3(terLocX, terLocY, terLocZ));
	mMat = glm::rotate(mMat, toRadians(55.f), glm::vec3(1.f, 0.f, 0.f));

	mvpMat = pMat * vMat * mMat;

	mvpLoc = glGetUniformLocation(renderingProgram, "mvp_matrix");

	glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, glm::value_ptr(mvpMat));

	glFrontFace(GL_CCW);

	//指定补丁原语的参数, pname指定要修改的参数,并且必须为GL_PATCH_VERTICES。 value为pname指定的参数指定新值。
	//当pname为GL_PATCH_VERTICES时,value指定将用于构成单个面片基元的顶点数。修补程序基元由镶嵌细分控制着色器(如果存在)消耗,随后用于镶嵌细分。
	//使用glDrawArrays或类似函数指定图元时,每个面片均由参数控制点组成,每个控制点均由从enabeld顶点数组中提取的顶点表示。
	//参数必须大于零,并且小于或等于GL_MAX_PATCH_VERTICES的值。
	glPatchParameteri(GL_PATCH_VERTICES, 1);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);  //用线段填充


	//在这个特定示例中,没有任何控制点被发送,但我们仍然需要指定至少一个。
	//类似地,在glDrawArrays()调用中,我们指示起始值为0,顶点数量为1,即使我们实际上没有从C++程序发送任何顶点。
	glDrawArrays(GL_TRIANGLES, 0, 1);  //
	glDrawArrays(GL_PATCHES, 0, 1);
}

void window_size_callback(GLFWwindow* window, int newWidth, int newHeight)
{
    
	glViewport(0, 0, newWidth, newHeight);
	aspect = (float)newWidth / (float)newHeight;
	pMat = glm::perspective(toRadians(45.f), aspect, 0.1f, 1000.f);
}

int main(int argc, char** argv)
{
    
	int glfwState = glfwInit();
	if (GLFW_FALSE == glfwState)
	{
    
		cout << "GLFW initialize failed, invoke glfwInit()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << endl;
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
	glfwWindowHint(GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_PROFILE);
	glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

	GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "Tessellation Grid Only", nullptr, nullptr);
	if (!window)
	{
    
		cout << "GLFW crate window failed, invoke glfwCreateWindow()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << endl;
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

	glfwMakeContextCurrent(window);

	glfwSetFramebufferSizeCallback(window, window_size_callback);

	int glewState = glewInit();
	if (GLEW_OK != glewState)
	{
    
		cout << "GLEW initialize failed, invoke glewInit()......Error file:" << __FILE__ << "......Error line:" << __LINE__ << endl;
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

	glfwSwapInterval(1);

	init(window);

	while (!glfwWindowShouldClose(window))
	{
    
		display(window, glfwGetTime());
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);

	return 0;
}

着色器程序

1. 顶点着色器

#version 460 core

uniform mat4 mvp_matrix;

void main(void)
{
    
}

2.片元着色器

#version 460 core

out vec4 color;
uniform mat4 mvp_matrix;

void main(void)
{
    
	color = vec4(1.f, 0.f, 0.f, 1.f);
}

3.曲面细分控制着色器

#version 460 core

uniform mat4 mvp_matrix;
layout(vertices = 1) out;

void main(void)
{
    
	gl_TessLevelOuter[0] = 6;
	gl_TessLevelOuter[1] = 6;
	gl_TessLevelOuter[2] = 6;
	gl_TessLevelOuter[3] = 6;

	gl_TessLevelInner[0] = 12;
	gl_TessLevelInner[1] = 12;
}


4.

曲面细分评估着色器

运行效果

在这里插入图片描述

代码分析

曲面细分器生成由两个参数定义的顶点网格:内层级别和外层级别。在这种情况下,
内层级别为12,外层级别为6——网格的外边缘被分为6 段,而跨越内部的线被分为
12 段。
程序12.1 中的特别相关的新结构被高亮显示。让我们首先讨论第一部分——C++/
OpenGL 代码。
编译这两个新着色器,跟顶点和片段着色器完全相同。然后将它们附加到同一个渲染程
序,并且链接调用保持不变。唯一的新项目是用于指定要实例化的着色器类型的常量——新
常量如下:
GL_TESS_CONTROL_SHADER
GL_TESS_EVALUATION_SHADER
请注意display()函数中的新项目。glDrawArrays()调用现在指定GL_PATCHES。当使用
曲面细分时,从C++/OpenGL 应用程序发送到管线(即在VBO 中)的顶点不会被渲染,但
通常会被当作控制点,就像我们在贝塞尔曲线中看到的那些一样。一组控制点被称作“补
丁”,并且在使用曲面细分的代码段中,GL_PATCHES 是唯一允许的图元类型。“补丁”中
顶点的数量在glPatchParameteri()的调用中指定。在这个特定示例中,没有任何控制点被发
送,但我们仍然需要指定至少一个。类似地,在glDrawArrays()调用中,我们指示起始值为
0,顶点数量为1,即使我们实际上没有从C++程序发送任何顶点。
对glPolygonMode()的调用指定了如何光栅化网格。默认值为GL_FILL。而我们的代码
中显示的是GL_LINE,如我们在图12.1 中看到的那样,它只会导致连接线被光栅化(因此
我们可以看到由曲面细分器生成的网格本身)。如果我们将该行代码更改为GL_FILL(或将
其注释掉,从而使用默认行为GL_FILL),我们将得到如图12.2 所示的版本。

在这里插入图片描述

现在让我们来过一遍4 个着色器。如前所述,顶点着色器几乎没什么可做的,因为
C++/OpenGL 应用程序没有提供任何顶点。它包含的是一个统一变量声明,以和其他着色器
相匹配,以及一个空的main()。在任何情况下,所有着色器程序都必须包含顶点着色器。
曲面细分控制着色器指定曲面细分器要生成的三角形网格的拓扑结构。通过将值分配给
名为gl_ TessLevelxxx 的保留字,设置6 个“级别”参数——两个“内部”和4 个“外部”
级别。我们这里细分了一个由三角形组成的大矩形网格,称为四边形。①级别参数告诉曲面

细分器在形成三角形时如何细分网格,它们的排列如图12.3 所示。
请注意控制着色器中的代码行:
layout (vertices=1) out;
这与之前的GL_PATCHES 讨论有关,用来指定从
顶点着色器传递给控制着色器(以及“输出”给评估着
色器)的每个“补丁”的顶点数。在我们现在这个程序
中没有任何顶点,但我们仍然必须指定至少一个,因为
它也会影响控制着色器被执行的次数。稍后这个值将反
映控制点的数量,并且必须与C++/OpenGL 应用程序中
glPatchParameteri()调用中的值匹配。
接下来让我们看一下曲面细分评估着色器。它以一行代码开头,形如:
layout (quads, equal_spacing, ccw) in;
乍一看这好像与控件着色器中的“out”布局语句有关,但实际上它们是无关的。相反,
这行代码是我们指示曲面细分器去生成排列在一个大矩形(“四边形”)中顶点的位置。它
还指定了细分线段(包括内部和外部)具有相等的长度(稍后我们将看到长度不等的细分
的应用场景)。“ccw”参数指定生成曲面细分网格顶点的缠绕顺序(在当前情况下,是逆
时针)。
然后,由曲面细分器生成的顶点被发送到评估着色器。因此,评估着色器既可以从控制
着色器(通常作为控制点),又可以从曲面细分器(曲面细分网格)接收顶点。在程序12.1
中,仅从曲面细分器接收顶点。
评估着色器对曲面细分器生成的每个顶点执行一次。可以使用内置变量gl_TessCoord 访
问顶点位置。曲面细分网格的朝向使得它位于X-Z 平面中,因此gl_TessCoord 的X 和Y 分
量被应用于网格的X 和Z 坐标。网格坐标,以及gl_TessCoord 的值,范围为0.0~1.0(这在
计算纹理坐标时会很方便)。然后,评估着色器使用MVP 矩阵定向每个顶点(这在前面章
节的示例中,是由顶点着色器完成的)。
最后,片段着色器只为每个像素输出一个恒定的黄色。当然,我们也可以使用它来为我
们的场景应用纹理或光照,就像我们在前面的章节中看到的那样。

在这里插入图片描述

源码下载

源码下载地址

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/aoxuestudy/article/details/124061093

智能推荐

机器学习模型评分总结(sklearn)_model.score-程序员宅基地

文章浏览阅读1.5w次,点赞10次,收藏129次。文章目录目录模型评估评价指标1.分类评价指标acc、recall、F1、混淆矩阵、分类综合报告1.准确率方式一:accuracy_score方式二:metrics2.召回率3.F1分数4.混淆矩阵5.分类报告6.kappa scoreROC1.ROC计算2.ROC曲线3.具体实例2.回归评价指标3.聚类评价指标1.Adjusted Rand index 调整兰德系数2.Mutual Informa..._model.score

Apache虚拟主机配置mod_jk_apache mod_jk 虚拟-程序员宅基地

文章浏览阅读344次。因工作需要,在Apache上使用,重新学习配置mod_jk1. 分别安装Apache和Tomcat:2. 编辑httpd-vhosts.conf: LoadModule jk_module modules/mod_jk.so #加载mod_jk模块 JkWorkersFile conf/workers.properties #添加worker信息 JkLogFil_apache mod_jk 虚拟

Android ConstraintLayout2.0 过度动画MotionLayout MotionScene3_android onoffsetchanged-程序员宅基地

文章浏览阅读335次。待老夫kotlin大成,扩展:MotionLayout 与 CoordinatorLayout,DrawerLayout,ViewPager 的 交互众所周知,MotionLayout 的 动画是有完成度的 即Progress ,他在0-1之间变化,一.CoordinatorLayout 与AppBarLayout 交互时,其实就是监听 offsetliner 这个 偏移量的变化 同样..._android onoffsetchanged

【转】多核处理器的工作原理及优缺点_多核处理器怎么工作-程序员宅基地

文章浏览阅读8.3k次,点赞3次,收藏19次。【转】多核处理器的工作原理及优缺点《处理器关于多核概念与区别 多核处理器工作原理及优缺点》原文传送门  摘要:目前关于处理器的单核、双核和多核已经得到了普遍的运用,今天我们主要说说关于多核处理器的一些相关概念,它的工作与那里以及优缺点而展开的分析。1、多核处理器  多核处理器是指在一枚处理器中集成两个或多个完整的计算引擎(内核),此时处理器能支持系统总线上的多个处理器,由总..._多核处理器怎么工作

个人小结---eclipse/myeclipse配置lombok_eclispe每次运行个新项目都需要重新配置lombok吗-程序员宅基地

文章浏览阅读306次。1. eclipse配置lombok 拷贝lombok.jar到eclipse.ini同级文件夹下,编辑eclipse.ini文件,添加: -javaagent:lombok.jar2. myeclipse配置lombok myeclipse像eclipse配置后,定义对象后,直接访问方法,可能会出现飘红的报错。 如果出现报错,可按照以下方式解决。 ..._eclispe每次运行个新项目都需要重新配置lombok吗

【最新实用版】Python批量将pdf文本提取并存储到txt文件中_python批量读取文字并批量保存-程序员宅基地

文章浏览阅读1.2w次,点赞31次,收藏126次。#注意:笔者在2021/11/11当天调试过这个代码是可用的,由于pdfminer版本的更新,网络上大多数的语法没有更新,我也是找了好久的文章才修正了我的代码,仅供学习参考。1、把pdf文件移动到本代码文件的同一个目录下,笔者是在pycharm里面运行的项目,下图中的x1文件夹存储了我需要转换成文本文件的所有pdf文件。然后要在此目录下创建一个存放转换后的txt文件的文件夹,如图中的txt文件夹。2、编写代码 (1)导入所需库# coding:utf-8import ..._python批量读取文字并批量保存

随便推点

Scala:访问修饰符、运算符和循环_scala ===运算符-程序员宅基地

文章浏览阅读1.4k次。http://blog.csdn.net/pipisorry/article/details/52902234Scala 访问修饰符Scala 访问修饰符基本和Java的一样,分别有:private,protected,public。如果没有指定访问修饰符符,默认情况下,Scala对象的访问级别都是 public。Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层_scala ===运算符

MySQL导出ER图为图片或PDF_数据库怎么导出er图-程序员宅基地

文章浏览阅读2.6k次,点赞7次,收藏19次。ER图导出为PDF或图片格式_数据库怎么导出er图

oracle触发器修改同一张表,oracle触发器中对同一张表进行更新再查询时,需加自制事务...-程序员宅基地

文章浏览阅读655次。CREATE OR REPLACE TRIGGER Trg_ReimFactBEFORE UPDATEON BP_OrderFOR EACH ROWDECLAREPRAGMA AUTONOMOUS_TRANSACTION;--自制事务fc varchar2(255);BEGINIF ( :NEW.orderstate = 2AND :NEW.TransState = 1 ) THENBEG..._oracle触发器更新同一张表

debounce与throttle区别及其应用场景_throttle和debounce应用在哪些场景-程序员宅基地

文章浏览阅读513次。目录概念debouncethrottle实现debouncethrottle应用场景debouncethrottle场景举例debouncethrottle概念debounce字面理解是“防抖”,何谓“防抖”,就是连续操作结束后再执行,以网页滚动为例,debounce要等到用户停止滚动后才执行,将连续多次执行合并为一次执行。throttle字面理解是“节流”,何谓“节流”,就是确保一段时..._throttle和debounce应用在哪些场景

java操作mongdb【超详细】_java 操作mongodb-程序员宅基地

文章浏览阅读526次。regex() $regex 正则表达式用于模式匹配,基本上是用于文档中的发现字符串 (下面有例子)注意:若未加 @Field("名称") ,则识别mongdb集合中的key名为实体类属性名。也可以对数组进行索引,如果被索引的列是数组时,MongoDB会索引这个数组中的每一个元素。也可以对整个Document进行索引,排序是预定义的按插入BSON数据的先后升序排列。save: 若新增数据的主键已经存在,则会对当前已经存在的数据进行修改操作。_java 操作mongodb

github push 推送代码失败. 使用ssh rsa key. remote: Support for password authentication was removed._git push remote: support for password authenticati-程序员宅基地

文章浏览阅读1k次。今天push代码到github仓库时出现这个报错TACKCHEN-MB0:tc-image tackchen$ git pushremote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.remote: Please see https://github.blog/2020-12-15-token-authentication_git push remote: support for password authentication was removed on august 1