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();
    }
}
相关推荐
2401_873479403 小时前
如何利用IP查询定位识别电商刷单?4个关键指标+工具配置方案
开发语言·tcp/ip·php
我爱cope4 小时前
【从0开始学设计模式-10| 装饰模式】
java·开发语言·设计模式
菜鸟学Python4 小时前
Python生态在悄悄改变:FastAPI全面反超,Django和Flask还行吗?
开发语言·python·django·flask·fastapi
浪浪小洋5 小时前
c++ qt课设定制
开发语言·c++
charlie1145141915 小时前
嵌入式C++工程实践第16篇:第四次重构 —— LED模板,从通用GPIO到专用抽象
c语言·开发语言·c++·驱动开发·嵌入式硬件·重构
故事和你915 小时前
洛谷-数据结构1-4-图的基本应用1
开发语言·数据结构·算法·深度优先·动态规划·图论
幺风6 小时前
Claude Code 源码分析 — Tool/MCP/Skill 可扩展工具系统
前端·javascript·ai编程
程序猿编码6 小时前
给你的网络流量穿件“隐形衣“:手把手教你用对称加密打造透明安全隧道
linux·开发语言·网络·安全·linux内核
ID_180079054736 小时前
淘宝 API 上货 / 商品搬家 业务场景实现 + JSON 返回示例
前端·javascript·json
M ? A6 小时前
Vue 动态组件在 React 中,VuReact 会如何实现?
前端·javascript·vue.js·经验分享·react.js·面试·vureact