个人学习用,请勿转载
第11章 模板
词汇
模板缓冲:stencil buffer 离屏:off-screen 双重混合:double blending 深度复杂性:depth complexity
模板参考值:stencil reference value 掩码值:masking value 比较函数:comparison function
递增:increment 递减:decrement 绕序:winding order
平面阴影:planar shadow 深度冲突:z-fighting
重复绘制:overdraw
内容
模板缓冲区、后台缓冲区和深度缓冲区有着相同的分辨率,用于将三者相同位置上的像素一一对应。
在指定一个模板缓冲区时,需要将它与一个深度缓冲区配合使用,模板缓冲区的作用是阻止特定的像素片段渲染至后台缓冲区。
当需要物体反射到镜子所在的平面上时,只应该绘制出镜子中的镜像部分,这时就可以用过模板缓冲区阻止范围外镜像部分的绘制操作。
- 要设置模板缓冲去状态,就要填写D3D12_DEPTH_STENCIL_DESC结构体示例,并将其赋予流水线状态对象(PSO)的D3D12_GRAPHICS_PIPELINE_DESC::DepthStencilState字段。学习模板缓冲区用法的最佳方式就是从现存的示例程序着手,一旦对实例中的模板缓冲区部分有了感性的认识,就可以更加得心应手的运用它了(?)。
学习目标:
- 探究如何通过填写流水线状态对象中的D3D12_DEPTH_STENCIL_DESC DepthStencilState字段控制深度缓冲区以及模板缓冲区。
- 学习通过模板缓冲去防止镜像被绘制到镜子以外的区域,以此实现正确的镜像效果。
- 了解双重混合的机制,从而利用模板缓冲去来有效杜绝这一情况的发生。
- 知晓深度复杂性概念,并介绍两种方法来度量场景的深度复杂性。
11.1深度/模板缓冲区的格式以及资源数据的清理
深度/模板缓冲其实也是一种纹理,因此必须用下列特定的数据格式创建:
1. DXGI_FORMAT_D32_FLOAT_S8X24_UINT:用一个32位浮点数来指定深度缓冲区,并以另一个32位无符号整数来指定模板缓冲区。其中无符号整数里的8位用于将模板缓冲区映射到范围[0,255],另外24位不可用,仅作填充占位。
2. DXGI_FORMAT_D24_UNORM_S8_UINT:指定一个无符号24位深度缓冲区,并将其映射到范围[0,1]内,另外8位无符号整数用于将模板缓冲区映射至范围[0,255]
D3Dapp应用框架中,当要创建深度缓冲区时就要如下指定它的格式:
DXGI_FORMAT mDepthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.Format = mDepthStencilFormat;
可以在每帧绘制画面之初,用下列方法重置缓冲区的局部数据
void ID3D12GraphicsCommandList::ClearDepthStencilVIew(
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView,
D3D12_CLEAR_FLAGS ClearFlags,
UINT8 Stencil,
UINT NumRects,
const D3D12_RECT *pRects);
- DepthStencilView:待清理的深度/模板缓冲区视图的描述符
- ClearFlags:指定为D3D12_CLEAR_FLAG_DEPTH 仅清理深度缓冲区,D3D12_CLEAR_FLAG_STENCIL则同时清理两种缓冲区。
- Depth:将此浮点值设置到深度缓冲区中的每一个像素。此浮点数x务必满足 0 ⩽ x ⩽ 1 0\leqslant x\leqslant 1 0⩽x⩽1
- Stencil:将此整数值设置到模板缓冲区的每一个像素,n满足 0 ⩽ n ⩽ 255 0\leqslant n \leqslant 255 0⩽n⩽255
- NumRects:数组pRects所指引的矩形数量。
- pRects:一个D3D12_RECT类型数组,它标定了一系列深度/模板缓冲区内要清理的区域。若指定nullptr则清理整个深度/模板缓冲区。演示程序中的每一帧都已经调用此方法:
mCommandList->ClearDepthStencilView(DepthStencilView(),
D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
1.0f,0,0,nullptr);
11.2模板测试
通过模板缓冲区阻止对后台缓冲区特定区域的绘制行为的操作名为模板测试。
if(StencilRef & StencilReadMask ⊴ \trianglelefteq ⊴ Value & StencilReadMask)
accept pixel
else
reject pixel
模板测试会随着像素的光栅化过程而进行(输出合并阶段)。若模板功能为开启状态,则需要经过下面两处运算。
1.左运算数(left-hand-side,LHS)由程序定义的模板参考值StencilRef与程序内定义的掩码值StencilReadMask通过AND运算来加以确定。
2.右运算数(right-hand-side,RHS)由正在接受模板测试的特定像素位于模板缓冲区内对应值VValue与程序中定义的掩码值StencilReadMask经过AND运算加以确定。
左运算与右运算中的StencilReadMask是同一个值。接下来模板测试所选定的比较函数对左运算数和右运算数进行比对,从而得到布尔型的返回值,如果测试结果为true,则将当前接受检测的像素写入后台缓冲区,如果结果为false,则禁止此像素向后台缓冲区的写操作。
运算符 ⊴ \trianglelefteq ⊴ 是D3D12_COMPARISON_FUNC枚举类型所定义的下列比较函数之一:
enum D3D12_COMPARISON_FUNC
{
D3D12_COMPARISON_FUNC_NEVER = 1,
D3D12_COMPARISON_FUNC_LESS = 2,
D3D12_COMPARISON_FUNC_EQUAL = 3,
D3D12_COMPARISON_FUNC_LESS_EQUAL = 4,
D3D12_COMPARISON_FUNC_GREATER = 5,
D3D12_COMPARISON_FUNC_NOT_EQUAL = 6,
D3D12_COMPARISON_FUNC_GREATER_EQUAL = 7,
D3D12_COMPARISON_FUNC_ALWAYS = 8
} D3D12_COMPARISON_FUNC;
- D3D12_COMPARISON_FUNC_NEVER:总是返回false
- D3D12_COMPARISON_FUNC_LESS:运算符<
- D3D12_COMPARISON_FUNC_EQUAL:运算符==
- D3D12_COMPARISON_FUNC_LESS_EQUAL:运算符<=
- D3D12_COMPARISON_FUNC_GREATER:运算符>
- D3D12_COMPARISON_FUNC_NOT_EQUAL:运算符!=
- D3D12_COMPARISON_FUNC_GREATER_EQUAL:运算符>=
- D3D12_COMPARISON_FUNC_ALWAYS 永远返回true
11.3描述深度/模板状态
填写D3D12_DEPTH_STENCIL_DESC实例:
typedef struct D3D12_DEPTH_STENCIL_DESC
{
BOOL DepthEnable;//默认值为true
D3D12_DEPTH_WRITE_MASK DepthWriteMask;
D3D12_COMPARISON_FUNC DepthFunc;
BOOL StencilEnable;//默认值为false
UINT8 StencilReadMask;//默认值为0xff,即D3D12_DEFAULT_STENCIL_WRITE_MASK
UINT8 StencilWriteMask;//默认值为0xff,即D3D12_DEFAULT_STENCIL_WRITE_MASK
D3D12_DEPTH_STENCILOP_DESC FrontFace;
D3D12_DEPTH_STENCILOP_DESC BackFace;
} D3D12_DEPTH_STENCIL_DESC;
11.3.1深度信息的相关设置
- DepthEnable:设置为true,则开启深度缓冲,若设为false则禁用,当深度缓冲被禁止时。物体的描绘顺序就尤为重要。并且如果深度缓冲被禁用,DepthWriteMask项也不会生效。
- DepthWriteMask:可设置为D3D12_DEPTH_WRITE_MASK_ZERO或者D3D12_DEPTH_WRITE_MASK_ALL,但两者不能共存,当DepthEnable为true,此参数为D3D12_DEPTH_WRITE_MASK_ZERO,则会禁止对深度缓冲区的写操作,但仍可执行深度测试,如果 D3D12_DEPTH_WRITE_MASK_ALL,则通过深度测试与模板测试的深度数据将被写入深度缓冲区,这种控制深度数据读写的能力,为某些特效的实现提供了良好的契机。
3.DepthFunc:该参数指定为枚举类型D3D12_COMPARISON_FUNC的成员之一,以此来定义深度测试所用的比较函数,此项一般设为D3D12_COMPARISON_FUNC_LESS,但Direct3D也允许用户根据需求来自定义深度测试。
11.3.2模板信息的相关设置
- StencilEnable:设置为true,则开启模板测试,fale则禁用
- StencilReadMask:用于模板测试,如果采用该项的默认值,则不会屏蔽任何一位模板值
#define D3D12_DEFAULT_STENCIL_READ_MASK(0xff) - StencilWriteMask:当模板缓冲区被更新时,我们可以通过掩码来屏蔽特定位的写入操作,例如,我们希望防止前4位被改写,便可以将掩码设置为0x0f,而默认配置不会屏蔽任何一位模板值
#define D3D12_DEFAULT_STENCIL_WRITE_MASK(0xff)、 - FrontFace:填写一个D3D12_DEPTH_STENCILOP_DESC结构体实例,以指出根据模板测试与深度测试的结果,应对正面朝向的三角形进行何种模板运算
- BackFace:填写一个D3D12_DEPTH_STENCILOP_DESC结构体实例,以指出根据模板测试与深度测试的结果,应对背面朝向 的三角形进行何种模板运算。
typedef struct D3D12_DEPTH_STENCILOP_DESC
{
D3D12_STENCIL_OP StencilFailOp;//默认值为D3D12_STENCIL_OP_KEEP
D3D12_STENCIL_OP StencilDepthFailOp;//默认值为D3D12_STENCIL_OP_KEEP
D3D12_STENCIL_OP StencilPassOp;//默认值为D3D12_STENCIL_OP_KEEP
D3D12_COMPARISON_FUNC StencilFunc;//默认值为D3D12_COMPARISON_FUNC_ALWAYS
} D3D12_DEPTH_STENCILOP_DESC;
- StencilFailOp:枚举类型D3D12_STENCIL_OP的成员之一,描述了当像素片段在模板测试失败时,应该如何更新模板缓冲区。
- StencilDepthFailOp:枚举类型D3D12_STENCIL_OP的成员之一,描述了当像素片段通过模板测试,在深度测试失败时如何更新模板缓冲区
- StencilPassOp:枚举类型D3D12_STENCIL_OP成员之一,描述了当像素通过模板测试与深度测试时该如何更新模板缓冲区。
- StencilFunc:枚举类型D3D12_COMPARISON_FUNC的成员之一,定义了模板测试所用的比较函数。
typedef
enum D3D12_STENCIL_OP
{
D3D12_STENCIL_OP_KEEP = 1,
D3D12_STENCIL_OP_ZERO = 2,
D3D12_STENCIL_OP_REPLACE = 3,
D3D12_STENCIL_OP_INCR_SAT = 4,
D3D12_STENCIL_OP_DECR_SAT = 5,
D3D12_STENCIL_OP_INVERT = 6,
D3D12_STENCIL_OP_INCR = 7,
D3D12_STENCIL_OP_DECR = 8
} D3D12_STENCIL_OP;
- D3D12_STENCIL_OP_KEEP :不修改模板缓冲区,保持当前数据
- D3D12_STENCIL_OP_ZERO:将模板缓冲区中的元素设置为0
- D3D12_STENCIL_OP_REPLACE:将模板缓冲区内的元素替换为用于模板测试的参考值,只有将深度/模板缓冲区状态绑定到渲染流水线时,才能够设定StencilRef值
- D3D12_STENCIL_OP_INCR_SAT:对模板缓冲区中的元素进行递增操作(increment),若递增值超出最大值,则将此模板缓冲区元素限定为最大值
- D3D12_STENCIL_OP_DECR_SAT:对模板缓冲区中的元素进行递减操作,递减值小于0,则将该模板缓冲区元素限定为0
- D3D12_STENCIL_OP_INVERT:对模板缓冲区的元素数据按二进制进行反转
- D3D12_STENCIL_OP_INCR:对模板缓冲区中的元素进行递增操作,如果递增值超出最大值,则循环回0。
- D3D12_STENCIL_OP_DECR:对模板缓冲区中元素进行递减操作,如果递减值超出最小值,则循环回最大值。
对正面朝向和背面朝向的三角形所进行的模板运算是可以互不相同的。有时候需要针对特定的图形学算法或透明几何体的处理来渲染背面朝向的多边形,此时的BackFace的设置则非常重要。
11.3.3创建和绑定深度/模板状态
将描述深度/模板状态的D3D12_DEPTH_STENCIL_DESC实例填写完整,就可以将其赋予PSO的D3D12_GRAPHICS_PIPELINE_DESC::DepthStencilState字段,使用此PSO绘制的几何体,都将根据上述的深度/模板设置进行渲染。
设置模板参考值的方法:由ID3D12GraphicsCommandList::OMSetStencilRef方法来实现,它以一个无符号整数作为参数:mCommandList->OMSetStencilRef(1);
11.4实现平面镜效果
实现镜像变成需解决的问题:
1. 必须了解平面反射物体的相关原理,以此来完整的绘制镜像
2. 需要标记处表面属于平面镜的部分
也需要将光源反映到镜子所在的平面内,否则会造成镜像中的光照不够精准
11.4.1镜像概述
我们需要将它反射到镜面的表面,超出镜面范围的骷髅头镜像绘制操作需要被模板缓冲区阻止。
1. 将地板、墙壁、以及骷髅头实物照常渲染到后台缓冲区内,此步骤不修改模板缓冲区
2. 清理模板缓冲区,将其整体置零
3. 仅将镜面渲染到模板缓冲区中。若要阻止其它颜色数据写入到后台缓冲区,可用下列设置所创建的混合状态:
D3D12_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask = 0;
再通过以下配置来禁止向深度缓冲区的写操作:
D3D12_DEPTH_STENCIL_DESC::DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
在向模板缓冲区渲染镜面的时候,我们将模板测试设置为每次都成功(D3D12_COMPARISON_ALWAYS),并且在通过测试时用1来替换模板缓冲区元素,如果深度测试失败,则采用枚举项D3D12_STENCIL_OP_KEEP,时模板缓冲区中的对应像素保持不变,由于仅向模板缓冲区绘制了镜面,因此在模板缓冲区内,除了镜面可见部分像素的对应像素为1,其它像素皆为0。
需要保证先绘制出骷髅头实物,再将镜面渲染至模板缓冲区中
4. 将骷髅头的镜像渲染至后台缓冲区及模板缓冲区中。只有通过模板测试的像素才能渲染到后台缓冲区,所以将其设置为仅当模板缓冲内的值为1时才能通过模板测试,可以通过令StencilRef为1,且模板运算符为D3D12_COMPARISON_FUNC_EQUAL来实现,如此只有模板缓冲区中元素为1的骷髅头像部分才得以渲染,由于只有镜面可见部分所对应的模板缓冲区中元素数组为1,所以仅有这一范围内的骷髅头镜像才能被渲染出来。
5.将镜面渲染到后台缓冲区中。为了解决透过镜面观察骷髅头的镜像,骷髅头会被镜子挡住的 问题,需要用混合技术渲染镜面。因此需要为镜面定义一个新的材质配置实例,将其漫反射alpha值设为0.3,并按透明混合状态来渲染镜面。
auto icemirror = std::make_unique<Material>();
icemirror->Name = "icemirror";
icemirror->MatCBIndex = 2;
icemirror->DiffuseSrvHeapIndex = 2;
icemirror->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 0.3f);
icemirror->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);
icemirror->Roughness = 0.5f;
11.4.2定义镜像的深度/模板状态
为实现上述算法,需要用到两个PSO对象,第一个用于在绘制镜面时标记模板缓冲区内镜面部分的镜像,第二个用于绘制镜面可见部分内的骷髅头镜像
// 用于标记模板缓冲区中镜面部分的PSO
//禁止对渲染目标的写操作
CD3DX12_BLEND_DESC mirrorBlendState(D3D12_DEFAULT);
D3D12_DEPTH_STENCIL_DESC mirrorDSS;
mirrorDSS.DepthEnable = true;
mirrorDSS.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
mirrorDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
mirrorDSS.StencilEnable = true;
mirrorDSS.StencilReadMask = 0xff;
mirrorDSS.StencilWriteMask = 0xff;
mirrorDSS.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
mirrorDSS.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
mirrorDSS.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE;
mirrorDSS.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
//不渲染背面朝向的多边形,因而对这些参数的设置并不关心
mirrorDSS.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
mirrorDSS.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
mirrorDSS.BackFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE;
mirrorDSS.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
D3D12_GRAPHICS_PIPELINE_STATE_DESC markMirrorsPsoDesc = opaquePsoDesc;
markMirrorsPsoDesc.BlendState = mirrorBlendState;
markMirrorsPsoDesc.DepthStencilState = mirrorDSS;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&markMirrorsPsoDesc, IID_PPV_ARGS(&mPSOs["markStencilMirrors"])));
//
// PSO for stencil reflections.
//用于渲染模板缓冲区中反射镜像的PSO
D3D12_DEPTH_STENCIL_DESC reflectionsDSS;
reflectionsDSS.DepthEnable = true;
reflectionsDSS.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
reflectionsDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
reflectionsDSS.StencilEnable = true;
reflectionsDSS.StencilReadMask = 0xff;
reflectionsDSS.StencilWriteMask = 0xff;
reflectionsDSS.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
reflectionsDSS.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
reflectionsDSS.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;
reflectionsDSS.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL;
// We are not rendering backfacing polygons, so these settings do not matter.
//并不关心背面朝向的多边形渲染
reflectionsDSS.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
reflectionsDSS.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
reflectionsDSS.BackFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;
reflectionsDSS.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL;
D3D12_GRAPHICS_PIPELINE_STATE_DESC drawReflectionsPsoDesc = opaquePsoDesc;
drawReflectionsPsoDesc.DepthStencilState = reflectionsDSS;
drawReflectionsPsoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_BACK;
drawReflectionsPsoDesc.RasterizerState.FrontCounterClockwise = true;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&drawReflectionsPsoDesc, IID_PPV_ARGS(&mPSOs["drawStencilReflections"])));
11.4.3绘制场景
以下代码概述了场景的绘制流程
// Draw opaque items--floors, walls, skull.
//绘制不透明的物体——地板、墙壁、骷髅头
auto passCB = mCurrFrameResource->PassCB->Resource();
mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);
// Mark the visible mirror pixels in the stencil buffer with the value 1
//将模板缓冲区可见的镜面像素标记为1
mCommandList->OMSetStencilRef(1);
mCommandList->SetPipelineState(mPSOs["markStencilMirrors"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Mirrors]);
// Draw the reflection into the mirror only (only for pixels where the stencil buffer is 1).
// Note that we must supply a different per-pass constant buffer--one with the lights reflected.
//只绘制镜子范围内模板缓冲区中标记为1的像素
//必须使用两个单独渲染过程常量缓冲区来完成工作,一个存储物体镜像,一个存储光照镜像
mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress() + 1 * passCBByteSize);
mCommandList->SetPipelineState(mPSOs["drawStencilReflections"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Reflected]);
// Restore main pass constants and stencil ref.
//恢复主渲染过程常量数据以及模板参考值
mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress());
mCommandList->OMSetStencilRef(0);
// Draw mirror with transparency so reflection blends through.
//绘制透明镜面使镜像与之混合。
mCommandList->SetPipelineState(mPSOs["transparent"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Transparent]);
绘制RenderLayer::Reflected层的时候如修改其渲染过程常量缓冲区,在绘制物体镜像的同事,还设计场景中光照的镜像(即物体的镜像也要有与之对应的光照)。光源本存于渲染过程常量缓冲区中,因此可以再额外创建一个渲染过程常量缓冲区,泳衣存储场景中的光照镜像,该常量缓冲的设置方法:
PassConstants mMainPassCB;
PassConstants mReflectedPassCB;
void StencilApp::UpdateReflectedPassCB(const GameTimer& gt)
{
mReflectedPassCB = mMainPassCB;
XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
XMMATRIX R = XMMatrixReflect(mirrorPlane);
// Reflect the lighting.
//光照镜像
for(int i = 0; i < 3; ++i)
{
XMVECTOR lightDir = XMLoadFloat3(&mMainPassCB.Lights[i].Direction);
XMVECTOR reflectedLightDir = XMVector3TransformNormal(lightDir, R);
XMStoreFloat3(&mReflectedPassCB.Lights[i].Direction, reflectedLightDir);
}
// Reflected pass stored in index 1
//将光照镜像的渲染过程常量数据存于渲染过程常量缓冲区中索引1的位置
auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(1, mReflectedPassCB);
}
11.4.4绕序与镜像
当一个三角形被反射到某平面时,其绕序并不会发生改变,因此其平面法线的方向同样保持不变,所以实际物体的外向法线在镜像中则变为了内向法线,为了纠正这一点,需要告知Direct3D将逆时针绕序的三角形看做是正面朝向,而将顺时针绕序的三角形看作背面朝向,实际上是对法线的方向进行了反射,以此使镜像称为外向朝向。可以通过设置下列PSO的光栅化属性来改变绕序的约定
drawReflectionsPsoDesc.RasterizerState.FrontCounterClockWise = true;
11.5实现平面阴影
需要借助几何建模的方式找到物体经光照投向平面的阴影,再运用表示阴影的50%透明度黑色材质来渲染阴影区域中的三角形,这有可能会导致“双重混合”的问题。
11.5.1平行光阴影
给定方向为L的平行光源,并用 r ( t ) = p + t L r(t)=p+tL r(t)=p+tL来标识途径点P的光线。光线r(t)与平面(n,d)交点为s。以此光源射出的光线照射到物体的各个顶点,用这些映射到平面上的交点集合便可以定义几何体所投射出的阴影形状。对点p来说,它的阴影投射可由下列公式求出:
s = r ( t s ) = p − n ⋅ p + d n ⋅ L s=r(t_s)=p-\frac{n·p+d}{n·L} s=r(ts)=p−n⋅Ln⋅p+d