RM-F代码解析

本文详细解析了一个ShaderToy示例,该示例使用GLSL实现了一种3D髓质效果,带有带状条纹的球体。通过射线追踪、球体相交测试、平面相交测试等技术,结合分形图案和自定义纹理运算,创造出具有情绪氛围的云层和球体内部结构。代码中包括了球体内外部的渲染、菲涅尔效应、环境光遮蔽、软阴影等效果,呈现出独特的视觉体验。
摘要由CSDN通过智能技术生成

RM-F代码解析

作者:shau,网址:https://www.shadertoy.com/view/MsVcRy

标签:3D,带状条纹,Moody Clouds,髓质效果

总共一个部分:Image

Image

​ 首先自定义了一个结构体Scene

struct Scene {
    //射线步进的距离
    float t;
    //物体标号:外球,内球,还是地板
    float id;
    //法线
    vec3 n;
    //近交点位置
    float stn;
    //远交点位置
    float stf;
};

​ 一开始,常规的坐标变化和相机设置

vec2 uv = (fragCoord.xy - iResolution.xy * 0.5) / iResolution.y;
vec3 ro, rd;
setupCamera(uv, ro, rd);
DrawScene

然后,转到drawScene函数中(返回填充好的Scene的结构体),进行场景搭建

Scene drawScene(vec3 ro, vec3 rd) {
    float mint = FAR;
    vec3 minn = vec3(0.0);
    float id = 0.0;

    vec3 fo = vec3(0.0, -1.0, 0.0);
    vec3 fn = vec3(0.0, 1.0, 0.0);
    float ft = planeIntersection(ro, rd, fn, fo);
    if (ft > 0.0 && ft < FAR) {
        mint = ft;
        id = FLOOR;
        minn = fn;
    }    
    
    vec4 si = sphIntersect(ro, rd, sphere);
    if (si.x > 0.0 && si.x < mint) {        
        vec3 rp = ro + rd * si.x;
        mint = si.x;
        id = SPHERE_EXTERIOR;
        minn = sphNormal(rp, sphere);
    } else if (si.y > 0.0 && si.y < mint) {        
        vec3 rp = ro + rd * si.y;
        mint = si.y;
        id = SPHERE_INTERIOR;
        minn = -sphNormal(rp, sphere);
    }
    
    return Scene(mint, id, minn, si.z, si.w);;
}

  • 首先是进行平面相交测试(来自IQ的工具博客),对于n,仅仅是作为向上向量进行点积求值,而o,则是决定地板的高度。在之后,进行传值。
float planeIntersection(vec3 ro, vec3 rd, vec3 n, vec3 o) {
    return dot(o - ro, n) / dot(rd, n);
}
  • 然后进行球体相交测试,这里和一般的相交测试不同的是,对于两个交点进行了pattern测试,对于原理,不太懂,但就效果而言,这个函数导致了球上的条带状条纹。在pattern函数内,就代码分析,abs和两个step函数的相乘的有否,不会对最终效果产生明显影响,而核心毫无疑问是$f.xy = f.x > .5 ? rp.yz / rp.x : f.y > .5 ? rp.xz / rp.y : rp.xy / rp.z; $,这里的常数0.5的数值大小对于实际效果无明显影响(所以到底有什么用),但是这个分支的存在导致了多个条纹(容易想得到),而在tex函数中,对于条纹的宽度进行了控制。回到测试函数,如果是条纹部分,则设置相应变量为0。

    float tex(vec3 rp) {
        rp.xy *= rot(T);
        if (rp.y > 0.1&& rp.y < 0.2) return 0.0;
        return 1.0;
    }        
    //Cube mapping trick from Fizzer
    float pattern(vec3 rp) {
        vec3 f = abs(rp);
        f = step(f.zxy, f) * step(f.yzx, f); 
        f.xy = f.x > .5 ? rp.yz / rp.x : f.y > .5 ? rp.xz / rp.y : rp.xy / rp.z; 
        return tex(f);
    }
    //See sphere functions IQ
    //http://iquilezles.org/www/articles/spherefunctions/spherefunctions.htm
    //slightly modified for cut patterns
    vec4 sphIntersect(vec3 ro, vec3 rd, vec4 sph) {
        vec3 oc = ro - sph.xyz;
        float b = dot(oc, rd);
        float c = dot(oc, oc) - sph.w * sph.w;
        float h = b * b - c;
        if (h < 0.0) return vec4(0.0); //missed
        h = sqrt(h);
        float tN = -b - h;
        float tNF = tN;
        if (pattern(ro + rd * tNF) == 0.0) tNF = 0.0;
        float tF = -b + h;
        float tFF = tF;
        if (pattern(ro + rd * tFF) == 0.0) tFF = 0.0;
        return vec4(tNF, tFF, tN, tF);
    }
    
  • 回到drawScene主函数,这里根据测试结果进行相应赋值,问题在于,根据两个交点的返回结果,将球形测试划分了两个部分,我一开始以为是内球和外球,但实际结果是正面亮纹和背面暗纹的区分。

