我们前面学过纹理贴图,我们可以用6个2D纹理的纹理构建成立方体,而立方体贴图本身就是包含6个2D纹理的纹理,它优势在于可以通过一个方向向量来进行索引/采样。只要在立方体贴图的中心点,就能使用立方体的实际位置向量来对立方体贴图进行采样。
创建立方体贴图
与2D纹理区别不大,创建立方体贴图同样是创建纹理,只是我们的glBindTexture的方法的参数要变为GLES20.GL_TEXTURE_CUBE_MAP,然后同样要处理环绕和过滤方式。与2D纹理不同的是,它需要用6张图片来分别设置立方体的六个面,同样是使用GLUtils.texImage2D方法,它的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 | 前 |
我们把创建立方体贴图的同样封装一下,代码如下
/**
* 立方体贴图
* @param context context
* @param resIds 贴图集合,顺序是:
* <ul><li>右{@link GLES20#GL_TEXTURE_CUBE_MAP_POSITIVE_X}</li>
* <li>左{@link GLES20#GL_TEXTURE_CUBE_MAP_NEGATIVE_X}</li>
* <li>上{@link GLES20#GL_TEXTURE_CUBE_MAP_POSITIVE_Y}</li>
* <li>下{@link GLES20#GL_TEXTURE_CUBE_MAP_NEGATIVE_Y}</li>
* <li>后{@link GLES20#GL_TEXTURE_CUBE_MAP_POSITIVE_Z}</li>
* <li>前{@link GLES20#GL_TEXTURE_CUBE_MAP_NEGATIVE_Z}</li></ul>
* @return int
*/
public static int createTextureCube(Context context, int[] resIds) {
if (resIds != null && resIds.length >= 6) {
int[] texture = new int[1];
//生成纹理
GLES20.glGenTextures(1, texture, 0);
checkGlError("glGenTexture");
//生成纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, texture[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
if (OpenGLVersion > 2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES30.GL_TEXTURE_WRAP_R, GLES20.GL_CLAMP_TO_EDGE);
}
Bitmap bitmap;
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
for (int i = 0; i < resIds.length; i++) {
bitmap = BitmapFactory.decodeResource(context.getResources(),
resIds[i], options);
if (bitmap == null) {
LogUtil.w("Resource ID " + resIds[i] + " could not be decoded.");
GLES20.glDeleteTextures(1, texture, 0);
return 0;
}
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, bitmap, 0);
bitmap.recycle();
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return texture[0];
}
return 0;
}
着色器的代码同样需要修改,我们需要使用samplerCube接收我们的立方体贴图,使用textureCube来生成立方体贴图,同样的我们需要定义六个面的顶点坐标和纹理坐标。片段着色器的代码如下
precision mediump float;
varying vec3 TexCoord; // 代表3D纹理坐标的方向向量
uniform samplerCube skybox; // 立方体贴图的纹理采样器
void main() {
gl_FragColor = textureCube(skybox, TexCoord);
}
天空盒
想象一下我们创建好一个立方体贴图,我们把视角设置在立方体贴图的中间,这时我们移动视角方向,就像是在一个立方体的房间内向各个方向观望。我们可以在这个网站(http://www.custommapmakers.org/skyboxes.php)上找到各种素材,暂时我们使用下面的六张图片实现立方体贴图,
然后绘制一个盒子放到我们的视角内,关键的代码如下
@Override
public void onDrawFrame(GL10 gl) {
super.onDrawFrame(gl);
drawTexture();
GLES20.glDepthFunc(GLES20.GL_LEQUAL);
drawSkyBox();
GLES20.glDepthFunc(GLES20.GL_LESS);
}
此时的效果如下图,
我们想要移动我们的视角,使用触摸事件的话有些繁琐了,这里我们使用手机自带的传感器中的旋转矢量功能,我们移动手机就能移动在天空盒中移动视角。在onSensorChanged中使用getRotationMatrixFromVector将旋转矢量转换为旋转矩阵,随后传入我们的Renderer中计算即可。源码地址https://github.com/jklwan/OpenGLProject/blob/master/sample/src/main/java/com/chends/opengl/renderer/advanced/opengl/CubeMapsRenderer.java,可以自己测试效果。本章对应文档https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/06%20Cubemaps/。