用2Dshader做一个3D地球仪

最终效果如下,不知道过程有没有错误但是效果算是出来了
屏幕录制2023-02-22-下午4.39

起因是看到ps有一个叫球面扭曲的功能,就有了个无端联想看看能不能再creator 2.x内做一些简单的3D视觉效果,试着能不能做成地球仪

首先要在creator上复刻ps的球面化,可以通过扭曲uv的方法实现
image

//球面化扭曲uv
    vec2 centered_uv = v_uv0 * 2.0 - 1.0;
    float z = sqrt(1.0 - clamp(dot(centered_uv, centered_uv),0.0,1.0));
    vec2 spherified_uv = centered_uv / (z + 1.0);
    vec2 uv = spherified_uv * 0.5 + 0.5;
    vec4 testcol = texture(texture, uv);
    gl_FragColor = testcol; 

之后要把球体外的部分抹除掉,也就是说把那部分的A值置0这样我们就可以得到一个完整的球体
image

//抹除超出球体部分的A值
    float hight = abs(v_uv0.y - 0.5); //高度
    float width = sqrt(0.25 - hight * hight); //根据高度和半径算出宽度
    float isInBall = smoothstep(abs(v_uv0.x - 0.5),abs(v_uv0.x - 0.5)+0.01,width); //判断是否处于球体部分
    testcol.a *= isInBall;

这里smoothstep最早是用step函数的,效果如下
image
如果换成smoothstep能起到一定程度的抗锯齿效果

但是,这个球体和常见的地球仪还是有很大的区别,经线和经纬球相似,但是纬线要用另外的方法模拟
单用球面的横坐标,纵坐标继续使用v_uv0.y的话效果如下
image
把纵坐标按球面的坐标进行扭曲,效果如下
image
横坐标的处理就简单了,只要进行简单的缩放,加上一个cc_time.x就能实现简单的自转效果

//uv缩放修改
    uv.y = asin((v_uv0.y-0.5)/0.5)/3.1415926 + 0.5; //通过v_uv0.y的反三角函数求得垂直方向上的偏移
    uv.x *= 0.8; //按道理应该是0.5,不知道是不是图片素材的原因,填0.8才像现实中的地球仪
    float speed = 0.2; //自转速度
    uv.x += cc_time.x * speed; //转起来
    vec4 testcol = texture(texture, uv); 

这时可以把球面化uv的xy以红绿色表现出来,会发现这里的表现和球体的法线图十分相似,于是我有了一个大胆的想法
image

//输出uv值
    vec4 testcol = vec4(spherified_uv.x*0.5+0.5,spherified_uv.y*0.5+0.5,0.0,1.0); //spherified_uv的取值范围是-1~1 这里要把他换算成0~1方便计算

尝试性的把法线方向,光线方向,视角方向写出来,就可以把高光和漫反射算出来了,就可以实现布林冯模型了
屏幕录制2023-02-22-下午6.41

//输出uv值
    vec3 normalDir = normalize(vec3(spherified_uv.x,spherified_uv.y,1.0)); //根据球面化扭曲推出的法线方向
    float randomValue = sin(cc_time.x);
    float randomValue1 = sin(cc_time.x*0.8);
    vec3 lightDir = normalize(vec3(randomValue,-randomValue1,1.0)); //随机生成的平行光,方向大致是往屏幕里面射
    vec3 viewDir = normalize(vec3(0.0,0.0,1.0)); //2D环境,视角方向自然是垂直屏幕的
    vec3 halfDir = normalize(viewDir + lightDir); //半角方向,方便求高光效果
    vec4 diffusCol = vec4(0.0,0.0,0.0,1.0);
    float diffusValue = clamp(dot(normalDir,lightDir),0.0,1.0); //点乘n和l的方向,求出漫反射部分
    float smoothness = 500.0; //通过这个可以控制光晕大小
    float specularValue = pow(clamp(dot(normalDir,halfDir),0.0,1.0),smoothness); //点乘n和h的方向再来n次方,求出高光部分
    col.rgb *= diffusValue;//求出漫反射采样效果,col就是上文求出的testcol
    vec4 highLightCol = vec4(specularValue,specularValue,specularValue,1.0);//高光效果,默认白光。需要调整高光颜色可以乘一个rgb值
    highLightCol.a *= isInBall; //防止高光溢出球体
    vec4 ambient = vec4(0.0,0.0,0.0,1.0); //这里是环境光,默认纯黑,写出来意思意思
    ambient.a *= isInBall; //防止环境光溢出球体
    vec4 finCol = col + highLightCol + ambient; //高光漫反射环境光混合输出
    gl_FragColor = finCol; 

既然都已经做出伪3D效果了 不如再把法线贴图弄进去吧
image

//输出uv值
    vec4 normalCol = texture(normalTexture, uv);//根据修改后的uv采样发现贴图
    //vec3 normalDir = normalize(vec3(spherified_uv.x,spherified_uv.y,1.0)); 
    vec3 normalDir = normalize((vec3(spherified_uv.x,spherified_uv.y,1.0)+(normalize(normalCol.rgb)*2.0-1.0) /2.0);//乘2减1是为了让法线大小从0~1变为-1~1(主要是xy方向)

好了这样就能显示了
屏幕录制2023-02-22-下午4.39

15赞

帅!感觉可以用在控制移动轮盘上和地图对应。

前排围观。

挖槽,牛皮

牛皮,但是不建议做,因为我不会,就显得我很呆很瓜皮

牛皮,但是不建议做,因为我不会,就显得我很呆很瓜皮

除了牛逼我也不知道说啥了

牛皮,但是不建议做,因为我不会,就显得我很呆很瓜皮

你的地球仪的实现看起来很不错!我有一些小建议和优化可以参考一下:

  1. 球面化扭曲UV的计算

目前使用的球面化扭曲UV的方法是很标准的,但可以尝试使用其他算法来计算这些坐标,例如球体UV贴图,这可以在网上找到。该方法的好处是可以更快地计算扭曲的UV坐标,但可能需要您制作一个UV贴图。

对于球面化扭曲uv的部分,可以使用另外一种更高效的实现方式

vec2 uv;
uv.x = atan(centered_uv.x, centered_uv.y) / 3.1415926 * 0.5 + 0.5;
uv.y = asin(centered_uv.z) / 3.1415926 + 0.5;

这种方式使用了三角函数来进行计算,但是比起之前的方式更加高效

  1. 抹除球体外部分的计算

您目前使用的方法是通过计算高度和宽度来确定每个像素是否在球体内。这种方法效率很低,因为它需要在每个像素上进行计算。相反,您可以在渲染地球仪之前对纹理进行剪裁,以便只渲染球体内部的像素。

对于抹除超出球体部分的A值的部分,可以不使用smoothstep函数,而是直接使用clamp函数来实现:

float width = sqrt(0.25 - hight * hight);
float isInBall = step(width, abs(v_uv0.x - 0.5));
testcol.a *= isInBall;

这样可以避免使用smoothstep函数时可能出现的锯齿问题,同时也可以提高效率。

  1. 光照计算

您目前使用的模型是一个很好的起点,但可以尝试使用其他光照模型,例如基于物理的渲染(PBR)。PBR模型可以更好地模拟真实光照,但可能需要一些额外的计算。

  1. 自转计算

您目前使用的自转方法是简单有效的。但是,如果您希望地球仪的自转看起来更平稳,您可以尝试使用更平滑的插值方法,例如球形插值。

对于纬线的模拟,可以使用一个更加精确的计算方式:

float phi = atan(centered_uv.y, centered_uv.x);
float r = length(centered_uv);
float theta = atan(2.0 * r, 2.0 * sqrt(2.0));
vec2 spherified_uv;
spherified_uv.x = 1.0 - (theta / 3.1415926);
spherified_uv.y = phi / (2.0 * 3.1415926) + 0.5;

这种方式可以更加准确地模拟纬线的形状

这就是 和 除了只会喊牛皮的我的 区别么

谢谢建议 我会尝试一下优化的~

无意冒犯,感觉像是chatgpt回复 :joy:

mark!