我怎样才能在我的 3d raymarched 世界中正确 translate/rotate 我的相机

How can I correctly translate/rotate my camera in my 3d raymarched world

我想要达到的目标

所以我是一个分形爱好者,决定使用光线行进在 WebGL 中构建一个 2D/3D 分形生成器,使用 Typescript 作为脚本语言。我从事 C#/Typescript 开发已有多年,但对 3d 编程的经验为零,因此我使用 Michael Walczyk 的博客作为起点。我在这里使用的一些代码来自他的教程。

我添加了您可以使用 WASDQEZC 键在对象中移动的功能。 WS = 前后扫射,AD = 左右扫射,QE = 上下扫射,ZC = 左右滚转。我将其与鼠标外观功能结合使用,该功能沿鼠标指针在渲染 canvas 上的方向移动。所以我想要的是像在 spacesim 中一样完全的运动自由。为此,我使用一个单独的相机旋转矩阵和平移值并将它们发送到着色器,如下所示:

  setCameraMatrix(): void {
    let cameraRotationMatrixLocation: WebGLUniformLocation = this.currentContext.getUniformLocation(this.currentProgram, "u_cameraRotation");
    let cameraTranslationLocation: WebGLUniformLocation = this.currentContext.getUniformLocation(this.currentProgram, "u_cameraTranslation");
    let foVLocation: WebGLUniformLocation = this.currentContext.getUniformLocation(this.currentProgram, "u_foV");

    //add point of camera rotation at beginning
    let cameraRotationMatrix: Array<number> = Matrix3D.identity();

    //set camera rotation and translation, Z-axis (heading) first, then X-axis (pitch), then Y-axis (roll)
    cameraRotationMatrix = Matrix3D.multiply(cameraRotationMatrix, Matrix3D.rotateZ(this.cameraRotateZ * Math.PI / 180));
    cameraRotationMatrix = Matrix3D.multiply(cameraRotationMatrix, Matrix3D.rotateX(this.cameraRotateX * Math.PI / 180));
    cameraRotationMatrix = Matrix3D.multiply(cameraRotationMatrix, Matrix3D.rotateY(this.cameraRotateY * Math.PI / 180));
    //cameraRotationMatrix = Matrix3D.multiply(cameraRotationMatrix, Matrix3D.translate(this.cameraTranslateX, this.cameraTranslateY, this.cameraTranslateZ));
    cameraRotationMatrix = Matrix3D.inverse(cameraRotationMatrix);

    let cameraPosition: Array<number> = [
      this.cameraTranslateX,
      this.cameraTranslateY,
      -this.cameraTranslateZ,
    ];

    this.currentContext.uniformMatrix4fv(cameraRotationMatrixLocation, false, cameraRotationMatrix);
    this.currentContext.uniform3fv(cameraTranslationLocation, cameraPosition);
    this.currentContext.uniform1f(foVLocation, this.foV);
  }

我尝试将相机平移值添加到相机矩阵,但这没有用。我得到了奇怪的失真效果并且无法正确处理所以我注释掉了那条线并暂时将其留在那里为了清楚起见。我这样做的原因是因为我的 GLSL 代码的构造方式:

来自片段着色器的主函数调用 ray_march 函数。 v_position 是一个 x,y 坐标来自顶点着色器的 vec2。:

    void main() {
      outColor = vec4(ray_march(u_cameraTranslation, u_cameraRotation * vec4(rayDirection(u_foV,v_position),1), u_world, vec3(u_light * vec4(0,0,0,1)).xyz ).xyz,1);
    }

我正在使用的ray_march功能。这源自 Michael Walczyk 博客中的示例代码。

    vec3 ray_march(in vec3 ro, in vec4 rd, in mat4 wm, in vec3 lightPosition) //ro = ray origin, rd = ray direction gt = geometry position after matrix multiplication
    {
        float total_distance_traveled = 0.0;
        const int NUMBER_OF_STEPS = 1024;

        float MINIMUM_HIT_DISTANCE = 0.001 * min_hit_distance_correction;

        const float MAXIMUM_TRACE_DISTANCE = 1000.0;

        for (int i = 0; i < NUMBER_OF_STEPS; i++)
        {
            vec3 current_position = ro + total_distance_traveled * vec3(rd);

            float distance_to_closest = map(current_position, wm);

            if (distance_to_closest < MINIMUM_HIT_DISTANCE) 
            {
              vec3 normal = calculate_normal(current_position, wm);
              vec3 outColor = vec3(1.0,0,0);
              vec3 v_surfaceToLight = lightPosition - current_position;
              vec3 v_surfaceToView = ro - current_position;

              //insert lighting code below this line

              return outColor;
            }

            if (total_distance_traveled > MAXIMUM_TRACE_DISTANCE)
            {
              break;
            }

            total_distance_traveled += distance_to_closest;
        }

        return vec3(0.25);//gray background
    }

