GLSL:如何使用投影矩阵计算光线方向?

Posted

技术标签:

【中文标题】GLSL:如何使用投影矩阵计算光线方向?【英文标题】:GLSL: How to calculate a ray direction using the projection matrix? 【发布时间】:2017-03-06 19:10:07 【问题描述】:

我在使用 SDF 函数在 GLSL 中进行球体追踪/光线行进时遇到了一些问题:

我的主程序(C++,使用 Vulkan)生成一个屏幕四边形,并为顶点着色器提供每个顶点 inPosition。顶点着色器可以访问窗口分辨率、投影矩阵和视图矩阵。使用glm::perspective(45.0, 1920/1080, 0.1, 100.0);生成投影矩阵。

在顶点着色器中,我尝试计算从vec4(0.0, 0.0, 0.0, 1.0) 的原点穿过图像平面的光线(使用齐次坐标的位置和方向)。我很困惑在哪里放置图像平面并选择 vec4(inPosition.xy, -5.0, 1.0) 现在沿着负 z 轴查看。

以下代码代表我的顶点着色器:

#version 450
#extension GL_ARB_separate_shader_objects : enable

struct Ray

  vec4 pos;
  vec4 dir;
;

layout(binding = 0) uniform UniformBufferObject 
  vec3 res;
  mat4 projection;
  mat4 view;
 ubo;

layout(location = 0) in vec3 inPosition;

layout(location = 0) out vec3 iResolution;
layout(location = 1) out Ray iRay;

out gl_PerVertex 
  vec4 gl_Position;
;

void main() 
  fragCoord = vec2(
    ((inPosition.x+1)/2) * (ubo.res.x-1),
    ((inPosition.y+1)/2) * (ubo.res.y-1)
  );
  iResolution = ubo.res;
  gl_Position = vec4(inPosition, 1.0);
  vec4 direction = inverse(ubo.projection) * vec4(inPosition.xy, -5.0, 1.0);
  iRay.dir = direction;
  iRay.pos = vec4(direction.xy, 0.0, 1.0);

我使用投影矩阵将方向转换为世界空间并将单位立方体扭曲为窗口分辨率。但是,在我的片段着色器中,SDF 函数和交叉点无法正常工作。如果我为距离和半径设置相同的值,我只能看到一个球体。查看片段着色器:

#version 450
#extension GL_ARB_separate_shader_objects : enable

struct Ray

  vec4 pos;
  vec4 dir;
;

layout(location = 0) in vec3 iResolution;
layout(location = 1) in Ray iRay;

layout(location = 0) out vec4 outColor;

float sdfSphere(vec3 p, float r) 

  return length(p) - r;


bool intersect(Ray ray)

  for(int i = 0; i < 100; i++) 
    float hit = sdfSphere((ray.pos.xyz + vec3(0.0, 0.0, -11.0)), 11.0);
    ray.pos += hit * ray.dir;
    if (hit < 0.001) 
      return true;
    
  
  return false;


void main() 

  bool result = intersect(iRay);
  if(result == false) 
    outColor = vec4(0.0, 0.0, 0.0, 1.0);
   else 
    outColor = vec4(1.0, 0.0, 0.0, 1.0);
  

我的问题是:如何正确应用投影矩阵?如果它已经正确应用,为什么我不能为 SDF 球体设置不同的位置/半径?

【问题讨论】:

如果你使用 Vulkan,你应该这样标记它,而不是使用 OpenGL 标记。 【参考方案1】:

这是我从片段坐标计算世界空间中的射线的代码。它在以下代码中使用一组模仿旧的固定功能管道(GLUP 统一变量)的统一变量。棘手的部分是正确应用视口变换,并考虑到一些变量在 [-1,1] 中,而另一些在 [0,1] 中(让我头疼不已)。

struct Ray 
    vec3 O; // Origin
    vec3 V; // Direction vector
;

// Notes: GLUP.viewport = [x0,y0,width,height]
// clip-space coordinates are in [-1,1] (not [0,1]) !

// Computes the ray that passes through the current fragment
// The ray is in world space.
Ray glup_primary_ray() 
    vec4 near = vec4(
    2.0 * ( (gl_FragCoord.x - GLUP.viewport[0]) / GLUP.viewport[2] - 0.5),
    2.0 * ( (gl_FragCoord.y - GLUP.viewport[1]) / GLUP.viewport[3] - 0.5),
        0.0,
        1.0
    );
    near = GLUP.inverse_modelviewprojection_matrix * near ;
    vec4 far = near + GLUP.inverse_modelviewprojection_matrix[2] ;
    near.xyz /= near.w ;
    far.xyz /= far.w ;
    return Ray(near.xyz, far.xyz-near.xyz) ;


// Updates fragment depth from a point in world space
void glup_update_depth(in vec3 M_world_space) 
    vec4 M_clip_space = GLUP.modelviewprojection_matrix * vec4(M_world_space,1.0);
    float z = 0.5*(1.0 + M_clip_space.z/M_clip_space.w);
    glup_FragDepth = (1.0-z)*gl_DepthRange.near + z*gl_DepthRange.far;

使用 glup_primary_ray() 绘制光线追踪球体的示例片段着色器:

in vec3 C; // center in world space;
in float r;

void main(void) 
  Ray R = glup_primary_ray();
    vec3 M,N;

    if(
    glupIsEnabled(GLUP_CLIPPING) &&
    GLUP.clipping_mode == GLUP_CLIP_SLICE_CELLS
    ) 
    N = GLUP.world_clip_plane.xyz;
    float w = GLUP.world_clip_plane.w;
    float t = -(w + dot(N,R.O)) / dot(N,R.V);
    M = R.O + t*R.V;
    if(dot(M-C,M-C) > r*r) 
        discard;
    
     else 
    vec3 D = R.O-C;    
    float a = dot(R.V,R.V);
    float b = 2.0*dot(R.V,D);
    float c = dot(D,D)-r*r;
    float delta = b*b-4.0*a*c;

    if(delta < 0.0) 
        discard;
    
    float t = (-b-sqrt(delta))/(2.0*a);
    M = R.O + t*R.V;
    N = M-C;
    //insert here code to compute the shading with N

    //update the depth buffer
    glup_update_depth(M);
       

完整的代码可在我的 GEOGRAM 库中找到:http://alice.loria.fr/software/geogram/doc/html/index.html (src/lib/geogram_gfx/GLUP/shaders)。

【讨论】:

【参考方案2】:

我花了好几天的时间来解决这个问题,因为我需要获得一个精确的解决方案,才能在 VR 中使用双眼同步视图的光线跟踪。 最终的工作解决方案是使用模型视图投影矩阵反转出现在顶点着色器中的标准化设备坐标: Computing ray origin and direction from Model View Projection matrices for raymarching

【讨论】:

请不要只写链接答案。

以上是关于GLSL:如何使用投影矩阵计算光线方向?的主要内容,如果未能解决你的问题,请参考以下文章

光线投射算法(如何计算一个坐标点是不是在一个多边形内)

OpenGL GLSL - 投影矩阵不起作用

如何使用 OpenGL 正确投影和扭曲 3d 点

如何获得可用于点光阴影贴图的投影矩阵?

GLSL 计算着色器闪烁块/正方形伪影

如何在 OpenGL ES 2.0 中将视图/模型/投影矩阵传递给我的顶点着色器?