1. 流程

在顶点和片段着色器之间有一个可选的几何着色器(Geometry Shader),几何着色器的输入是一个图元(如点或三角形)的一组顶点。几何着色器可以在顶点发送到下一着色器阶段之前对它们随意变换。然而,几何着色器最有趣的地方在于,它能够将(这一组)顶点变换为完全不同的图元,并且还能生成比原来更多的顶点。

1.1 一个简单例子

在几何着色器的顶部,我们需要声明从顶点着色器输入的图元类型。这需要在in关键字前声明一个布局修饰符(Layout Qualifier)。这个输入布局修饰符可以从顶点着色器接收下列任何一个图元值:

points:绘制GL_POINTS图元时(1)。
lines:绘制GL_LINES或GL_LINE_STRIP时(2)
lines_adjacency:GL_LINES_ADJACENCY或GL_LINE_STRIP_ADJACENCY(4)
triangles:GL_TRIANGLES、GL_TRIANGLE_STRIP或GL_TRIANGLE_FAN(3)
triangles_adjacency:GL_TRIANGLES_ADJACENCY或GL_TRIANGLE_STRIP_ADJACENCY(6)

接下来,我们还需要指定几何着色器输出的图元类型,这需要在out关键字前面加一个布局修饰符。和输入布局修饰符一样,输出布局修饰符也可以接受几个图元值(还可以指定组成该图元的最大顶点数):

points
line_strip
triangle_strip

接下来给一个简单的几何着色器例子:

#version 330 core
layout (points) in;
layout (line_strip, max_vertices = 2) out;void main() {    gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); EmitVertex();gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);EmitVertex();EndPrimitive();
}

可以看到,是从顶点着色器获取顶点数据的输出,然后把它组装成最多两个顶点组成的线元输出

那么它main函数写的是什么呢?

gl_Position是类似于顶点着色器的顶点向量,可以作为几何着色器的输出,这我们了解,那gl_in是什么呢,它其实也是GLSL的一个内建变量(被定义为了接口块),里面的变量之前都提到过了:

in gl_Vertex
{vec4  gl_Position;float gl_PointSize;float gl_ClipDistance[];
} gl_in[];

其实就是从顶点着色器里的得到的输出的顶点数据,我们可以对这些数据在几何着色器里进行处理,比如上面的例子,因为我们只对每个传入的顶点数据单独做处理,所以gl_in数组只有一个顶点,我们对传入的每个顶点进行左右位移,然后分别用EmitVertex()这个GLSL自带函数输出,最后用EndPrimitive()结束输出,那么,这两个经过位移处理的点就变成了组成线元的两个点,它将在后面的光栅化阶段插值形成线,这样,假设我们总共输入在屏幕的四个角的点的话,最后渲染出的画面是这样:

可以看到,四个点被重新装配成线元渲染到了屏幕上

1.2 几何着色器的创建

同样,如果要使用自定义的几何着色器,我们也要将它创建,编译并附加到着色器程序里(和其他着色器一样):

unsigned int geometryShader;
geometryShader = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometryShader, 1, &gShaderCode, NULL);
glCompileShader(geometryShader);
...
glAttachShader(program, geometryShader);
glLinkProgram(program);

1.3 造个房子

现在我们利用输入的每个点来造个房子,怎么做呢?我们先直接给出例子:

#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 5) out;void build_house(vec4 position)
{    gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0);    // 1:左下EmitVertex();   gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0);    // 2:右下EmitVertex();gl_Position = position + vec4(-0.2,  0.2, 0.0, 0.0);    // 3:左上EmitVertex();gl_Position = position + vec4( 0.2,  0.2, 0.0, 0.0);    // 4:右上EmitVertex();gl_Position = position + vec4( 0.0,  0.4, 0.0, 0.0);    // 5:顶部EmitVertex();EndPrimitive();
}void main() {    build_house(gl_in[0].gl_Position);
}

