【OpenGL】笔记二十七、几何着色器

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 4


struct 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;
}

效果:在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ycr的帐号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值