鱼眼Skybox着色器

man

我正在尝试编写全景查看器。通常,这涉及将图像映射到四边形以模拟天空盒。

对于立方体贴图图像,它很简单。无论是6个部位的图像复制到立方体地图的6个面的要么做一个着色器,做作为的OpenGL ES规范中指定的立方体贴图数学

对于像来自理光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: 'https://i.imgur.com/ChIfXM0.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>

但是有些图像不是等矩形的,它们是鱼眼?

我一直在试图找出做相同事情所需的数学运算(将其映射到基于Quad的Skybox),但是我一直没有运气

作为参考,我发现此页面具有从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: '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>

Also this

Clearly I'm missing something.

Klaycon

I believe the error lies in this logic:

// multiply u by 2 because we only have a 180degree view
gl_FragColor = texture2D(u_skybox, uv * vec2(-2, 1));

Although this works in the equirectangular case because the math works out such that the z-component only affects longitude, it is no longer valid in the fisheye case because n.z affects both axes.

You can account for the negative z-component in the formula by taking the absolute value of n.z and flipping n.x when the z is negative:

  // 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);

Here it is in action:

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>

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章