实例化技术:常用于对场景中同一对象[可能不同的位置、朝向、缩放大小、材质以及纹理]反复绘制多次的情形;
视锥体剔除技术:通过简单的测试将位于视锥体外的整组三角形从后续的处理流程中剔除出去。

1.硬件实例化

对于同一个对象,可能只是不同的位置、朝向、缩放大小、材质以及纹理,使它们各自维护一套顶点数据与索引数据将极大地耗费系统资源。我们以存储一份相对于其局部空间的几何体副本(即顶点列表与索引列表)的方法来加以取代,在多次绘制调用时,给不同的世界矩阵与材质即可。

但是,为绘制每个对象而调用的API开销依然可观,因为我们要针对每一个对象,设置独有的材质和世界矩阵。Direct3D实例化API使我们可以通过一次绘制调用构造出一个对象的多个实例。再者,有了动态索引的辅助,实例化技术会比Direct3D 11时期更佳灵活。

①绘制实例数据:

cmdList->DrawIndexedInstanced(ri->indexCount, 1, // 实例的数量ri->StartIndexLocation, ri->BaseVertexLocation, 0
);

我们之前一直在绘制实例,只是数量为1。如果将这个数量改为多个,就可以绘制多个完全一样的实例,但是如何给每个实例赋予其独有的数据(世界矩阵、纹理)呢?我们为每个实例对象指定它所独有的实例数据

②实例数据:

在本书的前一版中,实例数据都是自输入装配阶段获取的。在创建输入布局(input layout)时,可以通过枚举项D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA代替D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA来指定输入的数据为逐实例(per-instance)数据流,而非逐顶点(per-vertex)数据流。随后,再将第二个顶点缓冲区与含有实例数据的输入流相绑定。Direct3D 12仍然支持这种向流水线传递实例数据的方式,但我们选择另一种更为现代化的方法。

这种现代化的方法是:为所有实例都创建一个存有其实例数据的结构化缓冲区。

例如,如果要将数据实例化100次,我们就创建一个具有100个实例数据元素的结构化缓冲区。接着把结构化缓冲区资源绑定到渲染流水线上,并根据要绘制的实例在VS中索引相应的数据。VS可以通过系统标识符SV_InstanceID来确定实例编号(从0开始~)

示例:着色器程序