我正在使用的 rayDirection 函数。

  vec3 rayDirection(float fieldOfView, vec2 p) {
    float z = 1.0 / (tan(radians(fieldOfView) / 2.0));
    return normalize(vec3(p.xy, -z));
  }

我的问题

我在 3d 世界中正确移动和旋转相机时遇到问题。我通过应用一些三角学来使运动正确来做到这一点。例如,当我向前移动时,就是 Z 轴。但是当我向右转 90 度时,X 轴现在变成了 Z 轴。我正在使用三角函数来纠正这个问题并且实际上得到了一些工作但现在我陷入了三角函数的泥潭,看不到尽头,我觉得必须有更好和更简单的方法。看看我在说什么,这里是 'move' 函数的代码:

  move(event: KeyboardEvent): void {
    
    //strafe forward-back
    let tXForwardBack: number = (Math.sin(this.cameraRotateY * Math.PI / 180) * Math.cos(this.cameraRotateX * Math.PI / 180)) * this.clipSpaceFactor * this.speed;
    let tYForwardBack: number = Math.sin(this.cameraRotateX * Math.PI / 180) * this.speed;
    let tZForwardBack: number = (Math.cos(this.cameraRotateY * Math.PI / 180) * Math.cos(this.cameraRotateX * Math.PI / 180)) * this.clipSpaceFactor * this.speed;

    //strafe up-down
    let tXUpDown: number = ((Math.sin(this.cameraRotateX * Math.PI / 180) * Math.sin(this.cameraRotateY * Math.PI / 180)) * this.clipSpaceFactor * this.speed);
    let tYUpDown: number = Math.cos(this.cameraRotateX * Math.PI / 180) * this.speed;
    let tZUpDown: number = Math.sin(this.cameraRotateX * Math.PI / 180) * Math.cos(this.cameraRotateY * Math.PI / 180) * this.clipSpaceFactor * this.speed;

    //strafe left-right without roll. TODO: implement roll
    let tXLeftRight: number = Math.cos(this.cameraRotateY * Math.PI / 180) * this.clipSpaceFactor * this.speed;
    let tYLeftRight: number = 0;
    let tZLeftRight: number = Math.sin(this.cameraRotateY * Math.PI / 180) * this.clipSpaceFactor * this.speed;

    switch (event.key) {
      case "w": { //strafe forward
        this.cameraTranslateX = this.cameraTranslateX + tXForwardBack;
        this.cameraTranslateY = this.cameraTranslateY - tYForwardBack;
        this.cameraTranslateZ = this.cameraTranslateZ + tZForwardBack;
        //this.cameraTranslateZ = this.cameraTranslateZ + (this.clipSpaceFactor * this.speed);
        break;
      }
      case "s": { //strafe back
        this.cameraTranslateX = this.cameraTranslateX - tXForwardBack;
        this.cameraTranslateY = this.cameraTranslateY + tYForwardBack;
        this.cameraTranslateZ = this.cameraTranslateZ - tZForwardBack;
        break;
      }
      case "a": {//strafe left
        this.cameraTranslateX = this.cameraTranslateX - tXLeftRight;
        this.cameraTranslateY = this.cameraTranslateY + tYLeftRight;
        this.cameraTranslateZ = this.cameraTranslateZ + tZLeftRight;
        break;
      }
      case "d": { //strafe right
        this.cameraTranslateX = this.cameraTranslateX + tXLeftRight;
        this.cameraTranslateY = this.cameraTranslateY - tYLeftRight;
        this.cameraTranslateZ = this.cameraTranslateZ - tZLeftRight;
        break;
      }
      case "q": { //strafe up
        this.cameraTranslateX = this.cameraTranslateX + tXUpDown;
        this.cameraTranslateY = this.cameraTranslateY + tYUpDown;
        this.cameraTranslateZ = this.cameraTranslateZ + tZUpDown;
        break;
      }
      case "e": { //strafe down
        this.cameraTranslateX = this.cameraTranslateX - tXUpDown;
        this.cameraTranslateY = this.cameraTranslateY - tYUpDown;
        this.cameraTranslateZ = this.cameraTranslateZ - tZUpDown;
        break;
      }
      case "z": { //roll left
        this.cameraRotateZ = (this.cameraRotateZ + (this.sensitivity * this.speed)) % 360;
        break;
      }
      case "c": { //roll right
        this.cameraRotateZ = (this.cameraRotateZ - (this.sensitivity * this.speed)) % 360;
        break;
      }
    }

它在某种程度上确实有效,但你可以看到它的发展方向:(另外,当我沿 Y 轴上下查看时,我得到一个 'dead' 区域。我发现 这似乎描述了我的问题并说 'The trick is to apply the translation to the z-axis but in the local coordinate system of the camera.'

但是我如何使用我现有的代码来做到这一点?我尝试将世界矩阵 u_world 与旋转矩阵 u_rotationMatrix 相乘,但随后光照也发生变化,这只是一个对象旋转而不是单独的相机旋转。在我发布的线程中,没有照明,因此将相机矩阵与世界矩阵相乘对他们有用。但由于我实施的照明,它不适合我。另外,我似乎无法通过这种方式单独应用法线,因此我只将法线应用于世界矩阵而不是相机旋转矩阵,这样当我 rotate/translate 相机时,照明会保持原样。

我可以获得正确的世界矩阵法线和单独的相机矩阵的唯一方法是像这样 u_cameraRotation * vec4(rayDirection(u_foV,v_position),1) 将 rotationMatrix 与 rayDirection 相乘。但是当我这样做时,我必须应用所有这些可怕的、部分工作的三角函数混乱来得到一些像样的东西。我想要的是让它像 'The trick is to apply the translation to the z-axis but in the local coordinate system of the camera.'

一样工作

但我不知道怎么做。我尝试了各种各样的事情,但我现在被困住了。任何帮助将不胜感激。我想我已经足够充分地概述了我的问题,如果您遗漏任何内容,请告诉我。提前致谢。

看来我自己找到了答案。我应用了 this question which is similar to mine 中 Adisak 的部分回答。我用旋转顺序 ZXY 应用了他的 EulerAnglesToMatrix 函数,然后像这样提取 x、y 和 z 轴:

    let mx: Array<number> = Matrix3D.eulerAnglesToMatrix(pitch, yaw, roll, "ZXY");

    let xAxis: Array<number> = mx.slice(0, 3); //x,y,z
    let yAxis: Array<number> = mx.slice(3, 6); //x,y,z
    let zAxis: Array<number> = mx.slice(6, 9); //x,y,z

然后我像这样应用翻译,将 [this.cameraTranslateX,this.cameraTranslateY,this.cameraTranslateZ] 设置为片段着色器的统一 vec3 u_cameraTranslation 变量:

    switch (event.key) {
      case "w": { //strafe forward
        this.cameraTranslateX = this.cameraTranslateX - ((zAxis[0]) * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY - ((zAxis[1]  ) * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ + ((zAxis[2] ) * this.clipSpaceFactor * this.speed);
        break;
      }
      case "s": { //strafe back
        this.cameraTranslateX = this.cameraTranslateX + ((zAxis[0] ) * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY + ((zAxis[1] ) * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ - ((zAxis[2] ) * this.clipSpaceFactor * this.speed);
        break;
      }
      case "a": {//strafe left
        this.cameraTranslateX = this.cameraTranslateX - (xAxis[0] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY - (xAxis[1] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ + (xAxis[2] * this.clipSpaceFactor * this.speed);
        break;
      }
      case "d": { //strafe right
        this.cameraTranslateX = this.cameraTranslateX + (xAxis[0] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY + (xAxis[1] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ - (xAxis[2] * this.clipSpaceFactor * this.speed);
        break;
      }
      case "q": { //strafe up
        this.cameraTranslateX = this.cameraTranslateX + (yAxis[0] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY + (yAxis[1] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ - (yAxis[2] * this.clipSpaceFactor * this.speed);
        break;
      }
      case "e": { //strafe down
        this.cameraTranslateX = this.cameraTranslateX - (yAxis[0] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateY = this.cameraTranslateY - (yAxis[1] * this.clipSpaceFactor * this.speed);
        this.cameraTranslateZ = this.cameraTranslateZ + (yAxis[2] * this.clipSpaceFactor * this.speed);
        break;
      }
      case "z": { //roll left
        this.cameraRotateZ = (this.cameraRotateZ + (this.sensitivity * this.speed)) % 360;
        break;
      }
      case "c": { //roll right
        this.cameraRotateZ = (this.cameraRotateZ - (this.sensitivity * this.speed)) % 360;
        break;
      }
    }

我保留了光线行进功能。这正是我想要的。