Newer
Older
HuangJiPC / public / static / three / examples / jsm / exporters / KTX2Exporter.js
@zhangdeliang zhangdeliang on 21 Jun 5 KB update
import {
	FloatType,
	HalfFloatType,
	UnsignedByteType,
	RGBAFormat,
	RGFormat,
	RGIntegerFormat,
	RedFormat,
	RedIntegerFormat,
	LinearEncoding,
	sRGBEncoding,
	DataTexture,
	REVISION,
} from 'three';

import {
	write,
	KTX2Container,
	KHR_DF_CHANNEL_RGBSDA_ALPHA,
	KHR_DF_CHANNEL_RGBSDA_BLUE,
	KHR_DF_CHANNEL_RGBSDA_GREEN,
	KHR_DF_CHANNEL_RGBSDA_RED,
	KHR_DF_MODEL_RGBSDA,
	KHR_DF_PRIMARIES_BT709,
	KHR_DF_SAMPLE_DATATYPE_FLOAT,
	KHR_DF_SAMPLE_DATATYPE_LINEAR,
	KHR_DF_SAMPLE_DATATYPE_SIGNED,
	KHR_DF_TRANSFER_LINEAR,
	KHR_DF_TRANSFER_SRGB,
	VK_FORMAT_R16_SFLOAT,
	VK_FORMAT_R16G16_SFLOAT,
	VK_FORMAT_R16G16B16A16_SFLOAT,
	VK_FORMAT_R32_SFLOAT,
	VK_FORMAT_R32G32_SFLOAT,
	VK_FORMAT_R32G32B32A32_SFLOAT,
	VK_FORMAT_R8_SRGB,
	VK_FORMAT_R8_UNORM,
	VK_FORMAT_R8G8_SRGB,
	VK_FORMAT_R8G8_UNORM,
	VK_FORMAT_R8G8B8A8_SRGB,
	VK_FORMAT_R8G8B8A8_UNORM,
} from '../libs/ktx-parse.module.js';

const VK_FORMAT_MAP = {

	[ RGBAFormat ]: {
		[ FloatType ]: {
			[ LinearEncoding ]: VK_FORMAT_R32G32B32A32_SFLOAT,
		},
		[ HalfFloatType ]: {
			[ LinearEncoding ]: VK_FORMAT_R16G16B16A16_SFLOAT,
		},
		[ UnsignedByteType ]: {
			[ LinearEncoding ]: VK_FORMAT_R8G8B8A8_UNORM,
			[ sRGBEncoding ]: VK_FORMAT_R8G8B8A8_SRGB,
		},
	},

	[ RGFormat ]: {
		[ FloatType ]: {
			[ LinearEncoding ]: VK_FORMAT_R32G32_SFLOAT,
		},
		[ HalfFloatType ]: {
			[ LinearEncoding ]: VK_FORMAT_R16G16_SFLOAT,
		},
		[ UnsignedByteType ]: {
			[ LinearEncoding ]: VK_FORMAT_R8G8_UNORM,
			[ sRGBEncoding ]: VK_FORMAT_R8G8_SRGB,
		},
	},

	[ RedFormat ]: {
		[ FloatType ]: {
			[ LinearEncoding ]: VK_FORMAT_R32_SFLOAT,
		},
		[ HalfFloatType ]: {
			[ LinearEncoding ]: VK_FORMAT_R16_SFLOAT,
		},
		[ UnsignedByteType ]: {
			[ LinearEncoding ]: VK_FORMAT_R8_SRGB,
			[ sRGBEncoding ]: VK_FORMAT_R8_UNORM,
		},
	},

};

const KHR_DF_CHANNEL_MAP = {

	0: KHR_DF_CHANNEL_RGBSDA_RED,
	1: KHR_DF_CHANNEL_RGBSDA_GREEN,
	2: KHR_DF_CHANNEL_RGBSDA_BLUE,
	3: KHR_DF_CHANNEL_RGBSDA_ALPHA,

};

const ERROR_INPUT = 'THREE.KTX2Exporter: Supported inputs are DataTexture, Data3DTexture, or WebGLRenderer and WebGLRenderTarget.';
const ERROR_FORMAT = 'THREE.KTX2Exporter: Supported formats are RGBAFormat, RGFormat, or RedFormat.';
const ERROR_TYPE = 'THREE.KTX2Exporter: Supported types are FloatType, HalfFloatType, or UnsignedByteType."';
const ERROR_ENCODING = 'THREE.KTX2Exporter: Supported encodings are sRGB (UnsignedByteType only) or Linear.';