#ifndef NUM_DIR_LIGHTS#define NUM_DIR_LIGHTS 3
#endif#ifndef NUM_POINT_LIGHTS#define NUM_POINT_LIGHTS 0
#endif#ifndef NUM_SPOT_LIGHTS#define NUM_SPOT_LIGHTS 0
#endif#include "LightingUtil.hlsl"struct InstanceData
{float4x4 World;float4x4 TexTransform;uint     MaterialIndex;uint     InstPad0;uint     InstPad1;uint     InstPad2;
};struct MaterialData
{float4   DiffuseAlbedo;float3   FresnelR0;float    Roughness;float4x4 MatTransform;uint     DiffuseMapIndex;uint     MatPad0;uint     MatPad1;uint     MatPad2;
};Texture2D gDiffuseMap[7] : register(t0); // 占用t0~t6的space0空间StructuredBuffer<InstanceData> gInstanceData : register(t0, space1);
StructuredBuffer<MaterialData> gMaterialData : register(t1, space1);SamplerState gsamPointWrap        : register(s0);
SamplerState gsamPointClamp       : register(s1);
SamplerState gsamLinearWrap       : register(s2);
SamplerState gsamLinearClamp      : register(s3);
SamplerState gsamAnisotropicWrap  : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);cbuffer cbPass : register(b0)
{float4x4 gView;float4x4 gInvView;float4x4 gProj;float4x4 gInvProj;float4x4 gViewProj;float4x4 gInvViewProj;float3 gEyePosW;float cbPerObjectPad1;float2 gRenderTargetSize;float2 gInvRenderTargetSize;float gNearZ;float gFarZ;float gTotalTime;float gDeltaTime;float4 gAmbientLight;// Indices [0, NUM_DIR_LIGHTS) are directional lights;// indices [NUM_DIR_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHTS) are point lights;// indices [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS)// are spot lights for a maximum of MaxLights per object.Light gLights[MaxLights];
};struct VertexIn
{float3 PosL    : POSITION;float3 NormalL : NORMAL;float2 TexC    : TEXCOORD;
};struct VertexOut
{float4 PosH    : SV_POSITION;float3 PosW    : POSITION;float3 NormalW : NORMAL;float2 TexC    : TEXCOORD;// 修饰符nointerpolation:不要在将顶点着色器的输出传递给像素着色器之前进行插值输出nointerpolation uint MatIndex  : MATINDEX; };VertexOut VS(VertexIn vin, uint instanceID : SV_InstanceID)
{VertexOut vout = (VertexOut)0.0f;// 获取实例数据InstanceData instData = gInstanceData[instanceID];float4x4 world = instData.World;float4x4 texTransform = instData.TexTransform;uint matIndex = instData.MaterialIndex;vout.MatIndex = matIndex;// 获取材质数据MaterialData matData = gMaterialData[matIndex];float4 posW = mul(float4(vin.PosL, 1.0f), world);vout.PosW = posW.xyz;// 向量变换:假设要执行的是等比缩放,否则就要使用世界矩阵的逆转值矩阵进行计算vout.NormalW = mul(vin.NormalL, (float3x3)world);vout.PosH = mul(posW, gViewProj);float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), texTransform);vout.TexC = mul(texC, matData.MatTransform).xy;return vout;
}float4 PS(VertexOut pin) : SV_Target
{MaterialData matData = gMaterialData[pin.MatIndex];float4 diffuseAlbedo = matData.DiffuseAlbedo;float3 fresnelR0 = matData.FresnelR0;float  roughness = matData.Roughness;uint diffuseTexIndex = matData.DiffuseMapIndex;diffuseAlbedo *= gDiffuseMap[diffuseTexIndex].Sample(gsamLinearWrap, pin.TexC);pin.NormalW = normalize(pin.NormalW);float3 toEyeW = normalize(gEyePosW - pin.PosW);float4 ambient = gAmbientLight*diffuseAlbedo;const float shininess = 1.0f - roughness;Material mat = { diffuseAlbedo, fresnelR0, shininess };float3 shadowFactor = 1.0f;float4 directLight = ComputeLighting(gLights, mat, pin.PosW,pin.NormalW, toEyeW, shadowFactor);float4 litColor = ambient + directLight;// Common convention to take alpha from diffuse albedo.litColor.a = diffuseAlbedo.a;return litColor;
}

可以发现已经没有物体常量缓冲区的身影,每个物体的数据由实例缓冲区提供。

上述着色器程序对应的根签名代码如下:

void InstancingAndCullingApp::BuildRootSignature()
{CD3DX12_DESCRIPTOR_RANGE texTable;texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 7, 0, 0); // 7个SRV--7张纹理CD3DX12_ROOT_PARAMETER slotRootParameter[4];// 对应两个结构化缓冲区slotRootParameter[0].InitAsShaderResourceView(0, 1); slotRootParameter[1].InitAsShaderResourceView(1, 1);slotRootParameter[2].InitAsConstantBufferView(0);slotRootParameter[3].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_PIXEL);auto staticSamplers = GetStaticSamplers();CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4, slotRootParameter,(UINT)staticSamplers.size(), staticSamplers.data(),D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);ComPtr<ID3DBlob> serializedRootSig = nullptr;ComPtr<ID3DBlob> errorBlob = nullptr;HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());if(errorBlob != nullptr){::OutputDebugStringA((char*)errorBlob->GetBufferPointer());}ThrowIfFailed(hr);ThrowIfFailed(md3dDevice->CreateRootSignature(0,serializedRootSig->GetBufferPointer(),serializedRootSig->GetBufferSize(),IID_PPV_ARGS(mRootSignature.GetAddressOf())));
}

draw():

void InstancingAndCullingApp::Draw(const GameTimer& gt)
{...ID3D12DescriptorHeap* descriptorHeaps[] = { mSrvDescriptorHeap.Get() };mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);mCommandList->SetGraphicsRootSignature(mRootSignature.Get());// 绑定此场景中所需的全部材质// 对于结构化缓冲区而言,我们可以绕过描述符堆的使用而将其直接设置为根描述符auto matBuffer = mCurrFrameResource->MaterialBuffer->Resource();mCommandList->SetGraphicsRootShaderResourceView(1, matBuffer->GetGPUVirtualAddress());auto passCB = mCurrFrameResource->PassCB->Resource();mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress());// 绑定此场景中所需的全部纹理mCommandList->SetGraphicsRootDescriptorTable(3, mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());DrawRenderItems(mCommandList.Get(), mOpaqueRitems);...
}

DrawRenderItem:

void InstancingAndCullingApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems)
{// For each render item...for(size_t i = 0; i < ritems.size(); ++i){auto ri = ritems[i];cmdList->IASetVertexBuffers(0, 1, &ri->Geo->VertexBufferView());cmdList->IASetIndexBuffer(&ri->Geo->IndexBufferView());cmdList->IASetPrimitiveTopology(ri->PrimitiveType);// 设置此渲染项要用到的实例缓冲区auto instanceBuffer = mCurrFrameResource->InstanceBuffer->Resource();mCommandList->SetGraphicsRootShaderResourceView(0, instanceBuffer->GetGPUVirtualAddress());cmdList->DrawIndexedInstanced(ri->IndexCount, ri->InstanceCount, ri->StartIndexLocation, ri->BaseVertexLocation, 0);}
}

③创建实例缓冲区:

在CPU端,实例数据结构:

struct InstanceData
{DirectX::XMFLOAT4X4 World = MathHelper::Identity4x4();DirectX::XMFLOAT4X4 TexTransform = MathHelper::Identity4x4();UINT MaterialIndex;UINT InstancePad0;UINT InstancePad1;UINT InstancePad2;
};

因为渲染项含有实例化次数的相关信息,所以位于系统内存中的实例数据也应算作渲染项结构体的组成部分:

struct RenderItem
{...UINT InstanceCount = 0;...
};

为了使GPU可以访问到这些实例数据,还需要用InstanceData元素类型创建一个结构化缓冲区。由于该缓冲区是动态缓冲区(即上传缓冲区),所以就能在每一帧都对它进行更新。辅助类UploadBuffer创建动态缓冲区是极其方便的:

struct FrameResource
{
public:FrameResource(ID3D12Device* device, UINT passCount, UINT maxInstanceCount, UINT materialCount);FrameResource(const FrameResource& rhs) = delete;FrameResource& operator=(const FrameResource& rhs) = delete;~FrameResource();Microsoft::WRL::ComPtr<ID3D12CommandAllocator> CmdListAlloc;std::unique_ptr<UploadBuffer<PassConstants>> PassCB = nullptr;std::unique_ptr<UploadBuffer<MaterialData>> MaterialBuffer = nullptr;// 注意:在演示程序中,实例只有一个渲染项,所以只用了一个结构化缓冲区来存储实例数据,但要使程序更具通用性,我们需要为每个渲染项都添加一个结构化缓冲区,并为每个缓冲区分配足够大的空间来容纳要绘制的最多实例个数std::unique_ptr<UploadBuffer<InstanceData>> InstanceBuffer = nullptr;UINT64 Fence = 0;
};FrameResource::FrameResource(ID3D12Device* device, UINT passCount, UINT maxInstanceCount, UINT materialCount)
{ThrowIfFailed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,IID_PPV_ARGS(CmdListAlloc.GetAddressOf())));PassCB = std::make_unique<UploadBuffer<PassConstants>>(device, passCount, true);MaterialBuffer = std::make_unique<UploadBuffer<MaterialData>>(device, materialCount, false);InstanceBuffer = std::make_unique<UploadBuffer<InstanceData>>(device, maxInstanceCount, false);
}

2.包围体与视锥体

为了实现视锥体剔除(frnstum culling),我们要熟知视锥体与各种包围体(bounding volume)的数学描述。包围体只是与物体的形状近似,但是用数学表示起来比较简单,所以易于使用。

①DirectXMath碰撞检测库:

DirectXCollision.h工具库,它是DirectXMath库的一个组成部分。此库提供了一份常见几何图元相交测试的快速实现,例如:射线(光)与三角形(ray/triangle)相交检测、射(光)线与(包围)盒(ray/box)相交检测、盒盒(box/box)相交检测、盒与平面(box/plane)相交检测、盒与视锥体(box/fustum)相交检测以及球体与视锥体 (sphere/frustum )相交检测等。

②包围盒:

网格的轴对称包围盒(axis-aligned bounding box, AABB)是一种将目标网格紧密包围,且各面皆平行于坐标主轴的长方体。我们可以通过最小点和最大点来描述AABB。通过查找目标网格中所有顶点在x,y,z三个坐标轴上所取得的最小/大值,我们就能得到最小点和最大点的坐标。

或者以另一种方式来表示AABB:将盒的中心记作c、扩展(extents)向量记为e,后者存储的是由包围盒中心沿坐标轴到各盒面的距离。

DirectXMath碰撞检测库采用的是包围盒中心与扩展向量组合的表达方式

struct BoundingBox
{static const size_t CORNER_COUNT = 8;XMFLOAT3 Center; // AABB中心XMFLOAT3 Extents; // AABB中心到各盒面的距离...
}

这两种方式进行转换也是很方便的:

代码示例:计算骷髅头网格包围盒的具体流程

XMFLOAT3 vMinf3(+MathHelper::Infinity, +MathHelper::Infinity, +MathHelper::Infinity);
XMFLOAT3 vMaxf3(-MathHelper::Infinity, -MathHelper::Infinity, -MathHelper::Infinity);XMVECTOR vMin = XMLoadFloat3(&vMinf3);
XMVECTOR vMax = XMLoadFloat3(&vMaxf3);std::vector<Vertex> vertices(vcount);
for(UINT i = 0; i < vcount; ++i)
{fin >> vertices[i].Pos.x >> vertices[i].Pos.y >> vertices[i].Pos.z;fin >> vertices[i].Normal.x >> vertices[i].Normal.y >> vertices[i].Normal.z;XMVECTOR P = XMLoadFloat3(&vertices[i].Pos);// 将点投射到单位球面上并生成球面纹理坐标XMFLOAT3 spherePos;XMStoreFloat3(&spherePos, XMVector3Normalize(P));// 球坐标系的theta和phifloat theta = atan2f(spherePos.z, spherePos.x); // atan2f求两个向量的夹角// Put in [0, 2pi].if(theta < 0.0f)theta += XM_2PI;float phi = acosf(spherePos.y); // 反余弦值 float u = theta / (2.0f*XM_PI);float v = phi / XM_PI;vertices[i].TexC = { u, v };vMin = XMVectorMin(vMin, P); // XMVectorMin:各分量取minvMax = XMVectorMax(vMax, P);
}
BoundingBox bounds;
XMStoreFloat3(&bounds.Center, 0.5f*(vMin + vMax));
XMStoreFloat3(&bounds.Extents, 0.5f*(vMax - vMin));

轴对称包围盒及其旋转操作:

在局部空间中计算出的目标的AABB,可能放在世界空间中就不与坐标轴相平行,需要将它进行变换才能得到世界空间中的定向包围盒(oriented bounding box, OBB)。为了区别AABB这种特称的包围盒(任意朝向),在局部空间中的OBB称为OOBB(object-oriented bounding box)。但在实际工作中,我们通常总是先将网格变换到其局部空间里,再以局部空间内的轴对称包围盒进行碰撞检测

如果我们在世界空间中重新计算AABB,可能会得到一个肥胖性的长方体,与实际物体存在偏差。

还有一种办法,即放弃轴对称对齐包围盒,仅采用定向包围盒。此时,我们只需保存好定向包围盒相对于世界空间的朝向即可。DirectX碰撞检测库提供下述结构体来表示定向包围盒:

