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

本人初学者,文中定有代码、术语等错误,欢迎指正

文章目录

  • 几何着色器
  • 使用几何着色器
    • 造几个房子
    • 爆破物体
    • 法向量可视化

几何着色器

  • 简介

    • 在顶点和片段着色器之间有一个可选的几何着色器
    • 几何着色器的输入是一个图元(如点或三角形)的一组顶点。
    • 几何着色器可以在顶点发送到下一着色器阶段之前对它们随意变换
  • 代码例子

    #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();
    }
    
    • 输入的图元类型:layout (points) in;
      • 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)
    • 几何着色器输出的图元类型:layout (line_strip, max_vertices = 2) out;
      • points
      • line_strip
      • triangle_strip
  • 说明line_strip

    layout (line_strip, max_vertices = 5) out;

  • 内建变量

    我们需要某种方式来获取前一着色器阶段的输出

    in gl_Vertex// 4.8节讲的接口块
    {
        vec4  gl_Position;
        float gl_PointSize;
        float gl_ClipDistance[];
    } gl_in[];
    

    要注意的是,它被声明为一个数组,因为大多数的渲染图元包含多于1个的顶点,而几何着色器的输入是一个图元的所有顶点。

  • 生成线条

    void main() {
        gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); 
        EmitVertex();// gl_Position添加到图元中
    
        gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
        EmitVertex();
    
        EndPrimitive();// 合成
    }
    
    • 每次我们调用EmitVertex时,gl_Position中的向量会被添加到图元中来
    • EndPrimitive被调用时,所有发射出的(Emitted)顶点都会合成为指定的输出渲染图元。

使用几何着色器

造几个房子

  • 分析

    我们可以将几何着色器的输出设置为triangle_strip,并绘制三个三角形:其中两个组成一个正方形,另一个用作房顶。

  • triangle_strip说明

    • 在第一个三角形绘制完之后,每个后续顶点将会在上一个三角形边上生成另一个三角形:每3个临近的顶点将会形成一个三角形

    • 例子

      顶点为:123456

      生成的三角形有:(1, 2, 3)、(2, 3, 4)、(3, 4, 5)和(4, 5, 6),共形成4个三角形

    • 图示

  • 从而推出房子需要的顶点,以及顺序

    顶点为:123456

    生成的三角形有:(1, 2, 3)、(2, 3, 4)和(3, 4, 5),共形成3个三角形

  • 代码

    #version 330 core
    layout (points) in;//输入
    layout (triangle_strip, max_vertices = 5) out;// 输出,5个顶点
    
    in VS_OUT{// 4.8节讲的接口块
    	vec3 color;
    }gs_in[];
    
    out vec3 fColor;
    
    void build_house(vec4 position){
    	// 因为points只有一个顶点,所以下标为0
    	fColor = gs_in[0].color;//1234顶点使用同一个颜色
    	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();
    }
    void main(){
    	build_house(gl_in[0].gl_Position);
    }
    
    float points[] = {
        -0.5f,  0.5f, 1.0f, 0.0f, 0.0f, // top-left
        0.5f,  0.5f, 0.0f, 1.0f, 0.0f, // top-right
        0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // bottom-right
        -0.5f, -0.5f, 1.0f, 1.0f, 0.0f  // bottom-left
    };
    
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    
    shader.use();
    glBindVertexArray(VAO);
    glDrawArrays(GL_POINTS, 0 ,4);
    
  • 效果

    请添加图片描述

