首页 > 解决方案 > 从体素获取角度我做错了什么?

问题描述

如果我没有清楚地解释任何事情,请提前道歉,请随时要求澄清。这个爱好游戏项目对我来说意义重大

我正在使用 webgl 制作体素渲染引擎。它使用 gl.points 为每个体素绘制正方形。我只是使用由摄像机位置平移的投影矩阵,然后通过摄像机旋转来旋转。

gl_Position = 
 uMatrix * uModelMatrix * vec4(aPixelPosition[0],-aPixelPosition[2],aPixelPosition[1],1.0);

modelviewmatrix 只是默认的 mat4.create(),出于某种原因,没有它它不会显示任何内容。aPixelPosition 只是体素的 X、Z、Y(在 webgl 空间中)。

使用这样的东西:

gl_PointSize = (uScreenSize[1]*0.7) / (gl_Position[2]);

您可以根据体素与相机的距离来设置体素的大小。减去一个视觉错误,效果很好。

(图片来自一个大的空心立方体)

您可以看到后墙显示良好(因为它们都直接指向您)但是与您成一定角度显示的墙需要增加尺寸。所以我使用了你面对的位置和体素的位置减去你的相机位置之间的点积来获得每个块的角度并相应地为它们着色。

vPosition=acos(dot( normalize(vec2(sin(uYRotate),-cos(uYRotate))) ,
  normalize(vec2(aPixelPosition[0],aPixelPosition[1])-
vec2(uCam[0],uCam[1]))));

then color the blocks with what this returns.

(墙壁从黑色变为白色,具体取决于它们与您的角度)

这个视觉演示显示了这个问题,背面的墙壁都指向你,除了你直接看的那些,同一张脸侧面的墙壁对你的角度越来越大。

如果我使用它调整 pointSize 以随着角度增加,它将修复视觉故障,但它会引入一个新故障。

从这里看起来一切都很好,但是如果你真的靠近一堵墙并左右移动

当您左右扫描时,会出现相当明显的冒泡效果,因为您视图一侧的角度稍微大一些(即使它们无论如何都应该面向相同的方向)

很明显,我的数学不是最好的。我怎么能有它,所以只有侧面的墙壁会返回一个角度?而后墙上的那些都没有返回任何角度。万分感谢。

我已经尝试过这样做,所以点积总是检查体素 X,就好像它与相机一样,但这只是让它使每个体素的颜色都相同。

标签: webglvoxel

解决方案


我不确定您是否真的可以做您想做的事情,即表示体素(立方体)和二维正方形(gl.POINTS)。

我不确定我可以演示这个问题。也许我应该写一个程序来画这个,这样你就可以移动相机但是......

考虑这 6 个立方体

6个立方体

只是在他们的投影中心放置一个正方形是行不通的

在我看来,没有正方形可以以通用方式表示这些立方体,没有间隙,也没有其他问题。

为了确保没有间隙,立方体将覆盖的每个像素都需要被正方形覆盖。所以,首先我们可以绘制覆盖每个立方体的矩形

立方体屏幕范围

然后因为 gl.POINTS 是正方形的,我们需要将每个区域扩展为正方形

正方形

考虑到重叠的数量,将会有各种各样的问题。在极端角度下,特定正方形需要覆盖它所代表的立方体的屏幕空间的大小会变得非常大。然后,当一堆立方体的 Z 相同时,您将遇到 z-fighting 问题。例如,蓝色方块将出现在绿色方块的前面,它们重叠在一起,在绿色中形成一个小缺口。

我们可以在这里看到

在此处输入图像描述

每个绿色像素与右侧一列和向下一个体素的棕色像素部分重叠,因为该 POINT 在前面并且大到足以覆盖屏幕空间,棕色体素最终覆盖了左侧的绿色像素,并且上一个。

这是一个遵循上述算法的着色器。对于 3D 空间中的每个点,它假定一个单位立方体。它计算立方体 8 个点中每个点的归一化设备坐标 (NDC),并使用这些坐标来获取最小和最大 NDC 坐标。由此它可以计算出gl_PointSize覆盖那么大面积的需求。然后它将该点放置在该区域的中心。

'use strict';

/* global window, twgl, requestAnimationFrame, document */

const height = 120;
const width = 30
const position = [];
const color = [];
const normal = [];
for (let z = 0; z < width; ++z) {
  for (let x = 0; x < width; ++x) {
    position.push(x, 0, z);
    color.push(r(0.5), 1, r(0.5));
    normal.push(0, 1, 0);
  }
}
for (let y = 1; y < height ; ++y) {
  for (let x = 0; x < width; ++x) {
    position.push(x, -y, 0);
    color.push(0.6, 0.6, r(0.5));
    normal.push(0, 0, -1);
    
    position.push(x, -y, width - 1);
    color.push(0.6, 0.6, r(0.5));
    normal.push(0, 0, 1);
    
    position.push(0, -y, x);
    color.push(0.6, 0.6, r(0.5));
    normal.push(-1, 0, 0);
    
    position.push(width - 1, -y, x);
    color.push(0.6, 0.6, r(0.5));
    normal.push(1, 0, 0);
  }
}

function r(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return Math.random() * (max - min) + min;
}

const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector('canvas').getContext('webgl');

const vs = `
attribute vec4 position;
attribute vec3 normal;
attribute vec3 color;

uniform mat4 projection;
uniform mat4 modelView;
uniform vec2 resolution;

varying vec3 v_normal;
varying vec3 v_color;

vec2 computeNDC(vec4 p, vec4 off) {
  vec4 clipspace = projection * modelView * (p + off);
  return clipspace.xy / clipspace.w;
}


void main() {
  vec2 p0 = computeNDC(position, vec4(-.5, -.5, -.5, 0));
  vec2 p1 = computeNDC(position, vec4( .5, -.5, -.5, 0));
  vec2 p2 = computeNDC(position, vec4(-.5,  .5, -.5, 0));
  vec2 p3 = computeNDC(position, vec4( .5,  .5, -.5, 0));
  vec2 p4 = computeNDC(position, vec4(-.5, -.5,  .5, 0));
  vec2 p5 = computeNDC(position, vec4( .5, -.5,  .5, 0));
  vec2 p6 = computeNDC(position, vec4(-.5,  .5,  .5, 0));
  vec2 p7 = computeNDC(position, vec4( .5,  .5,  .5, 0));
  
  vec2 minNDC = 
    min(p0, min(p1, min(p2, min(p3, min(p4, min(p5, min(p6, p7)))))));
  vec2 maxNDC = 
    max(p0, max(p1, max(p2, max(p3, max(p4, max(p5, max(p6, p7)))))));

  vec2 minScreen = (minNDC * 0.5 + 0.5) * resolution;
  vec2 maxScreen = (maxNDC * 0.5 + 0.5) * resolution;
  vec2 rangeScreen = ceil(maxScreen) - floor(minScreen);
  float sizeScreen = max(rangeScreen.x, rangeScreen.y);
  
  // sizeSize is now how large the point has to be to touch the 
  // corners
  
  gl_PointSize = sizeScreen;
  
  vec4 pos = projection * modelView * position;
  
  // clip ourselves
  if (pos.x < -pos.w || pos.x > pos.w) {
    gl_Position = vec4(0,0,-10,1);
    return;
  }
 
  // pos is the wrong place to put the point. The correct
  // place to put the point is the center of the extents
  // of the screen space points
  
  gl_Position = vec4(
    (minNDC + (maxNDC - minNDC) * 0.5) * pos.w,
    pos.z,
    pos.w);
    
  v_normal = mat3(modelView) * normal;
  v_color = color;
}
`;

const fs = `
precision highp float;

varying vec3 v_normal;
varying vec3 v_color;

void main() {
  vec3 lightDirection = normalize(vec3(1, 2, 3));  // arbitrary light direction
  
  float l = dot(lightDirection, normalize(v_normal)) * .5 + .5;
  gl_FragColor = vec4(v_color * l, 1);
  gl_FragColor.rgb *= gl_FragColor.a;
}
`;

// compile shader, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// make some vertex data
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  position,
  normal,
  color: { numComponents: 3, data: color },
});

let camera;
const eye = [10, 10, 55];
const target = [0, 0, 0];
const up = [0, 1, 0];
const speed = 0.5;

const kUp = 38;
const kDown = 40;
const kLeft = 37;
const kRight = 39;
const kForward = 87;
const kBackward = 83;
const kSlideLeft = 65;
const kSlideRight = 68;

const keyMove = new Map();
keyMove.set(kForward,    { ndx: 8, eye:  1, target: -1 });
keyMove.set(kBackward,   { ndx: 8, eye:  1, target:  1 });
keyMove.set(kSlideLeft,  { ndx: 0, eye:  1, target: -1 });
keyMove.set(kSlideRight, { ndx: 0, eye:  1, target:  1 });
keyMove.set(kLeft,       { ndx: 0, eye:  0, target: -1 });
keyMove.set(kRight,      { ndx: 0, eye:  0, target:  1 });
keyMove.set(kUp,         { ndx: 4, eye:  0, target: -1 });
keyMove.set(kDown,       { ndx: 4, eye:  0, target:  1 });

function render() {  
  twgl.resizeCanvasToDisplaySize(gl.canvas);
  
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.CULL_FACE);
  
  const fov = Math.PI * 0.25;
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const near = 0.1;
  const far = 1000;
  const projection = m4.perspective(fov, aspect, near, far);
  
  camera = m4.lookAt(eye, target, up);
  const view = m4.inverse(camera);
  const modelView = m4.translate(view, [width / -2, 0, width / -2]);
  
  gl.useProgram(programInfo.program);
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
  twgl.setUniforms(programInfo, {
    projection,
    modelView,
    resolution: [gl.canvas.width, gl.canvas.height],
  });  
  
  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo, gl.POINTS);
}
render();

window.addEventListener('keydown', (e) => {
  e.preventDefault();
  
  const move = keyMove.get(e.keyCode);
  if (move) {
    const dir = camera.slice(move.ndx, move.ndx + 3);
    const delta = v3.mulScalar(dir, speed * move.target);
    v3.add(target, delta, target);
	  if (move.eye) {
	    v3.add(eye, delta, eye);
    }    
    render();
  }
});
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
#i { position: absolute; top: 0; left: 5px; font-family: monospace; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
<div id="i">ASWD ⬆️⬇️⬅️➡️</div>

即使最重要的是,您还会遇到其他问题POINTS

  1. 最大点大小只需为 1。

    规范说实现可以选择他们支持的最大大小点,并且 at 必须至少为 1。换句话说,某些实现可能只支持 1 的点大小。检查 WebGLStats看起来它看起来实际上你可能没问题,但仍然...

  2. 一些实现正确地剪辑 POINTS 并且不太可能被修复

    https://stackoverflow.com/a/56066386/128511


推荐阅读