周笔记(7/52) - DX11 - Tessellation/Hardware Instance

来源:互联网 发布:网易企业邮箱端口号 编辑:程序博客网 时间:2024/06/05 07:32

Tesselation(曲面细分)

要点

  1. 将一个几何块细分成更小的三角形,提供更多细节
    • 可以协作动态LOD
    • 提供更高效的动画
    • 节省空间
  2. 管线中的位置:VS -> HS(Hull Shader) -> Tessellator Stage -> DS(Domain Shader) -> GS(Geometry Shader)
  3. 基本概念:
    • 不是输出片元,而是输出一个带有若干个控制点(control point)的包(patch)。根据细分拓扑结构,这些控制点会填入拓补结构中的顶点,然后得到诸多三角形。
    • 输入的控制点的个数可以用 D3D11_PRIMITIVE_N_CONTROL_POINT_PATCH(n=1~32)来表示。使用 context->IASetPrimitiveTopology() 进行设置。
  4. 如何决定添加细节
    • 因素
      • 根据与摄像机的距离
      • 估计所占据的屏幕像素
      • 旋转角度与视线的距离
      • 粗糙程度
    • 注意
      • 开关tesselation是比较费的,尽量批量处理draw call
      • 8像素以下的面就不要再细分了
  5. Tesselation包括以下几个部分
    • hull shader
      • contant hull shader
        • 输入:InputPatch < VertexOut, 4 >,这里的4代表一个四边形
        • 输出: struct PatchTess{ // 这里的4和2是因为上边是四边形,对于三角形则是 3和1
          EdgeTess[4] : SV_TessFactor; // 代表4条边上分别细分成几段
          InsideTess[2] : SV_InsideTessFactor; // 代表内部的quad的长宽分别细分几段
          … // 也可以有其他信息
          }
        • 参数值代表了细分时的拓扑结构(图略)
        • 值为0代表这个primitive被丢弃了,从而省略之后的流程。例如对于frustum裁剪、背面消隐引起的不可见,都可以在这时候被直接丢弃。
        • 值都为1代表这个primitive实际上没有做什么工作——不要这样浪费GPU。
      • control point hull shader
        • 输入:
          InputPatch < VertexOut, 4 >(输入的控制点们)
          SV_OutputControlPointID(本次需要计算的输出的控制点的ID)
          注意两者的数量是没什么关系的
        • 输出:struct HullOut{
          float3 pos : POSITION;
          …}
        • 语法:
        • [domain(“quad”)]:patch的类型。包括 tri,quad,isoline。
        • [partitioning(“ingeger”)]:决定细分的模式。例如:integer——只factor的整数部分进行增删控制点(会导致突变);fractional_even/odd——使用分数部分来从两个整数factor之间光滑切换
        • [outputtopology(“triangle_cw”)]:绕序。还有triangle_ccw,line。
        • [outputcontrolpoints(4)]:hull shader输出控制点的数量。SV_OutputControlPointID为其索引。
        • [patchconstantfunc(“ConstantHS”)]:指示constant hull shader的名字。
        • [maxtessfactor(64.0f)]:告诉shader你会用到的最大的tessellation factor的hint。可以带来优化。最大为64。
      • tessellation阶段不受用户控制
      • domain shader
        • 根据tessellation 阶段输出的顶点,对其进行VS类似的功能,变换到齐次裁剪空间等
        • 输入:
          SV_DomainLocation(float2):控制点的uv坐标(或者对三角形而言uvw,这种重心坐标(barycentric)对Bezier曲线非常友好)
        • 输出:struct DomainOut{float3 pos:SV_POSITION;…}
        • 语法:
          [DOMAIN(“quad”)]

遗留问题

  • [partitioning(“ingeger”)] 不明白什么意思
  • 没太看懂 tessellation 是什么意思

Bezier Curve

定义

  1. 线性贝塞尔曲线
    • B(t) = (1-t) * P0 + t * P1 (0<=t<=1)
  2. 二次贝塞尔曲线
    • B(t) = (1-t) * ((1-t)P0 + tP1) + t * ((1-t)P1 + tP2)
      = (1-t)^2 * P0 + 2(1-t)tP1 + t^2 * P2
  3. 三次贝塞尔曲线
    • (以此类推)
  4. 用几何画板的一个简单绘图展示
    这里写图片描述
    (线段比例关系可以根据图片大致猜测)
    上图中中间BC线段是线性贝塞尔曲线;
    上下两条红线是二次贝塞尔曲线,它有1个控制点;
    中间红线是三次贝塞尔曲线,它有2个控制点;
    可以看出,贝塞尔曲线并不一定经过控制点;两端处的线段指示切线方向。
    联系到PhotoShop中常用的制作path的钢笔工具,可见那个钢笔工具其实就是一个三次贝塞尔曲线而已~
    远古的画图板,貌似是用的三次贝塞尔曲线~那时那个曲线工具,先拖一条直线,然后隔空拖两次得到一条曲线。。。【我至今还记得那时候无论怎么拖曲线都拖不理想的痛苦~
  5. 贝塞尔曲面(bezier surface)
    例如三次贝塞尔曲线,需要4x4个点。首先进行4行贝塞尔曲线计算,再将t从0遍历到1,计算每列的贝塞尔曲线。从而得到一个曲面。

