Texture filtering method and Light Source: Jeff molofee (nehe)'s OpenGL tutorial-Lesson 7

Source: Internet
Author: User
This lesson will teach you how to use three different texture filtering methods. It also teaches you how to use a keyboard to move objects in a scenario and how to use simple light in an OpenGL scenario. This course contains a lot of content. If you have any questions about the previous course, first review it. Before entering the code below, it is very important to have a good understanding of the basic knowledge.
We still modify the code in Lesson 1. Unlike the previous one, I will write the entire code segment as long as there are any major changes. At the beginning of the program, we add several new variables.
# Include <windows. h> // windows header file
# Include <stdio. h> // standard input/output header file (new)
# Include <Gl/Gl. h> // header file of the opengl32 Library
# Include <Gl/Glu. h> // header file of the glu32 Library
# Include <Gl/Glaux. h> // header file of the Glaux Library

HDC = NULL; // Private GDI device description table
Hglrc HRC = NULL; // permanent coloring description table
Hwnd = NULL; // save our window handle
Hinstance; // The instance that saves the program

Bool keys [256]; // Array Used for keyboard routines
Bool active = true; // The activity flag of the window. The default value is true.
Bool fullscreen = true; // The full screen flag is set to full screen by default.

The following lines are new. We add three Boolean variables.LightWhether the variable tracing light is enabled. The LP and FP variables are used to store whether the 'l' and 'F' keys are pressed. I will explain the importance of these variables later. Now, put it aside.
Bool light; // light source on/off
Bool LP; // is the L key pressed?
Bool FP; // is the F key pressed?
Now, set five variables to control the step size of the rotation angle around the X and Y axes, as well as the rotation speed around the X and Y axes. In addition, a Z variable is created to control the distance to the depth of the screen.
Glfloat xrot; // X Rotation
Glfloat yrot; // y Rotation
Glfloat xspeed; // X rotation speed
Glfloat yspeed; // y rotation speed

Glfloat Z =-5.0f; // The distance from the deep Screen

Set the array used to create the light source. We will use two different types of light. The first type is environmental light. The environment comes from all directions. Objects in all scenarios are under ambient light. The second type of light source is diffuse light. Diffuse Light is produced by a specific light source and reflected on the surface of an object in your scenario. The surface of any object that is directly illuminated by diffuse light becomes brighter, and the area that is almost invisible is darker. In this way, the edge of the wooden board box we created will produce a very good shadow effect.
The process of creating a light source is exactly the same as that of creating a color source. The first three parameters are RGB three-color components, and the last one is the alpha channel parameter.
Therefore, what we get from the following code is the white ambient light of the semi-bright (0.5f. If there is no ambient light, the area where the light is not diffuse will become very dark.
Glfloat lightambient [] = {0.5f, 0.5f, 0.5f, 1.0f}; // environmental light parameters (new)
The next line of code generates the brightest diffuse light. All parameter values are obtained as the maximum value 1.0f. It will shine in front of our wooden panel and looks good.
Glfloat lightdiffuse [] = {1.0f, 1.0f, 1.0f, 1.0f}; // diffuse light parameters (new)
Finally, we save the position of the light source. The first three parameters are the same as those in gltranslate. They are the displacement on the XYZ axis in turn. Since we want the light to shine directly on the front of the wooden box, the displacement on the XY axis is 0.0f. The third value is the displacement on the Z axis. To ensure that the light is always in front of the wooden box, we place the light source toward the observer (that is where you are .) Remove the screen. We usually call the position of the Screen Glass, that is, the 0.0f point of the Z axis. So the displacement on the Z axis is finally set to 2.0f. If you can see the light source, it will float in front of your monitor. Of course, if the wooden box is not behind the screen glass of the monitor, you will not be able to see the box. Note: I really appreciate the patience of nehe. To be honest, sometimes I get bored. Why is it that simple? But if everything is clear, will you still go through such a page to see it ?』
The last parameter is 1.0f. This will tell OpenGL that the coordinates specified here are the position of the light source. I will explain more in the future.
Glfloat lightposition [] = {0.0f, 0.0f, 2.0f, 1.0f}; // position of the light source (new)
FilterSpecifies the texture used for variable tracking. Use the first texture (texture 0)Gl_nearest(Not smooth) Build the filter method. Use the second texture (texture 1)Gl_linear(Linear filtering), the closer the image to the screen, the smoother it looks. The third texture (texture 2) uses the mipmapped filtering method, which creates a texture with excellent appearance. Based on our usage type,FilterThe values of the variables are equal to 0, 1, or 2. Next we start with the first texture.
Gluint texture [3] allocates storage space for three different textures. They are located in texture [0], texture [1], and texture [2] respectively.
Gluint filter; // Filter Type
Gluint texture [3]; // storage space of 3 textures

Lresult callback wndproc (hwnd, uint, wparam, lparam); // wndproc Definition

Now load a bitmap and use it to create three different textures. Use this courseGlauxThe auxiliary library to load bitmap, so you should confirm whether the Glaux library is included during compilation. I knowDelphiAndVC ++All includeGlauxLibrary, but other languages cannot be guaranteed. Note:GlauxYesOpenGLAuxiliary library, accordingOpenGLCross-platform features, all the code on the platform should be universal. However, the secondary database is not formal.OpenGLThe standard library does not appear on all platforms. But it is available on the Win32 platform. Of course, BCB is okay .』 Here I only annotate the newly added code. If you have any questions about a line of code, please refer to tutorial 6. That lesson explains in detail how to load and create textures.
After the previous Code andResizeglscene ()In the previous position, we added the following code. This is almost the same as the code for loading bitmap in Lesson 6.
Aux_rgbimagerec * loadbmp (char * filename) // load the bitmap
{
File * file = NULL; // file handle

If (! Filename) // confirm that the file name has been initialized
{
Return NULL; // No return null
}

File = fopen (filename, "R"); // check whether the file exists

If (File) // does it exist?
{
Fclose (File); // close the file handle
Return auxdibimageload (filename); // load the bitmap and return a pointer
}
Return NULL; // If loading fails, null is returned.
}

This Code calls the previous code to load the bitmap and convert it into three textures.StatusVariable tracking whether the texture has been loaded and created.
Int loadgltextures () // load bitmap and convert it to texture
{
Int status = false; // Status Indicator

Aux_rgbimagerec * textureimage [1]; // create a texture Bucket

Memset (textureimage, 0, sizeof (void *) * 1); // set the pointer to null

Now load the bitmap and convert it to a texture.Textureimage [0] = loadbmp ("Data/crate.bmp ")Call ourLoadbmp ()Function.DataDirectoryCrate.bmpWill be loaded. If everything is normal, the image data will be stored inTextureimage [0].StatusVariable is setTrueTo create a texture.
// Load the bitmap, check for errors, or exit if the bitmap does not exist.
If (textureimage [0] = loadbmp ("Data/crate.bmp "))
{
Status = true; // set status to true.
Now we have loaded image dataTextureimage [0]. We will use it to create three textures. The following line tells OpenGL that we want to create three textures, which will be stored inTexture [0],Texture [1]AndTexture [2].
Glgentextures (3, & texture [0]); // create a texture
In Lesson 6, we use linear filtered texture maps. This requires machines with high processing capabilities, but they look good. In this lesson, we will use the first texture to be createdGl_nearestMethod. In principle, this method does not actually implement filtering. It only occupies a small amount of processing capability and looks very bad. The only benefit is that our project can run normally on both very fast and very slow machines.
You will notice thatMinAndMagAll are usedGl_nearest, You can use them togetherGl_nearestAndGl_linear. Texture looks better, but we are more concerned about speed, so we use low quality textures.Min_filterUsed when the image is drawn less than the original size of the texture.Mag_filterIt is used when the image is drawn much larger than the original size of the texture.
// Create a nearest filter texture
Glbindtexture (gl_texture_2d, texture [0]);
Gltexparameteri (gl_texture_2d, gl_texture_mag_filter, gl_nearest); // (new)
Gltexparameteri (gl_texture_2d, gl_texture_min_filter, gl_nearest); // (new)
Glteximage2d (gl_texture_2d, 0, 3, textureimage [0]-> sizex, textureimage [0]-> sizey, 0, gl_rgb, gl_unsigned_byte, textureimage [0]-> data );
The next texture is the same as that in Lesson 6, linear filtering. The only difference is thatTexture [1]. Because this is the second texture. IfTexture [0], It overwritesGl_nearestTexture.
// Create a linear filter texture
Glbindtexture (gl_texture_2d, texture [1]);
Gltexparameteri (gl_texture_2d, gl_texture_mag_filter, gl_linear );
Gltexparameteri (gl_texture_2d, gl_texture_min_filter, gl_linear );
Glteximage2d (gl_texture_2d, 0, 3, textureimage [0]-> sizex, textureimage [0]-> sizey, 0, gl_rgb, gl_unsigned_byte, textureimage [0]-> data );
The following is a new method for creating a texture.Mipmapping! "NOTE: I cannot translate the Chinese character of this word, but it does not matter. After reading this section, you will know the meaning is the most important .』 You may notice that when the image becomes small on the screen, many details will be lost. The pattern that was pretty good just now has become very ugly. When you tell OpenGL to createMipmappedOpenGL will try to create high-quality textures of different sizes. When you drawMipmappedDuring the texture process, OpenGL selects the best texture (with more details) it has created, not just scales the original image (this will cause details to be lost ).
I once said that there is a way to bypass OpenGL's limitations on Texture width and height-64, 128, 256, and so on. The solution isGlubuild2dmipmaps. According to my findings, you can use any bitmap to create a texture. OpenGL automatically scales it to a normal size.
Because it is the third texture, we store it in texture [2]. In this case, all three textures are created.
// Create a mipmapped texture
Glbindtexture (gl_texture_2d, texture [2]);
Gltexparameteri (gl_texture_2d, gl_texture_mag_filter, gl_linear );
Gltexparameteri (gl_texture_2d, gl_texture_min_filter, gl_linear_mipmap_nearest); // (new)
The following line generates the mipmapped texture. We use three colors (red, green, and blue) to generate a 2D texture. Textureimage [0]-> sizex is the bitmap width, and extureimage [0]-> sizey is the bitmap height. gl_rgb means that RGB colors are used in sequence. Gl_unsigned_byte indicates that the unit of texture data is byte. Textureimage [0]-> data points to the bitmap used for texture creation.
Glubuild2dmipmaps (gl_texture_2d, 3, textureimage [0]-> sizex, textureimage [0]-> sizey, gl_rgb, expires, textureimage [0]-> data); (new)
}
Now, the memory used to store bitmap data is released. First, check whether the bitmap data is stored in textureimage [0]. If yes, delete it. Then release the bitmap structure to ensure that the memory is released.
If (textureimage [0]) // whether the texture exists
{
If (textureimage [0]-> data) // whether the texture image exists
{
Free (textureimage [0]-> data); // release the memory occupied by the texture image
}
Free (textureimage [0]); // release the image structure
}
Finally, we returnStatusVariable. If everything is OK,StatusThe value of the variable is true. Otherwise, the value is false.
Return status; // return the status variable.
}
Next we should load the texture and initialize the OpenGL settings.InitglThe first line of the function uses the above Code to load the texture. After creating the texture, we call
Glable (gl_texture_2d)Enable 2D Texture ing. The shadow mode is set to smooth shadow (Smooth Shading). The background color is set to black. We enable deep testing and then enable optimized perspective computing.
Int initgl (glvoid) // All OpenGL settings are started here
{
If (! Loadgltextures () // jump to the texture loading routine
{
Return false; // If the texture cannot be loaded, false is returned.
}

Glable (gl_texture_2d); // enable texture ing
Glshademodel (gl_smooth); // enables shadow smoothing.
Glclearcolor (0.0f, 0.0f, 0.0f, 0.5f); // black background
Glcleardepth (1.0f); // deep cache settings
Glable (gl_depth_test); // enable deep Test
Gldepthfunc (gl_lequal); // type of the deep Test
Glhint (gl_perspective_correction_hint, gl_nicest); // highly optimized Perspective Projection computing

Set the light source. The following line sets the ambient light volume. The light source starts to emit light at light1. At the beginning of this lesson, we stored the volume of ambient light inLightambientArray. Now we use this array (half-brightness ambient light ).
Gllightfv (gl_light1, gl_ambient, lightambient); // sets the ambient light.
Next, we will set the light volume of the diffuse light. It is stored in the lightdiffuse array (white light with full brightness ).
Gllightfv (gl_light1, gl_diffuse, lightdiffuse); // sets the diffuse light.
Set the position of the light source. Location stored inLightpositionArray (center right in front of the wooden box, X-0.0f, Y-0.0f, z direction to the observer 2 units <located outside the screen> ).
Gllightfv (gl_light1, gl_position, lightposition); // The Position of the light source.
Finally, we enable the No. 1 light source. We have not enabledGl_lightingSo you cannot see any light. Remember: Only setting, locating, or even enabling the light source will not work. Unless we enableGl_lighting.
Glable (gl_light1); // enable light source 1
Return true; // initialize OK
}
The next section of code draws the texture cube. I only annotate the newly added code. If you have any questions about the code without annotations, go back to Lesson 6.
Int drawglscene (glvoid) // start to draw all
{
Glclear (gl_color_buffer_bit | gl_depth_buffer_bit); // clear the screen and depth Cache
Glloadidentity (); // reset the current model observation matrix
Place and rotate the texture cube in the next three lines of code. Gltranslatef (0.0f, 0.0f, Z) moves the cube along the Z axis. Glrotatef (xrot, 1.0f, 0.0f, 0.0f) rotates the xrot around the X axis. Glrotatef (yrot, 0.0f, 1.0f, 0.0f) rotates the yrot around the Y axis.
Gltranslatef (0.0f, 0.0f, Z); // Z units for moving in/out of the screen
Glrotatef (xrot, 1.0f, 0.0f, 0.0f); // rotate around the X axis
Glrotatef (yrot, 0.0f, 1.0f, 0.0f); // rotate around the Y axis
The next line is similar to what we did in Lesson 6. The difference is that the texture we bind this time is texture [filter], not texture [0] in the previous lesson. At any time, we press the f key,Filter. If the value is greater than 2FilterWill be reset to 0. VariableFilterThe value is also set to 0. Use VariablesFilterWe can choose either of the three textures.
Glbindtexture (gl_texture_2d, texture [filter]); // select the texture determined by the filter.
Glbegin (gl_quads); // starts to draw a quadrilateral.
Glnormal3f is a new thing in this lesson. Normal refers to the normal. A normal refers to a straight line that goes through a point on a plane (polygon) and perpendicular to this plane (polygon. When using a light source, you must specify a normal. The normal tells OpenGL the orientation of the polygon and specifies the front and back of the polygon. If the normal is not specified, nothing can happen: the area that should not be illuminated is illuminated, and the back of the polygon is also illuminated ..... By the way, the normal should point to the outside of the polygon.

Looking at the front of the wooden box, you will notice that the normal is in the same direction as the Z axis. This means that the normal is pointing to the observer-yourself. This is exactly what we want. For the back of the wooden box, as we want, the normal is back to the observer. If the cube is rotated 180 degrees along the X or Y axis, the front side of the normal is still toward the observer, and the back side of the normal is also back to the observer. In other words, no matter which side it points to the observer as long as it moves toward the normal of the observer. Because the light source is close to the observer, this area will be illuminated whenever the normal is facing the observer. And the greater the normal is toward the light source, the brighter it is. If you place the observation point inside the cube, the normal is dark. Because the normal is outward. If there is no light source inside the cube, it is of course dark.

// Front side
Glnormal3f (0.0f, 0.0f, 1.0f); // The normal is directed to the observer.
Gltexcoord2f (0.0f, 0.0f); glvertex3f (-1.0f,-1.0f, 1.0f); // Point 1 (Front)
Gltexcoord2f (1.0f, 0.0f); glvertex3f (1.0f,-1.0f, 1.0f); // point 2 (Front)
Gltexcoord2f (1.0f, 1.0f); glvertex3f (1.0f, 1.0f, 1.0f); // point 3 (Front)
Gltexcoord2f (0.0f, 1.0f); glvertex3f (-1.0f, 1.0f, 1.0f); // point 4 (Front)
// Rear side
Glnormal3f (0.0f, 0.0f,-1.0f); // The normal is returned to the observer.
Gltexcoord2f (1.0f, 0.0f); glvertex3f (-1.0f,-1.0f,-1.0f); // Point 1 (back)
Gltexcoord2f (1.0f, 1.0f); glvertex3f (-1.0f, 1.0f,-1.0f); // point 2 (back)
Gltexcoord2f (0.0f, 1.0f); glvertex3f (1.0f, 1.0f,-1.0f); // point 3 (back)
Gltexcoord2f (0.0f, 0.0f); glvertex3f (1.0f,-1.0f,-1.0f); // point 4 (back)
// Top surface
Glnormal3f (0.0f, 1.0f, 0.0f); // normal up
Gltexcoord2f (0.0f, 1.0f); glvertex3f (-1.0f, 1.0f,-1.0f); // Point 1 (top)
Gltexcoord2f (0.0f, 0.0f); glvertex3f (-1.0f, 1.0f, 1.0f); // point 2 (top)
Gltexcoord2f (1.0f, 0.0f); glvertex3f (1.0f, 1.0f, 1.0f); // point 3 (top)
Gltexcoord2f (1.0f, 1.0f); glvertex3f (1.0f, 1.0f,-1.0f); // point 4 (top)
// Bottom
Glnormal3f (0.0f,-1.0f, 0.0f); // normal down
Gltexcoord2f (1.0f, 1.0f); glvertex3f (-1.0f,-1.0f,-1.0f); // Point 1 (bottom)
Gltexcoord2f (0.0f, 1.0f); glvertex3f (1.0f,-1.0f,-1.0f); // point 2 (bottom)
Gltexcoord2f (0.0f, 0.0f); glvertex3f (1.0f,-1.0f, 1.0f); // point 3 (bottom)
Gltexcoord2f (1.0f, 0.0f); glvertex3f (-1.0f,-1.0f, 1.0f); // point 4 (bottom)
// Right side
Glnormal3f (1.0f, 0.0f, 0.0f); // the right direction of the normal.
Gltexcoord2f (1.0f, 0.0f); glvertex3f (1.0f,-1.0f,-1.0f); // Point 1 (right)
Gltexcoord2f (1.0f, 1.0f); glvertex3f (1.0f, 1.0f,-1.0f); // point 2 (right)
Gltexcoord2f (0.0f, 1.0f); glvertex3f (1.0f, 1.0f, 1.0f); // point 3 (right)
Gltexcoord2f (0.0f, 0.0f); glvertex3f (1.0f,-1.0f, 1.0f); // point 4 (right)
// Left side
Glnormal3f (-1.0f, 0.0f, 0.0f); // The normal is left.
Gltexcoord2f (0.0f, 0.0f); glvertex3f (-1.0f,-1.0f,-1.0f); // Point 1 (left)
Gltexcoord2f (1.0f, 0.0f); glvertex3f (-1.0f,-1.0f, 1.0f); // point 2 (left)
Gltexcoord2f (1.0f, 1.0f); glvertex3f (-1.0f, 1.0f, 1.0f); // point 3 (left)
Gltexcoord2f (0.0f, 1.0f); glvertex3f (-1.0f, 1.0f,-1.0f); // point 4 (left)
Glend (); // The End Of The quadrilateral plot.
The following two lines of code increase the rotation values of xot and yrot by xspeed and yspeed respectively. The larger the value of xspeed and yspeed, the faster the cube turns.
Xrot + = xspeed; // xrot increases the xspeed unit.
Yrot + = yspeed; // yrot increases the yspeed unit.
Return true;
}
Now it is transferred to the winmain () main function. Here we will add the control code for switching the light source, rotating the wooden box, switching the filtering method, and moving the wooden box to the nearest distance. At the end of the winmain () function, you will see this line of swapbuffers (HDC) code. Then add the following code after this line.
The code checks whether the L key is pressed. If the L key has been pressed, but the LP value is not false, it means that the L key has not been released, and nothing will happen.
Swapbuffers (HDC); // exchange cache (dual cache)
If (Keys ['l'] &! LP) // is the L key pressed and released?
{
If the LP value is false, it means that the L key has not been pressed or has been released, and then the LP is set to true. The reason for checking the two conditions is to prevent the code from being repeatedly executed after the L key is held down, and cause the form to flash continuously.
After the LP is set to true, the computer will know that the L key has been pressed. Therefore, we can switch the on/off of the light source: Boolean variable light control light source switch.
Lp = true; // LP is set to true
Light =! Light; // true/false for switching the light source
The following lines check whether the light source should be enabled and the light variable value is used.
If (! Light) // if no light source exists
{
Gldisable (gl_lighting); // disable the light source
}
Else // otherwise
{
Glable (gl_lighting); // enables the light source.
}
}
The following code checks whether the "L" Key is released. If it is released, the variable LP is set to false. This means that the "L" key is not pressed. If this check is not performed, the light source cannot be switched off after it is enabled for the first time. The computer will think that the "L" key is always pressed.
If (! Keys ['l']) // is the L key released?
{
Lp = false; // If yes, set LP to false
}
Then perform a similar check on the "f" key. If you press the "f" key and the "f" key is not in the pressed State or it has never been pressed, set the variable FP to true. This means that the key is being pressed. Then add the filter variable to the value. If the filter variable is greater than 2 (because the array we use here is texture [3], and the texture greater than 2 does not exist), we reset the filter variable to 0.
If (Keys ['F'] &! FP) // is the F key pressed?
{
Fp = true; // FP is set to true
Filter + = 1; // Add a value for the filter
If (filter> 2) // is it greater than 2?
{
Filter = 0; // if it is reset to 0
}
}
If (! Keys ['F']) // is the F key released?
{
Fp = false; // If FP is set to false
}
Check whether the Pageup key is pressed. If yes, reduce the value of the Z variable. In this way, the call to gltranslatef (0.0f, 0.0f, Z) contained in the drawglscene function will make the wooden box farther away from the observer.
If (Keys [vk_prior]) // Pageup is pressed?
{
Z-= 0.02f; // if pressed, move the wooden box to the inside of the screen.
}
The next four lines check whether the Pagedown key is pressed. If yes, increase the value of the Z variable. In this way, the call to gltranslatef (0.0f, 0.0f, Z) contained in the drawglscene function will bring the wooden box closer to the observer.
If (Keys [vk_next]) // is the Pagedown pressed?
{
Z + = 0.02f; // if pressed, move the wooden box to the observer.
}
Check the direction keys. Press the left and right arrow keys xspeed to reduce or increase accordingly. Press the up and down arrow keys yspeed to reduce or increase accordingly. Remember that in future tutorials, if the xspeed and yspeed values increase, the cube will be converted faster. If you keep pressing a direction key, the cube will go in that direction faster.
If (Keys [vk_up]) // is the UP direction key pressed?
{
Xspeed-= 0.01f; // If yes, reduce xspeed
}
If (Keys [vk_down]) // is the down direction key pressed?
{
Xspeed + = 0.01f; // If yes, add xspeed
}
If (Keys [vk_right]) // is the right direction key pressed?
{
Yspeed + = 0.01f; // If yes, add yspeed
}
If (Keys [vk_left]) // is the left direction key pressed?
{
Yspeed-= 0.01f; // If yes, yspeed is reduced.
}
As in previous lessons, we finally need to correct the Form title.
If (Keys [vk_f1]) // is F1 pressed?
{
Keys [vk_f1] = false; // if it is set to false
Killglwindow (); // destroy the current window
Fullscreen =! Fullscreen; // switch to full screen/window mode
// Recreate the GL window
If (! Createglwindow ("nehe's textures, Lighting & keyboard tutorial", 640,480, 16, fullscreen ))
{
Return 0; // If a window cannot be created, the program exits.
}
}
}
}
}

// Close
Killglwindow (); // destroy the window
Return (msg. wparam); // exit the program
}
After completing this lesson, you should learn to create and use these three different texture ing filtering methods. And use the keyboard to interact with objects in the scenario. Finally, you should learn to apply simple light sources in the scene to make the scene look more realistic.
"Translator: the document of nehe seems very simple and arrogant. But how many other experts have you ever seen? I am not a master. I hope you are sincere .』
Below is the source code download link. Good luck!
* Download visual c ++ code for this lesson.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.