zoukankan      html  css  js  c++  java
  • opengl 学习 之 13 lesson

    opengl 学习 之 13 lesson

    简介

    法向量纹理,让纹理显示的更逼真?

    link

    http://www.opengl-tutorial.org/uncategorized/2017/06/07/website-update/

    http://www.opengl-tutorial.org/cn/intermediate-tutorials/tutorial-13-normal-mapping/ (还有中文版在一直没有察觉)

    image

    T & B

    需要两个法向量,蓝色是Normal法向量,红色是T向量简称切平面向量,绿色B向量,也是切平面向量且B 和 N cross T 同向

    SKILL

    作者对于镜面纹理使用了一种技巧 使用颜色“vec3(0.3,0.3,0.3)” 灰色作为镜面颜色。

    DEBUG

    • 使用 immediate 模式,不是特别清楚,简单来说就是绘制顶点的相关向量??
    //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); // So that glBegin/glVertex/glEnd work 作者在这里放上了调试接口
    
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf((const GLfloat*)&ProjectionMatrix[0]);
    glMatrixMode(GL_MODELVIEW);
    glm::mat4 MV = ViewMatrix * ModelMatrix;
    glLoadMatrixf((const GLfloat*)&MV[0]);
    

    image

    通过观察顶点的NTB向量的朝向debug。

    • 通过物体周围的颜色来debug
    color.xyz = LightDirection_tangentspace;
    

    code

    // Include standard headers
    #include <stdio.h>
    #include <stdlib.h>
    #include <vector>
    
    // Include GLEW
    #include <GL/glew.h>
    
    // Include GLFW
    #include <GLFW/glfw3.h>
    GLFWwindow* window;
    
    // Include GLM
    #include <glm/glm.hpp>
    #include <glm/gtc/matrix_transform.hpp>
    using namespace glm;
    
    #include <common/shader.hpp>
    #include <common/texture.hpp>
    #include <common/controls.hpp>
    #include <common/objloader.hpp>
    #include <common/vboindexer.hpp>
    #include <common/tangentspace.hpp>
    
    int main( void )
    {
    	// Initialise GLFW
    	if( !glfwInit() )
    	{
    		fprintf( stderr, "Failed to initialize GLFW
    " );
    		getchar();
    		return -1;
    	}
    
    	glfwWindowHint(GLFW_SAMPLES, 1);
    	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
    	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    	//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); // So that glBegin/glVertex/glEnd work
    
    	// Open a window and create its OpenGL context
    	window = glfwCreateWindow( 1024, 768, "Tutorial 13 - Normal Mapping", NULL, NULL);
    	if( window == NULL ){
    		fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.
    " );
    		getchar();
    		glfwTerminate();
    		return -1;
    	}
    	glfwMakeContextCurrent(window);
    
    	// Initialize GLEW
    	glewExperimental = true; // Needed for core profile
    	if (glewInit() != GLEW_OK) {
    		fprintf(stderr, "Failed to initialize GLEW
    ");
    		getchar();
    		glfwTerminate();
    		return -1;
    	}
    
    	// Ensure we can capture the escape key being pressed below
    	glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
        // Hide the mouse and enable unlimited mouvement
        glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
        
        // Set the mouse at the center of the screen
        glfwPollEvents();
        glfwSetCursorPos(window, 1024/2, 768/2);
    
    	// Dark blue background
    	glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
    
    	// Enable depth test
    	glEnable(GL_DEPTH_TEST);
    	// Accept fragment if it closer to the camera than the former one
    	glDepthFunc(GL_LESS); 
    
    	// Cull triangles which normal is not towards the camera
    	glEnable(GL_CULL_FACE);
    
    	GLuint VertexArrayID;
    	glGenVertexArrays(1, &VertexArrayID);
    	glBindVertexArray(VertexArrayID);
    
    	// Create and compile our GLSL program from the shaders
    	GLuint programID = LoadShaders( "NormalMapping.vertexshader", "NormalMapping.fragmentshader" );
    
    	// Get a handle for our "MVP" uniform
    	GLuint MatrixID = glGetUniformLocation(programID, "MVP");
    	GLuint ViewMatrixID = glGetUniformLocation(programID, "V");
    	GLuint ModelMatrixID = glGetUniformLocation(programID, "M");
    	GLuint ModelView3x3MatrixID = glGetUniformLocation(programID, "MV3x3");
    
    	// Load the texture
    	GLuint DiffuseTexture = loadDDS("diffuse.DDS");
    	GLuint NormalTexture = loadBMP_custom("normal.bmp");
    	GLuint SpecularTexture = loadDDS("specular.DDS");
    	
    	// Get a handle for our "myTextureSampler" uniform
    	GLuint DiffuseTextureID  = glGetUniformLocation(programID, "DiffuseTextureSampler");
    	GLuint NormalTextureID  = glGetUniformLocation(programID, "NormalTextureSampler");
    	GLuint SpecularTextureID  = glGetUniformLocation(programID, "SpecularTextureSampler");
    
    	// Read our .obj file
    	std::vector<glm::vec3> vertices;
    	std::vector<glm::vec2> uvs;
    	std::vector<glm::vec3> normals;
    	bool res = loadOBJ("cylinder.obj", vertices, uvs, normals);
    
    	std::vector<glm::vec3> tangents;
    	std::vector<glm::vec3> bitangents;
    	computeTangentBasis(
    		vertices, uvs, normals, // input
    		tangents, bitangents    // output
    	);
    
    	std::vector<unsigned short> indices;
    	std::vector<glm::vec3> indexed_vertices;
    	std::vector<glm::vec2> indexed_uvs;
    	std::vector<glm::vec3> indexed_normals;
    	std::vector<glm::vec3> indexed_tangents;
    	std::vector<glm::vec3> indexed_bitangents;
    	indexVBO_TBN(
    		vertices, uvs, normals, tangents, bitangents, 
    		indices, indexed_vertices, indexed_uvs, indexed_normals, indexed_tangents, indexed_bitangents
    	);
    
    	// Load it into a VBO
    
    	GLuint vertexbuffer;
    	glGenBuffers(1, &vertexbuffer);
    	glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    	glBufferData(GL_ARRAY_BUFFER, indexed_vertices.size() * sizeof(glm::vec3), &indexed_vertices[0], GL_STATIC_DRAW);
    
    	GLuint uvbuffer;
    	glGenBuffers(1, &uvbuffer);
    	glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
    	glBufferData(GL_ARRAY_BUFFER, indexed_uvs.size() * sizeof(glm::vec2), &indexed_uvs[0], GL_STATIC_DRAW);
    
    	GLuint normalbuffer;
    	glGenBuffers(1, &normalbuffer);
    	glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
    	glBufferData(GL_ARRAY_BUFFER, indexed_normals.size() * sizeof(glm::vec3), &indexed_normals[0], GL_STATIC_DRAW);
    
    	GLuint tangentbuffer;
    	glGenBuffers(1, &tangentbuffer);
    	glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
    	glBufferData(GL_ARRAY_BUFFER, indexed_tangents.size() * sizeof(glm::vec3), &indexed_tangents[0], GL_STATIC_DRAW);
    
    	GLuint bitangentbuffer;
    	glGenBuffers(1, &bitangentbuffer);
    	glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
    	glBufferData(GL_ARRAY_BUFFER, indexed_bitangents.size() * sizeof(glm::vec3), &indexed_bitangents[0], GL_STATIC_DRAW);
    
    	// Generate a buffer for the indices as well
    	GLuint elementbuffer;
    	glGenBuffers(1, &elementbuffer);
    	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
    	glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned short), &indices[0], GL_STATIC_DRAW);
    
    	// Get a handle for our "LightPosition" uniform
    	glUseProgram(programID);
    	GLuint LightID = glGetUniformLocation(programID, "LightPosition_worldspace");
    
    	// For speed computation
    	double lastTime = glfwGetTime();
    	int nbFrames = 0;
    
    	do{
    
    		// Measure speed
    		double currentTime = glfwGetTime();
    		nbFrames++;
    		if ( currentTime - lastTime >= 1.0 ){ // If last prinf() was more than 1sec ago
    			// printf and reset
    			printf("%f ms/frame
    ", 1000.0/double(nbFrames));
    			nbFrames = 0;
    			lastTime += 1.0;
    		}
    
    		// Clear the screen
    		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    		// Use our shader
    		glUseProgram(programID);
    
    		// Compute the MVP matrix from keyboard and mouse input
    		computeMatricesFromInputs();
    		glm::mat4 ProjectionMatrix = getProjectionMatrix();
    		glm::mat4 ViewMatrix = getViewMatrix();
    		glm::mat4 ModelMatrix = glm::mat4(1.0);
    		glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
    		glm::mat3 ModelView3x3Matrix = glm::mat3(ModelViewMatrix);
    		glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;
    
    		// Send our transformation to the currently bound shader, 
    		// in the "MVP" uniform
    		glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
    		glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]);
    		glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
    		glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
    		glUniformMatrix3fv(ModelView3x3MatrixID, 1, GL_FALSE, &ModelView3x3Matrix[0][0]);
    		
    
    		glm::vec3 lightPos = glm::vec3(0,0,4);
    		glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z);
    
    		// Bind our diffuse texture in Texture Unit 0
    		glActiveTexture(GL_TEXTURE0);
    		glBindTexture(GL_TEXTURE_2D, DiffuseTexture);
    		// Set our "DiffuseTextureSampler" sampler to use Texture Unit 0
    		glUniform1i(DiffuseTextureID, 0);
    
    		// Bind our normal texture in Texture Unit 1
    		glActiveTexture(GL_TEXTURE1);
    		glBindTexture(GL_TEXTURE_2D, NormalTexture);
    		// Set our "NormalTextureSampler" sampler to use Texture Unit 1
    		glUniform1i(NormalTextureID, 1);
    
    		// Bind our specular texture in Texture Unit 2
    		glActiveTexture(GL_TEXTURE2);
    		glBindTexture(GL_TEXTURE_2D, SpecularTexture);
    		// Set our "SpecularTextureSampler" sampler to use Texture Unit 2
    		glUniform1i(SpecularTextureID, 2);
    
    
    		// 1rst attribute buffer : vertices
    		glEnableVertexAttribArray(0);
    		glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    		glVertexAttribPointer(
    			0,                  // attribute
    			3,                  // size
    			GL_FLOAT,           // type
    			GL_FALSE,           // normalized?
    			0,                  // stride
    			(void*)0            // array buffer offset
    		);
    
    		// 2nd attribute buffer : UVs
    		glEnableVertexAttribArray(1);
    		glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
    		glVertexAttribPointer(
    			1,                                // attribute
    			2,                                // size
    			GL_FLOAT,                         // type
    			GL_FALSE,                         // normalized?
    			0,                                // stride
    			(void*)0                          // array buffer offset
    		);
    
    		// 3rd attribute buffer : normals
    		glEnableVertexAttribArray(2);
    		glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
    		glVertexAttribPointer(
    			2,                                // attribute
    			3,                                // size
    			GL_FLOAT,                         // type
    			GL_FALSE,                         // normalized?
    			0,                                // stride
    			(void*)0                          // array buffer offset
    		);
    
    		// 4th attribute buffer : tangents
    		glEnableVertexAttribArray(3);
    		glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
    		glVertexAttribPointer(
    			3,                                // attribute
    			3,                                // size
    			GL_FLOAT,                         // type
    			GL_FALSE,                         // normalized?
    			0,                                // stride
    			(void*)0                          // array buffer offset
    		);
    
    		// 5th attribute buffer : bitangents
    		glEnableVertexAttribArray(4);
    		glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
    		glVertexAttribPointer(
    			4,                                // attribute
    			3,                                // size
    			GL_FLOAT,                         // type
    			GL_FALSE,                         // normalized?
    			0,                                // stride
    			(void*)0                          // array buffer offset
    		);
    
    		// Index buffer
    		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
    
    		// Draw the triangles !
    		glDrawElements(
    			GL_TRIANGLES,      // mode
    			indices.size(),    // count
    			GL_UNSIGNED_SHORT, // type
    			(void*)0           // element array buffer offset
    		);
    
    		glDisableVertexAttribArray(0);
    		glDisableVertexAttribArray(1);
    		glDisableVertexAttribArray(2);
    		glDisableVertexAttribArray(3);
    		glDisableVertexAttribArray(4);
    
    
    		////////////////////////////////////////////////////////
    		// DEBUG ONLY !!!
    		// Don't use this in real code !!
    		////////////////////////////////////////////////////////
    
    
    		glMatrixMode(GL_PROJECTION);
    		glLoadMatrixf((const GLfloat*)&ProjectionMatrix[0]);
    		glMatrixMode(GL_MODELVIEW);
    		glm::mat4 MV = ViewMatrix * ModelMatrix;
    		glLoadMatrixf((const GLfloat*)&MV[0]);
    
    
    		glUseProgram(0);
    
    		// normals
    		glColor3f(0,0,1);
    		glBegin(GL_LINES);
    		for (unsigned int i=0; i<indices.size(); i++){
    			glm::vec3 p = indexed_vertices[indices[i]];
    			glVertex3fv(&p.x);
    			glm::vec3 o = glm::normalize(indexed_normals[indices[i]]);
    			p+=o*0.1f;
    			glVertex3fv(&p.x);
    		}
    		glEnd();
    		// tangents
    		glColor3f(1,0,0);
    		glBegin(GL_LINES);
    		for (unsigned int i=0; i<indices.size(); i++){
    			glm::vec3 p = indexed_vertices[indices[i]];
    			glVertex3fv(&p.x);
    			glm::vec3 o = glm::normalize(indexed_tangents[indices[i]]);
    			p+=o*0.1f;
    			glVertex3fv(&p.x);
    		}
    		glEnd();
    		// bitangents
    		glColor3f(0,1,0);
    		glBegin(GL_LINES);
    		for (unsigned int i=0; i<indices.size(); i++){
    			glm::vec3 p = indexed_vertices[indices[i]];
    			glVertex3fv(&p.x);
    			glm::vec3 o = glm::normalize(indexed_bitangents[indices[i]]);
    			p+=o*0.1f;
    			glVertex3fv(&p.x);
    		}
    		glEnd();
    		// light pos
    		glColor3f(1,1,1);
    		glBegin(GL_LINES);
    			glVertex3fv(&lightPos.x);
    			lightPos+=glm::vec3(1,0,0)*0.1f;
    			glVertex3fv(&lightPos.x);
    			lightPos-=glm::vec3(1,0,0)*0.1f;
    			glVertex3fv(&lightPos.x);
    			lightPos+=glm::vec3(0,1,0)*0.1f;
    			glVertex3fv(&lightPos.x);
    		glEnd();
    
    		// Swap buffers
    		glfwSwapBuffers(window);
    		glfwPollEvents();
    
    	} // Check if the ESC key was pressed or the window was closed
    	while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
    		   glfwWindowShouldClose(window) == 0 );
    
    	// Cleanup VBO and shader
    	glDeleteBuffers(1, &vertexbuffer);
    	glDeleteBuffers(1, &uvbuffer);
    	glDeleteBuffers(1, &normalbuffer);
    	glDeleteBuffers(1, &tangentbuffer);
    	glDeleteBuffers(1, &bitangentbuffer);
    	glDeleteBuffers(1, &elementbuffer);
    	glDeleteProgram(programID);
    	glDeleteTextures(1, &DiffuseTexture);
    	glDeleteTextures(1, &NormalTexture);
    	glDeleteTextures(1, &SpecularTexture);
    	glDeleteVertexArrays(1, &VertexArrayID);
    
    	// Close OpenGL window and terminate GLFW
    	glfwTerminate();
    
    	return 0;
    }
    
    
    
    #version 330 core
    
    // Input vertex data, different for all executions of this shader.
    layout(location = 0) in vec3 vertexPosition_modelspace;
    layout(location = 1) in vec2 vertexUV;
    layout(location = 2) in vec3 vertexNormal_modelspace;
    layout(location = 3) in vec3 vertexTangent_modelspace;
    layout(location = 4) in vec3 vertexBitangent_modelspace;
    
    // Output data ; will be interpolated for each fragment.
    out vec2 UV;
    out vec3 Position_worldspace;
    out vec3 EyeDirection_cameraspace;
    out vec3 LightDirection_cameraspace;
    
    out vec3 LightDirection_tangentspace;
    out vec3 EyeDirection_tangentspace;
    
    // Values that stay constant for the whole mesh.
    uniform mat4 MVP;
    uniform mat4 V;
    uniform mat4 M;
    uniform mat3 MV3x3;
    uniform vec3 LightPosition_worldspace;
    
    void main(){
    
    	// Output position of the vertex, in clip space : MVP * position
    	gl_Position =  MVP * vec4(vertexPosition_modelspace,1);
    	
    	// Position of the vertex, in worldspace : M * position
    	Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz;
    	
    	// Vector that goes from the vertex to the camera, in camera space.
    	// In camera space, the camera is at the origin (0,0,0).
    	vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz;
    	EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace;
    
    	// Vector that goes from the vertex to the light, in camera space. M is ommited because it's identity.
    	vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz;
    	LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace;
    	
    	// UV of the vertex. No special space for this one.
    	UV = vertexUV;
    	
    	// model to camera = ModelView
    	vec3 vertexTangent_cameraspace = MV3x3 * vertexTangent_modelspace;
    	vec3 vertexBitangent_cameraspace = MV3x3 * vertexBitangent_modelspace;
    	vec3 vertexNormal_cameraspace = MV3x3 * vertexNormal_modelspace;
    	
    	mat3 TBN = transpose(mat3(
    		vertexTangent_cameraspace,
    		vertexBitangent_cameraspace,
    		vertexNormal_cameraspace	
    	)); // You can use dot products instead of building this matrix and transposing it. See References for details.
    
    	LightDirection_tangentspace = TBN * LightDirection_cameraspace;
    	EyeDirection_tangentspace =  TBN * EyeDirection_cameraspace;
    	
    	
    }
    
    
    
    #version 330 core
    
    // Interpolated values from the vertex shaders
    in vec2 UV;
    in vec3 Position_worldspace;
    in vec3 EyeDirection_cameraspace;
    in vec3 LightDirection_cameraspace;
    
    in vec3 LightDirection_tangentspace;
    in vec3 EyeDirection_tangentspace;
    
    // Ouput data
    out vec3 color;
    
    // Values that stay constant for the whole mesh.
    uniform sampler2D DiffuseTextureSampler;
    uniform sampler2D NormalTextureSampler;
    uniform sampler2D SpecularTextureSampler;
    uniform mat4 V;
    uniform mat4 M;
    uniform mat3 MV3x3;
    uniform vec3 LightPosition_worldspace;
    
    void main(){
    
    	// Light emission properties
    	// You probably want to put them as uniforms
    	vec3 LightColor = vec3(1,1,1);
    	float LightPower = 40.0;
    	
    	// Material properties
    	vec3 MaterialDiffuseColor = texture( DiffuseTextureSampler, UV ).rgb;
    	vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor;
    	vec3 MaterialSpecularColor = texture( SpecularTextureSampler, UV ).rgb * 0.3;
    
    	// Local normal, in tangent space. V tex coordinate is inverted because normal map is in TGA (not in DDS) for better quality
    	vec3 TextureNormal_tangentspace = normalize(texture( NormalTextureSampler, vec2(UV.x,-UV.y) ).rgb*2.0 - 1.0);
    	
    	// Distance to the light
    	float distance = length( LightPosition_worldspace - Position_worldspace );
    
    	// Normal of the computed fragment, in camera space
    	vec3 n = TextureNormal_tangentspace;
    	// Direction of the light (from the fragment to the light)
    	vec3 l = normalize(LightDirection_tangentspace);
    	// Cosine of the angle between the normal and the light direction, 
    	// clamped above 0
    	//  - light is at the vertical of the triangle -> 1
    	//  - light is perpendicular to the triangle -> 0
    	//  - light is behind the triangle -> 0
    	float cosTheta = clamp( dot( n,l ), 0,1 );
    
    	// Eye vector (towards the camera)
    	vec3 E = normalize(EyeDirection_tangentspace);
    	// Direction in which the triangle reflects the light
    	vec3 R = reflect(-l,n);
    	// Cosine of the angle between the Eye vector and the Reflect vector,
    	// clamped to 0
    	//  - Looking into the reflection -> 1
    	//  - Looking elsewhere -> < 1
    	float cosAlpha = clamp( dot( E,R ), 0,1 );
    	
    	color = 
    		// Ambient : simulates indirect lighting
    		MaterialAmbientColor +
    		// Diffuse : "color" of the object
    		MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) +
    		// Specular : reflective highlight, like a mirror
    		MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance);
    
    }
    
    Hope is a good thing,maybe the best of things,and no good thing ever dies.----------- Andy Dufresne
  • 相关阅读:
    myEclipse项目转成Eclipse项目
    struts1.2上传多个文件
    mysql数据库无法连接(JDBC)java.net.ConnectException: Connection timed out
    【转载】showModalDialog returnValue is undefined in Google Chrome
    开发规范浅谈
    【转载】JExcelApi(JXL)学习笔记
    【转载】解决window.showModalDialog 模态窗口中location 打开新窗口问题
    Response attachment filename 中文乱码
    解决java.lang.IllegalArgumentException: Can't load standard profile: LINEAR_RGB.pf
    进程、线程、协程对比
  • 原文地址:https://www.cnblogs.com/eat-too-much/p/14082735.html
Copyright © 2011-2022 走看看