【转载】ShaderToy to UE 初级教程

1,250 阅读6分钟

原文链接:shader toy to UE初级教程 | EmberC

作为一个非移动端开发的美术,性能?消耗?不存在的,统统要给效果让步,除非程序拿着刀把我的脸按在键……%¥&……(*&SDJPAIFLKAS( &) )(i (+ ) &^%& ^$gfkljhl:"km:lhnk


最近看了一下 ShaderToy ,找到了一个无脑将 ShaderToy 搬运到 UE4 材质的方法,虽然通用性较差,但是胜在简单、快速、学习成本低

Shadertoy BETA 这个网站就不介绍了,网上有一大堆教程


正文开始

一、准备阶段

首先,你需要有个 梯¥%……*&& ,能上 ShaderToy

然后下载 VScode,并安装 VScode 插件 "ShaderToy" 和 "HLSL Preview"

二、GLSL 转 HLSL

首先,需要明确的是,Shadertoy 是用 GLSL 语法的代码,但是 UE4 的CustomNode 只支持 HLSL 的代码,我们要先转换一下。GLSL 和 HLSL 差异不大,只有类型和某些函数命名不一样,坐标系一个左手一个右手。

对于着色器语言,语法其实都很简单,函数也不多,但是难的不是语法,而是用这种语法将数学转换为图形。

HLSL 的学习,可以参考微软的文档 —— High-level shader language (HLSL)

GLSL 的学习,可以看一下 OpenGL 的文档 —— learnopengl.com/Getting-sta…

但是,这都不重要,重要的是应用。

以这个 shader 为例 —— www.shadertoy.com/view/4tyfDm

(1)将代码复制到 VS code 中,并 Ctrl+Shift+P 调出控制台执行 ShaderToy 这个插件,然后就能在 VScode 中显示效果了

(2)使用 VScode,新建 HLSL 文件,将代码复制进去,然后Ctrl+Shift+P 调出控制台执行 PreviewHLSL

(3) 这个时候,你会出现一大堆报错,不要慌,只需要改一下就行了。

Ctrl+H 使用批量替换,将 GLSL 的关键字全部替换成 HLSL 的关键字

比如说,GLSL中 4 维变量叫 vec4,但是 HLSL 中叫 float4,这里可以参考微软的 GLSL to HLSL 文档,需要将类型、部分函数的关键字,主函数全部替换掉

GLSL-to-HLSL reference - UWP applications

  • 代码报错

  • 内置函数替换

  • 主函数替换

除了上面这些,ShaderToy 中还有一些专有的写法,需要根据情况替换掉,比如一般 ShaderToy 代码中都有这些。

  • 转换成 HLSL 代码

由于 HLSL 中没有 time 这个内置函数,所以只能把这个参数暴露出去。

三、HLSL 转 UE4 CustomNode

因为 UE4 的中有 CustomNode 的材质 Debug 异常困难,所以,如果我们的代码以 HLSL 跑起来,那么进 UE4 也就不会有代码问题了。

UE4 的 CustomNode 表面上不支持 多函数模式的,但是 本质是支持的

这里需要用到结构体来实现多函数,可以参考以下文章

ikrima.dev/ue4guide/gr…

  • 将函数写在结构体中,然后再实例化调用

新建一个 HLSL,将子函数放置到结构体中

  1. 4 个子函数 csqriSpheremapraymarch

  2. 将子函数放到结构体中

  3. 将结构体实例化,然后主函数去掉,函数调用使用结构体,并注释掉参数声明

这里需要注意一点,CustomNode不能不赋值声明参数,如果有的话,直接使用,然后在 Inputs 界面中添加.(代码中 不用声明、不用声明、不用声明)。有一些可以调效果的参数也可以直接拿出来,比如我把 ro 参数、以及颜色参数添加进了 Inputs 界面。

  1. 比如 Time\UV 这些节点,可以直接 Inputs,不用在 CustomNode 中声明

结束~

总结一下

  1. 将 Shadertoy 的 GLSLHLSL
  2. 将 HLSL 利用 Struct 的方法转成 UE4 的 CustomNode 代码
  3. 根据需求,将一些 变量 提取出来,作为动态调节参数

其实,上面这一套流程都是固定的,写个程序,完全可以一键转 UE,有比较闲的大佬可以写个小脚本


最后附上代码

HLSL 代码

float2 csqr( float2 a )  
{ 
    return float2( a.x*a.x - a.y*a.y, 2.*a.x*a.y  ); 
}

float2 iSphere( in float3 ro, in float3 rd, in float4 sph )//from iq
{
	float3 oc = ro;// - sph.xyz;
	float b = dot( oc, rd );
	float c = dot( oc, oc ) - sph.w*sph.w;
	float h = 1.0; //b*b - c;
	return float2(-b-h, -b+h );
}

float map(in float3 p, float2 sctime) 
{
	
	float res = 0.;
	
    float3 c = p;
    c.xy = c.xy * sctime.x + float2(c.y, c.x) * sctime.y;
	for (int i = 0; i < 10; ++i) 
    {
        p =.7*abs(p)/dot(p,p) -.7;
        p.yz= csqr(p.yz);
        p=p.zxy;
        res += exp(-19. * abs(dot(p,c)));        
	}
	return res/2.;
}

float3 raymarch( in float3 ro, float3 rd, float2 tminmax , float2 sctime)
{
    //tminmax += float2(1.,1.) * sin( iTime * 1.3)*3.0;
   	float3 one3 = float3(1.,1.,1.);
    float3 t = one3 * tminmax.x;
    
    float3 dt = float3(.07, 0.02, 0.05);
    float3 col= float3(0.0,0.0,0.0);
    float3 c = one3 * 0.;
    for( int i=0; i<64; i++ )
	{
     	float3 s = float3(2.0, 3.0, 4.0);   
        t+=dt*exp(-s*c);
        float3 a = step(t,one3*tminmax.y);
        float3 pos = ro+t*rd;
        
        c.x = map(ro+t.x*rd, sctime);
        c.y = map(ro+t.y*rd, sctime);
        c.z = map(ro+t.z*rd, sctime);               
        
        col = lerp(col, .99*col+ .08*c*c*c, a);
    }
    
    float3 c0 = float3(0.4,0.3,0.99);
    float3 c1 = float3(0.9,0.7,0.0);
    float3 c2 = float3(0.9,0.1,0.2);
    return c0 * col.x + c1 * col.y + c2 * col.z;
}

float time;
float3 ro;
float4 main(in float2 uv:TEXCOORD0):SV_TARGET
{
    
	// float time = 1;
    // float2 q = fragCoord.xy / iResolution.xy;
    float2 p = -1.0 + 2.0 * uv;
    // p.x *= iResolution.x/iResolution.y;
    float2 m = float2(0.0,0.0);
    // float3 ro = float3(15.0,0.0,0.0);
    float3 ta = float3( 0.0 , 0.0, 0.0 );
    float3 ww = normalize( ta - ro );
    float3 uu = normalize( cross(ww,float3(0.0,1.0,0.0) ) );
    float3 vv = normalize( cross(uu,ww));
    float3 rd = normalize( p.x*uu + p.y*vv + 4.0*ww ); 
    float2 tmm = iSphere( ro, rd, float4(0.,0.,0.,2.) );
	// raymarch
    float3 col = raymarch(ro,rd,tmm, float2(sin(time), cos(time)));
	// shade    
    col =  .5 *(log(1.+col));
    col = clamp(col,0.,1.);
    return float4( col, 1.0 );

}

UE4 CustomNode

struct Functions
{
        
    float2 csqr( float2 a )  
    { 
        return float2( a.x*a.x - a.y*a.y, 2.*a.x*a.y  ); 
    }

    float2 iSphere( in float3 ro, in float3 rd, in float4 sph )//from iq
    {
        float3 oc = ro;// - sph.xyz;
        float b = dot( oc, rd );
        float c = dot( oc, oc ) - sph.w*sph.w;
        float h = 1.0; //b*b - c;
        return float2(-b-h, -b+h );
    }

    float map(in float3 p, float2 sctime) {
        
        float res = 0.;
        
        float3 c = p;
        c.xy = c.xy * sctime.x + float2(c.y, c.x) * sctime.y;
        for (int i = 0; i < 10; ++i) 
        {
            p =.7*abs(p)/dot(p,p) -.7;
            p.yz= csqr(p.yz);
            p=p.zxy;
            res += exp(-19. * abs(dot(p,c)));        
        }
        return res/2.;
    }

    float3 raymarch( in float3 ro, float3 rd, float2 tminmax , float2 sctime)
    {
        //tminmax += float2(1.,1.) * sin( iTime * 1.3)*3.0;
        float3 one3 = float3(1.,1.,1.);
        float3 t = one3 * tminmax.x;
        
        float3 dt = float3(.07, 0.02, 0.05);
        float3 col= float3(0.0,0.0,0.0);
        float3 c = one3 * 0.;
        for( int i=0; i<64; i++ )
        {
            float3 s = float3(2.0, 3.0, 4.0);   
            t+=dt*exp(-s*c);
            float3 a = step(t,one3*tminmax.y);
            float3 pos = ro+t*rd;
            
            c.x = map(ro+t.x*rd, sctime);
            c.y = map(ro+t.y*rd, sctime);
            c.z = map(ro+t.z*rd, sctime);               
            
            col = lerp(col, .99*col+ .08*c*c*c, a);
        }
        
        // float3 c0 = float3(0.4,0.3,0.99);
        // float3 c1 = float3(0.9,0.7,0.0);
        // float3 c2 = float3(0.9,0.1,0.2);
        return c0 * col.x + c1 * col.y + c2 * col.z;
    }

};

Functions f;
// float time;
// float3 ro;
float2 p = -1.0 + 2.0 * uv;
// p.x *= iResolution.x/iResolution.y;
float2 m = float2(0.0,0.0);

// float3 ro = float3(15.0,0.0,0.0);
float3 ta = float3( 0.0 , 0.0, 0.0 );
float3 ww = normalize( ta - ro );
float3 uu = normalize( cross(ww,float3(0.0,1.0,0.0) ) );
float3 vv = normalize( cross(uu,ww));
float3 rd = normalize( p.x*uu + p.y*vv + 4.0*ww );

float2 tmm = f.iSphere( ro, rd, float4(0.,0.,0.,2.) );

// raymarch
float3 col = f.raymarch(ro,rd,tmm, float2(sin(time), cos(time)));

// shade    
col =  .5 *(log(1.+col));
col = clamp(col,0.,1.);

return float4( col, 1.0 );

CustomNode Input 参数和材质节点

image.png


其实,这种做法只适用于一些 2D效果通用性比较差,可以说仅仅是还原了一张纹理

真正的在 UE4 中还原 ShaderToy 里面的效果,需要理解其 函数的意义 和背后的 数学原理 ,并将参数替换成 UE4 里面的节点,才能展现代码的灵魂所在。

比如上面这个效果,里面用到了 Ray Marching \ Ray Intersection \ fractal,但是单纯的抄到 UE4 中,仅仅成为了一个动态纹理徒有其表,下一篇讲一下,如何将 ShaderToy 的效果在 UE 中赋予 灵魂


补一个视频操作~

虚幻引擎材质教程:Shadertoy to Ue_00_哔哩哔哩