本文参考LearnOpenGL,链接原作者Joey de Vries
1. 下列代码是同时用两组VBO和VAO进行绘制,这里是设置界面,一个VAO挖一个VBO的数据,挖完存储起来,再挖下一组VBO。
unsigned int VBO[2],VAO[2];
glGenBuffers(2, VBO);
glGenVertexArrays(2, VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW);
glBindVertexArray(VAO[0]);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT,GL_FALSE, 3 * sizeof(float), (void*)0);
glBindVertexArray(VAO[1]);
glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
具体的绘制阶段只需要切换VAO就行:
glBindVertexArray(VAO[0]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(VAO[1]);
glDrawArrays(GL_TRIANGLES, 0, 3);
2. glUniform4f等函数,需要在glUseProgram之后使用才能发挥作用,所以说OpenGL作为一个渲染流水线,其各个API的调用顺序是很重要的。
3. OpenGL添加EBO之后绘制失败,发现VBO,VAO,EBO之间存在强制的绑定顺序。
看看下面这个图,先有的VAO,才能绑定EBO,EBO是VAO的最后一个槽位,所以说EBO必须在VAO之后,VBO和VAO的绑定顺序应该没有强制要求。
4. OpenGL默认会开启(GL_TEXTURE0)的槽位,会直接绑定,所以只贴一个图的时候,都不需要激活和绑定texture的槽位,直接用就行。
//glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, texture2);
myShader->Use();
//glUniform1i(glGetUniformLocation(myShader->GetID(), "texture1"), 0);
glUniform1i(glGetUniformLocation(myShader->GetID(), "texture2"), 3);
5. OpenGL 实现贴图的上下和左右颠倒:
上下颠倒见下
https://blog.csdn.net/narutojzm1/article/details/51940817
同理,实现左右颠倒,只需要把横坐标都颠倒过来就行,在这里是用1去相减,从而取反
float vertices[] = {
// ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 -
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1-1.0f, 1.0f, // 右上
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1-1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1-0.0f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1-0.0f, 1.0f // 左上
};
6. OpenGL里的环绕方式很有意思,当纹理坐标在0~1之间时,会只画一个贴图,当最大值为2时,会在该方向上变成两个
比如这样写:
float vertices[] = {
// ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 -
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 3.0f, 1.0f, // 右上
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 3.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上
};
只会横向拉伸
7. OpenGL画纹理的环绕方式的时候,改变环绕方式发现不起作用。
解决办法:
改变纹理环绕方式的函数是:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
我写的是:
glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
API写错了,难怪没反应。。。。
深度测试
8. 深度缓冲由窗口系统创建,可以由16、24、32位浮点数表示,最常见的是24位。
The depth buffer is automatically created by the windowing system and stores its depth values as 16, 24 or 32 bit floats. In most systems you'll see a depth buffer with a precision of 24 bits.
理论的深度值函数:
Z为摄像机离目标点的距离(应该是这样)
但是这样计算深度值,当F很小时,差距很小,不容易比较,为了放大差距,采用了新的函数,因为深度测试,只需要他们之间的大小比对结果是正确的就行,具体差值并不重要。
实际的深度值函数:
这是对应的曲线图,注意再这种非线性的函数下,当F为0.5时,该点深度并不在near和far平面,反而更靠近near平面(因为F=0.5时,Z=2)
值得一提的是,这个深度F的计算过程,OpenGL已经在 glm::perspective()
中完成了,具体可以参考
OpenGL Projection Matrix
当片元着色器这么写时:
void main()
{
FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
}
示意图,越近的深度越高,呈现灰色,因为深度函数不是随着距离线性变化的,所以在离镜头很近时,变化被放大了:
9. OpenGL的片元着色器可以直接通过GLSL内置的变量, 获取点在 屏幕坐标系上的位置,具体函数是
gl_FragCoord
对于该变量,有:
gl_FragCoord.x //片元在屏幕坐标系上的横坐标x
gl_FragCoord.y //片元在屏幕坐标系上的纵坐标y
gl_FragCoord.z //片元在屏幕坐标系上的深度(这个信息将用于跟深度缓冲的值进行比对)
10. GPU 支持的earlyZ 技术
值得注意的是,earlyZ启用之后,不允许再次对深度缓冲进行写入,所以这项技术,会与透明效果等因素冲突,具体原因以后再补充。
11. Z-fighting(深度冲突)
当两个同样x、y坐标的片元,深度是一样的,或者两个片元的深度差太小导致电脑无法识别,从而产生Z-fighting,由于不知道到底用哪个片元,所以视觉上会随时切换两个片元,导致看起来好像在打架。
由于非线性插值的深度函数,在物体离摄像头比较远的时候,深度差更小,越容易产生Z-fighting。
预防Z-fighting的几种方法
- 不让物体靠得太近,产生重叠,可以设置一个深度offset,也可以人为略微移动其中一个物体,避免冲突。
- 拉远near平面到摄像头的距离,基于非线性深度函数的计算,这样可以增大精度
- 增大depthBuffer的精度,大多数是24位的精度,但现在大多数显卡都可以支持32位精度的深度缓冲。
模板测试
12. 模板测试在片元着色器之后,深度测试之前,模板测试用来实现一些特殊的效果,也可以像深度测试一样,舍弃一些片元,不过舍弃的方式可以自己来设定,可以实现阴影等高级效果。
stencil buffer 现代显卡上的一个数据类型,每一个pixel对应一个8位的stencil buffer ,一共可以代表0~255一共256个数,为0时代表该片元被舍弃:
模板测试和深度测试都有关闭写入的方法:
glDepthMask(GL_FALSE); //禁用深度写入
glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样,就是写入什么都不会对其产生改变
glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
13. OpenGL中关于模板测试的两个函数:
glStencilFunc(GLenum func, GLint ref, GLuint mask);//描述了OpenGL应该怎样对模板缓冲内容做保留和抛弃等操作
第一个参数是 函数的模式,类似于glDepthFunc的参数,此时还会用到第二个参数,比如 GL_EQUAL就是检测是否,与ref 相等,mask是掩码,会在前两个参数进行运算前,分别对片元的模板值和ref值进行与(AND)操作,比如:
glStencilFunc(GL_EQUAL, 1, 0xFF) //默认掩码为11111111,不会产生任何影响
如果片元的模板值为1,则绘制该片元,否则抛弃该片元。
单纯对模板值进行废除或保留操作,
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
通过上述两个函数,可以实现以下功能
- 抛弃或保留特定区域的片元
- 根据需要,设置片元保留的方式
14. 利用模板缓冲,实现物体轮廓描边
比如对一个正方体实现描边,实现方法如下:
- 在模板写入开启的基础上,设置写入的方式,
glStencilFunc(GL_ALWAYS, 1, 0xFF)
,将所有在屏幕上被Render的物体的模板值都设为1 - 在这种模式下绘制正方体,此时屏幕上绘制该正方体的片元的模板值都为1,其他的部分模板值为0
- 关闭深度缓冲和模板缓冲写入
- 再画一个新的正方体,新正方体在原来正方体的scale上稍微放大一点。
- 然后用另外的shader 画新正方体,若对应的片元的模板值不为1,则画,否则不画。
- 重新开启深度测试和模板写入
注意开启深度测试功能和开启\关闭 深度写入的API是不一样的。
//开启深度测试
glEnable(GL_STENCIL_TEST)
//开启深度写入
glStencilMask(0XFF);//掩码为0XFF,任何模板值与做AND运算,值不变。
//关闭深度写入
glStencilMask(0X00);//掩码为0X00,任何模板值与做AND运算,值不变。
15. 混合Blend
混合,与模板测试和深度测试一样,属于逐片元操作,混合常作为实现透明效果的方法。
正常的顺序是,片元通过模板测试————深度测试————混合,三个阶段,最后输出到颜色缓冲区。
全透明效果
如果自带的图里面有alpha通道,可以通过在片元着色器里,用比较直接了当的方式,抛弃其中的白色(默认)片元,比如这样的图:
其空白部分的通道值为0,是透明的效果。直接在片元着色器里写就行:
#version 330 core
in vec2 TexCoords;
out vec4 FragColor;
uniform sampler2D texture1;
void main()
{
if(texture(texture1,TexCoords).a <0.1)
discard;
FragColor=texture(texture1,TexCoords) ;
}
半透明效果
实际上这种效果才真正用到了blend功能, 需要提前开启混合功能。
glEnable(GL_BLEND);
透明效果往往是在渲染流水线的最后,混合的过程是来决定之前,同样位置片元颜色需不需要进行混合,如果关闭混合,则会直接覆盖,使用混和,会把一个片元的多种颜色进行混合。
混合操作的颜色混合公式如下:
source指的是新的color
destination指的是原来已存的color,所以一般离屏幕更远的为destination color
F是二者的权重(factor)
比如说原片元颜色为红色(1.0,0,0,1.0);
要给它实现下图所示的,一个绿色玻璃下的观看效果
新的Csource为
(0,1.0,0,0.6)//设置透明度为0.6,若为1,则会直接覆盖原来的红色
这里的alpha值,就是上述公式中的权重值,所以最终的颜色为:
上述情况下,权重和为1,但是还有很多其他的混合情况,OpenGL提供了API glBlendFunc(GLenum sfactor, GLenum dfactor),可以设置source 和 destination 的权重值。
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //source权重值用其alpha值,destination权重值为1-source权重值
glBlendFunc函数和glBlendFuncSeparate函数的区别
glBlendFunc函数混合了四个通道,比如上面说的,
红色与绿色混和:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //如果这么写
得到的结果是(0.4,0.6,0,0.76)
glBlendFuncSeparate函数会把RGB和alpha通道分开,如果这样写:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
得到的结果前三位,仍是0.4,0.6,0,后面一位则应该是0.6 * 1+0.4 * 0 = 0.6
16. 当绘制多个物体的时候,blend操作容易与depth操作冲突,解决方法步骤如下:
- 先画不透明物体
- 将透明物体进行排序(深度排序)
- 按从远到近的顺序,依次画透明物体。
但实际上一个物体并不一定是所有的片元与其物体之间前后对应的顺序 都是相同的, 对于复杂的有交叉的不透明物体,这种方法并不实用,目前大部分游戏引擎使用的是网格化进行前后透明度物体的排序。