export class KTX2Exporter {

	parse( arg1, arg2 ) {

		let texture;

		if ( arg1.isDataTexture || arg1.isData3DTexture ) {

			texture = arg1;

		} else if ( arg1.isWebGLRenderer && arg2.isWebGLRenderTarget ) {

			texture = toDataTexture( arg1, arg2 );

		} else {

			throw new Error( ERROR_INPUT );

		}

		if ( VK_FORMAT_MAP[ texture.format ] === undefined ) {

			throw new Error( ERROR_FORMAT );

		}

		if ( VK_FORMAT_MAP[ texture.format ][ texture.type ] === undefined ) {

			throw new Error( ERROR_TYPE );

		}

		if ( VK_FORMAT_MAP[ texture.format ][ texture.type ][ texture.encoding ] === undefined ) {

			throw new Error( ERROR_ENCODING );

		}

		//

		const array = texture.image.data;
		const channelCount = getChannelCount( texture );
		const container = new KTX2Container();

		container.vkFormat = VK_FORMAT_MAP[ texture.format ][ texture.type ][ texture.encoding ];
		container.typeSize = array.BYTES_PER_ELEMENT;
		container.pixelWidth = texture.image.width;
		container.pixelHeight = texture.image.height;

		if ( texture.isData3DTexture ) {

			container.pixelDepth = texture.image.depth;

		}

		//

		const basicDesc = container.dataFormatDescriptor[ 0 ];

		// TODO: After `texture.encoding` is replaced, distinguish between
		// non-color data (unspecified model and primaries) and sRGB or Linear-sRGB colors.
		basicDesc.colorModel = KHR_DF_MODEL_RGBSDA;
		basicDesc.colorPrimaries = KHR_DF_PRIMARIES_BT709;
		basicDesc.transferFunction = texture.encoding === sRGBEncoding
			? KHR_DF_TRANSFER_SRGB
			: KHR_DF_TRANSFER_LINEAR;

		basicDesc.texelBlockDimension = [ 0, 0, 0, 0 ];

		basicDesc.bytesPlane = [

			container.typeSize * channelCount, 0, 0, 0, 0, 0, 0, 0,

		];

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

			let channelType = KHR_DF_CHANNEL_MAP[ i ];

			if ( texture.encoding === LinearEncoding ) {

				channelType |= KHR_DF_SAMPLE_DATATYPE_LINEAR;

			}

			if ( texture.type === FloatType || texture.type === HalfFloatType ) {

				channelType |= KHR_DF_SAMPLE_DATATYPE_FLOAT;
				channelType |= KHR_DF_SAMPLE_DATATYPE_SIGNED;

			}

			basicDesc.samples.push( {

				channelType: channelType,
				bitOffset: i * array.BYTES_PER_ELEMENT,
				bitLength: array.BYTES_PER_ELEMENT * 8 - 1,
				samplePosition: [ 0, 0, 0, 0 ],
				sampleLower: texture.type === UnsignedByteType ? 0 : - 1,
				sampleUpper: texture.type === UnsignedByteType ? 255 : 1,

			} );

		}

		//

		container.levels = [ {

			levelData: new Uint8Array( array.buffer, array.byteOffset, array.byteLength ),
			uncompressedByteLength: array.byteLength,

		} ];

		//

		container.keyValue[ 'KTXwriter' ] = `three.js ${ REVISION }`;

		//

		return write( container, { keepWriter: true } );

	}

}

function toDataTexture( renderer, rtt ) {

	const channelCount = getChannelCount( rtt.texture );

	let view;

	if ( rtt.texture.type === FloatType ) {

		view = new Float32Array( rtt.width * rtt.height * channelCount );

	} else if ( rtt.texture.type === HalfFloatType ) {

		view = new Uint16Array( rtt.width * rtt.height * channelCount );

	} else if ( rtt.texture.type === UnsignedByteType ) {

		view = new Uint8Array( rtt.width * rtt.height * channelCount );

	} else {

		throw new Error( ERROR_TYPE );

	}

	renderer.readRenderTargetPixels( rtt, 0, 0, rtt.width, rtt.height, view );

	return new DataTexture( view, rtt.width, rtt.height, rtt.texture.format, rtt.texture.type );

}

function getChannelCount( texture ) {

	switch ( texture.format ) {

		case RGBAFormat:

			return 4;

		case RGFormat:
		case RGIntegerFormat:

			return 2;

		case RedFormat:
		case RedIntegerFormat:

			return 1;

		default:

			throw new Error( ERROR_FORMAT );

	}

}