爆破物体

  • 分析

    我们是要将每个三角形沿着法向量的方向移动一小段时间。效果就是,整个物体看起来像是沿着每个三角形的法线向量爆炸一样。

  • 代码

    vs:顶点着色器

    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 2) in vec2 aTexCoords;
    
    out VS_OUT{// 4.8的接口块
        vec2 texCoords;
    }vs_out;
    
    uniform mat4 projection;
    uniform mat4 model;
    uniform mat4 view;
    
    void main()
    {
        gl_Position = projection * view * model * vec4(aPos, 1.0);// 变换到裁剪空间
        vs_out.texCoords = aTexCoords;
    }
    
    

    gs:几何着色器-关键地方

    #version 330 core
    layout (triangles) in;
    layout (triangle_strip, max_vertices = 3) out;// 输出,3个顶点
    
    // 从顶点着色器传入
    in VS_OUT{
    	vec2 texCoords;
    }gs_in[];
    
    // 为了传入给片段着色器
    out vec2 TexCoords;
    
    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));// a、b向量的叉积:第三个向量(法线)并垂直于a、b
    }
    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();
    }
    

    分析:

    • vs顶点着色器将顶点变换到裁剪空间后传给几何着色器
    • 几何着色器的顶点处于裁剪空间中,那么这里计算的法线是计算裁剪空间顶点的法线

    fs

    #version 330 core
    out vec4 FragColor;
    
    in vec2 TexCoords;
    
    uniform sampler2D texture_diffuse1;
    
    void main(){ 
    	FragColor = texture(texture_diffuse1, TexCoords);
    }
    

    cpp

    // 渲染这个模型
    model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
    model = glm::scale(model, glm::vec3(0.1f, 0.1f, 0.1f));
    shader.use();
    shader.setMat4("model", model);
    shader.setMat4("view", view);
    shader.setMat4("projection", projection);
    
    shader.setFloat("time", static_cast<float>(glfwGetTime()));
    
  • 效果


    请添加图片描述

