Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGL Texture2d array #5754

Merged
merged 21 commits into from Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c4b2e90
Extend immediate mode line drawing with support for custom material
heretique Oct 13, 2022
d040408
Texture array support
heretique Nov 5, 2022
0cfe4a1
Merge remote-tracking branch 'upstream/main'
heretique Oct 12, 2023
7a2f73f
Merge branch 'main' into texture2d-array
heretique Oct 12, 2023
3af97d5
Merge remote-tracking branch 'upstream/main'
heretique Oct 13, 2023
69938f7
Merge branch 'main' into texture2d-array
heretique Oct 13, 2023
9d4e28e
Remove line related code from PR
heretique Oct 13, 2023
9ba09c2
Small linting fix
heretique Oct 13, 2023
125dd7e
Merge branch 'main' into texture2d-array
heretique Oct 16, 2023
82ec3f5
Merge remote-tracking branch 'upstream/main' into texture2d-array
heretique Oct 17, 2023
4cbea8a
Merge remote-tracking branch 'upstream/main' into texture2d-array
heretique Oct 23, 2023
8da0e7c
Merge remote-tracking branch 'upstream/main' into texture2d-array
heretique Oct 31, 2023
d56f782
Add proper mipmaps support for texture arrays
heretique Nov 1, 2023
01a1e2c
Merge remote-tracking branch 'upstream/main' into texture2d-array
heretique Nov 1, 2023
cbdc735
Merge branch 'main' into texture2d-array
heretique Nov 3, 2023
dcce480
Merge branch 'main' into texture2d-array
heretique Nov 6, 2023
d0a8d2a
Merge branch 'main' into texture2d-array
heretique Nov 7, 2023
0dcf3e3
Merge branch 'main' into texture2d-array
heretique Nov 9, 2023
3367f54
Merge branch 'main' into texture2d-array
heretique Nov 9, 2023
079151c
Merge branch 'main' into texture2d-array
heretique Nov 10, 2023
a7b41c3
Left only one option and one internal field for settings array textures
heretique Nov 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/assets/textures/rocky_trail_diff_1k.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/src/examples/graphics/index.mjs
Expand Up @@ -54,6 +54,7 @@ export * from "./shader-toon.mjs";
export * from "./shader-wobble.mjs";
export * from "./shadow-cascades.mjs";
export * from "./shapes.mjs";
export * from "./texture-array.mjs";
export * from "./texture-basis.mjs";
export * from "./transform-feedback.mjs";
export * from "./video-texture.mjs";
311 changes: 311 additions & 0 deletions examples/src/examples/graphics/texture-array.mjs
@@ -0,0 +1,311 @@
import * as pc from 'playcanvas';

/**
* @param {import('../../app/example.mjs').ControlOptions} options - The options.
* @returns {JSX.Element} The returned JSX Element.
*/
function controls({ observer, ReactPCUI, React, jsx }) {
const { BindingTwoWay, LabelGroup, Panel, BooleanInput } = ReactPCUI;
class JsxControls extends React.Component {
render() {
const binding = new BindingTwoWay();
const link = {
observer,
path: 'mipmaps'
};
return jsx(Panel, { headerText: 'Texture Arrays' },
jsx(LabelGroup, { text: 'Show mipmaps' },
jsx(BooleanInput, {
type: "toggle",
binding,
link
})
)
);
}
}
return jsx(JsxControls);
}

