OpenGL学习笔记(八)-面剔除-帧缓冲-立方体贴图

参考网址:LearnOpenGL 中文版

第四章 高级OpenGL

4.4 面剔除

4.4.1 基本概念

1、一个3D立方体,从任意方向最多能同时看到3个面,以某种方式丢弃这几个看不见的面,能省下超过50%的片段着色器执行数。

2、如何确定物体的某一个面看不见?一个闭合形状的每个面都有两侧,每一侧要么面向用户,要么背对用户。面剔除能够检查所有面向观察者的面,并渲染它们,而丢弃那些背向的面。但要告诉OpenGL哪些是正向面和背向面,这可通过分析顶点数据的环绕顺序完成。

4.4.2 环绕顺序

1、当定义一组三角形顶点时,会以顺时针或逆时针的环绕顺序来定义它们。
在这里插入图片描述
2、一般按照逆时针定义三角形顶点,通过三角形图元的环绕顺序可确定三角形是正向还是背向。观察者所面向的三角形顶点都是以逆时针环绕顺序所渲染的,而立方体另一面的三角形顶点则是顺时针的环绕顺序。
3、在顶点数据中,将两个三角形都以逆时针顺序定义(1,2,3)。从观察者当前视角使用1、2、3的顺序来绘制的话,背面的三角形将会是以顺时针顺序渲染的,就要被剔除。
在这里插入图片描述

4.4.3 代码

1、启用OpenGL的GL_CULL_FACE面剔除选项,面剔除只对像立方体这样的封闭形状有效。

glEnable(GL_CULL_FACE);

2、允许我们改变需要剔除的面的类型,可以剔除正向面。GL_BACK:只剔除背向面。GL_FRONT:只剔除正向面。GL_FRONT_AND_BACK:剔除正向面和背向面。

glCullFace(GL_FRONT);

3、允许将顺时针的面定义为正向面,默认值是逆时针GL_CCW,另一个选项是顺时针GL_CW

glFrontFace(GL_CCW);

4.5 帧缓冲

4.5.1 创建帧缓冲

1、帧缓冲包括颜色缓冲、深度缓冲和模板缓冲,它被储存在内存中。目前所做的操作都是在默认帧缓冲的渲染缓冲上进行的,OpenGL允许我们定义我们自己的帧缓冲,就能够有更多方式来渲染。

2、使用glGenFramebuffers创建一个帧缓冲对象(Framebuffer Object, FBO),将它绑定为激活的(Active)帧缓冲。之后所有的读取和写入帧缓冲将会影响当前绑定的帧缓冲,可以使用GL_READ_FRAMEBUFFERGL_DRAW_FRAMEBUFFER,将一个帧缓冲分别绑定到读取目标或写入目标。

unsigned int fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

3、渲染到一个不同的帧缓冲被叫做离屏渲染,渲染指令将不会对窗口的视觉输出有任何影响。要保证所有的渲染操作在主窗口中有视觉效果,需要再次激活默认帧缓冲,将它绑定到0。

glBindFramebuffer(GL_FRAMEBUFFER, 0);
4.5.2 帧缓冲附件

1、一个完整的帧缓冲需要:附加至少一个缓冲(颜色、深度或模板缓冲);至少有一个颜色附件(Attachment); 所有的附件都必须是完整的(保留了内存); 每个缓冲都应该有相同的样本数。

  • 因此需要创建一些附件,并将附件附加到帧缓冲上。附件是一个内存位置,它能够作为帧缓冲的一个缓冲,可以将它想象为一个图像。当创建一个附件的时候我们有两个选项:纹理或渲染缓冲对象。
  • 检查帧缓冲是否完整:
    glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE
    

2、纹理附件:当把一个纹理附加到帧缓冲的时候,所有的渲染指令将会写入到这个纹理中,就像一个普通的颜色/深度或模板缓冲一样。使用纹理的优点是,渲染操作的结果储存在纹理图像中,可以在着色器中使用。

  • 纹理附件的创建与普通纹理差不多:

    unsigned int texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
  • 纹理对象维度设置为了屏幕大小,纹理的data参数为NULL,仅仅分配了内存而没有填充它。同时不用设置环绕方式或多级渐远纹理。

  • 将纹理附件附加到帧缓冲上,target:帧缓冲的目标;attachment:附件类型,当前附加颜色附件,最后的0意味着可以附加多个颜色附件;textarget:附加的纹理类型;texture:要纹理本身;level:多级渐远纹理的级别。

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    
  • 除了颜色附件之外,还可以附加一个深度和模板缓冲纹理到帧缓冲对象中。将附件类型设置为GL_DEPTH_ATTACHMENTGL_STENCIL_ATTACHMENT,将注意纹理的格式和内部格式类型变为GL_DEPTH_COMPONENTGL_STENCIL_INDEX

  • 也可以将深度缓冲和模板缓冲附加为一个单独的纹理。纹理的每32位数值将包含24位的深度信息和8位的模板信息,使用GL_DEPTH_STENCIL_ATTACHMENT类型,并配置纹理的格式,让它包含合并的深度和模板值。

    glTexImage2D(
      GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, 
      GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL
    );
    
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);
    

3、渲染缓冲对象附件:渲染缓冲对象是一个真正的缓冲,即一系列的字节、整数、像素等。它会将渲染数据储存为OpenGL原生的渲染格式,不会做任何针对纹理格式的转换。

  • 渲染缓冲对象通常都是只写的,所以你不能读取它们,只从当前绑定的帧缓冲中,而不是附件本身,使用glReadPixels来读取特定区域的像素。

  • 因为它的数据是原生的格式,当写入或者复制它的数据到其它缓冲中时是非常快的。在每个渲染迭代使用的glfwSwapBuffers,可以通过渲染缓冲对象实现:只需要写入一个渲染缓冲图像,并在最后交换到另外一个渲染缓冲就可以了。

  • 创建一个渲染缓冲对象的代码和帧缓冲的代码很类似:

    unsigned int rbo;
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    
  • 由于渲染缓冲对象通常都是只写的,经常用于深度和模板附件,因为通常不需要从深度和模板缓冲中读取值,只关心深度和模板测试。通过glRenderbufferStorage函数创建一个深度和模板渲染缓冲对象,与创建纹理对象类似。

    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
    
  • 附加渲染缓冲对象:

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
    
4.5.3 渲染到纹理

1、将会将场景渲染到一个附加到帧缓冲对象上的颜色纹理中,之后将在一个横跨整个屏幕的四边形上绘制这个纹理。这样视觉输出和没使用帧缓冲时是完全一样的,但这次是打印到了一个四边形上。

//创建一个帧缓冲对象
unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// 生成纹理
unsigned int texColorBuffer;
glGenTextures(1, &texColorBuffer);
glBindTexture(GL_TEXTURE_2D, texColorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
// 作为一个颜色附件附加到当前绑定的帧缓冲对象
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);  

2、添加一个深度和模板附件到帧缓冲中,能够进行深度测试。由于只需采样颜色缓冲,因此可以为它们创建一个渲染缓冲对象

unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo); 
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);  
glBindRenderbuffer(GL_RENDERBUFFER, 0);
//将渲染缓冲对象附加到帧缓冲的深度和模板附件上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
//解绑帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, 0);

3、绑定这个帧缓冲对象,渲染到帧缓冲的缓冲中。之后的渲染指令将会影响当前绑定的帧缓冲。所有的深度和模板操作都会从当前绑定的帧缓冲的深度和模板附件中读取。想绘制场景到一个纹理上,需要采取以下的步骤:

  • 将新的帧缓冲绑定为激活的帧缓冲,和往常一样渲染场景,渲染内容将写入新的帧缓冲中;
  • 由于上述为离屏缓冲,并不会产生视觉效果,因此要绑定默认的帧缓冲,再进行渲染;
  • 在默认帧缓冲中绘制一个横跨整个屏幕的四边形,将自定义帧缓冲的颜色缓冲作为它的纹理。
// 第一处理阶段,绑定自定义帧缓冲,渲染立方体和地板
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 我们现在不使用模板缓冲
glEnable(GL_DEPTH_TEST);
DrawScene();    
// 第二处理阶段,换绑到默认帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, 0); // 返回默认
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 
glClear(GL_COLOR_BUFFER_BIT);
//在默认帧缓冲中,访问自定义帧缓冲中的纹理缓冲,作为纹理贴到四边形上
screenShader.use();  
glBindVertexArray(quadVAO);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glDrawArrays(GL_TRIANGLES, 0, 6);  

在这里插入图片描述

4.5.4 后期处理

由于整个场景都被渲染到了一个纹理上,可以通过修改纹理数据创建出一些非常有意思的效果。
1、反相
在屏幕的片段着色器中,从屏幕纹理中取颜色值,然后用1.0减去它,对它进行反相:

void main()
{
    FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}

在这里插入图片描述
2.灰度
取所有的颜色分量,将它们平均化,让整个图像灰度化:

void main()
{
    FragColor = texture(screenTexture, TexCoords);
    float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
    FragColor = vec4(average, average, average, 1.0);
}

在这里插入图片描述
由于人眼会对绿色更加敏感一些,而对蓝色不那么敏感,因此使用加权的通道:

void main()
{
    FragColor = texture(screenTexture, TexCoords);
    float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b;
    FragColor = vec4(average, average, average, 1.0);
}

在这里插入图片描述
3.核效果
在纹理图像上可以做其余的图像处理,核效果通过对当前纹理坐标添加一个小的偏移量,取得周围的像素(纹理)值,用它的核值乘以周围的像素值,并将结果相加变成一个值。核是一个类矩阵的数值数组,它的中心为当前的像素:

[ − 1 − 1 − 1 − 1 9 − 1 − 1 − 1 − 1 ] \begin{bmatrix}-1 & -1 & -1 \\ -1 & 9 & -1 \\ -1 & -1 & -1\end{bmatrix} 111191111

const float offset = 1.0 / 300.0;  

void main()
{
    vec2 offsets[9] = vec2[](
        vec2(-offset,  offset), // 左上
        vec2( 0.0f,    offset), // 正上
        vec2( offset,  offset), // 右上
        vec2(-offset,  0.0f),   // 左
        vec2( 0.0f,    0.0f),   // 中
        vec2( offset,  0.0f),   // 右
        vec2(-offset, -offset), // 左下
        vec2( 0.0f,   -offset), // 正下
        vec2( offset, -offset)  // 右下
    );

    float kernel[9] = float[](
        -1, -1, -1,
        -1,  9, -1,
        -1, -1, -1
    );

    vec3 sampleTex[9];
    for(int i = 0; i < 9; i++)
    {
        sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
    }
    vec3 col = vec3(0.0);
    for(int i = 0; i < 9; i++)
        col += sampleTex[i] * kernel[i];

    FragColor = vec4(col, 1.0);
}

在这里插入图片描述
模糊操作核:

float kernel[9] = float[](
    1.0 / 16, 2.0 / 16, 1.0 / 16,
    2.0 / 16, 4.0 / 16, 2.0 / 16,
    1.0 / 16, 2.0 / 16, 1.0 / 16  
);

在这里插入图片描述
边缘检测:

[ 1 1 1 1 − 8 1 1 1 1 ] \begin{bmatrix}1 & 1 & 1 \\ 1 & -8 & 1 \\ 1 & 1 & 1 \end{bmatrix} 111181111

在这里插入图片描述

4.6 立方体贴图

4.6.1 创建立方体贴图

1、立方体贴图是一个包含了6个2D纹理的纹理类型,每个2D纹理都组成了立方体的一个面。通过一个方向向量来进行纹理索引/采样。假设有一个单位立方体,方向向量原点位于中心。使用一个橘黄色的方向向量来从立方体贴图上采样一个纹理值:

在这里插入图片描述
2、假设立方体贴图应用到一个立方体上,只要立方体的中心位于原点,就能使用立方体的实际位置向量来对立方体贴图进行采样,将所有顶点的纹理坐标当做是立方体的顶点位置。

3、立方体贴图纹理的创建,要绑定到GL_TEXTURE_CUBE_MAP

unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

4、因为立方体贴图包含有6个纹理,需要调用glTexImage2D函数6次,将纹理目标参数设置为立方体贴图的一个特定的面:

纹理目标方位
GL_TEXTURE_CUBE_MAP_POSITIVE_X
GL_TEXTURE_CUBE_MAP_NEGATIVE_X
GL_TEXTURE_CUBE_MAP_POSITIVE_Y
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
GL_TEXTURE_CUBE_MAP_POSITIVE_Z
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z

和OpenGL的枚举一样,纹理目标背后的int值是线性递增的,所以如果有一个纹理位置的数组,就可以从GL_TEXTURE_CUBE_MAP_POSITIVE_X开始遍历它们,在每个迭代中对枚举值加1,遍历了整个纹理目标:

int width, height, nrChannels;
unsigned char *data;  
for(unsigned int i = 0; i < textures_faces.size(); i++)
{
    data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);
    glTexImage2D(
        GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
        0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
    );
}

5、设定它的环绕和过滤方式,GL_TEXTURE_WRAP_R对应的是纹理的第三个维度(和位置的z一样)。因为正好处于两个面之间的纹理坐标可能不能击中一个面,所以使用GL_CLAMP_TO_EDGE对两个面之间采样,返回它们的边界值。

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

6、在片段着色器中,使用samplerCube类型的采样器,使用一个vec3的方向向量进行采样:

in vec3 textureDir; // 代表3D纹理坐标的方向向量
uniform samplerCube cubemap; // 立方体贴图的纹理采样器

