当前位置: 首页 > java >正文

【OpenGL】LearnOpenGL学习笔记19 - 几何着色器 Geometry Shader

上接:https://blog.csdn.net/weixin_44506615/article/details/151043459?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl | https://github.com/Duo1J/LearnOpenGL

几何着色器 (Geometry Shader)

接下来认识一种新的着色器 几何着色器,它在顶点和片段着色器之间执行,其输入是一个图元 (点或三角形),它可以对其进行随意的变换

#version 330 corelayout (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();// 结束line_strip图元EndPrimitive();
}

第二行 layout (points) in; 表明了绘制图元的类型

修饰符图元
pointsGL_POINTS
linesGL_LINES或GL_LINE_STRIP
lines_adjacencyGL_LINES_ADJACENCY 或 GL_LINE_STRIP_ADJACENCY
trianglesGL_TRIANGLES、GL_TRIANGLE_STRIP 或 GL_TRIANGLE_FAN
triangles_adjacencyGL_TRIANGLES_ADJACENCY 或 GL_TRIANGLE_STRIP_ADJACENCY

第三行 layout (line_strip, max_vertices = 2) out; 表示输出的图元类型

这里的 gl_in 是一个GLSL的内建变量

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

接下来尝试几个简单的几何着色器应用
在这之前我们先修改一下Shader类以加载几何着色器
Shader.h

// 增加可选的geoPath
Shader(const char* vertexPath, const char* fragmentPath, const char* geoPath = nullptr);

Shader.cpp
增加几何着色器

Shader::Shader(const char* vertexPath, const char* fragmentPath, const char* geoPath)
{std::string vertexCode;std::string fragmentCode;std::string geoCode;try{if (vertexPath != nullptr){std::ifstream vertexShaderFile;vertexShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);vertexShaderFile.open(vertexPath);std::stringstream vShaderStream;vShaderStream << vertexShaderFile.rdbuf();vertexShaderFile.close();vertexCode = vShaderStream.str();}if (fragmentPath != nullptr){std::ifstream fragmentShaderFile;fragmentShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);fragmentShaderFile.open(fragmentPath);std::stringstream fShaderStream;fShaderStream << fragmentShaderFile.rdbuf();fragmentShaderFile.close();fragmentCode = fShaderStream.str();}if (geoPath != nullptr){std::ifstream geoShaderFile;geoShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);geoShaderFile.open(geoPath);std::stringstream gShaderStream;gShaderStream << geoShaderFile.rdbuf();geoShaderFile.close();geoCode = gShaderStream.str();}}catch (std::ifstream::failure e){std::cout << "[Error] Failed to read shader file" << std::endl;}ProgramID = glCreateProgram();int success;char infoLog[512];// 顶点着色器unsigned int vertexShader = 0;if (!vertexCode.empty()){const char* vertexShaderCode = vertexCode.c_str();vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderCode, NULL);glCompileShader(vertexShader);glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "[Error] Vertex shader compile failed!\n" << infoLog << std::endl;};glAttachShader(ProgramID, vertexShader);}// 片段着色器unsigned int fragmentShader = 0;if (!fragmentCode.empty()){const char* fragmentShaderCode = fragmentCode.c_str();fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderCode, NULL);glCompileShader(fragmentShader);glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "[Error] Fragment shader compile failed!\n" << infoLog << std::endl;}glAttachShader(ProgramID, fragmentShader);}// 几何着色器unsigned int geoShader = 0;if (!geoCode.empty()){const char* geoShaderCode = geoCode.c_str();geoShader = glCreateShader(GL_GEOMETRY_SHADER);glShaderSource(geoShader, 1, &geoShaderCode, NULL);glCompileShader(geoShader);glGetShaderiv(geoShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(geoShader, 512, NULL, infoLog);std::cout << "[Error] Geo shader compile failed!\n" << infoLog << std::endl;}glAttachShader(ProgramID, geoShader);}glLinkProgram(ProgramID);glGetProgramiv(ProgramID, GL_LINK_STATUS, &success);if (!success){glGetProgramInfoLog(ProgramID, 512, NULL, infoLog);std::cout << "[Error] Shader program link failed!\n" << infoLog << std::endl;}glDeleteShader(vertexShader);glDeleteShader(fragmentShader);glDeleteShader(geoShader);
}

