OpenGL Environment Configuration!
Draw donuts
Without further ado, the main function directly before the code
#include "GLTools.h"
#include <GLUT/GLUT.h>
#include "GLFrustum.h"
#include "GLMatrixStack.h"
#include "GLGeometryTransform.h"
#include "GLFrame.h"GLFrustum viewFrustum; GLFrustum viewFrustum; // GLMatrixStack projectionMatrix; // GLMatrixStack modelViewMatrix; GLGeometryTransform transformPipeline; GLGeometryTransform transformPipeline; // Set the role frame as the viewer GLFrame viewFrame; // fixed shaderManager GLShaderManager shaderManager; // GLTriangleBatch torusBatch; // Render scene voidRenderScene() {/ / clear color depth buffer and buffer: if not empty, the last time the content of the drawing will be preserved in the buffer, you can turn off the a look at the effect of the code to run glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); / / the observer into model matrices stack modelViewMatrix. PushMatrix (viewFrame); // Set the drawing color GLfloatVRed [] = {1.0, 0.0, 0.0, 1.0}; / / surface shader / / shaderManager UseStockShader (GLT_SHADER_FLAT, transformPipeline GetModelViewProjectionMatrix (), vRed); // Use the default light shader /** // Parameter 1: GLT_SHADER_DEFAULT_LIGHT // Parameter 2: model view matrix // parameter 3: projection matrix // parameter 4: Base color value */ shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjec tionMatrix(),vRed); / / Draw torusBatch. The Draw (); / / out of the stack, to restore modelViewMatrix. After PopMatrix (); GlutSwapBuffers (); } voidSetupRC(){// Set the background color glClearColor(0.3f, 0.3f, 0.3f, 1.0f); / / initialize shader fixed managers shaderManager InitializeStockShaders (); ViewFrame.MoveForward(7.0); /** // Parameter 1: GLTriangleBatch Container help class // Parameter 2: outer edge radius // Parameter 3: inner edge radius // Parameter 4, 5: The number of subdivided cells of the primary radius and the subdivided cell of the secondary radius is generally twice the relationship */ gltMakeTorus(torusBatch, 1.0f, 0.3F, 52, 26); // Point size glPointSize(1.0f); } void SpecialKeys(int key,int x,int y){if(key == GLUT_KEY_UP) {viewFrame.rotateWorld (m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f); }if (key == GLUT_KEY_DOWN) {
viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0f);
}
if (key == GLUT_KEY_LEFT) {
viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0, 1.0, 0.0);
}
if(key == GLUT_KEY_RIGHT) {viewFrame.rotateWorld (m3dDegToRad(5.0), 0.0, 1.0f, 0.0); } // re-render glutPostRedisplay(); Void ChangeSize(int w, int h){// Prevent h from changing to 0if(h==0) { h = 1; } // Set window size glViewport(0, 0, w, h); / / create the perspective projection method viewFrustum. SetPerspective (35.0,float(w)/float(h), 1.0 f, 500.0 f); / / projection matrix projection to the projection matrix stack projectionMatrix. LoadMatrix (viewFrustum. GetProjectionMatrix ()); / / initialize the rendering pipeline, set the transformation pipeline to set two matrix to matrix white stack transformPipeline. SetMatrixStacks (modelViewMatrix projectionMatrix); } int main(int argc, char * argv[]){//// open a workspace gltSetWorkingDirectory(argv[0]); // Initialize glutInit(&argc, argv); / / apply for a color area, depth buffer buffer area, double buffer area, model glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL); // Set the size of window glutInitWindowSize(800, 600); // Set the window name glutCreateWindow("Donuts"); GlutReshapeFunc (ChangeSize); GlutSpecialFunc (SpecialKeys); GlutDisplayFunc (RenderScene); GLenum err = glewInit(); // Check whether the glew library can be initialized to ensure that the project can use the OpenGL framework.if(GLEW_OK ! = err) { fprintf(stderr,"GLEW Error:%s\n",glewGetErrorString(err));
return1; } // Initialize the canvas SetupRC(); Runloop glutMainLoop();return 0;
}
Copy the code
Draw the sweet graph effect
This may seem perfect, but when we press the “up, down, left and right” key on the keyboard to rotate, we get what we see below
What’s going on here? RenderScene = RenderScene = RenderScene = RenderScene = RenderScene = RenderScene = RenderScene = RenderScene
// Set the drawing color GLfloatVRed [] = {1.0, 0.0, 0.0, 1.0};Copy the code
Front and back culling, also known as “hidden face elimination”
When drawing a 3D scene, we need to decide which parts are visible to the observer and which parts are not. The parts that cannot be seen should be discarded as soon as possible. For example, after a non-opaque wall, it should not be rendered. This is called Hidden surface elimination.
How to understand the elimination of hidden surface?
Take a chestnut
When the sun shines on the earth, the earth is round, so the sun shines on only one side of the earth, the other side does not shine, and there is a division of day and night. In contrast to the sun, the sun only sees the day side of the Earth, not the night side.
Here’s another chestnut
The cans on the table, when we look at the cans, we can only see one side, and we can’t see the other side, to see the cans can can only be rotated, or people go to the corresponding position to see.
Donuts, the earth and cans all have one thing in common: 3D objects.
Mobile phones and computer screens are flat. When displaying A 3D object, of course only one side of the 3D graph can be displayed. When displaying a certain side of the 3D graph on a computer, do you need to render all the faces of the 3D object on the screen? Obviously not necessary. No matter how many faces the 3D graphics have, all we need to do is draw the face we can see on the screen. When the 3D object rotates or the observer moves, we get a new face and then redraw the new face. In this way, the computer does not have to deal with the graph data on the back, greatly reducing the amount of computation and improving performance.
How to eliminate hidden surfaces?
The 3D objects mentioned in the corn above, such as doughnuts, earth and soda cans, are all visible and invisible faces. We call the visible faces the front and the invisible faces the back.
To render this 3D object on a computer screen, we simply tell the computer to leave the front side and discard the back side.
So how do you tell if it’s heads or tails?
As we know, OpenGL has only three ways of connecting vertices: points, lines, and triangles. When rendering complex graphics, we use triangles in order to reuse vertices and save performance. But triangles are also drawn clockwise and counterclockwise.
- 1. Triangular faces joined in the order of counterclockwise vertices;
- Back: Triangular faces with degrees of alignment connected by clockwise vertices.
Take the counterclockwise figure above:
When we look at it from the screen it’s counterclockwise, but when we look at the triangle from the back of the screen, it becomes the back again.
So, the face of a 3D object that we can see is drawn counterclockwise (front); The faces we can’t see are drawn clockwise (back). So we just have to make a pact with the computer that what we can see is the front side, what we can’t see is the back side, and when we draw, we get rid of the back side.
This is the heads and tails cull!
How to implement front and back culling in OpenGL
Same old rule — code
Int iCull = 0;Copy the code
- Step 1: Add a right click menu to main and implement a click event method
// Add right click menu bar glutCreateMenu(ProcessMenu); glutAddMenuEntry("Front and back cull", 1);
glutAttachMenu(GLUT_RIGHT_BUTTON);
Copy the code
- Step 2: Implement the click event function
void ProcessMenu(int value){
switch (value) {
case0: {}break;
case1: { iCull = ! iCull; }break; } // Trigger rerender glutPostRedisplay(); }Copy the code
- Step 3: Modify the RenderScene function by adding the following code
if(iCull) {// Open the front and back culling glEnable(GL_CULL_FACE); }else{// turn off the front and back culling glDisable(GL_CULL_FACE); }Copy the code
To enable positive culling, just implement glEnable(GL_CULL_FACE); GlDisable (GL_CULL_FACE); Otherwise, everything would be open.
However, if you want to change clockwise to front and counterclockwise to back; It’s okay to get rid of the heads and keep the tails
Which face (front/back) the user chooses to exclude
void glCullFace(GLenum mode); The mode parameter is GL_FRONT,GL_BACK, and GL_FRONT_AND_BACK. The default value is GL_BACK
Use the user to specify which side to wrap around
void glFrontFace(GLenum mode); The mode parameter is GL_CW,GL_CCW. The default value is GL_CCW
Let’s see what happens
OK, so you can’t see the black on the back. But is that enough?
What happened when we rotated the doughnut to this position and found that it had taken a “bite”??
The depth of the test
What is depth?
Depth is the distance between the Z coordinate of the pixel and the observer in OpenGL coordinates.
What is a depth buffer?
The depth cache is an area of memory dedicated to storing the depth value of each pixel (drawn on the screen).
Why do we need a deep buffer?
When depth testing is not used, if we draw a close object first and then draw a distant object, the distant bitmap will cover the close object because of the later drawing. With the depth buffer, the order in which objects are drawn is not so important.
The closer a body is, the more observant it is. The smaller or larger the value of the number Z, the simpler it is, the more observant it is
- If the observer is in the positive direction of the Z-axis, the larger the z-value is, the closer it is to the observer.
- If the observer is in the negative direction of the Z-axis, the smaller the z-value, the closer it is to the observer.
That explains a lot, but what caused the doughnut to take a bite?
As we know, doughnuts are A 3 d graphics, rendering it’s depth of each pixel has its own values (Z value), in the process of drawing, will draw all the positive one, pictured above, should draw A red part below, then draw the B black shadows, buffer inside put is B the color data, So the color rendered to the screen is also the color of side B, resulting in a doughnut “bite.”
In order to solve this display anomaly caused by drawing sequence, we enabled deep testing.
Definition of depth testing
The DepthBuffer (DepthBuffer) corresponds to the ColorBuffer (ColorBuffer). The color cache stores pixel color information, while the depth buffer stores pixel depth information. When deciding whether to draw an object surface, the depth value of the pixel corresponding to the surface is first compared to the value in the current depth buffer. If it is greater than the value in the depth buffer, it is discarded. Otherwise, use the depth value and color value corresponding to this pixel. Update the depth buffer and color buffer separately. This process is called deep testing.
To solve the doughnut “bite” problem
When the depth test is enabled, when a pixel is drawn, the depth value of the pixel should be compared with the depth value of the corresponding pixel in the depth cache. If the observer is in the positive direction of the Z-axis, the one who saves the most is the one. If the observer is in the negative direction of the Z axis, the smaller one will be saved. This ensures that the pixel drawn will be the closest pixel to the observer, thus solving the doughnut “bite” problem.
How do I enable deep testing?
Right click on the menu bar to add “Deep Testing”
Int iDepth = 0;Copy the code
Right click on menu bar to add “Deep Test”
glutAddMenuEntry("Depth test", 0);
Copy the code
ProcessMenu function adds case 0
void ProcessMenu(int value){
switch (value) {
case0: { iDepth = ! iDepth; }break;
case1: { iCull = ! iCull; }break;
}
glutPostRedisplay();
}
Copy the code
RenderScene turns on depth testing
// Depth testif (iDepth) {
glEnable(GL_DEPTH_TEST);
}else{
glDisable(GL_DEPTH_TEST);
}
Copy the code
Run to view the effect
What we found was that in depth testing, we didn’t just solve the doughnut bite problem. In addition, it also solves the problem of eliminating front and back. This is also of course, because the depth values on the front side must be closer to the observer and rendered on the screen, and the depth values on the back side are farther away and not rendered on the screen.
Polygon migration
What is the reason for z-fighting?
We know through the above study, the principle of depth test is to compare two depth of pixel value (Z value) to determine exactly which pixels to save render in the depth buffer to the screen, so very close when two layers, namely the depth of the two pixel value (Z value) is very close, it would be how?
For example, when the depth values of A and B are respectively 1.00000000001 and 1.00000000000, due to the accuracy of the computer, the computer will judge that the two depth values are the same, then the following image will appear:
The depth values of Az part and Bz part are very close, and the computer judges that their depth values are the same size. At this time, two display effects, A and B, will appear. This is the z-fighting phenomenon.
How to solve the z-fighting phenomenon? — Polygon offset
Since the reason for z-fighting is that the two layers are too close to each other, why not let them be far away from each other?
The code above turns on polygon offset to keep two pixels with very similar depth values apart
glEnable(GL_POLYGON_OFFSET_FILL);
Copy the code
Turn off polygon offset
glDisable(GL_POLYGON_OFFSET_FILL);
Copy the code
How to prevent z-Fighting flicker problem
- Do not place two objects too close together to avoid overlapping when rendering. This approach requires that objects in the scene be inserted with a small offset, so it is possible to avoid z-fighting.
- Set as far away from the viewer as possible from the near clipping surface. Depth accuracy is very high near the near clipping surface, so keeping the near clipping surface as far away as possible will improve the accuracy of the entire clipping range. However, in this way, objects close to the observer will be clipped out, so it is necessary to debug the clipping surface parameters.
- The use of higher bit depth buffers, typically 24 bit depth buffers, some hardware now use 32 or 64 bit buffers to improve accuracy.