发现,我们将输入的点经过位移变成了5个新的顶点,而且输出定义为了layout (triangle_strip, max_vertices = 5) out
,指用最大5个顶点完成一系列三角形的装配,这是怎么做到的?我们可以画张图来看:


我们发现,如果按我们在几何着色器里定义的顶点顺序的话,它是这么装配三角形的:
123,234,345
一共三个三角形,这也就可以解释为什么输出会定义的装配图元的最大顶点数了,因为它会按顺序组装三角形,所以需要限制范围

我们还可以传入顶点颜色

fColor = gs_in[0].color; // gs_in[0] 因为只有一个输入顶点
gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0);    // 1:左下
EmitVertex();
gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0);    // 2:右下
EmitVertex();
gl_Position = position + vec4(-0.2,  0.2, 0.0, 0.0);    // 3:左上
EmitVertex();
gl_Position = position + vec4( 0.2,  0.2, 0.0, 0.0);    // 4:右上
EmitVertex();
gl_Position = position + vec4( 0.0,  0.4, 0.0, 0.0);    // 5:顶部
EmitVertex();
EndPrimitive();


因为知道了每个顶点的位置,我们还可以利用顶点着色器传入每个点对应的颜色,这样就可以得到不同颜色的房子:

fColor = gs_in[0].color;
gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0);    // 1:左下
EmitVertex();
gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0);    // 2:右下
EmitVertex();
gl_Position = position + vec4(-0.2,  0.2, 0.0, 0.0);    // 3:左上
EmitVertex();
gl_Position = position + vec4( 0.2,  0.2, 0.0, 0.0);    // 4:右上
EmitVertex();
gl_Position = position + vec4( 0.0,  0.4, 0.0, 0.0);    // 5:顶部
fColor = vec3(1.0, 1.0, 1.0);
EmitVertex();
EndPrimitive();


这样就好像有了冬天屋顶落雪的感觉

1.4 爆炸效果

既然能修改顶点的位置,那我们能不能使一个面整体进行位移呢?比如做出物体爆炸的效果:

当然可以,我们让面按照法向量的方向位移,而知道了一个三角形面的三个顶点传入的顺序,我们就可以得到它的两条边的向量,做叉乘来求法向量:

vec3 GetNormal()
{vec3 a = vec3(gl_in[0].gl_Position) - vec3(gl_in[1].gl_Position);vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position);return normalize(cross(a, b));
}

然后我们按时间变化定义这个面的位移量:

vec4 explode(vec4 position, vec3 normal)
{float magnitude = 2.0;vec3 direction = normal * ((sin(time) + 1.0) / 2.0) * magnitude; return position + vec4(direction, 0.0);
}

总代码:

#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;in VS_OUT {vec2 texCoords;
} gs_in[];out vec2 TexCoords; uniform float time;vec4 explode(vec4 position, vec3 normal) { ... }vec3 GetNormal() { ... }void main() {    vec3 normal = GetNormal();gl_Position = explode(gl_in[0].gl_Position, normal);TexCoords = gs_in[0].texCoords;EmitVertex();gl_Position = explode(gl_in[1].gl_Position, normal);TexCoords = gs_in[1].texCoords;EmitVertex();gl_Position = explode(gl_in[2].gl_Position, normal);TexCoords = gs_in[2].texCoords;EmitVertex();EndPrimitive();
}

注意,因为传入的顶点数据是triangles,所以gl_in数组的大小变为了3,我们就可以对三个顶点同时做处理了

1.5 法向量可视化

同样的,因为我们之前完成了利用点元画线的操作,我们自然可以做到法向量的可视化:

首先,在顶点着色器里处理顶点数据,都是我们之前做过的操作

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;out VS_OUT {vec3 normal;
} vs_out;uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0); mat3 normalMatrix = mat3(transpose(inverse(view * model)));vs_out.normal = normalize(vec3(projection * vec4(normalMatrix * aNormal, 0.0)));
}

