Texture
Texture Overview
Texture, is one of the most commonly used resources in 3D rendering. When shading a model, we need to set a color value for each fragment. Besides setting this color value directly and manually, we can also choose to read texels from a texture for shading, thereby achieving richer artistic effects.
Texture Types
| Type | Description |
|---|---|
| 2D Texture | The most commonly used art resource, sampled using two-dimensional UV coordinates |
| Cross Cube Texture | 6 2D textures form a cross cube texture, which can be used to implement effects such as skyboxes and environment reflections |
| LDR Cube Texture | 6 LDR textures form a panoramic sky image, which can be used to implement effects such as skyboxes and environment reflections |
| HDR Texture | Supports sampling textures in RGBE format |
| HDR Cube Texture | 6 HDR textures form a panoramic sky image, which can be used to implement effects such as skyboxes and environment reflections |
Creating Textures
1. Manually Creating a 2D Texture
By creating a texture instance, we can manually create a texture object, and then manually load the corresponding image resource through load:
2D texturessupport common web image formats,jpg/png/webp;HDR texturessupport loading.hdrimages inRGBEformat;
import { BitmapTexture2D } from '@orillusion/core';
// Create a 2D texture
let texture = new BitmapTexture2D();
// Load the texture resource
texture.load('path/to/image.png');
// Create an HDR texture
let hdrTexture = new HDRTexture();
hdrTexture = await hdrTexture.load('path/to/image.hdr');2. Loading via the Resource Manager
In addition to manually creating texture objects, we recommend conveniently loading images and automatically creating the corresponding textures through the resource manager:
import { Engine3D } from '@orillusion/core';
// 2D texture
let texture = engine.res.loadTexture('path/to/image.png');
// HDR texture
let hdrTexture = engine.res.loadHDRTexture('path/to/image.hdr');
// Cross cube texture
let texture = engine.res.loadTextureCube('path/to/sky.png');
// LDR panorama
let HDRTextureCube = engine.res.loadLDRTextureCube('path/to/sky.png');
// HDR panorama
let HDRTextureCube = engine.res.loadHDRTextureCube('path/to/sky.hdr');3. Manually Filling in Color Data
At its core, a texture actually corresponds to the color value of each pixel, i.e. the RGBA channels. We can manually create a Uint8Array to fill in the specific values of the rgba color channels, and then manually create a texture through the Uint8ArrayTexture class:
// Image parameters
let w = 32;
let h = 32;
let r = 255;
let g = 0;
let b = 0;
let a = 255;
// Create a raw Uint8Array
let textureData = new Uint8Array(w * h * 4);
// Fill in the rgba values
for (let i = 0; i < w; i++) {
for (let j = 0; j < h; j++) {
let pixelIndex = j * w + i;
textureData[pixelIndex * 4 + 0] = r;
textureData[pixelIndex * 4 + 1] = g;
textureData[pixelIndex * 4 + 2] = b;
textureData[pixelIndex * 4 + 3] = a;
}
}
// Create a texture through rawData
let texture = new Uint8ArrayTexture();
texture.create(16, 16, textureData, true);Loading Textures
2D Texture
We can directly assign a texture to the corresponding property of a material, such as the base texture (baseMap):
let floorMat = new LitMaterial();
let texture = await engine.res.loadTexture('path/to/image.png');
floorMat.baseMap = texture;import { Engine3D, Vector3, Scene3D, Object3D, Camera3D, AtmosphericComponent, View3D, UnLitMaterial, MeshRenderer, HoverCameraController, PlaneGeometry, DirectLight, Color } from '@orillusion/core';
let engine = await Engine3D.init();
let scene = new Scene3D();
let camera = new Object3D();
scene.addChild(camera);
let mainCamera = camera.addComponent(Camera3D);
mainCamera.perspective(60, engine.aspect, 0.1, 10000.0);
let hc = camera.addComponent(HoverCameraController);
hc.setCamera(0, 0, 2);
// create a unlit material
let mat = new UnLitMaterial();
let texture = await engine.res.loadTexture('https://cdn.orillusion.com/gltfs/cube/material_02.png');
mat.baseMap = texture;
// add a plane to display the image
let planeObj = new Object3D();
let mr = planeObj.addComponent(MeshRenderer);
mr.geometry = new PlaneGeometry(2, 2, 10, 10, Vector3.Z_AXIS);
mr.material = mat;
scene.addChild(planeObj);
// add a light
let lightObj = new Object3D();
lightObj.rotationX = -45;
let light = lightObj.addComponent(DirectLight);
light.lightColor = new Color(1.0, 1.0, 1.0, 1.0);
light.intensity = 10;
scene.addChild(lightObj);
// add an Atmospheric sky enviroment
scene.addComponent(AtmosphericComponent).sunY = 0.6;
// create a view with target scene and camera
let view = new View3D();
view.scene = scene;
view.camera = mainCamera;
// start render
engine.startRenderView(view);Cross Cube Texture
A cross cube texture has 6 faces, i.e. 6 2D textures arranged and combined into a cube box in the order shown below:

