Threejs, 纹理是怎么映射到模型上的

2024-06-13  本文已影响0人  BitMonkey

下面代码是通过 threejs ,绘制一个地球的程序。

  // 加载纹理
  const autumnTexture = new TextureLoader().load(autumn)
  // 渲染器
  const renderer = new WebGL1Renderer({ canvas })
  // 透视投影相机
  const camera = new PerspectiveCamera(60, canvas.width / canvas.height, 1, 2000)
  camera.position.set(
    settings.cameraX,
    settings.cameraY,
    7
  );
  camera.lookAt(0, 0, 0)
  
  camera.updateMatrixWorld();
  // 场景
  const scene = new Scene()
  {
    // 光源
    const color = 0xffffff
    const intensity = 1
    const light = new DirectionalLight(color, intensity)
    light.position.set(-1, 2, 4)

    const ambient = new AmbientLight(0xffffff, 0.2);
    scene.add(ambient)
    scene.add(light)
  }

  // 球
  const sphereGeometry = new SphereGeometry(1.5, 64, 32)
  // 材质
  const material = new MeshBasicMaterial({
    map: autumnTexture
  })
  const mesh = new Mesh(sphereGeometry, material);

  mesh.position.set(2, 3, 4);
  scene.add(mesh)
   renderer.render(scene, camera)
image.png

执行上面的代码,就会在画布上生成一个地球。现在我们就来分析下threejs 是怎么一步一步将地球纹理影射到球体上的。

当程序运行执行 WebGLRender 的 render 函数,首先我们就以渲染场景的 球为例,会根据球体配置的 材质去生成一个 WebGLProgram 对象,并根据传入Mesh 的材质去内置材质中获取相应的 着色器代码(排除 ShaderMaterial 等自定义材质),根据材质设置相应的着色器变量。
当成功编译并创建了 WebGLProgram 对象后,就会通过程序对象生成一个 WebGLUniform 对象,通过WebGLUniform 的构造函数可以知道,通过访问当前程序对象的激活的uniforms 变量,获取所有可访问的 uniform, 并通过 parseUniform 方法生成相应的 unifrom 的setter 函数。(WebGLUniform 对象有一个seq 变量,存储的是当前函数对象的所有SingleUniform or PureArrayUniform 的实例数组)

  class WebGLUniform {
    constructor( gl, program ) {

        this.seq = [];
        this.map = {};

        const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS );

        for ( let i = 0; i < n; ++ i ) {

            const info = gl.getActiveUniform( program, i ),
                addr = gl.getUniformLocation( program, info.name );

            parseUniform( info, addr, this );

        }

    }
}

当执行 WebGLRender 的setProgram 方法,会调用 WebGLUniform 的upload 方法将所有的uniforms 变量写入着色器,至此所有 的准备功能完成。
在upload 方法中会根据相应 的setter 写入参数。

由于我们这里讨论的是threejs 怎么将纹理映射上模型的,所以我们重点关注的是纹理的写入。
当threejs 在上一步生成纹理写入函数 setValueT1

// case 0x8b5e: // SAMPLER_2D
// case 0x8d66: // SAMPLER_EXTERNAL_OES
// case 0x8dca: // INT_SAMPLER_2D
// case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
// case 0x8b62: // SAMPLER_2D_SHADOW
// return setValueT1;
// Single texture (2D / Cube)

// 执行这个setValueT1 函数会 执行WebGLTexture 对象的 setTexture2D 方法,将纹理 单元绑定到当前程序对象上()
// state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot );
function setValueT1( gl, v, textures ) {

    const cache = this.cache;
    const unit = textures.allocateTextureUnit();

    if ( cache[ 0 ] !== unit ) {

        gl.uniform1i( this.addr, unit );
        cache[ 0 ] = unit;

    }

    textures.setTexture2D( v || emptyTexture, unit );

}

当第一次执行着色器渲染纹理 ,WebGLTexture 中 uploadTexture 会将配置在texture 中的数据调用 state.texImage2D 方法(等价执行gl.textImage2D方法),将数据写入相应 的纹理单元。至此所有纹理配置结束。正式进入场景的渲染,调用 gl.drawArray or gl.drawElement 方法执行着色器。

上一篇下一篇

猜你喜欢

热点阅读