Skip to content

Commit

Permalink
Merge pull request #14983 from puckipedia/webgl-tile-fixes
Browse files Browse the repository at this point in the history
WebGLTile: Properly render semi-transparent tiles
  • Loading branch information
tschaub committed Aug 8, 2023
2 parents 0981db7 + 09c83f3 commit c2409a4
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 79 deletions.
11 changes: 11 additions & 0 deletions src/ol/array.js
Expand Up @@ -49,6 +49,17 @@ export function ascending(a, b) {
return a > b ? 1 : a < b ? -1 : 0;
}

/**
* Compare function sorting arrays in descending order. Safe to use for numeric values.
* @param {*} a The first object to be compared.
* @param {*} b The second object to be compared.
* @return {number} A negative number, zero, or a positive number as the first
* argument is greater than, equal to, or less than the second.
*/
export function descending(a, b) {
return a < b ? 1 : a > b ? -1 : 0;
}

/**
* {@link module:ol/tilegrid/TileGrid~TileGrid#getZForResolution} can use a function
* of this type to determine which nearest resolution to use.
Expand Down
4 changes: 0 additions & 4 deletions src/ol/layer/WebGLTile.js
Expand Up @@ -274,10 +274,6 @@ function parseStyle(style, bandCount) {
${pipeline.join('\n')}
if (color.a == 0.0) {
discard;
}
gl_FragColor = color;
gl_FragColor.rgb *= gl_FragColor.a;
gl_FragColor *= ${Uniforms.TRANSITION_ALPHA};
Expand Down
164 changes: 100 additions & 64 deletions src/ol/renderer/webgl/TileLayerBase.js
Expand Up @@ -8,7 +8,6 @@ import TileRange from '../../TileRange.js';
import TileState from '../../TileState.js';
import WebGLLayerRenderer from './Layer.js';
import {abstract, getUid} from '../../util.js';
import {ascending} from '../../array.js';
import {create as createMat4} from '../../vec/mat4.js';
import {
createOrUpdate as createTileCoord,
Expand All @@ -21,6 +20,7 @@ import {
scale as scaleTransform,
translate as translateTransform,
} from '../../transform.js';
import {descending} from '../../array.js';
import {fromUserExtent} from '../../proj.js';
import {getIntersection, isEmpty} from '../../extent.js';
import {toSize} from '../../size.js';
Expand Down Expand Up @@ -48,7 +48,7 @@ const empty = {};
* @return {number} A depth value.
*/
function depthForZ(z) {
return 2 * (1 - 1 / (z + 1)) - 1;
return 1 / (z + 2);
}

/**
Expand Down Expand Up @@ -409,7 +409,7 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
* @protected
*/
beforeTilesRender(frameState, tilesWithAlpha) {
this.helper.prepareDraw(this.frameState, !tilesWithAlpha);
this.helper.prepareDraw(this.frameState, !tilesWithAlpha, true);
}

/**
Expand Down Expand Up @@ -440,6 +440,79 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
alpha
) {}

drawTile_(
frameState,
tileRepresentation,
tileZ,
gutter,
extent,
alphaLookup,
tileGrid
) {
if (!tileRepresentation.loaded) {
return;
}
const tile = tileRepresentation.tile;
const tileCoord = tile.tileCoord;
const tileCoordKey = getTileCoordKey(tileCoord);
const alpha = tileCoordKey in alphaLookup ? alphaLookup[tileCoordKey] : 1;

const tileResolution = tileGrid.getResolution(tileZ);
const tileSize = toSize(tileGrid.getTileSize(tileZ), this.tempSize_);
const tileOrigin = tileGrid.getOrigin(tileZ);
const tileExtent = tileGrid.getTileCoordExtent(tileCoord);
// tiles with alpha are rendered last to allow blending
const depth = alpha < 1 ? -1 : depthForZ(tileZ);
if (alpha < 1) {
frameState.animate = true;
}

const viewState = frameState.viewState;
const centerX = viewState.center[0];
const centerY = viewState.center[1];

const tileWidthWithGutter = tileSize[0] + 2 * gutter;
const tileHeightWithGutter = tileSize[1] + 2 * gutter;

const aspectRatio = tileWidthWithGutter / tileHeightWithGutter;

const centerI = (centerX - tileOrigin[0]) / (tileSize[0] * tileResolution);
const centerJ = (tileOrigin[1] - centerY) / (tileSize[1] * tileResolution);

const tileScale = viewState.resolution / tileResolution;

const tileCenterI = tileCoord[1];
const tileCenterJ = tileCoord[2];

resetTransform(this.tileTransform_);
scaleTransform(
this.tileTransform_,
2 / ((frameState.size[0] * tileScale) / tileWidthWithGutter),
-2 / ((frameState.size[1] * tileScale) / tileWidthWithGutter)
);
rotateTransform(this.tileTransform_, viewState.rotation);
scaleTransform(this.tileTransform_, 1, 1 / aspectRatio);
translateTransform(
this.tileTransform_,
(tileSize[0] * (tileCenterI - centerI) - gutter) / tileWidthWithGutter,
(tileSize[1] * (tileCenterJ - centerJ) - gutter) / tileHeightWithGutter
);

this.renderTile(
/** @type {TileRepresentation} */ (tileRepresentation),
this.tileTransform_,
frameState,
extent,
tileResolution,
tileSize,
tileOrigin,
tileExtent,
depth,
gutter,
alpha
);
}

