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).
layout (location = 0) out whatever
" Taht doesn't compile because you're using GLSL 3.30. That functionality was added in GL 4.2, and is also a part of the ARB_separate_shader_object specification. So you could use it with 3.30, but you would also need to explicitly request the use of that extension in your shader. – Nicol Bolas