opengl实现经纹理映射的旋转立方体_立方体纹理

news/2024/11/9 20:29:00

立方体纹理就是包含6个2D纹理的纹理.6个纹理有序排列在立方体的6个面.其可以通过方向向量采样立方体纹理上的纹素.

553c285fe9233f868f82fbd98d90d444.png

创建立方体贴图跟创建2D贴图一样,但是绑定到GL_TEXTURE_CUBE_MAP上.

glGenTextures(1, &CubeMapID);
glBindTexture(GL_TEXTURE_CUBE_MAP, CubeMapID);

立方体纹理右6个面,每个面都要调用一次glTexImage2D,因此共需要调用6次.每个面有特定的纹理目标.按照顺序是这样.

dc72a966bafb8b2672b82c487e39bbaa.png

这些纹理目标为枚举型,按照顺序他们的值是线性叠加的.因此编写代码的时候可以用for循环调用glTexImage2D,然后用循环次数作为这些纹理目标的枚举值.从GL_TEXTUREE_MAP_POSITIVE_X开始.

例如以i作为循环次数记录.

glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

立方体纹理也需要设置环绕模式与过滤模式.

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);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

一般的纹理是二维的因此仅需要设定ST的环绕模式即可.但是立方体纹理是三维的因此需要对R设置.可以简单理解为z坐标.

天空盒

立方体纹理常用于天空盒.设置好纹理数据和VAO,VBO之后,编写天空盒用的shader就可以了.

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

在绘制天空和的时候需要开启深度测试,以及关闭天空盒的深度写入.因为有些对象与相机的距离可能比天空盒面到相机的距离远.因此这种情况下天空盒有可能将某些物体覆盖了.并且需要注意需要先渲染天空盒再渲染其他物体.

int main()
{
glDepthMask(GL_FALSE);
//绘制天空盒
//...
glDepthMask(GL_TRUE);
//绘制其他物体
}

如果按照以往的思路将MVP矩阵参数传进去将纹理坐标设置好的话并不会得到一个良好的结果.

17f7c97335726a13d86534e64be3dd07.gif

因为我们不想天空盒跟场景中其他物体一样.天空盒不应受移动影响.仅受旋转和缩放影响.因此需要去掉观察矩阵的平移部分.即保留矩阵左上角部分.

view=glm::mat4(glm::mat3(view))

将这个view传进去shader可得到正确的显示.

053d93766c12f030035cfc92561ae585.png

以上的做法是关闭深度写入,让天空盒不参与深度测试.让每个像素都运行一次天空盒的片元着色器.但其实将天空盒的深度值永远设置为1就可以获得正确的效果了并且参与深度测试的话也不需要每个像素都调用天空盒的着色器.因为在某些复杂的场景里面天空盒仅仅显示一小部分.为此,我们需要将天空盒深度值永远设为1.

根据流水线,在顶点着色器运行时投影矩阵会进行透视除法,并最后处于标准化设备空间(NDC)中的.

fb77904243e5be0b5abcd926294beea8.png

因此若想经过透视除法后的z值为1,则需要将z值定为w.