/**
* Render the layer.
* @param {import("../../Map.js").FrameState} frameState Frame state.
Expand Down Expand Up @@ -564,77 +637,40 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
this.beforeTilesRender(frameState, blend);

const representationsByZ = tileRepresentationLookup.representationsByZ;
const zs = Object.keys(representationsByZ).map(Number).sort(ascending);

const zs = Object.keys(representationsByZ).map(Number).sort(descending);
for (let j = 0, jj = zs.length; j < jj; ++j) {
const tileZ = zs[j];
for (const tileRepresentation of representationsByZ[tileZ]) {
if (!tileRepresentation.loaded) {
continue;
}
const tile = tileRepresentation.tile;
const tileCoord = tile.tileCoord;
const tileCoord = tileRepresentation.tile.tileCoord;
const tileCoordKey = getTileCoordKey(tileCoord);
const alpha =
tileCoordKey in alphaLookup ? alphaLookup[tileCoordKey] : 1;

if (alpha < 1) {
frameState.animate = true;
if (tileCoordKey in alphaLookup) {
continue;
}

const tileResolution = tileGrid.getResolution(tileZ);
const tileSize = toSize(tileGrid.getTileSize(tileZ), this.tempSize_);
const tileOrigin = tileGrid.getOrigin(tileZ);
const tileExtent = tileGrid.getTileCoordExtent(tileCoord);
const depth = depthForZ(tileZ);

const viewState = frameState.viewState;
const centerX = viewState.center[0];
const centerY = viewState.center[1];

const tileWidthWithGutter = tileSize[0] + 2 * gutter;
const tileHeightWithGutter = tileSize[1] + 2 * gutter;

const aspectRatio = tileWidthWithGutter / tileHeightWithGutter;

const centerI =
(centerX - tileOrigin[0]) / (tileSize[0] * tileResolution);
const centerJ =
(tileOrigin[1] - centerY) / (tileSize[1] * tileResolution);

const tileScale = viewState.resolution / tileResolution;

const tileCenterI = tileCoord[1];
const tileCenterJ = tileCoord[2];

resetTransform(this.tileTransform_);
scaleTransform(
this.tileTransform_,
2 / ((frameState.size[0] * tileScale) / tileWidthWithGutter),
-2 / ((frameState.size[1] * tileScale) / tileWidthWithGutter)
);
rotateTransform(this.tileTransform_, viewState.rotation);
scaleTransform(this.tileTransform_, 1, 1 / aspectRatio);
translateTransform(
this.tileTransform_,
(tileSize[0] * (tileCenterI - centerI) - gutter) /
tileWidthWithGutter,
(tileSize[1] * (tileCenterJ - centerJ) - gutter) /
tileHeightWithGutter
this.drawTile_(
frameState,
tileRepresentation,
tileZ,
gutter,
extent,
alphaLookup,
tileGrid
);
}
}

this.renderTile(
/** @type {TileRepresentation} */ (tileRepresentation),
this.tileTransform_,
for (const tileRepresentation of representationsByZ[z]) {
const tileCoord = tileRepresentation.tile.tileCoord;
const tileCoordKey = getTileCoordKey(tileCoord);
if (tileCoordKey in alphaLookup) {
this.drawTile_(
frameState,
extent,
tileResolution,
tileSize,
tileOrigin,
tileExtent,
depth,
tileRepresentation,
z,
gutter,
alpha
extent,
alphaLookup,
tileGrid
);
}
}
Expand Down
32 changes: 28 additions & 4 deletions src/ol/webgl/Helper.js
Expand Up @@ -551,8 +551,9 @@ class WebGLHelper extends Disposable {
* subsequent draw calls.
* @param {import("../Map.js").FrameState} frameState current frame state
* @param {boolean} [disableAlphaBlend] If true, no alpha blending will happen.
* @param {boolean} [enableDepth] If true, enables depth testing.
*/
prepareDraw(frameState, disableAlphaBlend) {
prepareDraw(frameState, disableAlphaBlend, enableDepth) {
const gl = this.gl_;
const canvas = this.getCanvas();
const size = frameState.size;
Expand All @@ -576,10 +577,18 @@ class WebGLHelper extends Disposable {
gl.bindTexture(gl.TEXTURE_2D, null);

gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.depthRange(0.0, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, disableAlphaBlend ? gl.ZERO : gl.ONE_MINUS_SRC_ALPHA);
if (enableDepth) {
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
} else {
gl.disable(gl.DEPTH_TEST);
}
}

/**
Expand All @@ -602,18 +611,33 @@ class WebGLHelper extends Disposable {
* @param {import("../Map.js").FrameState} frameState current frame state
* @param {import("./RenderTarget.js").default} renderTarget Render target to draw to
* @param {boolean} [disableAlphaBlend] If true, no alpha blending will happen.
* @param {boolean} [enableDepth] If true, enables depth testing.
*/
prepareDrawToRenderTarget(frameState, renderTarget, disableAlphaBlend) {
prepareDrawToRenderTarget(
frameState,
renderTarget,
disableAlphaBlend,
enableDepth
) {
const gl = this.gl_;
const size = renderTarget.getSize();

gl.bindFramebuffer(gl.FRAMEBUFFER, renderTarget.getFramebuffer());
gl.bindRenderbuffer(gl.RENDERBUFFER, renderTarget.getDepthbuffer());
gl.viewport(0, 0, size[0], size[1]);
gl.bindTexture(gl.TEXTURE_2D, renderTarget.getTexture());
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.depthRange(0.0, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, disableAlphaBlend ? gl.ZERO : gl.ONE_MINUS_SRC_ALPHA);
if (enableDepth) {
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
} else {
gl.disable(gl.DEPTH_TEST);
}
}

/**
Expand Down
27 changes: 26 additions & 1 deletion src/ol/webgl/PostProcessingPass.js
Expand Up @@ -114,6 +114,7 @@ class WebGLPostProcessingPass {
this.renderTargetTextureSize_ = null;

this.frameBuffer_ = gl.createFramebuffer();
this.depthBuffer_ = gl.createRenderbuffer();

// compile the program for the frame buffer
// TODO: make compilation errors show up
Expand Down Expand Up @@ -201,6 +202,7 @@ class WebGLPostProcessingPass {

// rendering goes to my buffer
gl.bindFramebuffer(gl.FRAMEBUFFER, this.getFrameBuffer());
gl.bindRenderbuffer(gl.RENDERBUFFER, this.getDepthBuffer());
gl.viewport(0, 0, textureSize[0], textureSize[1]);

// if size has changed: adjust canvas & render target texture
Expand Down Expand Up @@ -243,6 +245,19 @@ class WebGLPostProcessingPass {
this.renderTargetTexture_,
0
);

gl.renderbufferStorage(
gl.RENDERBUFFER,
gl.DEPTH_COMPONENT16,
textureSize[0],
textureSize[1]
);
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.RENDERBUFFER,
this.depthBuffer_
);
}
}

Expand Down Expand Up @@ -273,13 +288,15 @@ class WebGLPostProcessingPass {
const attributes = gl.getContextAttributes();
if (attributes && attributes.preserveDrawingBuffer) {
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}

frameState.renderTargets[canvasId] = true;
}
}

gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
Expand Down Expand Up @@ -321,6 +338,14 @@ class WebGLPostProcessingPass {
return this.frameBuffer_;
}

/**
* @return {WebGLRenderbuffer} Depth buffer
* @api
*/
getDepthBuffer() {
return this.depthBuffer_;
}

/**
* Sets the custom uniforms based on what was given in the constructor.
* @param {import("../Map.js").FrameState} frameState Frame state.
Expand Down

0 comments on commit c2409a4

Please sign in to comment.