1、绘制小房子

首先我们需要一个点

// 几何着色器小房子顶点
float housePoints[] = {-0.5f,  0.5f,
};unsigned int houseVAO, houseVBO;
glGenVertexArrays(1, &houseVAO);
glGenBuffers(1, &houseVBO);
glBindVertexArray(houseVAO);
glBindBuffer(GL_ARRAY_BUFFER, houseVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(housePoints), &housePoints, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);

接着创建小房子的顶点、片段和几何着色器
HouseVertex.glsl 新建

#version 330 corelayout (location = 0) in vec2 aPos;void main()
{gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); 
}

HouseFragment.glsl 新建

#version 330 coreout vec4 FragColor;void main()
{FragColor = vec4(0.0, 1.0, 0.0, 1.0);   
}

HouseGeo.glsl 新建
依次绘制组成小房子的三个三角形

#version 330 corelayout (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);
}

最后创建着色器并绘制

Shader houseShader("Shader/HouseVertex.glsl", "Shader/HouseFragment.glsl", "Shader/HouseGeo.glsl");
//...
houseShader.Use();
glBindVertexArray(houseVAO);
glDrawArrays(GL_POINTS, 0, 1);

编译运行,顺利的话可以看见以下图像
几何着色器
线框模式

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
houseShader.Use();
glBindVertexArray(houseVAO);
glDrawArrays(GL_POINTS, 0, 1);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

几何着色器

2、“爆破”

接下来我们实现一个背包爆破的效果
首先通过叉乘计算图元法线,接着将顶点沿图元法线方向偏移
BagExplodeGeo.glsl 新增

#version 330 corelayout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;in VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Position;
} gs_in[];out VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Position;
} 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);
}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));
}void main() {    vec3 normal = GetNormal();gl_Position = Explode(gl_in[0].gl_Position, normal);gs_out.FragPos = gs_in[0].FragPos;gs_out.Normal = gs_in[0].Normal;gs_out.TexCoords = gs_in[0].TexCoords;gs_out.Position = gs_in[0].Position;EmitVertex();gl_Position = Explode(gl_in[1].gl_Position, normal);gs_out.FragPos = gs_in[1].FragPos;gs_out.Normal = gs_in[1].Normal;gs_out.TexCoords = gs_in[1].TexCoords;gs_out.Position = gs_in[1].Position;EmitVertex();gl_Position = Explode(gl_in[2].gl_Position, normal);gs_out.FragPos = gs_in[2].FragPos;gs_out.Normal = gs_in[2].Normal;gs_out.TexCoords = gs_in[2].TexCoords;gs_out.Position = gs_in[2].Position;EmitVertex();EndPrimitive();
}

顶点和片段着色器需要一并换成接口块
VertexShader.glsl

#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;// 先保留输出
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
out vec3 Position;uniform mat4 model;
layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};out VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Position;
} vs_out;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);FragPos = vec3(model * vec4(aPos, 1.0));vs_out.FragPos = FragPos;Normal = mat3(transpose(inverse(model))) * aNormal;vs_out.Normal = Normal;TexCoords = aTexCoords;vs_out.TexCoords = aTexCoords;Position = vec3(model * vec4(aPos, 1.0));vs_out.Position = Position;
}

FragmentShader.glsl

// ...
in VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Position;
} fs_in;
// ...

Main.cpp

// 加上几何着色器
Shader shader("Shader/VertexShader.glsl", "Shader/FragmentShader.glsl", "Shader/BagExplodeGeo.glsl");// 主循环
// 设置时间
shader.SetFloat("time", glfwGetTime());

