我想要达到的目标
所以我是一个分形爱好者,并决定使用 raymarching 在 WebGL 中构建一个 2D/3D 分形生成器,使用 Typescript 作为脚本语言。我多年来一直是 C#/Typescript 开发人员,但对 3D 编程的经验为零,我使用 Michael Walczyk 的博客作为起点。我在这里使用的一些代码来自他的教程。
我添加了可以使用 WASDQEZC 键在对象中移动的功能。WS = 前后扫射,AD = 左右扫射,QE = 上下扫射,ZC = 左右滚动。我将它与鼠标外观功能结合起来,该功能在鼠标指针位于渲染画布上的方向上移动。所以我想要的是完全自由的移动,就像在太空模拟中一样。为此,我使用了一个单独的相机旋转矩阵和平移值,并将它们发送到着色器,如下所示:
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
是一个 vec2,x,y 坐标来自顶点着色器。:
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 轴上下查看时,我得到一个“死区”。我发现这个线程似乎描述了我的问题并说“诀窍是将平移应用到 z 轴,但在相机的局部坐标系中。”
但是我如何用我现有的代码做到这一点?我尝试将世界矩阵乘以u_world
旋转矩阵,u_rotationMatrix
但随后照明也会发生变化,它只是对象旋转而不是单独的相机旋转。在我发布的线程中,没有照明,因此将相机矩阵与世界矩阵相乘对他们有用。但它不适合我,因为我实施了照明。此外,我似乎无法以这种方式单独应用法线,因此我只将法线应用到世界矩阵而不是相机旋转矩阵,这样当我旋转/平移相机时,照明就会保持在原位。
我可以获得世界矩阵和单独相机矩阵的正确法线的唯一方法是将rotationMatrix与rayDirection相乘,如下所示u_cameraRotation * vec4(rayDirection(u_foV,v_position),1)
。但是当我这样做时,我必须应用所有这些可怕的、部分工作的三角函数来得到一些像样的东西。我想要的是让它像“诀窍是将平移应用到 z 轴但在相机的局部坐标系中”一样工作。
但我不知道怎么做。我尝试了各种各样的事情,但我目前陷入困境。任何帮助将不胜感激。我想我已经足够充分地概述了我的问题,如果您遗漏了什么,请告诉我。提前致谢。