在 three.js 中向 ShaderMaterial 添加光照

Adding Lighting to a ShaderMaterial in three.js

我想创建一个像 MeshLambertMaterial 一样具有光照的 ShaderMaterial。我创建了一个 vertex and fragment shader,并在 ShaderMaterial 中包含了来自 THREE.ShaderLib[ 'lambert'].uniforms 的制服。

如果我错了请纠正我,但我相信添加光照的下一步是将 MeshLambertMaterial 中使用的 fragment and vertex 着色器代码与我的自定义着色器代码合并。

合并顶点着色器很简单。但是,在片段着色器中,如何为 meshlambert_frag.glsl 中的代码提供基色(将由我的着色器代码生成)以应用光照计算?片段着色器可能看起来像这样:

// my custom shader code before the main function
// meshlambert_frag.glsl shader code before the main function
void main {
  vec4 myBaseColor ... // set by my custom fragment shader code
  // meshlambert_frag.glsl code in the main function would use myBaseColor as a base 
  // color for the lighting calculations before setting gl_FragColor
}

此外,在这种情况下,复制如此大块的代码(来自 meshlambert 着色器)是否是一种不好的做法?什么是更好的解决方案?

将颜色传递给 MeshLambertMaterial 中使用的片段着色器代码的方法是修改 diffuseColor 变量。因此,您可以使用 diffuseColor.rgb *= aVec3 在片段着色器中设置颜色。这是我的着色器代码:

// vertex
#define LAMBERT

varying vec3 vLightFront;

#ifdef DOUBLE_SIDED

    varying vec3 vLightBack;

#endif

#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <envmap_pars_vertex>
#include <bsdfs>
#include <lights_pars>
#include <color_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>

varying vec3 vertexColor;
varying vec2 vUv;
varying vec4 worldPosition;

void main() {
    vertexColor = vec3(255, 100, 0);
    vUv = uv;
    worldPosition = modelMatrix * vec4( position, 1.0 );

    #include <uv_vertex>
    #include <uv2_vertex>
    #include <color_vertex>

    #include <beginnormal_vertex>
    #include <morphnormal_vertex>
    #include <skinbase_vertex>
    #include <skinnormal_vertex>
    #include <defaultnormal_vertex>

    #include <begin_vertex>
    #include <morphtarget_vertex>
    #include <skinning_vertex>
    #include <project_vertex>
    #include <logdepthbuf_vertex>
    #include <clipping_planes_vertex>

    #include <worldpos_vertex>
    #include <envmap_vertex>
    #include <lights_lambert_vertex>
    #include <shadowmap_vertex>

}
// fragment
uniform float time;
varying vec2 vUv;

vec4 permute( vec4 x ) {

    return mod( ( ( x * 34.0 ) + 1.0 ) * x, 289.0 );

}

vec4 taylorInvSqrt( vec4 r ) {

    return 1.79284291400159 - 0.85373472095314 * r;

}

float snoise( vec3 v ) {

    const vec2 C = vec2( 1.0 / 6.0, 1.0 / 3.0 );
    const vec4 D = vec4( 0.0, 0.5, 1.0, 2.0 );

    // First corner

    vec3 i  = floor( v + dot( v, C.yyy ) );
    vec3 x0 = v - i + dot( i, C.xxx );

    // Other corners

    vec3 g = step( x0.yzx, x0.xyz );
    vec3 l = 1.0 - g;
    vec3 i1 = min( g.xyz, l.zxy );
    vec3 i2 = max( g.xyz, l.zxy );

    vec3 x1 = x0 - i1 + 1.0 * C.xxx;
    vec3 x2 = x0 - i2 + 2.0 * C.xxx;
    vec3 x3 = x0 - 1. + 3.0 * C.xxx;

    // Permutations

    i = mod( i, 289.0 );
    vec4 p = permute( permute( permute(
            i.z + vec4( 0.0, i1.z, i2.z, 1.0 ) )
        + i.y + vec4( 0.0, i1.y, i2.y, 1.0 ) )
        + i.x + vec4( 0.0, i1.x, i2.x, 1.0 ) );

    // Gradients
    // ( N*N points uniformly over a square, mapped onto an octahedron.)

    float n_ = 1.0 / 7.0; // N=7

    vec3 ns = n_ * D.wyz - D.xzx;

    vec4 j = p - 49.0 * floor( p * ns.z *ns.z );  //  mod(p,N*N)

    vec4 x_ = floor( j * ns.z );
    vec4 y_ = floor( j - 7.0 * x_ );    // mod(j,N)

    vec4 x = x_ *ns.x + ns.yyyy;
    vec4 y = y_ *ns.x + ns.yyyy;
    vec4 h = 1.0 - abs( x ) - abs( y );

    vec4 b0 = vec4( x.xy, y.xy );
    vec4 b1 = vec4( x.zw, y.zw );


    vec4 s0 = floor( b0 ) * 2.0 + 1.0;
    vec4 s1 = floor( b1 ) * 2.0 + 1.0;
    vec4 sh = -step( h, vec4( 0.0 ) );

    vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
    vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;

    vec3 p0 = vec3( a0.xy, h.x );
    vec3 p1 = vec3( a0.zw, h.y );
    vec3 p2 = vec3( a1.xy, h.z );
    vec3 p3 = vec3( a1.zw, h.w );

    // Normalise gradients

    vec4 norm = taylorInvSqrt( vec4( dot( p0, p0 ), dot( p1, p1 ), dot( p2, p2 ), dot( p3, p3 ) ) );
    p0 *= norm.x;
    p1 *= norm.y;
    p2 *= norm.z;
    p3 *= norm.w;

    // Mix final noise value

    vec4 m = max( 0.6 - vec4( dot( x0, x0 ), dot( x1, x1 ), dot( x2, x2 ), dot( x3, x3 ) ), 0.0 );
    m = m * m;
    return 42.0 * dot( m*m, vec4( dot( p0, x0 ), dot( p1, x1 ),
                                dot( p2, x2 ), dot( p3, x3 ) ) );

}

