4、片元着色器之光线步进及其和兰伯特光照模型的结合应用
1、什么是光线步进?
光线步进(Ray Marching)是一种用于渲染和追踪的技术,尤其在处理体积数据和隐式表面时非常有效。与传统的光线追踪方法不同,光线步进不直接计算光线与物体的交点,而是通过在光线上逐步前进来寻找相交的表面。这种方法通常用于场景中存在复杂几何体或体积效果的情况。
2、基本步骤
- 光线发射:从相机位置发射光线,通常在场景中每个像素位置发射一条光线。
- 步进前进:在光线上以固定的步长逐步前进,每一步都计算当前位置的场景信息。
- 场景求值:在每一步中,使用一个场景描述函数(如距离场)来判断当前点距离最近表面的距离。如果距离小于某个阈值,则认为光线和物体相交。
- 交点判断:一旦光线与物体相交,计算交点的颜色和光照,并将结果返回。
- 重复步骤:如果没有找到交点,继续前进,直到光线超出场景的边界。
3、应用场景:
- 体积渲染:适用于模拟云、烟、雾等体积效果。
- 复杂几何体:处理复杂的隐式曲面,特别是无法直接通过多边形网格表示的几何体。
- 效果优化:在某些情况下,光线步进可以提供更高的渲染灵活性和表现力,尤其是在实时渲染和着色器编程中。
4、优缺点
灵活性高,可以处理复杂场景;
适合动态场景的渲染;
性能开销大,尤其是在高分辨率和复杂场景中;
需要精心调整步长,以平衡渲染质量和性能。
5、示例
//限制光线步进的范围,防止计算过长的距离。
const float maxDistance=40.0;
// 球心在原点,半径为1.0的球体
float sdSphere(vec3 point)
{
return length(point)-1.0;
}
// 光线步进,start指的是相机(视点)位置;direction表示从相机(视点)发出的光线的方向
float rayMarching(vec3 start,vec3 direction)
{
float d=0.;
for(int i=0;i<9999;i++)
{
vec3 point=start+d*direction;
// 像素点到球体表面的距离
float d0=sdSphere(point);
// 当d0小于0.001时认为射线和球体表面相交了
if(d0<.001||d > maxDistance) break;
d+=d0;
}
return d;
}
vec3 getNormal(vec3 p)
{
float d=sdSphere(p);
vec2 e=vec2(.001,.0);
float fdx=d-sdSphere(p-e.xyy);
float fdy=d-sdSphere(p-e.yxy);
float fdz=d-sdSphere(p-e.yyx);
return normalize(vec3(fdx,fdy,fdz));
}
// 构建绕y轴旋转的旋转矩阵
mat2 rot(float angle){
float c= cos(angle);
float s = sin(angle);
return mat2(c,-s,s,c);
}
void mainImage(out vec4 fragColor,in vec2 fragCoord){
// 将像素坐标标准化为 [-1, 1] 的范围
vec2 uv=(fragCoord-iResolution.xy*.5)/iResolution.y;
vec3 color=vec3(0.0);
// 视点在z轴正方向上,渲染结果就是相机在该位置看到的结果
vec3 cameraPosition=vec3(0.,0.,5.0);
// 改变direction的z分量实际上相当于修改了fov的大小,fov越小,物体看起来越大(前提是其他参数不变)
//vec3 direction=normalize(vec3(uv,-1.0));// fov是90°
vec3 direction=normalize(vec3(uv,-2.0));// fov是53.13°
float d=rayMarching(cameraPosition,direction);
if(d<maxDistance){
vec3 point=cameraPosition+d*direction;
vec3 normal=getNormal(point);
// 定义光源位置,在xoy坐标系的第一象限
vec3 lightPosition=vec3(5.,5.,0.);
// 相机绕着y轴转
lightPosition.xz*=rot(iTime);
// 定义光源方向
vec3 lightDir=normalize(lightPosition-point);
// 漫反射系数
float kd=1.;
// 光源的强度
float lightIntensity=1.;
// 使用Lambert光照模型计算亮度
// float intensity = kd * lightIntensity * (0.5*dot(normal, lightDir)+0.5);//半兰伯特模型
float intensity=kd*lightIntensity*max(dot(normal,lightDir),0.);//兰伯特模型
// 设置球体颜色
color+=vec3(1.0, 0.5, 0.3)*intensity;
}
fragColor=vec4(color,1.0);
}
光线步进+兰伯特(gif经过压缩,质量有点不好)
光线步进+半兰伯特(gif经过压缩,质量有点不好)