30 KiB
二、shadertoy着色器基本结构
shadertoy上的shader是纯2d绘图,没有几何顶点这些概念,它的绘图方式和canvas绘图方式很像,它将整个canvas作为绘图的画布,所以输入参数fragCoord的x值范围是(0,画布的宽度),fragCoord的y值范围是(0,画布的高度),画布的宽高在定义的输入参数中是iResolution,所以fragCoord.x范围就是(0,iResolution.x),fragCoord.y范围就是(0,iResolution.y)。
如图所示,shadertoy上的shader示例最基本的着色器结构主要包括两个部分:
a、输入参数的定义
b、着色器的入口函数
1、输入参数,通过uniform来定义外部的输入值。
uniform vec3 iResolution; // 视口分辨率,即画布的宽高
uniform float iTime; // shader 的运行时间 秒
uniform float iTimeDelta; // 渲染时间 秒
uniform float iFrameRate; // shader 帧率
uniform int iFrame; // shader 帧率
uniform float iChannelTime[4]; // 频道运行时间 不管
uniform vec3 iChannelResolution[4]; // 频道分辨率 不管
uniform vec4 iMouse; // 鼠标坐标
uniform samplerXX iChannel0..3; // 输入的纹理 比如我们从一张图片上采用颜色
uniform vec4 iDate; // 日期 年月日 不管
uniform float iSampleRate; // 声音采样 不管
2、入口函数mainImage
void mainImage(out vec4 fragColor, in vec2 fragCoord )
{
fragColor = vec4(1.);
}
方法的第一个参数fragColor,是一个vec4类型的变量,表示最后输出的颜色值。
方法的第二个参数fragCoord,是一个vec2类型的变量,表示输入的像素坐标。
三、在cesium中如何使用
shadertoy上的着色器是在一个canvas画布上进行工作的,要移植到Cesium中,我们需要找一个载体来替代canvas。我们知道,Cesium绘制几何图形可以通过Entity和Primitive两种方式,那么只有Primitive+Appearance比较合适了,关于Primitive的使用及介绍,可以观看前面的章节。要将着色器移植到Cesium中,我们先来重点看看shadertoy上的shader着色器需要用到的参数。以下面这个例子讲解https://www.shadertoy.com/view/XdlSDs
glsl代码:
void mainImage(out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord.xy-iResolution.xy)/iResolution.y;
float tau = 3.1415926535*2.0;
float a = atan(p.x,p.y);
float r = length(p)*0.75;
vec2 uv = vec2(a/tau,r);
//get the color
float xCol = (uv.x - (iTime / 3.0)) * 3.0;
xCol = mod(xCol, 3.0);
vec3 horColour = vec3(0.25, 0.25, 0.25);
if (xCol < 1.0) {
horColour.r += 1.0 - xCol;
horColour.g += xCol;
}
else if (xCol < 2.0) {
xCol -= 1.0;
horColour.g += 1.0 - xCol;
horColour.b += xCol;
}
else {
xCol -= 2.0;
horColour.b += 1.0 - xCol;
horColour.r += xCol;
}
// draw color beam
uv = (2.0 * uv) - 1.0;
float beamWidth = (0.7+0.5*cos(uv.x*10.0*tau*0.15*clamp(floor(5.0 + 10.0*cos(iTime)), 0.0, 10.0))) * abs(1.0 / (30.0 * uv.y));
vec3 horBeam = vec3(beamWidth);
fragColor = vec4((( horBeam) * horColour), 1.0);
}
a、首先是fragCoord,前面介绍过,fragCoord表示当前处理的像素坐标,是一个vec2类型,fragCoord.x范围为(0,画布宽度),fragCoord.y范围为(0,画布高度)。
b、其次是iResolution,iResolution代表的是当前画布的宽高,即绘图区域的尺寸,所以fragCoord.x范围就是(0,iResolution.x),fragCoord.y范围就是(0,iResolution.y)。
c、然后我们还在代码中看到有个iTime,该参数代表当前运行的时间,一般用来实现动画,因为您会发现大多数shader的效果都是动态的。
d、最后是输出结果fragColor,代表最后计算的颜色输出值,在Cesium中为out_FragColor。
接下来介绍在Cesium如何获取对应的参数:
a、fragCoord在Cesium有个gl_FragCoord与之对应,这是一个WebGL内置的变量。
b、iResolution在Cesium有个czm_viewport与之对应,不过使用时采用zw分量即 czm_viewport.zw
c、iTime在Cesium中没有对应的变量,我们可以通过变量的方式传递一个参数,然后在渲染时不断修改该值,不过这种方式略显麻烦,在Cesium中我们可以用float iTime=czm_frameNumber/100.来模拟,czm_frameNumber代表当前帧,是一个自增长的数值,所以可以用来模拟时间不断地增长。
接下来我们实操一下,在Cesium中实现该shader的效果:
1、首先我们创建一个Primitive并添加到sene中
let xMin = 115.894604, yMin = 39.516896, xMax = 117.431959, yMax = 40.630521;
let rect = new Cesium.Rectangle(Cesium.Math.toRadians(xMin), Cesium.Math.toRadians(yMin), Cesium.Math.toRadians(xMax), Cesium.Math.toRadians(yMax));
const rectangle = new Cesium.RectangleGeometry({
rectangle: rect,
height: 10000.0,
});
const geometry = Cesium.RectangleGeometry.createGeometry(rectangle);
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: geometry
}),
}));
2、此时会发现什么也看不见,这是因为没有设置外观,我们创建一个默认的外观
let appearance = new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.RED
}
}
}),
})
primitive.appearance = appearance;
3、接下来我们为外观添加片元着色器,并将shadertoy上的shader赋值给外观的片元着色器属性。
shadertoy上glsl代码:
fragmentShaderSource: `
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord.xy-iResolution.xy)/iResolution.y;
float tau = 3.1415926535*2.0;
float a = atan(p.x,p.y);
float r = length(p)*0.75;
vec2 uv = vec2(a/tau,r);
//get the color
float xCol = (uv.x - (iTime / 3.0)) * 3.0;
xCol = mod(xCol, 3.0);
vec3 horColour = vec3(0.25, 0.25, 0.25);
if (xCol < 1.0) {
horColour.r += 1.0 - xCol;
horColour.g += xCol;
}
else if (xCol < 2.0) {
xCol -= 1.0;
horColour.g += 1.0 - xCol;
horColour.b += xCol;
}
else {
xCol -= 2.0;
horColour.b += 1.0 - xCol;
horColour.r += xCol;
}
// draw color beam
uv = (2.0 * uv) - 1.0;
float beamWidth = (0.7+0.5*cos(uv.x*10.0*tau*0.15*clamp(floor(5.0 + 10.0*cos(iTime)), 0.0, 10.0))) * abs(1.0 / (30.0 * uv.y));
vec3 horBeam = vec3(beamWidth);
fragColor = vec4((( horBeam) * horColour), 1.0);
}
`
操作步骤:
a、首先我们需要修改着色器入口函数,即将mainImage修改为main,因为Appearance片元着色器的入口函数是main。
b、然后将输出结果的fragColor修改为out_FragColor。
c、最后我们按照上面说的替换掉fragCoord,fragCoord、iTime
我们需要的代码:
fragmentShaderSource: `
void main()
{
float iTime=czm_frameNumber/100.;
vec2 p = (2.0 * gl_FragCoord.xy-czm_viewport.zw)/czm_viewport.w;
float tau = 3.1415926535*2.0;
float a = atan(p.x,p.y);
float r = length(p)*0.75;
vec2 uv = vec2(a/tau,r);
//get the color
float xCol = (uv.x - (iTime / 3.0)) * 3.0;
xCol = mod(xCol, 3.0);
vec3 horColour = vec3(0.25, 0.25, 0.25);
if (xCol < 1.0) {
horColour.r += 1.0 - xCol;
horColour.g += xCol;
}
else if (xCol < 2.0) {
xCol -= 1.0;
horColour.g += 1.0 - xCol;
horColour.b += xCol;
}
else {
xCol -= 2.0;
horColour.b += 1.0 - xCol;
horColour.r += xCol;
}
// draw color beam
uv = (2.0 * uv) - 1.0;
float beamWidth = (0.7+0.5*cos(uv.x*10.0*tau*0.15*clamp(floor(5.0 + 10.0*cos(iTime)), 0.0, 10.0))) * abs(1.0 / (30.0 * uv.y));
vec3 horBeam = vec3(beamWidth);
out_FragColor = vec4((( horBeam) * horColour), 1.0);
}
`
但是移动地球我们会发现绘制的结果始终是在屏幕中心,这其实是一个正确的结果,因为这个示例在shadertoy上也是始终绘制在屏幕中心。现在我们需要将Primitive的几何体作为绘制的画布,上面我们是使用gl_FragCoord坐标来获取当前应该处理的像素,我们现在需要改变为Appearance的纹理坐标,通过Appearence的纹理坐标,计算当前Appearence上需要处理的像素。
vec2 p = (2.0 * gl_FragCoord.xy-czm_viewport.zw)/czm_viewport.w;
该行代码的意思是将绘图区的宽高转换到[-1,1]的一个区间中。而在Appearance的片元着色器中,相对于该Primitive对应的Geometry而言,绘图区的宽高已经被限制在了[0,1]的区间了,这可以由片元着色器的st推断,因为片元着色器的st一般就是[0,1]。现在我们改造一下代码,将绘图区限定到Appearance的纹理区间中
vec2 p = 2.0 * v_st - 1.;//(2.0*fragCoord.xy-iResolution.xy)/iResolution.y;
因为v_st区间是[0,1],所以我们的变换一下到[-1,1],运行结果如下
到这里我们已经成功将shadertoy上这个示例的shader移植到Cesium上的Primitve中
四、着色器使用技巧
上面示例的shader成功移植到Cesium中的Primitive上。分享2个在使用shadertoy上的shader时的技巧:
1、在Cesium中如何选择对应的载体作为画布
因为shadertoy上的shader类型canvas的绘制原理,是将canvas作为一个画布,在Cesium中我们可以选择Entity或Primtive来作为载体。又因为要方便操作片元着色器,所以我们选择了Primitive,但是Primitive中又有很多Geometry类型,那具体使用哪种Geometry呢?根据经验,最好选择像RectangleGeometry、PlaneGeomery这种比较规则的几何类型,因为shader绘图其实是根据纹理坐标来实现的,而这种规则的几何往往 它的纹理坐标也比较规则。
2、如何去除黑色背景
虽然我们已经成功将shader移植到Cesium中,但是黑色的背景着实有点丑,我们使用shader的初衷是为了好看、酷炫,这效果好像有点违背了我们的初衷。那我们该如何去除这个黑色的背景呢?我们可以分析一下这个黑色,其实黑色的值就是vec3(0,0,0),rgb三个分量越接近于0,就越黑,当然,这也代表着r+g+b约接近0,所以我们可以这样去消除黑色背景。
out_FragColor = vec4(color,color.r+color.g+color.g);
这样就可以了。
五、难度加深,带有输入纹理数据的着色器示例(应用)
实现一个shader上带有输入纹理数据的例子
按照 “在Cesium中如何使用” 一节的思路,我们先创建好Primitive、Appearance,然后将着色器代码拷贝进来并修改相关参数。
let xMin = 115.894604, yMin = 39.516896, xMax = 117.431959, yMax = 40.630521;
const rectangle = new Cesium.RectangleGeometry({
rectangle: rect,
height: 10000.0,
});
const geometry = Cesium.RectangleGeometry.createGeometry(rectangle);
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: geometry
}),
appearance: new Cesium.MaterialAppearance({
material:new Cesium.Material({
fabric: {
uniforms: {
image:"./texture.png"
}
}
})
,
fragmentShaderSource: `
in vec3 v_positionEC;
in vec3 v_normalEC;
in vec2 v_st;
// Maximum number of cells a ripple can cross.
#define MAX_RADIUS 2
// Set to 1 to hash twice. Slower, but less patterns.
#define DOUBLE_HASH 0
// Hash functions shamefully stolen from:
// https://www.shadertoy.com/view/4djSRW
#define HASHSCALE1 .1031
#define HASHSCALE3 vec3(.1031, .1030, .0973)
float hash12(vec2 p)
{
vec3 p3 = fract(vec3(p.xyx) * HASHSCALE1);
p3 += dot(p3, p3.yzx + 19.19);
return fract((p3.x + p3.y) * p3.z);
}
vec2 hash22(vec2 p)
{
vec3 p3 = fract(vec3(p.xyx) * HASHSCALE3);
p3 += dot(p3, p3.yzx+19.19);
return fract((p3.xx+p3.yz)*p3.zy);
}
void main( )
{
//float resolution = 10. * exp2(-3.*iMouse.x/iResolution.x);
// vec2 uv = fragCoord.xy / iResolution.y * resolution;
float resolution =20.;
vec2 uv = v_st * resolution;
vec2 p0 = floor(uv);
float iTime=czm_frameNumber/100.;
vec2 circles = vec2(0.);
for (int j = -MAX_RADIUS; j <= MAX_RADIUS; ++j)
{
for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i)
{
vec2 pi = p0 + vec2(i, j);
#if DOUBLE_HASH
vec2 hsh = hash22(pi);
#else
vec2 hsh = pi;
#endif
vec2 p = pi + hash22(hsh);
float t = fract(0.3*iTime + hash12(hsh));
vec2 v = p - uv;
float d = length(v) - (float(MAX_RADIUS) + 1.)*t;
float h = 1e-3;
float d1 = d - h;
float d2 = d + h;
float p1 = sin(31.*d1) * smoothstep(-0.6, -0.3, d1) * smoothstep(0., -0.3, d1);
float p2 = sin(31.*d2) * smoothstep(-0.6, -0.3, d2) * smoothstep(0., -0.3, d2);
circles += 0.5 * normalize(v) * ((p2 - p1) / (2. * h) * (1. - t) * (1. - t));
}
}
circles /= float((MAX_RADIUS*2+1)*(MAX_RADIUS*2+1));
float intensity = mix(0.01, 0.15, smoothstep(0.1, 0.6, abs(fract(0.05*iTime + 0.5)*2.-1.)));
vec3 n = vec3(circles, sqrt(1. - dot(circles, circles)));
vec3 color = texture(image_0, uv/resolution - intensity*n.xy).rgb + 5.*pow(clamp(dot(n, normalize(vec3(1., 0.7, 0.5))), 0., 1.), 6.);
out_FragColor = vec4(color, 1.0);
}
`
})
}));
分析代码:
float resolution = 10. * exp2(-3.*iMouse.x/iResolution.x);
vec2 uv = fragCoord.xy / iResolution.y * resolution;
意思是将uv转换到[0,x]的区间,在Cesium中应该这样写,我们先固定一个值
float resolution =20.;
vec2 uv = v_st * resolution;
本节的重点是
vec3 color = texture(iChannel0, uv/resolution - intensity*n.xy).rgb + 5.*pow(clamp(dot(n,normalize(vec3(1., 0.7, 0.5))), 0., 1.), 6.);
texture(iChannel0, uv/resolution - intensity*n.xy).rgb
从纹理中采样颜色值,这个iChannel0是sampler2D,在Cesium对应Cesium.Texture,不过我们可以直接传一张图片,Cesium会自动为我们封装成Cesium.Texture。将图片传递到着色器,这里我们采用Materail的属性uniforms,需要注意的是如果您在Appearance的片元着色器中使用Material中的uniforms参数值时,你必须在参数名的后面加上一个序号,比如你的第一个参数为在Material的uniforms中为image,那在Appearance的片元着色器中必须使用image_0,如下:
texture(image_0, uv/resolution - intensity*n.xy).rgb
六、Perlin-Worley噪声实现云图
shadertoy示例地址:https://www.shadertoy.com/view/3dVXDc
该示例使用了Perlin+Worley(柏林+沃利)噪声来实现云的效果,并且是先将结果绘制到一张Texture中,然后在主函数中根据纹理坐标读取纹理中的值。
1、首先是噪声函数,定义在Common中
4、主函数中为了显示了各个噪声的结果,将画布拆分为了5份,我们可以注释掉一些不需要的结果显示,比如修改代码如下:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 st = fragCoord / iResolution.xy;
vec2 uv = fragCoord / iResolution.y;
st.x *= 5.; // 5 columns for different noises
uv -= .02 * iTime;
vec3 col = vec3(0.);
float perlinWorley = textureLod(iChannel0, uv * .5, 0.).x;
// worley fbms with different frequencies
vec3 worley = textureLod(iChannel0, uv, 0.).yzw;
float wfbm = worley.x * .625 +
worley.y * .125 +
worley.z * .25;
// cloud shape modeled after the GPU Pro 7 chapter
float cloud = remap(perlinWorley, wfbm - 1., 1., 0., 1.);
cloud = remap(cloud, .85, 1., 0., 1.); // fake cloud coverage
// if (st.x < 1.)
// col += perlinWorley;
// else if(st.x < 2.)
// col += worley.x;
// else if(st.x < 3.)
// col += worley.y;
// else if(st.x < 4.)
// col += worley.z;
// else if(st.x < 5.)
col += cloud;
// column dividers
// float div = smoothstep(.01, 0., abs(st.x - 1.));
// div += smoothstep(.01, 0., abs(st.x - 2.));
// div += smoothstep(.01, 0., abs(st.x - 3.));
// div += smoothstep(.01, 0., abs(st.x - 4.));
// col = mix(col, vec3(0., 0., .866), div);
fragColor = vec4(col,1.0);
}
因为我们不需要绘制到Texture的步骤,所以需要合并BufferA和主函数的代码
uv -= .02 * iTime;
vec2 m = vec2(0.5);
vec4 col = vec4(0.);
float slices = 128.; // number of layers of the 3d texture
float freq = 4.;
float pfbm= mix(1., perlinfbm(vec3(uv, floor(m.y*slices)/slices), 4., 7), .5);
pfbm = abs(pfbm * 2. - 1.); // billowy perlin noise
col.g += worleyFbm(vec3(uv, floor(m.y*slices)/slices), freq);
col.b += worleyFbm(vec3(uv, floor(m.y*slices)/slices), freq*2.);
col.a += worleyFbm(vec3(uv, floor(m.y*slices)/slices), freq*4.);
col.r += remap(pfbm, 0., 1., col.g, 1.); // perlin-worley
float perlinWorley = col.r;
// worley fbms with different frequencies
vec3 worley = col.yzw;
float wfbm = worley.x * .625 +
worley.y * .125 +
worley.z * .25;
// cloud shape modeled after the GPU Pro 7 chapter
float cloud = remap(perlinWorley, wfbm - 1., 1., 0., 1.);
cloud = remap(cloud, .65, 1., 0., 1.); // fake cloud coverage
vec3 col_ = vec3(0.);
col_ += cloud;
5、在cesium中如何实现:我们先创建好Primitive、Appearance,然后将着色器代码拷贝进来并修改相关参数
let xMin = 115.894604, yMin = 39.516896, xMax = 117.431959, yMax = 40.630521;
const rectangle = new Cesium.RectangleGeometry({
rectangle: rect,
height: 10000.0,
});
const geometry = Cesium.RectangleGeometry.createGeometry(rectangle);
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: geometry
}),
appearance: new Cesium.MaterialAppearance({
material:new Cesium.Material({
fabric: {
uniforms: {
image:"./texture.png"
}
}
})
,
fragmentShaderSource: `
in vec3 v_positionEC;//顶点 相机(眼睛)坐标系
in vec3 v_normalEC;//顶点法线 相机(眼睛)坐标系
in vec2 v_st;//纹理坐标 uv
/**
This tab contains all the necessary noise functions required to model a cloud shape.
*/
// Hash by David_Hoskins
#define UI0 1597334673U
#define UI1 3812015801U
#define UI2 uvec2(UI0, UI1)
#define UI3 uvec3(UI0, UI1, 2798796415U)
#define UIF (1.0 / float(0xffffffffU))
vec3 hash33(vec3 p)
{
uvec3 q = uvec3(ivec3(p)) * UI3;
q = (q.x ^ q.y ^ q.z)*UI3;
return -1. + 2. * vec3(q) * UIF;
}
float remap(float x, float a, float b, float c, float d)
{
return (((x - a) / (b - a)) * (d - c)) + c;
}
// Gradient noise by iq (modified to be tileable)
float gradientNoise(vec3 x, float freq)
{
// grid
vec3 p = floor(x);
vec3 w = fract(x);
// quintic interpolant
vec3 u = w * w * w * (w * (w * 6. - 15.) + 10.);
// gradients
vec3 ga = hash33(mod(p + vec3(0., 0., 0.), freq));
vec3 gb = hash33(mod(p + vec3(1., 0., 0.), freq));
vec3 gc = hash33(mod(p + vec3(0., 1., 0.), freq));
vec3 gd = hash33(mod(p + vec3(1., 1., 0.), freq));
vec3 ge = hash33(mod(p + vec3(0., 0., 1.), freq));
vec3 gf = hash33(mod(p + vec3(1., 0., 1.), freq));
vec3 gg = hash33(mod(p + vec3(0., 1., 1.), freq));
vec3 gh = hash33(mod(p + vec3(1., 1., 1.), freq));
// projections
float va = dot(ga, w - vec3(0., 0., 0.));
float vb = dot(gb, w - vec3(1., 0., 0.));
float vc = dot(gc, w - vec3(0., 1., 0.));
float vd = dot(gd, w - vec3(1., 1., 0.));
float ve = dot(ge, w - vec3(0., 0., 1.));
float vf = dot(gf, w - vec3(1., 0., 1.));
float vg = dot(gg, w - vec3(0., 1., 1.));
float vh = dot(gh, w - vec3(1., 1., 1.));
// interpolation
return va +
u.x * (vb - va) +
u.y * (vc - va) +
u.z * (ve - va) +
u.x * u.y * (va - vb - vc + vd) +
u.y * u.z * (va - vc - ve + vg) +
u.z * u.x * (va - vb - ve + vf) +
u.x * u.y * u.z * (-va + vb + vc - vd + ve - vf - vg + vh);
}
// Tileable 3D worley noise
float worleyNoise(vec3 uv, float freq)
{
vec3 id = floor(uv);
vec3 p = fract(uv);
float minDist = 10000.;
for (float x = -1.; x <= 1.; ++x)
{
for(float y = -1.; y <= 1.; ++y)
{
for(float z = -1.; z <= 1.; ++z)
{
vec3 offset = vec3(x, y, z);
vec3 h = hash33(mod(id + offset, vec3(freq))) * .5 + .5;
h += offset;
vec3 d = p - h;
minDist = min(minDist, dot(d, d));
}
}
}
// inverted worley noise
return 1. - minDist;
}
// Fbm for Perlin noise based on iq's blog
float perlinfbm(vec3 p, float freq, int octaves)
{
float G = exp2(-.85);
float amp = 1.;
float noise = 0.;
for (int i = 0; i < octaves; ++i)
{
noise += amp * gradientNoise(p * freq, freq);
freq *= 2.;
amp *= G;
}
return noise;
}
// Tileable Worley fbm inspired by Andrew Schneider's Real-Time Volumetric Cloudscapes
// chapter in GPU Pro 7.
float worleyFbm(vec3 p, float freq)
{
return worleyNoise(p*freq, freq) * .625 +
worleyNoise(p*freq*2., freq*2.) * .25 +
worleyNoise(p*freq*4., freq*4.) * .125;
}
void main( )
{
vec2 uv = v_st;
float iTime=czm_frameNumber/100.;
uv -= .02 * iTime;
vec2 m = vec2(0.5);
vec4 col = vec4(0.);
float slices = 128.; // number of layers of the 3d texture
float freq = 4.;
float pfbm= mix(1., perlinfbm(vec3(uv, floor(m.y*slices)/slices), 4., 7), .5);
pfbm = abs(pfbm * 2. - 1.); // billowy perlin noise
col.g += worleyFbm(vec3(uv, floor(m.y*slices)/slices), freq);
col.b += worleyFbm(vec3(uv, floor(m.y*slices)/slices), freq*2.);
col.a += worleyFbm(vec3(uv, floor(m.y*slices)/slices), freq*4.);
col.r += remap(pfbm, 0., 1., col.g, 1.); // perlin-worley
float perlinWorley = col.r;
// worley fbms with different frequencies
vec3 worley = col.yzw;
float wfbm = worley.x * .625 +
worley.y * .125 +
worley.z * .25;
// cloud shape modeled after the GPU Pro 7 chapter
float cloud = remap(perlinWorley, wfbm - 1., 1., 0., 1.);
cloud = remap(cloud, .65, 1., 0., 1.); // fake cloud coverage
vec3 col_ = vec3(0.);
col_ += cloud;
out_FragColor = vec4(col_,col.r); `
})
}));
七、流体水面
示例地址:https://www.shadertoy.com/view/4dd3Rl
取到代码在cesium中如何使用参考上面体积云的示例,步骤相同。
<link href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/editerView/markdown_views-f23dff6052.css" rel="stylesheet">
<link href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/style-e504d6a974.css" rel="stylesheet">