曲面细分着色器没有输出?
No output from tessellation shader?
我一直在研究地形 LOD 算法,但主要逻辑在 CPU:
我尝试将大部分逻辑转换为 opengl 管道的细分控制和评估阶段,但没有任何显示:
我将代码简化为一个基本的 "hello quad" 程序
#define GLEW_STATIC
#include <glew.h>
#include <glfw3.h>
#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <algorithm>
using namespace std;
/* Shader Source */
// vertex shader
const GLchar * VS_src[] = {
"#version 430\n"
"layout(location = 0) in vec3 position;\n"
"out vec4 vposition;\n"
"void main()\n"
"{\n"
" vposition = vec4(position, 1.0);\n"
"}\n"
};
// tesselation control shader
const GLchar * TCS_src[] = {
"#version 430\n"
"layout(vertices = 4) out;\n"
"in vec4 vposition[];\n"
"out vec4 tposition[];\n"
"void main()\n"
"{\n"
" tposition[gl_InvocationID] = vposition[gl_InvocationID];\n"
" if (gl_InvocationID == 0)\n"
" {\n"
" float tessLevel = 1.0;\n"
" gl_TessLevelInner[0] = tessLevel;\n"
" gl_TessLevelInner[1] = tessLevel;\n"
" gl_TessLevelOuter[0] = tessLevel;\n"
" gl_TessLevelOuter[1] = tessLevel;\n"
" gl_TessLevelOuter[2] = tessLevel;\n"
" gl_TessLevelOuter[3] = tessLevel;\n"
" }\n"
"}\n"
};
// tesselation evaluation shader
const GLchar * TES_src[] = {
"#version 430\n"
"uniform mat4 mvp;\n"
"layout(quads) in;\n"
"in vec4 tposition[];\n"
"void main()\n"
"{\n"
" float x = gl_TessCoord[0] * (tposition[1].x - tposition[0].x) + tposition[0].x;\n"
" float z = gl_TessCoord[2] * (tposition[1].z - tposition[2].z) + tposition[2].z;\n"
" float y = 0.0;\n"
" gl_Position = mvp * vec4(x, y, z, 1.0);\n"
"}\n"
};
// fragment shader
const GLchar * FS_src[] = {
"#version 430\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
"color = vec3(0.0);\n"
"}\n"
};
/* Link Shaders to Program */
GLuint LoadShaders(const GLchar ** VS, const GLchar ** TCS, const GLchar ** TES, const GLchar ** FS)
{
// Create the shaders
GLuint VS_ID = glCreateShader(GL_VERTEX_SHADER);
GLuint TCS_ID = glCreateShader(GL_TESS_CONTROL_SHADER);
GLuint TES_ID = glCreateShader(GL_TESS_EVALUATION_SHADER);
GLuint FS_ID = glCreateShader(GL_FRAGMENT_SHADER);
GLint Result = GL_FALSE;
int InfoLogLength;
// Compile Vertex Shader
printf("compiling vertex shader\n");
glShaderSource(VS_ID, 1, VS, NULL);
glCompileShader(VS_ID);
// Check Vertex Shader
glGetShaderiv(VS_ID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(VS_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(VS_ID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
printf("%s\n", &VertexShaderErrorMessage[0]);
}
// Compile Tess Control Shader
printf("compiling tesselation control shader\n");
glShaderSource(TCS_ID, 1, TCS, NULL);
glCompileShader(TCS_ID);
// Check Tess Control Shader
glGetShaderiv(TCS_ID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(TCS_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> TessControlShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(TCS_ID, InfoLogLength, NULL, &TessControlShaderErrorMessage[0]);
printf("%s\n", &TessControlShaderErrorMessage[0]);
}
// Compile Tess Evaluation Shader
printf("compiling tesselation evaluation shader\n");
glShaderSource(TES_ID, 1, TES, NULL);
glCompileShader(TES_ID);
// Check Tess Evaluation Shader
glGetShaderiv(TES_ID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(TES_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> TessEvaluationShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(TES_ID, InfoLogLength, NULL, &TessEvaluationShaderErrorMessage[0]);
printf("%s\n", &TessEvaluationShaderErrorMessage[0]);
}
// Compile Fragment Shader
printf("compiling fragment shader\n");
glShaderSource(FS_ID, 1, FS, NULL);
glCompileShader(FS_ID);
// Check Fragment Shader
glGetShaderiv(FS_ID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(FS_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(FS_ID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
printf("%s\n", &FragmentShaderErrorMessage[0]);
}
// Link the program
printf("linking program\n");
GLuint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VS_ID);
glAttachShader(ProgramID, TCS_ID);
glAttachShader(ProgramID, TES_ID);
glAttachShader(ProgramID, FS_ID);
glLinkProgram(ProgramID);
// Check the program
glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> ProgramErrorMessage(InfoLogLength+1);
glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
printf("%s\n", &ProgramErrorMessage[0]);
}
glDetachShader(ProgramID, VS_ID);
glDetachShader(ProgramID, TCS_ID);
glDetachShader(ProgramID, TES_ID);
glDetachShader(ProgramID, FS_ID);
glDeleteShader(VS_ID);
glDeleteShader(TCS_ID);
glDeleteShader(TES_ID);
glDeleteShader(FS_ID);
return ProgramID;
}
/* MAIN */
int main()
{
GLFWwindow * window;
if (!glfwInit()) return 0;
glfwWindowHint(GLFW_SAMPLES, 0);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
window = glfwCreateWindow(1600, 900, "Test", NULL, NULL);
if (!window) return 0;
glfwMakeContextCurrent(window);
glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
glewExperimental=true;
if (glewInit() != GLEW_OK) return 0;
// Init
glm::mat4 p = glm::perspective(glm::radians(45.0f), 1600.0f / 900.0f, 0.1f, 1000.0f);
// look at <0,0,0> from <20,20,20>
glm::mat4 v = glm::lookAt(glm::vec3(20.0f), glm::vec3(0.0f), glm::vec3(0,1,0));
glm::mat4 m = glm::mat4(1.0f);
glm::mat4 mvp = p * v * m;
// draw 1 quad
std::vector<unsigned int> indices;
std::vector<glm::vec3> vertices;
indices.push_back(0);
indices.push_back(1);
indices.push_back(2);
indices.push_back(3);
vertices.push_back(glm::vec3(-10.0f, 0.0f, -10.0f));
vertices.push_back(glm::vec3( 10.0f, 0.0f, -10.0f));
vertices.push_back(glm::vec3( 10.0f, 0.0f, 10.0f));
vertices.push_back(glm::vec3(-10.0f, 0.0f, 10.0f));
// VAO
GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);
// program
GLuint ProgramID = LoadShaders(VS_src, TCS_src, TES_src, FS_src);
// mvp uniform
GLuint MatrixID = glGetUniformLocation(ProgramID, "mvp");
// Vertex Buffer
GLuint vertexbuffer;
glGenBuffers(1, &vertexbuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);
// Element Buffer
GLuint elementbuffer;
glGenBuffers(1, &elementbuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
// loop
while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 )
{
glViewport(0, 0, 1600, 900);
glClearColor(0.478f, 0.702f, 0.816f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glUseProgram(ProgramID);
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &mvp[0][0]);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glPatchParameteri(GL_PATCH_VERTICES, 4);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
glDrawElements(GL_PATCHES, indices.size(), GL_UNSIGNED_INT, (void*)0);
glDisableVertexAttribArray(0);
glfwSwapBuffers(window);
glfwPollEvents();
}
// cleanup
glDeleteVertexArrays(1, &VertexArrayID);
glDeleteProgram(ProgramID);
glDeleteBuffers(1, &vertexbuffer);
glDeleteBuffers(1, &elementbuffer);
glfwTerminate();
return 0;
}
但仍然没有显示任何内容(除了蓝色透明背景)。所有着色器编译无错误,程序链接无错误。
当使用quads
原始模式时,只有gl_TessCoord
的前2个组件有意义。第三个分量是 0.0。 gl_TessCoord[0]
和 gl_TessCoord[1]
提供标准化的二维坐标,类似于纹理的 UV 坐标。
这意味着您必须在曲面细分评估着色器中使用 gl_TessCoord[1]
而不是 gl_TessCoord[2]
:
float x = gl_TessCoord[0] * (tposition[1].x - tposition[0].x) + tposition[0].x;
float z = gl_TessCoord[1] * (tposition[1].z - tposition[2].z) + tposition[2].z;
规格:
GLSL - The OpenGL Shading Language 4.6, 7.1 Built-In Language Variables, page 129:
The variable gl_TessCoord
is available only in the tessellation evaluation language. It specifies a threecomponent (u,v,w)
vector identifying the position of the vertex being processed by the shader relative to the primitive being tessellated.
OpenGL 4.6 core profile specification, 11.2.2.2 Quad Tessellation, page 416:
If the tessellation primitive mode is quads, a rectangle is subdivided into a collection of triangles covering the area of the original rectangle. First, the original rectangle is subdivided into a regular mesh of rectangles, where the number of rectangles along the u = 0
and u = 1
(vertical) and v = 0
and v = 1
(horizontal) edges are derived from the first and second inner tessellation levels, respectively.
我一直在研究地形 LOD 算法,但主要逻辑在 CPU:
我尝试将大部分逻辑转换为 opengl 管道的细分控制和评估阶段,但没有任何显示:
我将代码简化为一个基本的 "hello quad" 程序
#define GLEW_STATIC
#include <glew.h>
#include <glfw3.h>
#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <algorithm>
using namespace std;
/* Shader Source */
// vertex shader
const GLchar * VS_src[] = {
"#version 430\n"
"layout(location = 0) in vec3 position;\n"
"out vec4 vposition;\n"
"void main()\n"
"{\n"
" vposition = vec4(position, 1.0);\n"
"}\n"
};
// tesselation control shader
const GLchar * TCS_src[] = {
"#version 430\n"
"layout(vertices = 4) out;\n"
"in vec4 vposition[];\n"
"out vec4 tposition[];\n"
"void main()\n"
"{\n"
" tposition[gl_InvocationID] = vposition[gl_InvocationID];\n"
" if (gl_InvocationID == 0)\n"
" {\n"
" float tessLevel = 1.0;\n"
" gl_TessLevelInner[0] = tessLevel;\n"
" gl_TessLevelInner[1] = tessLevel;\n"
" gl_TessLevelOuter[0] = tessLevel;\n"
" gl_TessLevelOuter[1] = tessLevel;\n"
" gl_TessLevelOuter[2] = tessLevel;\n"
" gl_TessLevelOuter[3] = tessLevel;\n"
" }\n"
"}\n"
};
// tesselation evaluation shader
const GLchar * TES_src[] = {
"#version 430\n"
"uniform mat4 mvp;\n"
"layout(quads) in;\n"
"in vec4 tposition[];\n"
"void main()\n"
"{\n"
" float x = gl_TessCoord[0] * (tposition[1].x - tposition[0].x) + tposition[0].x;\n"
" float z = gl_TessCoord[2] * (tposition[1].z - tposition[2].z) + tposition[2].z;\n"
" float y = 0.0;\n"
" gl_Position = mvp * vec4(x, y, z, 1.0);\n"
"}\n"
};
// fragment shader
const GLchar * FS_src[] = {
"#version 430\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
"color = vec3(0.0);\n"
"}\n"
};
/* Link Shaders to Program */
GLuint LoadShaders(const GLchar ** VS, const GLchar ** TCS, const GLchar ** TES, const GLchar ** FS)
{
// Create the shaders
GLuint VS_ID = glCreateShader(GL_VERTEX_SHADER);
GLuint TCS_ID = glCreateShader(GL_TESS_CONTROL_SHADER);
GLuint TES_ID = glCreateShader(GL_TESS_EVALUATION_SHADER);
GLuint FS_ID = glCreateShader(GL_FRAGMENT_SHADER);
GLint Result = GL_FALSE;
int InfoLogLength;
// Compile Vertex Shader
printf("compiling vertex shader\n");
glShaderSource(VS_ID, 1, VS, NULL);
glCompileShader(VS_ID);
// Check Vertex Shader
glGetShaderiv(VS_ID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(VS_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(VS_ID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
printf("%s\n", &VertexShaderErrorMessage[0]);
}
// Compile Tess Control Shader
printf("compiling tesselation control shader\n");
glShaderSource(TCS_ID, 1, TCS, NULL);
glCompileShader(TCS_ID);
// Check Tess Control Shader
glGetShaderiv(TCS_ID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(TCS_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> TessControlShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(TCS_ID, InfoLogLength, NULL, &TessControlShaderErrorMessage[0]);
printf("%s\n", &TessControlShaderErrorMessage[0]);
}
// Compile Tess Evaluation Shader
printf("compiling tesselation evaluation shader\n");
glShaderSource(TES_ID, 1, TES, NULL);
glCompileShader(TES_ID);
// Check Tess Evaluation Shader
glGetShaderiv(TES_ID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(TES_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> TessEvaluationShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(TES_ID, InfoLogLength, NULL, &TessEvaluationShaderErrorMessage[0]);
printf("%s\n", &TessEvaluationShaderErrorMessage[0]);
}
// Compile Fragment Shader
printf("compiling fragment shader\n");
glShaderSource(FS_ID, 1, FS, NULL);
glCompileShader(FS_ID);
// Check Fragment Shader
glGetShaderiv(FS_ID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(FS_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(FS_ID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
printf("%s\n", &FragmentShaderErrorMessage[0]);
}
// Link the program
printf("linking program\n");
GLuint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VS_ID);
glAttachShader(ProgramID, TCS_ID);
glAttachShader(ProgramID, TES_ID);
glAttachShader(ProgramID, FS_ID);
glLinkProgram(ProgramID);
// Check the program
glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> ProgramErrorMessage(InfoLogLength+1);
glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
printf("%s\n", &ProgramErrorMessage[0]);
}
glDetachShader(ProgramID, VS_ID);
glDetachShader(ProgramID, TCS_ID);
glDetachShader(ProgramID, TES_ID);
glDetachShader(ProgramID, FS_ID);
glDeleteShader(VS_ID);
glDeleteShader(TCS_ID);
glDeleteShader(TES_ID);
glDeleteShader(FS_ID);
return ProgramID;
}
/* MAIN */
int main()
{
GLFWwindow * window;
if (!glfwInit()) return 0;
glfwWindowHint(GLFW_SAMPLES, 0);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
window = glfwCreateWindow(1600, 900, "Test", NULL, NULL);
if (!window) return 0;
glfwMakeContextCurrent(window);
glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
glewExperimental=true;
if (glewInit() != GLEW_OK) return 0;
// Init
glm::mat4 p = glm::perspective(glm::radians(45.0f), 1600.0f / 900.0f, 0.1f, 1000.0f);
// look at <0,0,0> from <20,20,20>
glm::mat4 v = glm::lookAt(glm::vec3(20.0f), glm::vec3(0.0f), glm::vec3(0,1,0));
glm::mat4 m = glm::mat4(1.0f);
glm::mat4 mvp = p * v * m;
// draw 1 quad
std::vector<unsigned int> indices;
std::vector<glm::vec3> vertices;
indices.push_back(0);
indices.push_back(1);
indices.push_back(2);
indices.push_back(3);
vertices.push_back(glm::vec3(-10.0f, 0.0f, -10.0f));
vertices.push_back(glm::vec3( 10.0f, 0.0f, -10.0f));
vertices.push_back(glm::vec3( 10.0f, 0.0f, 10.0f));
vertices.push_back(glm::vec3(-10.0f, 0.0f, 10.0f));
// VAO
GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);
// program
GLuint ProgramID = LoadShaders(VS_src, TCS_src, TES_src, FS_src);
// mvp uniform
GLuint MatrixID = glGetUniformLocation(ProgramID, "mvp");
// Vertex Buffer
GLuint vertexbuffer;
glGenBuffers(1, &vertexbuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);
// Element Buffer
GLuint elementbuffer;
glGenBuffers(1, &elementbuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
// loop
while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 )
{
glViewport(0, 0, 1600, 900);
glClearColor(0.478f, 0.702f, 0.816f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glUseProgram(ProgramID);
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &mvp[0][0]);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glPatchParameteri(GL_PATCH_VERTICES, 4);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
glDrawElements(GL_PATCHES, indices.size(), GL_UNSIGNED_INT, (void*)0);
glDisableVertexAttribArray(0);
glfwSwapBuffers(window);
glfwPollEvents();
}
// cleanup
glDeleteVertexArrays(1, &VertexArrayID);
glDeleteProgram(ProgramID);
glDeleteBuffers(1, &vertexbuffer);
glDeleteBuffers(1, &elementbuffer);
glfwTerminate();
return 0;
}
但仍然没有显示任何内容(除了蓝色透明背景)。所有着色器编译无错误,程序链接无错误。
当使用quads
原始模式时,只有gl_TessCoord
的前2个组件有意义。第三个分量是 0.0。 gl_TessCoord[0]
和 gl_TessCoord[1]
提供标准化的二维坐标,类似于纹理的 UV 坐标。
这意味着您必须在曲面细分评估着色器中使用 gl_TessCoord[1]
而不是 gl_TessCoord[2]
:
float x = gl_TessCoord[0] * (tposition[1].x - tposition[0].x) + tposition[0].x;
float z = gl_TessCoord[1] * (tposition[1].z - tposition[2].z) + tposition[2].z;
规格:
GLSL - The OpenGL Shading Language 4.6, 7.1 Built-In Language Variables, page 129:
The variable
gl_TessCoord
is available only in the tessellation evaluation language. It specifies a threecomponent(u,v,w)
vector identifying the position of the vertex being processed by the shader relative to the primitive being tessellated.
OpenGL 4.6 core profile specification, 11.2.2.2 Quad Tessellation, page 416:
If the tessellation primitive mode is quads, a rectangle is subdivided into a collection of triangles covering the area of the original rectangle. First, the original rectangle is subdivided into a regular mesh of rectangles, where the number of rectangles along the
u = 0
andu = 1
(vertical) andv = 0
andv = 1
(horizontal) edges are derived from the first and second inner tessellation levels, respectively.