简体   繁体   中英

Is it possible to have the same vertex shader and fragment shader with or without a geometry shader?

So I'm just learning about geometry shaders, and I have a use case for one.

For performance reasons I don't want to use the geometry shader all the time, even as a pass through, because most objects most of the time don't need it. However when I do want it, the vertex and fragment shaders should do the same thing. Can I reuse my vertex and fragment shaders.

IE.

vertex:

#version 330
in vec3 position;
out vec3 whatever;

void main()
{
    ...
}

geometry:

#version 330
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in whatever[];
out whatever;

void main()
{
    ...
}

fragment:

#version 330
in whatever

void main()
{
    ...
}

So without the geometry shader this works, as vertex out whatever corresponds to fragment in whatever . However with the geometry shader I end up redifining whatever as both an in and an out.

I read you can use: layout (location = 0) out whatever and then you don't need the same names, but this doesn't work for me, giving compilation error: ERROR: -1:65535: '' : storage qualifier not valid with layout qualifier id . I think this is due to me not having a new enough opengl version to support that syntax.

I also read you can use the extension: arb_separate_shader_objects, but failed to find any examples of it being used.

Any suggestions?

You can in fact do this. But you need interface blocks to do it. Indeed, this is one of the primary problems input/output interface blocks were created to solve:

#version 330
in vec3 position;
out Data
{
  vec3 whatever;
};

void main()
{
    ...
    whatever = ...;
}

This is your vertex shader, using an interface block for its outputs. Vertex shader inputs cannot be aggregated into interface blocks. Notice that the vertex shader calls the member of the interface block whatever . That will be important soon.

In your fragment shader:

#version 330
in Data
{
    in vec3 whatever;
};

void main()
{
    ...
    ... = whatever;
}

The fragment shader now has a complementary input block declared. In order for this to work, the block must use the same name as the corresponding output block from the previous stage. And it must declare all of the same variables as the corresponding output block, in the same order.

Note again that the fragment shader refers to the variable as whatever . This will be important presently.

If you took these two shaders and linked them together (either directly or indirectly with separate programs ), they would work fine. Now, it's time to see what the Geometry Shader would have to look like to fit between them:

#version 330
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;

in Data
{
    vec3 whatever;
} vertex_input[];

out Data
{
    vec3 whatever;
} vertex_output;

void main()
{
    ...
    vertex_output.whatever = vertex_input[0].whatever;
}

OK, a lot of stuff just happened.

The first thing you'll note is that we seem to have declared the same interface block twice. No, we have not; input and output interface blocks are in different namespaces. So it's perfectly fine to declare an input interface block with the same block name as an output one.

The input Data matches up with the output Data from the vertex shader. The output Data matches up with the input Data for the fragment shader. So the interfaces match.

Now, you might notice that we declared these blocks differently. The input block has a tag vertex_input[] , while the output block has vertex_output . This is not like a struct variable being declared after a struct declaration in C/C++. This name is what is known as the interface block's instance name . This is very important.

Why? Because it allows us to scope names of interface block members.

Blocks declared without an instance name globally scope their members. This is why we could refer to whatever in the VS and FS with just that name. However, since the GS needs to have two separate whatever variables, we need some way to differentiate them.

That's what the instance name is for. By giving the block an instance name, we must prefix all references to that variable with that instance name.

Note that the blocks across interfaces are matched by the block name . That is, the input of the GS matches the output of the VS because they're both named Data . The instance name is only used within a shader to name-scope members. It does not affect interface matching.

Lastly, you'll notice that the GS's input variable is not an array. It is instead the instance name of the interface block which is arrayed. That's how interface blocks work in the GS (and tessellation shaders that take arrayed inputs/outputs).

Given this definition, you can slip this GS between the VS and FS without having to change any of them. So you don't have to modify the VS or FS code at all (obviously besides the use of interface blocks).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM