http://ogldev.atspace.co.uk/www/tutorial27/tutorial27.html
we have been utilizing the vertex and the fragment shaders from early on in this series of tutorials but in fact we left out an important shader stage called the geometry shader (GS). this type of shder was introduced by microsoft in directx10 and was later incorporated into the core opengl in version 3.2.
whiel the VS is executed per vertex and the FS is executed per pixel the GS is executed per primitive. this means that if we are drawing triangles each invocation of the GS receives exactly one triangle; if we are drawing lines each invocation of the GS receives exactly one line, etc.
this provides the GS a unique view of the model where the connectivity between the vertices is exposed to the developer, allowing her to develop new techniques that are based on that knowlege.
while the vertex shader always takes one vertex as input and outputs one vertex (i.e. it can not create or destroy vertices on the fly) the GS has the unique capability of making changes to the primitives that are passing through it. these changes include:
1/ chaning the topology of the incoming primitives. the GS can receive primitives in any topology type but can only output point lists, line strips and triangle strips (the strip topolgies are described below).
2/ the GS takes one primitive as input and can either drop it altoghther or output one or more primitives (this means that it can produce both less and more vertices than what it got). this capability is known as growing geometry. we will take advantage of this capability in this tutorial.
geometry shaders are optional. if u compile a program without a GS the primitives will simply flow directly from the vertex shader down to the fragment shader. that is why we have been able to get to this point without mentioning them.
triangle lists are constructed using trios 三元组 of vertices. vertices 0-2 are the first triangle, vertices 3-5 are the second and so forth.
to calcualte the number of triangles generated from any number of vertices simply divide the number of vertices by 3 (dropping the reminder).
triangle strips are more efficient because instead of adding 3 vertices to get a new triangle most of the time we just need to ad one vertex.
to construct a triangle strip start out with 3 vertices for the first triangle. when u add a fourth vertex u get the second triangle which is constructed from vertices 1-3.
when u add a fifth vertex u get the third triangle which is constructed vertices 2-4, etc. so from the second triangle and on every new vertex is joined with previous two to create a new triangle. here is an example:
as u can see, 7 triangles were created from just 9 vertices. if this was a triangle list we would have only 3 triangles.
triangle strips have an important property with regard to the winding order inside the triangles— the order is reversed on the odd triangles.
now that we understand the concept of geometry shaders let us see how they can help us implement a very useful and popular technique called billborading.
a billboard is a quad which always faces the camera. as the camera moves around the scene the billboard turns with it so that the vector from the billboard to the camera is always perpedicular to the billboard face.
this is the same idea as billboards in the real world that are placed along the highways in a way that will make them as visible as possible to the cars that are passing by.
once we got the quad to face the camera it is very easy to texture map it with image of a monster, tree or whatever and create a large number of scene objects that always face the camera.
billboards are often used to create a forest where a large number of trees is required in order to create the effect.
since the texture on the billboard is always facing the camera the player is fooled into thinking that the object has real depth where in fact it is completely flat. each billboard requires only 4 vertices and therefore it much cheaper in comparison to a full blown model. 一个吹起的模型,哈哈哈
in this tutorial we create a vertex buffer and populate with world space locations for the billboards. each location is just a single point (3D vector).
we will feed the locations into the GS and grow each location into a quad. this means that the input topology of the GS will be point list while the output topology will be triangle strip. taking advantage of triangle strips we will create a quad using 4 vertices:
the GS will take care of turing the quad to face the camera and will attach the proper texture coordinates to each outgoing vertex. the fragment shader will only need to sample the texture and provide the final color.
let us see how we can make the billboard always face the camera. in the following picture the black dot represents the camera and the red to represents the location of the billboard. bothAA dots are in world space and while it looks like they are located on a surface which is paralled to the XZ plane they do not have to be. any two points will do.
we now create a vector from the billboard location to the camera:
next we add the vector (0,1,0):
now do a cross product between these two vectors. the result is a vector which is perpedicular to the surface created by the two vectors. this vector points in the exact direction along which we need to extend the point and create a quad. the quad will be perpedicular to the vector from the original point to the camera, which is what we want. looking at the same scene from above we get the following (the yellow vector is the result of the cross product):
one of the things that often confuses developers is in what order to do the cross product (A cross B or B cross A?). the two options produce two vectors that are opposite to one another. knowing in advance the resulting vector is critical because we need to output the vertices such that the two triangles that make up the quad will be in clockwise order when looking at them from the point of view of the camera.
the left hand rule comes to our rescue here. 左手定则来发挥了作用,作者写的rescue us,拯救这个词用的很好。
this rule says that if u are standing at the location of the billboard and your forefinger is pointing towards the camera and you middle finger is pointing upworld (towards the sky) then your thumb will point along the result of “forefinger” cross “middle finger” (the remaining two fingers are often kept clamped here).
in this tutorial we call the result of the cross prodcut the “right” vector because it points towards the right when looking at your hand like that from the camera point of view. doing a “middle finger” cross “forefinger” will simple generate the “left” vector.
(we are using the left hand rule because we are working in a left hand coordinate system (Z grows as we move further into the scene). the right hand coordinate system is exactly the reverse).
这段话非常的重要,也是学习opengl中非常重要的知识点。
我来好好解释下,上面的图其实画的不够好,我来重新的画下:
上图中:V2叉乘V1,使用左手定则,得到的是V3向量,在摄像机(黑色点位置处),看V3向量,这个V3向量是右向量。
(billboard_list.h:27)
class BillboardList
{
public:
BillboardList();
~BillboardList();
bool Init(const std::string& TexFilename);
void Render(const Matrix4f& VP, const Vector3f& CameraPos);
private:
void CreatePositionBuffer();
GLuint m_VB;
Texture* m_pTexture;
BillboardTechnique m_technique;
};
the billboard List class encapsulates everything u need in order to geneate billboards. the init() function of the class takes the filename that contains the image which will be texture mapped on the billboard. the render() function is called from the main render loop and takes care of setting up the state and rendering the billboard.
this function needs two parameters: the combined view and projection matrix and the location of the camera in world space. since the billboard location is specified in world space we go directly to view and projection and skip the world transformation part. the class has three private attributes: a vertex buffer to store the location of the billboards, a pointer to the texture to map on the billboard and the billboard technique that contains the relevant shaders.
(billboard_list.cpp:80)
void BillboardList::Render(const Matrix4f& VP, const Vector3f& CameraPos)
{
m_technique.Enable();
m_technique.SetVP(VP);
m_technique.SetCameraPosition(CameraPos);
m_pTexture->Bind(COLOR_TEXTURE_UNIT);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, m_VB);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vector3f), 0); // position
glDrawArrays(GL_POINTS, 0, NUM_ROWS * NUM_COLUMNS);
glDisableVertexAttribArray(0);
}
this function enables the billboard technique, sets the required state into opengl and draws the points that are turned into quads in the GS. in this demo the billboards are laid out in strict rows and columns which expalins why we multiply them to get the number of points in the buffer. note that we are using point list as our input topology. the GS will need to match that.
(billboard_technique.h:24)
class BillboardTechnique : public Technique
{
public:
BillboardTechnique();
virtual bool Init();
void SetVP(const Matrix4f& VP);
void SetCameraPosition(const Vector3f& Pos);
void SetColorTextureUnit(unsigned int TextureUnit);
private:
GLuint m_VPLocation;
GLuint m_cameraPosLocation;
GLuint m_colorMapLocation;
};
this is the interface of the billboard technique. it requires only three parameters in order to do its job: the combined view/projection matrix, the position of the camera in world space and the number of the texutre unit where the billboard texture is bound.
(billboard.vs)
#version 330
layout (location = 0) in vec3 Position;
void main()
{
gl_Position = vec4(Position, 1.0);
}
this is the VS of the billboard technique and with most of the action taking place in the GS u can not ask for a simpler VS. (我觉得这里是简单的vs,为啥是can not ask for a simpler VS 呢,哦,这里的simpler是比较级,所以他的意思是,是简单到不能再简单的VS了。)
the vertex buffer contains only position vectors and since they are already specified in world space we only need to pass them through to the GS. that is it.
(billboard.gs:1)
#version 330
layout (points) in;
layout (triangle_strip) out;
layout (max_vertices = 4) out;
the core of the billboard technique is located in the GS. let us take a look at it piece by piece. we start by declaring some global stuff using the “layout” keyword. we tell the pipeline that the incoming topology is point list and the outgoing topology is triangle strip. we also tell it that we will emit no more than four vertices. this keyword is used to give the graphics driver a hint about the maximum number of vertices that can be emitted by the GS. knowing the limit in advance gives the driver an opportunity to optimize the behavior of the GS for the particular case. since we know that we are going to emit a quad for each incoming vertex we declare the maximum as four vertices.
(billboard.gs:7)
uniform mat4 gVP;
uniform vec3 gCameraPos;
out vec2 TexCoord;
the GS gets the position in world space so it only needs a view/projection matrix. it also needs the camera location in order to calcualte how to orient the billboard towards it. the GS generates texture coordinates for the FS so we must declare them.
(billboard.gs:12)
void main()
{
vec3 Pos = gl_in[0].gl_Position.xyz;
the line above is unique to the GS. since it is executed on a complete primitive we actually have access to each of the vertices that comprise it.
this is done using the built-in ‘gl_in’. this avariable is an array of structures that contains, among other things, the position that was written into gl_Position in the VS.
to access it we go to the slot we are interested in using the index of the vertex. in this specific example the input topology is point list so there is only a single vertex. we access it using ‘gl_in[0]’. if the input topology was a triangle we could also have written ‘gl_in[1]’ and ‘gl_in[2]’.
we only need the first three components of the position vector and we extract them to a local variable using ‘.xyz’.
vec3 toCamera = normalize(gCameraPos - Pos);
vec3 up = vec3(0.0,1.0,0.0);
vec3 right = cross(toCamera, up);
here we make the billboard face the camera per the explanation at the end of the backgroud section. we do a cross product between the vector from the point to the camera and a vector that points straight up. this provides the vector that points right when looking at the point from the camera point of view. we will now use it to ‘grow’ a quad around the point.
Pos -= (right * 0.5);
gl_Position = gVP * vec4(Pos, 1.0);
TexCoord = vec2(0.0, 0.0);
EmitVertex();
Pos.y += 1.0;
gl_Position = gVP * vec4(Pos, 1.0);
TexCoord = vec2(0.0, 1.0);
EmitVertex();
Pos.y -= 1.0;
Pos += right;
gl_Position = gVP * vec4(Pos, 1.0);
TexCoord = vec2(1.0, 0.0);
EmitVertex();
Pos.y += 1.0;
gl_Position = gVP * vec4(Pos, 1.0);
TexCoord = vec2(1.0, 1.0);
EmitVertex();
EndPrimitive();
}
the point in the vertex buffer is considered to be at the center of the bottom of the quad. 图中的红色的点。
we need to generate two fron facing triangles from it. we start by going left to the bottom left corner of the quad. 图中的1号点;
this is done by substracting half of the ‘right’ vector from the point.
next we calcualte the position in clip space by multiplying the point by the view/projection matrix.
we also set the texture coordinate (0,0) because we plan to cover the entire texture space using the quad.
to send the newly generated vertex down the pipe we call the built-in function EmitVertex().
after this function is called the variables that we have written to are considered undefined and we have to set new data for them.
in a similar way we generate the top left 图中2号点;and bottom right corners of the quad.图中3号点。this is the first front facing triangle. since the output topology of the GS is triangle strip we onle need one more vertex for the second triangle. it will be structured using the new vertex and last two vertices (which are the quad diagonal). the fourth and final vertex is the top right corner of the quad. 图中的第四个点. to end the triangle strip we call the built-in function EndPrimitive().
图中形成了两个三角形:
一个是:1,2,3形成的三角形;
一个是:2,3,4形成的三角形;
(billboard.fs)
#version 330
uniform sampler2D gColorMap;
in vec2 TexCoord;
out vec4 FragColor;
void main()
{
FragColor = texture2D(gColorMap, TexCoord);
if (FragColor.r == 0 && FragColor.g == 0 && FragColor.b == 0) {
discard;
}
}
the FS is very simple - most of its work is to sample the texture here the built-in keyword ‘discard’ is used in order to drop a pixel completely on certain cases. the picture of the hell-knight from Doom which is included in this tutorial shows the monster on a black background. using this texture as-is will make billboard look like a full sized card which is much larger than the monster itself. to overcome this we test the texel color and if it is black we drop the pixel. this allows us to select only the pixels that acutually make up the monster. try to disable ‘discard’ and see the difference.