support 3d texture

This commit is contained in:
pissang 2023-02-17 17:19:32 +08:00
parent 5b08297737
commit 56a9a407ad
7 changed files with 303 additions and 73 deletions

View File

@ -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<string, UniformType> }) {
return uniform.type === 'sampler2D' || uniform.type === 'samplerCube';
export function isTextureUniform(uniform: { type: UniformType }) {
return uniform.type.startsWith('sampler');
}
export default Shader;

View File

@ -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;

111
src/Texture2DArray.ts Normal file
View File

@ -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<Texture2DArrayOpts, 'image'> {}
class Texture2DArray extends Texture {
readonly textureType = 'texture2DArray';
private _image: TextureImageSource[] = [];
constructor(opts?: Partial<Texture2DArrayOpts>) {
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;

48
src/Texture3D.ts Normal file
View File

@ -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;

View File

@ -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<CubeTarget, TextureImageSource>;
(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;

View File

@ -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
};

View File

@ -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