struct BoundingOrientedBox
{static const size_t CORNER_COUNT = 8;XMFLOAT3 Center;XMFLOAT3 Extends;XMFLOAT4 Orientation; // 表示包围盒旋转(box->world)的单位四元数(unit quaternion)...
}

四元数的概念会在第22章详细讲解,它可以像旋转矩阵那样来表示一种旋转动作。

通过一个给定的点集并借助DirectX碰撞检测库中的下列静态成员函数,我们就能构建出所需的AABB与OBB:

void BoundingBox::CreateFromPoints(_Out_ BoundingBox& Out,_In_ size_t Count,_In_reads_bytes_(sizeof(XMFLOAT3)+Stride*(Count-1)) const XMFLOAT3* pPoints,_In_ size_t Stride
);void BoundingOrientedBox::CreateFromPoints(_Out_ BoundingOrientedBox& Out,_In_ size_t Count,_In_reads_bytes_(sizeof(XMFLOAT3)+Stride*(Count-1)) const XMFLOAT3* pPoints,_In_ size_t Stride
);

如果我们定义了顶点结构体如下:

struct Basic32{XMFLOAT3 Posl;XMFLOAT3 Normal;XMFLOAT2 TexC;
};

并且,构成网络所用的顶点数组为:

std::vector<Vertex::Basic32> vertices;

那么我们就能按下面那样调用函数来生成包围盒:

BoundingBox box;
BoundingBox::CreateFromPoints(box,vertices.size(),&vertices[0].Pos,sizeof(Vertex::Basic32)
);

为了计算出目标网格的包围体,我们要在系统内存中准备一份可供使用的顶点列表副本,并存于如std::vector这样的类型中。这样做的原因是,CPU无法从以渲染为目的而创建的顶点缓冲区中读取数据。 -- 拾取(picking)与碰撞检测(collision detection)两种技术就是这样实现的。

③包围球:

包围球通过球心和半径来描述它。计算网格包围球的方法是先计算其AABB,然后再求取AABB的中心,以此作为包围球的中心,半径就是球心c到网格上任意顶点p之间的最大距离。

 grid:网格

在世界变换中,由于缩放操作的存在,包围球可能不再紧绕于目标网格,所以我们需要对包围球进行缩放。我们也可以将所有的网格与游戏场景中相同的缩放比例进行建模,这也难怪就避免了后续的缩放变换。

DirectX碰撞检测库提供了下述结构体来表示包围球:

struct BoundingSphere
{XMFLOAT3 Center;float Radius;...
}

还提供了下列静态成员函数,利用一组点集即可创建包围球:

void BoundingSphere::CreateFromPoints(_Out_ BoundingSphere& Out,_In_ size_t Count,_In_reads_bytes_(sizeof(XMFLOAT3)+Stride*(Count-1)) const XMFLOAT3* pPoints,_In_ size_t Stride
);

④视锥体:

视锥体(平截头体)有左、右、近、远、顶、底这6个相交的平面如

