1. 本影(umbra)和半影(penumbra)
如下图,在绿色横条下方的区域即为本影区,另外由于长条光源产生的微弱光照区域即为半影区。
产生软阴影的方法:
1.使用多个电光源模拟 区域光源,然后对于每个电光源的本影区域加入 累积buffer,求平均即可得软阴影
2. 简单使用一个pass,对本影缘边进行模糊,比如soler,filtering
2.平面阴影(planar shadow)
如下点光源,在平面 y = 0, 上投影,得p点x坐标为
假设平面方程为 n*x + d = 0, 可得到p为
生成半透明的阴影,即阴影下的纹理颜色也可以看见。此时可以
1.简单效果, 半透明投影的图形
2.使用stencil buffer,获取到阴影区域,这样阴影区域不会重复绘制,产生太重的阴影.
3.Shadow Volume
如下光线投射到两橙色杆子,我们可以采用如下办法判断远处橙色折杆阴影区域。
在折杆上取点投射到人眼,红色点表示进入某阴影区,粉红色点表示出了某个阴影区,则根据
一条视线上红色点和粉红色点的个数,可以判断物体是否在阴影区。
有另外一种方法可以更简便,参考《real time rendering》P341。 使用stencil buffer
4.Shadow Map
shadow map 采用存储深度值方法,且渲染两次判断是否在阴影区。如下,va处的深度值不大于存储在a处
的深度值,所以出于可视线区;相反vb的深度则大于存储的深度值,故在阴影区。
使用该方法,容易有个问题,存储的深度值和实际的深度判断在可视表面容易有误差区域,
实际在光线区域中的点,因为深度值判断小于存储的深度值,会有阴影。
coding
1.plane
此处光线定义如下
p = 0 + t d;
p 与 a 共面条件 为 (p-a)n = 0; 即 (o + t d -a)n = 0; t = -b /a; 这里我们不必要计算出具体的 t 点,因为只有在渲染改点的时候才需要知道,另外,还不确定该点是否为需要渲染的点(只渲染距离view plane 最近的点),故t取一个范围值。
bool
Plane::shadow_hit(const Ray& ray, float& tmin) const {
float t = (a - ray.o) * n / (ray.d * n);
if (t > kEpsilon) {
tmin = t;
return (true);
}
else
return (false);
}
2.sphere
p 为ray , c 为球心,则相交有(p - c) (p - c) - r*r = 0, 带入 p = o + t*d ,可得到一元二次方程。
Sphere::hit(const Ray& ray, double& tmin, ShadeRec& sr) const {
double t;
Vector3D temp = ray.o - center;
double a = ray.d * ray.d;
double b = 2.0 * temp * ray.d;
double c = temp * temp - radius * radius;
double disc = b * b - 4.0 * a * c;
if (disc < 0.0)
return(false);
else {
double e = sqrt(disc);
double denom = 2.0 * a;
t = (-b - e) / denom; // smaller root
if (t > kEpsilon) {
tmin = t;
sr.normal = (temp + t * ray.d) / radius;
sr.local_hit_point = ray.o + t * ray.d;
return (true);
}
t = (-b + e) / denom; // larger root
if (t > kEpsilon) {
tmin = t;
sr.normal = (temp + t * ray.d) / radius;
sr.local_hit_point = ray.o + t * ray.d;
return (true);
}
}
return (false);
}
3.point light
如下,以物体表面为原点发射一条 shadow ray 到光源,如果ray 和平面有相交,则可进一步判断是否在阴影区,如果没有相交,则不在阴影区。
bool
PointLight::in_shadow(const Ray& ray, const ShadeRec& sr) const {
float t;
int num_objects = sr.w.objects.size();
float d = location.distance(ray.o);
for(int j = 0; j < num_objects; j++)
if (sr.w.objects[j]->shadow_hit(ray, t) && t < d)
return true;
return false;
}
4.phone
对于phone 模型,相交的判断,即只要判断 入射光线 w 和 法线 normal 的夹角。
RGBColor
Phong::shade(ShadeRec& sr) {
Vector3D wo = -sr.ray.d;
RGBColor L = ambient_brdf->rho(sr, wo) * sr.w.ambient_ptr->L(sr);
int num_lights = sr.w.lights.size();
for (int j = 0; j < num_lights; j++) {
Vector3D wi = sr.w.lights[j]->get_direction(sr);
float ndotwi = sr.normal * wi;
if (ndotwi > 0.0) {
bool in_shadow = false;
if (sr.w.lights[j]->casts_shadows()) {
Ray shadowRay(sr.hit_point, wi);
in_shadow = sr.w.lights[j]->in_shadow(shadowRay, sr);
}
if (!in_shadow)
L += ( diffuse_brdf->f(sr, wo, wi)
+ specular_brdf->f(sr, wo, wi)) * sr.w.lights[j]->L(sr) * ndotwi;
}
}
return (L);
}
参考文献:
1. 《Ray Tracing from the ground up》
2. 清华大学胡事名 教学PPT