void main()
{             
    FragColor = texture(cubemap, textureDir);
}
4.6.2 天空盒

1、天空盒是一个包含了整个场景的立方体,它包含周围环境的6个图像,让玩家以为他处在一个比实际大得多的环境当中。天空盒图像通常有以下的形式:
在这里插入图片描述
2、加载天空盒:函数与加载纹理图像类似,改为接受一个包含6个纹理路径的vector:

unsigned int loadCubemap(vector<std::string> faces)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

    int width, height, nrChannels;
    for (unsigned int i = 0; i < faces.size(); i++)
    {
        unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
        if (data)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
                         0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
            );
            stbi_image_free(data);
        }
        else
        {
            std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
            stbi_image_free(data);
        }
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    return textureID;
}
vector<std::string> faces
{
    "right.jpg",
    "left.jpg",
    "top.jpg",
    "bottom.jpg",
    "front.jpg",
    "back.jpg"
};
unsigned int cubemapTexture = loadCubemap(faces);

3、由于天空盒是绘制在一个立方体上的,需要另一个VAO、VBO以及新的一组顶点。另外需要一组的着色器,在顶点着色器中,

#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);
}

4、绘制天空盒时,需要将它变为场景中的第一个渲染的物体,并且禁用深度写入。这样子天空盒就会永远被绘制在其它物体的背后了。

//先渲染天空
glDepthMask(GL_FALSE);
skyShader->use();

glm::mat4 viewMat = glm::mat4(glm::mat3(camera.GetViewMatrix()));
glm::mat4 projMat = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
skyShader->setMat4f("view", viewMat);
skyShader->setMat4f("projection", projMat);

glBindVertexArray(skyVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
// ... 绘制剩下的场景

5、我们希望天空盒是以玩家为中心的,这样不论玩家移动了多远,天空盒都不会变近。然而,当前的观察矩阵会变换天空盒的位置,当玩家移动的时候,立方体贴图也会移动。所以移除观察矩阵中的位移部分,让移动不会影响天空盒的位置向量。

glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));

6、如果先渲染天空盒,就会对屏幕上的每一个像素运行一遍片段着色器。因此,应该最后渲染天空盒,只在深度测试通过的地方渲染天空盒的片段。由于天空盒是一个1x1x1的立方体,它很可能会不通过大部分的深度测试,所以令天空盒有着最大的深度值1.0,只要它前面有一个物体,深度测试就会失败。
透视除法是在顶点着色器运行之后执行的,将gl_Position的xyz坐标除以w分量,相除结果的z分量等于顶点的深度值。令输出位置的z分量等于它的w分量,当透视除法执行之后,z分量会变为w / w = 1.0。

void main()
{
    TexCoords = aPos;
    vec4 pos = projection * view * vec4(aPos, 1.0);
    gl_Position = pos.xyww;
}

还需要改变一下深度函数,从默认的GL_LESS改为GL_LEQUAL。深度缓冲将会填充上天空盒的1.0值,保证天空盒在值小于或等于深度缓冲而不是小于时通过深度测试。

glDepthFunc(GL_LEQUAL);

在这里插入图片描述

4.6.3 环境映射

1、通过使用环境的立方体贴图,可以给物体反射和折射的属性。这样使用环境立方体贴图的技术叫做环境映射。
2、反射:物体反射它周围环境,根据观察者的视角,物体的颜色或多或少等于它的环境。根据观察方向向量I和物体的法向量N,来计算反射向量R,最终的R向量将会作为索引/采样立方体贴图的方向向量,返回环境的颜色值。

  • 片段着色器中
#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);
}
  • 顶点着色器中,使用法线矩阵变换,Position输出向量是一个世界空间的位置向量。
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 Normal;
out vec3 Position;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    Normal = mat3(transpose(inverse(model))) * aNormal;
    Position = vec3(model * vec4(aPos, 1.0));
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}
  • 更新一下顶点数据,并更新属性指针,设置cameraPos变量,要绑定立方体贴图
glBindVertexArray(cubeVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);

在这里插入图片描述
3、折射:经折射后观察向量R的方向轻微弯曲了,R将会用来从立方体贴图中采样。折射可以使用GLSL的内建refract函数来实现,它需要两个材质之间的折射率,折射率决定了材质中光线弯曲的程度。
在这里插入图片描述

  • 使用折射率计算光传播的两种材质间的比值,光线从空气进入玻璃,比值为1.00/1.52。
void main()
{             
    float ratio = 1.00 / 1.52;
    vec3 I = normalize(Position - cameraPos);
    vec3 R = refract(I, normalize(Normal), ratio);
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值