我正在尝试编写一个全景查看器。大多数情况下,这涉及将图像映射到四边形以模拟天空盒。
对于立方体贴图图像来说,这是相当简单的。要么 copy the 6 parts of the image to a 6 planes of a cube map或者制作一个具有 the cubemap math as specified in the OpenGL ES spec 功能的着色器
对于等距柱状图像,例如来自 Ricoh Theta 的图像
你可以使用这个数学
// convert from direction (n) to texcoord (uv)
float latitude = acos(n.y);
float longitude = atan(n.z, n.x);
vec2 sphereCoords = vec2(longitude, latitude) * vec2(0.5 / PI, 1.0 / PI);
vec2 uv = fract(vec2(0.5,1.0) - sphereCoords);
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
varying vec4 v_position;
void main() {
v_position = position;
gl_Position = position;
gl_Position.z = 1.0;
}
`;
const fs = `
precision highp float;
uniform sampler2D u_skybox;
uniform mat4 u_viewDirectionProjectionInverse;
varying vec4 v_position;
#define PI radians(180.0)
void main() {
vec4 t = u_viewDirectionProjectionInverse * v_position;
vec3 n = normalize(t.xyz / t.w);
// convert from direction (n) to texcoord (uv)
float latitude = acos(n.y);
float longitude = atan(n.z, n.x);
vec2 sphereCoords = vec2(longitude, latitude) * vec2(0.5 / PI, 1.0 / PI);
vec2 uv = fract(vec2(0.5,1.0) - sphereCoords);
// multiply u by 2 because we only have a 180degree view
gl_FragColor = texture2D(u_skybox, uv * vec2(-2, 1));
}
`;
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const tex = twgl.createTexture(gl, {
src: '/image/h0H38.jpg',
flipY: true,
});
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl, 2);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const projectionMatrix = m4.perspective(45 * Math.PI / 180, aspect, 1, 20);
const cameraMatrix = m4.rotationY(time * 0.1);
m4.rotateX(cameraMatrix, Math.sin(time * 0.3) * 0.5, cameraMatrix);
const viewMatrix = m4.inverse(cameraMatrix);
viewMatrix[12] = 0;
viewMatrix[13] = 0;
viewMatrix[14] = 0;
const viewDirectionProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
const viewDirectionProjectionInverseMatrix = m4.inverse(viewDirectionProjectionMatrix);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
u_skyBox: tex,
});
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
但是有些图像不是等距矩形,而是鱼眼?
我一直在试图找出做同样的事情所需的数学(将其映射到基于四边形的天空盒),但我一直没有运气
作为引用,我发现 this page从 3d 坐标到鱼眼坐标的转换。它说
// convert from direction (n) to texcoord (uv)
float r = 2.0 * atan(length(n.xy), n.z) / PI;
float theta = atan(n.y, n.x);
vec2 uv = vec2(cos(theta), sin(theta)) * r * 0.5 + 0.5;
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
varying vec4 v_position;
void main() {
v_position = position;
gl_Position = position;
gl_Position.z = 1.0;
}
`;
const fs = `
precision highp float;
uniform sampler2D u_skybox;
uniform mat4 u_viewDirectionProjectionInverse;
varying vec4 v_position;
#define PI radians(180.0)
void main() {
vec4 t = u_viewDirectionProjectionInverse * v_position;
vec3 n = normalize(t.xyz / t.w);
// convert from direction (n) to texcoord (uv)
float r = 2.0 * atan(length(n.xy), n.z) / PI;
float theta = atan(n.y, n.x);
vec2 uv = vec2(cos(theta), sin(theta)) * r * 0.5 + 0.5;
#if 0
// Calculate fisheye angle and radius
float theta = atan(n.z, n.x);
float phi = atan(length(n.xz), n.y);
float r = phi / PI;
// Pixel in fisheye space
vec2 uv = vec2(0.5) + r * vec2(cos(theta), sin(theta));
#endif
// multiply u by 2 because we only have a 180degree view
gl_FragColor = texture2D(u_skybox, uv * vec2(-2, 1));
}
`;
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const tex = twgl.createTexture(gl, {
src: '/image/SN1K0.jpg',
flipY: true,
});
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl, 2);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const projectionMatrix = m4.perspective(45 * Math.PI / 180, aspect, 1, 20);
const cameraMatrix = m4.rotationY(time * 0.1);
m4.rotateX(cameraMatrix, 0.7 + Math.sin(time * 0.3) * .7, cameraMatrix);
const viewMatrix = m4.inverse(cameraMatrix);
viewMatrix[12] = 0;
viewMatrix[13] = 0;
viewMatrix[14] = 0;
const viewDirectionProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
const viewDirectionProjectionInverseMatrix = m4.inverse(viewDirectionProjectionMatrix);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
u_skyBox: tex,
});
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
还有this
显然我错过了一些东西。
最佳答案
我认为错误在于这个逻辑:
// multiply u by 2 because we only have a 180degree view
gl_FragColor = texture2D(u_skybox, uv * vec2(-2, 1));
尽管这在等距柱状投影情况下有效,因为数学计算结果表明 z 分量仅影响经度,但它在鱼眼情况下不再有效,因为 n.z
影响两个轴。
您可以通过取 n.z
的绝对值并在 z 为负数时翻转 n.x
来解释公式中的负 z 分量:
// convert from direction (n) to texcoord (uv)
float r = 2.0 * atan(length(n.xy), abs(n.z)) / PI;
float theta = atan(n.y, n.x * sign(n.z));
vec2 uv = vec2(cos(theta), sin(theta)) * r * 0.5 + vec2(0.5);
这是在行动:
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
varying vec4 v_position;
void main() {
v_position = position;
gl_Position = position;
gl_Position.z = 1.0;
}
`;
const fs = `
precision highp float;
uniform sampler2D u_skybox;
uniform mat4 u_viewDirectionProjectionInverse;
varying vec4 v_position;
#define PI radians(180.0)
void main() {
vec4 t = u_viewDirectionProjectionInverse * v_position;
vec3 n = normalize(t.xyz / t.w);
// convert from direction (n) to texcoord (uv)
float r = 2.0 * atan(length(n.xy), abs(n.z)) / PI;
float theta = atan(n.y, n.x * sign(n.z));
vec2 uv = vec2(cos(theta), sin(theta)) * r * 0.5 + vec2(0.5);
gl_FragColor = texture2D(u_skybox, uv * vec2(-1.0, 1.0));
}
`;
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const tex = twgl.createTexture(gl, {
src: 'https://i.imgur.com/dzXCQwM.jpg',
flipY: true,
});
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl, 2);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const projectionMatrix = m4.perspective(45 * Math.PI / 180, aspect, 1, 20);
const cameraMatrix = m4.rotationY(time * 0.1);
m4.rotateX(cameraMatrix, 0.7 + Math.sin(time * 0.3) * .7, cameraMatrix);
const viewMatrix = m4.inverse(cameraMatrix);
viewMatrix[12] = 0;
viewMatrix[13] = 0;
viewMatrix[14] = 0;
const viewDirectionProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
const viewDirectionProjectionInverseMatrix = m4.inverse(viewDirectionProjectionMatrix);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_viewDirectionProjectionInverse: viewDirectionProjectionInverseMatrix,
u_skyBox: tex,
});
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
关于glsl - 鱼眼天空盒着色器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60819068/