|
总结上一篇笔记所做的工作:
- 建立一个positionBuffer缓冲区;
- 写好ComputeShader的内核函数和其他相关函数;
- 在GPUGraph脚本中调用procedural draw.
目前所有Point的position都已经由GPU计算好了,但是进入play Mode后会发现所有屏幕中只有一个被渲染的立方体,因为所有的立方体都重叠在一起了。此时由于几乎没有数据需要从CPU发送到GPU,所以性能相比于之前有很大的提升。
接下来复制之前的PointSurface重命名为PointSurfaceGPU,改写如下:
Shader "Graph/Point Surface GPU"
{
Properties
{
_Smoothness("Smoothness", Range(0, 1)) = 0.5
}
SubShader
{
CGPROGRAM
//下面的ConfigureProcedural函数只会在常规绘制中调用,为了在阴影中也调用,我们需要增加一个
//自己定义的阴影通道 addshadow
#pragma surface ConfigureSurface Standard fullforwardshadows addshadow
//类似于GPU instancing,需要由pragma instancing_options指令指示,
//这表示表面着色器需要为每一个顶点调用一个函数,因此增加一个void ConfigureProcedural函数
//assumeuniformscaling用于命令Unity假设所有实例具有统一的缩放
#pragma instancing_options assumeuniformscaling procedural:ConfigureProcedural
//shader在editor模式编译是异步的,(为了方便修改),在编译完成前会用一个青蓝色的代替shader
//暂时代替,通常情况下没什么问题,但是代替shader不会使用程序化着色,当我们一次性渲染一百万
//个点的时候,可能会造成unity和操作系统崩溃。我们可以关闭异步shader功能,但是这仅仅是目前这
//一个shader的问题,我们可以添加 #pragma editor_sync_compilation 指令强制Unity暂缓一下,
//然后立刻编译shader,防止使用替代Shader
#pragma editor_sync_compilation
//target 4.5 代表需要支持OpenGL ES 3.1
#pragma target 4.5
struct Input
{
float3 worldPos;
};
float _Smoothness;
//只在shader编译procedural drawing的时候定义
#if defined(UNITY_PROCEDURAL_INSTANCING_ENABLED)
StructuredBuffer<float3> _Positions; //不需要写入所以不需要RWstructBuffer
#endif
//间距,控制模型空间到世界空间矩阵的缩放
float _Step;
void ConfigureProcedural()
{
#if defined(UNITY_PROCEDURAL_INSTANCING_ENABLED)
float3 position = _Positions[unity_InstanceID]; //模型空间到世界空间变换矩阵
//因为我们在程序绘图,所以unity_ObjectToWorld是一个单位矩阵; _Step 0 0 position.x
//我们先把它置零,然后把第三列坐标赋值为(position, 1.0) 0 _Step 0 position.y
//然后把缩放参数设置为_Step 0 0 _Step position.z
unity_ObjectToWorld = 0.0; //0 0 0 1
unity_ObjectToWorld._m03_m13_m23_m33 = float4(position, 1.0);
unity_ObjectToWorld._m00_m11_m22 = _Step;
#endif
}
void ConfigureSurface(Input input, inout SurfaceOutputStandard surface)
{
surface.Albedo = saturate(input.worldPos.xyz * 0.5 + 0.5);
surface.Smoothness = _Smoothness;
}
ENDCG
}
FallBack &#34;Diffuse&#34;
}
这段代码主要的作用除了着色,还有获取ComputeShader中的_Positions参数(由脚本传入)
修改脚本如下:
using UnityEngine;
public class GPUGraph : MonoBehaviour
{
[SerializeField]
ComputeShader computeShader; //定义所调用的compute shader
[SerializeField]
Material material; //需要的材质,使用之前的point surface
[SerializeField]
Mesh mesh; //mesh网格,使用自带的cube网格
static readonly int //把字符串添加到指示字段,方便修改和debug
positionsID = Shader.PropertyToID(&#34;_Positions&#34;),
resolutionID = Shader.PropertyToID(&#34;_Resolution&#34;),
stepID = Shader.PropertyToID(&#34;_Step&#34;),
timeID = Shader.PropertyToID(&#34;_Time&#34;);
[SerializeField, Range(10, 1000)] //分辨率
int resolution = 10;
[SerializeField]
FunctionLibrary.FunctionName function = default;
[SerializeField, Min(0f)]
float functionDuration = 1f, transitionDuration = 1f;
public enum TransitionMode { Cycle, Random }
[SerializeField]
TransitionMode transitionMode = TransitionMode.Cycle;
//Transform[] points;
float duration;
bool transitioning;
FunctionLibrary.FunctionName transitionFunction;
ComputeBuffer positionsBuffer;
void OnEnable()
{
//创建一块GPU区域用来存放positions,每个位置数据有3个float变量,所以是3 * 4 byte
positionsBuffer = new ComputeBuffer(resolution * resolution, 3 * 4);
}
private void OnDisable()
{
positionsBuffer.Release(); //释放存储区域
//可以激活Unity的内存垃圾回收机制(没有这步也会回收,不过是任意的,为了避免内存阻塞,要尽快回收)
positionsBuffer = null;
}
private void Update()
{
duration += Time.deltaTime;
if (transitioning)
{
if(duration >= transitionDuration)
{
duration -= transitionDuration;
transitioning = false;
}
}
else if (duration >= functionDuration)
{
duration -= functionDuration;
transitioning = true;
transitionFunction = function;
PickNextFunction();
}
UpdateFunctionOnGPU(); //需要运行内核,所以放在update里
}
void PickNextFunction()
{
function = transitionMode == TransitionMode.Cycle ?
FunctionLibrary.GetNextFunctionName(function) :
FunctionLibrary.GetRandomFunctionNameOtherThan(function);
}
void UpdateFunctionOnGPU()
{
float step = 2f / resolution; //间隔,为了使得所有cube保持在[-1,1]之间
computeShader.SetInt(resolutionID, resolution); //设置compute shader的参数
computeShader.SetFloat(stepID, step);
computeShader.SetFloat(timeID, Time.time);
computeShader.SetBuffer(0, positionsID, positionsBuffer); //设置缓冲区,不会传输数据,
//但是会和Compute内核建立联系
//第一个参数是内核的序号,单个内核序号为0
//必要时可以用FindKernel确定序号
int groups = Mathf.CeilToInt(resolution / 8f);
computeShader.Dispatch(0, groups, groups, 1); //运行内核,需要4个int参数,第一个索引其余三个时组数
//1表示计算第一组8*8个位置,由于固定8*8组大小,
//(8*8是因为cumputeShader中设置的是8*8)
//需要的组数可以 分辨率/8f 并向上取整
material.SetBuffer(positionsID, positionsBuffer);
material.SetFloat(stepID, step);
var bounds = new Bounds(Vector3.zero, Vector3.one * (2f + 2f / resolution));
//调用procedural draw,参数依次是网格,子网格,材质,边界,draw个数
Graphics.DrawMeshInstancedProcedural(mesh, 0, material, bounds, positionsBuffer.count);
}
}
最终可以看到同时渲染100万个点,我的电脑大概是37帧左右。
同时渲染100万个点 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|