Webgl - 在 Safari 中渲染纹理深度 returns gl.INVALID_FRAMEBUFFER_OPERATION
Webgl - Rendering depth to texture returns gl.INVALID_FRAMEBUFFER_OPERATION in Safari
如果您在 Safari 中查看以下代码片段(摘自 https://webglfundamentals.org/webgl/lessons/webgl-shadows.html),您会发现它看起来与在其他浏览器中不同,因为深度纹理始终是全黑的:
'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>
我自己的实现基于此,并且 运行 属于同一问题。在调用 gl.clea 或 gl.drawElements 之后调用 getError 时,WebGL returns a gl.INVALID_FRAMEBUFFER_OPERATION。我假设帧缓冲区的设置有问题,但我尝试过的都没有帮助。提前致谢!
对于 Safari 15 之前的版本,您需要设置一个颜色附件,即使示例没有使用它也是如此
首先创建一个RGBA/UNSIGNED_BYTE与深度纹理大小相同的颜色附件
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);
然后将其附加到帧缓冲区
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,
)
很遗憾,这不是 Safari 中的错误。这反映了 OpenGL ES 2.0 spec 的不幸选择
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
换句话说,在 OpenGL ES 2.0 中 无需帧缓冲区附件组合即可工作!!!!!!!
The WebGL spec itself 添加了需要 3 个组合才能工作的要求
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
因此,按照 2 条规范,即使 WEBGL_depth_texture
扩展添加了深度纹理,其中 none 仍需要在有或没有任何其他附件的情况下作为帧缓冲区中的附件工作!!!
是的,你没有看错。 WEBGL_depth_texture 扩展规范说您可以创建它们并附加它们。 OpenGL ES 2.0 规范表示它们不需要作为附件工作,或者更确切地说,任何组合是否有效取决于实现。 WebGL 列出了工作所需的 3 种组合,但这 3 种组合不包括深度纹理,仅包括深度渲染缓冲区。
幸运的是,如果您添加 RGBA/UNSIGNED_BYTE 颜色附件,它们似乎在任何地方都有效。
积极的一面是,OpenGL ES 3.0 和 WebGL2 列出了更多工作所需的组合。
如果您在 Safari 中查看以下代码片段(摘自 https://webglfundamentals.org/webgl/lessons/webgl-shadows.html),您会发现它看起来与在其他浏览器中不同,因为深度纹理始终是全黑的:
'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>
我自己的实现基于此,并且 运行 属于同一问题。在调用 gl.clea 或 gl.drawElements 之后调用 getError 时,WebGL returns a gl.INVALID_FRAMEBUFFER_OPERATION。我假设帧缓冲区的设置有问题,但我尝试过的都没有帮助。提前致谢!
对于 Safari 15 之前的版本,您需要设置一个颜色附件,即使示例没有使用它也是如此
首先创建一个RGBA/UNSIGNED_BYTE与深度纹理大小相同的颜色附件
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);
然后将其附加到帧缓冲区
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,
)
很遗憾,这不是 Safari 中的错误。这反映了 OpenGL ES 2.0 spec 的不幸选择
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
换句话说,在 OpenGL ES 2.0 中 无需帧缓冲区附件组合即可工作!!!!!!!
The WebGL spec itself 添加了需要 3 个组合才能工作的要求
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
textureCOLOR_ATTACHMENT0
=RGBA
/UNSIGNED_BYTE
texture +DEPTH_ATTACHMENT
=DEPTH_COMPONENT16
renderbufferCOLOR_ATTACHMENT0
=RGBA
/UNSIGNED_BYTE
texture +DEPTH_STENCIL_ATTACHMENT
=DEPTH_STENCIL
renderbuffer
因此,按照 2 条规范,即使 WEBGL_depth_texture
扩展添加了深度纹理,其中 none 仍需要在有或没有任何其他附件的情况下作为帧缓冲区中的附件工作!!!
是的,你没有看错。 WEBGL_depth_texture 扩展规范说您可以创建它们并附加它们。 OpenGL ES 2.0 规范表示它们不需要作为附件工作,或者更确切地说,任何组合是否有效取决于实现。 WebGL 列出了工作所需的 3 种组合,但这 3 种组合不包括深度纹理,仅包括深度渲染缓冲区。
幸运的是,如果您添加 RGBA/UNSIGNED_BYTE 颜色附件,它们似乎在任何地方都有效。
积极的一面是,OpenGL ES 3.0 和 WebGL2 列出了更多工作所需的组合。