Skip to content

Instantly share code, notes, and snippets.

@akirayou
Created March 11, 2025 12:59
Show Gist options
  • Save akirayou/afa7d33034d6f979b4d12e6c2986aa81 to your computer and use it in GitHub Desktop.
Save akirayou/afa7d33034d6f979b4d12e6c2986aa81 to your computer and use it in GitHub Desktop.
オーロラフィルムシェーダー@three.js
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>オーロラフィルムシェーダー - シンプル版</title>
<style>body { margin: 0; overflow: hidden; }</style>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js"
}
}
</script>
</head>
<body>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js';
// シンプルな4色チェッカーボード背景
function createCheckerboardTexture(size, squares) {
const canvas = document.createElement('canvas');
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
const s = size / squares;
const colors = ['#FF66CC', '#66CCFF', '#66FF66', '#FFCC66'];
for (let i = 0; i < squares; i++) {
for (let j = 0; j < squares; j++) {
ctx.fillStyle = colors[(i % 2) + 2 * (j % 2)];
ctx.fillRect(i * s, j * s, s, s);
}
}
const tex = new THREE.CanvasTexture(canvas);
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set(4, 4);
return tex;
}
// シーン・カメラ・レンダラーの設定
const scene = new THREE.Scene();
scene.background = createCheckerboardTexture(512, 8);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// bumpMap を外部画像から読み込み(リピート設定)
const loader = new THREE.TextureLoader();
loader.setCrossOrigin("anonymous");
const bumpTexture = loader.load('paper_00061.jpg', tex => {
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set(4, 4);
});
// 頂点シェーダー
const vShader = `
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vViewDir;
void main(){
vUv = uv;
vNormal = normalize(normalMatrix * normal);
vec4 mvPos = modelViewMatrix * vec4(position, 1.0);
vViewDir = normalize(-mvPos.xyz);
gl_Position = projectionMatrix * mvPos;
}
`;
// フラグメントシェーダー
const fShader = `
precision mediump float;
uniform sampler2D bumpMap;
uniform vec3 lightDirection1, lightColor1, ambientLightColor;
uniform vec3 auroraColor1, auroraColor2;
uniform float time, bumpScale, bumpStrength, metallicFactor;
varying vec2 vUv;
varying vec3 vNormal, vViewDir;
void main(){
float eps = 0.001;
vec2 uv = vUv * bumpScale;
float bumpVal = texture2D(bumpMap, uv).r;
float bumpRight = texture2D(bumpMap, uv + vec2(eps, 0.0)).r;
float bumpUp = texture2D(bumpMap, uv + vec2(0.0, eps)).r;
vec2 dBump = vec2((bumpRight - bumpVal)/eps, (bumpUp - bumpVal)/eps);
// 簡易タンジェント空間計算
vec3 T = normalize(abs(vNormal.x) > abs(vNormal.z) ? vec3(-vNormal.y, vNormal.x, 0.0) : vec3(0.0, -vNormal.z, vNormal.y));
vec3 B = normalize(cross(vNormal, T));
vec3 perturbed = normalize(vNormal - bumpStrength * (T * dBump.x + B * dBump.y));
vec3 effectiveN = gl_FrontFacing ? perturbed : -perturbed;
float dotNV = clamp(abs(dot(normalize(vViewDir), effectiveN)), 0.0, 1.0);
float vf = dotNV * dotNV;
vec3 transmitted = mix(auroraColor1, auroraColor2, vf);
float shininess = 32.0;
vec3 specular = vec3(0.0);
vec3 L = normalize(lightDirection1);
vec3 H = normalize(L + normalize(vViewDir));
float spec = pow(max(abs(dot(effectiveN, H)), 0.0), shininess);
float inc = 1.0 - max(dot(effectiveN, L), 0.0);
specular += spec * inc;
specular = lightColor1 * metallicFactor * specular;
vec3 reflection = specular + ambientLightColor;
float specInt = length(reflection);
float mixF = exp(-2.0 * specInt);
vec3 color = reflection + transmitted * mixF;
float alpha = 0.7;
gl_FragColor = vec4(color * alpha, alpha);
}
`;
const uniforms = {
bumpMap: { value: bumpTexture },
lightDirection1: { value: new THREE.Vector3(-1, 1, 1).normalize() },
lightColor1: { value: new THREE.Color(0.8, 0.8, 0.8) },
ambientLightColor: { value: new THREE.Color(0.1, 0.1, 0.1) },
auroraColor1: { value: new THREE.Color(0.2, 0.5, 1.0) },
auroraColor2: { value: new THREE.Color(1.0, 0.5, 0.7) },
time: { value: 0.0 },
bumpScale: { value: 2.0 },
bumpStrength: { value: 0.01 },
metallicFactor: { value: 50.0 }
};
const material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vShader,
fragmentShader: fShader,
side: THREE.DoubleSide,
transparent: true,
premultipliedAlpha: true
});
const geom = new THREE.IcosahedronGeometry(1.5, 0);
const mesh = new THREE.Mesh(geom, material);
scene.add(mesh);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
const clock = new THREE.Clock();
function animate(){
requestAnimationFrame(animate);
uniforms.time.value = clock.getElapsedTime();
controls.update();
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment