openGL系列文章目录
前言
术语Tessellation(镶嵌)是指一大类设计活动,通常是指在平坦的表面上,用各种几何形状的瓷砖相邻排列以形成图案。它的目的可以是艺术性的或实用性的,很多例子可以追溯到几千年前在3D 图形学中,Tessellation 指的是有点不同的东西(曲面细分),但显然是由它的经典对应物(镶嵌)启发而成的。在这里,曲面细分指的是生成并且操控大量三角形以渲染复杂的形状和表面,尤其是使用硬件进行渲染。曲面细分是OpenGL 核心近期才增加的新功能,在2010 年的4.0 版本中出现。
一、曲面细分
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 参数重载版本。
二、细分二维四边形
理解OpenGL的硬件细分操作最好的方法就是细分一个二维四边形,而后可视化细分的结果。使用线性插值后,产生的三角形和细分坐标(u,v)相关。通过使用不同的输入和输出细分级别产生四边形对于学习很有价值。
曲面细分图元生成器(TPG)根据6个参数来划分(u,v)空间。它们是内部细分级别(0和1两个)和外部细分级别(0-3共6个)。下面是对它们的描述:
外部细分级别0(OL0):沿v方向,u坐标为0的边进行划分的个数。 外部细分级别1(OL1):沿u方向,v坐标为0的边进行划分的个数。 外部细分级别2(OL2):沿v方向,u坐标为1的边进行划分的个数。 外部细分级别3(OL3):沿u方向,v坐标为1的边进行划分的个数。 内部细分级别0(IL0):沿u方向进行内部划分的个数。 内部细分级别1(IL1):沿v方向进行内部划分的个数。 下图显示了划分级别对应的空间划分。外部划分级别定义了沿边划分的个数,内部划分级别定义了
这6个细分级别可以通过数组g_TessLevelOuter和gl_TessLevelInner设置。比如,gl_TessLevelInner[0]对应IL0,gl_TessLevelOuter[2]对应OL2。
我们绘制一个细分四边形来帮助理解OpenGL的四边形细分,如下图所示。 我们使用线性插值进行细分,图中的三角形代表了四边形的划分。x轴对应u坐标,y轴对应v坐标。三角形的顶点由TPG产生。细分的三角形数量,可以直接从图中看出。比如,何止外部细分级别为2,内部细分级别为8时,我们可以看到外部的边被划分为两部分,在内部,(u,v)被划分为8部分。
在进行代码实现之前,我们先讨论以下线性插值。对于下图的四边形,它内部的任一点可以通过对四边形四角的顶点插值得到。 我们通过TPG生成u,v坐标,然后通过上图的线性插值确定它在四边形中的位置。
准备
我们通过Uniform变量Inner和Outer在OpenGL程序中设置细分级别。我们使用之前提到的几何着色器来处理这些三角形。
然后,设置OpenGL程序渲染包含4个顶点的Patch图元。
实现
我们采取下面的步骤来进行四边形细分:
-
使用下面的代码作为顶点着色器: -
#version 400 layout (location = 0 ) in vec2 VertexPosition; void main() { gl_Position = vec4(VertexPosition, 0.0, 1.0); } -
使用下面的代码作为曲面细分控制着色器: -
#version 400 layout( vertices=4 ) out; uniform int Outer; uniform int Inner; void main() { // Pass along the vertex position unmodified gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; gl_TessLevelOuter[0] = float(Outer); gl_TessLevelOuter[1] = float(Outer); gl_TessLevelOuter[2] = float(Outer); gl_TessLevelOuter[3] = float(Outer); gl_TessLevelInner[0] = float(Inner); gl_TessLevelInner[1] = float(Inner); } -
使用下面代码作为曲面细分计算着色器: -
#version 400 layout( quads, equal_spacing, ccw ) in; uniform mat4 MVP; void main() { float u = gl_TessCoord.x; float v = gl_TessCoord.y; vec4 p0 = gl_in[0].gl_Position; vec4 p1 = gl_in[1].gl_Position; vec4 p2 = gl_in[2].gl_Position; vec4 p3 = gl_in[3].gl_Position; // Linear interpolation gl_Position = p0 * (1-u) * (1-v) + p1 * u * (1-v) + p3 * v * (1-u) + p2 * u * v; // Transform to clip coordinates gl_Position = MVP * gl_Position; } -
使用专栏文章在着色后的网格上绘制线框中的几何着色器。 -
使用下面的代码作为片段着色器:
#version 400 uniform float LineWidth; uniform vec4 LineColor; uniform vec4 QuadColor; noperspective in vec3 EdgeDistance; // From geometry shader layout ( location = 0 ) out vec4 FragColor; float edgeMix() { //insert code here to determine how much of the edge //color to include (see recipe “Drawing a wireframe on //top of a shaded mesh”). ** } void main() { float mixVal = edgeMix(); FragColor=mix( QuadColor, LineColor, mixVal); } 6. 在OpenGL程序中设置Patch图元顶点个数:
glPatchParameteri(GL_PATCH_VERTICES, 4); 7. 渲染Patch图元。
原理
我们的顶点着色器在这里只用来传递参数。
我们在TCS中定义Patch图元图元的顶点数目:
layout (vertices=4) out 在main函数,我们没有对顶点进行修改,只是进行了细分级别的设置。所有4个外部细分级别被设置为变量Outer的值,所有内部细分级别被设置为变量Inner的值。
在曲面细分计算着色器,我们设置了一些其它的细分参数:
layout ( quads, equal_spacing, ccw ) in; 参数quads表示TPG进行的是四边形细分。参数equal_spacing表示所有细分大小相同,最后一个参数ccw表示产生的顶点的顺序是逆时针的。
在TES的main函数,我们通过数组gl_TessCoord获取参数坐标。然后读取数组gl_in中存储的Patch图元的4个顶点信息。最后将这些信息存储在临时变量中进行插值计算。
插值计算的结果赋值给变量gl_Position。最后,使用模型视图投影矩阵转换顶点坐标到剪切空间。
在片段着色器,我们通过混合函数来渲染三角形的边和非边部分。
参阅
专栏文章在着色后的网格上绘制线框
参考
OpenGL_4.0_Shading_Language_Cookbook
|