突然发觉,要绘制一堆点,好像有另一种方式——ComputeShader。这个之前有研究过,但是一直没怎么用过,不过作为在GPU上计算的渲染方式,倒是挺适合做统一规则的大量元素的绘制。
今天就先试着将昨天的点阵图用CS的方式实现看看,顺便就当作是温习一下知识点。
先看鼓捣出来的一堆效果:
其实要用CS做图还是比较麻烦的,因为涉及的文件挺多的,而且都很重要。
先从shader脚本开始说起,因为它是最简单的。这个脚本是图像渲染的最后一步,即图像数据传入该脚本后,该脚本负责渲染。其中关键是StructuredBuffer数据,里面装的是经过compute脚本处理后的数据。
Shader "MyShader/CS_SS_0"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma target 5.0
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _Color;
StructuredBuffer<float3> points;//由cs脚本传入值,结构化的颜色缓存
struct appdata
{
uint id:SV_VERTEXID;//每个点都有指定的id
};
struct v2f
{
float4 pos:SV_POSITION;
float3 uv :TEXCOORD0;
};
v2f vert(appdata v){
v2f o;
o.pos=mul(UNITY_MATRIX_VP,float4(points[v.id],1));
return o;
}
fixed4 frag(v2f i):SV_Target{
return _Color;
}
ENDCG
}
}
}
然后说c#脚本,它是负责管理各文件数据的,比如创建材质、将compute脚本处理后的数据传入材质等:
using UnityEngine;
public class CS_0 : MonoBehaviour
{
public ComputeShader comShader;
private Material mat;
private ComputeBuffer buffer;
public int size = 1;
private int number;//点的数量
private int kernelId;
public MeshTopology meshType;
public float twirl = 0.001f;
private float angle;
public Color color;
private void Start()
{
mat = new Material(Shader.Find("MyShader/CS_SS_0"));
mat.SetColor("_Color", color);
number = 512 * 8 * 8 * 8;
buffer = new ComputeBuffer(number, 12);//初始化缓存区,12是指结构体points的字节长度,float=4,float3=12
kernelId = comShader.FindKernel("CS_0");//找到入口的id
}
private void OnRenderObject()
{
comShader.SetBuffer(kernelId, "Result", buffer);//根据id,将缓存区关联上
comShader.Dispatch(kernelId, size, size, size);//派遣size组的点,当size=1时,即刚好为CS_0的numthreads范围
mat.SetPass(0);//打开材质球
mat.SetBuffer("points", buffer);
Graphics.DrawProceduralNow(meshType, number);//绘制到现有的材质球上(会自动找到打开的材质球)MeshTopology有好几种,一般用点或线;DrawProceduralNow函数是将打开的材质绘制到GPU上
angle += Time.deltaTime;
if (angle > 360) angle -= 360;
comShader.SetFloat("angle", angle);
comShader.SetFloat("twirl", twirl);
}
private void OnDestroy()
{
buffer.Release();//释放
}
}
最后说compute文件,这就是将在GPU中计算的脚本:
#pragma kernel CS_0
float angle;
float twirl;
RWStructuredBuffer<float3>Result;
struct points{
float3 pos;
};
inline uint Index(uint3 id)
{
return id.x + id.y *64 + id.z *64*8*8;//根据每个点的id(x,y,z)计算出来:每一层有8*8个点,y加1就要加64位,z加1就要加64*8位,另外还要乘numthreads最后一位,为什么呢?
}
inline float3 Rot(float3 pos)
{
float xyz=angle*(pos.x+pos.y+pos.z-pos.x*pos.y*pos.z)*twirl;
float s=sin(xyz);
float c=cos(xyz);
float3x3 m=float3x3 (
s,c,0,
s,-c,0,
0,0,1
);
return mul(m,pos);
// return float3(s*pos.z,c*pos.z,pos.z*c/s);
}
[numthreads(8,8,8)]//一组空间的范围,即每次传值都是渲染一组
void CS_0(uint3 id : SV_DispatchThreadID,uint3 TID:SV_GROUPTHREADID)
{
uint f=Index(id);
Result[f]=Rot(id);//返回计算后的点
}
关于这块的脚本,用的是HLSL语言,用着跟CG差不多。我只是用矩阵变换的方式做了一下旋转,其实还有好多顶点计算方式。今天就先到这,明天可以继续研究一下CS。
最后放上我设置的参数: