首页 > 代码库 > 用球体模拟天空

用球体模拟天空

之前说到可以用球体作为Sky Dome模拟天空,那么就来说一说其中的细节.

Sky Dome就是天空穹顶,是一种在三维场景中模拟天空的方法,用Sky Dome模拟的天空较Sky Box更为逼真,对应用Sky Box的场景采用雾效果很容易穿帮,而Sky Dome不会,因为Sky Box是方形从视点到各个顶点的距离不相等,Sky Dome则是球体,把视点设置为球体中心则到各个顶点的距离相等,雾效果的可见程度与视点到顶点的距离有直接关系.

那么来看看这么用之前的球体模型模拟Sky Dome吧.

之前已经给球体建了个模,现在的问题是怎么把天空贴图贴到球体上去,这将会用到一种新的贴图方法Cubemap以及着色器. 先看一下效果如何:




嗯,效果就是这样.现在看下具体是怎么实现的:

首先要了解Cubemap的原理,具体的教程网上有很多,可以搜索一些来看,这边讨论一下天空穹顶的实现细节.

先准备6张图,Cubemap需要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?

这6张图各自在四个方向上必须与相邻的四张贴图是连续的,这样才不会导致穿帮. 使用以下方法创建Cubemap:

bool loadCubemapTexture(const char* xpos,const char* xneg,
		const char* ypos,const char* yneg,
		const char* zpos,const char* zneg,
		GLuint& id) {
	BmpLoader bmpXpos,bmpXneg,bmpYpos,bmpYneg,bmpZpos,bmpZneg;

	if(!bmpXpos.loadBitmap((char*)xpos)||
			!bmpXneg.loadBitmap((char*)xneg)||
			!bmpYpos.loadBitmap((char*)ypos)||
			!bmpYneg.loadBitmap((char*)yneg)||
			!bmpZpos.loadBitmap((char*)zpos)||
			!bmpZneg.loadBitmap((char*)zneg)) {
		printf("loadBitmap error\n");
		return false;
	}

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

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

	glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X,0,GL_RGB,
			bmpXpos.bitInfo->biWidth,bmpXpos.bitInfo->biHeight
			,0,GL_RGB,GL_UNSIGNED_BYTE,bmpXpos.image);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X,0,GL_RGB,
			bmpXneg.bitInfo->biWidth,bmpXneg.bitInfo->biHeight
			,0,GL_RGB,GL_UNSIGNED_BYTE,bmpXneg.image);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y,0,GL_RGB,
			bmpYpos.bitInfo->biWidth,bmpYpos.bitInfo->biHeight
			,0,GL_RGB,GL_UNSIGNED_BYTE,bmpYpos.image);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,0,GL_RGB,
			bmpYneg.bitInfo->biWidth,bmpYneg.bitInfo->biHeight
			,0,GL_RGB,GL_UNSIGNED_BYTE,bmpYneg.image);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z,0,GL_RGB,
			bmpZpos.bitInfo->biWidth,bmpZpos.bitInfo->biHeight
			,0,GL_RGB,GL_UNSIGNED_BYTE,bmpZpos.image);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,0,GL_RGB,
			bmpZneg.bitInfo->biWidth,bmpZneg.bitInfo->biHeight
			,0,GL_RGB,GL_UNSIGNED_BYTE,bmpZneg.image);

	return true;
}

注意从此开始这6张贴图属于同一个Cubemap对象,它们是一个整体. 

	initCubemapTexture(YZ_PATH,YZ_PATH,XZ_PATH,XZ_PATH,
			XY_PATH,XY_PATH,CUBE_MAP_TEXTURE);

现在这个Cubemap对象的名字叫做CUBE_MAP_TEXTURE.

使用glBindTexture(GL_TEXTURE_CUBE_MAP,CUBE_MAP_TEXTURE)我们就可以使用这个纹理对象了.等一下,纹理有了但是还没有纹理坐标.

让我们看一下OpenGL官方网站是怎么说的:

The texture coordinates for cubemaps are 3D vector directions. These are conceptually directions from within the cube defined by the cubemap, pointing in a particular direction.

嗯,Cubemap的纹理坐标是一个3d向量,并非是传统纹理坐标的2d向量, 应该是从球体中心到球体表面上的向量,如图所示:

看明白了吧,那么来看一下Shader又是怎么写的:
Vertex Shader:
varying vec3 texCoord;

void main() {
	texCoord = vec3(gl_Vertex.x, gl_Vertex.y, gl_Vertex.z);
	texCoord = normalize(texCoord);
	gl_Position = ftransform();
}

Fragment Shader:
uniform samplerCube texCube;

varying vec3 texCoord;

void main() {
    gl_FragColor = textureCube(texCube, texCoord);
}

接着编写渲染代码:
	useShader(shaderTex);
	glBindTexture(GL_TEXTURE_CUBE_MAP,CUBE_MAP_TEXTURE);

	glPushMatrix();
	glTranslatef(0,-10,-200);
	glScalef(100,100,100);
	sphere->render();
	glPopMatrix();

先使用坐标轴贴图,让我们看看效果对不对:


呦,效果不对,xy轴反了,怎么办?修改Vertex Shader!
varying vec3 texCoord;

void main() {
	texCoord = vec3(gl_Vertex.x, -gl_Vertex.y, -gl_Vertex.z);
	texCoord = normalize(texCoord);
	gl_Position = ftransform();
}


嗯,这样就对了,然后换上天空贴图就行了. 还在等什么,赶紧试一试呗!

用球体模拟天空