草地编辑器-草的绘制

5.7k words

Desc:

草地编辑器之缝合怪

效果

Image text

草的绘制

上一篇中,已经得到了需要绘制的草的信息,包括顶点、法线、顶点色等,其中顶点由一个 Vector3 存储,可以通过CPU计算出,草对应的顶点位置,传递给GPU,也可以通过计算着色器的方式,让GPU去计算草的顶点位置信息

脚本通过 ComputeBuffer 将数据传递给计算着色器

ComputeBuffer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private ComputeBuffer m_sourceVertexBuffer;
private ComputeBuffer m_drawBuffer;
private ComputeBuffer m_argsBuffer;
...


m_sourceVertexBuffer = new ComputeBuffer(vertices.Length, Source_Vert_Stride, ComputeBufferType.Structured, ComputeBufferMode.Immutable);
m_sourceVertexBuffer.SetData(vertices);

m_drawBuffer = new ComputeBuffer(numSourceVertices * maxBladeTriangles, Draw_Stride, ComputeBufferType.Append);
m_drawBuffer.SetCounterValue(0);

m_argsBuffer = new ComputeBuffer(1, Indirect_Args_Stride, ComputeBufferType.IndirectArguments);

m_idGrassKernel = m_instantiatedComputeShader.FindKernel("Main");

m_instantiatedComputeShader.SetBuffer(m_idGrassKernel, "_SourceVertices",m_sourceVertexBuffer);
m_instantiatedComputeShader.SetBuffer(m_idGrassKernel, "_DrawTriangles", m_drawBuffer);
m_instantiatedComputeShader.SetBuffer(m_idGrassKernel, "_IndirectArgsBuffer", m_argsBuffer);


m_instantiatedMaterial.SetBuffer("_DrawTriangles", m_drawBuffer);
更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// LateUpdate is called after all Update calls
private void LateUpdate(){
// Clear the draw and indirect args buffers of last frame's data
m_drawBuffer.SetCounterValue(0);
m_argsBuffer.SetData(argsBufferReset);



// Update the shader with frame specific data
SetGrassDataUpdate();

// Dispatch the grass shader. It will run on the GPU
m_instantiatedComputeShader.Dispatch(m_idGrassKernel, m_dispatchSize, 1, 1);

// DrawProceduralIndirect queues a draw call up for our generated mesh
Graphics.DrawProceduralIndirect(m_instantiatedMaterial, bounds, MeshTopology.Triangles,
m_argsBuffer, 0, null, null, castShadow, true, gameObject.layer);

}

ComputeBuffer

缓冲对象(Buffer Object)传递:缓冲对象是一种可以在GPU端存储数据的对象,它可以用来传递一些较大的数据结构,如矩阵、数组等。在Shader中,可以通过glBindBuffer和glVertexAttribPointer函数绑定和传递缓冲对象

在Unity中,缓冲对象(Buffer Object)通常指的是ComputeBuffer。ComputeBuffer是一种可以在GPU端存储数据的对象,它可以用来传递一些较大的数据结构,如矩阵、数组等,以及用于计算着色器的数据。ComputeBuffer可以在Unity中使用ComputeShader进行创建和绑定,可以通过SetBufferData方法和GetData方法来传递数据和获取数据

除了ComputeBuffer,Unity中还有其他类型的缓冲对象,如GraphicsBuffer、VertexBuffer、IndexBuffer等,这些缓冲对象通常用于渲染过程中,用于传递顶点数据、索引数据等。在Unity中,可以通过Mesh类的SetVertexBufferParams和SetIndexBufferParams方法来创建和绑定这些缓冲对象

计算着色器中数据的创建

SourceVertices
1
2
3
4
5
6
7
struct SourceVertex
{
float3 positionOS; // position in object space
float3 normalOS;
float2 uv; // contains widthMultiplier, heightMultiplier
float3 color;
};
上一步中存储的顶点信息,由C#脚本传递给 Compute Shader,cs中根据顶点数据进而创建相关草的顶点信息
DrawTriangle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// This describes a vertex on the generated mesh
struct DrawVertex
{
float3 positionWS; // The position in world space
float2 uv;
float3 color;
};

// A triangle on the generated mesh
struct DrawTriangle
{
float3 normalOS;
DrawVertex vertices[3]; // The three points on the triangle
};
计算着色器中返回给Shader的数据结构,代表着一个三角面的数据,包括法线、世界空间坐标、顶点色信息.三角面的三个顶点法线是一致的
Data Create

Image text
来源
每个位置去设置相应的叶片、草数量信息

写入缓存中

1
2
3
4
5
6
7
8
9
10
11
12
13

AppendStructuredBuffer<DrawTriangle> _DrawTriangles;
...

for (int k = 0; k < numTrianglesPerBlade; ++k)
{
DrawTriangle tri = (DrawTriangle)0;
tri.normalOS = faceNormal;
tri.vertices[0] = drawVertices[k];
tri.vertices[1] = drawVertices[k + 1];
tri.vertices[2] = drawVertices[k + 2];
_DrawTriangles.Append(tri);
}

InterlockedAdd(_IndirectArgsBuffer[0].numVerticesPerInstance,
numTrianglesPerBlade * numBladesPerVertex * 3);

Shader中的计算

数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// This describes a vertex on the generated mesh
struct DrawVertex
{
float3 positionWS; // The position in world space
float2 uv;
float3 diffuseColor;
};

// A triangle on the generated mesh
struct DrawTriangle
{
float3 normalOS;
DrawVertex vertices[3]; // The three points on the triangle
};

StructuredBuffer<DrawTriangle> _DrawTriangles;
顶点着色器中数据的读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// -- retrieve data generated from compute shader
v2f vert(uint vertexID : SV_VertexID)
{
// Initialize the output struct
v2f output = (v2f)0;
DrawTriangle tri = _DrawTriangles[vertexID / 3];
DrawVertex input = tri.vertices[vertexID % 3];

output.positionCS = UnityWorldToClipPos(input.positionWS);
output.positionWS = input.positionWS;

float3 faceNormal = tri.normalOS;
output.normalWS = UnityObjectToWorldNormal(faceNormal);
output.uv = input.uv;

output.diffuseColor = input.diffuseColor;

return output;
}
像素着色器部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35


void UpdateTex()
{
camToDrawWith.enabled = true;
camToDrawWith.targetTexture = tempTex;
Shader.SetGlobalTexture("_TerrainDiffuse", tempTex);
}
public void DrawDiffuseMap()
{
DrawToMap("_TerrainDiffuse");
}

void DrawToMap(string target)
{
camToDrawWith.enabled = true;
camToDrawWith.targetTexture = tempTex;
camToDrawWith.depthTextureMode = DepthTextureMode.Depth;
Shader.SetGlobalFloat("_OrthographicCamSize", camToDrawWith.orthographicSize);
Shader.SetGlobalVector("_OrthographicCamPos", camToDrawWith.transform.position);
camToDrawWith.Render();
Shader.SetGlobalTexture(target, tempTex);
camToDrawWith.enabled = false;
}

fixed4 frag (v2f i) : SV_Target
{
...
_TopTint = _TopTint * ambient;
// tint the top blades and add in light color
terrainForBlending = lerp(terrainForBlending,terrainForBlending+ ( _TopTint* float4(i.diffuseColor, 1)) , verticalFade);
// final = lerp((terrainForBlending) * shadow , terrainForBlending, shadow);
final = terrainForBlending;
return _MainColor;
}

采样地表颜色进行混合

采样地表的颜色,通过创建一个摄像机,通过采样指定层,得到一张RT贴图,Shader中对于RT贴图进行采样,与草的颜色进行混合
Image text

靠近相机时按比例缩小草

Twitter

为了让面片两面显示,需要将剔除关闭

1
Cull Off 

OverDraw问题

大批量草地通常会引发大量的 overdraw,因为草地由许多小片叶子组成,每个叶子都需要渲染,这会导致大量的重叠和 overdraw 问题

解决方案

Early-Z和Z-Prepass

Image text
Image text

  • 1: 前面的像素可能被clip掉,但是深度依旧写入,会导致后面深度测试不通过
  • 3: 透明度混合开启后的物体一般不会写入深度,Early-Z因此无法生效

Image text
由近及远排序会导致CPU计算量增大,而且会破坏和批,使用Z-Prepass的方法解决

Image text
在第一个pass后,就可以得到一个深度缓存,对应和摄像机的最小距离
Image text
存在的问题:一个物体有多个pass会破坏动态和批

Image text
Image text
Image text
Image text
对于数量多的不透明物体需要节省overdraw时,会考虑用到这种技术