编译运行,顺利的话可以看见背包在爆破又恢复接着又爆破
几何着色器

3、法线可视化

最后来做一个很使用的功能 法线可视化
我们从顶点着色器中传递法向量到几何着色器,然后在几何着色器中沿着法向量画一条线

NormalVisualVertex.glsl 新增

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

NormalVisualGeo.glsl 新增

#version 330 corelayout (triangles) in;
layout (line_strip, max_vertices = 6) out;in VS_OUT {vec3 normal;
} gs_in[];const float MAGNITUDE = 0.2;layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};void GenerateLine(int index)
{gl_Position = projection * gl_in[index].gl_Position;EmitVertex();gl_Position = projection * (gl_in[index].gl_Position +  vec4(gs_in[index].normal, 0.0) * MAGNITUDE);EmitVertex();EndPrimitive();
}void main()
{GenerateLine(0); GenerateLine(1); GenerateLine(2); 
}

GreenFragment.glsl 由HouseFragment.glsl改名

#version 330 coreout vec4 FragColor;void main()
{FragColor = vec4(0.0, 1.0, 0.0, 1.0);   
}

Main.cpp

// 法线可视化Shader
Shader normalVisualShader("Shader/NormalVisualVertex.glsl", "Shader/GreenFragment.glsl", "Shader/NormalVisualGeo.glsl");
// 绑定UBO槽
BindMatericesBlock(normalVisual, normalVisualShader, 0);// 主循环
// 绘制背包之后
// 法线可视化
normalVisualShader.Use();
normalVisualShader.SetMat4("model", modelMatrix);
model.Draw(normalVisualShader);

编译运行,顺利的话可以看见以下图像
当出现光照问题时,我们可以通过法向量可视化来排查
法向量可视化
完整代码可在顶部git仓库中找到

http://www.xdnf.cn/news/19913.html

相关文章:

  • 解决 Android Studio 中 build 目录已被 Git 跟踪后的忽略问题
  • 【stm32】定时器中断与定时器外部时钟
  • el-table 行高亮,点击行改变背景
  • CVE-2025-6507(CVSS 9.8):H2O-3严重漏洞威胁机器学习安全
  • 安全测试漫谈:如何利用X-Forwarded-For头进行IP欺骗与防护
  • TDengine NOW() 函数用户使用手册
  • Ubuntu环境下的 RabbitMQ 安装与配置详细教程
  • RabbitMQ篇
  • 20250903的学习笔记
  • LangChain实战(十三):Agent Types详解与选择策略
  • 动态IP和静态IP配置上有什么区别
  • 单片机控制两只直流电机正反转C语言
  • 如何保存训练的最优模型和使用最优模型文件
  • 【wpf】WPF开发避坑指南:单例模式中依赖注入导致XAML设计器崩溃的解决方案
  • SpringBoot注解生效原理分析
  • AI落地新趋势:美林数据揭示大模型与小模型的协同进化论
  • Java中 String、StringBuilder 和 StringBuffer 的区别?
  • 小皮80端口被NT内核系统占用解决办法
  • 期货反向跟单—从小白到高手的进阶历程 七(翻倍跟单问题)
  • 【Java】对于XML文档读取和增删改查操作与JDBC编程的读取和增删改查操作的有感而发
  • 加解密安全-侧信道攻击
  • Python分布式任务队列:万级节点集群的弹性调度实践
  • Unity 枪械红点瞄准器计算
  • linux内核 - 服务进程是内核的主要责任
  • dockerfile文件的用途
  • 机器能否真正语言?人工智能NLP面临的“理解鸿沟与突破
  • 键盘上面有F3,四,R,F,V,按下没有反应,维修记录
  • Echo- Go Web Framework的介绍
  • MCP over SSE 通信过程详解:双通道架构下的高效对话
  • 关于牙科、挂号、医生类小程序或管理系统项目 项目包含微信小程序和pc端两部分