#version 330 core
layout(location=0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

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

将z值定为w进行透视除法后就变为1.

这时候还需要开启深度测试并调用深度测试函数设置比较运算符为GL_LEQUAL.

GL_LEQUAL为片元的深度值小于等于缓存的深度值时通过测试.因为如果调用默认的GL_LESS的话基本上天空盒是不会通过测试的.这时候就不需要设置关闭深度写入了.

glDepthFunc(GL_LEQUAL);
//绘制天空盒
glDepthFunc(GL_LESS);

绘制完天空盒之后改回默认的深度运算操作符,以便其他物体进行深度测试.

如果场景里面没有需要透明度混合的物体的话则天空盒可以随便放置于主循环的某一个位置进行渲染.否则的话会出现这种情况.

2dfa16b0aaa58e7e9a2122d747a7ee3a.png

2B因为不需要透明度混合,因此可以获得正常的渲染效果.但是草地于窗户精灵体需要透明度混合因此会出现奇怪的效果.

这里我是先渲染场景的所有物体,最后才渲染天空盒的.因此这时候场景物体部分的深度值比1小,通过测试与默认的颜色缓存的颜色进行混合.接着天空盒与深度缓存进行测试,因为天空盒深度值为1,比场景里物体的深度值大,因此这部分片元会被抛弃.所以物体混合的颜色里面没有天空盒纹理的颜色.

为此,最好在主循环刚开始的时候渲染天空盒.让天空盒颜色代替默认颜色缓存里的颜色.

1b510a4d1c8c119b8cc6acf5ecee060d.png

所以,无论是对天空盒关闭深度写入还是设置天空盒的深度值为1,都最好在主循环开始的时候渲染.

立方体纹理可以用于环境映射.常用有反射和折射.

首先是反射

c592308df0b5067d6024f17f38758bb8.png

反射很简单.主要就是获取向量R的方向然后通过R在立方体纹理中获取纹素.

在顶点着色器获取片元的位置以及法向量输出到片元着色器.

#version 330 core 
layout (location=0) in vec3 aPos;
layout (location=1) in vec3 aNormal;

out vec3 fragPos;
out vec3 Normal;

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

void main() 
{  
	Normal=mat3(transpose(inverse(view*model)))*aNormal;
	fragPos=vec3(view*model*vec4(aPos,1.0));

	gl_Position=projection*view*model*vec4(aPos,1.0); 
} 

这里的法线用了法线矩阵消除缩放对物体法线的影响.片元位置放置在相机空间中,因为我偏好于将这些计算放在相机空间,这样就不用在片元着色器计算观察位置到片元位置了,直接可以用片元位置代替,因为在相机空间中,摄像机的位置就是原点.

根据上面的图片,需要在片元着色器计算反射向量

.反射向量可以通过GLSL内置函数
计算,也可以用反射向量的计算方式计算
.因为在顶点着色器片元位置在相机空间下,所以向量
仅需要对片元位置归一化即可.
#version 330 core 
out vec4 FragColor; 

in vec3 Normal;
in vec3 fragPos;

uniform samplerCube skybox;

void main() 
{ 
	//环境映射反射
        vec3 I=normalize(fragPos);
	vec3 R=reflect(I,normal);

	FragColor=vec4(texture(skybox,R).xyz,1.0);
} 

db917a15cbf685cbfec047a3444f5398.png

可以运用反射贴图使模型的仅某一部分进行反射.通过引入反射贴图并对反射贴图进行采样获得当前网格的反射系数,然后运用插值对模型的反射贴图与其他光照模型进行综合

struct Material
{
	sampler2D texture_diffuse0;
	sampler2D texture_specular0;
	sampler2D texture_normal0;
	sampler2D texture_reflection0;
	float shininess;//影响镜面高光的散射/半径
};

在material就结构体里面增加一个反射贴图的声明.接着求出反射系数.

float reflectRate=(texture(material.texture_reflection0,TexCoords).r+texture(material.texture_reflection0,TexCoords).g+texture(material.texture_reflection0,TexCoords).b)/3;

不过反射贴图一般颜色分量不是0就是1,所以也可以简单点

	float reflectRate=texture(material.texture_reflection0,TexCoords).r;

在总颜色输出那里做一个插值就可以了.

void main() 
{ 
	vec3 normal=normalize(Normal);
	vec3 viewDir=normalize(-fragPos);//观察空间下进行
	vec3 Direction=CalcDirectionalLight(dirLight,normal,viewDir);
	vec3 Point = CalcPointLight(pointLight,normal,fragPos,viewDir);
	vec3 Spot=CalcSpotLight(spotLight,normal,fragPos,viewDir);

	//环境映射反射
	vec3 I=normalize(fragPos);
	vec3 R=reflect(I,normal);

	float reflectRate=texture(material.texture_reflection0,TexCoords).r;

	vec4 reflection=texture(skybox,R);
	FragColor=vec4(reflectRate*reflection.xyz+(1-reflectRate)*(Spot+Point+Direction),1.0);
} 

最终效果中可以看到身上比较闪闪的地方都有天空盒的映射,其中以眼部的镜片效果最明显.

2c03b761e70ea9f391843833262fcb6c.png

折射

折射与反射的区别不大,就是将

函数改为
函数,再加一个折射率常数.

折射用到了斯涅耳定律,斯涅耳定律很简单:

674a7ad0d8f9b2d49b29d85277176b96.png

其中

,
表示两种介质的折射率,
,
表示光线方向与法线的夹角.

介质的折射率有一个固定的参数.

fd5030e9ce0be6c93d68de4983d56736.png

但是!!!我们不需要自己计算斯涅尔定律,反射有内建GLSL函数.折射与反射的区别不大,就是将

函数改为
函数,再加一个折射率常数.将上述反射部分的片元着色器代码修改下.

其中

,两种折射率比例.然后作为
的第三个参数就行
float ratio=1.0/1.33;
vec3 I=normalize(fragPos);
vec3 R=refract(I,normal,ratio);

FragColor=vec4(texture(skybox,R).xyz,1.0);

2ac74d730115b80701e4ed5d83942569.png

当然,

函数想要自己算也不是不行.
杨超:cg refract函数​zhuanlan.zhihu.com
5b7fe9b981fe6a9b449af525029025b2.png

这里有一篇折射函数的推导过程.其中

.

按照这篇文章的推导即可得到正确折射向量.

vec3 refract(vec3 I,vec3 normal,float ratio)
{
	vec3 OC=dot(-I,normal)*normal;
	vec3 CA=-I-OC;
	vec3 EB=-ratio*CA;
	float EB_length_2=EB.x*EB.x+EB.y*EB.y+EB.z+EB.z;

	vec3 OE=-sqrt(1-EB_length_2)*normal;

	return EB+OE;
}
立方体贴图 - LearnOpenGL CN​learnopengl-cn.github.io
995f1f52817866d08a5f010ec509f2a7.png

http://www.niftyadmin.cn/n/2747045.html

相关文章

rocketmq广播消息为什么不能重试_RocketMQ系列(五)广播与延迟消息

今天要给大家介绍RocketMQ中的两个功能,一个是“广播”,这个功能是比较基础的,几乎所有的mq产品都是支持这个功能的;另外一个是“延迟消费”,这个应该算是RocketMQ的特色功能之一了吧。接下来,我们就分别看…

数据结构 二叉树 根据后序和中序遍历输出先序遍历

根据后序和中序遍历输出先序遍历 题目描述: 本题要求根据给定的一棵二叉树的后序遍历和中序遍历结果,输出该树的先序遍历结果。 输入格式: 第一行给出正整数N(≤30),是树中结点的个数。随后两行,每行给出N个整数,分别…

adplayer移植【转】

本文转载自:https://blog.csdn.net/qq361294382/article/details/50525412 这两天做madplayer移植,由于是刚装的ubuntu14.04,所以有好多库没装,还有其它未配置起来的地方,搞起来有几个问题,不过组后按着教程…

梯度下降的超参数大于等于2什么意思_梯度、散度、旋度与矢量分析

矢量分析在场论中非常重要,而三个基本算子(梯度、散度与旋度)又是构成各种复杂关系式的基础,下面逐一介绍,应特别注意散度与旋度的基本定义。对于矢量恒等式,在此列出是为了使用时查找方便,具体…

维护盘pe linux,不进入pe系统也能轻松维护硬盘,简直神器!

原标题:不进入pe系统也能轻松维护硬盘,简直神器!相对其他器件而言,硬盘就属于比较脆弱的一类,如果硬盘没有保护好很容易出现问题,一旦遭遇硬盘损坏,将会带来很大的麻烦,所以我们需要…

linux 蓝牙 profile,Linux系统下蓝牙立体声配置A2DP profile

系统配置:Linux debian 2.6.22.6 #7 Mon Sep 3 10:46:00 CST2007 ppc GNU/Linuxbluetooth software: bluez-lib bluez-utils均是3.22。bluez.orgbluetooth hardware: iBook G4 内置的CSR 蓝牙2.0芯片、MotorolaS705蓝牙立体声耳机,也是CSR 蓝牙2.0芯片。…

Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported

记一次Java Bug的解决过程 Q:Bug描述 前端form表单数据提交时,后端出现Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type application/x-www-form-urlencoded;charsetUTF-8 not supported]这样的提示,也没有触发Con…

【持续更新】NOIP注意事项

1.无根据的乱搞不可能对 2.必须造极限数据跑一下 3.必须测空间 4.不管用不用都把cstring加上 5.开文件测样例 6.删一长串代码最好注释 7.到10:00先敲暴力 8.题读三遍 9.先做好得分的,而不是先做好玩的 10.不准写LCT之类的神奇东西 11.就算存不下也要把dp方程写出来 …