当前位置: 首页 > news >正文

网站建设 设计方案 百度文库网易企业邮箱账号

网站建设 设计方案 百度文库,网易企业邮箱账号,dedecms做资源下载网站,做轴承生意的网站文章目录 一、前言二、环境光照三、漫反射光照3.1、法向量3.2、计算漫反射光照3.3、全部代码3.4、法线矩阵 四、镜面光照4.1、全部代码 一、前言 现实世界的光照是极其复杂的#xff0c;而且会受到诸多因素的影响#xff0c;这是我们有限的计算能力所无法模拟的。 因此Open… 文章目录 一、前言二、环境光照三、漫反射光照3.1、法向量3.2、计算漫反射光照3.3、全部代码3.4、法线矩阵 四、镜面光照4.1、全部代码 一、前言 现实世界的光照是极其复杂的而且会受到诸多因素的影响这是我们有限的计算能力所无法模拟的。 因此OpenGL的光照使用的是简化的模型对现实的情况进行近似这样处理起来会更容易一些而且看起来也差不多一样。 这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。 冯氏光照模型的主要结构由3个分量组成环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。 下面这张图展示了这些光照分量看起来的样子 环境光照(Ambient)即使在黑暗的情况下世界上通常也仍然有一些光亮月亮、远处的光所以物体几乎永远不会是完全黑暗的。为了模拟这个我们会使用一个环境光照常量它永远会给物体一些颜色。漫反射光照(Diffuse)模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源它就会越亮。镜面光照(Specular)模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。 二、环境光照 光通常都不是来自于同一个光源而是来自于我们周围分散的很多光源即使它们可能并不是那么显而易见。 光的一个属性是它可以向很多方向发散并反弹从而能够到达不是非常直接临近的点。所以光能够在其它的表面上反射对一个物体产生间接的影响。 考虑到这种情况的算法叫做全局照明(Global Illumination)算法但是这种算法既开销高昂又极其复杂。 由于我们现在对那种又复杂又开销高昂的算法不是很感兴趣所以我们将会先使用一个简化的全局照明模型即环境光照。 我们使用一个很小的常量光照颜色添加到物体片段的最终颜色中这样子的话即便场景中没有直接的光源也能看起来存在有一些发散的光。 把环境光照添加到场景里非常简单。我们用光的颜色乘以一个很小的常量环境因子再乘以物体的颜色然后将最终结果作为片段的颜色 片段着色器 #version 330 coreout vec4 FragColor;uniform vec3 objectColor; uniform vec3 lightColor;void main() {//环境光float ambientStrength 0.1;vec3 ambient ambientStrength * lightColor;vec3 result ambient * objectColor;FragColor vec4(result, 1.0); } 如果你现在运行你的程序你会注意到冯氏光照的第一个阶段已经应用到你的物体上了。这个物体非常暗但由于应用了环境光照注意光源立方体没受影响是因为我们对它使用了另一个着色器也不是完全黑的。它看起来应该像这样 三、漫反射光照 漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度。为了能够更好的理解漫反射光照请看下图 图左上方有一个光源它所发出的光线落在物体的一个片段上。我们需要测量这个光线是以什么角度接触到这个片段的。如果光线垂直于物体表面这束光对物体的影响会最大化更亮。为了测量光线和片段的角度我们使用一个叫做法向量(Normal Vector)的东西它是垂直于片段表面的一个向量这里以黄色箭头表示这两个向量之间的角度很容易就能够通过点乘计算出来。点乘返回一个标量我们可以用它计算光线对片段颜色的影响。不同片段朝向光源的方向的不同这些片段被照亮的情况也不同。 注意为了只得到两个向量夹角的余弦值我们使用的是单位向量长度为1的向量所以我们需要确保所有的向量都是标准化的否则点乘返回的就不仅仅是余弦值了见变换。 所以计算漫反射光照需要 法向量一个垂直于顶点表面的向量。定向的光线作为光源的位置与片段的位置之间向量差的方向向量。为了计算这个光线我们需要光的位置向量和片段的位置向量。 3.1、法向量 法向量是一个垂直于顶点表面的单位向量。由于顶点本身并没有表面它只是空间中一个独立的点我们利用它周围的顶点来计算出这个顶点的表面。 我们能够使用一个小技巧使用叉乘对立方体所有的顶点计算法向量但是由于3D立方体不是一个复杂的形状所以我们可以简单地把法线数据手工添加到顶点数据中。 float vertices[] {//顶点数据 //顶点法向量-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f };修改了顶点数据我们接下来就要修改顶点属性指针顶点着色器 注意灯使用同样的顶点数组作为它的顶点数据然而灯的着色器并没有使用新添加的法向量。我们不需要更新灯的着色器或者是属性的配置但是我们必须至少修改一下顶点属性指针来适应新的顶点数组的大小 我们只想使用每个顶点的前三个float并且忽略后三个float所以我们只需要把步长参数改成float大小的6倍就行了。 //物体 //---------------------------------------------------------------- unsigned int VBO, objectVAO; glGenVertexArrays(1, objectVAO); //创建顶点数组对象 glGenBuffers(1, VBO); //创建顶点缓冲对象glBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //将顶点数据复制到GL_ARRAY_BUFFER缓冲区之后可通过VBO进行操作glBindVertexArray(objectVAO); //绑定VAO//设定顶点属性指针 //位置属性 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); //法向量属性 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);//光源(VBO用上面的) //---------------------------------------------------------------- unsigned int lightVAO; glGenVertexArrays(1, lightVAO); //创建顶点数组对象 glBindVertexArray(lightVAO); //绑定VAOglBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); 所有光照的计算都是在片段着色器里进行所以我们需要将法向量由顶点着色器传递到片段着色器。我们这么做 注意光源始终是不变的这里的着色器是指被投光物体的着色器我们最终也是看物体随光线的变化 顶点着色器 #version 330 corelayout (location 0) in vec3 aPos; layout (location 1) in vec3 aNormal;out vec3 Normal;uniform mat4 model; uniform mat4 view; uniform mat4 projection;void main() {Normal aNormal;gl_Position projection * view * model * vec4(aPos, 1.0f); } 片段着色器 #version 330 corein vec3 Normal;out vec4 FragColor;uniform vec3 objectColor; uniform vec3 lightColor; uniform vec3 lightPos;void main() {......FragColor vec4(result, 1.0); } 3.2、计算漫反射光照 我们现在对每个顶点都有了法向量但是我们仍然需要光源的位置向量和片段的位置向量 光源的位置向量 由于光源的位置是一个静态变量我们可以简单地在片段着色器中把它声明为uniform uniform vec3 lightPos;然后在渲染循环中渲染循环的外面也可以因为它不会改变更新uniform。我们使用在前面声明的lightPos向量作为光源位置 lightingShader.setVec3(lightPos, lightPos);片段的位置向量 我们会在世界空间中进行所有的光照计算因此我们需要一个在世界空间中的顶点位置。 我们可以通过把顶点位置属性乘以模型矩阵不是观察和投影矩阵来把它变换到世界空间坐标。 这个在顶点着色器中很容易完成所以我们声明一个输出变量并计算它的世界空间坐标 顶点着色器 #version 330 corein vec3 Normal; in vec3 FragPos;out vec4 FragColor;uniform vec3 objectColor; uniform vec3 lightColor; uniform vec3 lightPos;void main() {......FragColor vec4(result, 1.0); }最后在片段着色器中添加相应的输入变量 片段着色器 #version 330 corein vec3 FragPos; in vec3 Normal;out vec4 FragColor;uniform vec3 objectColor; uniform vec3 lightColor; uniform vec3 lightPos;void main() {//环境光float ambientStrength 0.1;vec3 ambient ambientStrength * lightColor;//diffusevec3 norm normalize(Normal);vec3 lightDir normalize(lightColor - FragPos);float diff max(dot(norm, lightDir), 0.0);vec3 diffuse diff * lightColor;vec3 result (ambient diffuse) * objectColor;FragColor vec4(result, 1.0); } 现在所有需要的变量都设置好了我们可以在片段着色器中添加光照计算了。 我们需要做的第一件事是计算光源和片段位置之间的方向向量。光的方向向量是光源位置向量与片段位置向量之间的向量差。 我们能够简单地通过让两个向量相减的方式计算向量差。我们同样希望确保所有相关向量最后都转换为单位向量所以我们把法线和最终的方向向量都进行标准化 vec3 norm normalize(Normal); //法线向量 vec3 lightDir normalize(lightColor - FragPos); //光的方向向量 注意当计算光照时我们通常不关心一个向量的模长或它的位置我们只关心它们的方向。所以几乎所有的计算都使用单位向量完成因为这简化了大部分的计算比如点乘。所以当进行光照计算时确保你总是对相关向量进行标准化来保证它们是真正地单位向量。忘记对向量进行标准化是一个十分常见的错误。 下一步我们对norm和lightDir向量进行点乘计算光源对当前片段实际的漫反射影响。结果值再乘以光的颜色得到漫反射分量。两个向量之间的角度越大漫反射分量就会越小 float diff max(dot(norm, lightDir), 0.0); vec3 diffuse diff * lightColor;注意如果两个向量之间的角度大于90度点乘的结果就会变成负数这样会导致漫反射分量变为负数。为此我们使用max函数返回两个参数之间较大的参数从而保证漫反射分量不会变成负数。负数颜色的光照是没有定义的所以最好避免它。 现在我们有了环境光分量和漫反射分量我们把它们相加然后把结果乘以物体的颜色来获得片段最后的输出颜色。 vec3 result (ambient diffuse) * objectColor; FragColor vec4(result, 1.0);3.3、全部代码 1、物体顶点着色器 #version 330 core layout (location 0) in vec3 aPos; layout (location 1) in vec3 aNormal;uniform mat4 model; uniform mat4 view; uniform mat4 projection;out vec3 FragPos; out vec3 Normal;void main() {FragPos vec3(model * vec4(aPos, 1.0));Normal aNormal;gl_Position projection * view * model * vec4(aPos, 1.0f); } 2、物体片段着色器 #version 330 corein vec3 FragPos; in vec3 Normal;out vec4 FragColor;uniform vec3 objectColor; uniform vec3 lightColor; uniform vec3 lightPos;void main() {//环境光float ambientStrength 0.1;vec3 ambient ambientStrength * lightColor;//diffusevec3 norm normalize(Normal);vec3 lightDir normalize(lightColor - FragPos);float diff max(dot(norm, lightDir), 0.0);vec3 diffuse diff * lightColor;vec3 result (ambient diffuse) * objectColor;FragColor vec4(result, 1.0); } 3、main #include mainwindow.h #include QApplication//在包含GLFW的头文件之前包含了GLAD的头文件; //GLAD的头文件包含了正确的OpenGL头文件例如GL/gl.h; //所以需要在其它依赖于OpenGL的头文件之前包含GLAD; #include glad/glad.h #include GLFW/glfw3.h//GLM //#include glm/glm.hpp //#include glm/gtc/matrix_transform.hpp //#include glm/gtc/type_ptr.hpp#include iostream #include QDebug#include shader.h #include stb_image.h #include camera.hvoid framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xposIn, double yposIn); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void processInput(GLFWwindow *window);// settings const unsigned int SCR_WIDTH 1920; const unsigned int SCR_HEIGHT 1080;//Camera Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); float lastX SCR_WIDTH / 2.0; float lastY SCR_HEIGHT / 2.0; bool firstMouse true;float deltaTime 0.0f; // 当前帧与上一帧的时间差 float lastFrame 0.0f; // 上一帧的时间//Light glm::vec3 lightPos(1.2f, 1.0f, 2.0f);using namespace std;int main(int argc, char *argv[]) {QApplication a(argc, argv);//MainWindow w;//w.show();//初始化GLFW//--------------------glfwInit();//配置GLFW//--------------------//告诉GLFW使用的OpenGL本是3.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//告诉GLFW使用的是核心模式Core-profileglfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//创建一个新的OpenGL环境和窗口//-----------------------------------GLFWwindow* window glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, LearnOpenGL, NULL, NULL);if (window NULL){std::cout Failed to create GLFW window std::endl;glfwTerminate(); //glfw销毁窗口和OpenGL环境并释放资源return -1;}//设置参数window中的窗口所关联的OpenGL环境为当前环境//-----------------------------------glfwMakeContextCurrent(window);//设置窗口尺寸改变大小时的回调函数窗口尺寸发送改变时会自动调用//-----------------------------------glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//设置鼠标事件的回调函数鼠标移动时会自动调用//-----------------------------------glfwSetCursorPosCallback(window, mouse_callback);//设置鼠标滚轮事件的回调函数鼠标滚轮移动时会自动调用//-----------------------------------glfwSetScrollCallback(window, scroll_callback);//告诉GLFW捕捉鼠标glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//glad加载系统相关的OpenGL函数指针//---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout Failed to initialize GLAD std::endl;return -1;}//开启深度测试glEnable(GL_DEPTH_TEST);Shader objectShader(C:/Qt_Pro/OpenGL_GLFW/shader/shader.vs,C:/Qt_Pro/OpenGL_GLFW/shader/shader.fs);Shader lightShader(C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.vs,C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.fs);//顶点数据//---------------------------------------------------------------------float vertices[] {//顶点数据 //顶点法向量-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f};//物体//----------------------------------------------------------------unsigned int VBO, objectVAO;glGenVertexArrays(1, objectVAO); //创建顶点数组对象glGenBuffers(1, VBO); //创建顶点缓冲对象glBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //将顶点数据复制到GL_ARRAY_BUFFER缓冲区之后可通过VBO进行操作glBindVertexArray(objectVAO); //绑定VAO//设定顶点属性指针//位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);//法向量属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);//光源(VBO用上面的)//----------------------------------------------------------------unsigned int lightVAO;glGenVertexArrays(1, lightVAO); //创建顶点数组对象glBindVertexArray(lightVAO); //绑定VAOglBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);//渲染循环//我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口;//我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入;//因此我们需要在程序中添加一个while循环它能在我们让GLFW退出前一直保持运行;//------------------------------------------------------------------------------while (!glfwWindowShouldClose(window)) //如果用户准备关闭参数window所指定的窗口那么此接口将会返回GL_TRUE否则将会返回GL_FALSE{//更新时间差float currentFrame static_castfloat(glfwGetTime());deltaTime currentFrame - lastFrame;lastFrame currentFrame;//用户输入//------------------------------------------------------------------------------processInput(window); //检测是否有输入//渲染指令//------------------------------------------------------------------------------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//被投光物体////激活着色器程序对象objectShader.use();objectShader.setVec3(objectColor, 1.0f, 0.5f, 0.31f);objectShader.setVec3(lightColor, 1.0f, 1.0f, 1.0f);objectShader.setVec3(lightPos, lightPos);//创建变换矩阵glm::mat4 model glm::mat4(1.0f);objectShader.setMat4(model, model);glm::mat4 view camera.GetViewMatrix();objectShader.setMat4(view, view);glm::mat4 projection glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);objectShader.setMat4(projection, projection);//绘制三角形glBindVertexArray(objectVAO); //绑定VAOglDrawArrays(GL_TRIANGLES, 0, 36);////光源////激活着色器程序对象lightShader.use();lightShader.setMat4(view, view);lightShader.setMat4(projection, projection);model glm::mat4(1.0f);model glm::translate(model, lightPos);model glm::scale(model, glm::vec3(0.2f));lightShader.setMat4(model, model);//绘制三角形glBindVertexArray(lightVAO); //绑定VAOglDrawArrays(GL_TRIANGLES, 0, 36);////告诉GLFW检查所有等待处理的事件和消息包括操作系统和窗口系统中应当处理的消息。如果有消息正在等待它会先处理这些消息再返回否则该函数会立即返回//---------------------------------------------------------------------------------------------------------------------------------glfwPollEvents();//请求窗口系统将参数window关联的后缓存画面呈现给用户(双缓冲绘图)//------------------------------------------------------------------------------glfwSwapBuffers(window);}//释放资源glDeleteVertexArrays(1, objectVAO);glDeleteVertexArrays(1, lightVAO);glDeleteBuffers(1, VBO);//glDeleteProgram(objectShader);//glDeleteProgram(lightShader);//glfw销毁窗口和OpenGL环境并释放资源之后必须再次调用glfwInit()才能使用大多数GLFW函数//------------------------------------------------------------------glfwTerminate();return a.exec(); }//检测是否有输入 //--------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) {if(glfwGetKey(window, GLFW_KEY_ESCAPE) GLFW_PRESS) //ESC键退出glfwSetWindowShouldClose(window, true);float cameraSpeed static_castfloat(2.5 * deltaTime);if (glfwGetKey(window, GLFW_KEY_W) GLFW_PRESS)camera.ProcessKeyboard(FORWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_S) GLFW_PRESS)camera.ProcessKeyboard(BACKWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_A) GLFW_PRESS)camera.ProcessKeyboard(LEFT, deltaTime);if (glfwGetKey(window, GLFW_KEY_D) GLFW_PRESS)camera.ProcessKeyboard(RIGHT, deltaTime); }//给glfw窗口注册的尺寸改变回调函数 //--------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) {//确保视口匹配新的窗口尺寸请注意宽度和高度将比视网膜显示器上指定的大得多glViewport(0, 0, width, height); } // 鼠标移动时的回调函数 // ------------------------------------------------------- void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {float xpos static_castfloat(xposIn);float ypos static_castfloat(yposIn);if (firstMouse){lastX xpos;lastY ypos;firstMouse false;}float xoffset lastX - xpos;float yoffset ypos - lastY; //翻转因为Y轴是从下到上越来越大lastX xpos;lastY ypos;camera.ProcessMouseMovement(xoffset, yoffset); }//鼠标滚轮的回调函数 // ---------------------------------------------------------------------- void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {camera.ProcessMouseScroll(static_castfloat(yoffset)); } 3.4、法线矩阵 现在我们已经把法向量从顶点着色器传到了片段着色器。可是目前片段着色器里的计算都是在世界空间坐标中进行的。 所以我们是不是应该把法向量也转换为世界空间坐标基本正确但是这不是简单地把它乘以一个模型矩阵就能搞定的。 首先法向量只是一个方向向量不能表达空间中的特定位置。同时法向量没有齐次坐标顶点位置中的w分量。 这意味着位移不应该影响到法向量。因此如果我们打算把法向量乘以一个模型矩阵我们就要从矩阵中移除位移部分只选用模型矩阵左上角3×3的矩阵注意我们也可以把法向量的w分量设置为0再乘以4×4矩阵这同样可以移除位移。对于法向量我们只希望对它实施缩放和旋转变换。 其次如果模型矩阵执行了不等比缩放顶点的改变会导致法向量不再垂直于表面了。因此我们不能用这样的模型矩阵来变换法向量。下面的图展示了应用了不等比缩放的模型矩阵对法向量的影响 每当我们应用一个不等比缩放时注意等比缩放不会破坏法线因为法线的方向没被改变仅仅改变了法线的长度而这很容易通过标准化来修复法向量就不会再垂直于对应的表面了这样光照就会被破坏。 修复这个行为的诀窍是使用一个为法向量专门定制的模型矩阵。这个矩阵称之为法线矩阵(Normal Matrix)它使用了一些线性代数的操作来移除对法向量错误缩放的影响。如果你想知道这个矩阵是如何计算出来的建议去阅读这个文章。 法线矩阵被定义为「模型矩阵左上角3x3部分的逆矩阵的转置矩阵」。真是拗口如果你不明白这是什么意思别担心我们还没有讨论逆矩阵(Inverse Matrix)和转置矩阵(Transpose Matrix)。注意大部分的资源都会将法线矩阵定义为应用到模型-观察矩阵(Model-view Matrix)上的操作但是由于我们只在世界空间中进行操作不是在观察空间我们只使用模型矩阵。 在顶点着色器中我们可以使用inverse和transpose函数自己生成这个法线矩阵这两个函数对所有类型矩阵都有效。注意我们还要把被处理过的矩阵强制转换为3×3矩阵来保证它失去了位移属性以及能够乘以vec3的法向量。 Normal mat3(transpose(inverse(model))) * aNormal;矩阵求逆是一项对于着色器开销很大的运算因为它必须在场景中的每一个顶点上进行所以应该尽可能地避免在着色器中进行求逆运算。以学习为目的的话这样做还好但是对于一个高效的应用来说你最好先在CPU上计算出法线矩阵再通过uniform把它传递给着色器就像模型矩阵一样。 在漫反射光照部分光照表现并没有问题这是因为我们没有对物体进行任何缩放操作所以我们并不真的需要使用一个法线矩阵而是仅以模型矩阵乘以法线就可以。但是如果你会进行不等比缩放使用法线矩阵去乘以法向量就是必须的了。 四、镜面光照 和漫反射光照一样镜面光照也决定于光的方向向量和物体的法向量但是它也决定于观察方向例如玩家是从什么方向看向这个片段的。镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子那么镜面光照最强的地方就是我们看到表面上反射光的地方。你可以在下图中看到效果 我们通过根据法向量翻折入射光的方向来计算反射向量。然后我们计算反射向量与观察方向的角度差它们之间夹角越小镜面光的作用就越大。由此产生的效果就是我们看向在入射光在表面的反射方向时会看到一点高光。 观察向量是我们计算镜面光照时需要的一个额外变量我们可以使用观察者的世界空间位置和片段的位置来计算它。之后我们计算出镜面光照强度用它乘以光源的颜色并将它与环境光照和漫反射光照部分加和。 我们选择在世界空间进行光照计算但是大多数人趋向于更偏向在观察空间进行光照计算。在观察空间计算的优势是观察者的位置总是在(0, 0, 0)所以你已经零成本地拿到了观察者的位置。然而若以学习为目的我认为在世界空间中计算光照更符合直觉。如果你仍然希望在观察空间计算光照的话你需要将所有相关的向量也用观察矩阵进行变换不要忘记也修改法线矩阵。 要得到观察者的世界空间坐标我们直接使用摄像机的位置向量即可它当然就是那个观察者。那么让我们把另一个uniform添加到片段着色器中并把摄像机的位置向量传给着色器 片段着色器 #version 330 corein vec3 FragPos; in vec3 Normal;out vec4 FragColor;uniform vec3 objectColor; uniform vec3 lightColor; uniform vec3 lightPos; uniform vec3 viewPos; //观察者坐标void main() {...... }main.cpp //被投光物体 // //激活着色器程序对象 objectShader.use(); objectShader.setVec3(objectColor, 1.0f, 0.5f, 0.31f); objectShader.setVec3(lightColor, 1.0f, 1.0f, 1.0f); objectShader.setVec3(lightPos, lightPos); objectShader.setVec3(viewPos, camera.Position); //设置观察者坐标//创建变换矩阵 ...... //绘制三角形 ......//现在我们已经获得所有需要的变量可以计算高光强度了。 首先我们定义一个镜面强度(Specular Intensity)变量给镜面高光一个中等亮度颜色让它不要产生过度的影响。 float specularStrength 0.5;如果我们把它设置为1.0f我们会得到一个非常亮的镜面光分量这对于一个珊瑚色的立方体来说有点太多了。下一节教程中我们会讨论如何合理设置这些光照强度以及它们是如何影响物体的。 下一步我们计算视线方向向量和对应的沿着法线轴的反射向量 vec3 viewDir normalize(viewPos - FragPos); vec3 reflectDir reflect(-lightDir, norm);需要注意的是我们对lightDir向量进行了取反。reflect函数要求第一个向量是从光源指向片段位置的向量但是lightDir当前正好相反是从片段指向光源由先前我们计算lightDir向量时减法的顺序决定。为了保证我们得到正确的reflect向量我们通过对lightDir向量取反来获得相反的方向。第二个参数要求是一个法向量所以我们提供的是已标准化的norm向量。 剩下要做的是计算镜面分量。下面的代码完成了这件事 float spec pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specular specularStrength * spec * lightColor;我们先计算视线方向与反射方向的点乘并确保它不是负值然后取它的32次幂。这个32是高光的反光度(Shininess)。 一个物体的反光度越高反射光的能力越强散射得越少高光点就会越小。在下面的图片里你会看到不同反光度的视觉效果影响 我们不希望镜面成分过于显眼所以我们把指数保持为32。 剩下的最后一件事情是把它加到环境光分量和漫反射分量里再用结果乘以物体的颜色 vec3 result (ambient diffuse specular) * objectColor; FragColor vec4(result, 1.0);我们现在为冯氏光照计算了全部的光照分量。根据你的视角你可以看到类似下面的画面 4.1、全部代码 被投光物体片段着色器 #version 330 corein vec3 FragPos; in vec3 Normal;out vec4 FragColor;uniform vec3 objectColor; uniform vec3 lightColor; uniform vec3 lightPos; uniform vec3 viewPos; //观察者坐标void main() {//环境光照float ambientStrength 0.1;vec3 ambient ambientStrength * lightColor;//diffusevec3 norm normalize(Normal);vec3 lightDir normalize(lightColor - FragPos);float diff max(dot(norm, lightDir), 0.0);vec3 diffuse diff * lightColor;float specularStrength 0.5; //镜面强度vec3 viewDir normalize(viewPos - FragPos);vec3 reflectDir reflect(-lightDir, norm);float spec pow(max(dot(viewDir, reflectDir), 0.0), 32);vec3 specular specularStrength * spec * lightColor;vec3 result (ambient diffuse specular) * objectColor;FragColor vec4(result, 1.0); } main #include mainwindow.h #include QApplication//在包含GLFW的头文件之前包含了GLAD的头文件; //GLAD的头文件包含了正确的OpenGL头文件例如GL/gl.h; //所以需要在其它依赖于OpenGL的头文件之前包含GLAD; #include glad/glad.h #include GLFW/glfw3.h//GLM //#include glm/glm.hpp //#include glm/gtc/matrix_transform.hpp //#include glm/gtc/type_ptr.hpp#include iostream #include QDebug#include shader.h #include stb_image.h #include camera.hvoid framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xposIn, double yposIn); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void processInput(GLFWwindow *window);// settings const unsigned int SCR_WIDTH 1920; const unsigned int SCR_HEIGHT 1080;//Camera Camera camera(glm::vec3(0.0f, 1.0f, 3.0f)); float lastX SCR_WIDTH / 2.0; float lastY SCR_HEIGHT / 2.0; bool firstMouse true;float deltaTime 0.0f; // 当前帧与上一帧的时间差 float lastFrame 0.0f; // 上一帧的时间//Light glm::vec3 lightPos(1.2f, 1.5f, 2.0f);using namespace std;int main(int argc, char *argv[]) {QApplication a(argc, argv);//MainWindow w;//w.show();//初始化GLFW//--------------------glfwInit();//配置GLFW//--------------------//告诉GLFW使用的OpenGL本是3.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//告诉GLFW使用的是核心模式Core-profileglfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//创建一个新的OpenGL环境和窗口//-----------------------------------GLFWwindow* window glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, LearnOpenGL, NULL, NULL);if (window NULL){std::cout Failed to create GLFW window std::endl;glfwTerminate(); //glfw销毁窗口和OpenGL环境并释放资源return -1;}//设置参数window中的窗口所关联的OpenGL环境为当前环境//-----------------------------------glfwMakeContextCurrent(window);//设置窗口尺寸改变大小时的回调函数窗口尺寸发送改变时会自动调用//-----------------------------------glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//设置鼠标事件的回调函数鼠标移动时会自动调用//-----------------------------------glfwSetCursorPosCallback(window, mouse_callback);//设置鼠标滚轮事件的回调函数鼠标滚轮移动时会自动调用//-----------------------------------glfwSetScrollCallback(window, scroll_callback);//告诉GLFW捕捉鼠标glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//glad加载系统相关的OpenGL函数指针//---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout Failed to initialize GLAD std::endl;return -1;}//开启深度测试glEnable(GL_DEPTH_TEST);Shader objectShader(C:/Qt_Pro/OpenGL_GLFW/shader/shader.vs,C:/Qt_Pro/OpenGL_GLFW/shader/shader.fs);Shader lightShader(C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.vs,C:/Qt_Pro/OpenGL_GLFW/shader/light_cube.fs);//顶点数据//---------------------------------------------------------------------float vertices[] {//顶点数据 //顶点法向量-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f};//物体//----------------------------------------------------------------unsigned int VBO, objectVAO;glGenVertexArrays(1, objectVAO); //创建顶点数组对象glGenBuffers(1, VBO); //创建顶点缓冲对象glBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //将顶点数据复制到GL_ARRAY_BUFFER缓冲区之后可通过VBO进行操作glBindVertexArray(objectVAO); //绑定VAO//设定顶点属性指针//位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);//法向量属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);//光源(VBO用上面的)//----------------------------------------------------------------unsigned int lightVAO;glGenVertexArrays(1, lightVAO); //创建顶点数组对象glBindVertexArray(lightVAO); //绑定VAOglBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO与GL_ARRAY_BUFFER缓冲区绑定glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);//渲染循环//我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口;//我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入;//因此我们需要在程序中添加一个while循环它能在我们让GLFW退出前一直保持运行;//------------------------------------------------------------------------------while (!glfwWindowShouldClose(window)) //如果用户准备关闭参数window所指定的窗口那么此接口将会返回GL_TRUE否则将会返回GL_FALSE{//更新时间差float currentFrame static_castfloat(glfwGetTime());deltaTime currentFrame - lastFrame;lastFrame currentFrame;//用户输入//------------------------------------------------------------------------------processInput(window); //检测是否有输入//渲染指令//------------------------------------------------------------------------------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//被投光物体////激活着色器程序对象objectShader.use();objectShader.setVec3(objectColor, 1.0f, 0.5f, 0.31f);objectShader.setVec3(lightColor, 1.0f, 1.0f, 1.0f);objectShader.setVec3(lightPos, lightPos);objectShader.setVec3(viewPos, camera.Position);//创建变换矩阵glm::mat4 model glm::mat4(1.0f);objectShader.setMat4(model, model);glm::mat4 view camera.GetViewMatrix();objectShader.setMat4(view, view);glm::mat4 projection glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);objectShader.setMat4(projection, projection);//绘制三角形glBindVertexArray(objectVAO); //绑定VAOglDrawArrays(GL_TRIANGLES, 0, 36);////光源////激活着色器程序对象lightShader.use();lightShader.setMat4(view, view);lightShader.setMat4(projection, projection);model glm::mat4(1.0f);model glm::translate(model, lightPos);model glm::scale(model, glm::vec3(0.2f));lightShader.setMat4(model, model);//绘制三角形glBindVertexArray(lightVAO); //绑定VAOglDrawArrays(GL_TRIANGLES, 0, 36);////告诉GLFW检查所有等待处理的事件和消息包括操作系统和窗口系统中应当处理的消息。如果有消息正在等待它会先处理这些消息再返回否则该函数会立即返回//---------------------------------------------------------------------------------------------------------------------------------glfwPollEvents();//请求窗口系统将参数window关联的后缓存画面呈现给用户(双缓冲绘图)//------------------------------------------------------------------------------glfwSwapBuffers(window);}//释放资源glDeleteVertexArrays(1, objectVAO);glDeleteVertexArrays(1, lightVAO);glDeleteBuffers(1, VBO);//glDeleteProgram(objectShader);//glDeleteProgram(lightShader);//glfw销毁窗口和OpenGL环境并释放资源之后必须再次调用glfwInit()才能使用大多数GLFW函数//------------------------------------------------------------------glfwTerminate();return a.exec(); }//检测是否有输入 //--------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) {if(glfwGetKey(window, GLFW_KEY_ESCAPE) GLFW_PRESS) //ESC键退出glfwSetWindowShouldClose(window, true);float cameraSpeed static_castfloat(2.5 * deltaTime);if (glfwGetKey(window, GLFW_KEY_W) GLFW_PRESS)camera.ProcessKeyboard(FORWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_S) GLFW_PRESS)camera.ProcessKeyboard(BACKWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_A) GLFW_PRESS)camera.ProcessKeyboard(LEFT, deltaTime);if (glfwGetKey(window, GLFW_KEY_D) GLFW_PRESS)camera.ProcessKeyboard(RIGHT, deltaTime); }//给glfw窗口注册的尺寸改变回调函数 //--------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) {//确保视口匹配新的窗口尺寸请注意宽度和高度将比视网膜显示器上指定的大得多glViewport(0, 0, width, height); } // 鼠标移动时的回调函数 // ------------------------------------------------------- void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {float xpos static_castfloat(xposIn);float ypos static_castfloat(yposIn);if (firstMouse){lastX xpos;lastY ypos;firstMouse false;}float xoffset lastX - xpos;float yoffset ypos - lastY; //翻转因为Y轴是从下到上越来越大lastX xpos;lastY ypos;camera.ProcessMouseMovement(xoffset, yoffset); }//鼠标滚轮的回调函数 // ---------------------------------------------------------------------- void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {camera.ProcessMouseScroll(static_castfloat(yoffset)); }
http://www.pierceye.com/news/336649/

相关文章:

  • 网站建设全域云网站建设流程详解
  • 梅州市五华县建设银行网站写作网站招聘
  • 博物馆网站建设情况工业互联网龙头公司排名
  • 做网站用什么系统做网站开发电脑配置
  • 企业网站推广的主要方法上海中汇建设发展有限公司网站
  • 郑州做网站公司电话网站是否有管理员权限
  • 开发建设信息的网站广东省建设厅的注册中心网站首页
  • 用cms做的网站 的步骤有域名如何做网站
  • h5个人网站源码江苏启安建设集团有限公司网站
  • 网站开发net教程网站后台登陆路径
  • 织梦网站模板安装教程国外设计有名网站
  • 最专业企业营销型网站建设南充 网站开发
  • 国外有哪些网站做推广的比较好北京展览馆网站建设
  • 国外英语写作网站网站后台 刷新
  • 如何制作自己的网站详情页设计
  • 南京免费自助建站模板wordpress 增加侧边栏
  • 做信息分类网站难吗广告设计公司有哪些
  • 做seo网站优化多少钱网站开发客户哪里找
  • 做网站一定要云解析吗海南公司注册网站
  • 建站之家官网办公装修设计
  • 永康网站建设的公司wordpress 图片分类
  • 网站商务通弹出窗口图片更换设置wordpress4.9 多站点
  • 如何仿制一个网站注册商标设计
  • 网站建设属于什么岗位旅游网站设计模板
  • 自己做的网站怎么链接火车头采集软件开发模型是什么
  • 新网站怎么做才会被收录正品海外购网站有哪些
  • 广东手机网站建设品牌js制作网页计算器
  • 化隆网站建设公司学做网站多久
  • 网站域名如何查询上海室内设计公司哪家好
  • 电子书推送网站怎么做新做的网站如何