我成功地将
背向追踪转换成了适用于
GLSL的迭代过程,使用了我在评论中提出的方法。虽然离优化还有很大的距离,而且我还没有实现所有的物理特性(如斯涅尔定律等...),但作为概念证明已经可以工作了。我把所有的东西都放在片段着色器里,
CPU端的代码只是以
32位非夹紧浮点纹理GL_LUMINANCE32F_ARB
的形式发送
uniforms
常量和场景。渲染只是单一的
QUAD
覆盖整个屏幕。
- 传递场景
我决定将场景存储在纹理中,这样每个射线/片段就可以直接访问整个场景。这个纹理是2D的,但它被用作32位浮点数的线性列表。我选择了这种格式:
enum _fac_type_enum
{
_fac_triangles=0, // r,g,b,a, n, triangle count, { x0,y0,z0,x1,y1,z1,x2,y2,z2 }
_fac_spheres, // r,g,b,a, n, sphere count, { x,y,z,r }
};
const GLfloat _n_glass=1.561;
const GLfloat _n_vacuum=1.0;
GLfloat data[]=
{
// r, g, b, a, n, type,count
0.2,0.3,0.5,0.5,_n_glass,_fac_triangles, 4, // tetrahedron
// px, py, pz, r, g, b
-0.5,-0.5,+1.0,
0.0,+0.5,+1.0,
+0.5,-0.5,+1.0,
0.0, 0.0,+0.5,
-0.5,-0.5,+1.0,
0.0,+0.5,+1.0,
0.0, 0.0,+0.5,
0.0,+0.5,+1.0,
+0.5,-0.5,+1.0,
0.0, 0.0,+0.5,
+0.5,-0.5,+1.0,
-0.5,-0.5,+1.0,
};
您可以添加/更改任何类型的对象。本示例仅包含单个半透明蓝色四面体。您还可以添加变换矩阵、材料属性的更多系数等...
- 架构
顶点着色器只初始化视图的角落光线(起始位置和方向),这些光线进行插值,因此每个片段表示后向光线追踪过程的起始光线。
迭代后向光线跟踪
因此,我创建了一个“静态”光线列表,并用起始光线进行初始化。迭代分为两个步骤,首先是后向光线跟踪:
- 循环遍历来自第一个的所有光线
- 找到与场景的最近交点...
将位置、表面法线和材料属性存储到光线 struct
中
- 如果找到交点且不是最后一层“递归”,则在列表末尾添加反射/折射光线。
同时,将它们的索引存储到已处理的光线struct
中。
现在,您的光线应该包含您需要重建颜色所需的所有交点信息。为此:
- 倒序遍历所有递归级别
- 对于匹配当前递归层的每个光线
- 计算光线颜色
因此,使用您想要的灯光方程。如果光线包含子节点,请根据材料属性(反射和折射系数...)将其颜色添加到结果中。
现在,第一条光线应该包含您要输出的颜色。
使用的Uniforms:
tm_eye
视图摄像机矩阵
aspect
视图ys/xs纵横比
n0
空间折射率(尚未使用)
focal_length
相机焦距
fac_siz
场景方形纹理的分辨率
fac_num
实际使用的浮点数数量
fac_txr
场景纹理单元
预览:
![preview](https://istack.dev59.com/maSq7.webp)
片段着色器包含我的调试打印,因此如果使用纹理,则还需要该纹理,请参见QA:
待办事项:
为对象、相机等添加矩阵。
添加材质属性(光泽度,反射/折射系数)。
斯涅尔定律:现在新光线的方向是错误的……
可以将R,G,B分别作为三个起始光线,最后再合并。
基于光线长度伪造SSS次表面散射。
更好地实现灯光(现在它们只是代码中的常量)。
实现更多基元(现在仅支持三角形)。
[编辑1] 代码调试和升级
我删除了旧的源代码以适应30KB的限制。如果您需要它,请从编辑历史记录中查找。我有更多时间进行高级调试,以下是结果:
![preview](https://istack.dev59.com/ZdwLJ.webp)
此版本解决了一些几何、精度、域问题和错误。我已经实现了反射和折射,如测试光线所示:
![debug view](https://istack.dev59.com/RbeUG.webp)
在调试视图中,只有方块是透明的,最后一条未命中任何物体的光线被忽略。所以你可以看到光线分裂了……由于全反射角度,光线结束在方块内部。我出于速度原因禁用了物体内部的所有反射。
32位的floats
用于交点检测时会有一些噪声,所以您可以改用64位的doubles
,但速度会显著下降。另一个选择是重写方程以使用相对坐标,在这种情况下更精确。
这里是float
着色器源代码:
顶点:
#version 420 core
uniform float aspect;
uniform float focal_length;
uniform mat4x4 tm_eye;
layout(location=0) in vec2 pos;
out smooth vec2 txt_pos;
out smooth vec3 ray_pos;
out smooth vec3 ray_dir;
void main(void)
{
vec4 p;
txt_pos=pos;
p=tm_eye*vec4(pos.x/aspect,pos.y,0.0,1.0);
ray_pos=p.xyz;
p-=tm_eye*vec4(0.0,0.0,-focal_length,1.0);
ray_dir=normalize(p.xyz);
gl_Position=vec4(pos,0.0,1.0);
}
碎片:
#version 420 core
in smooth vec3 ray_pos;
in smooth vec3 ray_dir;
uniform float n0;
uniform int fac_siz;
uniform int fac_num;
uniform sampler2D fac_txr;
out layout(location=0) vec4 frag_col;
#define _reflect
#define _refract
#ifdef _debug_print
in vec2 txt_pos;
uniform sampler2D txr_font;
uniform float txt_fxs,txt_fys;
const int _txtsiz=64;
int txt[_txtsiz],txtsiz;
vec4 txt_col=vec4(0.0,0.0,0.0,1.0);
bool _txt_col=false;
void txt_decimal(vec2 v);
void txt_decimal(vec3 v);
void txt_decimal(vec4 v);
void txt_decimal(float x);
void txt_decimal(int x);
void txt_print(float x0,float y0);
#endif
void main(void)
{
const vec3 light_dir=normalize(vec3(0.1,0.1,1.0));
const float light_iamb=0.1;
const float light_idir=0.5;
const vec3 back_col=vec3(0.2,0.2,0.2);
const float _zero=1e-6;
const int _fac_triangles=0;
const int _fac_spheres =1;
struct _ray
{
vec3 pos,dir,nor;
vec3 col;
float refl,refr;
float n0,n1,l;
int lvl,i0,i1;
};
const int _lvls=5;
const int _rays=(1<<_lvls)-1;
_ray ray[_rays]; int rays;
vec3 v0,v1,v2,pos;
vec3 c,col;
float refr,refl;
float tt,t,n1,a;
int i0,ii,num,id;
vec2 st; int i,j; float ds=1.0/float(fac_siz-1);
#define fac_get texture(fac_txr,st).r; st.s+=ds; i++; j++; if (j==fac_siz) { j=0; st.s=0.0; st.t+=ds; }
ray[0].pos=ray_pos;
ray[0].dir=normalize(ray_dir);
ray[0].nor=vec3(0.0,0.0,0.0);
ray[0].refl=0.0;
ray[0].refr=0.0;
ray[0].n0=n0;
ray[0].n1=1.0;
ray[0].l =0.0;
ray[0].lvl=0;
ray[0].i0=-1;
ray[0].i1=-1;
rays=1;
#ifdef _debug_print
bool _dbg=false;
float dbg_x0=45.0;
float dbg_y0= 1.0;
float dbg_xs=12.0;
float dbg_ys=_rays+1.0;
dbg_xs=40.0;
dbg_ys=10;
float x=0.5*(1.0+txt_pos.x)/txt_fxs; x-=dbg_x0;
float y=0.5*(1.0-txt_pos.y)/txt_fys; y-=dbg_y0;
if ((x>=0.0)&&(x<=dbg_xs)
&&(y>=0.0)&&(y<=dbg_ys))
{
_dbg=true;
ray[0].pos=vec3(0.0,0.0,0.0)*2.5;
ray[0].dir=vec3(0.0,0.0,1.0);
}
#endif
for (i0=0;i0<rays;i0++)
{
t=tt=-1.0; ii=1; ray[i0].l=0.0;
ray[i0].col=back_col;
pos=ray[i0].pos; n1=n0;
for (st=vec2(0.0,0.0),i=j=0;i<fac_num;)
{
c.r=fac_get;
c.g=fac_get;
c.b=fac_get;
refl=fac_get;
refr=fac_get;
n1=fac_get;
a=fac_get; id=int(a);
a=fac_get; num=int(a);
if (id==_fac_triangles)
for (;num>0;num--)
{
v0.x=fac_get; v0.y=fac_get; v0.z=fac_get;
v1.x=fac_get; v1.y=fac_get; v1.z=fac_get;
v2.x=fac_get; v2.y=fac_get; v2.z=fac_get;
vec3 e1,e2,n,p,q,r;
float t,u,v,det,idet;
e1=v1-v0;
e2=v2-v0;
p=cross(ray[i0].dir,e2);
det=dot(e1,p);
if (abs(det)<1e-8) continue;
idet=1.0/det;
r=ray[i0].pos-v0;
u=dot(r,p)*idet;
if ((u<0.0)||(u>1.0)) continue;
q=cross(r,e1);
v=dot(ray[i0].dir,q)*idet;
if ((v<0.0)||(u+v>1.0)) continue;
t=dot(e2,q)*idet;
if ((t>_zero)&&((t<=tt)||(ii!=0)))
{
ii=0; tt=t;
ray[i0].col=c;
ray[i0].refl=refl;
ray[i0].refr=refr;
t=1.0-u-v;
pos=(v0*t)+(v1*u)+(v2*v);
e1=v1-v0;
e2=v2-v1;
ray[i0].nor=cross(e1,e2);
}
}
if (id==_fac_spheres)
for (;num>0;num--)
{
float r;
v0.x=fac_get; v0.y=fac_get; v0.z=fac_get; r=fac_get;
float aa,bb,cc,dd,l0,l1,rr;
vec3 p0,dp;
p0=ray[i0].pos-v0;
dp=ray[i0].dir;
rr = 1.0/(r*r);
aa=2.0*rr*dot(dp,dp);
bb=2.0*rr*dot(p0,dp);
cc= rr*dot(p0,p0)-1.0;
dd=((bb*bb)-(2.0*aa*cc));
if (dd<0.0) continue;
dd=sqrt(dd);
l0=(-bb+dd)/aa;
l1=(-bb-dd)/aa;
if (l0<0.0) l0=l1;
if (l1<0.0) l1=l0;
t=min(l0,l1); if (t<=_zero) t=max(l0,l1);
if ((t>_zero)&&((t<=tt)||(ii!=0)))
{
ii=0; tt=t;
ray[i0].col=c;
ray[i0].refl=refl;
ray[i0].refr=refr;
pos=ray[i0].pos+(ray[i0].dir*t);
ray[i0].nor=pos-v0;
}
}
}
ray[i0].l=tt;
ray[i0].nor=normalize(ray[i0].nor);
if ((ii==0)&&(ray[i0].lvl<_lvls-1))
{
t=dot(ray[i0].dir,ray[i0].nor);
#ifdef _reflect
if ((ray[i0].refl>_zero)&&(t<_zero))
{
ray[i0].i0=rays;
ray[rays]=ray[i0];
ray[rays].lvl++;
ray[rays].i0=-1;
ray[rays].i1=-1;
ray[rays].pos=pos;
ray[rays].dir=ray[rays].dir-(2.0*t*ray[rays].nor);
ray[rays].n0=ray[i0].n0;
ray[rays].n1=ray[i0].n0;
rays++;
}
#endif
#ifdef _refract
if (ray[i0].refr>_zero)
{
ray[i0].i1=rays;
ray[rays]=ray[i0];
ray[rays].lvl++;
ray[rays].i0=-1;
ray[rays].i1=-1;
ray[rays].pos=pos;
t=dot(ray[i0].dir,ray[i0].nor);
if (t>0.0)
{
ray[rays].n0=ray[i0].n0;
ray[rays].n1=n0;
v0=-ray[i0].nor; t=-t;
}
else{
ray[rays].n0=n1;
ray[rays].n1=ray[i0].n0;
ray[i0 ].n1=n1;
v0=ray[i0].nor;
}
n1=ray[i0].n0/ray[i0].n1;
tt=1.0-(n1*n1*(1.0-t*t));
if (tt>=0.0)
{
ray[rays].dir=(ray[i0].dir*n1)-(v0*((n1*t)+sqrt(tt)));
rays++;
}
}
#endif
}
else if (i0>0)
{
ray[i0]=ray[rays-1];
rays--; i0--;
}
}
for (i0=rays-1;i0>=0;i0--)
{
t=abs(dot(ray[i0].nor,light_dir)*light_idir)+light_iamb;
t*=1.0-ray[i0].refl-ray[i0].refr;
ray[i0].col.rgb*=t;
ii=ray[i0].i0;
if (ii>=0) ray[i0].col.rgb+=ray[ii].col.rgb*ray[i0].refl;
ii=ray[i0].i1;
if (ii>=0) ray[i0].col.rgb+=ray[ii].col.rgb*ray[i0].refr;
}
col=ray[0].col;
#ifdef _debug_print
if (_dbg)
{
float x=dbg_x0,y=dbg_y0;
vec3 a=vec3(1.0,2.0,3.0);
vec3 b=vec3(5.0,6.0,7.0);
txtsiz=0; txt_decimal(dot(a,b)); txt_print(x,y); y++;
txtsiz=0; txt_decimal(cross(a,b)); txt_print(x,y); y++;
if (_txt_col) col=txt_col.rgb;
}
#endif
frag_col=vec4(col,1.0);
}
#ifdef _debug_print
void txt_decimal(vec2 v)
{
txt[txtsiz]='('; txtsiz++;
txt_decimal(v.x); txt[txtsiz]=','; txtsiz++;
txt_decimal(v.y); txt[txtsiz]=')'; txtsiz++;
txt[txtsiz]=0;
}
void txt_decimal(vec3 v)
{
txt[txtsiz]='('; txtsiz++;
txt_decimal(v.x); txt[txtsiz]=','; txtsiz++;
txt_decimal(v.y); txt[txtsiz]=','; txtsiz++;
txt_decimal(v.z); txt[txtsiz]=')'; txtsiz++;
txt[txtsiz]=0;
}
void txt_decimal(vec4 v)
{
txt[txtsiz]='('; txtsiz++;
txt_decimal(v.x); txt[txtsiz]=','; txtsiz++;
txt_decimal(v.y); txt[txtsiz]=','; txtsiz++;
txt_decimal(v.z); txt[txtsiz]=','; txtsiz++;
txt_decimal(v.w); txt[txtsiz]=')'; txtsiz++;
txt[txtsiz]=0;
}
void txt_decimal(float x)
{
int i,j,c;
float y,a;
const float base=10;
if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; }
else { txt[txtsiz]='+'; txtsiz++; }
y=x; x=floor(x); y-=x;
i=txtsiz;
for (;txtsiz<_txtsiz;)
{
a=x;
x=floor(x/base);
a-=base*x;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (x<=0.0) break;
}
j=txtsiz-1;
for (;i<j;i++,j--)
{
c=txt[i]; txt[i]=txt[j]; txt[j]=c;
}
for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;)
{
y*=base;
a=floor(y);
y-=a;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (y<=0.0) break;
}
txt[txtsiz]=0;
}
void txt_decimal(int x)
{
int a,i,j,c;
const int base=10;
if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; }
else { txt[txtsiz]='+'; txtsiz++; }
i=txtsiz;
for (;txtsiz<_txtsiz;)
{
a=x;
x/=base;
a-=base*x;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (x<=0) break;
}
j=txtsiz-1;
for (;i<j;i++,j--)
{
c=txt[i]; txt[i]=txt[j]; txt[j]=c;
}
txt[txtsiz]=0;
}
void txt_print(float x0,float y0)
{
int i;
float x,y;
x=0.5*(1.0+txt_pos.x)/txt_fxs; x-=x0;
y=0.5*(1.0-txt_pos.y)/txt_fys; y-=y0;
if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return;
i=int(x);
x-=float(i);
i=txt[i];
x+=float(int(i&31));
y+=float(int(i>>5));
x/=32.0; y/=8.0;
txt_col=texture(txr_font,vec2(x,y));
_txt_col=true;
}
#endif
代码还没有被优化,我先想让物理正确运行。目前还没有实现Fresnells,但是使用了材料的
refl, refr
系数。
同时你可以忽略调试打印信息(它们被#define
封装起来)。
我为几何纹理构建了一个小类,这样我就可以轻松设置场景对象。这就是预览场景的初始化方式:
ray.beg();
ray.add_material(1.0,1.0,1.0,0.3,0.0,_n_glass); ray.add_box ( 0.0, 0.0, 6.0,9.0,9.0,0.1);
ray.add_material(1.0,1.0,1.0,0.1,0.8,_n_glass); ray.add_sphere( 0.0, 0.0, 0.5,0.5);
ray.add_material(1.0,0.1,0.1,0.3,0.0,_n_glass); ray.add_sphere( +2.0, 0.0, 2.0,0.5);
ray.add_material(0.1,1.0,0.1,0.3,0.0,_n_glass); ray.add_box ( -2.0, 0.0, 2.0,0.5,0.5,0.5);
ray.add_material(0.1,0.1,1.0,0.3,0.0,_n_glass);
ray.add_tetrahedron
(
0.0, 0.0, 3.0,
-1.0,-1.0, 4.0,
+1.0,-1.0, 4.0,
0.0,+1.0, 4.0
);
ray.end();
重要的是计算法线面朝向物体外部,因为这用于检测内部/外部物体交叉。
P.S.
如果您有兴趣,这是我的体积三维背向光线追踪器:
这是支持半球体对象的“网格”光线追踪器的新版本: