Created
March 11, 2025 12:59
-
-
Save akirayou/afa7d33034d6f979b4d12e6c2986aa81 to your computer and use it in GitHub Desktop.
オーロラフィルムシェーダー@three.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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