之后,我们利用顶点着色器传入的法向量,对每个顶点画出一条法线(因为一个面三个顶点,组成线元就需要6个点):

#version 330 core
layout (triangles) in;
layout (line_strip, max_vertices = 6) out;in VS_OUT {vec3 normal;
} gs_in[];const float MAGNITUDE = 0.4;void GenerateLine(int index)
{gl_Position = gl_in[index].gl_Position;EmitVertex();gl_Position = gl_in[index].gl_Position + vec4(gs_in[index].normal, 0.0) * MAGNITUDE;EmitVertex();EndPrimitive();
}void main()
{GenerateLine(0); // 第一个顶点法线GenerateLine(1); // 第二个顶点法线GenerateLine(2); // 第三个顶点法线
}

效果:

可见,这种效果还可以用来生成毛发

2. 补充

教程中的爆炸代码只包含了无光照的着色器,为了结合前几节有光照的效果,要对输入输出进行重新定义:
顶点着色器:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 Texcoord;out VS_OUT{vec3 normal;vec3 fragPos;  vec2 texCoords;
}vs_out;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);vs_out.normal = mat3(transpose(inverse(model))) * aNormal;vs_out.fragPos = vec3(model * vec4(aPos, 1.0));vs_out.texCoords = Texcoord;
}

几何着色器:

对于要输出到片段着色器的变量,我们必须在EmitVertex的时候将这些变量赋给输出接口块一并发射出去(因为几何着色器是对一个图元的顶点处理,所以要在main函数里面发射多个点,但是在片段着色器里面还是一个顶点一个顶点处理的,所以我们的输出接口块不是数组)

#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;in VS_OUT {vec3 normal;vec3 fragPos;  vec2 texCoords;
} gs_in[];out GS_OUT{vec3 Normal;vec3 FragPos;  vec2 TexCoords;
} gs_out;uniform float time;vec4 explode(vec4 position, vec3 normal)
{float magnitude = 2.0;vec3 direction = normal * ((sin(time) + 1.0) / 2.0) * magnitude; return position + vec4(direction, 0.0);
}void main() {    gl_Position = explode(gl_in[0].gl_Position, gs_in[0].normal);gs_out.TexCoords = gs_in[0].texCoords;gs_out.Normal = gs_in[0].normal;gs_out.FragPos = gs_in[0].fragPos;EmitVertex();gl_Position = explode(gl_in[1].gl_Position, gs_in[1].normal);gs_out.TexCoords = gs_in[1].texCoords;gs_out.Normal = gs_in[1].normal;gs_out.FragPos = gs_in[1].fragPos;EmitVertex();gl_Position = explode(gl_in[2].gl_Position, gs_in[2].normal);gs_out.TexCoords = gs_in[2].texCoords;gs_out.Normal = gs_in[2].normal;gs_out.FragPos = gs_in[2].fragPos;EmitVertex();EndPrimitive();
}

片段着色器:

#version 330 core
struct Material {sampler2D texture_diffuse1;sampler2D texture_specular1;sampler2D texture_reflection1;samplerCube texture1;sampler2D texture_normal1;sampler2D texture_height1;float shininess;
}; struct PointLight {vec3 position;float constant;float linear;float quadratic;vec3 ambient;vec3 diffuse;vec3 specular;
};
#define NR_POINT_LIGHTS 4struct DirLight {vec3 direction;vec3 ambient;vec3 diffuse;vec3 specular;
};  struct SpotLight {vec3 position;vec3 direction;float cutOff;float outerCutOff;float constant;float linear;float quadratic;vec3 ambient;vec3 diffuse;vec3 specular;
};  out vec4 FragColor;in GS_OUT{vec3 Normal;vec3 FragPos;  vec2 TexCoords;
} fs_in;uniform vec3 viewPos;
uniform Material material;
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform DirLight dirLight;
uniform SpotLight spotLight;
//uniform sampler2D emission;vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light, vec3 norm, vec3 FragPos, vec3 viewDir);void main()
{   // 属性vec3 norm = normalize(fs_in.Normal);vec3 viewDir = normalize(viewPos - fs_in.FragPos);vec3 R = reflect(- viewDir, norm);vec3 reflectMap = vec3(texture(material.texture_reflection1, fs_in.TexCoords));vec3 reflection = vec3(texture(material.texture1, R).rgb) * reflectMap * 2;// 第一阶段:定向光照vec3 result = CalcDirLight(dirLight, norm, viewDir);// 第二阶段:点光源for(int i = 0; i < NR_POINT_LIGHTS; i++)result += CalcPointLight(pointLights[i], norm, fs_in.FragPos, viewDir);    // 第三阶段:聚光result += CalcSpotLight(spotLight, norm, fs_in.FragPos, viewDir) + reflection;    FragColor = vec4(result, 1.0);
}vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{vec3 lightDir = normalize(-light.direction);// 漫反射着色float diff = max(dot(normal, lightDir), 0.0);// 镜面光着色vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// 合并结果vec3 ambient  = light.ambient  * vec3(texture(material.texture_diffuse1, fs_in.TexCoords));vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.texture_diffuse1, fs_in.TexCoords));vec3 specular = light.specular * spec * vec3(texture(material.texture_specular1, fs_in.TexCoords));return (ambient + diffuse + specular);
}vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{vec3 lightDir = normalize(light.position - fragPos);// 漫反射着色float diff = max(dot(normal, lightDir), 0.0);// 镜面光着色vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// 衰减float distance    = length(light.position - fragPos);float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    // 合并结果vec3 ambient  = light.ambient  * vec3(texture(material.texture_diffuse1, fs_in.TexCoords));vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.texture_diffuse1, fs_in.TexCoords));vec3 specular = light.specular * spec * vec3(texture(material.texture_specular1, fs_in.TexCoords));ambient  *= attenuation;diffuse  *= attenuation;specular *= attenuation;return (ambient + diffuse + specular);
}vec3 CalcSpotLight(SpotLight light, vec3 norm, vec3 FragPos, vec3 viewDir)
{vec3 lightDir = normalize(light.position - FragPos);float theta = dot(lightDir, normalize(-light.direction));float epsilon   = light.cutOff - light.outerCutOff;float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); vec3 result; // 执行光照计算// 环境光vec3 ambient = light.ambient * vec3(texture(material.texture_diffuse1, fs_in.TexCoords));// 漫反射 float diff = max(dot(norm, lightDir), 0.0);vec3 diffuse = light.diffuse * (diff * vec3(texture(material.texture_diffuse1, fs_in.TexCoords)));// 镜面光vec3 reflectDir = reflect(-lightDir, norm);  float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);vec3 specular = light.specular * (spec * vec3(texture(material.texture_specular1, fs_in.TexCoords)));  float distance = length(light.position - FragPos);float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));diffuse *= attenuation * intensity;specular *= attenuation * intensity;  return  ambient + diffuse + specular;
}

效果:

