从零开始软渲染6-ShadowMap

这一节我们来讨论阴影。

ShadowMap

我们如何判断一个点是否处于阴影中呢?

比如说在图中的ABCD四个点,B和C处于阴影中,A和D不处于阴影中,我们要做的事情就是能够把这两组点区分开来。这两组点有什么区别呢?我们把自己放在光源的位置,这就一目了然了:在光源的位置,沿着光源方向观察,A和D在视野内,B和C不在视野内,这就很容易判断出,B和C是处于阴影中的。

ShadowMap的原理就是如此,先在光源的位置,沿着光源方向渲染一张深度图,然后在实际渲染场景时,把每个点变换到光源视角下,比较其深度和深度图中对应位置深度大小,倘若其深度比深度图中的深度大,说明从光源的角度是看不到这个点的,那么这个点应该处在阴影中。反之亦然。

代码实现

首先我们要定义一块空间来存储ShadowMap,就本质来说它和深度缓冲没什么区别,我们就在初始化深度缓冲的时候初始化ShadowMap:

for (int i = mWidth * mHeight; i--; zBuffer[i] = shadowBuffer[i] = FLT_MAX);

为了渲染出ShadowMap,我们也需要一个额外的shader,为了提供必要的抽象,我们先定义一个IShader基类:

class IShader {

public:

    virtual ~IShader() {};

    virtual TGAColor fragment(const Vector3& bar) = 0;
    bool depthPass;
};

通过继承基类,我们定义出DepthShader:

class DepthShader: public IShader {

public:

    DepthShader(Matrix4* m_mvp);
    ~DepthShader();


    Vector4 vertex(int id, const Vector4& modelPts, const Vector3& uv, const Vector4& normalDir);
    TGAColor fragment(const Vector3& bar);
    Matrix3 varying_uv;
    Matrix4 varying_normal;
private:
    DepthShader(const DepthShader&);
    Matrix4 uniform_M;
    Matrix4 clipCoors;
};

DepthShader的实现比较简单,因为不需要实际画出像素,Fragment里直接返回一个空值就可以了:

#include "DepthShader.h"


DepthShader::DepthShader(Matrix4* m_mvp) :uniform_M(*m_mvp)
{
    depthPass = true;
}

DepthShader::~DepthShader()
{

}

Vector4 DepthShader::vertex(int id, const Vector4& modelPts, const Vector3& uv, const Vector4& normalDir)
{
    Vector4 clipCoor = uniform_M * modelPts;
    clipCoors.setColumn(id, clipCoor);
    return clipCoor;
}

TGAColor DepthShader::fragment(const Vector3& bar)
{
    TGAColor color;
    return color;
}

在画模型里的每个三角形的时候,我们先用DepthShader画一遍,再用SimpleShader画一遍:

for (size_t i = 0; i < shapes.size(); i++) {
		size_t index_offset = 0;
		assert(shapes[i].mesh.num_face_vertices.size() == shapes[i].mesh.material_ids.size());
		SimpleShader shader(&mvpMatrix, &shadowMvpMatrix, &modelMatrix, &tgaImage, lightDir, shadowBuffer);
		DepthShader depthShader(&shadowMvpMatrix);
		// For each face
		for (size_t f = 0; f < shapes[i].mesh.num_face_vertices.size(); f++) {
			size_t fnum = shapes[i].mesh.num_face_vertices[f];
			Vector4 modelPts;
			Vector4 clip_coords[3];
			Vector4 depth_clip_coords[3];
			Vector3 uv;
			Vector4 normal;
			Vector4 viewDir[3];
			// For each vertex in the face
			for (size_t v = 0; v < fnum; v++) {
				tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v];
				modelPts = Vector4(attrib.vertices[3 * idx.vertex_index + 0], attrib.vertices[3 * idx.vertex_index + 1], attrib.vertices[3 * idx.vertex_index + 2], 1);
				uv = Vector3(attrib.texcoords[2 * idx.texcoord_index + 0], attrib.texcoords[2 * idx.texcoord_index + 1], 0);
				normal = Vector4(attrib.normals[3 * idx.normal_index + 0], attrib.normals[3 * idx.normal_index + 1], attrib.normals[3 * idx.normal_index + 2],0);
				viewDir[v] = cameraPos - modelMatrix * modelPts;
				clip_coords[v] = shader.vertex(v, modelPts, uv, normal);
				depth_clip_coords[v] = depthShader.vertex(v, modelPts, uv, normal);
			}
			if(!useWireFrame)
				triangle(depth_clip_coords, &depthShader, shadowBuffer);

			if (shader.varying_normal.getColumn(0).dot(viewDir[0]) > 0 || shader.varying_normal.getColumn(1).dot(viewDir[1]) > 0 || shader.varying_normal.getColumn(2).dot(viewDir[2]) > 0)
				triangle(clip_coords, &shader, zBuffer);
			index_offset += fnum;
		}
	}

同时我们也要改造一下SimpleShader,在Fragment里把点变换到光源视角下,然后和ShadowMap里比较来判断是否处于阴影中,如果处于阴影中,就把颜色衰减一点:

TGAColor SimpleShader::fragment(const Vector3& bar)
{
	Vector3 buv = varying_uv * bar;
	Vector3 bn = varying_normal * bar;
	bn.normalize();
	float intensity = (bn.dot(light_dir)) * 0.5f + 0.5f;
	TGAColor color = tgaImage->get(buv[0] * tgaImage->get_width(), buv[1] * tgaImage->get_height());

	Vector4 clipPos = varying_clip.getColumn(0) * bar.x + varying_clip.getColumn(1) * bar.y + varying_clip.getColumn(2) * bar.z;
	Vector4 transformedPos = uniform_Mshadow * clipPos;
	transformedPos /= transformedPos.w;
	transformedPos.x = (transformedPos.x / 2 + 0.5) * SCREEN_WIDTH;
	transformedPos.y = (transformedPos.y / 2 + 0.5) * SCREEN_HEIGHT;
	transformedPos.z = (transformedPos.z / 2 + 0.5);
	bool notInShadow = false;
	if (transformedPos.x < 0 || transformedPos.x > SCREEN_WIDTH || transformedPos.y < 0 || transformedPos.y > SCREEN_HEIGHT || transformedPos.z < 0 || transformedPos.z > 1)
		notInShadow = true;
	else
	{
		int idx = (int)transformedPos.x + (int)transformedPos.y * SCREEN_WIDTH;
		notInShadow = shadowBuffer[idx] > transformedPos.z - 0.008;
	}
	float shadow = .2 + .8 * notInShadow;
	return color * intensity * shadow;
}

Shadow Acne

notInShadow = shadowBuffer[idx] > transformedPos.z - 0.008;

在计算是否在阴影中时,我们加上了一点点的偏移量,这个偏移量叫做Shadow Bias,为了看到这个偏移量的效果,我们可以先不加这个偏移量试试:

可以看到图中出现了很多条纹状的阴影,这种现象就称为Shadow Acne。

Shadow Acne产生的原因在于ShadowMap是离散的,因此多个场景中的点都对应到ShadowMap中的同一个像素。然而这些点的实际深度有的比ShadowMap中对应的深度小,有的比ShadowMap中对应的深度大,前者没有问题,后者就会出现不应该产生的阴影。为了解决这个问题,就需要手动加上一个Shadow Bias:

基于此,我们就能够得到一个正常的阴影了:

 

最后附上代码:

https://github.com/LittleLittleWind/TaurusSoftRenderer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值