我在 3D 世界空间中有一个点云,我想通过使用 MVP 矩阵投影它的各个点来将其渲染到屏幕上。但投影似乎有些不对劲。似乎有 2 个点云副本在所有轴上镜像,因此沿相反方向移动:
为了调试该问题,我尝试仅渲染一个像素并将该点修复为 (0, 0, 0)。该像素仍然在相反方向出现两次:
这是相机位于其上方的像素 (0, 1, 0),显示在其上方和下方:
这是我用来计算 MVP 矩阵、分派着色器并 blit 到输出纹理的 C# 脚本:
using UnityEngine;
public class MultiPass : MonoBehaviour
{
public Texture2D input;
public ComputeShader computeShader;
public Vector2Int projectionResolution;
public RenderTexture transformed;
private Camera mainCamera;
private int project, projectGroupsX, projectGroupsY;
private void Start()
{
mainCamera = GetComponent<Camera>();
project = computeShader.FindKernel("Project");
computeShader.GetKernelThreadGroupSizes(project, out uint threadGroupSizeX, out uint threadGroupSizeY, out _);
projectGroupsX = projectionResolution.x / (int)threadGroupSizeX;
projectGroupsY = projectionResolution.y / (int)threadGroupSizeY;
transformed = new RenderTexture(projectionResolution.x, projectionResolution.y, 0);
transformed.enableRandomWrite = true;
transformed.format = RenderTextureFormat.RGFloat;
computeShader.SetFloat("PI", Mathf.PI);
computeShader.SetFloat("PI2", Mathf.PI * 2);
computeShader.SetVector("INPUT_RESOLUTION", new Vector2(input.width, input.height));
computeShader.SetVector("PROJECTION_RESOLUTION", new Vector2(projectionResolution.x, projectionResolution.y));
computeShader.SetTexture(project, "Input", input);
computeShader.SetTexture(project, "Transformed", transformed);
}
private void Update()
{
RenderTexture rt = RenderTexture.active;
RenderTexture.active = transformed;
GL.Clear(false, true, Color.clear);
RenderTexture.active = rt;
Matrix4x4 MVP = GL.GetGPUProjectionMatrix(mainCamera.projectionMatrix, true) * mainCamera.worldToCameraMatrix;
computeShader.SetMatrix("MVP", MVP);
computeShader.Dispatch(project, projectGroupsX, projectGroupsY, 1);
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(transformed, destination);
}
}
这是将点与矩阵相乘的着色器:
#define NCLIP 1
#define FCLIP 30
#define MAP_TO_RANGE(tc, targetRange) \
((tc) * ((targetRange) - 1))
#define NORMALIZE_RANGE(tc, sourceRange) \
((tc) / ((sourceRange) - 1))
Texture2D<float4> Input;
RWTexture2D<float2> Transformed;
float2 INPUT_RESOLUTION, PROJECTION_RESOLUTION;
float PI, PI2;
float4x4 MVP;
#pragma kernel Project
[numthreads(8, 8, 1)]
void Project(uint3 id : SV_DispatchThreadID)
{
// 1. calculate some points (the depth based point cloud or just a sphere)
float2 uv = NORMALIZE_RANGE(id.xy, PROJECTION_RESOLUTION);
float d = Input[MAP_TO_RANGE(uv, INPUT_RESOLUTION)].a;
float2 ll1 = uv.yx;
ll1.x *= PI;
ll1.y *= PI2;
ll1.y += PI;
float CP = d * (FCLIP - NCLIP) + NCLIP;
// override point cloud with sphere
// CP = FCLIP;
float3 P = float3(
CP * sin(ll1.y) * sin(ll1.x),
CP * cos(ll1.x),
CP * cos(ll1.y) * sin(ll1.x));
// for only transforming one point
/*
if (!any(id.xy))
P = float3(0, 0, 0);
else
return;
*/
// 2. the actual matrix multiplication
float4 pos4 = float4(P, 1);
float4 transformedPosition = mul(MVP, pos4);
float2 screenCoords;
screenCoords = (transformedPosition.xy / transformedPosition.w) * 0.5 + 0.5;
screenCoords = MAP_TO_RANGE(screenCoords, PROJECTION_RESOLUTION);
Transformed[screenCoords] = float2(1, 1);
}
我在这里绝对是走进了死胡同。我无法想象点生成是一个问题,因为即使我仅在 (0, 0, 0) 处的一个点进行硬编码,问题仍然存在。我也无法想象问题在于转换为屏幕/纹理坐标,因为这个问题可能会以扭曲甚至颠倒图像的形式显示,但不是在 3D 空间中精确移动的 2 组点。所以我想,唯一剩下的就是投影矩阵。但我尝试了几乎所有可以在网上找到的构建矩阵的方法,但没有一个对这个问题产生影响。因此,我将非常感谢任何关于我正在做的矩阵乘法可能出现的问题或我可以采取的其他步骤来尝试和调试这个问题的想法。另外,如果脚本的任何其他方面可能导致问题,我很乐意知道,以便我可以进行调查。
顺便说一句,很抱歉质量很差,我想我不能在这里嵌入除了 gif 之外的任何东西。如果有人想要更详细的文件,请告诉我
好吧,又是一次相当平庸的 stackoverflow 体验。花一天时间调试这个问题,找到示例,将它们可视化,并重构最初的大约 1000 行代码,使其尽可能具体到示例。只是在没有解释的情况下立即被否决。
无论如何,我已经能够通过在CPU上进一步调试单个矩阵乘法来弄清楚它。事实证明,如果有东西在相机后面,它也会被渲染,但是以这种镜像的方式。所以基本上,解决这个问题就是检测相机前面还是后面有什么东西。我能够通过检查转换后的向量的 w 分量来区分。添加这样的检查解决了问题:
if (transformedPosition.w <= 0)
{
return;
}
另外,在转换为屏幕空间时,不要忘记这样的检查:
if (screenCoords.x < 0 || screenCoords.y < 0)
{
return;
}
希望这可以帮助某人