THREE.JS像素风格渲染


<!DOCTYPE html>
<html lang="en">
    <head>
        <title>three.js webgl - PIXEL EFFECT</title>
        <meta charset="utf-8" />
        <meta
            name="viewport"
            content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
        <link
            type="text/css"
            rel="stylesheet"
            href="../../three.js/examples/main.css" />
    </head>

    <body>
        <div
            id="container"
            style="background: black"></div>

        <script type="importmap">
            {
                "imports": {
                    "three": "../../three.js/build/three.module.js",
                    "three/addons/": "../../three.js/examples/jsm/"
                }
            }
        </script>

        <script type="module">
            import * as THREE from "three";

            import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
            import { OrbitControls } from "three/addons/controls/OrbitControls.js";
            import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
            import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
            import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
            import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
            import { FXAAShader } from "three/addons/shaders/FXAAShader.js";

            let camera, scene, renderer, controls;
            let composer, fxaaPass;
            const container = document.querySelector("#container");

            // Configure and create Draco decoder.
            const dracoLoader = new DRACOLoader();
            dracoLoader.setDecoderPath("../../three.js/examples/jsm/libs/draco/");
            dracoLoader.setDecoderConfig({ type: "js" });

            init();

            function init() {
                camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 15);

                scene = new THREE.Scene();
                // scene.background = new THREE.Color(0x443333);
                // scene.fog = new THREE.Fog(0x443333, 1, 4);
                scene.background = new THREE.Color(0x000000);

                // Ground
                // const plane = new THREE.Mesh(
                //     new THREE.PlaneGeometry(8, 8),
                //     new THREE.MeshPhongMaterial({ color: 0xcbcbcb, specular: 0x101010 })
                // );
                // plane.rotation.x = -Math.PI / 2;
                // plane.position.y = 0.035;
                // plane.receiveShadow = true;
                // scene.add(plane);

                // Lights
                const hemiLight = new THREE.HemisphereLight(0x8d7c7c, 0x494966, 1);
                scene.add(hemiLight);

                const spotLight = new THREE.SpotLight();
                spotLight.castShadow = true;
                spotLight.intensity = 5;
                spotLight.angle = Math.PI / 16;
                spotLight.penumbra = 1;
                spotLight.position.set(-1, 1, 1);
                scene.add(spotLight);

                let material;
                material = new THREE.ShaderMaterial({
                    uniforms: {
                        // lightPosition: { value: new THREE.Vector3(0.000001,0.000001,1) },
                        lightPosition: { value: camera.position },
                    },
                    vertexShader: `
                        varying vec2 vUv;
                        varying vec3 Normal;

                        void main() {
                            vUv = uv;
                            vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
                            gl_Position = projectionMatrix * modelViewPosition;
                            Normal =  normalize(normal);

                        }`,
                    fragmentShader: `
                        uniform vec3 lightPosition;
                        varying vec3 Normal;

                        void main() {
                                
                            vec3 color = vec3(1.);
                            float intensity = abs( dot(normalize(lightPosition),Normal) );

                            if( abs( intensity ) < 0.5) {
                                gl_FragColor = vec4(vec3(1), 1.0);
                            } else {
                                // gl_FragColor = vec4(vec3(0), 1.0);
                                // discard;
                                gl_FragColor = vec4(vec3(0), 1.0);
                            }
                                
                            // gl_FragColor = vec4(vec3(Normal), 1.0);
                        }`,
                });

                // const mesh = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1), material);
                // const mesh = new THREE.Mesh(new THREE.SphereGeometry( 0.0015, 32, 16 ), material);
                // const mesh = new THREE.Mesh(new THREE.TorusKnotGeometry( 0.02, 0.006, 100, 16 ), material);
                material = new THREE.MeshStandardMaterial({});
                // material = new THREE.MeshNormalMaterial({});

                const mesh = new THREE.Mesh(new THREE.TorusKnotGeometry(0.02, 0.006, 100, 16), material);

                // mesh.position.set(-0.085, 0.14, 0.02);
                mesh.position.set(0.1, 0.1, 0);
                mesh.castShadow = true;
                mesh.receiveShadow = true;

                scene.add(mesh);

                dracoLoader.load("../../three.js/examples/models/draco/bunny.drc", function (geometry) {
                    geometry.computeVertexNormals();

                    // const material = new THREE.MeshStandardMaterial({ color: 0xa5a5a5 });

                    const mesh = new THREE.Mesh(geometry, material);
                    mesh.castShadow = true;
                    // mesh.receiveShadow = true;
                    scene.add(mesh);

                    // Release decoder resources.
                    dracoLoader.dispose();
                });

                // renderer
                renderer = new THREE.WebGLRenderer({ antialias: false, alpha: false });
                renderer.setPixelRatio(window.devicePixelRatio);
                renderer.setSize(window.innerWidth, window.innerHeight);
                renderer.setAnimationLoop(animate);
                renderer.shadowMap.enabled = true;
                container.appendChild(renderer.domElement);

                controls = new OrbitControls(camera, renderer.domElement);
                controls.target.set(0, 0.1, 0);
                controls.object.position.set(0, 0.1, 0.3);

                window.addEventListener("resize", onWindowResize);

                /**             后处理           */
                const renderPass = new RenderPass(scene, camera);
                renderPass.clearAlpha = 0;

                const pixelRatio = renderer.getPixelRatio();

                // FXAA设计用于在转换为低动态范围和转换为sRGB颜色空间进行显示后,在引擎后处理结束时应用。
                fxaaPass = new ShaderPass(FXAAShader);
                fxaaPass.material.uniforms["resolution"].value.x = 1 / (container.offsetWidth * pixelRatio);
                fxaaPass.material.uniforms["resolution"].value.y = 1 / (container.offsetHeight * pixelRatio);

                composer = new EffectComposer(renderer);

                composer.addPass(renderPass);

                /** 渲染法线信息 */
                const normalMaterial = new THREE.MeshNormalMaterial();

                const normalRenderTarget = new THREE.WebGLRenderTarget(
                    container.offsetWidth * pixelRatio,
                    container.offsetHeight * pixelRatio,
                    {
                        minFilter: THREE.NearestFilter,
                        magFilter: THREE.NearestFilter,
                        type: THREE.HalfFloatType,
                    }
                );

                window.updateNormal = () => {
                    renderOverride(renderer, normalMaterial, normalRenderTarget, 0, 0);
                };

                const PixelPass = new ShaderPass(
                    new THREE.ShaderMaterial({
                        name: "PixelShader",
                        uniforms: {
                            tNormal: { value: normalRenderTarget.texture },
                            /**
                             * ShaderPass 每次render前 传递上一个通道的结果
                             * this.uniforms[ this.textureID ].value = readBuffer.texture;
                             * this.textureID默认为 tDiffuse
                             * 同时需要定义 tDiffuse: { value: null } 确保有一个key接收纹理
                             */
                            tDiffuse: { value: null },
                            resolution: {
                                value: new THREE.Vector2(
                                    1 / (container.offsetWidth * pixelRatio),
                                    1 / (container.offsetHeight * pixelRatio)
                                ),
                            },
                            pixelSize: { value: 3 * 4 },
                            lightPosition: { value: camera.position },
                        },

                        defines: {},

                        vertexShader: /* glsl */ `
                        varying vec2 vUv;

                        void main() {

                            vUv = uv;
                            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

                        }`,

                        fragmentShader: /* glsl */ `
                        precision highp float;

                        uniform sampler2D tDiffuse;
                        uniform sampler2D tNormal;
                        uniform float pixelSize;
                        uniform vec2 resolution;
                        uniform vec3 lightPosition;

                        varying vec2 vUv;

                        // 将每个片段映射到其最近的、不重叠的像素大小窗口的中心。 窗口中心的片段决定了其窗口中其他片段的颜色。
                        vec2 getCoord () {
                            float x = mod( gl_FragCoord.x, pixelSize);
                            float y = mod( gl_FragCoord.y, pixelSize);

                            x = floor(pixelSize / 2.0) - x;
                            y = floor(pixelSize / 2.0) - y;

                            x = gl_FragCoord.x + x;
                            y = gl_FragCoord.y + y;

                            return vec2(x,y) * resolution;
                        }

                        void main() {
                            vec2 coord = getCoord();

                            vec4 normal = texture2D( tNormal, coord );
                            
                            // float intensity = abs( dot( normalize( vec3( lightPosition ) ), normal.rgb ) );
                            // 简单边缘余弦角度
                            float intensity = abs( dot( normalize( vec3( 0,0,1 ) ), normal.rgb ) );
                        
                            vec3 color = texture2D( tDiffuse, coord ).rgb;
                            // color = normal.rgb;
                            // color = vec3(intensity);

                            float normalGentle = normal.x + normal.y + normal.z;

                            if(normalGentle > 0.00){

                                if( intensity > 0.9) {

                                    gl_FragColor = vec4(vec3(0), .0);

                                } else {

                                    // gl_FragColor = vec4(vec3(color), 1.0);
                                    gl_FragColor = vec4(vec3(1), 1.0);
                                    // gl_FragColor = vec4(vec3(1), 1.0);
                                    // discard;

                                }
                            }

                            gl_FragColor = vec4(vec3(color), 1.0);
                        }
                    `,
                    }),
                    "tDiffuse"
                );

                // 处理颜色格式
                const outputPass = new OutputPass();

                composer.addPass(PixelPass);

                composer.addPass(outputPass);

                composer.addPass(fxaaPass);
            }

            function onWindowResize() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();

                renderer.setSize(window.innerWidth, window.innerHeight);
            }

            function animate() {
                // renderer.render(scene, camera);
                window.updateNormal && window.updateNormal();
                composer.render();
            }

            // const pixelSizeCatch = {}

            // function getCoord(pixelSize, _x, _y) {
            //     let x = Math.floor(_x) % pixelSize;
            //     let y = Math.floor(_y) % pixelSize;

            //     x = Math.floor(pixelSize / 2) - x;
            //     y = Math.floor(pixelSize / 2) - y;

            //     x = _x + x;
            //     y = _y + y;
            //     return `${x},${y}`;
            // }

            // const pixelSize = 5;

            // for (let i = 0; i < pixelSize * 4; i++) {
            //     for (let j = 0; j < pixelSize * 4; j++) {
            //         const coord = getCoord(pixelSize, j, i)
            //         const item = pixelSizeCatch[coord];
            //         if(!item){
            //             pixelSizeCatch[coord] = `(${j},${i})`
            //         }else {
            //             pixelSizeCatch[coord] += `, (${j},${i})`
            //         }
            //     }
            // }
            function renderOverride(renderer, overrideMaterial, renderTarget, clearColor, clearAlpha) {
                const originalClearColor = new THREE.Color();
                const tempColor = new THREE.Color();

                originalClearColor.copy(renderer.getClearColor(tempColor));

                const originalClearAlpha = renderer.getClearAlpha(tempColor);
                const originalAutoClear = renderer.autoClear;

                renderer.setRenderTarget(renderTarget);
                renderer.autoClear = false;

                clearColor = overrideMaterial.clearColor || clearColor;
                clearAlpha = overrideMaterial.clearAlpha || clearAlpha;

                if (clearColor !== undefined && clearColor !== null) {
                    renderer.setClearColor(clearColor);
                    renderer.setClearAlpha(clearAlpha || 0.0);
                    renderer.clear();
                }

                scene.overrideMaterial = overrideMaterial;
                renderer.render(scene, camera);
                scene.overrideMaterial = null;

                // restore original state

                renderer.autoClear = originalAutoClear;
                renderer.setClearColor(originalClearColor);
                renderer.setClearAlpha(originalClearAlpha);
                // renderer.setRenderTarget(null);
            }
        </script>
    </body>
</html>
相关推荐
我曾经是个程序员24 分钟前
鸿蒙学习记录
开发语言·前端·javascript
羊小猪~~39 分钟前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·javascript·css·vue.js·vscode·ajax·html5
2401_857600952 小时前
基于 SSM 框架 Vue 电脑测评系统:赋能电脑品质鉴定
前端·javascript·vue.js
天之涯上上2 小时前
Pinia 是一个专为 Vue.js 3 设计的状态管理库
前端·javascript·vue.js
高山我梦口香糖3 小时前
[react] <NavLink>自带激活属性
前端·javascript·react.js
撸码到无法自拔3 小时前
React:组件、状态与事件处理的完整指南
前端·javascript·react.js·前端框架·ecmascript
高山我梦口香糖3 小时前
[react]不能将类型“string | undefined”分配给类型“To”。 不能将类型“undefined”分配给类型“To”
前端·javascript·react.js
Elena_Lucky_baby3 小时前
实现路由懒加载的方式有哪些?
前端·javascript·vue.js
一雨方知深秋4 小时前
智慧商城:封装getters实现动态统计 + 全选反选功能
开发语言·javascript·vue2·foreach·find·every
海威的技术博客4 小时前
关于JS中的this指向问题
开发语言·javascript·ecmascript