周笔记(6/52) - DX11 - Geometry Shader/Compute Shader

时间:2022-12-26 03:56:11

Geometry Shader

要点

  • 作用于每个片元(primitive)
    • each_outPrimitiveList = GeometryShader(each_primitive.vertexList)
    • 不论三角形带还是三角形列表,输入都是一个完整三角形顶点列表
  • 离开GS的顶点坐标必须是齐次裁剪空间
  • 输出必须预先定义最大顶点数N([maxvertexcount(N)]),且越小越好
  • 可以使用GS来制作billboard效果,可以节省四分之三的顶点吞吐
  • SV_PrimitiveID:面元索引。可以直接在PS里边加入该输入;如果有GS的话,一定要出现在GS的signature中才能使用它。
  • SV_VertexID:顶点索引。VS中即可使用。

用法

  • 输出流 SteamOutputObject
    • 类型:PointStream/LineStream/TriangleStream
    • 总是strip的形式
    • 可以选择不输出
    • 如果一个strip中的顶点数不能够成一个primitive,那么那个strip将被舍去
  • 函数
    • 追加顶点:void SteamOutputObject::Append(OutputVertexType v)
    • 开始新的三角形带:void SteamOutputObject::RestartStrip()
  • 其他同vs和ps

Texture Array

要点

  • 在PS中进行Sample的时候,对于Texture2D列表的索引必须是const,所以无法临时更改Sample对象
  • 但是使用TextureArray,就可以通过制定z值来索引到

用法

  1. 制作TextureArray的SRV
    • 创建每个Texture:
      • 填充 D3DX11_IMAGE_LOAD_INFO
      • 从File中读取ID3D11Resource:D3DX11CreateTextureFromFile
      • 获取 D3D11_TEXTURE2D_DESC
    • 创建TextureArray:
      • 根据texture的desc填充desc,ArraySize为所需个数
      • 创建ID3D11Texture2D:CreateTexture2D
    • 拷贝Texture进入TextureArray:
      • 遍历每个texture的每个mip level
      • 锁定:context->Map
      • 拷贝:context->UpdateSubresource
      • 解锁:context->Unmap
    • 创建TextureArrayView:
      • 填充 D3D11_SHADER_RESOURCE_VIEW_DESC
      • device->CreateShaderResourceView
  2. 获取Texture
    • textureArray.Sample(samLinear, float3(tex_uv, tex_idx))

Alpha-to-converage

要点

  • 应对大量透明物体时如何选择:Alpha Test ? Alpha to converage ? Alpha Blend ?
    • Alpha Test: aliasing严重
    • Alpha Blend: 计算量大
    • 带MSAA的Alpha Test: 只能对depth stencil引起的边缘进行反走样,不能对alpha test引起的边缘反走样。注意MSAA计算四个子片元时,是对depth stencil引起的显隐进行的平均。
  • Alpha to converage解决上述问题
    • 纱门透明
    • 多种pattern
    • 虽然效果肯定没有blend那么好~

用法

  • 填充 D3D11_BLEND_DESC: AlphaToConverageEnable=True; BlendEnable=False
  • 创建blend state:device->CreateBlendState

Compute Shader

要点

  1. GPGPU(general purpose GPU)
    • 流操作:GPU被设计为对于从单个或者连续位置(流操作streaming operation)读取大量内存的情况有优化,这不同于CPU被设计为针对随机内存读取。
    • 大量并行的:而且,因为顶点和像素的处理是互相独立的,所以GPU被架构为大量并行的。例如NVIDIA的Fermi架构支持16个流多处理器(streaming multiprocessor),每个有32个CUDA核心,亦即总共有512个CUDA核心。
    • GPGPU(general purpose GPU):使用GPU的以上特性,对非图形的数据并行算法的工作也能很好的运算。
    • 数据交换速度:GPU与VRAM > CPU与RAM > CPU与GPU。瓶颈往往在CPU与GPU
    • Thread Group:
    • 每个multiprocessor中有多个thread group,每个thread group中是多个thread
    • thread group中的thread可以使用需要同步(synchronization)操作(operation)来共享内存,不同thread group的thread不能共享内存。
    • GPU会把thread group中的多个thread打进包(wrap)中,每个wrap固定容纳32(NV)或64(ATI)个thread,然后交给multiprocessor。为了效率,最好还是把一个thread group里的thread的数量设置为32或64的倍数。
  2. CS
    • 独立于管线之外,它可以和管线混合起来,也可以独立去做GPGPU工作:
      周笔记(6/52) - DX11 - Geometry Shader/Compute Shader
    • CS中的越界行为是被定义好了的:读取时返回0,写入时无操作。
    • 依赖于thread的系统标志值(thread identification system values)
      • SV_GroupID(int3): 被声明出的一系列thread group的其中一个的id
      • SV_GroupThreadID(int3): 每个group中的一个thread的id
      • SV_DispatchThreadID: 在所有group的范围内,每个thread有完全唯一的此dispatch id。即:DispatchThreadID.xyz = groupID.xyz * ThreadGroupSize.xyz + groupThreadID.xyz
      • SV_GroupThreadID: 在thread的局部存储中索引
      • 线性版本的索引:idx = id.z * size_x * size_y + id.y * size_x + id.z。注意这与CPU中的matrix的索引方式相反。
      • 我们可以使用这些id来作为texture的索引用

