手把手教学cesium自定义材质
cesium材质shader编写教学
一、说明
本文章主讲shader,不是浅浅的教大家怎么在cesium方面自定义材质,而是由最简单的效果逐步深入,了解圆形、线、渐变、动画的思路。所以最好需要有一点shader的基础,当然下文我也会稍微介绍一下。如果想看视频版的话点击这里跳转b站
二、webgl shader language基础知识
webgl的介绍啥的就不多赘述了,这里主要了解一下shader中数据来源以及之后用到的函数。
1、shader中的四种数据来源
类型 | 中文 | 说明 |
---|---|---|
attribute | 属性 | 主要是顶点着色器用来从缓冲(Buffer)中获取数据 |
uniform | 统一值 | 在每一次渲染中全局有效且不变的值 |
texture |
纹理 | 一个数据序列,大多数情况下可以理解为图像数据 |
varying | 可变量 | 顶点着色器向片元着色器传值的方式,并且会以插值的形式传递 |
2、文章出现的shader内置函数
函数名(参数) | 说明 |
---|---|
pow(x,y) | x的y次方。如果x小于0,结果是未定义的。同样,如果x=0并且y<=0,结果也是未定义的。 |
sqrt(x) | 计算x的开方。如果x小于0,结果是未定义的。 |
fract(x) | 返回x-floor(x),即返回x的小数部分 |
step(edge, x) | 如果x < edge,返回0.0,否则返回1.0 |
smoothstep(edge0, edge1, x) | 如果x <= edge0,返回0.0 ;如果x >= edge1 返回1.0;如果edge0 < x < edge1,则执行0~1之间的平滑埃尔米特差值。 如果edge0 >= edge1,结果则是从1~0之间递减,也就是x=edge0会返回1.0。(最好不要这样做)。 |
distance(a,b) | 计算a,b两点间的距离,二维三维都可以 |
三、cesium中为几何体自定义材质
下面的代码直接复制即可使用,当然我这里不包含viewer的初始化(自己初始化一个就行)。如何自定义材质步骤我就简单介绍一下:
1.按照cesium格式要求写一个czm_getMaterial函数,然后将代码字符串放入Material中
2.再用这个Material组成MaterialAppearance或者PolylineMaterialAppearance
3.在primitive中使用这个材质就行。
如果想在entity中使用,那可以随便找一篇文章参考一下,自定义材质最重要的还是shader部分,其他都是大差不差的。
1、模块导入
import {
Cartesian3,
Color,
Primitive,
PolygonGeometry,
BoxGeometry,
PolygonHierarchy,
PolylineGeometry,
EllipsoidGeometry,
GeometryInstance,
Material,
MaterialAppearance,
PolylineMaterialAppearance,
Transforms,
} from 'cesium'
2、材质声明以及在几何体中使用
这里我用了四种几何体来观察材质的效果,所以代码会有点多,复制后只需要关注shader就行。代码我是写在TS里的,我看了一遍没有类型标注啥的,应该不用修改就可以直接在JS里运行
const shaderSource = `
uniform vec4 color;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
`
const myMaterial = new Material({
translucent: false,
fabric: {
type: 'test',
uniforms: {
color: new Color(1, 0, 0, 1),
},
source: shaderSource
}
})
const appearance = new MaterialAppearance({
material: myMaterial,
})
const polylineAppearance = new PolylineMaterialAppearance({
material: myMaterial
})
const polygonPrimitive = new Primitive({
geometryInstances: new GeometryInstance({
geometry: new PolygonGeometry({
polygonHierarchy: new PolygonHierarchy(Cartesian3.fromDegreesArray([
114, 25,
114.01, 25,
114.01, 25.01,
114, 25.01,
])),
height: 1000
})
}),
appearance
})
const boxPrimitive = new Primitive({
geometryInstances: new GeometryInstance({
geometry: BoxGeometry.fromDimensions({
dimensions: new Cartesian3(1000, 1000, 1000)
}),
modelMatrix: Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(114.005, 25.02, 1000))
}),
appearance
})
const polylinePrimitive = new Primitive({
geometryInstances: new GeometryInstance({
geometry: new PolylineGeometry({
positions: Cartesian3.fromDegreesArrayHeights([
114.02, 25.02, 1000,
114.05, 25.02, 1000
]),
width: 2,
})
}),
appearance: polylineAppearance
})
const ellipsoidPrimitive = new Primitive({
geometryInstances: new GeometryInstance({
geometry: new EllipsoidGeometry({
radii: new Cartesian3(1000, 1000, 1000),
}),
modelMatrix: Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(114.03, 25.005, 1000))
}),
appearance
})
viewer.scene.primitives.add(polygonPrimitive)
viewer.scene.primitives.add(boxPrimitive)
viewer.scene.primitives.add(polylinePrimitive)
viewer.scene.primitives.add(ellipsoidPrimitive)
viewer.camera.flyTo({
destination: Cartesian3.fromDegrees(114.02, 25.01, 10000),
duration: 0
})
3、cesium材质shader需求说明
我们将材质的shader单独拎出,让我们看看cesium材质需要的source是什么样的。
uniform vec4 color;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
上面的代码就是一个最简单的cesium材质,效果和cesium自带的Color材质一模一样,代码有些细微差别。
cesium所需的材质shader具体来说就是需要写一个 czm_getMaterial 函数。然后这个函数返回一个czm_material 类型的结构体(结构体可以类比为JS中的对象)。这个结构体最好通过 czm_getDefaultMaterial 函数获得,再在此基础上修改。
我们可以在这个函数上方声明一些uniform来从JS中获取一些数据,比如这个最简单的示例就从JS中获取颜色值,并赋予 czm_material 类型的结构体的diffuse属性。
4、czm_materialInput 结构体说明
czm_materialInput 类型的结构体是 czm_getMaterial 函数的参数,cesium会在几何体的片元着色器中调用 czm_getMaterial ,并将参数传递给该函数。
属性名 | 类型 | 说明 |
---|---|---|
s |
float | 一维纹理坐标 |
st |
vec2 | 二维纹理坐标 |
str |
vec3 | 三维纹理坐标 |
normalEC |
vec3 | 眼坐标系中的未扰动表面法线 |
tangentToEyeMatrix |
mat3 | 将切线空间法线转换为眼空间的矩阵 |
positionToEyeEC |
vec3 | 从片段到眼睛的向量(在眼坐标系中)。其大小为片段到眼睛的距离(以米为单位) |
5、czm_material 结构体说明
cesium对该结构体的说明是:
保存可用于照明的材质信息。由所有 czm_getMaterial 函数返回。
从中我们可以了解到该结构体不仅仅保存着材质信息,并且该结构体还可以被用于光照(cesium使用的冯氏光照模型第二个参数就是该结构体)。
属性名 | 类型 | 说明 |
---|---|---|
diffuse |
vec3 | 向各个方向均匀散射的入射光,默认值vec3(0.0)。(漫反射颜色) |
specular |
float | 沿单个方向反射的入射光的强度,默认值0.0。(镜面高光强度) |
shininess |
float | 镜面反射的清晰度。值越高,创建的镜面高光越小、越聚焦。默认值1.0 |
normal |
vec3 | 表面的法线在眼睛坐标中。它用于法线贴图等效果。默认值为曲面的未修改法线。(就是normalEC) |
emission |
vec3 | 材质在所有方向上均匀发射的光。默认值为 vec3(0.0),它不发光。 |
alpha |
float | 此材质的不透明度。0.0 是完全透明的;1.0 是完全不透明的。默认值1.0 |
四、shader深入
1、最简单的shader
先简单介绍一下fragmentShader,在渲染管线中其主要负责光栅化,通俗的来讲就是确定每一个像素的颜色。在一次绘制中,对于每一次像素都会调用一次fragmentShader,其中有些东西是变化的,例如像素的坐标、可变量的值;但也有些东西是不变的,例如uniform的值。
uniform vec4 color;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
再次将最简单的shader拿过来,这次看看在这个简单的shader中我们做了什么。首先我们声明了一个 uniform(统一值) ,类型为vec4,名称为 color 。在之后我们将其保存在 outColor 中。为什么要这样多此一举呢,因为uniform的值我们是无法改变的,为了之后能改变颜色值,所以我们用一个变量接收一下。
然后在通过 czm_getDefaultMaterial 获得 czm_material 类型的结构体,前面说过,czm_material 最好通过czm_getDefaultMaterial 来获取一个默认的,再在此基础上改变,而不是直接自己声明一个。
最后我们将传递的颜色的rgb值赋予diffuse属性,a值赋予alpha属性。alpha属性应该不用过多介绍了。而diffuse属性则是比较重要的一个属性,这里我们需要分为两种情况,一种是应用光照,一种是不应用光照。
#ifdef FLAT
out_FragColor = vec4(material.diffuse + material.emission, material.alpha);
#else
out_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC);
#endif
- 不应用光照:在上面的代码中,如果我们定义了FLAT属性,也就不会使用光照模型,这时候像元的颜色就等于material的diffuse属性与emission属性之和,而emission属性我们一般不会去使用。所以这时候可以将diffuse属性当成最终的颜色
- 应用光照:如果应用了光照,那么就会将material传入czm_phong这个函数中,下面是cesium中的冯氏光照模型的具体实现。我们从代码中可以看到cesium的冯氏光照模型将传递过来的diffuse分为了两半,一半变成了ambient (环境光),另一半则是与计算出来的diffuse(漫反射光强)与光的颜色相乘作为最终的漫反射光。从代码中我们就可以得到一个信息,那就是在没有自发光emission的情况下,cesium物体最暗的颜色就是material.diffuse的一半。
vec4 czm_phong(vec3 toEye, czm_material material, vec3 lightDirectionEC)
{
float diffuse = czm_private_getLambertDiffuseOfMaterial(vec3(0.0, 0.0, 1.0), material);
if (czm_sceneMode == czm_sceneMode3D) {
diffuse += czm_private_getLambertDiffuseOfMaterial(vec3(0.0, 1.0, 0.0), material);
}float specular = czm_private_getSpecularOfMaterial(lightDirectionEC, toEye, material);
vec3 materialDiffuse = material.diffuse * 0.5;vec3 ambient = materialDiffuse;
vec3 color = ambient + material.emission;
color += materialDiffuse * diffuse * czm_lightColor;
color += material.specular * specular * czm_lightColor;return vec4(color, material.alpha);
}
2、渐变
渐变的产生大多需要借助纹理坐标,在webgl中,纹理的坐标系如下图所示,cesium纹理坐标设置大多都是完全覆盖面的。假如绘制了一个正方形,那左下角为纹理坐标的原点,右上角坐标则为(1,1)。不管实际上物体是正方形还是长方形还是什么形状,纹理坐标右上角就是(1,1)。利用好这一点就可以产生渐变效果了
纹理坐标我们可以从czm_materialInput.st中获取,其中s为横坐标,t为纵坐标。我们可以简单的将outColor的r值设置为s或者t观察效果。
uniform vec4 color;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
// outColor.r = st.s;
outColor.r = st.t;
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
左边将r值设置为st.s,右边则是st.t 。
我们当然也可以将alpha设置为纹理坐标的某个值
那怎么生成多个渐变,渐变的原理我们大概已经了解了,因为纹理坐标都是0-1的,而rgba四个值也都是0-1之间的,如果想要在横坐标生成3个渐变效果,是不是应该需要3个0-1之间的值,这怎么得到呢。很简单,我们只需要将st的s乘以3,那么s就在0-3之间波动了,这时候我们再取其小数部分,因为小数部分就是在 [0 - 1) 之间的,这样我们就得到三个0-1了。我们可以将需要生成的数量通过uniform来传递(不要忘记在js中传递uniform值)。
uniform vec4 color;
uniform float gradationNumber;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
// outColor.r = st.s;
// outColor.r = st.t;
// outColor.a = st.s;
// outColor.a = st.t;
outColor.a = fract(st.s * gradationNumber);
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
3、如何画圆
画圆的话我们可以先了解一下圆的定义:
在同一平面内到定点的距离等于定长的点的集合叫做圆
其中关键就是距离,但是距离取多少合适这谁知道。不要紧,因为我们有纹理坐标,纹理坐标范围是固定的,如果我们以(0.5,0.5)为圆心,那么与范围相切的圆的半径就是0.5,包含整个范围的圆的半径就是 。而距离的话我们知道webgl有个distance函数可以很方便的计算。
uniform vec4 color;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
vec2 origin = vec2(0.5, 0.5);
float dis = distance(origin, st);
if(dis <= 0.5) {
outColor.rgb = vec3(0.0, 1.0, 0.0);
}
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
稍微调整一下代码,将圆以外部分的透明度的调整为0
if(dis > 0.5){
outColor.a = 0.0;
}
ps:上图中左下角是椭圆是因为绘制的就是个长方形,因为虽然设置的长宽都是0.01度,但是因为不在赤道附近经纬度一度的长度差异有点大 。
4、如何画线
在材质的绘制中有时候也需要画线,例如绘制十字,圆线等等。实际上材质中是不能绘制线的,但是我们可以绘制一个长方体,让其一条边非常短,这样就类似线了。
通过uniform得到一个线宽(演示中设置的是0.02),然后如果在 (线的位置 - 线宽,线的位置 + 线宽)之间的话就将outColor换成线的颜色,这样就可以在材质中绘制一条线了。
uniform vec4 color;
uniform float lineWidth;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
// 绘制竖线
if(st.s > 0.5 - lineWidth / 2.0 && st.s < 0.5 + lineWidth / 2.0) {
outColor.rgb = vec3(0.0, 1.0, 0.0);
}
// 绘制横线
if(st.t > 0.5 - lineWidth / 2.0 && st.t < 0.5 + lineWidth / 2.0) {
outColor.rgb = vec3(0.0, 1.0, 0.0);
}
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
有了这个思路我们可很轻松的绘制圆形线
uniform vec4 color;
uniform float lineWidth;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
vec2 origin = vec2(0.5, 0.5);
float radius = 0.5;
float dis = distance(origin, st);
if(dis > radius - lineWidth / 2.0 && dis < radius + lineWidth / 2.0) {
outColor.rgb = vec3(0.0, 1.0, 0.0);
}
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
5、让线动起来
cesium中的材质动画离不开cesium的一个自动uniform,那就是czm_frameNumber,这个uniform表示当前处于cesium的第几帧绘制,从0开始,每次绘制cesium都会将其递增1。
我们现在假设cesium帧率为60,那么我们使用 czm_frameNumber / 60.0 。这样得到的结果如果只看整数部分那就是每秒钟递增1,只看小数部分那就是一直在 [0,1) 之间循环,循环时间为1秒。那假如我们将这个结果赋予alpha值,是不是就是物体透明的不断地从0-1变化,循环时间为1秒。那怎么通过这个让线动起来呢,我们知道纹理坐标的值也是0-1之间的,那如果我们将这个值赋予作为线的位置呢(我们之前线的位置为0.5),让我们试试看吧。
uniform vec4 color;
uniform float lineWidth;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float time = fract(czm_frameNumber / 144.0); // 在144帧之内从0-1不断循环的数字
float linePositionX = time; // 将其作为竖线的位置
if(st.s > linePositionX - lineWidth / 2.0 && st.s < linePositionX + lineWidth / 2.0) {
outColor.rgb = vec3(0.0, 1.0, 0.0);
}
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
ps:下图的第二个效果只是将st.s换成st.t即可,以后就不再说明了。
6、移动的渐变区域
cesium中很多好看的材质本质上都是一个移动的渐变区域,例如扫描、飞线、穿梭线、波纹等等、区别在于移动的方向、时间函数等具体实现细节,大致思路都基本是一样的。
这里我们继续沿用上一次的代码,首先我们可以将上一次代码中的lineWidth换成percent,因为不再是线而是区域了,并且起始位置不再是中心,区域所占的比例用percent表示,然后我们将percent设为0.4,然后再看看效果。
uniform vec4 color;
uniform float percent;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float time = fract(czm_frameNumber / 144.0);
float startPosition = time;
if(st.s > startPosition - percent / 2.0 && st.s < startPosition + percent / 2.0) {
outColor.rgb = vec3(0.0, 1.0, 0.0);
}
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
现在我们成功将一部分区域换成了另一个颜色,那么怎么换成渐变色呢。这时候就需要使用数学中的映射了(应该是叫映射),举个例子,例如现在0.2-0.6区域我们将其改为了绿色,那么我们就需要在此区域内,将0.2-0.6转为0-1,再将转化后的0-1生成出渐变。
我们先将outColor的a值设为0。我们知道的是在if判断中,st.s的值就是我们需要映射为0-1的值,我们先来进行一下线性映射。原始的边界值我们知道是 (startPosition - percent,startPosition),我们需要映射为(0,1);原始值是st.s,我们设为x,映射后的值我们设为y。那么就有 x - (startPosition - percent) / startPosition - (startPosition - percent) = y - 0 / 1 - 0。结果就是 y = x - startPosition +percent / percent 。其中x就是st.s,我们将其转为代码试试。
uniform vec4 color;
uniform float percent;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float time = fract(czm_frameNumber / 144.0);
float startPosition = time;
if(st.s > startPosition - percent && st.s < startPosition) {
float value = (st.s - (startPosition - percent)) / percent;
outColor.rgb = vec3(0.0, value, 0.0);
}
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
可以看到我们已经成功的生成出了渐变,绿色值由0-1。
我们稍微调整一下效果,让默认alpha为0,区间内alpha由0-1递增。有了透明部分不要忘记将translucent设置为true
uniform vec4 color;
uniform float percent;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float time = fract(czm_frameNumber / 144.0);
float startPosition = time;
outColor.a = 0.0; // 透明度默认为0
if(st.s > startPosition - percent && st.s < startPosition) {
float value = (st.s - (startPosition - percent)) / percent;
outColor.a = value;
}
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
上面我们是自己将区域内的纹理值映射到0-1之间的,这样的映射也是一种线性的映射。webgl中就有这种功能的函数,smoothstep,并且该函数的映射是非线性的,我们可以尝试一下。
uniform vec4 color;
uniform float percent;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float time = fract(czm_frameNumber / 144.0);
float startPosition = time;
outColor.a = 0.0;
if(st.s > startPosition - percent && st.s < startPosition) {
// 使用smoothstep替换原本的线性映射
float value = smoothstep(startPosition - percent, startPosition, st.s);
outColor.a = value;
}
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
左边为smoothstep的结果,右边是原本的结果,肉眼看起来好像没有多大的差别。
五、引申
1.减少条件语句的使用
shader中条件语句有可能会导致效率的降低,具体情况大家可以百度了解,这里就不再说明。可以的情况下我们最好将条件语句转为使用step等
uniform vec4 color;
uniform float percent;
czm_material czm_getMaterial(czm_materialInput materialInput) {
vec4 outColor = color;
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float time = fract(czm_frameNumber / 144.0);
float startPosition = time;
// 将if判断取消,转而使用step函数
float value = smoothstep(startPosition - percent, startPosition, st.s) * step(-time, -st.s);
outColor.a = value;
material.diffuse = czm_gammaCorrect(outColor.rgb);
material.alpha = outColor.a;
return material;
}
这里将4.6最后的代码搬来,取消掉其中的if判断,转而将结果 乘以一个 step(-time, -st.s) ,因为我们需要不在区间内的其他地方alpha值为0.0,如果小于区间smoothstep函数回返回0的,但是大于区间的地方还是1。而乘上的那个 step(-time, -st.s) 在大于区间的地方值为0,这样也就将区间外的地方透明的变为了0。
2.非线性动画
上面的动画的时间函数都是线性的,如何做到非线性的变换,例如先缓慢再迅速再缓缓,就如同CSS中过渡的ease-in-out一样。这里我只提供思路,我们知道time是0-1之间的,我们可以将其作为x,然后我们就可以使用我们想要的时间函数,也就是 y = f(x) 。如果y的值域不在0-1之间,那我们就将其映射到0-1之间即可。例如我们可以让 time = pow(time, 2.0),结果同样在0-1之间,这样得到的time就不再是线性的了,动画也就有了速率上的变换。例如下面的gif图片就是 time = pow(time ,4.0) 后的结果,与上面4.6的结果对比可以明显的看到先慢后快的。
3.精确控制动画时间
上面的代码time都来自czm_frameNumber / 144.0的小数部分,如果想要控制速率呢,这也十分简单。我们可以声明一个float类型的统一值,名字叫speed,用其乘以czm_frameNumber即可,然后后面的分母我们可以调大些,比如10000。
float time = fract(czm_frameNumber * speed / 10000.0);
这样我们就可以通过控制speed来控制动画的速率。
那如果想精确的控制动画的时间呢,这就不是传递一个uniform可以办到的了。思路就是:我们在每一次绘制时传递时间差值,就和frameNumber类似,从0开始增加,每次绘制传递距离第一次绘制经过的毫秒数,然后用这个来精确控制动画的时间。传递的方式就是通过requestAnimationFrame不断的改变创建的Material的uniform。
六、总结
文章只是提供从0开始的简单的材质实现,讲到这也该结束了,虽然还有很多效果我没有讲,例如圆形的扩散、扇形的扫描效果、线的交替等等,但是那都可以在我讲了的效果的基础上延申出去,而且也十分简单。
最后如果本文章对你有帮助不要忘了点赞收藏哦。
更多推荐
所有评论(0)