【OpenGL】笔记二十七、几何着色器相关推荐

  1. OpenGL基础41:几何着色器

    在顶点着色器之后,片段着色器之前,还有几何着色器,它是可选的,在<OpenGL基础3:渲染管线>这一章中就有提到了,有了几何着色器后可以做很多骚操作,更容易实现很多有意思的效果 一.最简单 ...

  2. Unity Shader:用几何着色器实现复联3灭霸的终极大招灰飞烟灭

    (图1:正常渲染) (图2:几何着色器粒子化特效进行中) (图3:几何着色器粒子化特效进行中) 1,用几何着色器进行图元转换 在OpenGL渲染管线中,几何着色器Geometry Shader有一个独 ...

  3. LearnOpenGL-高级OpenGL-9.几何着色器

    本人初学者,文中定有代码.术语等错误,欢迎指正 文章目录 几何着色器 使用几何着色器 造几个房子 爆破物体 法向量可视化 几何着色器 简介 在顶点和片段着色器之间有一个可选的几何着色器 几何着色器的输 ...

  4. 第二十二章 opengl之高级OpenGL(几何着色器)

    OpenGL 使用几何着色器 用点造物体 爆破物体 法向量可视化 在顶点和片段着色器之间有一个可选的几何着色器(Geometry Shader), 几何着色器的输入是一个图元(如点或三角形)的一组顶点 ...

  5. OpenGL学习笔记(十)-几何着色器-实例化

    参考网址:LearnOpenGL 中文版 4.7 几何着色器 4.7.1 基本概念 1.顶点和片段着色器之间有一个可选的几何着色器,几何着色器的输入是一个图元(如点或三角形)的一组顶点,顶点发送到下一 ...

  6. dx12 龙书第十二章学习笔记 -- 几何着色器

    如果不启用曲面细分(tessellation)这一环节,那么几何着色器(geometry shader)这个可选阶段便会位于顶点着色器与像素着色器之间.顶点着色器以顶点作为输入数据,而几何着色器的输入 ...

  7. opengl 设置每个点的颜色_OpenGL学习笔记(四)着色器

    本文为学习LearnOpenGL的学习笔记,如有书写和理解错误还请大佬扶正: 教程链接: 着色器 - LearnOpenGL CN​learnopengl-cn.github.io 一,基础概念 1, ...

  8. OpenGL中的曲面细分和几何着色器

    [摘要]本文我们先介绍OpenGL中的曲面细分的一些基本概念,然后给两个例子说明不得不用这项技术的理由. 曲面细分是OpenGL 4.0之后才定义的功能,使用之前请确认你的显卡驱动支持OpenGL4. ...

  9. OpenGL Tessellation and Geometry Shaders镶嵌和几何着色器的实例

    OpenGL 镶嵌和几何着色器 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <sb7.h> class tessllatedgst ...

最新文章

  1. python 操作ipynb文件笔记
  2. 全方位讲解硬件防火墙的选择
  3. [caffe] 数据制作和训练
  4. 恢复linux里被误删除的文件
  5. pandas重命名变量名
  6. 揭秘:神策数据产品矩阵,全方位筑就你的数据驱动闭环
  7. JSONP - 跨域AJAX
  8. URLEncode编码和URLDecode解码
  9. Mysql实现幂等_阿里面试官:接口的幂等性怎么设计?
  10. 什么是E1接口,E1的使用注意事项
  11. php 求 相似 比,php比较相似字符串的方法
  12. 正向代理和反向代理有和区别
  13. 理解委托是类型安全的
  14. shell 学习之case语句
  15. 专业RAW图像处理软件Capture One Pro 22
  16. linux如何删除tree命令,误删tree命令如何恢复
  17. SpringCloud教程合集
  18. ensp下载与安装问题
  19. Android实时监听短信并上传服务器
  20. Linux 文件隐藏权限

热门文章

  1. linux 操作excel文件,Linux下输出excel文件
  2. 遇见你是我最美的意外
  3. Anaconda3最新版2022版的下载安装配置及使用教程(建议收藏,持续更新..)
  4. 淘宝网热浪引擎平台资费规则
  5. 深度解读物联网区块链“IOTA”:不仅解决IoT痛点,还解决区块链痛点
  6. Unity资源导入自动化设置
  7. 郑州计算机五年大专学校排名,2021年河南十大专科学校排名 河南最好的高职院校...
  8. 三分之一的程序猿之创业组队与打怪升级
  9. 2021极术通讯-CSL-YOLO | 超越Tiny-YOLO V4,全新设计轻量化YOLO模型实现边缘实时检测
  10. 从互联网角度出发,慧算账受客户追捧