The cross cube texture can be used to implement effects such as skyboxes and environment reflections. We recommend directly using the Res method to load 1 complete cross cube texture and assigning it directly to scene.envMap:
// Load a cross cube texture
let textureCube = engine.res.loadTextureCube('path/to/crossSky.png');
// Set the skybox
scene.envMap = textureCube;In addition, we can also manually load a cube texture of 6 independent faces through the BitmapTextureCube class:
let textureCube = new BitmapTextureCube();
// Load the 6 independent faces separately
await textureCube.load([
'x Right',
'-x Left',
'y Up',
'-y Down',
'z Front',
'-z Back'
]);import { Camera3D, Engine3D, View3D, HoverCameraController, Object3D, Scene3D, BitmapTextureCube, SkyRenderer } from '@orillusion/core';
let engine = await Engine3D.init();
let scene = new Scene3D();
let camera = new Object3D();
scene.addChild(camera);
let mainCamera = camera.addComponent(Camera3D);
mainCamera.perspective(60, engine.aspect, 1, 2000.0);
let ctrl = camera.addComponent(HoverCameraController);
ctrl.setCamera(180, 0, 10);
let evnMap = new BitmapTextureCube();
let urls: string[] = [];
urls.push('https://cdn.orillusion.com/textures/cubemap/skybox_nx.png');
urls.push('https://cdn.orillusion.com/textures/cubemap/skybox_px.png');
urls.push('https://cdn.orillusion.com/textures/cubemap/skybox_py.png');
urls.push('https://cdn.orillusion.com/textures/cubemap/skybox_ny.png');
urls.push('https://cdn.orillusion.com/textures/cubemap/skybox_nz.png');
urls.push('https://cdn.orillusion.com/textures/cubemap/skybox_pz.png');
await evnMap.load(urls, engine.context3D);
let sky = scene.addComponent(SkyRenderer);
sky.map = evnMap;
// create a view with target scene and camera
let view = new View3D();
view.scene = scene;
view.camera = mainCamera;
// start render
engine.startRenderView(view);Panorama Cube Texture
In addition to the cross cube texture, we can also load equirectangular type textures through Res. It supports both RGBA type ordinary images and hdr images in RGBE format:
// Ordinary format panorama
let ldrTextureCube = await engine.res.loadLDRTextureCube('path/to/sky.png');
// Load an hdr panorama texture
let hdrTextureCube = await engine.res.loadHDRTextureCube('path/to/sky.hdr');import { Camera3D, Engine3D, View3D, HoverCameraController, Object3D, Scene3D, SkyRenderer } from '@orillusion/core';
let engine = await Engine3D.init();
let scene = new Scene3D();
let sky = scene.addComponent(SkyRenderer);
let hdrTextureCube = await engine.res.loadHDRTextureCube('https://cdn.orillusion.com/hdri/T_Panorama05_HDRI.HDR');
sky.map = hdrTextureCube;
let camera = new Object3D();
scene.addChild(camera);
let mainCamera = camera.addComponent(Camera3D);
mainCamera.perspective(60, engine.aspect, 1, 2000.0);
let ctrl = camera.addComponent(HoverCameraController);
ctrl.setCamera(180, 0, 10);
let view = new View3D();
view.scene = scene;
view.camera = mainCamera;
engine.startRenderView(view);Texture Settings
1. Texture Repeat
The default sampling range of a texture is [0,1], i.e. tiling the texture across the entire plane. We can manually change the coordinate range over which the texture repeats by setting the uvTransform_1 property of the material:
let mat = new LitMaterial();
// Make the texture repeat 2 times in both the horizontal and vertical directions
mat.uvTransform_1 = new Vector4(0,0,2,2);
mat.baseMap = new BitmapTexture2D();When the texture uvtransform_1 exceeds the [0,1] range, we can control the way it repeats in the horizontal and vertical directions by setting the texture's addressModeU and addressModeV properties, for example:
let texture = new BitmapTexture2D();
// Horizontal direction, default repeat mode
texture.addressModeU = GPUAddressMode.repeat;
// Vertical direction, default repeat mode
texture.addressModeV = GPUAddressMode.repeat;Currently WebGPU supports the following repeat modes by default:
- Repeat mode (repeat): the default mode, i.e. for out-of-range values, resampling starts again from
[0,1]

- Mirror repeat mode (mirror_repeat): for out-of-range values, after a mirror flip, resampling starts again from
[0,1].

- Clamp mode (clamp_to_edge): for out-of-range values, samples the color of the texel at the texture edge.

import { Engine3D, Vector3, Scene3D, Object3D, Camera3D, AtmosphericComponent, View3D, UnLitMaterial, MeshRenderer, HoverCameraController, PlaneGeometry, Vector4, GPUAddressMode, DirectLight, Color } from '@orillusion/core';
let engine = await Engine3D.init();
let scene = new Scene3D();
let camera = new Object3D();
scene.addChild(camera);
let mainCamera = camera.addComponent(Camera3D);
mainCamera.perspective(60, engine.aspect, 0.1, 10000.0);
let hc = camera.addComponent(HoverCameraController);
hc.setCamera(0, 0, 2);
// add a dir light
let lightObj = new Object3D();
lightObj.rotationX = -45;
let light = lightObj.addComponent(DirectLight);
light.lightColor = new Color(1.0, 1.0, 1.0, 1.0);
light.intensity = 10;
scene.addChild(lightObj);
// add an Atmospheric sky enviroment
scene.addComponent(AtmosphericComponent).sunY = 0.6;
// create a view with target scene and camera
let view = new View3D();
view.scene = scene;
view.camera = mainCamera;
// start render
engine.startRenderView(view);
let texture = await engine.res.loadTexture('https://cdn.orillusion.com/images/webgpu.webp');
// texture.addressModeU = GPUAddressMode.repeat;
// texture.addressModeV = GPUAddressMode.repeat;
let mat = new UnLitMaterial();
mat.setUniformVector4('transformUV1', new Vector4(0, 0, 2, 2));
mat.baseMap = texture;
let planeObj = new Object3D();
let mr = planeObj.addComponent(MeshRenderer);
mr.geometry = new PlaneGeometry(2, 2, 10, 10, Vector3.Z_AXIS);
mr.material = mat;
scene.addChild(planeObj);
let select = document.createElement('select');
select.innerHTML = `
<option value="repeat">Repeat</option>
<option value="mirror_repeat">Mirror-Repeat</option>
<option value="clamp_to_edge">Clamp-to-Edge</option>
`;
select.setAttribute('style', 'position:fixed;right:5px;top:5px');
document.body.appendChild(select);
select.addEventListener('change', () => {
texture.addressModeU = GPUAddressMode[select.value];
texture.addressModeV = GPUAddressMode[select.value];
});2. Sampling Filter Mode
Generally speaking, texels and screen pixels do not correspond exactly, which requires the GPU to scale the pixel size. However, different scaling modes will have a certain influence on the final pixel color. We can control the filter mode used by the GPU when magnifying (Mag) and minifying (Min) pixels by setting the texture's magFilter and minFilter properties.
let texture = new BitmapTexture2D();
// Magnification mode, default linear mode
texture.magFilter = 'linear';
// Minification mode, default linear mode
texture.minFilter = 'linear';Currently WebGPU supports linear linear sampling and nearest nearest-point sampling modes.
Generally speaking, linear mode has smoother pixel edges, suitable for complex graphic transitions; nearest has sharper pixel edges, suitable for textures with clear color distribution and distinct edges. You can see the influence of different sampling modes on the texture's appearance through the following example:
import { Engine3D, Vector3, Scene3D, Object3D, Camera3D, AtmosphericComponent, View3D, UnLitMaterial, MeshRenderer, HoverCameraController, PlaneGeometry, BitmapTexture2D, DirectLight, Color } from '@orillusion/core';
let engine = await Engine3D.init();
let scene = new Scene3D();
let camera = new Object3D();
scene.addChild(camera);
let mainCamera = camera.addComponent(Camera3D);
mainCamera.perspective(60, engine.aspect, 0.1, 10000.0);
let hc = camera.addComponent(HoverCameraController);
hc.setCamera(0, 0, 0.2);
// add a dir light
let lightObj = new Object3D();
lightObj.rotationX = -45;
let light = lightObj.addComponent(DirectLight);
light.lightColor = new Color(1.0, 1.0, 1.0, 1.0);
light.intensity = 10;
scene.addChild(lightObj);
// add an Atmospheric sky enviroment
scene.addComponent(AtmosphericComponent).sunY = 0.6;
// create a view with target scene and camera
let view = new View3D();
view.scene = scene;
view.camera = mainCamera;
// start render
engine.startRenderView(view);
let texture = new BitmapTexture2D();
await texture.load('https://cdn.orillusion.com/gltfs/cube/material_02.png');
texture.magFilter = 'linear';
texture.minFilter = 'linear';
let mat = new UnLitMaterial();
mat.baseMap = texture;
let planeObj = new Object3D();
let mr = planeObj.addComponent(MeshRenderer);
mr.geometry = new PlaneGeometry(2, 2, 10, 10, Vector3.Z_AXIS);
mr.material = mat;
scene.addChild(planeObj);
let select = document.createElement('select');
select.innerHTML = `
<option value="linear">Linear</option>
<option value="nearest">Nearest</option>
`;
select.setAttribute('style', 'position:fixed;right:5px;top:5px');
document.body.appendChild(select);
select.addEventListener('change', () => {
texture.magFilter = select.value;
texture.minFilter = select.value;
});3. Mipmap
In the 3D world, because different objects are at near and far distances from the camera, the corresponding texture images are large and small. If the same texture resolution is used, distant objects need to pick a small portion of pixel colors from the high-resolution original image, which not only wastes GPU performance but also causes an unrealistic feeling or a large amount of moire due to pixel distortion.Orillusion uses the concept of a Mipmap to solve this problem. Simply put, it automatically scales a high-resolution image into a series of textures of different resolutions. Depending on the distance between the texture and the viewer, textures of different resolutions are used. Distant objects use lower-resolution textures, which is more natural in resolution and can also effectively save GPU performance.
We can enable or disable it through useMipmap, which is enabled by default
let texture = new BitmapTexture2D();
// true by default
texture.useMipmap = true;import { Engine3D, Scene3D, Object3D, Camera3D, AtmosphericComponent, View3D, UnLitMaterial, MeshRenderer, PlaneGeometry, BitmapTexture2D, Vector4, OrbitController, DirectLight, Color } from '@orillusion/core';
let engine = await Engine3D.init();
let scene = new Scene3D();
let camera = new Object3D();
camera.y = 10;
camera.z = 30;
scene.addChild(camera);
let mainCamera = camera.addComponent(Camera3D);
mainCamera.perspective(60, engine.aspect, 0.1, 10000.0);
let oribit = camera.addComponent(OrbitController);
oribit.autoRotate = true;
// add a dir light
let lightObj = new Object3D();
lightObj.rotationX = -45;
let light = lightObj.addComponent(DirectLight);
light.lightColor = new Color(1.0, 1.0, 1.0, 1.0);
light.intensity = 1;
scene.addChild(lightObj);
// add an Atmospheric sky enviroment
scene.addComponent(AtmosphericComponent).sunY = 0.6;
// create a view with target scene and camera
let view = new View3D();
view.scene = scene;
view.camera = mainCamera;
// start render
engine.startRenderView(view);
const imageCanvas = document.createElement('canvas');
const context = imageCanvas.getContext('2d') as CanvasRenderingContext2D;
{
imageCanvas.width = imageCanvas.height = 128;
context.fillStyle = '#444';
context.fillRect(0, 0, 128, 128);
context.fillStyle = '#fff';
context.fillRect(0, 0, 64, 64);
context.fillRect(64, 64, 64, 64);
}
const image = imageCanvas.toDataURL('image/png');
let texture = new BitmapTexture2D();
texture.useMipmap = true;
await texture.load(image);
let mat = new UnLitMaterial();
mat.baseMap = texture;
mat.setUniformVector4('transformUV1', new Vector4(0, 0, 100, 100));
let plane = new PlaneGeometry(1000, 1000, 10, 10);
let planeObj = new Object3D();
let mr = planeObj.addComponent(MeshRenderer);
mr.geometry = plane;
mr.material = mat;
scene.addChild(planeObj);
let select = document.createElement('select');
select.innerHTML = `
<option value="true">Use MipMap</option>
<option value="false">No MipMap</option>
`;
select.setAttribute('style', 'position:fixed;right:5px;top:5px');
document.body.appendChild(select);
select.addEventListener('change', () => {
texture.useMipmap = select.value === 'true';
});
