ShaderToy解析:Minimal ray tracer 光线追踪

23 篇文章 10 订阅
4 篇文章 0 订阅

原地址:https://www.shadertoy.com/view/XsSSWW

这次号称"最小"的ray tracer,但整体不如上篇。代码没有上篇易读。

但仍有一些新东西值得我这个初学者学习。

1.AAbox怎么Ray。再利用一下线代知识,普通box应该也可以ray。

2.反射系数,Schlick approximation。

3.Tone mapping。简单来说由于各种原因y=x^n(过曝),现在要y=x^(1/k)恢复回来。

4.简单的多重采样抗锯齿

下面是注释,小改的代码

//坐标轴为上y右x屏幕向外z
#define MAX_BOUNCES 1
float gamma = 2.2;

// ---8<----------------------------------------------------------------------
// Material

struct Material
{
    vec3 c;		// diffuse color
    vec3 f0;	// specular color (colored)
};

// ---8<----------------------------------------------------------------------
// Geometry

struct Ray
{
    vec3 o;		// origin
    vec3 d;		// direction
};

struct Hit
{
    float t;	// solution to p=o+t*d
    vec3 n;		// normal
    Material m;	// material
};
const Hit noHit = Hit(1e10, vec3(0.), Material(vec3(-1.), vec3(-1.)));

struct Plane
{
    float d;	// solution to dot(n,p)+d=0
    vec3 n;		// normal
    Material m;	// material
};

struct Sphere
{
	float r;	// radius
    vec3 p;		// center position
    Material m;	// material
};

struct AABox
{
    vec3 s;		// size
    vec3 p;		// center position
    Material m;	// material
};

Hit intersectPlane(Plane p, Ray r)
{
    float dotnd = dot(p.n, r.d);
    if (dotnd > 0.) return noHit;

    float t = -(dot(r.o, p.n) + p.d) / dotnd;
    return Hit(t, p.n, p.m);
}

bool isInside(vec2 a, vec2 b)
{
    return a.x < b.x && a.y < b.y;
}

void AAboxPlaneIntersection(vec3 o, vec3 d, vec3 s, inout float t, out float ndir)
{
    ndir = 0.;
	//例如x轴方向,看x,y屏幕,如果向量束从左向右碰触y边界,-o.x>0,d.x>0,则o + tmin * d则保证在左y边界上,只要测试o.yz + tmin * d.yz是否在yz矩形内
	//如果向量束从右向左碰触y边界,-o.x<0,d.x<0,...同理
    if (d.x != 0.)
    {
        float tmin = (-0.5 * s.x - o.x) / d.x;
        if (tmin >= 0. && tmin < t && isInside(abs(o.yz + tmin * d.yz), 0.5 * s.yz))
        {
            t = tmin;
            ndir = -1.;
        }

        float tmax = (0.5 * s.x - o.x) / d.x;
        if (tmax >= 0. && tmax < t && isInside(abs(o.yz + tmax * d.yz), 0.5 * s.yz))
        {
            t = tmax;
            ndir = 1.;
        }
    }
}
  
//这里我加了提前返回  
Hit intersectBox(AABox b, Ray r)
{
    Hit hit = noHit;
    vec3 ro = r.o - b.p;

    float ndir = 0.;
	//从x轴方向
    AAboxPlaneIntersection(ro.xyz, r.d.xyz, b.s.xyz, hit.t, ndir);
    if (ndir != 0.) { hit.n = vec3(ndir, 0., 0.); hit.m = b.m; return hit;}

	//从y轴方向
    AAboxPlaneIntersection(ro.yzx, r.d.yzx, b.s.yzx, hit.t, ndir);
    if (ndir != 0.) { hit.n = vec3(0., ndir, 0.); hit.m = b.m; return hit;}

	//从z轴方向
    AAboxPlaneIntersection(ro.zxy, r.d.zxy, b.s.zxy, hit.t, ndir);
    if (ndir != 0.) { hit.n = vec3(0., 0., ndir); hit.m = b.m; return hit;}
	
	return hit;
}

Hit intersectSphere(Sphere s, Ray r)
{
	vec3 op = s.p - r.o;
    float b = dot(op, r.d);
    float det = b * b - dot(op, op) + s.r * s.r;
    if (det < 0.) return noHit;

    det = sqrt(det);
    float t = b - det;
    if (t < 0.) t = b + det;
    if (t < 0.) return noHit;

    return Hit(t, (r.o + t*r.d - s.p) / s.r, s.m);
}

bool compare(inout Hit a, Hit b)
{
	//这里判断b是否存在我也是佛了
    if (b.m.f0.r >= 0. && b.t < a.t)
    {
        a = b;
        return true;
    }
    return false;
}

Hit intersectScene(Ray r)
{
    Sphere s1 = Sphere(1., vec3(-2., 1., 0.), Material(vec3(1.0, 0.0, 0.2), vec3(0.04)));
    Sphere s2 = Sphere(0.8, vec3(0.5, 0.8, -1.2), Material(vec3(0.0), vec3(0.55, 0.56, 0.55)));
    Sphere s3 = Sphere(0.8, vec3(2.0, 0.8, -0.8), Material(vec3(0.0), vec3(1., 0.77, 0.34)));
    Plane p = Plane(0., vec3(0., 1., 0.), Material(vec3(0.5, 0.4, 0.3), vec3(0.04)));
    AABox b = AABox(vec3(0.6, 0.1, 0.75), vec3(-2.0, 0.5, 3.0), Material(vec3(0.1), vec3(0.95, 0.64, 0.54)));

    Hit hit = noHit;
    compare(hit, intersectPlane(p, r));
    compare(hit, intersectSphere(s1, r));
    compare(hit, intersectSphere(s2, r));
    compare(hit, intersectSphere(s3, r));
	compare(hit, intersectBox(b, r));
    return hit;
}

// ---8<----------------------------------------------------------------------
// Light

vec3 sunCol = vec3(1e3);
vec3 sunDir = normalize(vec3(0.0, 1.0, 0.0));
//骚操作画蓝天和太阳
vec3 skyColor(vec3 d)
{
    float transition = pow(smoothstep(0.02, .5, d.y), 0.4);

    vec3 sky = 2e2*mix(vec3(0.52, 0.77, 1), vec3(0.12, 0.43, 1), transition);
    vec3 sun = vec3(1e7) * pow(abs(dot(d, sunDir)), 5000.);
    return sky + sun;
}

float pow5(float x) { return x * x * x * x * x; }

// Schlick approximation
vec3 fresnel(vec3 h, vec3 v, vec3 f0)
{
  return pow5(1. - clamp(dot(h, v), 0., 1.)) * (1. - f0) + f0;
}

vec3 radiance(Ray r)
{
    float epsilon = 4e-4;

    vec3 accum = vec3(0.);
    vec3 attenuation = vec3(1.);

    for (int i = 0; i <= MAX_BOUNCES; ++i)
    {
        Hit hit = intersectScene(r);

        if (hit.m.f0.r >= 0.)
        {
			//这里材质里高光参数就成了此材质在x,y,z方向的反射系数
			//反射系数=反射光强/入射光强
			//显然这里材质的"specular"xyz越小,就越暗。
			//当视线与normal越接近,越返回f0
			//越垂直,越返回1
			//f属于[f0,1]
            vec3 f = fresnel(hit.n, -r.d, hit.m.f0);

			//在这个if里检测阴影
            if (intersectScene(Ray(r.o + hit.t * r.d + epsilon * sunDir, sunDir)).m.f0.r < 0.)
            {
				//(1.-f):当视线与normal越接近,越返回1-f0;越垂直,越返回0
                accum += (1.-f) * attenuation * hit.m.c * clamp(dot(hit.n, sunDir), 0., 1.) * sunCol;
            }

            // Specular: next bounce
            attenuation *= f;
            vec3 d = reflect(r.d, hit.n);
            r = Ray(r.o + hit.t * r.d + epsilon * d, d);
        }
        else
        {
            accum += attenuation * skyColor(r.d);
            break;
        }
		
    }
    return accum;
}

// ---8<----------------------------------------------------------------------
// Tone mapping
// 色调映射/gamma矫正
// See: http://filmicgames.com/archives/75
vec3 Uncharted2ToneMapping(vec3 color)
{
	float A = 0.15;
	float B = 0.50;
	float C = 0.10;
	float D = 0.20;
	float E = 0.02;
	float F = 0.30;
	float W = 11.2;
	float exposure = 0.012;
	color *= exposure;
	color = ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F;
	float white = ((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F;
	color /= white;
	color = pow(color, vec3(1. / gamma));
	return color;
}

// ---8<----------------------------------------------------------------------
// Scene

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	//uv属于[-1,1]
	vec2 uv = 2. * fragCoord.xy / iResolution.xy - 1.;

    float o1 = 0.25;
    float o2 = 0.75;
    vec2 msaa[4];
    msaa[0] = vec2( o1,  o2);
    msaa[1] = vec2( o2, -o1);
    msaa[2] = vec2(-o1, -o2);
    msaa[3] = vec2(-o2,  o1);

    vec3 color = vec3(0.);
    for (int i = 0; i < 4; ++i)
    {
		//视点位置
        vec3 p0 = vec3(0., 1.1, 4.);
		//根据鼠标移动改变视点位置,如果没有鼠标则p=p0
        vec3 p = vec3((2. * (iMouse.xy==vec2(0.)?.5*iResolution.xy:iMouse.xy) / iResolution.xy - 1.) * vec2(1., 1.), 0.) + p0;
        vec3 offset = vec3(msaa[i] / iResolution.y, 0.);
		//向量(1,1,-1.5)指定了视锥的形状(为右上角的视线方向)
		//显然abs(-1.5)越小,视野越大
		//还额外加了offset,循环4次,从4个点采样取平均可得像素抗锯齿后颜色(多重采样抗锯齿)
		//注意到msaa数组4个点呈正方形,中心点在(0,0)
        vec3 d = normalize(vec3(iResolution.x/iResolution.y * uv.x, uv.y, -1.5) + offset);
        Ray r = Ray(p, d);
        color += radiance(r) / 4.0 ;
    }

	//由于场景光强极高,不经过ToneMapping会亮得很难看见
	fragColor = vec4(Uncharted2ToneMapping(color),1.0);
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值