OpenGL学习笔记(立方体贴图、高级数据、高级GLSL)
目录
- 立方体贴图
- 天空盒
- 环境映射
- 斯涅尔定律(Snell's Law)
- 菲涅尔效应(Fresnel Effect)
- 动态环境贴图
- 高级数据
- 分批顶点属性
- 复制缓冲
- 高级GLSL
- 顶点着色器变量
- 片段着色器变量
- 接口块
- Uniform缓冲对象
- Uniform块布局
- 使用Uniform缓冲
- 测试
GitHub主页:https://github.com/sdpyy1
OpenGL学习仓库:https://github.com/sdpyy1/CppLearn/tree/main/OpenGLtree/main/OpenGL):https://github.com/sdpyy1/CppLearn/tree/main/OpenGL
立方体贴图
相当于6个2D纹理,通过一个方向向量来进行采样
创建立方体贴图
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
因为立方体贴图包含有6个纹理,每个面一个,我们需要调用glTexImage2D函数6次
天空盒
用提供好的纹理制作3D纹理
float skyboxVertices[] = {// positions-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, 1.0f,-1.0f, -1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, 1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, -1.0f,1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,-1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, -1.0f, 1.0f,-1.0f, -1.0f, 1.0f,-1.0f, 1.0f, -1.0f,1.0f, 1.0f, -1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, 1.0f};// sky的VAOGLuint skyVAO, skyVBO;glGenVertexArrays(1, &skyVAO);glBindVertexArray(skyVAO);glGenBuffers(1, &skyVBO);glBindBuffer(GL_ARRAY_BUFFER, skyVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);glBindVertexArray(0);
#version 330 core
layout (location = 0) in vec3 aPos;out vec3 TexCoords;uniform mat4 projection;
uniform mat4 view;void main()
{TexCoords = aPos;gl_Position = projection * view * vec4(aPos, 1.0);
}
#version 330 core
out vec4 FragColor;in vec3 TexCoords;uniform samplerCube skybox;void main()
{FragColor = texture(skybox, TexCoords);
}
渲染时应该先渲染天空盒,这里要注意一点,对View矩阵的设置(全局坐标转视图坐标)的平移操作,是通过平移场景中所有的物体实现的,如果我们希望我们摄像机始终在天空盒中,就要取消View矩阵的平移部分。
glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
在渲染时,要先渲染天空盒,渲染前要关闭深度缓存的写入,这是因为天空盒z最远就是1,
glDepthMask(GL_FALSE);skyShader.use();glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));skyShader.setMat4("projection", projection);skyShader.setMat4("view", view);glBindVertexArray(skyVAO);glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);glDrawArrays(GL_TRIANGLES, 0, 36);glDepthMask(GL_TRUE);
这样就是先了摄像机移动时,只是相当于内部的物体移动了,而天空盒不会移动,所以摄像机逃不出天空盒
另外在渲染天空盒之前要关闭深度缓冲的写入,因为盒子最远z值只有-1,当一个物体Model变换放在一个靠后的位置,就会因深度缓冲中写入了-1,而不会渲染,如下图的情况
当渲染天空盒时关闭深度缓存,那别的物体的渲染不会受到天空盒距离而被遮挡,但是仍会因为超出裁剪空间F面而消失
但这种做法并不是最优,下面进行优化,如果先渲染天空盒,其实没必要的,因为天空盒是放在最后的,所以肯定是有一部分被模型挡住的。所以改为最后渲染天空盒。
在坐标系统小节中我们说过,透视除法是在顶点着色器运行之后执行的,将gl_Position的xyz坐标除以w分量。我们又从深度测试小节中知道,相除结果的z分量等于顶点的深度值。使用这些信息,我们可以将输出位置的z分量等于它的w分量,让z分量永远等于1.0,这样子的话,当透视除法执行之后,z分量会变为w / w = 1.0。
这样做的目的是让天空盒的深度值永远等于1
void main()
{TexCoords = aPos;vec4 pos = projection * view * vec4(aPos, 1.0);gl_Position = pos.xyww;
}
通过把天空盒的z值设置为最大,也就是只要有其他物体渲染,那他的z值一定小于等于1,那么它就会被渲染,从而让天空盒不会挡住比较远的物体
现在渲染天空就放在了最后一步,因为他的z是最大,只要有物体已经渲染了,那对应的像素就不会再渲染天空盒(如果先渲染天空盒,那每个像素都需要渲染一遍天空盒才继续下一步)
// 渲染天空glDepthFunc(GL_LEQUAL);skyShader.use();glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));skyShader.setMat4("projection", projection);skyShader.setMat4("view", view);glBindVertexArray(skyVAO);glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);glDrawArrays(GL_TRIANGLES, 0, 36);glDepthFunc(GL_LESS);
glDepthFunc(GL_LEQUAL);是为了让小于等于深度缓冲就通过测试,因为深度缓冲默认就是1,天空盒也是1,如果是GL_LESS,那永远都不会通过,场景全黑了。
环境映射
进一步还可以把3D纹理利用在模型的纹理上,一张图就解释了
通过摄像机与物体表面反射的方向向量,去3D贴图找颜色,就得到一种反光的效果
#version 330 core
out vec4 FragColor;in vec3 Normal;
in vec3 Position;uniform vec3 cameraPos;
uniform samplerCube skybox;void main()
{vec3 I = normalize(Position - cameraPos);vec3 R = reflect(I, normalize(Normal));FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
来点小创新,外部用反射效果,而内部用其他的采样器
void main()
{vec3 I = normalize(Position - cameraPos);vec3 R = reflect(I, normalize(Normal));if(dot(-I, Normal)<0){FragColor = vec4(mix(texture(texture1,texCoords), texture(texture2,texCoords),0.5));}else{FragColor = vec4(texture(skybox, R).rgb, 1.0);}
}
用方块演示
斯涅尔定律(Snell’s Law)
当光波从一种介质传播到另一种具有不同折射率的介质时,会发生折射现象,其入射角与折射角之间的关系,可以用斯涅尔定律(Snell’s Law)来描述。
折射定律表明,当光波从介质1传播到介质2时,假若两种介质的折射率不同,则会发生折射现像,其入射光和折射光都处于同一平面,称为“入射平面”,并且与界面法线的夹角满足如下关系:
该公式指出如果进入一个高折射率的介质中,与法线的夹角就会变小
菲涅尔效应(Fresnel Effect)
描述光线在介质表面反射与透射比例随入射角变化的物理现象:
反射率(R):入射光被反射的比例。
透射率(T):入射光进入介质的比例
这张图就能看出,近处可以看到水下的东西,但是远处就只能看到倒影了。其原理就在于,当光线入射角大的时候,反射效果强;光线入射角小的时候,反射效果弱。
动态环境贴图
通过使用帧缓冲,我们能够为物体的6个不同角度创建出场景的纹理,并在每个渲染迭代中将它们储存到一个立方体贴图中。之后我们就可以使用这个(动态生成的)立方体贴图来创建出更真实的,包含其它物体的,反射和折射表面了。这就叫做动态环境映射(Dynamic Environment Mapping),因为我们动态创建了物体周围的立方体贴图,并将其用作环境贴图。
这句话就是说在一个物体6个角度上安排摄像机进行渲染,渲染完成放在6张纹理上,贴到物体上,就能完美模拟镜面效果,但一听就计算量巨大~
高级数据
OpenGL中的缓冲只是一个管理特定内存块的对象,没有其它更多的功能了。在我们将它绑定到一个缓冲目标(Buffer Target)时,我们才赋予了其意义。当我们绑定一个缓冲到GL_ARRAY_BUFFER时,它就是一个顶点数组缓冲,但我们也可以很容易地将其绑定到GL_ELEMENT_ARRAY_BUFFER。OpenGL内部会为每个目标储存一个缓冲,并且会根据目标的不同,以不同的方式处理缓冲。
到目前为止,我们一直是调用glBufferData函数来填充缓冲对象所管理的内存,这个函数会分配一块GPU内存,并将数据添加到这块内存中。如果我们将它的data参数设置为NULL,那么这个函数将只会分配内存,但不进行填充。这在我们需要预留(Reserve)特定大小的内存,之后回到这个缓冲一点一点填充的时候会很有用。
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVs), &cubeVs, GL_STATIC_DRAW);
还有更进阶的填充方法,就是glBufferSubData
,它用来填充缓冲的特定区域
glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // 范围: [24, 24 + sizeof(data)]
在调用glBufferSubData
之前必须先调用glBufferData
分配足够的空间。
将数据导入缓冲的另外一种方法是,请求缓冲内存的指针,直接将数据复制到缓冲当中。通过调用glMapBuffer函数,OpenGL会返回当前绑定缓冲的内存指针,供我们操作:
float data[] = {0.5f, 1.0f, -0.35f...
};
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// 获取指针
void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// 复制数据到内存
memcpy(ptr, data, sizeof(data));
// 记得告诉OpenGL我们不再需要这个指针了
glUnmapBuffer(GL_ARRAY_BUFFER);
如果要直接映射数据到缓冲,而不事先将其存储到临时内存中,glMapBuffer这个函数会很有用。比如说,你可以从文件中读取数据,并直接将它们复制到缓冲内存中。
分批顶点属性
在之前我们都是把一个点的不同属性精密排列并在VAO中进行定义
float cubeVs[] = {// positions // normals // texture coords-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
这里可以尝试不同的方式。我们将采用分批(Batched)的方式111122223333。很好理解,就是顶点数据放一起,全部存完之后才开始法线。
用刚才的指定位置存放来批量存放
float positions[] = { ... };
float normals[] = { ... };
float tex[] = { ... };
// 填充缓冲
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);
之后在设置VAO时,主要就是要调整步长
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));
复制缓冲
当你的缓冲已经填充好数据之后,你可能会想与其它的缓冲共享其中的数据,或者想要将缓冲的内容复制到另一个缓冲当中。glCopyBufferSubData能够让我们相对容易地从一个缓冲中复制数据到另一个缓冲中。这个函数的原型如下:
void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset,GLintptr writeoffset, GLsizeiptr size);
readtarget和writetarget参数需要填入复制源和复制目标的缓冲目标。比如说,我们可以将VERTEX_ARRAY_BUFFER缓冲复制到VERTEX_ELEMENT_ARRAY_BUFFER缓冲,分别将这些缓冲目标设置为读和写的目标。当前绑定到这些缓冲目标的缓冲将会被影响到。
但是如果从一个VBO复制到另外一个VBO呢,OpenGL每次只能绑定一个VBO呀。OpenGL提供给我们另外两个缓冲目标,叫做GL_COPY_READ_BUFFER
和GL_COPY_WRITE_BUFFER
。
等于是把两个VBO绑到了其他地方
glBindBuffer(GL_COPY_READ_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
高级GLSL
我们将会讨论一些有趣的内建变量(Built-in Variable),管理着色器输入和输出的新方式以及一个叫做Uniform缓冲对象(Uniform Buffer Object)的有用工具。
顶点着色器变量
GLSL还定义了另外几个以gl_为前缀的变量,它们能提供给我们更多的方式来读取/写入数据。我们已经在前面教程中接触过其中的两个了:顶点着色器的输出向量gl_Position,和片段着色器的gl_FragCoord。
gl_Position就是接收MVP变换后的裁剪空间坐标位置(后续进行透视除法后才是NDC空间)
gl_PointSize
我们能够选用的其中一个图元是GL_POINTS,如果使用它的话,每一个顶点都是一个图元,都会被渲染为一个点。我们可以通过OpenGL的glPointSize函数来设置渲染出来的点的大小,但我们也可以在顶点着色器中修改这个值。
GLSL定义了一个叫做gl_PointSize输出变量,它是一个float变量,你可以使用它来设置点的宽高(像素)。在顶点着色器中修改点的大小的话,你就能对每个顶点设置不同的值了
它需要开启
glEnable(GL_PROGRAM_POINT_SIZE);
设置点的大小
gl_VertexID
他是顶点着色器的输入。整型变量gl_VertexID储存了正在绘制顶点的当前ID。当(使用glDrawElements)进行索引渲染的时候,这个变量会存储正在绘制顶点的当前索引。当(使用glDrawArrays)不使用索引进行绘制的时候,这个变量会储存从渲染调用开始的已处理顶点数量。
片段着色器变量
gl_FragCoord 存储着当前像素在窗口下的坐标和深度
利用这个变量可以做一个有趣的效果,窗口左右两边用不同的着色方式
void main()
{vec3 I = normalize(Position - cameraPos);if(gl_FragCoord.x < 400){FragColor = vec4(mix(texture(texture1,texCoords), texture(texture2,texCoords),0.5));}else{vec3 R = reflect(I, normalize(Normal));FragColor = texture(skybox, R);}
}
gl_FrontFacing
gl_FrontFacing将会告诉我们当前片段是属于正向面的一部分还是背向面的一部分
也可以将正面和背面用不同的渲染
gl_FragDepth
gl_FragDepth的输出变量,我们可以使用它来在着色器内设置片段的深度值。然而,由我们自己设置深度值有一个很大的缺点,只要我们在片段着色器中对
gl_FragDepth进行写入,OpenGL就会(像深度测试小节中讨论的那样)禁用所有的提前深度测试(Early Depth Testing)。它被禁用的原因是,OpenGL无法在片段着色器运行之前得知片段将拥有的深度值,因为片段着色器可能会完全修改这个深度值。
通过在片段着色器顶部声明,来让OpenGL更好的提高性能(注意这个特性只在OpenGL 4.2版本或以上才提供)
layout (depth_<condition>) out float gl_FragDepth;
接口块
到目前为止,每当我们希望从顶点着色器向片段着色器发送数据时,我们都声明了几个对应的输入/输出变量。将它们一个一个声明是着色器间发送数据最简单的方式了,但当程序变得更大时,你希望发送的可能就不只是几个变量了,它还可能包括数组和结构体。
为了帮助我们管理这些变量,GLSL为我们提供了一个叫做接口块(Interface Block)的东西,来方便我们组合这些变量。接口块的声明和struct的声明有点相像,不同的是,现在根据它是一个输入还是输出块(Block),使用in或out关键字来定义的。
out VS_OUT
{vec2 TexCoords;
} vs_out;
在使用时
vs_out.TexCoords = aTexCoords;
同样,片段着色器需要定义一样名字的接口块来接收
in VS_OUT
{vec2 TexCoords;
} fs_in;
Uniform缓冲对象
当多个shader程序需要的uniform是一致的情况,那理论上只需要存储一次即可
OpenGL为我们提供了一个叫做Uniform缓冲对象(Uniform Buffer Object)的工具,它允许我们定义一系列在多个着色器程序中相同的全局Uniform变量。当使用Uniform缓冲对象的时候,我们只需要设置相关的uniform一次。当然,我们仍需要手动设置每个着色器中不同的uniform。并且创建和配置Uniform缓冲对象会有一点繁琐。
uniform快定义如下,对于同一个场景,视图矩阵和投影矩阵应该是相同的,这时候就可以使用缓冲,使用时直接使用,不需要xx.xx
layout (std140) uniform Matrices
{mat4 projection;mat4 view;
};
void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);
}
其中layout (std140)
是说当前定义的Uniform块对它的内容使用一个特定的内存布局。这个语句设置了Uniform块布局(Uniform Block Layout)。
Uniform块布局
块只是一块预留内存,我们还需要告诉OpenGL内存的哪一部分对应着着色器中的哪一个uniform变量。
假设着色器中有以下的这个Uniform块:
layout (std140) uniform ExampleBlock
{float value;vec3 vector;mat4 matrix;float values[3];bool boolean;int integer;
};
GLSL默认的Uniform内存布局是Shared(共享),当使用 layout(shared)(或省略布局声明,因为这是默认行为)时,OpenGL 允许硬件厂商 根据自身架构特点决定变量在内存中的排列方式(如对齐、填充规则)。但是一旦换了硬件他的优化方式不一样。就会出问题。这种内存布局主要是针对该shader只会运行在同一个平台的情况(比如任天堂独占游戏)。
使用共享布局时,GLSL是可以为了优化而对uniform变量的位置进行变动的,只要变量的顺序保持不变。因为我们无法知道每个uniform变量的偏移量,我们也就不知道如何准确地填充我们的Uniform缓冲了。我们能够使用像是glGetUniformIndices这样的函数来查询这个信息,但这超出本节的范围了。虽然共享布局给了我们很多节省空间的优化,但是我们需要查询每个uniform变量的偏移量,这会产生非常多的工作量。
解决跨平台的方法就是使用std140布局。std140布局声明了每个变量的偏移量都是由一系列规则所决定的,这显式地声明了每个变量类型的内存布局。由于这是显式提及的,我们可以手动计算出每个变量的偏移量。
也就是说我们规定了每种类型占据的空间,并且都可以通过整数倍N(每4个字节将会用一个N来表示)来计算偏移量
在这种布局下,我们就可以自己计算偏移量来将数据存入uniform缓冲中
使用Uniform缓冲
经典缓冲对象的创建与内存分配
unsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // 分配152字节的内存
glBindBuffer(GL_UNIFORM_BUFFER, 0);
只会就可以通过glBufferSubData
来更新每个偏移量下的变量了。同时我们还需要将该缓冲连接到具体的uniform块上
做法就是shader和缓冲连接到同一个绑定点上(类似于纹理的绑定)
glUniformBlockBinding
用于shader,第一个参数是一个程序对象,之后是一个Uniform块索引和链接到的绑定点。Uniform块索引(Uniform Block Index)是着色器中已定义Uniform块的位置值索引。这可以通过调用glGetUniformBlockIndex
来获取(就是定义的uniform块在shader的位置)
unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");
glUniformBlockBinding(shaderA.ID, lights_index, 2);
注意我们需要对每个着色器重复这一步骤。
在新版本中有简化版
接下来还需要绑定缓冲到相同的绑定点
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock);
// 这种方式可以选取缓冲的一部分进行绑定,更加灵活
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);
测试
天空盒和物体使用的同一套投影变换矩阵,所以可以进行绑定
首先来创建一个缓冲区
// 创建uboGLuint ubo;glGenBuffers(1, &ubo);glBindBuffer(GL_UNIFORM_BUFFER, ubo);glBufferData(GL_UNIFORM_BUFFER, sizeof(glm::mat4) * 2, NULL, GL_DYNAMIC_DRAW);
绑定UBO到一个绑定点上
// 这个绑定点包含ubo所有内容(2个mat4)glBindBufferBase(GL_UNIFORM_BUFFER, 1, ubo);// 这个绑定点只有1个mat,他就是用来表示天空盒的透视矩阵,因为view矩阵,天空盒和别人不一样glBindBufferRange(GL_UNIFORM_BUFFER, 2, ubo, 0, sizeof(glm::mat4));
加载完shader,并将shader绑定到绑定点上
// 着色器设置Shader cubeShader("./shader/bag.vert", "./shader/bag.frag");Shader skyShader("./shader/sky.vert", "./shader/sky.frag");// shader的uniform块绑定到绑定点unsigned int projectionMatIndexInCubeShader = glGetUniformBlockIndex(cubeShader.ID, "projectionMat");unsigned int projectionMatIndexInSkyShader = glGetUniformBlockIndex(skyShader.ID, "projectionMat");glUniformBlockBinding(cubeShader.ID, projectionMatIndexInCubeShader, 1);glUniformBlockBinding(skyShader.ID, projectionMatIndexInSkyShader, 2);
因为投影矩阵是公用的,所以只需要设置一次即可
// 把投影矩阵插入缓冲中glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), &projection[0][0]);
在每轮循环中,view矩阵需要修改一次,但只需要修改缓存中的矩阵,程序就不需要修改了
// 设置视角矩阵的缓冲glm::mat4 view = camera.GetViewMatrix();glBindBuffer(GL_UNIFORM_BUFFER, ubo);glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), &view[0][0]);glBindBuffer(GL_UNIFORM_BUFFER, 0);
另外天空盒的view需要单独设置,因为它与别人不一样,它不能进行位移
最后在shader中添加uniform块
layout (std140) uniform projectionMat
{mat4 projection;mat4 view;
};
layout (std140) uniform projectionMat
{mat4 projection;
};
最终程序正常运行,并且摄像机移动也正常,并且天空盒也没有出现移动,一切正常。
相关文章:
OpenGL学习笔记(立方体贴图、高级数据、高级GLSL)
目录 立方体贴图天空盒环境映射斯涅尔定律(Snells Law)菲涅尔效应(Fresnel Effect)动态环境贴图 高级数据分批顶点属性复制缓冲 高级GLSL顶点着色器变量片段着色器变量接口块Uniform缓冲对象Uniform块布局使用Uniform缓冲测试 Git…...
嵌入式进阶:如何选择合适的开发平台?
随着现代工业、物联网以及人工智能技术的迅速发展,嵌入式系统已经由简单的控制器向复杂的高性能系统迈进。从传统家电到智能机器人、从自动驾驶汽车到工业自动化,每一项应用都对嵌入式系统的响应速度、运行稳定性和能耗管理提出了更高要求。在这种背景下…...
CVPR‘25 SOTA——GoalFlow论文精读
1)第一遍___粗读 Q: 这篇论文试图解决什么问题? A: 这篇论文提出了一个名为 GoalFlow 的端到端自动驾驶方法,旨在解决自动驾驶场景中高质量多模态轨迹生成的问题。具体而言,它试图解决以下问题: 轨迹选择的复杂性&am…...
vue3 onMounted 使用方法和注意事项
基础用法 / 语法糖写法 <script> import { onMounted } from vue;// 选项式 API 写法 export default {setup() {onMounted(() > {console.log(组件已挂载);});} } </script><script setup> onMounted(() > {console.log(组件已挂载); }); </scrip…...
【ubuntu】linux开机自启动
目录 开机自启动: /etc/rc.loacl system V 使用/etc/rc*.d/系统运行优先级 遇到的问题: 1. Linux 系统启动阶段概述 方法1:/etc/rc5.d/ 脚本延时日志 方法二:使用 udev 规则来触发脚本执行 开机自启动: /etc/…...
OpenCV day2
Matplotlib相关知识 Matplotlib相关操作: import numpy as np from matplotlib import pyplot as pltx np.linspace(0, 2 * np.pi, 100) y1 np.sin(x) y2 np.cos(x)# 使用红色虚线,圆点标记,线宽1.5,标记大小为6绘制sin plt.p…...
OpenCV 图形API(31)图像滤波-----3x3 腐蚀操作函数erode3x3()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 使用3x3矩形结构元素腐蚀图像。 该函数通过使用中心作为锚点的3x3矩形结构元素来腐蚀源图像。腐蚀操作可以应用多次(迭代࿰…...
机器学习概述自用笔记(李宏毅)
机器学习概述 机器学习即找一个复杂的人类写不出来的函数,把输入(向量,矩阵,序列)转换为输出。 regression:输出是一个数值(预测PM2.5的数值) classification:选择设置…...
大数据面试问答-Spark
1. Spark 1.1 Spark定位 "Apache Spark是一个基于内存的分布式计算框架,旨在解决Hadoop MapReduce在迭代计算和实时处理上的性能瓶颈。 1.2 核心架构 Spark架构中有三个关键角色: Driver:解析代码生成DAG,协调任务调度&a…...
UE5 设置父物体和解除父子关系(移除子物体)
文章目录 设置父物体解除父子关系 Acotor类似于untiy的objecttransfrom,可以用来进行父子操作 设置父物体 Actor attach to Actor节点 解除父子关系 Detach From Actor...
Git - 怎么把当前修改追加到前面某个commit中
怎么把当前修改追加到前面某个commit中 git log commit b7cb11b53388d410d07e3b3084c67274cee4cdad (HEAD -> hotfix/task-108344, origin_dbackup/hotfix/task-108344) Author: aaa <aaammm.com> Date: Thu Mar 27 15:08:32 2025 0800Fix #108344: add bbbcommit …...
【HFP】蓝牙 HFP 协议状态通知机制研究
目录 一、状态通知体系架构 1.1 核心功能矩阵 1.2 三层控制体系 1.3 角色分工 1.4 协议栈层级 二、核心AT命令解析 2.1 ATCMER:指示器状态报告控制 2.2 ATBIA:指示器激活控制 2.3 CIEV:未请求结果码 三、关键功能实现机制 3.1 注册…...
unity100天学习计划
以下是一个为期100天的Unity学习大纲,涵盖从零基础到独立开发完整游戏的全流程,结合理论、实践和项目实战,每天学习2-3小时: 第一阶段:基础奠基(Day 1-20) 目标:掌握Unity引擎基础与C#编程 Day 1-5:引擎入门 安装Unity Hub和Unity Editor(LTS版本)熟悉Unity界面:S…...
STM32电机库 电机控制特性
ST MC FW库提供FOC和六步法两种电机控制方式。这使得它能够驱动永磁同步电机 (PMSM) 和无刷直流电机 (BLDC)。FOC 更适合 PMSM,而六步法更适合 BLDC 电机。该固件可以驱动内嵌式PMSM 和标贴式PMSM。 ST Motor Control 固件库提供以下功能: FOC SVPWM 生成: 可配置的 PW…...
MySQL数据库 - 事务
事务 此笔记参考黑马教程,仅学习使用,如有侵权,联系必删 文章目录 事务1. 事务简介2. 事务操作2.1 事务操作 - 方式一2.2 方式二代码实现 3. 事务四大特性(ACID)4. 并发事务问题5. 事务隔离级别代码实现 总结 1. 事务…...
火山引擎旗下的产品
用户问的是火山引擎旗下的产品,我需要详细列出各个类别下的产品。首先,我得确认火山引擎有哪些主要业务领域,比如云计算、大数据、人工智能这些。然后,每个领域下具体有哪些产品呢?比如云计算方面可能有云服务器、容器…...
用 Python 从零构建异步回显服务器
简介 让我们从 0 开始,搭建一个异步服务输出服务器。 套接字 套接字(socket),是不同计算机中实现通信的一种方式,你可以理解成一个接口,它会在客户端和服务端建立连接,一台发送数据ÿ…...
【3D文件】可爱小鹿3D建模,3D打印文件
【3D文件】可爱小鹿3D建模,3D打印文件 免费下载,下载链接: 3D文件可爱小鹿3D建模,可爱小鹿建模仿真,小鹿仿真设计,3D打印文件,免费下载资源-CSDN文库 资源下载: 3D文件可爱小鹿3D…...
RabbitMQ 优先级队列详解
本文是博主在记录使用 RabbitMQ 在执行业务时遇到的问题和解决办法,因此查阅了相关资料并做了以下记载,记录了优先级队列的机制和使用要点。 本文为长文,详细介绍了相关的知识,可作为学习资料看。 文章目录 一、优先级队列介绍1、…...
串口通信简述
一.串口的特点 1.全双工异步通信 全双工指通信双方可以同时进行数据的发送和接收操作。 异步通信是指通信双方不使用共同的时钟信号来同步数据传输,而是通过特殊的信号或约定来标识数据的开始和结束 2.数据字长度可编程(8 位或 9 位) 不…...
【2025年五一数学建模竞赛A题】完整思路和代码
1.问题背景与重述 2.解题思路分析 2.1 问题一的分析 问题一假设无人机以平行于水平面的方式飞行并投放物资,可以将物资的运动 类比成平抛运动,由于物资的重量较大,因此不能简单的看成质点,还要考虑物资 的重量。 2.1.1本题要求给…...
为了四季度的盈利,李斌的换人还在继续
李斌对蔚来和乐道人事调整还在继续。 4月10日,蔚来发布内部邮件宣布大量人事变动。 蔚来方面: 原用户关系(UR)负责人沈泓因个人原因将离开公司。 任命孙明担任用户关系(UR)负责人,向高级副总…...
Pytest 自动化测试框架详解
Pytest和Unittest测试框架的区别? 如何区分这两者,很简单unittest作为官方的测试框架,在测试方面更加基础,并且可以再次基础上进行二次开发,同时在用法上格式会更加复杂;而pytest框架作为第三方框架&#x…...
sqli-labs靶场 less 9
文章目录 sqli-labs靶场less 9 时间盲注 sqli-labs靶场 每道题都从以下模板讲解,并且每个步骤都有图片,清晰明了,便于复盘。 sql注入的基本步骤 注入点注入类型 字符型:判断闭合方式 (‘、"、’、“”…...
奇趣点播系统测试报告
1.项目简介 本项目旨在搭建一个视频共享点播系统,服务器支持用户通过前端浏览器访问服务器,获取展示与观看和操作的界面,最终实现视频的上传以及观看和删改查等基础管理功能。让用户拥有良好的观看体验和分享视频的快捷方式,此外…...
空地机器人在复杂动态环境下,如何高效自主导航?
随着空陆两栖机器人(AGR)在应急救援和城市巡检等领域的应用范围不断扩大,其在复杂动态环境中实现自主导航的挑战也日益凸显。对此香港大学王俊铭基于阿木实验室P600无人机平台自主搭建了一整套空地两栖机器人,使用Prometheus开源框架完成算法的仿真验证与…...
01 - QEMU 初始化概览 - Init()
目录 1.初始化 - qemu_init() 1.1.基本设备 1.2.日志 1.3.模块信息 1.4.子系统 1.5.选项解析 - 阶段一 1.6.选项解析 - 阶段二 1.7.选项配置 1.8.Trace 1.9.主线程 1.10.CPU 时钟 1.11.其他设置 1.12.创建虚拟机 1.13.启动虚拟机 2.主线程 - qemu_main() 2.1.处…...
Vue3 使用ref
<button click"changeMsg">change</button> <div>{{ message }}</div>//接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。 const message ref(hello world) const mum 1 co…...
React中 点击事件写法 的注意(this、箭头函数)
目录 1、错误写法:onClick{this.acceptAlls()} 2、正确写法:onClick{this.acceptAlls}(不带括号) 总结 方案1:构造函数绑定 方案2:箭头函数包装方法(更简洁) 方案3&am…...
DeepSeek AI大模型:中国智能时代的“争气机“-AI生成
DeepSeek AI大模型:中国智能时代的"争气机" 当全球科技巨头在万亿参数竞赛中你追我赶时,一家中国公司悄然改写了游戏规则。DeepSeek AI最新发布的"探月"大模型不仅以中英双语能力打破技术壁垒,更用"动态脑区"设…...
Java老鼠迷宫(递归)---案例来自韩顺平老师讲Java
题目: 粉色圈圈是启动,红色方框是阻挡,蓝色五角星是出口,走到出口,老鼠winner 代码: public class test6 {public static void main(String[] args){//创建二维数组int[][] map new int[8][7];// 最外围都…...
Python大数据视频教程
概述 最新整理的Python大数据视频教程已出,需要学习的小伙伴抓紧了。 课程亮点: ❶ 编程基石:从Python基础到高阶函数式编程,用代码驯服数据 ❷ 数据魔法:SQL进阶ETL实战,Pandas玩转百万级数据分析 ❸ 分…...
Java工厂模式解析:灵活对象创建的实践指南
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、模式定义与分类 工厂模式(Factory Pattern)是创建型设计模式的核心成员之一,主要解决对象创建过程中的灵活性问题。根…...
PDF转换格式失败?原因及解决方法全解析
在日常工作中,我们经常会遇到将PDF转换为Word、Excel、PPT等格式的需求。有时候以为一键转换就能搞掂,没想到却转换失败。到底问题出在哪?别急,我们可以看看是否以下几个问题引起的,找到解决问题的关键! 原…...
《轨道力学讲义》——第五讲:摄动理论基础
第五讲:摄动理论基础 引言 在实际的航天任务中,我们很少能够使用理想的二体问题来精确描述航天器的运动。地球的非球形性、大气阻力、太阳辐射压以及第三天体引力等各种因素都会对航天器轨道产生偏离理想轨道的影响。这些额外的力被称为"摄动力&q…...
【NLP】23.小结:选择60题
Question 1: What does the fixed lookup table in traditional NLP represent? A. A table of one‐hot vectors B. A table of pre‐trained dense word embeddings C. A dictionary of word definitions D. A table of n-gram counts Answer (中文): 答案选 B。传统NLP中“…...
C++核心机制-this 指针传递与内存布局分析
示例代码 #include<iostream> using namespace std;class A { public:int a;A() {printf("A:A()的this指针:%p!\n", this);}void funcA() {printf("A:funcA()的this指针:%p!\n", this);} };class B { public:int b;B() {prin…...
python asyncio 的基本使用
1、引言 asyncio 是 Python 标准库中的一个库,提供了对异步 I/O 、事件循环、协程和任务等异步编程模型的支持。 asyncio 文档 2、进程、线程、协程 线程 线程是操作系统调度的基本单位,同一个进程中的多个线程共享相同的内存空间。线程之间的切换由操…...
大模型中提到的分词器是什么
分词器在大模型中的核心解析 1. 分词器的定义与基本作用 分词器(Tokenizer)是自然语言处理(NLP)中的核心组件,负责将原始文本拆分为模型可处理的离散单元(称为Token)。其核心功能包括: 文本离散化:将连续字符序列转化为数字序列,作为模型的输入。语义单元提取:通过…...
Android中使用BuildConfig.DEBUG无法找到的解决方法
BuildConfig是Android构建工具自动生成的一个类,通常位于应用的包名下,包含一些构建相关的常量,比如DEBUG标志、应用ID、版本信息等。 遇到的问题可能有几种情况。首先,可能项目没有正确构建,导致BuildConfig没有被生…...
Python(14)Python内置函数完全指南:从基础使用到高阶技巧
目录 背景介绍一、内置函数全景分类1. 数据类型转换(15个)2. 数学运算(12个)3. 迭代处理(9个)4. 对象操作(11个)5. 输入输出(4个) 二、高阶函数应用场景1. en…...
echo命令,tail命令,反引号,重定向符
echo命令: 作用:在命令行中输出指定的内容,相当于print语句 语法:echo 指定的内容(当内容包含空格和特殊字符的时候,语句很复杂的时候,最好用双引号括起来) tail命令:…...
集成学习介绍
集成学习(Ensemble Learning)是一种机器学习范式,它通过组合多个模型的预测来提高整体模型的性能。单一模型可能在某些方面表现不佳或具有较高的偏差或方差,而集成方法能够通过结合多个模型的优点来克服这些问题,从而提…...
【CUDA】ubuntu环境下安装cuda
写在前面 软硬件匹配问题 :如老显卡安装ubuntu24, 会发现适合显卡的cuda不适合ubuntu24, 适合ubuntu24的cuda不适合显卡,因此安装ubuntu系统前,务必查明 :当前设备的显卡支持的cuda,支持哪些ubuntu版本 下面的三个问题…...
进程间通信-信号量
消息队列(Message Queue)是一种在不同组件或进程间进行异步通信的机制,它允许应用程序以松耦合的方式交换消息。消息队列就像一个缓冲区,发送者将消息放入队列,接收者从队列中取出消息进行处理。以下为你详细介绍消息队…...
Ubuntu 22.04安装MySQL : Qwen2.5 模型对话数据收集与微调教程
在Ubuntu 22.04安装MySQL的教程请点击下方链接进行参考: 点击这里获取MySQL安装教程 今天将为大家带来如何微调Qwen2.5模型并连接数据库进行对话的教程。快跟着小编一起试试吧~ 1 大模型 Qwen2.5 微调步骤 1.1 从 github 仓库 克隆项目 克隆存储库:#拉取代码 git clo…...
L1-8 新年烟花
单位 杭州百腾教育科技有限公司 新年来临,许多地方会举行烟花庆典庆祝。小 C 也想参加庆典,但不幸的是跟他一个想法的人实在太多,活动场地全是人人人人人人人人人…… 活动场地可视作一个 NM 的矩阵,其中有一些格子是空的&#…...
OpenCV中的轮廓检测方法详解
文章目录 引言一、什么是轮廓?二、OpenCV中的轮廓检测基础1. 基本步骤2. findContours函数详解 三、轮廓检索模式四、轮廓近似方法五、轮廓特征分析1. 轮廓面积2. 轮廓周长/弧长3. 轮廓近似(多边形拟合)4. 凸包5. 边界矩形6. 最小闭合圆7. 拟…...
AIP-231 批量方法:Get
编号231原文链接AIP-231: Batch methods: Get状态批准创建日期2019-06-18更新日期2019-06-18 一些API允许用户获取一组特定资源在一个时间点(例如使用读事务)的状态。批量获取方法提供了这个功能。 指南 API 可以 按照以下模式支持批量获取࿱…...
人工智能基础-matplotlib基础
绘制图形 import numpy as np x np.linspace(0, 10, 100) y np.sin(x) import matplotlib as mpl import matplotlib.pyplot as plt plt.plot(x, y) plt.show()绘制多条曲线 siny y.copy() cosy np.cos(x) plt.plot(x, siny) plt.plot(x, cosy) plt.show()设置线条颜色 …...