Related
If you look at the following snippet in Safari (taken from https://webglfundamentals.org/webgl/lessons/webgl-shadows.html), you will notice it looks different than in other browsers, because the depth texture is always completely black:
'use strict';
function main() {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
return;
}
const ext = gl.getExtension('WEBGL_depth_texture');
if (!ext) {
return alert('need WEBGL_depth_texture'); // eslint-disable-line
}
// setup GLSL programs
const textureProgramInfo = webglUtils.createProgramInfo(gl, ['3d-vertex-shader', '3d-fragment-shader']);
const colorProgramInfo = webglUtils.createProgramInfo(gl, ['color-vertex-shader', 'color-fragment-shader']);
const sphereBufferInfo = primitives.createSphereBufferInfo(
gl,
1, // radius
32, // subdivisions around
24, // subdivisions down
);
const planeBufferInfo = primitives.createPlaneBufferInfo(
gl,
20, // width
20, // height
1, // subdivisions across
1, // subdivisions down
);
const cubeBufferInfo = primitives.createCubeBufferInfo(
gl,
2, // size
);
const cubeLinesBufferInfo = webglUtils.createBufferInfoFromArrays(gl, {
position: [
-1, -1, -1,
1, -1, -1,
-1, 1, -1,
1, 1, -1,
-1, -1, 1,
1, -1, 1,
-1, 1, 1,
1, 1, 1,
],
indices: [
0, 1,
1, 3,
3, 2,
2, 0,
4, 5,
5, 7,
7, 6,
6, 4,
0, 4,
1, 5,
3, 7,
2, 6,
],
});
// make a 8x8 checkerboard texture
const checkerboardTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, checkerboardTexture);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.LUMINANCE, // internal format
8, // width
8, // height
0, // border
gl.LUMINANCE, // format
gl.UNSIGNED_BYTE, // type
new Uint8Array([ // data
0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
]));
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
const depthTexture = gl.createTexture();
const depthTextureSize = 512;
gl.bindTexture(gl.TEXTURE_2D, depthTexture);
gl.texImage2D(
gl.TEXTURE_2D, // target
0, // mip level
gl.DEPTH_COMPONENT, // internal format
depthTextureSize, // width
depthTextureSize, // height
0, // border
gl.DEPTH_COMPONENT, // format
gl.UNSIGNED_INT, // type
null); // data
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
const depthFramebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, depthFramebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER, // target
gl.DEPTH_ATTACHMENT, // attachment point
gl.TEXTURE_2D, // texture target
depthTexture, // texture
0); // mip level
function degToRad(d) {
return d * Math.PI / 180;
}
const settings = {
cameraX: 6,
cameraY: 5,
posX: 2.5,
posY: 4.8,
posZ: 4.3,
targetX: 2.5,
targetY: 0,
targetZ: 3.5,
projWidth: 1,
projHeight: 1,
perspective: true,
fieldOfView: 120,
};
webglLessonsUI.setupUI(document.querySelector('#ui'), settings, [
{ type: 'slider', key: 'cameraX', min: -10, max: 10, change: render, precision: 2, step: 0.001, },
{ type: 'slider', key: 'cameraY', min: 1, max: 20, change: render, precision: 2, step: 0.001, },
{ type: 'slider', key: 'posX', min: -10, max: 10, change: render, precision: 2, step: 0.001, },
{ type: 'slider', key: 'posY', min: 1, max: 20, change: render, precision: 2, step: 0.001, },
{ type: 'slider', key: 'posZ', min: 1, max: 20, change: render, precision: 2, step: 0.001, },
{ type: 'slider', key: 'targetX', min: -10, max: 10, change: render, precision: 2, step: 0.001, },
{ type: 'slider', key: 'targetY', min: 0, max: 20, change: render, precision: 2, step: 0.001, },
{ type: 'slider', key: 'targetZ', min: -10, max: 20, change: render, precision: 2, step: 0.001, },
{ type: 'slider', key: 'projWidth', min: 0, max: 2, change: render, precision: 2, step: 0.001, },
{ type: 'slider', key: 'projHeight', min: 0, max: 2, change: render, precision: 2, step: 0.001, },
{ type: 'checkbox', key: 'perspective', change: render, },
{ type: 'slider', key: 'fieldOfView', min: 1, max: 179, change: render, },
]);
const fieldOfViewRadians = degToRad(60);
// Uniforms for each object.
const planeUniforms = {
u_colorMult: [0.5, 0.5, 1, 1], // lightblue
u_color: [1, 0, 0, 1],
u_texture: checkerboardTexture,
u_world: m4.translation(0, 0, 0),
};
const sphereUniforms = {
u_colorMult: [1, 0.5, 0.5, 1], // pink
u_color: [0, 0, 1, 1],
u_texture: checkerboardTexture,
u_world: m4.translation(2, 3, 4),
};
const cubeUniforms = {
u_colorMult: [0.5, 1, 0.5, 1], // lightgreen
u_color: [0, 0, 1, 1],
u_texture: checkerboardTexture,
u_world: m4.translation(3, 1, 0),
};
function drawScene(projectionMatrix, cameraMatrix, textureMatrix, programInfo) {
// Make a view matrix from the camera matrix.
const viewMatrix = m4.inverse(cameraMatrix);
gl.useProgram(programInfo.program);
// set uniforms that are the same for both the sphere and plane
// note: any values with no corresponding uniform in the shader
// are ignored.
webglUtils.setUniforms(programInfo, {
u_view: viewMatrix,
u_projection: projectionMatrix,
u_textureMatrix: textureMatrix,
u_projectedTexture: depthTexture,
});
// ------ Draw the sphere --------
// Setup all the needed attributes.
webglUtils.setBuffersAndAttributes(gl, programInfo, sphereBufferInfo);
// Set the uniforms unique to the sphere
webglUtils.setUniforms(programInfo, sphereUniforms);
// calls gl.drawArrays or gl.drawElements
webglUtils.drawBufferInfo(gl, sphereBufferInfo);
// ------ Draw the cube --------
// Setup all the needed attributes.
webglUtils.setBuffersAndAttributes(gl, programInfo, cubeBufferInfo);
// Set the uniforms unique to the cube
webglUtils.setUniforms(programInfo, cubeUniforms);
// calls gl.drawArrays or gl.drawElements
webglUtils.drawBufferInfo(gl, cubeBufferInfo);
// ------ Draw the plane --------
// Setup all the needed attributes.
webglUtils.setBuffersAndAttributes(gl, programInfo, planeBufferInfo);
// Set the uniforms unique to the cube
webglUtils.setUniforms(programInfo, planeUniforms);
// calls gl.drawArrays or gl.drawElements
webglUtils.drawBufferInfo(gl, planeBufferInfo);
}
// Draw the scene.
function render() {
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
// first draw from the POV of the light
const lightWorldMatrix = m4.lookAt(
[settings.posX, settings.posY, settings.posZ], // position
[settings.targetX, settings.targetY, settings.targetZ], // target
[0, 1, 0], // up
);
const lightProjectionMatrix = settings.perspective
? m4.perspective(
degToRad(settings.fieldOfView),
settings.projWidth / settings.projHeight,
0.5, // near
10) // far
: m4.orthographic(
-settings.projWidth / 2, // left
settings.projWidth / 2, // right
-settings.projHeight / 2, // bottom
settings.projHeight / 2, // top
0.5, // near
10); // far
// draw to the depth texture
gl.bindFramebuffer(gl.FRAMEBUFFER, depthFramebuffer);
gl.viewport(0, 0, depthTextureSize, depthTextureSize);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
drawScene(lightProjectionMatrix, lightWorldMatrix, m4.identity(), colorProgramInfo);
// now draw scene to the canvas projecting the depth texture into the scene
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
let textureMatrix = m4.identity();
textureMatrix = m4.translate(textureMatrix, 0.5, 0.5, 0.5);
textureMatrix = m4.scale(textureMatrix, 0.5, 0.5, 0.5);
textureMatrix = m4.multiply(textureMatrix, lightProjectionMatrix);
// use the inverse of this world matrix to make
// a matrix that will transform other positions
// to be relative this this world space.
textureMatrix = m4.multiply(
textureMatrix,
m4.inverse(lightWorldMatrix));
// Compute the projection matrix
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const projectionMatrix =
m4.perspective(fieldOfViewRadians, aspect, 1, 2000);
// Compute the camera's matrix using look at.
const cameraPosition = [settings.cameraX, settings.cameraY, 7];
const target = [0, 0, 0];
const up = [0, 1, 0];
const cameraMatrix = m4.lookAt(cameraPosition, target, up);
drawScene(projectionMatrix, cameraMatrix, textureMatrix, textureProgramInfo);
// ------ Draw the frustum ------
{
const viewMatrix = m4.inverse(cameraMatrix);
gl.useProgram(colorProgramInfo.program);
// Setup all the needed attributes.
webglUtils.setBuffersAndAttributes(gl, colorProgramInfo, cubeLinesBufferInfo);
// scale the cube in Z so it's really long
// to represent the texture is being projected to
// infinity
const mat = m4.multiply(
lightWorldMatrix, m4.inverse(lightProjectionMatrix));
// Set the uniforms we just computed
webglUtils.setUniforms(colorProgramInfo, {
u_color: [0, 0, 0, 1],
u_view: viewMatrix,
u_projection: projectionMatrix,
u_world: mat,
});
// calls gl.drawArrays or gl.drawElements
webglUtils.drawBufferInfo(gl, cubeLinesBufferInfo, gl.LINES);
}
}
render();
}
main();
#import url("https://webglfundamentals.org/webgl/resources/webgl-tutorials.css");
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
.gman-widget-value {
min-width: 5em;
}
<canvas id="canvas"></canvas>
<div id="uiContainer">
<div id="ui">
</div>
</div>
<!-- vertex shader -->
<script id="3d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec2 a_texcoord;
uniform mat4 u_projection;
uniform mat4 u_view;
uniform mat4 u_world;
uniform mat4 u_textureMatrix;
varying vec2 v_texcoord;
varying vec4 v_projectedTexcoord;
void main() {
// Multiply the position by the matrix.
vec4 worldPosition = u_world * a_position;
gl_Position = u_projection * u_view * worldPosition;
// Pass the texture coord to the fragment shader.
v_texcoord = a_texcoord;
v_projectedTexcoord = u_textureMatrix * worldPosition;
}
</script>
<!-- fragment shader -->
<script id="3d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// Passed in from the vertex shader.
varying vec2 v_texcoord;
varying vec4 v_projectedTexcoord;
uniform vec4 u_colorMult;
uniform sampler2D u_texture;
uniform sampler2D u_projectedTexture;
void main() {
vec3 projectedTexcoord = v_projectedTexcoord.xyz / v_projectedTexcoord.w;
bool inRange =
projectedTexcoord.x >= 0.0 &&
projectedTexcoord.x <= 1.0 &&
projectedTexcoord.y >= 0.0 &&
projectedTexcoord.y <= 1.0;
// the 'r' channel has the depth values
vec4 projectedTexColor = vec4(texture2D(u_projectedTexture, projectedTexcoord.xy).rrr, 1);
vec4 texColor = texture2D(u_texture, v_texcoord) * u_colorMult;
float projectedAmount = inRange ? 1.0 : 0.0;
gl_FragColor = mix(texColor, projectedTexColor, projectedAmount);
}
</script>
<!-- vertex shader -->
<script id="color-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
uniform mat4 u_projection;
uniform mat4 u_view;
uniform mat4 u_world;
void main() {
// Multiply the position by the matrices.
gl_Position = u_projection * u_view * u_world * a_position;
}
</script>
<!-- fragment shader -->
<script id="color-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
</script><!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and http://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="https://webglfundamentals.org/webgl/resources/webgl-lessons-ui.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/primitives.js"></script>
I've based my own implementations on this, and run into the same issue. WebGL returns a gl.INVALID_FRAMEBUFFER_OPERATION when calling getError after a call to gl.clea or to gl.drawElements. I assume something is wrong with the setup of the framebuffer, but nothing I tried helps. Thanks in advance!
For Safari pre version 15 you need to setup a color attachment even if though the sample doesn't use it
first create an RGBA/UNSIGNED_BYTE color attachment the same size as the depth texture
const dummy = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, dummy);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
depthTextureSize,
depthTextureSize,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
null,
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
Then attach it to the framebuffer
const depthFramebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, depthFramebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER, // target
gl.DEPTH_ATTACHMENT, // attachment point
gl.TEXTURE_2D, // texture target
depthTexture, // texture
0); // mip level
// ---- added ----
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
dummy,
0,
)
Unfortunately this is not a bug in Safari. It's a reflection of unfortunate choices of the OpenGL ES 2.0 spec which says
4.4.5 Framebuffer Completeness
...
Framebuffer Completeness
...
The framebuffer object target is said to be framebuffer complete if it is the window-system-provided framebuffer, or if all the following conditons are true:
In this subsection, each rule is followed by an error enum in bold
...
The combination of internal formats of the attached images does not violate an implementation-dependent set of restrictions.
FRAMEBUFFER_UNSUPPORTED
In other words, in OpenGL ES 2.0 NO COMBINATIONS OF FRAMEBUFFER ATTACHMENTS ARE REQUIRED TO WORK!!!!!!!
The WebGL spec itself added the requirement that 3 combinations are required to work
6.8 Framebuffer Object Attachments
...
The following combinations of framebuffer object attachments, when all of the attachments are framebuffer attachment complete, non-zero, and have the same width and height, must result in the framebuffer being framebuffer complete:
COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture
COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture + DEPTH_ATTACHMENT = DEPTH_COMPONENT16 renderbuffer
COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture + DEPTH_STENCIL_ATTACHMENT = DEPTH_STENCIL renderbuffer
So, following the 2 specs, even though the WEBGL_depth_texture extension adds depth textures, none of them are required to work as attachments in framebuffers with or without any other attachments!!!
Yes, you read that right. The WEBGL_depth_texture extension spec says you can create them and attach them. The OpenGL ES 2.0 spec says they are not required to work as attachments or rather that it's up to the implementation whether any combinations work. WebGL lists 3 combinations that are required to work but those 3 combinations do not include depth textures, only depth renderbuffers.
Fortunately it appears they do work everywhere if you add an RGBA/UNSIGNED_BYTE color attachment.
On a positive note, OpenGL ES 3.0 and WebGL2 list many more combinations that are required to work.
I'm drawing two polylines (which are lines in the sample) in webgl with enabled blending.
gl.uniform4f(colorUniformLocation, 0, 0, 0, 0.3);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.2, -1, 0.2, 1,]), gl.STATIC_DRAW);
gl.drawArrays(gl.LINE_STRIP, 0, 2);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, -1, 0, 1,0, -1, 0, 1]), gl.STATIC_DRAW);
gl.drawArrays(gl.LINE_STRIP, 0, 4);
Here is the codepen sample.
The left line are crossed with itself and it seems like it blends with itself, so as result it becomes darker.
I would like the blend to work between those polylines, but don't want a polyline to blend with itself. Is there a way to do it?
One way would be to use the stencil test. You'd set webgl so that the stencil stores a certain value when a pixel is drawn and you'd set the stencil test so it fails if it sees that value.
First an example that draws 2 sets of 2 overlapping triangles with blending on. The pairs will get darker where they overlap
function main() {
const m4 = twgl.m4;
const gl = document
.querySelector('canvas')
.getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`;
// compile shader, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
gl.useProgram(programInfo.program);
// create a buffer and put data in it
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-0.5, -0.2,
0.5, -0.2,
0.5, 0.2,
-0.2, -0.5,
-0.2, 0.5,
0.2, 0.5,
],
},
});
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.uniform??
twgl.setUniforms(programInfo, {
color: [0.5, 0, 0, 0.5],
matrix: m4.identity(),
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
twgl.setUniforms(programInfo, {
color: [0, 0, 0.5, 0.5],
matrix: m4.rotateZ(
m4.translation([-0.1, 0.2, 0]),
Math.PI * 1.2),
});
twgl.drawBufferInfo(gl, bufferInfo);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Then the same example with the stencil test on
First we need to ask for a stencil buffer
const gl = someCanvas.getContext('webgl2', {stencil: true});
Then we turn on the stencil test
gl.enable(gl.STENCIL_TEST);
Set up the test so it only draws if the stencil buffer is zero
gl.stencilFunc(
gl.EQUAL, // the test
0, // reference value
0xFF, // mask
);
And set the operation so we increment the stencil when we draw so they will no longer be zero and therefore fail the test
gl.stencilOp(
gl.KEEP, // what to do if the stencil test fails
gl.KEEP, // what to do if the depth test fails
gl.INCR, // what to do if both tests pass
);
Between the first draw and the second we clear the stencil buffer
gl.clear(gl.STENCIL_BUFFER_BIT);
Example
function main() {
const m4 = twgl.m4;
const gl = document
.querySelector('canvas')
.getContext('webgl', {stencil: true});
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`;
// compile shader, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
gl.useProgram(programInfo.program);
// create a buffer and put data in it
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-0.5, -0.2,
0.5, -0.2,
0.5, 0.2,
-0.2, -0.5,
-0.2, 0.5,
0.2, 0.5,
],
},
});
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.STENCIL_TEST);
gl.stencilFunc(
gl.EQUAL, // the test
0, // reference value
0xFF, // mask
);
gl.stencilOp(
gl.KEEP, // what to do if the stencil test fails
gl.KEEP, // what to do if the depth test fails
gl.INCR, // what to do if both tests pass
);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.uniform??
twgl.setUniforms(programInfo, {
color: [0.5, 0, 0, 0.5],
matrix: m4.identity(),
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
gl.clear(gl.STENCIL_BUFFER_BIT);
twgl.setUniforms(programInfo, {
color: [0, 0, 0.5, 0.5],
matrix: m4.rotateZ(
m4.translation([-0.1, 0.2, 0]),
Math.PI * 1.2),
});
twgl.drawBufferInfo(gl, bufferInfo);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Another solution you could also use the depth test if you're drawing 2D stuff. The default depth test only draws if the depth is gl.LESS than the current depth so just turning the depth test on and setting a different depth between draws would also work if the depth of the triangles is the same. You could compute a different depth value for each thing you draw, you'd need to look up the bit resolution of the depth buffer. Or, you could use gl.polygonOffset
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.POLYGON_OFFSET_FILL);
... then ...
for (let i = 0; i < numThingsToDraw; ++i) {
gl.polygonOffset(0, -i); // each thing 1 depth unit less
draw2DThing(things[i]);
}
example
function main() {
const m4 = twgl.m4;
const gl = document
.querySelector('canvas')
.getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`;
// compile shader, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
gl.useProgram(programInfo.program);
// create a buffer and put data in it
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-0.5, -0.2,
0.5, -0.2,
0.5, 0.2,
-0.2, -0.5,
-0.2, 0.5,
0.2, 0.5,
],
},
});
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.POLYGON_OFFSET_FILL);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.uniform??
twgl.setUniforms(programInfo, {
color: [0.5, 0, 0, 0.5],
matrix: m4.identity(),
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
gl.polygonOffset(0, -1);
twgl.setUniforms(programInfo, {
color: [0, 0, 0.5, 0.5],
matrix: m4.rotateZ(
m4.translation([-0.1, 0.2, 0.0]),
Math.PI * 1.2),
});
twgl.drawBufferInfo(gl, bufferInfo);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
I would like to make it so that these blocks are all drawn to one layer than that entire layer is made transparent. Or if there is a way I can use blend functions or alpha blending to do it that would be fine too. Thanks a lot.
What is your definition of efficient? Under what circumstances? What conditions?
Here's a few solutions. It's hard to tell if they fit without more details.
First let's repo the issue
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(0, .5, 0, .5);
}
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// create buffers and upload vertex data
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
render();
function render() {
gl.clearColor(0, .4, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.BLEND);
gl.enable(gl.CULL_FACE);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.useProgram(programInfo.program);
const halfHeight = 1;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const halfWidth = halfHeight * aspect;
const projection = m4.ortho(
-halfWidth, halfWidth, -halfHeight, halfWidth, 0.1, 20);
const camera = m4.lookAt(
[5, 2, 5], // eye
[0, -.5, 0], // target
[0, 1, 0], // up
);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
for (let x = -1; x <= 1; ++x) {
let mat = m4.translate(viewProjection, [x, 0, 0]);
twgl.setUniforms(programInfo, {
u_matrix: mat,
});
// calls drawArrays or drawElements
twgl.drawBufferInfo(gl, bufferInfo);
}
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Note the example above just clears the background to [0, .4, 0, 1] which is dark green. It then draws 3 cubes using [0, .5, 0, .5] which is full green (as in [0, 1, 0, 1]) except premultiplied by 50% alpha. Using premultiplied colors the blending is set to gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) Face culling is on.
As for solutions off the top of my head looking at your picture you could
Draw front to back with z-test on
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(0, .5, 0, .5);
}
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// create buffers and upload vertex data
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
render();
function render() {
gl.clearColor(0, .4, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.BLEND);
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.useProgram(programInfo.program);
const halfHeight = 1;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const halfWidth = halfHeight * aspect;
const projection = m4.ortho(
-halfWidth, halfWidth, -halfHeight, halfWidth, 0.1, 20);
const camera = m4.lookAt(
[5, 2, 5], // eye
[0, -.5, 0], // target
[0, 1, 0], // up
);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
for (let x = 1; x >= -1; --x) {
let mat = m4.translate(viewProjection, [x, 0, 0]);
twgl.setUniforms(programInfo, {
u_matrix: mat,
});
// calls drawArrays or drawElements
twgl.drawBufferInfo(gl, bufferInfo);
}
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Note the only changes to the top version are the addition of
gl.enable(gl.DEPTH_TEST);
And drawing in reverse order
for (let x = 1; x >= -1; --x) {
I have no idea how your data is stored. Assuming it's a grid you'd have to write code to iterate over the grid in the correct order from the view of the camera.
Your example only shows a green background so you could just draw opaque and multiply or mix by a color, the same color as your background.
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 u_backgroundColor;
uniform float u_mixAmount;
void main() {
gl_FragColor = mix(vec4(0, 1, 0, 1), u_backgroundColor, u_mixAmount);
}
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// create buffers and upload vertex data
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
render();
function render() {
gl.clearColor(0, .4, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.useProgram(programInfo.program);
const halfHeight = 1;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const halfWidth = halfHeight * aspect;
const projection = m4.ortho(
-halfWidth, halfWidth, -halfHeight, halfWidth, 0.1, 20);
const camera = m4.lookAt(
[5, 2, 5], // eye
[0, -.5, 0], // target
[0, 1, 0], // up
);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
for (let x = 1; x >= -1; --x) {
let mat = m4.translate(viewProjection, [x, 0, 0]);
twgl.setUniforms(programInfo, {
u_matrix: mat,
u_backgroundColor: [0, 0.4, 0, 1],
u_mixAmount: 0.5,
});
// calls drawArrays or drawElements
twgl.drawBufferInfo(gl, bufferInfo);
}
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
The solution above changes the fragment shader to
uniform vec4 u_backgroundColor;
uniform float u_mixAmount;
void main() {
gl_FragColor = mix(vec4(0, 1, 0, 1), u_backgroundColor, u_mixAmount);
}
Where vec4(0, 1, 0, 1) is the cube's green color. We then set u_backgroundColor to match the background color of 0, .4, 0, 1 and set u_mixAmount to .5 (50%)
This solution might sound dumb but it's common to want to fade to a background color which is basically how fog works. You don't actually make things more transparent in the distance you just draw with the fog color.
draw all the tiles without transparency into another texture, then draw that texture with transparency
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl', {alpha: false});
const vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
}
`;
const mixVs = `
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = u_matrix * position;
v_texcoord = texcoord;
}
`;
const mixFs = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_tex;
uniform float u_alpha;
void main() {
gl_FragColor = texture2D(u_tex, v_texcoord) * u_alpha;
}
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const mixProgramInfo = twgl.createProgramInfo(gl, [mixVs, mixFs]);
// create buffers and upload vertex data
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const xyQuadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
// create framebuffer with RGBA/UNSIGNED_BYTE texture
// and depth buffer renderbuffer that matches the size
// of the canvas
const fbi = twgl.createFramebufferInfo(gl);
render();
function render() {
renderTiles();
renderScene();
}
function renderScene() {
// bind canvas and set viewport
twgl.bindFramebufferInfo(gl, null);
gl.clearColor(0, 0.4, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.useProgram(mixProgramInfo.program);
twgl.setBuffersAndAttributes(gl, mixProgramInfo, xyQuadBufferInfo);
twgl.setUniforms(mixProgramInfo, {
u_matrix: m4.identity(),
u_tex: fbi.attachments[0], // the texture
u_alpha: .5,
});
// calls drawArrays or drawElements
twgl.drawBufferInfo(gl, xyQuadBufferInfo);
}
function renderTiles() {
// bind framebuffer and set viewport
twgl.bindFramebufferInfo(gl, fbi);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.disable(gl.BLEND);
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.useProgram(programInfo.program);
const halfHeight = 1;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const halfWidth = halfHeight * aspect;
const projection = m4.ortho(
-halfWidth, halfWidth, -halfHeight, halfWidth, 0.1, 20);
const camera = m4.lookAt(
[5, 2, 5], // eye
[0, -.5, 0], // target
[0, 1, 0], // up
);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
for (let x = 1; x >= -1; --x) {
let mat = m4.translate(viewProjection, [x, 0, 0]);
twgl.setUniforms(programInfo, {
u_matrix: mat,
u_backgroundColor: [0, 0.4, 0, 1],
u_mixAmount: 0.5,
});
// calls drawArrays or drawElements
twgl.drawBufferInfo(gl, bufferInfo);
}
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
The change above creates an RGBA texture and a depth renderbuffer the same size as the canvas and attaches them to a framebuffer. It then renders the tiles into that texture opaquely. Then it renders the texture over the canvas with 50% alpha. Note that the canvas itself is set to {alpha: false} so that the canvas doesn't blend with the elements behind it.
Generate new geometry that doesn't have the hidden surfaces
The problem is your drawing 3 cubes and the edges between them. A Minecraft like solution would probably generate new geometry that didn't have the inner edges. It would be pretty easy to walk a grid of tiles and decide whether or not to add that edge of the cube based on if there is a neighbor or not.
In Minecraft they only have to generate new geometry when blocks are added or removed and with some creative coding that might involve only modifying a few vertices rather than regenerating the entire mesh. They also probably generate in a gird like very 64x64x64 area.
I am trying to figure out how to achieve color and alpha blending between primitives using Regl.
I know Regl command's have a blend property and I've tried replicating the following webgl settings that do the trick:
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
using the following blend settings in Regl:
blend: {
enable: true,
func: { src: 'src alpha', dst:'one minus src alpha' }
},
But the blending only seem to work in regard to the background color but not between the points. (See the example below.)
Example: http://jsfiddle.net/8gyf7pek/13/
const canvas1 = document.querySelector('#c1');
const canvas2 = document.querySelector('#c2');
//////////////////////////////////////////////
// PURE WEBGL
//////////////////////////////////////////////
const gl = canvas1.getContext('webgl');
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
const vertexShaderSource = `
attribute vec2 position;
attribute vec4 color;
varying vec4 v_color;
void main() {
gl_PointSize = 50.0;
gl_Position = vec4(position, 0, 1);
v_color = color;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
const positionAttributeLocation = gl.getAttribLocation(program, 'position');
const colorAttributeLocation = gl.getAttribLocation(program, 'color');
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.enableVertexAttribArray(colorAttributeLocation);
gl.vertexAttribPointer(colorAttributeLocation, 4, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-0.05, -0.05, -0.05, 0.05, 0.05, 0.05, 0.05, -0.05,
]), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
const red = [1, 0, 0, 0.5];
const blue = [0, 0, 1, 0.5];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
...red, ...red,
...blue, ...blue,
]), gl.STATIC_DRAW);
gl.drawArrays(gl.POINTS, 0, 4);
function createShader(gl, type, shaderSource) {
const shader = gl.createShader(type);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if(!success) {
console.warn(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
return shader;
}
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if(!success) {
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
return program;
}
//////////////////////////////////////////////
// REGL
//////////////////////////////////////////////
const regl = createREGL(canvas2);
regl.clear({ color: [0, 0, 0, 0], depth: 1 });
regl({
frag: `
precision mediump float;
varying vec4 fragColor;
void main () {
gl_FragColor = fragColor;
}`,
vert: `
precision mediump float;
attribute vec2 position;
attribute vec4 color;
varying vec4 fragColor;
uniform float pointWidth;
void main () {
fragColor = color;
gl_PointSize = pointWidth;
gl_Position = vec4(position, 0, 1);
}`,
attributes: {
position: [
[-0.05, -0.05],
[-0.05, 0.05],
[0.05, -0.05],
[0.05, 0.05],
],
color: [
[1, 0, 0, 0.5],
[1, 0, 0, 0.5],
[0, 0, 1, 0.5],
[0, 0, 1, 0.5]
],
},
uniforms: {
pointWidth: 50,
},
blend: {
enable: true,
func: { src: 'src alpha', dst:'one minus src alpha' }
},
count: 4,
primitive: 'points',
})();
#bg {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
color: #808080;
background: black;
}
#c1, #c2 {
width: 240px;
height: 240px;
border: 1px solid white;
}
em {
display: block;
}
<div id="bg">
<canvas id="c1"></canvas>
<canvas id="c2"></canvas>
<em>Left is pure WebGL. Right is Regl.</em>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/regl/1.3.7/regl.min.js"></script>
Am I doing something wrong? How could I achieve the same kind of blending that the pure webgl code produces? Thanks!
Thanks to this great answer I figured it out:
In a nutshell, the blend function needs to be adjusted and the depth test needs to be disabled. (But I still don't know why the blend function, that worked in the vanilla WebGL example, didn't work in Regl)
Use the following blend mode
blend: {
enable: true,
func: {
srcRGB: 'src alpha',
srcAlpha: 'src alpha',
dstRGB: 'one minus src alpha',
dstAlpha: 'one minus src alpha',
},
},
Disable depth test
depth: { enable: false },
Here's the fixed example from my question: http://jsfiddle.net/8gyf7pek/22/
In trying to create VSM shadows that work on mobile platforms I'm exploring the possibility of 24 bit depth textures to store the moments (some mobile platforms don't support floating-point textures).
The problem is that I need omni-lights with shadows which means I need cubemaps (ideally). At least firefox does not seem to support this, printing Error: WebGL warning: texImage2D: With format DEPTH_COMPONENT24, this function may only be called with target=TEXTURE_2D, data=null, and level=0. to the console.
I'm calling gl.texImage2D with DEPTH_COMPONENT as format and internal format. For type I've tried gl.UNSIGNED_SHORT, gl.UNSIGNED_INT and ext.UNSIGNED_INT_24_8_WEBGL, all to no avail.
I could map the sides of a cube to a 2d texture and add a margin to each side to avoid interpolation artifacts but that seems overly involved and hard to maintain.
Are there other workarounds to have sampler cubes with DEPTH_COMPONENT format?
This is for WebGL 1
EDIT: I've made a few modifications to the code in gman's answer to better reflect my problem. Here's a jsfiddle. It looks like to does work on chrome (dark red cube on red background) but not on firefox (everything is black).
If you want to use depth textures you need to try to enable the WEBGL_depth_texture extension. note that many mobile devices don't support depth textures. (click the filters in the top left)
Then, according to the spec, you don't pass DEPTH_COMPONENT24 to texImage2D. In pass DEPTH_COMPONENT and a type of gl.UNSIGNED_SHORT or gl.UNSIGNED_INT the implementation chooses the bit depth. You can check what resolution you got by calling gl.getParameter(gl.DEPTH_BITS);
function main() {
const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension("WEBGL_depth_texture");
if (!ext) {
alert("Need WEBGL_depth_texture");
return;
}
const width = 128;
const height = 128;
const depthTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, depthTex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0,
gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
// calls gl.bindTexture, gl.texParameteri
twgl.setTextureParameters(gl, depthTex, {
minMag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
});
// calls gl.createTexture, gl.bindTexture, gl.texImage2D, gl.texParameteri
const cubeTex = twgl.createTexture(gl, {
target: gl.TEXTURE_CUBE_MAP,
minMag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
width: width,
height: height,
});
const faces = [
gl.TEXTURE_CUBE_MAP_POSITIVE_X,
gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
];
const fbs = faces.map(face => {
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, face, cubeTex, 0);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTex, 0);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
console.log("can't use this framebuffer attachment combo");
}
return fb;
});
const vs = `
attribute vec4 position;
attribute vec3 normal;
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
varying vec3 v_normal;
void main() {
gl_Position = u_worldViewProjection * position;
v_normal = (u_worldInverseTranspose * vec4(normal, 0)).xyz;
}
`;
const fs = `
precision mediump float;
uniform vec3 u_color;
uniform vec3 u_lightDir;
varying vec3 v_normal;
void main() {
float light = dot(u_lightDir, normalize(v_normal)) * .5 + .5;
gl_FragColor = vec4(u_color * light, 1);
}
`;
const vs2 = `
attribute vec4 position;
uniform mat4 u_matrix;
varying vec3 v_texcoord;
void main() {
gl_Position = u_matrix * position;
v_texcoord = position.xyz;
}
`;
const fs2 = `
precision mediump float;
uniform samplerCube u_cube;
varying vec3 v_texcoord;
void main() {
gl_FragColor = textureCube(u_cube, normalize(v_texcoord));
}
`;
// compile shaders, links program, looks up locations
const colorProgramInfo = twgl.createProgramInfo(gl, [vs, fs]);
// compile shaders, links program, looks up locations
const cubeProgramInfo = twgl.createProgramInfo(gl, [vs2, fs2]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const cubeBufferInfo = twgl.primitives.createCubeBufferInfo(gl);
function render(time) {
time *= 0.001; // seconds
gl.enable(gl.DEPTH_TEST);
gl.useProgram(colorProgramInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, colorProgramInfo, cubeBufferInfo);
// draw a different color on each face
faces.forEach((face, ndx) => {
const c = ndx + 1;
const color = [
(c & 0x1) ? 1 : 0,
(c & 0x2) ? 1 : 0,
(c & 0x4) ? 1 : 0,
];
gl.bindFramebuffer(gl.FRAMEBUFFER, fbs[ndx]);
gl.viewport(0, 0, width, height);
gl.clearColor(1 - color[0], 1 - color[1], 1 - color[2], 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const fov = Math.PI * 0.25;
const aspect = width / height;
const zNear = 0.001;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const world = m4.translation([0, 0, -3]);
m4.rotateY(world, Math.PI * .1 * c * time, world);
m4.rotateX(world, Math.PI * .15 * c * time, world);
// calls gl.uniformXXX
twgl.setUniforms(colorProgramInfo, {
u_color: color,
u_lightDir: v3.normalize([1, 5, 10]),
u_worldViewProjection: m4.multiply(projection, world),
u_worldInverseTranspose: m4.transpose(m4.inverse(world)),
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, cubeBufferInfo);
});
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(cubeProgramInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, cubeProgramInfo, cubeBufferInfo);
const fov = Math.PI * 0.25;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.001;
const zFar = 10;
const mat = m4.perspective(fov, aspect, zNear, zFar);
m4.translate(mat, [0, 0, -2], mat);
m4.rotateY(mat, Math.PI * .25 * time, mat);
m4.rotateX(mat, Math.PI * .25 * time, mat);
twgl.setUniforms(cubeProgramInfo, {
u_cube: cubeTex,
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, cubeBufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
Otherwise you can use depth renderbuffers. Where's an example who's code is here and the code that creates the framebuffers for the cubemap is here.
Update
As for cubemap depth textures the spec specifically says only TEXTURE_2D is supported.
The error INVALID_OPERATION is generated in the following situations:
texImage2D is called with format and internalformat of DEPTH_COMPONENT
or DEPTH_STENCIL and target is not TEXTURE_2D,
You might have to switch to WebGL2. It works in both firefox and chrome
function main() {
const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector("canvas").getContext("webgl2");
const width = 128;
const height = 128;
const colorTex = twgl.createTexture(gl, {
target: gl.TEXTURE_CUBE_MAP,
minMag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
width: width,
height: height,
});
// calls gl.createTexture, gl.bindTexture, gl.texImage2D, gl.texParameteri
const depthTex = twgl.createTexture(gl, {
target: gl.TEXTURE_CUBE_MAP,
internalFormat: gl.DEPTH_COMPONENT24,
format: gl.DEPTH_COMPONENT,
type: gl.UNSIGNED_INT,
width: width,
height: height,
wrap: gl.CLAMP_TO_EDGE,
minMax: gl.NEAREST,
});
const faces = [
gl.TEXTURE_CUBE_MAP_POSITIVE_X,
gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
];
const fbs = faces.map(face => {
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, face, colorTex, 0);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, face, depthTex, 0);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
console.log("can't use this framebuffer attachment combo");
}
return fb;
});
const vs = `
attribute vec4 position;
attribute vec3 normal;
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
varying vec3 v_normal;
void main() {
gl_Position = u_worldViewProjection * position;
gl_Position.z = 0.5;
v_normal = (u_worldInverseTranspose * vec4(normal, 0)).xyz;
}
`;
const fs = `
precision mediump float;
uniform vec3 u_color;
uniform vec3 u_lightDir;
varying vec3 v_normal;
void main() {
float light = dot(u_lightDir, normalize(v_normal)) * .5 + .5;
gl_FragColor = vec4(u_color * light, 1);
}
`;
const vs2 = `
attribute vec4 position;
uniform mat4 u_matrix;
varying vec3 v_texcoord;
void main() {
gl_Position = u_matrix * position;
v_texcoord = position.xyz;
}
`;
const fs2 = `
precision mediump float;
uniform samplerCube u_cube;
varying vec3 v_texcoord;
void main() {
gl_FragColor = textureCube(u_cube, normalize(v_texcoord)) / vec4(2.0, 1.0, 1.0, 1.0);
}
`;
// compile shaders, links program, looks up locations
const colorProgramInfo = twgl.createProgramInfo(gl, [vs, fs]);
// compile shaders, links program, looks up locations
const cubeProgramInfo = twgl.createProgramInfo(gl, [vs2, fs2]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const cubeBufferInfo = twgl.primitives.createCubeBufferInfo(gl);
function render(time) {
time *= 0.001; // seconds
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.useProgram(colorProgramInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, colorProgramInfo, cubeBufferInfo);
// draw a different color on each face
faces.forEach((face, ndx) => {
const c = ndx + 1;
const color = [
(c & 0x1) ? 1 : 0,
(c & 0x2) ? 1 : 0,
(c & 0x4) ? 1 : 0,
];
gl.bindFramebuffer(gl.FRAMEBUFFER, fbs[ndx]);
gl.viewport(0, 0, width, height);
gl.clearColor(1 - color[0], 1 - color[1], 1 - color[2], 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const fov = Math.PI * 0.25;
const aspect = width / height;
const zNear = 0.001;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const world = m4.translation([0, 0, -3]);
m4.rotateY(world, Math.PI * .1 * c * time, world);
m4.rotateX(world, Math.PI * .15 * c * time, world);
// calls gl.uniformXXX
twgl.setUniforms(colorProgramInfo, {
u_color: color,
u_lightDir: v3.normalize([1, 5, 10]),
u_worldViewProjection: m4.multiply(projection, world),
u_worldInverseTranspose: m4.transpose(m4.inverse(world)),
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, cubeBufferInfo);
});
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(cubeProgramInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, cubeProgramInfo, cubeBufferInfo);
const fov = Math.PI * 0.25;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.001;
const zFar = 10;
const mat = m4.perspective(fov, aspect, zNear, zFar);
m4.translate(mat, [0, 0, -2], mat);
m4.rotateY(mat, Math.PI * .25 * time, mat);
m4.rotateX(mat, Math.PI * .25 * time, mat);
twgl.setUniforms(cubeProgramInfo, {
u_cube: colorTex,
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, cubeBufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>