zoukankan      html  css  js  c++  java
  • WebGL学习之纹理盒

    原文地址:WebGL学习之纹理盒
    我们之前已经学习过二维纹理 gl.TEXTURE_2D,而且还使用它实现了各种效果。但还有一种立方体纹理 gl.TEXTURE_CUBE_MAP,它包含了6个纹理代表立方体的6个面。不像常规的纹理坐标有2个纬度,立方体纹理使用法向量,换句话说三维方向。本节实现的demo请看 天空盒
    sky box

    根据法向量的朝向选取立方体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//前
    

    环境贴图

    其实我们更应该把cube map叫作纹理盒,通常纹理盒不是给立方体设置纹理用的,设置立方体纹理的标准用法其实是使用二维贴图,那么纹理盒用来做什么的呢?纹理盒最常见的用法是用来做环境贴图。在百度和google地图中的3D街景就是环境贴图应用的一个例子。

    纹理

    下面是6张红色峡谷图片
    canyon

    canyon

    将以上尺寸为512x512的图片填充到立方体的每个面,以下就是纹理的创建加载过程

    // 创建纹理。
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
     
    const faceInfos = [
      {
        target: gl.TEXTURE_CUBE_MAP_POSITIVE_X, 
        url: '/img/sorbin_rt.jpg',
      },
      {
        target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 
        url: '/img/sorbin_lf.jpg',
      },
      {
        target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 
        url: '/img/sorbin_up.jpg',
      },
      {
        target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 
        url: '/img/sorbin_dn.jpg',
      },
      {
        target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 
        url: '/img/sorbin_bk.jpg',
      },
      {
        target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 
        url: '/img/sorbin_ft.jpg',
      },
    ];
    faceInfos.forEach((faceInfo) => {
      const {target, url} = faceInfo;
      // 上传画布到立方体贴图的每个面
      const level = 0;
      const format = gl.RGBA;
      const width = 512;
      const height = 512;
      const type = gl.UNSIGNED_BYTE;
      // 设置每个面,使其立即可渲染
      gl.texImage2D(target, level, format, width, height, 0, format, type, null);
     
      // 异步加载图片
      const image = new Image();
      image.src = url;
      image.onload = function() {
        // 图片加载完成将其拷贝到纹理
        gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
        gl.texImage2D(target, level, internalFormat, format, type, image);
        gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
      };
    });
    gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
    

    法向量

    标准立方体法向量 和 纹理盒法向量的区别
    立方体向量

    3D立方体使用纹理盒有一个巨大的好处就是不需要额外指定纹理坐标。只要盒子是被放置在世界坐标系的原点,盒子本身的坐标就可以作为纹理坐标使用,因为在3D世界中位置本身就是一个向量,表示一个方向,我们要的就是这个方向。

    所以顶点着色器非常简单

    attribute vec4 a_position;
    uniform mat4 u_vpMatrix;
    varying vec3 v_normal;
    
    void main() {
        gl_Position = u_vpMatrix * a_position;
        //因为位置是以几何中心为原点的,可以用顶点坐标作为法向量
        v_normal = normalize(a_position.xyz);
    } 
    

    片段着色器中我们需要用samplerCube 代替 sampler2DtextureCube代替texture2DtextureCube 需要vec3类型的向量。 法向量从顶点着色器传递过来经过了插值处理,需要重新单位化。

    precision mediump float; // 从顶点着色器传入。
    varying vec3 v_normal; // 纹理。
    uniform samplerCube u_texture; 
    
    void main() {   
      gl_FragColor = textureCube(u_texture, normalize(v_normal));
    }
    

    实现

    运行后得到如下的效果,很明显就能看出是个立方体,并不是我们想要的360度环绕的3D场景。
    纹理盒

    其实我们只需要将相机位置置于原点(0,0,0),同时lookAt向其中的一个面就可以了。但是在原点有个问题,如果要旋转查看场景怎么办?我们可以通过旋转相机的位置,这其实就相当于立方体旋转,同时我们不需要矩阵位移相关的信息,只需要方向相关的信息就好了。同时还可以禁止写入深度缓存,造成背景在很远的假象,让效果更加真实。

    const viewPosition = new Vector3([0,0,1]);//相机位置
    const lookAt = [0, 0, 0];//原点
    
    //相机绕y轴旋转
    cameraMatrix.rotate(0.2,0,1,0);
    viewPoint = cameraMatrix.multiplyVector3(viewPosition);
    vpMatrix.setPerspective( 30, canvas.width / canvas.height, 0.1, 5 );
    vpMatrix.lookAt(...viewPoint.elements, ...lookAt, 0, 1, 0);
    
    //重置位移
    vpMatrix.elements[12] = 0;
    vpMatrix.elements[13] = 0;
    vpMatrix.elements[14] = 0;
    
    // 禁止写入深度缓存,造成背景在很远的假象
    gl.depthMask(false);
    

    环境纹理映射

    环境贴图还有个更通俗的叫法-天空盒。接着我们还要实现一个非常帅气的效果,在天空盒三维场景中,让其中的物体反射场景周围的着色。这个操作就叫做环境纹理映射(environment mapping)。

    反射

    如果物体的表面像光滑的镜子,那么我们就能看到物体反射出天空和周围的景色。反射的原理非常简单,那就是使用反射公式映射纹理盒对应的纹素:
    纹理映射

    相机位置(观察点)和 物体顶点的位置,顶点位置又包含着法线信息,通过GLSL的reflect函数就可以非常容易的计算反射向量R,进而确定看到的是哪一块表面的着色。

    实现

    我们就在天空盒下面增加一个镜面立方体,那就需要增加一对着色器,首先顶点着色器需要增加法线,mvp矩阵

    attribute vec4 a_position;
    attribute vec4 a_normal;
    uniform mat4 u_vpMatrix;
    uniform mat4 u_modelMatrix;
    varying vec3 v_position;
    varying vec3 v_normal;
    
    void main() {
        v_position = (u_modelMatrix * a_position).xyz;
        v_normal = vec3(u_modelMatrix * a_normal);
        gl_Position = u_vpMatrix * u_modelMatrix * a_position;
    }
    

    片元着色器则需要添加相机位置,纹理以及顶点着色器传递过来的法线和顶点位置

    precision highp float;
    varying vec3 v_position;
    varying vec3 v_normal;
    uniform samplerCube u_texture;
    uniform vec3 u_viewPosition;
    
    void main() {
        vec3 normal = normalize(v_normal);
        vec3 eyeToSurfaceDir = normalize(v_position - u_viewPosition);
        vec3 direction = reflect(eyeToSurfaceDir,normal);
        gl_FragColor = textureCube(u_texture, direction);
    }
    

    这样我们绘制的时候就要轮流切换着色器program

    function draw(){
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
        //天空盒
        gl.useProgram(program.program);
    		//绘制天空盒
      	//...
    
        //立方体
        gl.useProgram(cProgram.program);
    		//绘制立方体
      	//...
    
        requestAnimationFrame(draw);
    }
    

    最后实现如下效果,demo情况 天空盒
    天空盒

    后记

    其实纹理盒除了可以做环境贴图,还可以结合光照,阴影贴图作出很多酷炫的效果。

  • 相关阅读:
    共享纸巾更换主板代码分析 共享纸巾主板更换后的对接代码
    Python Django Ajax 传递列表数据
    Python Django migrate 报错解决办法
    Python 创建字典的多种方式
    Python 两个list合并成一个字典
    Python 正则 re.sub替换
    python Django Ajax基础
    Python Django 获取表单数据的三种方式
    python Django html 模板循环条件
    Python Django ORM 字段类型、参数、外键操作
  • 原文地址:https://www.cnblogs.com/edwardloveyou/p/10818894.html
Copyright © 2011-2022 走看看