Signatur
Beschreibung
EXT_texture_compression_rgtc ist eine WebGL-Erweiterung. Sie macht die Red-Green Texture Compression (RGTC) nutzbar, die in DirectX als BC4 (ein Kanal) und BC5 (zwei Kanäle) bekannt ist. Die Formate speichern jeweils 4×4 Pixel in einem festen Block: BC4 braucht 8 Byte pro Block (0,5 Byte pro Pixel), BC5 braucht 16 Byte pro Block (1 Byte pro Pixel). Das Objekt bekommst du über gl.getExtension("EXT_texture_compression_rgtc"). Es hat keine Methoden, sondern nur vier Konstanten für die Formate.
Typische Einsatzfälle sind Einkanal-Daten wie Höhenkarten, Rauheits- oder Ambient-Occlusion-Maps und Masken (RGTC1) sowie Normal-Maps in Tangentenraum (RGTC2). Bei Normal-Maps werden nur X und Y gespeichert, Z berechnet der Shader. Weil RGTC jeden Kanal unabhängig mit eigener Interpolation kodiert, ist die Qualität pro Kanal deutlich besser als bei S3TC/DXT, wenn man die Daten dort in einzelne Kanäle packt.
Die Konstanten übergibst du als internalformat an compressedTexImage2D() oder compressedTexSubImage2D(). In WebGL 2 gehen sie außerdem an texStorage2D() und bei Textur-Arrays an compressedTexImage3D(). Die Pixeldaten müssen schon vorkomprimiert sein, zum Beispiel aus KTX2- oder DDS-Dateien. WebGL komprimiert zur Laufzeit nichts.
Diese Formate gibt es hauptsächlich auf Desktop-GPUs. Frag die Erweiterung daher immer zuerst ab und halte einen Ausweichweg bereit: ein anderes komprimiertes Format (ETC2, ASTC) oder unkomprimierte R8/RG8-Texturen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $COMPRESSED_RED_RGTC1_EXT | GLenum (0x8DBB) | Ein vorzeichenloser Kanal (Rot), entspricht BC4_UNORM. 8 Byte pro 4×4-Block, Werte von 0 bis 1. | |
| $COMPRESSED_SIGNED_RED_RGTC1_EXT | GLenum (0x8DBC) | Ein vorzeichenbehafteter Kanal (Rot), entspricht BC4_SNORM. 8 Byte pro Block, Werte von −1 bis 1. | |
| $COMPRESSED_RED_GREEN_RGTC2_EXT | GLenum (0x8DBD) | Zwei vorzeichenlose Kanäle (Rot, Grün), entspricht BC5_UNORM. 16 Byte pro Block. Üblich für Normal-Maps. | |
| $COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT | GLenum (0x8DBE) | Zwei vorzeichenbehaftete Kanäle, entspricht BC5_SNORM. 16 Byte pro Block, Werte von −1 bis 1. Damit lassen sich Normalen-Komponenten direkt speichern. |
Rückgabewert
gl.getExtension("EXT_texture_compression_rgtc") gibt das Erweiterungsobjekt mit den vier Konstanten zurück. Unterstützen Gerät oder Browser RGTC nicht, ist der Rückgabewert null.Beispiele
Unterstützung prüfen und Konstanten auslesen
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl2") ?? canvas.getContext("webgl");
const rgtc = gl.getExtension("EXT_texture_compression_rgtc");
if (rgtc) {
console.log("RGTC verfügbar");
console.log(rgtc.COMPRESSED_RED_RGTC1_EXT.toString(16));
console.log(rgtc.COMPRESSED_RED_GREEN_RGTC2_EXT.toString(16));
} else {
console.log("RGTC nicht verfügbar – Ausweichformat verwenden");
}
BC4-Höhenkarte mit compressedTexImage2D hochladen
// Byte-Größe eines RGTC-Mip-Levels berechnen
const rgtcByteSize = (width, height, blockBytes) =>
Math.ceil(width / 4) * Math.ceil(height / 4) * blockBytes;
const uploadHeightmap = async (gl, url, width, height) => {
const ext = gl.getExtension("EXT_texture_compression_rgtc");
if (!ext) throw new Error("RGTC wird nicht unterstützt");
// Rohe BC4-Blockdaten, z. B. aus einer DDS-/KTX-Datei extrahiert
const buffer = await (await fetch(url)).arrayBuffer();
const expected = rgtcByteSize(width, height, 8);
const data = new Uint8Array(buffer, 0, expected);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.compressedTexImage2D(
gl.TEXTURE_2D, 0,
ext.COMPRESSED_RED_RGTC1_EXT,
width, height, 0,
data
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
console.log(`Hochgeladen: ${expected} Byte`);
return tex;
};
// Aufruf: await uploadHeightmap(gl, "/terrain/height_512.bc4", 512, 512);
WebGL 2: BC5-Normal-Map mit texStorage2D und Z-Rekonstruktion im Shader
const gl = document.querySelector("canvas").getContext("webgl2");
const ext = gl.getExtension("EXT_texture_compression_rgtc");
const createNormalMap = (mipLevels) => {
// mipLevels: Array von { width, height, data: Uint8Array }
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
const { width, height } = mipLevels[0];
gl.texStorage2D(gl.TEXTURE_2D, mipLevels.length,
ext.COMPRESSED_RED_GREEN_RGTC2_EXT, width, height);
mipLevels.forEach((mip, level) => {
gl.compressedTexSubImage2D(gl.TEXTURE_2D, level, 0, 0,
mip.width, mip.height, ext.COMPRESSED_RED_GREEN_RGTC2_EXT, mip.data);
});
return tex;
};
// Fragment-Shader: Z aus X und Y berechnen
const fragmentSource = `#version 300 es
precision highp float;
uniform sampler2D uNormalMap;
in vec2 vUv;
out vec4 outColor;
void main() {
vec2 xy = texture(uNormalMap, vUv).rg * 2.0 - 1.0;
float z = sqrt(max(0.0, 1.0 - dot(xy, xy)));
vec3 n = normalize(vec3(xy, z));
outColor = vec4(n * 0.5 + 0.5, 1.0);
}`;