varying vec4 worldPosition;

uniform vec3 diffuse;
uniform vec3 emissive;
uniform float opacity;

varying vec3 vLightFront;

#ifdef DOUBLE_SIDED

    varying vec3 vLightBack;

#endif

#include <common>
#include <packing>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <emissivemap_pars_fragment>
#include <envmap_pars_fragment>
#include <bsdfs>
#include <lights_pars>
#include <fog_pars_fragment>
#include <shadowmap_pars_fragment>
#include <shadowmask_pars_fragment>
#include <specularmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>

float sigmoid(float x) {
    return x / (1.0 + abs(x));
}

varying vec3 vertexColor;

void main() {
    #include <clipping_planes_fragment>


    vec4 diffuseColor = vec4(diffuse, opacity );
    ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
    vec3 totalEmissiveRadiance = emissive;

    #include <logdepthbuf_fragment>
    #include <map_fragment>
    if(worldPosition.y > 300.0) { //snow
        float noise = snoise(vec3(worldPosition.x * 4.0, worldPosition.y * 4.0, worldPosition.z * 4.0))/20.0;
        diffuseColor.rgb *= vec3(1.0 - noise, 1.0 - noise, 1.0 - noise);
    }
    else if (worldPosition.y > 100.0) { // dirt
        float scale = 5.0;
        float effectscale = 0.2;
        float noise = (snoise(vec3(worldPosition.x * scale, worldPosition.y * scale, worldPosition.z * scale)) - 0.2) * effectscale;
        noise = sigmoid(noise);
        diffuseColor.rgb *= vec3(0.54 + noise, 0.27 + noise, 0.07 + noise);
    }
    else { // grass
        float scale = 4.0;
        float effectscale = 0.08;
        float noise = (snoise(vec3(worldPosition.x * scale, worldPosition.y * scale, worldPosition.z * scale)) - 0.2) * effectscale;
        diffuseColor.rgb *= vec3(0, 0.48 + noise, 0.05 + noise);

    }

    #include <color_fragment>
    #include <alphamap_fragment>
    #include <alphatest_fragment>
    #include <specularmap_fragment>
    #include <emissivemap_fragment>

    // accumulation
    reflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );

    #include <lightmap_fragment>

    reflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );

    #ifdef DOUBLE_SIDED

        reflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;

    #else

        reflectedLight.directDiffuse = vLightFront;

    #endif

    reflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();

    // modulation
    #include <aomap_fragment>

    vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;

    #include <normal_flip>
    #include <envmap_fragment>

    gl_FragColor = vec4( outgoingLight, diffuseColor.a );

    #include <premultiplied_alpha_fragment>
    #include <tonemapping_fragment>
    #include <encodings_fragment>
    #include <fog_fragment>
}

结果看起来不太好,但是有一个带有照明的自定义着色器的概念。您所要做的就是复制 MeshLambertShader 中使用的代码并修改 diffuseColor.rgb。要减少着色器中的代码量,您可以添加 MeshLambert 着色器代码的着色器块(使用 THREE.ShaderChunk 数组)。然后你可以在你的着色器中添加#includes,它对应于你刚刚推送的着色器块的名称。例如,您可以将以下内容添加到 javascript 文件中:

THREE.ShaderChunk["meshlambert_premain_fragment"] = `
        uniform vec3 diffuse;
        uniform vec3 emissive;
        uniform float opacity;

        varying vec3 vLightFront;

        #ifdef DOUBLE_SIDED

            varying vec3 vLightBack;

        #endif

        #include <common>
        #include <packing>
        #include <color_pars_fragment>
        #include <uv_pars_fragment>
        #include <uv2_pars_fragment>
        #include <map_pars_fragment>
        #include <alphamap_pars_fragment>
        #include <aomap_pars_fragment>
        #include <lightmap_pars_fragment>
        #include <emissivemap_pars_fragment>
        #include <envmap_pars_fragment>
        #include <bsdfs>
        #include <lights_pars>
        #include <fog_pars_fragment>
        #include <shadowmap_pars_fragment>
        #include <shadowmask_pars_fragment>
        #include <specularmap_pars_fragment>
        #include <logdepthbuf_pars_fragment>
        #include <clipping_planes_pars_fragment>
`;

稍后,在您想要使用 MeshLambert 光照的着色器中,您可以编写以下内容,从而大大减少着色器文件中的代码量:

#include <meshlambert_premain_fragment>