Learn Opengl
立方体贴图包含6个2D纹理,这每个2D纹理是一个立方体(cube)的一个面,也就是说它是一个有贴图的立方体。
从立方体贴图上使用橘黄色向量采样一个纹理值看起来和下图有点像:
方向向量的大小无关紧要。一旦提供了方向,OpenGL就会获取方向向量触碰到立方体表面上的相应的纹理像素(texel),这样就返回了正确的纹理采样值。
方向向量触碰到立方体表面的一点也就是立方体贴图的纹理位置,这意味着只要立方体的中心位于原点上,我们就可以使用立方体的位置向量来对立方体贴图进行采样。然后我们就可以获取所有顶点的纹理坐标,就和立方体上的顶点位置一样。所获得的结果是一个纹理坐标,通过这个纹理坐标就能获取到立方体贴图上正确的纹理。
要创建一个立方体贴图,在进行任何纹理操作之前,需要生成一个纹理,激活相应纹理单元然后绑定到合适的纹理目标上。这次要绑定到 GL_TEXTURE_CUBE_MAP
纹理类型:
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
由于立方体贴图包含6个纹理,立方体的每个面一个纹理,我们必须调用glTexImage2D
函数6次,函数的参数和前面教程讲的相似。然而这次我们必须把纹理目标(target)参数设置为立方体贴图特定的面,这是告诉OpenGL我们创建的纹理是对应立方体哪个面的。因此我们便需要为立方体贴图的每个面调用一次 glTexImage2D
。
由于立方体贴图有6个面,OpenGL就提供了6个不同的纹理目标,来应对立方体贴图的各个面。
纹理目标(Texture target) | 方位 |
---|---|
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值都是连续增加的,所以我们有一个纹理位置的数组或vector,就能以 GL_TEXTURE_CUBE_MAP_POSITIVE_X
为起始来对它们进行遍历,每次迭代枚举值加 1
,这样循环所有的纹理目标效率较高:
for(GLuint i = 0; i < cubefaces.size(); i++)
{
Image* img = new Image();
img->initWithImageFile(cubefaces.at(i));
int width = img->getWidth();
int height = img->getHeight();
unsigned char* imgdata = img->getData();
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imgdata);
}
这儿我们有个vector叫textures_faces
,它包含立方体贴图所各个纹理的文件路径,并且以上表所列的顺序排列。它将为每个当前绑定的cubemp的每个面生成一个纹理。
由于立方体贴图和其他纹理没什么不同,我们也要定义它的环绕方式和过滤方式:
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);
在绘制物体之前,将使用立方体贴图,而在渲染前我们要激活相应的纹理单元并绑定到立方体贴图上,这和普通的2D纹理没什么区别。
在片段着色器中,我们也必须使用一个不同的采样器——samplerCube,用它来从texture
函数中采样,但是这次使用的是一个vec3
方向向量,取代vec2
。下面是一个片段着色器使用了立方体贴图的例子:
//skybox.fsh
varying vec3 v_textureCoord; // 用一个三维方向向量来表示立方体贴图纹理的坐标
uniform samplerCube u_skyTexture; // 立方体贴图纹理采样器
void main()
{
gl_FragColor = textureCube(u_skyTexture, v_textureCoord);
}
使用立方体贴图可以简单的实现很多有意思的技术,其中之一便是著名的天空盒(Skybox)。天空盒(Skybox)是一个包裹整个场景的立方体,它由6个图像构成一个环绕的环境,给玩家一种他所在的场景比实际的要大得多的幻觉。
立方体贴图完全满足天空盒的要求:我们有一个立方体,它有6个面,每个面需要一个贴图。上图中使用了几个夜空的图片给予玩家一种置身广袤宇宙的感觉,可实际上,他还是在一个小盒子之中。
网上有很多这样的天空盒的资源。这个网站就提供了很多。这些天空盒图像通常有下面的样式:
如果你把这6个面折叠到一个立方体中,你机会获得模拟了一个巨大的风景的立方体。有些资源所提供的天空盒比如这个例子6个图是连在一起的,你必须手工它们切割出来,不过大多数情况它们都是6个单独的纹理图像。
加载天空盒和之前我们加载立方体贴图的没什么大的不同。为了加载天空盒我们将使用下面的函数,它接收一个包含6个纹理文件路径的vector:
// Loads a cubemap texture from 6 individual texture faces, 从6张单独的纹理面加载立方体贴图
// 次序必须是:
// +X (right)
// -X (left)
// +Y (top)
// -Y (bottom)
// +Z (back)
// -Z (front)
GLuint OpenGLCubeMap::loadCubeMap()
{
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
for(GLuint i = 0; i < cubefaces.size(); i++)
{
Image* img = new Image();
img->initWithImageFile(cubefaces.at(i));
int width = img->getWidth();
int height = img->getHeight();
unsigned char* imgdata = img->getData();
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imgdata);
}
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);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
return textureID;
}
cubefaces.push_back("dispair-ridge_rt.png");
cubefaces.push_back("dispair-ridge_lf.png");
cubefaces.push_back("dispair-ridge_up.png");
cubefaces.push_back("dispair-ridge_dn.png");
cubefaces.push_back("dispair-ridge_bk.png");
cubefaces.push_back("dispair-ridge_ft.png");
立方体贴图用于给3D立方体帖上纹理,可以用立方体的位置作为纹理坐标进行采样。当一个立方体的中心位于原点(0,0,0)的时候,它的每一个位置向量也就是以原点为起点的方向向量。这个方向向量就是我们要得到的立方体某个位置的相应纹理值。出于这个理由,我们只需要提供位置向量,而无需纹理坐标。为了渲染天空盒,我们需要一组新着色器,它们不会太复杂。因为我们只有一个顶点属性,顶点着色器非常简单:
// skybox.vsh
attribute vec3 a_position;
varying vec3 v_textureCoord;
uniform mat4 projection;
uniform mat4 view;
void main()
{
gl_Position = projection * view * vec4(a_position, 1.0);
v_textureCoord = a_position;
}
我们把顶点属性中的位置向量作为纹理的方向向量,使用它们从立方体贴图采样纹理值。渲染天空盒现在很简单,我们有了一个立方体贴图纹理,我们简单绑定立方体贴图纹理,天空盒就自动地用天空盒的立方体贴图填充了。为了绘制天空盒,我们将把它作为场景中第一个绘制的物体并且关闭深度写入。这样天空盒才能成为所有其他物体的背景来绘制出来。
glDepthMask(GL_FALSE);
skyboxShader.Use();
// ... Set view and projection matrix
glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glDepthMask(GL_TRUE);
// ... Draw rest of the scene
glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
Mat4* view = cam->GetViewMatrix();
// x x x 0
// x x x 0
// x x x 0
// 0 0 0 1
view->m[12] = 0;
view->m[13] = 0;
view->m[14] = 0;
view->m[15] = 1;
view->m[3] = 0;
view->m[7] = 0;
view->m[11] = 0;
全部代码如下:
//
// OpenGLCubeMap.cpp
// shaderTest
// 立方体贴图实现天空盒
// Created by MacSBL on 2017/1/23.
//
//
#include "OpenGLCubeMap.h"
#ifndef GL_STENCIL_INDEX
#define GL_STENCIL_INDEX 0x1901
#endif
bool OpenGLCubeMap::init()
{
if (!Layer::init()) {
return false;
}
origin = Director::getInstance()->getVisibleOrigin();
vsize = Director::getInstance()->getVisibleSize();
GLfloat cubeVertices[] = {
// Back face
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // Bottom-left
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // bottom-left
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
// Front face
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, // top-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
// Left face
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-left
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right
// Right face
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
// Bottom face
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 1.0f, // top-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right
// Top face
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f // bottom-left
};
GLfloat 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
};
auto gprogram = GLProgram::createWithFilenames("box.vsh", "box.fsh");
this->setGLProgram(gprogram);
// box vao;
glGenVertexArrays(1, &boxvao);
glBindVertexArray(boxvao);
GLuint boxvbo;
glGenBuffers(1, &boxvbo);
glBindBuffer(GL_ARRAY_BUFFER, boxvbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), cubeVertices, GL_STATIC_DRAW);
//给vertex shader 的属性传值
GLuint posloc = glGetAttribLocation(gprogram->getProgram(), "a_position");
glEnableVertexAttribArray(posloc);
glVertexAttribPointer(posloc, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
GLuint texloc = glGetAttribLocation(gprogram->getProgram(), "a_texcoord");
glEnableVertexAttribArray(texloc);
glVertexAttribPointer(texloc, 2, GL_FLOAT, GL_FALSE, 5* sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glBindVertexArray(0);
//////////////////////////////////////////////////////////////////////////////////////////////////////
//skybox
skyboxprogram = new GLProgram();
skyboxprogram->initWithFilenames("skybox.vsh", "skybox.fsh");
skyboxprogram->link();
glGenVertexArrays(1, &skyvao);
glBindVertexArray(skyvao);
GLuint skyvbo;
glGenBuffers(1, &skyvbo);
glBindBuffer(GL_ARRAY_BUFFER, skyvbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), skyboxVertices, GL_STATIC_DRAW);
GLuint spos = skyboxprogram->getAttribLocation("a_position");
glEnableVertexAttribArray(spos);
glVertexAttribPointer(spos, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GLfloat), (GLvoid*)0);
glBindVertexArray(0);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// 开启深度测试
Director::getInstance()->setDepthTest(true);
glDepthFunc(GL_LESS);
//加载纹理
auto sprite = Sprite::create("container2.png");
boxTexture = sprite->getTexture()->getName();
cubefaces.push_back("dispair-ridge_rt.png");
cubefaces.push_back("dispair-ridge_lf.png");
cubefaces.push_back("dispair-ridge_up.png");
cubefaces.push_back("dispair-ridge_dn.png");
cubefaces.push_back("dispair-ridge_bk.png");
cubefaces.push_back("dispair-ridge_ft.png");
cubTexture = this->loadCubeMap();
cam = new MyCamera(Vec3(0, 0, 3));
//touch事件
auto elistener = EventListenerTouchOneByOne::create();
elistener->onTouchBegan = CC_CALLBACK_2(OpenGLCubeMap::onTouchBegan, this);
elistener->onTouchMoved = CC_CALLBACK_2(OpenGLCubeMap::onTouchMoved, this);
elistener->onTouchEnded = CC_CALLBACK_2(OpenGLCubeMap::onTouchEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(elistener, this);
return true;
}
// Loads a cubemap texture from 6 individual texture faces, 从6张单独的纹理面加载立方体贴图
// 次序必须是:
// +X (right)
// -X (left)
// +Y (top)
// -Y (bottom)
// +Z (back)
// -Z (front)
GLuint OpenGLCubeMap::loadCubeMap()
{
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
for(GLuint i = 0; i < cubefaces.size(); i++)
{
Image* img = new Image();
img->initWithImageFile(cubefaces.at(i));
int width = img->getWidth();
int height = img->getHeight();
unsigned char* imgdata = img->getData();
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imgdata);
}
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);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
return textureID;
}
void OpenGLCubeMap::visit(cocos2d::Renderer *render, const cocos2d::Mat4 &parentTransform, uint32_t parentflag)
{
Layer::visit(render, parentTransform, parentflag);
_command.init(_globalZOrder);
_command.func = CC_CALLBACK_0(OpenGLCubeMap::onDraw, this);
Director::getInstance()->getRenderer()->addCommand(&_command);
}
void OpenGLCubeMap::onDraw()
{
//先绘制天空盒
glDepthMask(GL_FALSE);
skyboxprogram->use();
Mat4* view = cam->GetViewMatrix();
//我们希望天空盒以玩家为中心,这样无论玩家移动了多远,天空盒都不会变近,这样就产生一种四周的环境真的非常大的印象。
//当前的视图矩阵对所有天空盒的位置进行了转转缩放和平移变换,所以玩家移动,立方体贴图也会跟着移动!我们打算移除视图矩阵的平移部分,这样移动就影响不到天空盒的位置向量了。
//在基础光照教程里我们提到过我们可以只用4X4矩阵的3×3部分去除平移。我们可以简单地将矩阵转为33矩阵再转回来,就能达到目标
// x x x 0
// x x x 0
// x x x 0
// 0 0 0 1
view->m[12] = 0;
view->m[13] = 0;
view->m[14] = 0;
view->m[15] = 1;
view->m[3] = 0;
view->m[7] = 0;
view->m[11] = 0;
Mat4* projection = new Mat4;
Mat4::createPerspective(cam->Zoom, vsize.width/vsize.height, 0.1, 100, projection);
glUniformMatrix4fv(skyboxprogram->getUniformLocation("view"), 1, GL_FALSE, view->m);
glUniformMatrix4fv(skyboxprogram->getUniformLocation("projection"), 1, GL_FALSE, projection->m);
//sky box
glBindVertexArray(skyvao);
glActiveTexture(GL_TEXTURE0);
glUniform1i(skyboxprogram->getUniformLocation("u_skyTexture"), 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glDepthMask(GL_TRUE);
/////////////////////////////////////////////////////////////////////////////////////////////////////
//再绘制箱子
auto program = this->getGLProgram();
program->use();
view = cam->GetViewMatrix();
//给vertex shader 的uniform 传值
glUniformMatrix4fv(glGetUniformLocation(program->getProgram(), "view"), 1, GL_FALSE,view->m);
glUniformMatrix4fv(program->getUniformLocation("projection"), 1, GL_FALSE, projection->m);
//box
glBindVertexArray(boxvao);
GL::bindTexture2DN(0, boxTexture);
glUniform1i(program->getUniformLocation("u_tex"), 0);
auto model = new Mat4();
model->translate(0, 0, 0);
glUniformMatrix4fv(glGetUniformLocation(program->getProgram(), "model"), 1, GL_FALSE, model->m);
glDrawArrays(GL_TRIANGLES, 0, 36);
/////////////////////////////////////////////////////////////////////////////////////////////////////
}
#pragma mark touch
bool OpenGLCubeMap::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *evt)
{
return true;
}
void OpenGLCubeMap::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *evt)
{
Vec2 curpos = touch->getLocationInView();
Vec2 prepos = touch->getPreviousLocationInView();
GLfloat dx = curpos.x - prepos.x;
GLfloat dy = curpos.y - prepos.y;
//移动摄像机
// GLfloat camspeed = 0.05f;
// if (curpos.y - prepos.y > 0) { //w
// cam->ProcessKeyboard(Cam_Move::FORWARD, camspeed);
// }else if (curpos.y - prepos.y < 0){ //s
// cam->ProcessKeyboard(Cam_Move::BACKWARD, camspeed);
// }
// else if (curpos.x - prepos.x < 0){ //a
// cam->ProcessKeyboard(Cam_Move::LEFT, camspeed);
// }else if (curpos.x - prepos.x > 0){ //d
// cam->ProcessKeyboard(Cam_Move::RIGHT, camspeed);
// }
//(3)旋转摄像机
cam->ProcessMouseMovement(dx, dy);
//(4)缩放
// if(fov >= 1 && fov <= 45){
// fov -= dx * camspeed;
// }
// if(fov <= 1){
// fov = 1;
// }
// if(fov >= 45){
// fov = 45;
// }
}
void OpenGLCubeMap::onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *evt)
{
}
下图展示了我们如何计算反射向量,然后使用这个向量去从一个立方体贴图中采样:
我们基于观察方向向量I和物体的法线向量N计算出反射向量R。我们可以使用GLSL的内建函数reflect来计算这个反射向量。最后向量R作为一个方向向量对立方体贴图进行索引/采样,返回一个环境的颜色值。最后的效果看起来就像物体反射了天空盒。
因为我们在场景中已经设置了一个天空盒,创建反射就不难了。我们改变一下箱子使用的那个片段着色器,给箱子一个反射属性:
//fragment shader
varying vec3 v_Position;
varying vec3 v_Normal;
uniform vec3 u_cameraPos;
uniform samplerCube u_cubetex;
void main()
{
//给木头箱子一个反射属性
// vec3 I = normalize(v_Position - u_cameraPos);
// vec3 R = reflect(I, normalize(v_Normal));
// gl_FragColor = textureCube(u_cubetex, R);
//折射
float ratio = 1.00 / 2.42;
vec3 I = normalize(v_Position - u_cameraPos);
vec3 R = refract(I, normalize(v_Normal), ratio);
gl_FragColor = textureCube(u_cubetex, R);
}
//vertex shader
attribute vec3 a_position;
attribute vec3 a_normal;
varying vec3 v_Position;
varying vec3 v_Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 transpos_inverse_model; //使用inverse和transpose函数生成正规矩阵,进行了一点线性代数操作移除了对法向量的错误缩放效果。
void main()
{
gl_Position = projection * view * model * vec4(a_position, 1.0);
// v_Normal = mat3(transpose(inverse(model))) * a_normal;
v_Normal = mat3(transpos_inverse_model) * a_normal;
v_Position = vec3(model * vec4(a_position, 1.0));
}
我们用了法线向量,所以我们打算使用一个法线矩阵(normal matrix)变换它们。Position
输出的向量是一个世界空间位置向量。顶点着色器输出的Position
用来在片段着色器计算观察方向向量。
因为我们使用法线,你还得更新顶点数据,更新属性指针。还要确保设置cameraPos
的uniform。
然后在渲染箱子前我们还得绑定立方体贴图纹理:
//box
glBindVertexArray(boxvao);
// GL::bindTexture2DN(0, boxTexture);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubTexture);
glUniform1i(program->getUniformLocation("u_cubetex"), 0);
auto model = new Mat4();
model->translate(0, -1, -1);
model->rotateY(45);
glUniformMatrix4fv(glGetUniformLocation(program->getProgram(), "model"), 1, GL_FALSE, model->m);
//使用inverse和transpose函数生成model的正规矩阵,进行了一点线性代数操作移除了对法向量的错误缩放效果。
auto transpose_inverse_model = model;
transpose_inverse_model->inverse();
transpose_inverse_model->transpose();
glUniformMatrix4fv(program->getUniformLocation("transpos_inverse_model"), 1, GL_FALSE, transpose_inverse_model->m);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
新的立方体数组:
GLfloat cubeVertices[] = {
// Positions // Normals
-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
};
折射是光线通过特定材质对光线方向的改变。我们通常看到像水一样的表面,光线并不是直接通过的,而是让光线弯曲了一点。它看起来像你把半只手伸进水里的效果。
折射遵守斯涅尔定律,使用环境贴图看起来就像这样:
我们有个观察向量I,一个法线向量N,这次折射向量是R。就像你所看到的那样,观察向量的方向有轻微弯曲。弯曲的向量R随后用来从立方体贴图上采样。
折射可以通过GLSL的内建函数refract来实现,除此之外还需要一个法线向量,一个观察方向和一个两种材质之间的折射指数。
折射指数决定了一个材质上光线扭曲的数量,每个材质都有自己的折射指数。下表是常见的折射指数:
材质 | 折射指数 |
---|---|
空气 | 1.00 |
水 | 1.33 |
冰 | 1.309 |
玻璃 | 1.52 |
宝石 | 2.42 |
我们使用这些折射指数来计算光线通过两个材质的比率。我们已经绑定了立方体贴图,提供了定点数据,设置了摄像机位置的uniform。现在只需要改变片段着色器:
void main()
{
//折射
float ratio = 1.00 / 2.42;
vec3 I = normalize(v_Position - u_cameraPos);
vec3 R = refract(I, normalize(v_Normal), ratio);
gl_FragColor = textureCube(u_cubetex, R);
}