three.js 积雪效果-通过后期处理实现

javascript 复制代码
/**
 * 雪覆盖 后期处理
 */
import {
    WebGLRenderer,
    WebGLRenderTarget,
    Texture,
    ShaderMaterial,
    Scene,
    Camera,
    Color,
    DoubleSide,
    NearestFilter,
    RGBAFormat,
    Vector3
} from "three";
import { Pass, FullScreenQuad } from "three/examples/jsm/postprocessing/Pass";

export class SnowCover extends Pass {
    uniforms: { 
        tDiffuse: { value: Texture | null },
        tNormal: { value: Texture | null },
        uSnowColor: { value: Color },
        uThreshold: { value: number },
        uOpacity: { value: number },
        uSnowDirection: { value: Vector3 },
        uRandomStrength: { value: number }
    };
    material: ShaderMaterial;
    private _fsQuad: FullScreenQuad;
    private normalRenderTarget: WebGLRenderTarget;
    private normalMaterial: ShaderMaterial;
    private scene: Scene;
    private camera: Camera;

    /**
     * Constructs a new output pass.
     */
    constructor(scene: Scene, camera: Camera) {
        super();

        this.scene = scene;
        this.camera = camera;

        // Create a render target for normals
        this.normalRenderTarget = new WebGLRenderTarget(window.innerWidth, window.innerHeight);
        this.normalRenderTarget.texture.format = RGBAFormat;
        this.normalRenderTarget.texture.minFilter = NearestFilter;
        this.normalRenderTarget.texture.magFilter = NearestFilter;

        // Material to capture world normals
        this.normalMaterial = new ShaderMaterial({
            vertexShader: `
                varying vec3 vNormal;
                void main() {
                    // Calculate world normal
                    vNormal = normalize(mat3(modelMatrix) * normal);
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `,
            fragmentShader: `
                varying vec3 vNormal;
                void main() {
                    // Pack normal to 0..1 range
                    gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
                }
            `,
            side: DoubleSide
        });

        /**
         * The pass uniforms.
         */
        this.uniforms = {
            tDiffuse: { value: null },
            tNormal: { value: null },
            uSnowColor: { value: new Color(0xffffff) },
            uThreshold: { value: 1 },
            uOpacity: { value: 1.0 },
            uSnowDirection: { value: new Vector3(-0.4, 1.0, 0.2) },
            uRandomStrength: { value: 0.2 }
        };

        /**
         * The pass material.
         */
        this.material = new ShaderMaterial({
            name: "SnowCover",
            uniforms: this.uniforms,
            vertexShader: `
                varying vec2 vUv;

                void main() {
                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }
            `,
            fragmentShader: `
                varying vec2 vUv;
                uniform sampler2D tDiffuse;
                uniform sampler2D tNormal;
                uniform vec3 uSnowColor;
                uniform float uThreshold;
                uniform float uOpacity;
                uniform vec3 uSnowDirection;
                uniform float uRandomStrength;

                // Simple pseudo-random function
                float random(vec2 st) {
                    return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
                }

                void main() {
                    vec4 diffuse = texture2D(tDiffuse, vUv);
                    vec4 normalData = texture2D(tNormal, vUv);
                    
                    // If normalData is transparent (background), skip snow
                    if (normalData.a < 0.1) {
                        gl_FragColor = diffuse;
                        return;
                    }

                    vec3 normal = normalize(normalData.rgb * 2.0 - 1.0);
                    vec3 snowDir = normalize(uSnowDirection);

                    // Add some randomness to the snow direction per pixel
                    // This simulates "random direction" snow accumulation
                    float noise = random(vUv * 100.0) * 2.0 - 1.0; // -1 to 1
                    
                    // Base snow factor based on direction
                    float snowFactor = dot(normal, snowDir);
                    
                    // Apply random influence
                    snowFactor += noise * uRandomStrength * (1.0 - uThreshold);

                    // Smooth transition
                    float s = smoothstep(uThreshold, uThreshold + 0.2, snowFactor);
                    
                    vec3 finalColor = mix(diffuse.rgb, uSnowColor, s * uOpacity);
                    
                    gl_FragColor = vec4(finalColor, diffuse.a);
                }
            `,
        });

        this._fsQuad = new FullScreenQuad(this.material);
    }

    /**
     * Performs the output pass.
     */
    render(
        renderer: WebGLRenderer,
        writeBuffer: WebGLRenderTarget,
        readBuffer: WebGLRenderTarget /*, deltaTime, maskActive */,
    ) {
        // 1. Render Normals
        const originalOverride = this.scene.overrideMaterial;
        const originalRenderTarget = renderer.getRenderTarget();
        const originalClearColor = new Color();
        renderer.getClearColor(originalClearColor);
        const originalClearAlpha = renderer.getClearAlpha();

        this.scene.overrideMaterial = this.normalMaterial;
        renderer.setRenderTarget(this.normalRenderTarget);
        renderer.setClearColor(0x000000, 0); // Clear to transparent
        renderer.clear();
        renderer.render(this.scene, this.camera);

        // Restore state
        this.scene.overrideMaterial = originalOverride;
        renderer.setRenderTarget(originalRenderTarget);
        renderer.setClearColor(originalClearColor, originalClearAlpha);

        // 2. Render Snow Effect
        this.uniforms["tDiffuse"].value = readBuffer.texture;
        this.uniforms["tNormal"].value = this.normalRenderTarget.texture;

        if (this.renderToScreen) {
            renderer.setRenderTarget(null);
            this._fsQuad.render(renderer);
        } else {
            renderer.setRenderTarget(writeBuffer);
            if (this.clear) renderer.clear();
            this._fsQuad.render(renderer);
        }
    }

    setSize(width: number, height: number) {
        this.normalRenderTarget.setSize(width, height);
    }

    /**
     * Frees the GPU-related resources allocated by this instance. Call this
     * method whenever the pass is no longer used in your app.
     */
    dispose() {
        this.material.dispose();
        this._fsQuad.dispose();
        this.normalRenderTarget.dispose();
        this.normalMaterial.dispose();
    }
}
相关推荐
1024奇点6 小时前
【仓颉语言入门 · 第二篇 第 22 课】
开发语言·ide·开源
Warren2Lynch7 小时前
C4 模型详解:利用 Visual Paradigm 可视化软件架构的综合指南
开发语言·uml
Knight_AL7 小时前
Java 文件下载时文件名中的 `#` 被转换成 `%23`?一次 URL 编码与解码问题排查
java·开发语言
FRing038 小时前
vue2干货总结2(v-bind、v-for、v-model、指令修饰符、v-bind操作class和style)
开发语言·前端·vue.js·vue2
幸运小猿8 小时前
模拟慢上传-处理超时重试image 丢失线上问题
前端·javascript·html
by209998 小时前
C++ 智能指针的所有权与使用
开发语言·c++·经验分享
砚底藏山河8 小时前
python量化自定义周期 K 线合成
java·开发语言·数据库·python·金融
IT召唤狮9 小时前
开源企业级零信任项目Pangolin,零信任能挡住PHP核弹漏洞吗?
开发语言·开源·php
知已难寻9 小时前
03 · OpenStack的计算管理 Nova
运维·服务器·开发语言·云原生·云计算·openstack·开源软件
Rover Ramble9 小时前
C++ 用互斥锁实现读写锁
开发语言·c++