法向量可视化

  • 引出

    检测法向量是否正确的一个很好的方式就是对它们进行可视化,几何着色器正是实现这一目的非常有用的工具。

  • 实现思路

    • 我们首先不使用几何着色器正常绘制场景

    • 然后再次绘制场景,但这次只显示通过几何着色器生成法向量。

      几何着色器接收一个三角形图元,并沿着法向量生成三条线——>每个顶点一个法向量

  • 代码

    法线可视化的着色器

    vs

    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aNormal;
    
    out VS_OUT{
        vec3 normal;
    }vs_out;
    
    uniform mat4 model;
    uniform mat4 view;
    
    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)));
    }
    

    gs

    #version 330 core
    layout (triangles) in; // 输入:一个三角形3个顶点
    layout (line_strip, max_vertices = 6) out;// 输出:3条线,每条线2个顶点,共6个顶点
    
    // 从顶点着色器传入
    in VS_OUT{
    	vec3 normal;
    }gs_in[];
    
    const float MAGNITUDE = 0.02;
    
    uniform mat4 projection;// 投影矩阵
    // 从点变成线
    void GenerateLine(int index){
    	gl_Position = projection * gl_in[index].gl_Position;// 起始点变换到裁剪空间
    	EmitVertex();
    	// 1.在观察空间中线的终顶点沿着法线增长 2.顶点再变换到裁剪空间
    	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);
    }
    

    分析:

    • vs顶点着色器将顶点变换到观察空间后传给几何着色器

      所以法线也要变换到观察空间再传给几何着色器

    • 几何着色器的顶点

      • 在观察空间沿着法线增长

        (gl_in[index].gl_Position + vec4(gs_in[index].normal, 0.0) * MAGNITUDE)
        
      • 增长后的顶点与projection投影矩阵相乘在裁剪空间

        然后传给片段着色器之前:经过透视除法到标准化设备坐标系,再经过视口变换到屏幕坐标(opengl自动执行)

    fs

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

    cpp

    Shader shader("assest/shader/3模型/3.1.模型加载.vs", "assest/shader/3模型/3.1.模型加载.fs");
    Shader normalshader("assest/shader/4高级OpenGL/6.9.3.几何着色器-模型法向量可视化.vs", "assest/shader/4高级OpenGL/6.9.3.几何着色器-模型法向量可视化.fs", "assest/shader/4高级OpenGL/6.9.3.几何着色器-模型法向量可视化.gs");
    while (!glfwWindowShouldClose(window))
    {
        glm::mat4 model = glm::mat4(1.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    
        // 渲染这个模型
        model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
        model = glm::scale(model, glm::vec3(0.1f, 0.1f, 0.1f));
        shader.use();
        shader.setMat4("model", model);
        shader.setMat4("view", view);
        shader.setMat4("projection", projection);
        ourModel.Draw(shader);
    
        // 由几何着色器的设置,顶点的位置,渲染为法线
        normalshader.use();
        normalshader.setMat4("model", model);
        normalshader.setMat4("view", view);
        normalshader.setMat4("projection", projection);
        ourModel.Draw(normalshader);
    
  • 效果

    请添加图片描述

  • 疑问点

    为什么要在观察空间中顶点沿着法线增长变成线。

    几何着色器不可以直接在裁剪空间下对顶点增长吗?

    测试代码:

    void main()
    {	// 顶点变换到裁剪空间
        gl_Position = projection * view * model * vec4(aPos, 1.0);
        // 将法线变换到裁剪空间
        mat3 normalMatrix = mat3(transpose(inverse(projection * view * model)));
        vs_out.normal = normalize(vec3(vec4(normalMatrix * aNormal, 0.0)));
    }
    
    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();
    }
    

    会发现绘制出来的线很奇怪

    个人猜测:

    • 前置知识

      由1.8所讲的坐标系统中提到的:一旦顶点进入到裁剪空间,那么OpenGL会自动执行

      1. 透视除法到标准化设备坐标系

      2. 再经过视口变换到屏幕坐标

    • 所以

      在几何着色器的时候,顶点此时不在裁剪空间,而是在屏幕坐标系,从而绘制出来的法线不正确!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/25599.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spark入门介绍

目录 一、Spark框架概述 1、Spark简介 2、发展 二、Spark功能及特点 1、定义

K8s进阶7——Sysdig、Falco、审计日志

文章目录 一、分析容器系统调用&#xff1a;Sysdig1.1. 安装1.2 常用参数1.3 采集分析1.4 示例1.4.1 查看某进程系统调用事件1.4.2 查看建立TCP连接事件1.4.3 查看某目录下打开的文件描述符1.4.4 查看容器的系统调用 1.5 Chisels工具1.5.1 网络类1.5.2 硬盘类1.5.3 cpu类1.5.4 …

奇舞周刊第493期:Hook 革命!浅谈 React 新 Hook 的未来与思想

关注前端生态发展&#xff0c;了解行业动向。 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ Hook 革命&#xff01;浅谈 React 新 Hook 的未来与思想 作者阳羡曾写文章对 React 新 Hook use 的设计理念和限制进行了深入分析&#xff0c;并提供了一个可能的实现来帮助读者…

如何在 Alpine Linux 上启用或禁用防火墙?

防火墙是计算机网络安全的重要组成部分&#xff0c;它用于保护计算机和网络免受未经授权的访问和恶意攻击。Alpine Linux 是一种轻量级的 Linux 发行版&#xff0c;常用于构建容器化应用和嵌入式系统。本文将详细介绍如何在 Alpine Linux 上启用或禁用防火墙。步骤 1&#xff1…

Seata-Server安装

1.去哪下 发布说明: https://github.com/seata/seata/releases 2.怎么玩 本地**Transactional**全局**GlobalTransactional** 我们只需要使用一个 GlobalTransactional 注解在业务方法上: 3.Seata-Server安装 官网地址 3.1.版本0.9.0 seata-server-0.9.0.zip解压到指定目…

Linux——进程轮换

目录 一.进程切换 1.定义&#xff1a; 2.硬件上下文&#xff1a; 总结&#xff1a; 一.进程切换 1.定义&#xff1a; 进程切换(process switch),作为抢占式多任务的一个重要功能&#xff0c;其实质就是OS内核挂起正在运行的进程A,然后将先前被挂起的另一个进程B恢复运行。 2.硬…

什么是Redission可重入锁,其实现原理是什么?

一、概述 Redission是一个可重入锁&#xff0c;它可以在分布式系统中用于实现互斥锁。这种锁可以允许多个线程同时获取锁&#xff0c;但在任何给定时间只有一个线程可以执行受保护的代码块。 Redission锁提供了一种简单的方法来保证在分布式系统中的互斥性&#xff0c;同时支…

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods 你已了解Pod以及如何通过ReplicaSets等资源部署它们以确保持续运行。虽然某些Pod可以独立完成工作&#xff0c;但现今许多应用程序需要响应外部请求。例如&#xff0c;在微服务的情况…

day42_jsp

今日内容 零、 复习昨日 一、JSP 二、EL 三、JSTL 四、MVC 零、 复习昨日 一、JSP 1.0 引言 现有问题 在之前学习Servlet时&#xff0c;服务端通过Servlet响应客户端页面&#xff0c;有什么不足之处&#xff1f; 开发方式麻烦&#xff1a;继承父类、覆盖方法、配置Web.xml或注…

【Proteus仿真】51单片机+步进电机驱动

【Proteus仿真】51单片机步进电机驱动 &#x1f516;Proteus仿真基础实验-步进电机驱动&#x1f33f;Proteus8.12平台 &#x1f4cb;步进电机简介 步进电机是一种将电脉冲转换为角位移的开环控制元步进电机。一般地&#xff0c;当步进驱动器接收到脉冲信号时&#xff0c;它将根…

【工作流】Activiti工作流简介以及Spring Boot 集成 Activiti7

文章目录 前言一、activiti介绍二、工作流引擎三、BPMN四、数据库五、Spring Boot 集成 Activiti7安装插件引入依赖配置文件 总结 前言 什么是工作流&#xff1f; 工作流指通过计算机对业务流程进行自动化管理&#xff0c;实现多个参与者按照预定义的流程去自动执行业务流程。 …

Linux :: vim 编辑器:详解:文本复制/粘贴/剪切/删除 与 撤销普通操作及撤销撤销操作

前言&#xff1a;本篇是 Linux 基本操作篇章的内容&#xff01; 笔者使用的环境是基于腾讯云服务器&#xff1a;CentOS 7.6 64bit。 学习集&#xff1a; C 入门到入土&#xff01;&#xff01;&#xff01;学习合集Linux 从命令到网络再到内核&#xff01;学习合集 前文&#x…

【哈希】位图/布隆过滤器

位图 前言 在实现位图结构之前我们先看一个问题&#xff1a; 给出40亿个不重复的无符号整型&#xff0c;并且是无序的。然后给一个无符号整数&#xff0c;怎样快速判断这个数是否在40亿个数之中。 方法一&#xff1a;对40亿个数据进行遍历。我们会发现&#xff0c;时间复杂度…

使用kotlin用回溯法解决电话号码的字母组合问题

17. 电话号码的字母组合 难度中等 2474 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#…

C++const函数的运用:深度解析const函数的魅力

C 深度解析const函数的魅力 1. C const函数的基本概念&#xff08;Basic Concepts of const Functions in C&#xff09;1.1 const函数的定义与特性&#xff08;Definition and Characteristics of const Functions&#xff09;1.2 const函数的使用场景&#xff08;Usage Scena…

Spring(四)基于xml的自动装配

自动装配&#xff1a;根据指定的策略&#xff0c;在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值。 首先我们来熟悉三层架构的创建过程&#xff1a; 三层架构为controller层&#xff0c;service层&#xff0c;dao层。 在service层里面创建ser…

php内置类小结

文章目录 php内置类小结Error、Exception进行xss、绕过hash比较Error类Exception类使用Error、Exception内置类绕过md5、sha1等哈希比较Error类详解Exception类详解例题&#xff1a;[2020 极客大挑战]Greatphp 使用DirectaryIterator、Filesystemlterator、Globlterator内置类读…

为什么要“内卷”创始人?如何内卷?

受疫情影响&#xff0c;近几年各个行业都受到很大的冲击&#xff0c;同时有许多知识创业者反而逆势增长&#xff0c;这是为什么呢&#xff1f;因为有一个好的领导者&#xff01;一家企业的发展&#xff0c;和创始人的心力和决心紧密联系着&#xff0c;只有好的将军才能带领出好…

如何解决航空企业数字化转型中的痛点?

数字化时代&#xff0c;越来越多的企业开始关注数字技术&#xff0c;希望通过数字化改造提高企业效率和竞争力&#xff0c;为企业创造更多的商机和利润。今天就来同大家探讨航空领域&#xff0c;小程序在企业数字化转型中发挥的作用、 航空业员工端App的敏捷转型挑战 技术上的…

资源配额(ResourceQuota) 资源限制(LimitRange)

资源配额 ResourceQuota 资源配额 ResourceQuota&#xff1a;限制命名空间总容量。 当多个团队、多个用户共享使用K8s集群时&#xff0c;会出现不均匀资源使用&#xff0c;默认情况下先到先得&#xff0c;这时可以通过ResourceQuota来对命名空间资源使用总量做限制&#xff0c;…