テクスチャに対応する

そろそろ単色の3D物体に飽きてきたので、テクスチャに対応しましょう。

Texture2Dクラスを作る

Texture2Dクラスを作ります。URLから画像を読み込んでWebGLテクスチャを作る場合と、1x1ピクセルの単色のWebGLテクスチャを作る場合に対応します。後者は、テクスチャ画像が読込み途中などの場合に、シェーダーにデフォルトのテクスチャとして供給するために使います。

import { System } from "./System.js"; import { SamplerMagFilter, SamplerMinFilter, SamplerWrapMode } from "./definitions.js"; import { Vector4 } from "./math/Vector4.js"; export type TextureParameters = { magFilter: SamplerMagFilter; minFilter: SamplerMinFilter; wrapS: SamplerWrapMode; wrapT: SamplerWrapMode; generateMipmap: boolean; }; export class Texture2D { private _width = 0; private _height = 0; private _texture: WebGLTexture | null = null; constructor() { } get width() { return this._width; } get height() { return this._height; } loadByUrl(url: string, param: TextureParameters) { const image = new Image(); image.onload = () => { this._width = image.width; this._height = image.height; const gl = System.gl; const texture = gl.createTexture(); if (texture == null) { throw new Error('Failed to create texture.'); } this._texture = texture; gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); if (param.generateMipmap) { gl.generateMipmap(gl.TEXTURE_2D); } gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, param.wrapS); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, param.wrapT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param.magFilter); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param.minFilter); }; image.src = url; } load1x1(color: Vector4, param: TextureParameters) { this._width = 1; this._height = 1; const gl = System.gl; const texture = gl.createTexture(); if (texture == null) { throw new Error('Failed to create texture.'); } gl.bindTexture(gl.TEXTURE_2D, texture); const pixel = new Uint8Array([color.x * 255, color.y * 255, color.z * 255, color.w * 255]); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixel); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, param.wrapS); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, param.wrapT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param.magFilter); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param.minFilter); this._texture = texture; } isReady() { return this._texture != null; } bind() { const gl = System.gl; gl.bindTexture(gl.TEXTURE_2D, this._texture); } destroy() { const gl = System.gl; gl.deleteTexture(this._texture); } }

definitions.tsに以下の定義も追加します。

// definitions.ts export const SamplerMagFilter = { Nearest: 9728, Linear: 9729, } as const; export type SamplerMagFilter = typeof SamplerMagFilter[keyof typeof SamplerMagFilter]; export const SamplerMinFilter = { Nearest: 9728, Linear: 9729, NearestMipmapNearest: 9984, LinearMipmapNearest: 9985, NearestMipmapLinear: 9986, LinearMipmapLinear: 9987, } as const; export type SamplerMinFilter = typeof SamplerMinFilter[keyof typeof SamplerMinFilter]; export const SamplerWrapMode = { ClampToEdge: 33071, MirroredRepeat: 33648, Repeat: 10497, } as const; export type SamplerWrapMode = typeof SamplerWrapMode[keyof typeof SamplerWrapMode];

Materialクラスの中のシェーダーも、テクスチャを使うように書き換えます。

image.png

Materalクラスのコンストラクタの最後で、1x1ピクセルの白色のテクスチャを1つ作成します。これは、テクスチャの準備が間に合わなかった場合にシェーダーに供給するデフォルトテクスチャです。

image.png

テクスチャの設定の処理です。

image.png

Gltf2Importerクラスに、glTFからテクスチャを取り出す関数を追加します。

// Gltf2Importer.ts private static _loadTexture(json: Gltf2, basePath: string) { const textures: Texture2D[] = []; for (let textureJson of json.textures) { const imageJson = json.images[textureJson.source!]; // get sampler info const textureParameters: TextureParameters = { magFilter: SamplerMagFilter.Linear, minFilter: SamplerMinFilter.LinearMipmapLinear, wrapS: SamplerWrapMode.Repeat, wrapT: SamplerWrapMode.Repeat, generateMipmap: true, } if (textureJson.sampler != null) { const sampler = json.samplers[textureJson.sampler] textureParameters.magFilter = sampler.magFilter as SamplerMagFilter ?? SamplerMagFilter.Linear; textureParameters.minFilter = sampler.minFilter as SamplerMinFilter ?? SamplerMinFilter.LinearMipmapLinear; } const tex = new Texture2D(); if (imageJson.uri != null) { tex.loadByUrl(basePath + imageJson.uri, textureParameters); } textures.push(tex); } return textures; }

glTFにおいては、Materialの部分にテクスチャ番号の指定が入っています。

image.png

また、glTFからテクスチャ座標のデータも取るようにします。

image.png

最後に

これで、無事にテクスチャ付きのモデルを表示することができました。

image.png

ここまでの作業は、リポジトリのこちら から参照できます。