建立第一人称摄像机

杂记

  1. 这本书提供的是每次移动时,对摄像机矩阵进行旋转的策略。以前试过这种,误差还是挺大。比不上DX Sample里边记录一个四元数然后每次移动时修改四元数,再生成矩阵的算法。
  2. 一些函数
    • XMMatrixPerspectiveFovLH
    • XMVectorMultiplyAdd(step,look,originpos) = step*look+originpos,特别适合摄像机平移
    • XMMatrixRotationAxis(axis, angle)
    • XMVector3TransformNormal(originVec, transformMat)
    • XMVectorSplatZ 将vector的所有分量都设为其z值
    • XMVectorReciprocal 计算逐分量的倒数
    • viewMat(i, 0) 是x方向,viewMat(i, 1)是y方向,viewMat(i, 2) 是z方向,viewMat(i, 3) 是平移位置

硬件实例化

要点

  1. 意思就是如何在一个场景中多次画同一个物体
  2. 如果每次绘制都重设颜色、贴图、世界矩阵,那依然还是有很多API overhead。在DX9中常见CPU是瓶颈,从而限制drawcall的数量。
    • 可以使用 batch 技术,来减少drawcall
    • 可以配合纹理列表数组(texture array),使用硬件实例化(hardware instancing),来减少drawcall。
  3. 硬件实例化是对vs做的操作,通过对input layout的input slot class进行修改,来输入多个instance的数据

步骤

  1. VS
    • 增加 SV_InstanceID 类型,指示instance的索引
  2. Input Layout
    • D3D11_INPUT_ELEMENT_DESC
      • UINT InputSlotClass: 指示输入element是作为vertex element还是instanced element
        • D3D11_INPUT_PER_VERTEX_DATA
        • D3D11_INPUT_PER_INSTANCE_DATA
      • UINT InstanceDataStepRate: 指示每个instanced data element中有多少个instance。例如要用用3个颜色画6个instance,那么step就是2。对于vertex element,这个应该是0。
      • UINT InputSlot: 可以用多个Slot了
      • UINT SemanticIndex: 同一类型的数据,这个index要递增了
        这里写图片描述
    • context->IASetInputLayout(startSlot, numBuffers, pVB, pStride, pOffset)
      • numBuffers: slot的个数
      • pVB: 两个buffer分开
      • pStride: 数组内容为两个buffer各自的stride
      • pOffset: 数组内容为两个buffer各自的stride
    • context->IASetInputLayout
      • 输入desc
        这里写图片描述
    • context->DrawIndextedInstanced(indexCountPerInstance, instanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation)
      • indexCountPerInstance: 每个instance的顶点数
      • instanceCount: instance数量
      • 后三个指示从哪里开始读取vertex
        这里写图片描述
  3. 建立Instanced buffer
    • 使用dynamic buffer,因为instanced buffer通常是经常变化的
    • D3D11_BUFFER_DESC
      • Usage: D3D11_USAGE_DYNAMIC
      • CPUAccessFlags: D3D11_CPU_ACCESS_WRITE

视锥剔除(frustum culling)

杂记

  1. 去看看xnacollision.h/cpp里边对于各种几何碰撞的检测~
  2. AABB: axis-aligned bounding box
    OBB:oriented bounding box
    bounding sphere(球形包围盒)
  3. 为了碰撞检测和拾取,常常保留一份vertices的备份在CPU
  4. Frustum体的碰撞检测(xna也提供了)
    • Sphere:
      • 检测球和六个面的位置关系
    • AABB:
      • AABB的每个面,检测它和frustum面法线最平行的那个对角线——它的两个端点分别在frustum面两侧的时候会相交
      • 最平行:因为是AABB所以只需要按照法线方向来取自己顶点里的最大最小值就行了
  5. 用CPU来做视锥剔除,可以减少三角形被送入GPU的流中。在输入数据的时候,不输入不可见的数据,然后减小 context->DrawIndexedInstanced 中的 instancedCount 参数即可。

遗留问题

  1. 去看看xnacollision.h/cpp里边对于各种几何碰撞的检测~
0 0
原创粉丝点击