用法

  1. 建立一个3D格子的thread group:
    • context->Dispatch(UINT threadGroupCountX, ..Y, ..Z)
  2. 输入类型(SRV):
    • Shader Resource View
    • Texture2D inputA;
    • bindFlag: D3D11_BIND_CONSTANT_BUFFER
    • 类型:ID3D11ShaderResourceView
    • 创建:ID3DX11EffectShaderResourceVariable
    • 绑定:ID3DX11EffectShaderResourceVariable->SetResource
  3. 输出类型RW(UAV):
    • Unordered Access View
    • RWTexture2D output;
    • bindFlag: D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_SHADER_RESOURCE
    • 类型:ID3D11ShaderResourceView + ID3D11UnorderedAccessView
    • 创建:CreateShaderResourceView + CreateUnorderedAccessView
    • 绑定:ID3DX11EffectUnorderedAccessViewVariable->SetUnorderedAccessView
  4. 在HLSL中使用自定义类型的buffer:
    • 在CPP中声明一个同类型的Data
    • buffer desc:
      • ByteWidth = sizeof(Data) * length
      • StructureByteStride = sizeof(Data)
      • MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED
    • view desc:
      • Format = DXGI_FORMAT_UNKNOWN
    • 其他不变
  5. 指定每个group中的thread个数(3D格子):
    • [numthreads(16,16,1)]
  6. 函数:
    • 使用 SV_DispatchThreadID 作为要处理的 texture 的 sample 的 uv,在thread足够多的情况下,即可处理texture上的所有pixel
    • 使用 [] 对texture进行索引,或者使用 SampleLevel(而非Sample)来代替。SampleLevel具有mipmap及其插值功能,但要注意它使用的是归一化的坐标范围
    • 使用 Consume 和 Append 来从 input texture 中pop一个pixel和向 output texture写入一个pixel
      • ComsumeStructuredBuffer / AppendStructuredBuffer
      • 注意在创建UAV的view desc时,要指定buffer.Flags 为 D3D11_BUFFER_UAV_FLAG_APPEND
      • 注意 Consume 是一次性的。
      • 注意 Append 不是自动扩容的。
      • 但是因为thread的顺序是无需的,所以这只在当vertex的互相顺序无关紧要时能用。例如粒子。
  7. 使用 group 内共享的内存 (Shader memory / thread local storage)
    • 声明:groupshared float4 gCache[256]
    • 最大空间为32kb
    • 使用SV_ThreadGroupID来索引
    • 过多的使用会导致一个multiprocessor中不能放入多个Thread group,导致并行出现困难,从而降低效率
    • 通常的使用情形是存储texture值。比如blur这种需要对一个pixel进行获取(比较慢)很多次的操作。把值全都存入shared memory就可以快速访问pixel了。
    • 使用synchronization操作来等待所有thread都加载进shared memory:在load之后加上一句GroupMemoryBarrierWithGroupSync()
  8. 读取结果:
    • 填充 buffer desc 时:
      • Usage: D3D11_USAGE_STAGING
      • BindFlags: 0
      • CPU access: D3D11_CPU_ACCESS_READ
    • context->CopyResource:把结果拷贝到 buffer (CPU)
    • 声明 D3D11_MAPPED_SUBRESOURCE b
    • context->Map(ID3D11Buffer*, 0, D3D11_MAP_READ, 0, &b)
    • context->Unmap

Blur例子

除了主线之外也值得注意的几个点

  • 高斯模糊是separable的,也就是x和y方向独立,据此,分别做x和y方向的,可以把一次模糊的访问量从n平方减少到n,大大减少了shared memory用量
  • 离屏渲染(render-to-off-screen-texture or render-to-texture)
    • 先渲染到一个非back buffer的target中,再渲染到back buffer
    • 如此一来,在CS中所要用到的CPU access就变为可能了。
    • bindFlag: D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS
    • 例如:
      • 阴影映射(Shadow mapping)
      • 空间环境光遮蔽 (Screen Space Ambient Occlusion)
      • 使用cube map进行动态反射 (Dynamic reflection with cube maps)
  • context的前后缓存切换有间接费用(overhead),应尽可能最小化
  • 对于blur,可以使离屏渲染的buffer变为1/4,以减少计算。之后用magnification filter放大回去就是了。
  • 如果texture的宽高和Thread的个数不成倍数,那么会有一些不相关的thread混进末尾。我们只能在shader中做好越界检查和钳位了。
  • shared memory: 例如横向n像素r半径的blur,需要n+2r的memory。

流程

  1. 绑定 A 为 SRV (作为input)
    • SetInputMap
  2. 绑定 B 为 UAV(作为output)
    • SetOutputMap
  3. 设置好thread,进行水平方向的blur
    • numGroupX = ceil( mWidth / 256.f )
    • device->Dispatch(numGroupX, mHeight, 1)
    • device->CSSetShaderResources
    • device->CSSetUnorderedAccessViews
    • fx->Tech->GetPassByIndex->Apply
    • 最后要清空:
      • CSSetShaderResources(0,1,nullSRV)
      • CSSetUnorderedAccessViews(0,1,nummUAV,0)
      • CSSetShader(0,0,0)
  4. 绑定 B 为 SRV(作为input)
  5. 绑定 A 为 UAV(作为output)
  6. 进行垂直方向的blur
    • 类似3.
  7. 于是最终A中存储的就是blur后的结果