/**
* @typedef {{ 'shader.vert': string, 'shader.frag': string }} Files
* @typedef {import('../../options.mjs').ExampleOptions<Files>} Options
* @param {Options} options - The example options.
* @returns {Promise<pc.AppBase>} The example application.
*/
async function example({ canvas, deviceType, data, files, assetPath, scriptsPath, glslangPath, twgslPath }) {
function generateMipmaps(width, height) {
const colors = [
[0, 128, 0], // Green
[255, 255, 0], // Yellow
[255, 165, 0], // Orange
[255, 0, 0], // Red
[0, 0, 255], // Blue
[75, 0, 130], // Indigo
[238, 130, 238], // Violet
[255, 192, 203], // Pink
[165, 42, 42], // Brown
[128, 128, 128], // Gray
[128, 0, 128], // Purple
[0, 128, 128], // Teal
[0, 0, 0], // Black
[255, 255, 255] // White
];

const mipmapLevels = Math.log2(Math.max(width, height)) + 1;
const levels = [];
for (let i = 0; i < mipmapLevels; i++) {
const levelWidth = width >> i;
const levelHeight = height >> i;

const data = new Uint8Array(levelWidth * levelHeight * 4);
levels.push(data);

const color = colors[i % colors.length];

for (let j = 0; j < levelWidth * levelHeight; j++) {
data[j * 4 + 0] = color[0];
data[j * 4 + 1] = color[1];
data[j * 4 + 2] = color[2];
data[j * 4 + 3] = 255;
}
}
return levels;
}

const assets = {
rockyTrail: new pc.Asset("rockyTrail", "texture", { url: assetPath + "textures/rocky_trail_diff_1k.jpg" }),
rockBoulder: new pc.Asset("rockBoulder", "texture", { url: assetPath + "textures/rock_boulder_cracked_diff_1k.jpg" }),
coastSand: new pc.Asset("coastSand", "texture", { url: assetPath + "textures/coast_sand_rocks_02_diff_1k.jpg" }),
aerialRocks: new pc.Asset("aeralRocks", "texture", { url: assetPath + "textures/aerial_rocks_02_diff_1k.jpg" }),
script: new pc.Asset('script', 'script', { url: scriptsPath + 'camera/orbit-camera.js' })
};

const gfxOptions = {
deviceTypes: [deviceType],
glslangUrl: glslangPath + 'glslang.js',
twgslUrl: twgslPath + 'twgsl.js'
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);
const createOptions = new pc.AppOptions();
createOptions.graphicsDevice = device;
createOptions.mouse = new pc.Mouse(document.body);
createOptions.touch = new pc.TouchDevice(document.body);
createOptions.keyboard = new pc.Keyboard(document.body);

createOptions.componentSystems = [
// @ts-ignore
pc.RenderComponentSystem,
// @ts-ignore
pc.CameraComponentSystem,
// @ts-ignore
pc.LightComponentSystem
];
createOptions.resourceHandlers = [
// @ts-ignore
pc.TextureHandler,
// @ts-ignore
pc.ContainerHandler
];

const app = new pc.Application(canvas, createOptions);

// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

// Ensure canvas is resized when window changes size
const resize = () => app.resizeCanvas();
window.addEventListener('resize', resize);
app.on('destroy', () => {
window.removeEventListener('resize', resize);
});

const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
assetListLoader.load(() => {

app.start();

app.scene.ambientLight = new pc.Color(0.2, 0.2, 0.2);

// Create directional light
const light = new pc.Entity();
light.addComponent('light', {
type: 'directional'
});
light.setLocalEulerAngles(45, 0, 45);

// Create the shader definition and shader from the vertex and fragment shaders
const shader = pc.createShaderFromCode(app.graphicsDevice, files['shader.vert'], files['shader.frag'], 'myShader', {
aPosition: pc.SEMANTIC_POSITION,
aUv0: pc.SEMANTIC_TEXCOORD0
});

const shaderGround = pc.createShaderFromCode(app.graphicsDevice, files['shader.vert'], files['ground.frag'], 'groundsShader', {
aPosition: pc.SEMANTIC_POSITION,
aUv0: pc.SEMANTIC_TEXCOORD0
});

const textureArrayOptions = {
format: pc.PIXELFORMAT_R8_G8_B8_A8,
width: 1024,
height: 1024,
arrayLength: 4, // array texture with 4 textures
magFilter: pc.FILTER_NEAREST,
minFilter: pc.FILTER_NEAREST_MIPMAP_NEAREST,
mipmaps: true,
addressU: pc.ADDRESS_CLAMP_TO_EDGE,
addressV: pc.ADDRESS_CLAMP_TO_EDGE,
levels: [[
assets.rockyTrail.resource.getSource(),
assets.rockBoulder.resource.getSource(),
assets.aerialRocks.resource.getSource(),
assets.coastSand.resource.getSource()
]]
};

const textureArray = new pc.Texture(app.graphicsDevice, textureArrayOptions);
textureArray.upload();

// generate mipmaps for visualization
const mipmaps = generateMipmaps(textureArrayOptions.width, textureArrayOptions.height);
const levels = mipmaps.map((data) => {
const textures = [];
for (let i = 0; i < textureArrayOptions.arrayLength; i++) {
textures.push(data);
}
return textures;
});
textureArrayOptions.levels = levels;
const mipmapTextureArray = new pc.Texture(app.graphicsDevice, textureArrayOptions);

// Create a new material with the new shader
const material = new pc.Material();
material.shader = shader;
material.setParameter("uDiffuseMap", textureArray);
material.update();

// Create a another material with the new shader
const groundMaterial = new pc.Material();
groundMaterial.shader = shaderGround;
groundMaterial.cull = pc.CULLFACE_NONE;
groundMaterial.setParameter("uDiffuseMap", textureArray);
groundMaterial.update();

// Create an Entity for the ground
const ground = new pc.Entity();
ground.addComponent("render", {
type: "plane",
material: groundMaterial
});
ground.setLocalScale(5, 1, 5);
ground.setLocalPosition(0, -2, 0);
app.root.addChild(ground);

// Create a torus shape
const torus = pc.createTorus(app.graphicsDevice, {
tubeRadius: 0.2,
ringRadius: 0.3,
segments: 50,
sides: 40
});
const shape = new pc.Entity();
shape.addComponent('render', {
material: material,
meshInstances: [new pc.MeshInstance(torus, material)]
});
shape.setPosition(0, 0, 0);
shape.setLocalScale(2, 2, 2);

// Create an Entity with a camera component
const camera = new pc.Entity();
camera.addComponent("camera", {
clearColor: new pc.Color(0.4, 0.45, 0.5)
});

// Adjust the camera position
camera.translate(0, 1, 2);
camera.lookAt(0, 0, 0);

camera.addComponent("script");
const orbit = camera.script.create("orbitCamera", {
attributes: {
inertiaFactor: 0.2, // Override default of 0 (no inertia),
distanceMax: 10.0
}
});
camera.script.create("orbitCameraInputMouse");
camera.script.create("orbitCameraInputTouch");

// Add the new Entities to the hierarchy
app.root.addChild(light);
app.root.addChild(shape);
app.root.addChild(camera);

// Set an update function on the app's update event
let angle = 0;
let time = 0;
app.on("update", function (dt) {
time += dt;
angle = (angle + dt * 10) % 360;

// Rotate the boxes
shape.setEulerAngles(angle, angle * 2, angle * 4);
shape.render.meshInstances[0].setParameter('uIndex', Math.floor(time) % 4);
});
data.on('mipmaps:set', (/** @type {number} */ value) => {
groundMaterial.setParameter("uDiffuseMap", value ? mipmapTextureArray : textureArray);
});
});
return app;
}


export class TextureArrayExample {
static CATEGORY = 'Graphics';
static NAME = 'Texture Array';
static WEBGPU_ENABLED = false;

static FILES = {
'shader.vert': /* glsl */`
attribute vec3 aPosition;
attribute vec2 aUv0;

uniform mat4 matrix_model;
uniform mat4 matrix_viewProjection;

varying vec2 vUv0;

void main(void)
{
vUv0 = aUv0;
gl_Position = matrix_viewProjection * matrix_model * vec4(aPosition, 1.0);
}`,
'shader.frag': /* glsl */`
precision mediump float;

varying vec2 vUv0;
uniform float uIndex;

uniform mediump sampler2DArray uDiffuseMap;

void main(void)
{
gl_FragColor = texture(uDiffuseMap, vec3(vUv0, uIndex));
}`,
'ground.frag': /* glsl */`
precision mediump float;

varying vec2 vUv0;

uniform mediump sampler2DArray uDiffuseMap;

void main(void)
{
gl_FragColor = texture(uDiffuseMap, vec3(vUv0, step(vUv0.x, 0.5) + 2.0 * step(vUv0.y, 0.5)));
}`
};
static controls = controls;
static example = example;
}
1 change: 1 addition & 0 deletions src/platform/graphics/constants.js
Expand Up @@ -1194,6 +1194,7 @@ export const UNIFORMTYPE_VEC2ARRAY = 21;
export const UNIFORMTYPE_VEC3ARRAY = 22;
export const UNIFORMTYPE_VEC4ARRAY = 23;
export const UNIFORMTYPE_MAT4ARRAY = 24;
export const UNIFORMTYPE_TEXTURE2D_ARRAY = 25;

export const uniformTypeToName = [
'bool',
Expand Down