我有一些关于使用SDF函数在GLSL中进行球体跟踪/射线行进的问题:
我的主程序(C,使用Vulkan)生成一个屏幕四边形,并为顶点着色器提供每个顶点的位置
。顶点着色器可以访问窗口分辨率、投影矩阵和视图矩阵。投影矩阵使用glm::透视(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球体设置不同的位置/半径?
这是我的代码,从片段的坐标计算世界空间中的射线。它使用一组统一变量,在下面的代码中模仿旧的固定功能管道(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)。
我花了几天的时间来解决这个问题,因为我需要得到一个精确的解,以便在双眼同步视图的VR中使用射线推进。最终的解决方案是使用模型视图投影矩阵来反转出现在顶点着色器中的标准化设备坐标:从模型视图投影矩阵计算射线的起源和方向