arcgis-custom-rendering
Skill0 starsChanged 7 months agoMITView on GitHub
Create custom layer types with WebGL rendering using RenderNode, BaseLayerViewGL2D, BaseTileLayer, and BaseDynamicLayer. Use for advanced visualizations, custom data sources, and post-processing effects.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- geoai-latam/SKILL_JS5_ARCGIStheir own version, first seen Nov 8, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- geoai-latam/SKILL_JS5_ARCGIS★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository47 other files it gives its agents.
- AGENTS.mdAGENTS.md
- skills/arcgis-ai-components/AGENTS.mdAGENTS.md
- skills/arcgis-authentication/AGENTS.mdAGENTS.md
- skills/arcgis-charts/AGENTS.mdAGENTS.md
- skills/arcgis-coding-components/AGENTS.mdAGENTS.md
- skills/arcgis-core-maps/AGENTS.mdAGENTS.md
- skills/arcgis-editing/AGENTS.mdAGENTS.md
- skills/arcgis-embeddable-maps/AGENTS.mdAGENTS.md
- skills/arcgis-interaction/AGENTS.mdAGENTS.md
- skills/arcgis-layers/AGENTS.mdAGENTS.md
- skills/arcgis-starter-app/AGENTS.mdAGENTS.md
- skills/arcgis-visualization/AGENTS.mdAGENTS.md
- skills/arcgis-widgets-ui/AGENTS.mdAGENTS.md
- arcgis-3d-layersSkill
- arcgis-advanced-layersSkill
- arcgis-ai-componentsSkill
- arcgis-arcadeSkill
- arcgis-authenticationSkill
- arcgis-chartsSkill
- arcgis-cim-symbolsSkill
- arcgis-coding-componentsSkill
- arcgis-coordinates-projectionSkill
- arcgis-core-mapsSkill
- arcgis-core-utilitiesSkill
- arcgis-editingSkill
- arcgis-embeddable-mapsSkill
- arcgis-feature-effectsSkill
- arcgis-geometry-operationsSkill
- arcgis-imagerySkill
- arcgis-interactionSkill
- arcgis-knowledge-graphsSkill
- arcgis-layersSkill
- arcgis-map-toolsSkill
- arcgis-performanceSkill
- arcgis-popup-templatesSkill
- arcgis-portal-contentSkill
- arcgis-rest-servicesSkill
- arcgis-scene-environmentSkill
- arcgis-smart-mappingSkill
- arcgis-spatial-analysisSkill
- arcgis-starter-appSkill
- arcgis-tables-formsSkill
- arcgis-time-animationSkill
- arcgis-utility-networksSkill
- arcgis-visualizationSkill
- arcgis-widgets-advancedSkill
- arcgis-widgets-uiSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 1 is distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
Use this skill for creating custom layers, WebGL rendering in 2D/3D, custom tile sources, and post-processing effects in SceneView.
- Very specific: line 468
Memory management: Always clean up WebGL resources (programs, buffers, VAOs, textures) in detach() or destroy().
- Very specific: line 470
Abort signals: Always pass options?.signal to esriRequest for proper cancellation.
- Very specific: line 472
Coordinate systems: Use displayViewMatrix3 for pixel-aligned rendering, viewMatrix3 for map-coordinate rendering.
- Very specific: line 476
createSubclass pattern: Use BaseTileLayer.createSubclass({...}) — do not use ES6 class extends.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
ArcGIS Custom Rendering
Use this skill for creating custom layers, WebGL rendering in 2D/3D, custom tile sources, and post-processing effects in SceneView.
Import Patterns
ESM (npm)
// 3D custom rendering
import RenderNode from "@arcgis/core/views/3d/webgl/RenderNode.js";
// 2D custom rendering
import BaseLayerView2D from "@arcgis/core/views/2d/layers/BaseLayerView2D.js";
import BaseLayerViewGL2D from "@arcgis/core/views/2d/layers/BaseLayerViewGL2D.js";
// Custom layer bases
import BaseTileLayer from "@arcgis/core/layers/BaseTileLayer.js";
import BaseDynamicLayer from "@arcgis/core/layers/BaseDynamicLayer.js";
import BaseElevationLayer from "@arcgis/core/layers/BaseElevationLayer.js";
import Layer from "@arcgis/core/layers/Layer.js";
// Utilities
import esriRequest from "@arcgis/core/request.js";
import Color from "@arcgis/core/Color.js";CDN (dynamic import)
const RenderNode = await $arcgis.import(
"@arcgis/core/views/3d/webgl/RenderNode.js",
);
const BaseTileLayer = await $arcgis.import(
"@arcgis/core/layers/BaseTileLayer.js",
);
const BaseLayerViewGL2D = await $arcgis.import(
"@arcgis/core/views/2d/layers/BaseLayerViewGL2D.js",
);Custom Rendering Summary
| Class | 2D | 3D | Typical Use |
RenderNode | — | Yes | Post-processing effects in SceneView |
BaseLayerView2D | Yes | — | Custom 2D canvas rendering |
BaseLayerViewGL2D | Yes | — | WebGL-accelerated 2D rendering |
BaseTileLayer | Yes | Yes | Custom tile sources and processing |
BaseDynamicLayer | Yes | Yes | On-demand dynamic rendering |
BaseElevationLayer | — | Yes | Custom elevation/terrain data |
RenderNode (3D Post-Processing)
RenderNode creates custom rendering passes in SceneView's WebGL 2 pipeline.
Basic RenderNode
const LuminanceRenderNode = RenderNode.createSubclass({
constructor() {
this.consumes = { required: ["composite-color"] };
this.produces = "composite-color";
},
render(inputs) {
const input = inputs.find(({ name }) => name === "composite-color");
const colorTexture = input.getTexture();
const output = this.acquireOutputFramebuffer();
const gl = this.gl;
gl.useProgram(this.program);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, colorTexture);
gl.uniform1i(this.texLocation, 0);
gl.bindVertexArray(this.vao);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// Preserve depth from input
output.attachDepth(input.getAttachment(gl.DEPTH_STENCIL_ATTACHMENT));
return output;
},
destroy() {
if (this.program) this.gl?.deleteProgram(this.program);
if (this.vao) this.gl?.deleteVertexArray(this.vao);
},
});
// Add to SceneView
const view = new SceneView({ container: "viewDiv", map });
view.when(() => {
new LuminanceRenderNode({ view });
});RenderNode Key Concepts
| Property/Method | Description |
consumes | Render targets this node needs as input (e.g., { required: ["composite-color"] }) |
produces | Name of the render target produced (set null to disable) |
gl | WebGL 2 context (read-only) |
view | Reference to SceneView |
render(inputs) | Override to implement rendering logic |
acquireOutputFramebuffer() | Get framebuffer to render into |
requestRender() | Request a new frame |
RenderNode Enable/Disable
const MyRenderNode = RenderNode.createSubclass({
properties: {
enabled: {
get() {
return this.produces != null;
},
set(value) {
this.produces = value ? "composite-color" : null;
this.requestRender();
},
},
},
});GLSL ES 3.0 Fragment Shader Example
#version 300 es
precision highp float;
out lowp vec4 fragColor;
in vec2 uv;
uniform sampler2D colorTex;
void main() {
vec4 color = texture(colorTex, uv);
// Luminance (grayscale) conversion
fragColor = vec4(vec3(dot(color.rgb, vec3(0.2126, 0.7152, 0.0722))), color.a);
}Custom TileLayer
Basic Custom TileLayer
const TintLayer = BaseTileLayer.createSubclass({
properties: {
urlTemplate: null,
tint: {
value: null,
type: Color,
},
},
getTileUrl(level, row, col) {
return this.urlTemplate
.replace("{z}", level)
.replace("{x}", col)
.replace("{y}", row);
},
fetchTile(level, row, col, options) {
const url = this.getTileUrl(level, row, col);
return esriRequest(url, {
responseType: "image",
signal: options?.signal,
}).then((response) => {
const image = response.data;
const width = this.tileInfo.size[0];
const height = this.tileInfo.size[0];
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
if (this.tint) {
context.fillStyle = this.tint.toCss();
context.fillRect(0, 0, width, height);
context.globalCompositeOperation = "difference";
}
context.drawImage(image, 0, 0, width, height);
return canvas;
});
},
});
const customLayer = new TintLayer({
urlTemplate: "https://tile.opentopomap.org/{z}/{x}/{y}.png",
tint: new Color("#132178"),
title: "Custom Tinted Layer",
});
map.add(customLayer);BaseTileLayer Key Methods
| Method | Description |
getTileUrl(level, row, col) | Return URL string for a tile |
fetchTile(level, row, col, options) | Fetch and optionally process tile; return canvas or image |
load() | Called when layer loads |
refresh() | Reload all tiles |
Watch Property Changes
const CustomLayer = BaseTileLayer.createSubclass({
properties: {
customProperty: null,
},
initialize() {
reactiveUtils.watch(
() => this.customProperty,
() => this.refresh(),
);
},
});Custom DynamicLayer
const CustomDynamicLayer = BaseDynamicLayer.createSubclass({
properties: {
data: null,
},
getImageUrl(extent, width, height) {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const context = canvas.getContext("2d");
// Convert geo coords to pixel coords
const xScale = width / (extent.xmax - extent.xmin);
const yScale = height / (extent.ymax - extent.ymin);
this.data.forEach((point) => {
const x = (point.x - extent.xmin) * xScale;
const y = height - (point.y - extent.ymin) * yScale;
context.beginPath();
context.arc(x, y, 5, 0, Math.PI * 2);
context.fillStyle = "red";
context.fill();
});
return canvas.toDataURL("image/png");
},
});Custom Elevation Layer
const ExaggeratedElevationLayer = BaseElevationLayer.createSubclass({
properties: {
exaggeration: 2,
},
load() {
this._elevation = new ElevationLayer({
url: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer",
});
this.addResolvingPromise(this._elevation.load());
},
fetchTile(level, row, col, options) {
return this._elevation.fetchTile(level, row, col, options).then((data) => {
const exaggeration = this.exaggeration;
for (let i = 0; i < data.values.length; i++) {
data.values[i] *= exaggeration;
}
return data;
});
},
});WebGL Custom 2D Rendering (BaseLayerViewGL2D)
Custom LayerView with WebGL
const CustomGLLayerView = BaseLayerViewGL2D.createSubclass({
attach() {
const gl = this.context;
this.program = this.createShaderProgram(gl);
this.buffer = gl.createBuffer();
this.vao = gl.createVertexArray();
},
render(renderParameters) {
const gl = renderParameters.context;
const { displayViewMatrix3, size } = renderParameters.state;
gl.useProgram(this.program);
gl.uniformMatrix3fv(
gl.getUniformLocation(this.program, "u_matrix"),
false,
displayViewMatrix3,
);
gl.bindVertexArray(this.vao);
gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount);
},
detach() {
const gl = this.context;
gl.deleteProgram(this.program);
gl.deleteBuffer(this.buffer);
gl.deleteVertexArray(this.vao);
},
});Custom Layer with createLayerView
const CustomLayer = Layer.createSubclass({
createLayerView(view) {
if (view.type === "2d") {
return new CustomGLLayerView({ view, layer: this });
}
},
});BaseLayerView2D (Canvas-based)
const CustomLayerView2D = BaseLayerView2D.createSubclass({
attach() {
// Initialize resources
},
render(renderParameters) {
const { context, state } = renderParameters;
const ctx = context; // CanvasRenderingContext2D
// state.size — viewport size [width, height]
// state.resolution — map units per pixel
// state.extent — current visible extent
const screenPoint = state.toScreen(state.center);
ctx.fillStyle = "red";
ctx.fillRect(screenPoint[0] - 5, screenPoint[1] - 5, 10, 10);
},
detach() {
// Clean up resources
},
});LayerView Lifecycle
| Method | Description |
attach() | Called when LayerView is added to view — init resources |
render(renderParameters) | Called every frame to draw content |
detach() | Called when LayerView is removed — clean up resources |
requestRender() | Request a re-render on next frame |
LERC Decoding
import * as Lerc from "https://cdn.jsdelivr.net/npm/lerc@4.0.4/+esm";
const LercLayer = BaseTileLayer.createSubclass({
properties: {
urlTemplate: null,
minValue: 0,
maxValue: 1000,
},
fetchTile(level, row, col, options) {
const url = this.urlTemplate
.replace("{z}", level)
.replace("{x}", col)
.replace("{y}", row);
return esriRequest(url, {
responseType: "array-buffer",
signal: options?.signal,
}).then((response) => {
const decodedPixels = Lerc.decode(response.data);
const { width, height, pixels } = decodedPixels;
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
const imageData = ctx.createImageData(width, height);
for (let i = 0; i < pixels[0].length; i++) {
const value = pixels[0][i];
const normalized =
(value - this.minValue) / (this.maxValue - this.minValue);
imageData.data[i * 4] = Math.round(normalized * 255);
imageData.data[i * 4 + 1] = 0;
imageData.data[i * 4 + 2] = Math.round((1 - normalized) * 255);
imageData.data[i * 4 + 3] = 255;
}
ctx.putImageData(imageData, 0, 0);
return canvas;
});
},
});Custom Blend Layer
const BlendLayer = BaseTileLayer.createSubclass({
properties: {
multiplyLayers: null,
},
load() {
this.multiplyLayers.forEach((layer) => {
this.addResolvingPromise(layer.load());
});
},
fetchTile(level, row, col, options) {
const tilePromises = this.multiplyLayers.map((layer) =>
layer.fetchTile(level, row, col, options),
);
return Promise.all(tilePromises).then((images) => {
const width = this.tileInfo.size[0];
const height = this.tileInfo.size[1];
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(images[0], 0, 0, width, height);
ctx.globalCompositeOperation = "multiply";
for (let i = 1; i < images.length; i++) {
ctx.drawImage(images[i], 0, 0, width, height);
}
return canvas;
});
},
});Common Pitfalls
- CORS: External tile servers must support CORS for canvas-based processing.
- Canvas size: Match tile size exactly with
tileInfo.size[0]— mismatches cause rendering artifacts. - Memory management: Always clean up WebGL resources (programs, buffers, VAOs, textures) in
detach()ordestroy(). - Abort signals: Always pass
options?.signaltoesriRequestfor proper cancellation. - Coordinate systems: Use
displayViewMatrix3for pixel-aligned rendering,viewMatrix3for map-coordinate rendering. - RenderNode WebGL version: SceneView uses WebGL 2 — shaders must use
#version 300 es. - createSubclass pattern: Use
BaseTileLayer.createSubclass({...})— do not use ES6class extends.
Reference Samples
layers-custom-tilelayer— Custom tinted tile layerlayers-custom-blendlayer— Multi-layer blendinglayers-custom-lerc-2d— LERC data decodinglayers-custom-elevation-exaggerated— Elevation exaggerationlayers-custom-dynamiclayer— Dynamic on-demand renderingcustom-gl-tiles— WebGL tile renderingcustom-gl-visuals— Complex WebGL visualizationscustom-gl-animated-lines— Animated line renderingcustom-render-node-color— RenderNode color post-processingcustom-render-node-dof— Depth of field effectcustom-render-node-windmills— 3D geometry renderingcustom-lv-deckgl— deck.gl integration
Related Skills
arcgis-3d-layers— SceneLayer, PointCloudLayer, VoxelLayerarcgis-scene-environment— Lighting, weather, atmospherearcgis-layers— Standard layer typesarcgis-performance— Rendering optimization
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