ColourScene

​ 然后将scene结构体传入此函数中,也是此shader最重要的部分。

vec3 colourScene(vec3 ro, vec3 rd, Scene scene) {
    
    vec3 pc = clouds(rd) * glowColour();
    vec3 gc = vec3(0.0);
    vec3 lp = vec3(4.0, 5.0, -2.0);
	//交点的世界坐标
    vec3 rp = ro + rd * scene.t;
    	
    vec3 ld = normalize(lp - rp);
    float lt = length(lp - rp);
    float atten = 1.0 / (1.0 + lt * lt * 0.051);
    
    if (scene.stn > 0.0) {
        gc = fractalMarch(ro + rd * scene.stn, rd, scene.stf - scene.stn);
        pc = gc;
    }

    if (scene.id == FLOOR) {
        
        // calc texture sampling footprint	
        vec3 uvw = texCoords(rp * 0.15);
		vec3 ddx_uvw = dFdx(uvw); 
    	vec3 ddy_uvw = dFdy(uvw);
        float fc = checkersTextureGradTri(uvw, ddx_uvw, ddy_uvw);
        
    	float diff = max(dot(ld, scene.n), 0.05);
        float ao = 1.0 - sphOcclusion(rp, scene.n, sphere);  
        float spec = pow(max(dot(reflect(-ld, scene.n), -rd), 0.0), 32.0);
        float sh = sphSoftShadow(rp, ld, sphere, 2.0);

        pc += glowColour() * fc * diff * atten;
        pc += vec3(1.0) * spec;
        pc *= ao * sh; 
        
        vec3 gld = normalize(-rp);
        if (sphIntersect(rp, gld, sphere).x == 0.0) {
            pc += glowColour() / (1.0 + length(rp) * length(rp));    
        }
    }
    
    if (scene.id == SPHERE_EXTERIOR) {
    	
        float ao = 0.5 + 0.5 * scene.n.y;
        float spec = pow(max(dot(reflect(-ld, scene.n), -rd), 0.0), 32.0);
        float fres = pow(clamp(dot(scene.n, rd) + 1.0, 0.0, 1.0), 2.0);
        
        pc *= 0.4 * (1.0 - fres);
        pc += vec3(1.0) * fres * 0.2;
        pc *= ao;
        pc += vec3(1.0) * spec;
    }

      
    if (scene.id == SPHERE_INTERIOR) {
    	float ao = 0.5 + 0.5 * scene.n.y;
        float ilt = length(rp) - SR;
        pc += glowColour() * ao / (1.0 + ilt * ilt);
    }
    //*/
    
    return pc;
}

第一行代码:右乘项是根据时间在自定义的调色板上进行取值,左乘项如下所示,效果是产生Moody Clouds,使用了FBM函数,这里可以作为背景技术在以后的日子中使用。然后,就效果论,参数CT的分母影响云状网格的密度,分母越小,网格越密;此外,使用静态的CT实际上呈现并没有网格的效果,个人猜测,是函数原理上是周期函数,在每个周期的部分区域会呈现网格状效果

#define CT T / 15.0
//Moody clouds from Patu
//https://www.shadertoy.com/view/4tVXRV
vec3 clouds(vec3 rd) {
    vec2 uv = rd.xz / (rd.y + 0.6);
    float nz = fbm(vec3(uv.yx * 1.4 + vec2(CT, 0.0), CT)) * 1.5;
    //就效果而言,指数4.0配合Clamp,显而易见是控制云雾显示区域的面积,指数越小,云雾区域越大
    return clamp(pow(vec3(nz), vec3(4.0)) * rd.y, 0.0, 1.0);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3P7DumJN-1623487929469)(C:\Users\ZoroD\Desktop\IQ–master\ShaderToy优秀代码阅读解析\MoodyCloud.gif)]

然后,对于参数而言,lp是光源的位置,根据光源和交点的距离计算雾化参数atten,然后进行球内分形图案计算:如果击中了球,则转入fractalMarch函数

 if (scene.stn > 0.0) {
        gc = fractalMarch(ro + rd * scene.stn, rd, scene.stf - scene.stn);
        pc = gc;
    }
......
#define SR 0.2
#define EPS 0.005
vec3 fractalMarch(vec3 ro, vec3 rd, float maxt) {
    
    vec3 pc = vec3(0.0);
    float t = 0.0;
    float ns = 0.;
    
    for (int i = 0; i < 64; i++) {
        
        vec3 rp = ro + t * rd;
        float lt = length(rp) - SR;

        ns = fractal(rp); 
        
        if (lt < EPS || t > maxt) break;
        t += 0.02 * exp(-2.0 * ns);

        pc = 0.99 * (pc + 0.08 * glowColour() * ns) / (1.0 + lt * lt * 1.);
        pc += 0.1 * glowColour() / (1.0 + lt * lt);  
    } 
    
    return pc;
}
  • 在球内进行射线步进的过程,和一般的过程相似,其中,SR决定了球体内部黑球的大小,然后我们需要分析的是fractal函数,射线推进和pc的取值都和它相关,这是髓质效果的核心。x的取值是为了动画效果,常数值也无所谓;在循环中,第一行(关键)是递归核心操作,计算新的rp值(取正值后归一化,然后利用x进行线性操作);最后一行是根据rp的值计算res的增量(核心);中间两行就效果而言,无明显作用。

    vec2 csqr(vec2 a) {return vec2(a.x * a.x - a.y * a.y, 2.0 * a.x * a.y);}
    //fractal from GUIL
    //https://www.shadertoy.com/view/MtX3Ws
    float fractal(vec3 rp) {
    	
    	float res = 0.0;
    	float x = 0.8 + sin(T * 0.2) * 0.3;
        
        rp.yz *= rot(T);
        
        vec3 c = rp;
    	
        for (int i = 0; i < 10; ++i) {
            rp = x * abs(rp) / dot(rp, rp) - x;
            rp.yz = csqr(rp.yz);
            rp = rp.zxy;
            res += exp(-99.0 * abs(dot(rp, c)));   
    	}
        
        return res;
    }
    

    在进行实验时,如果将rp递归更新的公式中的减法变成加法,会有带状星球的效果。之后,是分情况进行渲染。

    第一种是渲染地板。将点的坐标进行缩放之后,获取它的梯度(ddx,ddy),然后进行如下棋盘处理(关于这个技术,在IQ7中有更为详细的介绍)

    // see https://www.shadertoy.com/view/MtffWs
    vec3 pri(vec3 x) {
        vec3 h = fract(x / 2.0) - 0.5;
        return x * 0.5 + h * (1.0 - 2.0 * abs(h));
    }
    
    float checkersTextureGradTri(vec3 p, vec3 ddx, vec3 ddy) {
        p.z += T;
        vec3 w = max(abs(ddx), abs(ddy)) + 0.01; // filter kernel
        vec3 i = (pri(p + w) - 2.0 * pri(p) + pri(p - w)) / (w * w); // analytical integral (box filter)
        return 0.5 - 0.5 * i.x *  i.y * i.z; // xor pattern
    }
    

    然后依次计算diff,AO,spec,球的柔和阴影。

    float diff = max(dot(ld, scene.n), 0.05);
    float ao = 1.0 - sphOcclusion(rp, scene.n, sphere);  
    float spec = pow(max(dot(reflect(-ld, scene.n), -rd), 0.0), 32.0);
    float sh = sphSoftShadow(rp, ld, sphere, 2.0);
    

    然后计算条纹在地板上产生的光影

     vec3 gld = normalize(-rp);
     if (sphIntersect(rp, gld, sphere).x == 0.0) {
           pc += glowColour() / (1.0 + length(rp) * length(rp));    
     }
    

    然后是计算SPHERE_EXTERIOR下的球,这也是球体渲染的主要部分,一开始,简单的AO和常规的Spec项计算,然后是菲涅尔项计算fres(越接近球的中心,此项越小)

    float ao = 0.5 + 0.5 * scene.n.y;
    float spec = pow(max(dot(reflect(-ld, scene.n), -rd), 0.0), 32.0);
    float fres = pow(clamp(dot(scene.n, rd) + 1.0, 0.0, 1.0), 2.0);
    //此项导致球的外围黑化,中心影响较小,符合电解质的性质
    pc *= 0.4 * (1.0 - fres);
    //此项在球的边缘加了一层白边,更加符合现实
    pc += vec3(1.0) * fres * 0.2;
    pc *= ao;
    pc += vec3(1.0) * spec;
    

    最后一种情况,无论是代码还是效果都较为简单,就不分析了。

再次回到主函数,进行最后一步,在原有计算结果上加上vMarch函数的返回值。这里的作用是为球的带状条纹增加发光效果。在射线步进的循环中,每次移动的步伐是一致的,但是,不是很懂这个map函数和对应的ns变量的作用,在之后,比较简单,从当前点的位置往球心的位置投射线进行相交测试,若是打在光带上,则进行对应渲染

float map(vec3 rp) {
	return min(length(rp) - sphere.w, rp.y + 1.0);
}

vec3 vMarch(vec3 ro, vec3 rd) {

    vec3 pc = vec3(0.0);
    float t = 0.0;
    
    for (int i = 0; i < 96; i++) {
        
        vec3 rp = ro + rd * t;
        float ns = map(rp);
        //这个不管是作用分析还是实际效果上,都无明显作用
        float fz = pattern(rp);
        
        if ((ns < EPS && fz > 0.0) || t > FAR) break;
        
        vec3 ld = normalize(-rp);
        float lt = length(rp);
        //lt项对效果无影响
        if (sphIntersect(rp, ld, sphere).x == 0.0 || lt < sphere.w) {
            lt -= SR;//不重要
            pc += glowColour() * 0.1 / (1.0 + lt * lt * 12.0);        
        }
        
        t += 0.05;
    }
    
    return pc;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clppt6ho-1623487929471)(C:\Users\ZoroD\Desktop\IQ–master\ShaderToy优秀代码阅读解析\DFG.PNG)]

工具类

IQ噪声
//IQs noise
float noise(vec3 rp) {
    vec3 ip = floor(rp);
    rp -= ip; 
    vec3 s = vec3(7, 157, 113);
    vec4 h = vec4(0.0, s.yz, s.y + s.z) + dot(ip, s);
    rp = rp * rp * (3.0 - 2.0 * rp); 
    h = mix(fract(sin(h) * 43758.5), fract(sin(h + s.x) * 43758.5), rp.x);
    h.xy = mix(h.xz, h.yw, rp.y);
    return mix(h.x, h.y, rp.z); 
}
常规FBM(H=1,G=0.5)
float fbm(vec3 x) {
    float r = 0.0;
    float w = 1.0;
    float s = 1.0;
    for (int i = 0; i < 5; i++) {
        w *= 0.5;
        s *= 2.0;
        r += w * noise(s * x);
    }
    return r;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
优化后的代码如下: ```bash #!/bin/bash set -euo pipefail # 加载函数脚本和常用变量 common_path=/path/to/common . "${common_path}/edw_function.sh" . "${common_path}/edw_setting.sh" # 解析参数 sql_file_name=$1 etl_dt=$2 start_offset=$(( $3 + 1 )) end_offset=$(( $4 + 1 )) table_name=${sql_file_name:11} download_sql_file=/dmcp/nas/edw/hql/gp_download/${sql_file_name}.sql exec_file=/dmcp/nas/edw/exec/${today_dt_1}/${sql_file_name}_exec.sql data_path=/data/gp_download/${table_name} # 检查下载 SQL 文件是否存在 if [ ! -e "${download_sql_file}" ]; then echo "${download_sql_file} not found" exit 99 fi # 生成执行 SQL 文件 if [ -e "${exec_file}" ]; then rm -f "${exec_file}" fi envsubst < "${download_sql_file}" > "${exec_file}" # 删除旧数据 rm -f "${data_path}/0*" # 执行 Hive SQL if hive -f "${exec_file}"; then curr_time=$(date +"%Y-%m-%d %H:%M:%S") echo "${curr_time} download ${table_name} succeeded" else curr_time=$(date +"%Y-%m-%d %H:%M:%S") echo "${curr_time} download ${table_name} failed" exit 99 fi ``` 优化点如下: 1. 添加 `set -euo pipefail`,使得脚本在发生错误时立即退出。 2. 使用双引号包裹变量,以避免由空格引起的解析错误。 3. 使用 `$(( ))` 表示算术表达式。 4. 修改变量名,使其更具有可读性。 5. 将日志输出提取出来,使代码更加清晰。 6. 删除了一些不必要的条件判断,如文件是否存在的判断已经在第 12 行执行了,没有必要在第 22 行重复判断。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JMXIN422

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值