From 56a9a407adffa0ac2e7ba41b69ca98f6d27e6841 Mon Sep 17 00:00:00 2001 From: pissang Date: Fri, 17 Feb 2023 17:19:32 +0800 Subject: [PATCH] support 3d texture --- src/Shader.ts | 5 +- src/Texture2D.ts | 30 ++++------ src/Texture2DArray.ts | 111 ++++++++++++++++++++++++++++++++++++ src/Texture3D.ts | 48 ++++++++++++++++ src/TextureCube.ts | 32 ++++------- src/core/vendor.ts | 21 +++++++ src/gl/GLTexture.ts | 129 +++++++++++++++++++++++++++++++----------- 7 files changed, 303 insertions(+), 73 deletions(-) create mode 100644 src/Texture2DArray.ts create mode 100644 src/Texture3D.ts diff --git a/src/Shader.ts b/src/Shader.ts index 4a8a6672..4e441422 100644 --- a/src/Shader.ts +++ b/src/Shader.ts @@ -18,6 +18,7 @@ export type UniformType = | 'bool' | 'int' | 'sampler2D' + | 'sampler2DArray' | 'samplerCube' | 'float' | 'vec2' @@ -720,8 +721,8 @@ export class Shader< static Fragment = FragmentShader; } -export function isTextureUniform(uniform: { type: UniformType | Record }) { - return uniform.type === 'sampler2D' || uniform.type === 'samplerCube'; +export function isTextureUniform(uniform: { type: UniformType }) { + return uniform.type.startsWith('sampler'); } export default Shader; diff --git a/src/Texture2D.ts b/src/Texture2D.ts index a1f2b811..e24f8a3b 100644 --- a/src/Texture2D.ts +++ b/src/Texture2D.ts @@ -1,7 +1,5 @@ import Texture, { TextureImageSource, TextureOpts, TexturePixelSource } from './Texture'; -import * as constants from './core/constants'; import vendor from './core/vendor'; -import Renderer from './Renderer'; export interface Texture2DData { image?: TextureImageSource; @@ -23,7 +21,6 @@ export interface Texture2DOpts extends TextureOpts, Texture2DData { * }, ....] */ mipmaps?: Texture2DData[]; - convertToPOT?: boolean; } /** * @example @@ -104,25 +101,20 @@ class Texture2D extends Texture { } load(src: string, crossOrigin?: string) { - const image = vendor.createImage(); - if (crossOrigin) { - image.crossOrigin = crossOrigin; - } - image.onload = () => { - this.dirty(); - this.trigger('load', this); - }; - image.onerror = () => { - this.trigger('error', this); - }; - - image.src = src; - this.image = image; + this.image = vendor.loadImage( + src, + crossOrigin, + () => { + this.dirty(); + this.trigger('load', this); + }, + () => { + this.trigger('error', this); + } + ); return this; } } -Texture2D.prototype.convertToPOT = false; - export default Texture2D; diff --git a/src/Texture2DArray.ts b/src/Texture2DArray.ts new file mode 100644 index 00000000..3a2e9ca9 --- /dev/null +++ b/src/Texture2DArray.ts @@ -0,0 +1,111 @@ +import Texture, { TextureImageSource, TextureOpts, TexturePixelSource } from './Texture'; +import vendor from './core/vendor'; + +export type Texture2DArrayData = { + image?: TextureImageSource[]; + pixels?: TexturePixelSource[]; +}; + +export interface Texture2DArrayOpts extends TextureOpts { + /** + * @example + * [{ + * image: mipmap0, + * pixels: null + * }, { + * image: mipmap1, + * pixels: null + * }, ....] + */ + mipmaps?: Texture2DArrayData[]; +} + +interface Texture2DArray extends Omit {} +class Texture2DArray extends Texture { + readonly textureType = 'texture2DArray'; + + private _image: TextureImageSource[] = []; + + constructor(opts?: Partial) { + super(opts); + } + + hasImage() { + return this._image.length > 0; + } + + get image(): TextureImageSource[] { + return this._image; + } + set image(val: TextureImageSource[]) { + if (this._image !== val) { + this._image = val; + this.dirty(); + } + } + get width() { + if (this.hasImage()) { + return this.image[0].width; + } + return this._width; + } + set width(value: number) { + if (this.hasImage()) { + console.warn("Texture from image can't set width"); + } else { + if (this._width !== value) { + this.dirty(); + } + this._width = value; + } + } + get height() { + if (this.hasImage()) { + return this.image[0].height; + } + return this._height; + } + set height(value: number) { + if (this.hasImage()) { + console.warn("Texture from image can't set height"); + } else { + if (this._height !== value) { + this.dirty(); + } + this._height = value; + } + } + + isRenderable() { + if (this.hasImage()) { + return this.image[0].width > 0 && this.image[0].height > 0; + } else { + return !!(this.width && this.height); + } + } + + load(srcList: string[], crossOrigin?: string) { + let loading = 0; + this.image = []; + srcList.forEach((src, idx) => { + this.image![idx] = vendor.loadImage( + src, + crossOrigin, + () => { + if (--loading === 0) { + this.dirty(); + this.trigger('success', this); + } + }, + () => { + loading--; + } + ); + loading++; + }); + + return this; + } +} + +export default Texture2DArray; diff --git a/src/Texture3D.ts b/src/Texture3D.ts new file mode 100644 index 00000000..7cf3b296 --- /dev/null +++ b/src/Texture3D.ts @@ -0,0 +1,48 @@ +import Texture, { TextureOpts, TexturePixelSource } from './Texture'; + +export interface Texture3DData { + /** + * Pixels data. Will be ignored if image is set. + */ + pixels?: TexturePixelSource; +} + +export interface Texture3DOpts extends TextureOpts, Texture3DData {} + +interface Texture3D extends Texture3DOpts {} +class Texture3D extends Texture { + readonly textureType = 'texture3D'; + + private _depth: number = 512; + + pixels?: TexturePixelSource; + + constructor(opts?: TextureOpts) { + super(opts); + } + + get width() { + return this._width; + } + set width(value: number) { + this._width = value; + } + get height() { + return this._height; + } + set height(value: number) { + this._height = value; + } + get depth() { + return this._depth; + } + set depth(value: number) { + this._depth = value; + } + + isRenderable() { + return this.width > 0 && this.height > 0 && this.depth > 0; + } +} + +export default Texture3D; diff --git a/src/TextureCube.ts b/src/TextureCube.ts index 1664dbec..b9fa385b 100644 --- a/src/TextureCube.ts +++ b/src/TextureCube.ts @@ -1,9 +1,6 @@ import Texture, { TextureImageSource, TextureOpts, TexturePixelSource } from './Texture'; -import * as constants from './core/constants'; import * as mathUtil from './math/util'; import vendor from './core/vendor'; -import Renderer from './Renderer'; -import { GLEnum } from './core/type'; import { keys } from './core/util'; const isPowerOfTwo = mathUtil.isPowerOfTwo; @@ -125,25 +122,20 @@ class TextureCube extends Texture { let loading = 0; this.image = {} as Record; (keys(imageList) as CubeTarget[]).forEach((target) => { - const src = imageList[target]; - const image = vendor.createImage(); - if (crossOrigin) { - image.crossOrigin = crossOrigin; - } - image.onload = () => { - loading--; - if (loading === 0) { - this.dirty(); - this.trigger('success', this); + this.image![target] = vendor.loadImage( + imageList[target], + crossOrigin, + () => { + if (--loading === 0) { + this.dirty(); + this.trigger('success', this); + } + }, + () => { + loading--; } - }; - image.onerror = function () { - loading--; - }; - + ); loading++; - image.src = src; - this.image![target] = image; }); return this; diff --git a/src/core/vendor.ts b/src/core/vendor.ts index eab7f525..3422c410 100644 --- a/src/core/vendor.ts +++ b/src/core/vendor.ts @@ -8,6 +8,12 @@ interface Vendor { createCanvas: () => HTMLCanvasElement; createBlankCanvas: (color: string) => HTMLCanvasElement; createImage: () => HTMLImageElement; + loadImage: ( + src: string, + crossOrigin?: string, + onload?: () => void, + onerror?: () => void + ) => HTMLImageElement; request: { get: typeof get; }; @@ -68,6 +74,21 @@ vendor.createImage = function () { return new g.Image(); }; +vendor.loadImage = function (src, crossOrigin, onload, onerror) { + const image = vendor.createImage(); + if (crossOrigin) { + image.crossOrigin = crossOrigin; + } + image.onload = function () { + onload && onload(); + }; + image.onerror = function () { + onerror && onerror(); + }; + image.src = src; + return image; +}; + vendor.request = { get }; diff --git a/src/gl/GLTexture.ts b/src/gl/GLTexture.ts index 202f28f9..1e049e4c 100644 --- a/src/gl/GLTexture.ts +++ b/src/gl/GLTexture.ts @@ -2,10 +2,21 @@ import * as constants from '../core/constants'; import { GLEnum } from '../core/type'; import { getPossiblelInternalFormat } from '../Texture'; import Texture2D, { Texture2DData } from '../Texture2D'; +import Texture2DArray, { Texture2DArrayData } from '../Texture2DArray'; +import Texture3D, { Texture3DData } from '../Texture3D'; import TextureCube, { cubeTargets, TextureCubeData } from '../TextureCube'; import GLExtension from './GLExtension'; -function getAvailableMinFilter(texture: Texture2D | TextureCube) { +type AllTextureType = Texture2D | TextureCube | Texture2DArray | Texture3D; + +const textureTargetMap = { + texture2D: constants.TEXTURE_2D, + textureCube: constants.TEXTURE_CUBE_MAP, + texture2DArray: constants.TEXTURE_2D_ARRAY, + texture3D: constants.TEXTURE_3D +}; + +function getAvailableMinFilter(texture: AllTextureType) { const minFilter = texture.minFilter; if (!texture.useMipmap) { return minFilter === constants.NEAREST_MIPMAP_NEAREST @@ -23,14 +34,14 @@ class GLTexture { */ slot: number = -1; - private _texture: Texture2D | TextureCube; + private _texture: AllTextureType; /** * Instance of webgl texture */ private _webglIns?: WebGLTexture; - constructor(texture: Texture2D | TextureCube) { + constructor(texture: AllTextureType) { this._texture = texture; } @@ -48,8 +59,7 @@ class GLTexture { update(gl: WebGL2RenderingContext, glExt: GLExtension) { const texture = this._texture; - const isTexture2D = texture.textureType === 'texture2D'; - const textureTarget = isTexture2D ? constants.TEXTURE_2D : constants.TEXTURE_CUBE_MAP; + const textureTarget = textureTargetMap[texture.textureType]; this.bind(gl); // Pixel storage @@ -63,7 +73,7 @@ class GLTexture { const glInternalFormat = texture.internalFormat || getPossiblelInternalFormat(texture.format, texture.type); - const mipmaps = texture.mipmaps || []; + const mipmaps = (texture as Texture2D).mipmaps || []; const mipmapsLen = mipmaps.length; let glType = texture.type; let width = texture.width; @@ -83,34 +93,22 @@ class GLTexture { const updateTextureData = ( gl: WebGL2RenderingContext, - data: Texture2DData | TextureCubeData, + data: Texture2DData | Texture3DData | Texture2DArrayData | TextureCubeData, level: number, width: number, height: number ) => { - if (isTexture2D) { - this._updateTextureData2D( - gl, - data as Texture2DData, - level, - width, - height, - glInternalFormat, - glFormat, - glType - ); - } else { - this._updateTextureDataCube( - gl, - data as TextureCubeData, - level, - width, - height, - glInternalFormat, - glFormat, - glType - ); - } + this[`_update_${texture.textureType}`]( + gl, + data as any, + level, + width, + height, + (texture as Texture3D).depth || 0, + glInternalFormat, + glFormat, + glType + ); }; if (mipmapsLen) { @@ -130,12 +128,13 @@ class GLTexture { this.unbind(gl); } - private _updateTextureData2D( + private _update_texture2D( gl: WebGL2RenderingContext, data: Texture2DData, level: number, width: number, height: number, + depth: number, glInternalFormat: GLEnum, glFormat: GLEnum, glType: GLEnum @@ -188,12 +187,78 @@ class GLTexture { } } - private _updateTextureDataCube( + private _update_texture2DArray( + _gl: WebGL2RenderingContext, + data: Texture2DArrayData, + level: number, + width: number, + height: number, + depth: number, + glInternalFormat: GLEnum, + glFormat: GLEnum, + glType: GLEnum + ) { + const source = data.image || data.pixels; + if (source) { + _gl.texStorage3D( + constants.TEXTURE_2D_ARRAY, + level, + glInternalFormat, + width, + height, + source.length + ); + source.forEach((source, idx) => + _gl.texSubImage3D( + constants.TEXTURE_2D_ARRAY, + level, + 0, + 0, + width, + height, + idx, + 0, + glFormat, + glType, + source as HTMLImageElement + ) + ); + } + } + + private _update_texture3D( + _gl: WebGL2RenderingContext, + data: Texture3DData, + level: number, + width: number, + height: number, + depth: number, + glInternalFormat: GLEnum, + glFormat: GLEnum, + glType: GLEnum + ) { + const source = data.pixels; + _gl.texImage3D( + constants.TEXTURE_3D, + level, + glInternalFormat, + width, + height, + depth, + 0, + glFormat, + glType, + source || null + ); + } + + private _update_textureCube( _gl: WebGL2RenderingContext, data: TextureCubeData, level: number, width: number, height: number, + depth: number, glInternalFormat: GLEnum, glFormat: GLEnum, glType: GLEnum