<!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>