dx12 龙书第十六章学习笔记 -- 实例化与视锥体剔除相关推荐

  1. dx12 龙书第十九章学习笔记 -- 法线贴图

    本章需要掌握的内容:①法线贴图②TBN坐标系 1.使用法线贴图的动机 左图是使用法线贴图后的结果,右图是没有使用法线贴图(使用法线插值)的结果.可以明显感知到,高光在右图中是分布在一条直线上的,并没有 ...

  2. dx12 龙书第十二章学习笔记 -- 几何着色器

    如果不启用曲面细分(tessellation)这一环节,那么几何着色器(geometry shader)这个可选阶段便会位于顶点着色器与像素着色器之间.顶点着色器以顶点作为输入数据,而几何着色器的输入 ...

  3. 工程伦理第十二章学习笔记2020最新

    工程伦理第十二章学习笔记2020最新 继续更新

  4. 工程伦理第六章学习笔记2020最新

    工程伦理第六章学习笔记2020最新 继续更新第六章

  5. 游戏设计的艺术:一本透镜的书——第二十六章 团队以技术制作出游戏

    这是一本游戏设计方面的好书 转自天之虹的博客:http://blog.sina.com.cn/jackiechueng 感谢天之虹的无私奉献 Word版可到本人的资源中下载 第二十六章团队以技术制作出 ...

  6. 游戏设计的艺术:一本透镜的书——第十六章 故事和游戏结构能用间接控制巧妙地联合起来

    这是一本游戏设计方面的好书 转自天之虹的博客:http://blog.sina.com.cn/jackiechueng 感谢天之虹的无私奉献 Word版可到本人的资源中下载 第十六章 故事和游戏结构能 ...

  7. 推荐系统三十六式——学习笔记(三)

    由于工作需要,开始学习推荐算法,参考[极客时间]->[刑无刀大牛]的[推荐系统三十六式],学习并整理. 3 原理篇之紧邻推荐 3.1 协同过滤 要说提到推荐系统中,什么算法最名满天下,我想一定是 ...

  8. chmod 777 什么意思:鸟哥的linux私房菜第六章学习笔记

    鸟哥的linux私房菜基础学习篇:心血来潮决定看一下这本书,虽然都是讲很基础的东西,但是,绝对不是浪费时间,比如我看完了之后就终于明白了chmod777是什么意思了. 写这个读书笔记也主要是想记录一些 ...

  9. dx12 龙书第十八章学习笔记 -- 立方体贴图

    本章讨论:立方体贴图 cube map,即以特殊的方式来运用这种由6个纹理所构成的基本数组.我们可以利用这项技术方便地映射天空纹理或模拟反射. 1.什么是立方体贴图 -- Cube Map 立方体贴图 ...

  10. 《社会心理学》第六章学习笔记

    从众和服从 群体行为为何一致? 什么情景下会表现出从众? 哪些人倾向从众? 哪些人倾向抵制从众? 从众是否代表唯唯诺诺? 从众的概念 根据他人而做出行为或信念的转变. 顺从:由外部力量施压而违心的从众 ...

最新文章

  1. 如何重命名本地Git分支?
  2. ARM函数调用时参数传递规则
  3. RedHat下建立群集
  4. 简易 IM 双向通信电脑端 GUI 应用——基于 Netty、WebSocket、JavaFX 、多线程技术等
  5. 周二强新概念c语言答案,新编C语言程序设计(周二强版)课后习题练习4答案
  6. Hibernate的session一级缓存
  7. Python(字符编码)
  8. HONOR荣耀50/荣耀50Pro怎么解锁huawei 荣耀50pro屏幕锁开机锁激活设备锁了应该如何强制解除鸿蒙系统刷机解锁方法流程步骤不开机跳过锁屏移除锁定进系统方法经验
  9. 苹果电池显示维修_iFixit拆解苹果iPhone 12/Pro:显示屏和电池可互换
  10. 基于时空大数据的GIS技术,推动网格化管理创新发展
  11. SQL Server 数据库之分离和附加数据库
  12. PAT乙级 打印沙漏(20)
  13. sql导出的身份证后几位是000
  14. 图像超分辨率论文笔记
  15. 洛谷P1551 亲戚(并查集)
  16. Java程序应用实例:“你好 Java”
  17. 【ubuntu】Ubuntu 安装中文输入法
  18. 单片机输入和输出模式简要说明
  19. 聚类篇——(四)有序样品聚类
  20. 关于html5外文翻译三千字,论文外文文献翻译3000字左右.pdf

热门文章

  1. 程序分析-Joern工具工作流程分析
  2. CDN-内容推送网络
  3. C++桌面小精灵:实现像Office助手一样的帮助精灵
  4. SPJ数据库—初识sql语句(02)(注释版)
  5. cocoscreator 使用内置自带的资源和生成单色图片
  6. [云原生专题-4]:云平台 - 在阿里云平台快速搭建服务器集群
  7. 水深6到9米有鱼吗_红黄尾鲴鱼钓法大全(附配方)
  8. php larval 数据库when,Laravel DB类操作数据库
  9. DisparityCost Volume in Stereo
  10. java 滑杆和进度条_进度条 和 滑条