Codrops 원본
Relighting Images with Depth Maps and Three.js
섹션 · MIT
갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.
SOURCE FILES
원본 코드 읽기
수집한 원본 소스와 실행 안내를 함께 제공합니다.
demo2.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Relightning Images — demo 2</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/src/style.css">
</head>
<body>
<div id="app"></div>
<aside id="info">
<h1>Relightning Images by Dominik Fojcik</h1>
<nav class="project-links" aria-label="Project links">
<span>Article</span>
<a href="https://github.com/DGFX/codrops-relightning-images">Code</a>
<a href="https://tympanus.net/codrops/demos/">All Demos</a>
</nav>
<nav class="tags" aria-label="Tags">
<a href="https://tympanus.net/codrops/demos/?tag=3d">#3d</a>
<a href="https://tympanus.net/codrops/demos/?tag=three-js">#three.js</a>
</nav>
</aside>
<nav id="nav" aria-label="Relightning demos">
<a href="/">demo 1</a>
<a href="/demo2.html" class="active">demo 2</a>
<a href="/demo3.html">demo 3</a>
</nav>
<script type="module" src="/src/app.js"></script>
</body>
</html>
demo3.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Relightning Images — demo 3</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/src/style.css">
</head>
<body>
<div id="app"></div>
<aside id="info">
<h1>Relightning Images by Dominik Fojcik</h1>
<nav class="project-links" aria-label="Project links">
<span>Article</span>
<a href="https://github.com/DGFX/codrops-relightning-images">Code</a>
<a href="https://tympanus.net/codrops/demos/">All Demos</a>
</nav>
<nav class="tags" aria-label="Tags">
<a href="https://tympanus.net/codrops/demos/?tag=3d">#3d</a>
<a href="https://tympanus.net/codrops/demos/?tag=three-js">#three.js</a>
</nav>
</aside>
<nav id="nav" aria-label="Relightning demos">
<a href="/">demo 1</a>
<a href="/demo2.html">demo 2</a>
<a href="/demo3.html" class="active">demo 3</a>
</nav>
<script type="module" src="/src/app.js"></script>
</body>
</html>
함께 쓰는 파일 18개 보기
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Relightning Images</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/src/style.css">
</head>
<body>
<div id="app"></div>
<aside id="info">
<h1>Relightning Images by Dominik Fojcik</h1>
<nav class="project-links" aria-label="Project links">
<span>Article</span>
<a href="https://github.com/DGFX/codrops-relightning-images">Code</a>
<a href="https://tympanus.net/codrops/demos/">All Demos</a>
</nav>
<nav class="tags" aria-label="Tags">
<a href="https://tympanus.net/codrops/demos/?tag=3d">#3d</a>
<a href="https://tympanus.net/codrops/demos/?tag=three-js">#three.js</a>
</nav>
</aside>
<nav id="nav" aria-label="Relightning demos">
<a href="/" class="active">demo 1</a>
<a href="/demo2.html">demo 2</a>
<a href="/demo3.html">demo 3</a>
</nav>
<script type="module" src="/src/app.js"></script>
</body>
</html>
src/app.js
import {
AgXToneMapping,
Color,
OrthographicCamera,
Scene,
WebGPURenderer,
} from 'three/webgpu'
import { inspector } from './debug.js'
import { setupDemo } from './demos.js'
import { setupLight } from './effect/light.js'
import { createPlane } from './effect/plane.js'
import { setupTextures } from './effect/textures.js'
const MAX_PIXEL_RATIO = 2
const VIEW_HEIGHT = 4
const demo = setupDemo()
const renderer = new WebGPURenderer({ antialias: true })
renderer.setPixelRatio(Math.min(devicePixelRatio, MAX_PIXEL_RATIO))
renderer.setSize(innerWidth, innerHeight)
renderer.toneMapping = AgXToneMapping
renderer.inspector = inspector
document.getElementById('app').append(renderer.domElement)
await renderer.init()
await setupTextures(demo)
const scene = new Scene()
scene.background = new Color('#000000')
const camera = new OrthographicCamera()
camera.position.set(0, 0, 5)
setupLight(scene, camera)
const plane = createPlane()
scene.add(plane)
function onResize() {
const aspect = innerWidth / innerHeight
camera.top = VIEW_HEIGHT * 0.5
camera.bottom = -camera.top
camera.right = camera.top * aspect
camera.left = -camera.right
camera.updateProjectionMatrix()
plane.scale.set(VIEW_HEIGHT * aspect, VIEW_HEIGHT, 1)
}
onResize()
addEventListener('resize', () => {
renderer.setSize(innerWidth, innerHeight)
onResize()
})
renderer.setAnimationLoop(() => renderer.render(scene, camera))
src/debug.js
import { Inspector } from 'three/addons/inspector/Inspector.js'
import { int, output, select, uniform, vec3, vec4 } from 'three/tsl'
export const inspector = new Inspector()
export const gui = inspector.createParameters('Light plane')
const uDebugView = uniform(0, 'int')
const DEBUG_VIEWS = [
{ label: 'depth', node: (_material, depth) => vec3(depth) },
{
label: 'normal',
node: (material) => material.normalNode.mul(0.5).add(0.5),
},
{ label: 'diffuse', node: (material) => material.colorNode },
{ label: 'shadow', node: (material) => vec3(material.aoNode) },
]
export function setDebugView(material, depth) {
material.outputNode = vec4(
DEBUG_VIEWS.reduceRight(
(fallback, { node }, index) =>
select(
uDebugView.equal(int(index + 1)),
node(material, depth),
fallback,
),
output.rgb,
),
output.a,
)
const debugView = gui.addFolder('Debug View').close()
debugView
.add(
uDebugView,
'value',
Object.fromEntries([
['final', 0],
...DEBUG_VIEWS.map(({ label }, index) => [label, index + 1]),
]),
)
.name('output')
}
src/demos.js
import { depthSmoothing } from './effect/depth-map.js'
import { ambientLight, pointLight } from './effect/light.js'
import {
uDetailScale,
uDisplacementScale,
uNormalScale,
} from './effect/nodes/normal.js'
import { uShadowIntensity, uShadowSoftness } from './effect/nodes/shadow.js'
const DEMOS = {
'/demo2': {
map: '/textures/relief.jpg',
depth: '/textures/relief-depth.webp',
setUniforms() {
depthSmoothing.percent = 0.6
uDisplacementScale.value = 0.905
uNormalScale.value = 0.86
uDetailScale.value = 0.83
uShadowIntensity.value = 0.55
uShadowSoftness.value = 0.164
pointLight.color.set('#ffcb8f')
pointLight.decay = 2.95
pointLight.position.z = 0.77
ambientLight.intensity = 0
},
},
'/demo3': {
map: '/textures/mother.jpg',
depth: '/textures/mother-depth.webp',
setUniforms() {
depthSmoothing.percent = 0.5
uDisplacementScale.value = 1.9
uNormalScale.value = 2.16
uDetailScale.value = 0.7
uShadowSoftness.value = 0.152
pointLight.color.set('#e7dcd0')
pointLight.intensity = 10.1
pointLight.decay = 3.8
pointLight.position.z = 1.97
ambientLight.intensity = 0.02
},
},
}
const DEFAULT_DEMO = {
map: '/textures/wall.jpg',
depth: '/textures/wall-depth.webp',
}
export function setupDemo() {
const pathname = location.pathname.replace(/\.html$/, '')
const demo = DEMOS[pathname] ?? DEFAULT_DEMO
demo.setUniforms?.()
return demo
}
src/effect/depth-map.js
import { texture } from 'three/tsl'
import {
DataTexture,
DataUtils,
HalfFloatType,
LinearFilter,
RedFormat,
} from 'three/webgpu'
import { gui } from '../debug.js'
import { smoothBands } from '../lib/blur.js'
export const depthSmoothing = { percent: 1.3 }
const BAKE_DELAY_MS = 150
const WORKING_WIDTH = 1024
const smoothDepthMap = new DataTexture(
new Uint16Array([DataUtils.toHalfFloat(0.5)]),
1,
1,
RedFormat,
HalfFloatType,
)
smoothDepthMap.minFilter = LinearFilter
smoothDepthMap.magFilter = LinearFilter
smoothDepthMap.needsUpdate = true
export const smoothDepthNode = texture(smoothDepthMap)
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d', { willReadFrequently: true })
let depthImage = null
export function setDepthImage(image) {
depthImage = image
if (!depthImage) return
const scale = Math.min(1, WORKING_WIDTH / depthImage.width)
const width = Math.max(1, Math.round(depthImage.width * scale))
const height = Math.max(1, Math.round(depthImage.height * scale))
canvas.width = width
canvas.height = height
context.setTransform(1, 0, 0, -1, 0, height)
context.drawImage(depthImage, 0, 0, width, height)
const { data } = context.getImageData(0, 0, width, height)
const values = new Float32Array(width * height)
for (let i = 0; i < values.length; i++) {
values[i] = data[i * 4] / 255
}
smoothBands(
{ values, width, height },
Math.round((depthSmoothing.percent / 100) * width),
)
const halfFloats = new Uint16Array(values.length)
for (let i = 0; i < halfFloats.length; i++) {
halfFloats[i] = DataUtils.toHalfFloat(values[i])
}
smoothDepthMap.dispose()
smoothDepthMap.image = { data: halfFloats, width, height }
smoothDepthMap.needsUpdate = true
}
let bakeTimer = 0
export const depthFolder = gui.addFolder('Depth').close()
depthFolder
.add(depthSmoothing, 'percent', 0.1, 12, 0.1)
.name('smooth depth')
.addEventListener('change', () => {
clearTimeout(bakeTimer)
bakeTimer = setTimeout(() => setDepthImage(depthImage), BAKE_DELAY_MS)
})
src/effect/light.js
import { uniform } from 'three/tsl'
import { AmbientLight, PointLight } from 'three/webgpu'
import { gui } from '../debug.js'
const MAX_DECAY = 4
export const pointLight = new PointLight('#e1ded1', 2.35, 0, 1)
pointLight.position.set(1.2, 0.8, 0.9)
Object.defineProperty(pointLight.userData, 'radius', {
get: () => MAX_DECAY - pointLight.decay,
set: (radius) => {
pointLight.decay = MAX_DECAY - radius
},
})
export const ambientLight = new AmbientLight('#ffffff', 0.3)
export const uLightPosition = uniform(pointLight.position)
export function setupLight(scene, camera) {
scene.add(pointLight, ambientLight)
addEventListener('pointermove', (event) => {
const ndcX = (event.clientX / innerWidth) * 2 - 1
const ndcY = -((event.clientY / innerHeight) * 2 - 1)
pointLight.position.x = ndcX * camera.right
pointLight.position.y = ndcY * camera.top
})
const light = gui.addFolder('Light').close()
light.addColor(pointLight, 'color').name('color')
light.add(pointLight, 'intensity', 0, 12, 0.05).name('brightness')
light.add(pointLight.userData, 'radius', 0, MAX_DECAY, 0.05).name('radius')
light.add(pointLight.position, 'z', -0.5, 4, 0.01).name('elevation')
light.add(ambientLight, 'intensity', 0, 4, 0.05).name('ambient')
}
src/effect/nodes/diffuse.js
import { Fn, step, uniform } from 'three/tsl'
import { depthFolder } from '../depth-map.js'
import { mapNode } from '../textures.js'
const uDepthThreshold = uniform(0)
export const diffuseNode = Fn(([vUv, depth]) =>
mapNode.sample(vUv).rgb.mul(step(uDepthThreshold, depth)),
)
depthFolder.add(uDepthThreshold, 'value', 0, 1, 0.01).name('depth threshold')
src/effect/nodes/normal.js
import {
Fn,
float,
luminance,
modelScale,
uniform,
vec2,
vec3,
} from 'three/tsl'
import { gui } from '../../debug.js'
import { smoothDepthNode } from '../depth-map.js'
import { mapNode } from '../textures.js'
import { coverScaleNode } from './texture-fit.js'
export const uDisplacementScale = uniform(4)
export const uNormalScale = uniform(3)
export const uDetailScale = uniform(3)
const DETAIL_GAIN = 4
const DETAIL_LOD = 3
const DETAIL_STEP_TEXELS = 8
const GRADIENT_TEXELS = 3
const depthGradient = Fn(([vUv, step]) => {
const alongX = vec2(step.x, 0)
const alongY = vec2(0, step.y)
const left = smoothDepthNode.sample(vUv.sub(alongX)).r
const right = smoothDepthNode.sample(vUv.add(alongX)).r
const bottom = smoothDepthNode.sample(vUv.sub(alongY)).r
const top = smoothDepthNode.sample(vUv.add(alongY)).r
return vec2(right.sub(left), top.sub(bottom)).mul(0.5)
})
const detailGradient = Fn(([vUv, step]) => {
const alongX = vec2(step.x, 0)
const alongY = vec2(0, step.y)
const map = (uvNode) => mapNode.sample(uvNode).level(DETAIL_LOD).rgb
const left = luminance(map(vUv.sub(alongX)))
const right = luminance(map(vUv.add(alongX)))
const bottom = luminance(map(vUv.sub(alongY)))
const top = luminance(map(vUv.add(alongY)))
return vec2(right.sub(left), top.sub(bottom)).mul(0.5)
})
export const normalNode = Fn(([vUv]) => {
const step = vec2(GRADIENT_TEXELS).div(vec2(smoothDepthNode.size()))
const slope = depthGradient(vUv, step)
.mul(coverScaleNode())
.div(step.mul(modelScale.xy))
.mul(uDisplacementScale)
.mul(uNormalScale)
const detail = detailGradient(
vUv,
vec2(DETAIL_STEP_TEXELS).div(vec2(mapNode.size())),
)
.mul(uDetailScale)
.mul(DETAIL_GAIN)
const shape = vec3(slope.x.negate(), slope.y.negate(), float(1))
return shape.add(vec3(detail.x.negate(), detail.y.negate(), 0)).normalize()
})
const normal = gui.addFolder('Normal').close()
normal.add(uDisplacementScale, 'value', 0, 4, 0.005).name('displacement scale')
normal.add(uNormalScale, 'value', 0, 3, 0.01).name('normal scale')
normal.add(uDetailScale, 'value', 0, 10, 0.01).name('normal details')
src/effect/nodes/shadow.js
import {
Fn,
Loop,
float,
modelScale,
positionWorld,
uniform,
vec3,
} from 'three/tsl'
import { gui } from '../../debug.js'
import { smoothDepthNode } from '../depth-map.js'
import { uLightPosition } from '../light.js'
import { uDisplacementScale } from './normal.js'
import { coverScaleNode } from './texture-fit.js'
export const uShadowIntensity = uniform(0.86)
export const uShadowSoftness = uniform(0.092)
const SHADOW_STEPS = 12
const MIN_LIGHT_ANGLE = 0.15
const SOFTNESS_GROWTH = 3
export const shadowNode = Fn(([vUv, depth]) => {
const surfacePosition = vec3(
positionWorld.xy,
depth.sub(1).mul(uDisplacementScale),
)
const surfaceToLight = uLightPosition.sub(surfacePosition)
const lightDirection = surfaceToLight.div(surfaceToLight.length().max(0.001))
const surfaceDepth = smoothDepthNode.sample(vUv).r
const remainingDepth = surfaceDepth.oneMinus()
const rayOffset = lightDirection.xy
.div(lightDirection.z.max(MIN_LIGHT_ANGLE))
.mul(uDisplacementScale)
.mul(coverScaleNode())
.div(modelScale.xy)
.mul(remainingDepth)
const maxOcclusion = float(0).toVar()
Loop(SHADOW_STEPS, ({ i }) => {
const rayProgress = float(i).add(1).div(SHADOW_STEPS)
const rayDepth = surfaceDepth.add(remainingDepth.mul(rayProgress))
const blockerDepth = smoothDepthNode.sample(
vUv.add(rayOffset.mul(rayProgress)),
).r
const sampleSoftness = uShadowSoftness.mul(
rayProgress.mul(SOFTNESS_GROWTH).add(1),
)
const sampleOcclusion = blockerDepth
.sub(rayDepth)
.div(sampleSoftness)
.clamp(0, 1)
maxOcclusion.assign(maxOcclusion.max(sampleOcclusion))
})
const lightFacingMask = lightDirection.z.greaterThan(0).select(1, 0)
return maxOcclusion.mul(uShadowIntensity).mul(lightFacingMask).oneMinus()
})
const shadow = gui.addFolder('Shadow').close()
shadow.add(uShadowIntensity, 'value', 0, 1, 0.01).name('shadow intensity')
shadow.add(uShadowSoftness, 'value', 0.002, 0.2, 0.002).name('shadow softness')
src/effect/nodes/texture-fit.js
import { Fn, screenSize, screenUV, vec2 } from 'three/tsl'
import { mapNode } from '../textures.js'
export const coverScaleNode = Fn(() => {
const viewAspect = screenSize.x.div(screenSize.y).toVar()
const mapSize = vec2(mapNode.size()).toVar()
const imageAspect = mapSize.x.div(mapSize.y).toVar()
return imageAspect
.greaterThan(viewAspect)
.select(
vec2(viewAspect.div(imageAspect), 1),
vec2(1, imageAspect.div(viewAspect)),
)
})
export const coverUv = Fn(() =>
screenUV.flipY().sub(0.5).mul(coverScaleNode()).add(0.5),
)
src/effect/plane.js
import {
cameraFar,
cameraNear,
positionView,
viewZToOrthographicDepth,
} from 'three/tsl'
import { Mesh, MeshPhongNodeMaterial, PlaneGeometry } from 'three/webgpu'
import { setDebugView } from '../debug.js'
import { diffuseNode } from './nodes/diffuse.js'
import { normalNode, uDisplacementScale } from './nodes/normal.js'
import { shadowNode } from './nodes/shadow.js'
import { coverUv } from './nodes/texture-fit.js'
import { depthNode } from './textures.js'
export function createPlane() {
const vUv = coverUv()
const depth = depthNode.sample(vUv).r
const material = new MeshPhongNodeMaterial({ specular: 0x000000 })
material.colorNode = diffuseNode(vUv, depth)
material.normalNode = normalNode(vUv)
material.aoNode = shadowNode(vUv, depth)
material.depthNode = viewZToOrthographicDepth(
positionView.z.add(depth.sub(1).mul(uDisplacementScale)),
cameraNear,
cameraFar,
)
setDebugView(material, depth)
return new Mesh(new PlaneGeometry(1, 1), material)
}
src/effect/textures.js
import { texture } from 'three/tsl'
import { SRGBColorSpace, Texture, TextureLoader } from 'three/webgpu'
import { setDepthImage } from './depth-map.js'
export const mapNode = texture(new Texture())
export const depthNode = texture(new Texture())
const loader = new TextureLoader()
async function setMap(url) {
const map = await loader.loadAsync(url)
map.colorSpace = SRGBColorSpace
mapNode.value.dispose()
mapNode.value = map
}
async function setDepth(url) {
const map = await loader.loadAsync(url)
depthNode.value.dispose()
depthNode.value = map
setDepthImage(map.image)
}
export function setupTextures({ map, depth }) {
return Promise.all([setMap(map), setDepth(depth)])
}
src/lib/blur.js
const TOLERANCE = 1.5 / 255
const SMOOTH_RADIUS = 2
function blurAxis(source, target, { width, height, radius }) {
const span = radius * 2 + 1
for (let row = 0; row < height; row++) {
const offset = row * width
let sum = source[offset] * radius
for (let column = 0; column <= radius; column++) {
sum += source[offset + Math.min(column, width - 1)]
}
for (let column = 0; column < width; column++) {
target[column * height + row] = sum / span
sum -= source[offset + Math.max(column - radius, 0)]
sum += source[offset + Math.min(column + radius + 1, width - 1)]
}
}
}
function boxBlur({ values, width, height }, radius) {
if (radius < 1) return
const transposed = new Float32Array(values.length)
blurAxis(values, transposed, { width, height, radius })
blurAxis(transposed, values, { width: height, height: width, radius })
}
export function smoothBands(field, radius) {
const original = field.values.slice()
boxBlur(field, radius)
for (let i = 0; i < field.values.length; i++) {
field.values[i] = Math.min(
Math.max(field.values[i], original[i] - TOLERANCE),
original[i] + TOLERANCE,
)
}
boxBlur(field, SMOOTH_RADIUS)
}
src/style.css
html,
body {
margin: 0;
height: 100%;
overflow: hidden;
background: #0b0b0c;
font-family: "Instrument Serif", serif;
}
#app {
position: fixed;
inset: 0;
}
canvas {
display: block;
}
#info {
position: fixed;
top: 0;
left: 0;
z-index: 1;
box-sizing: border-box;
width: min(532px, calc(100vw - 24px));
padding: 32px 14px 38px;
color: #fff;
}
#info h1 {
margin: 0 0 16px;
font-size: 18px;
font-weight: 500;
line-height: 1.1;
letter-spacing: -0.035em;
}
#info nav {
display: flex;
flex-wrap: wrap;
gap: 8px 20px;
}
#info a,
#info span {
color: rgb(255 255 255 / 65%);
text-decoration: none;
}
#info a:hover {
color: #fff;
}
.project-links {
font-size: 12px;
font-weight: 400;
line-height: 1.2;
}
#info .tags {
margin-top: 18px;
gap: 8px 12px;
font-size: 12px;
font-weight: 300;
line-height: 1.2;
}
#nav {
position: fixed;
bottom: 16px;
left: 16px;
z-index: 1;
display: flex;
gap: 16px;
font-size: 13px;
line-height: 1;
}
#nav a {
color: rgb(255 255 255 / 58%);
text-decoration: none;
}
#nav a.active,
#nav a:hover {
color: #fff;
}
@media (max-width: 600px) {
#info {
padding: 20px 14px 24px;
}
#info h1 {
margin-bottom: 12px;
}
#info .tags {
margin-top: 12px;
}
}
vite.config.js
import { cp } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { dirname, join, resolve } from 'node:path'
import { defineConfig } from 'vite'
const extensionsDir = join(
dirname(
createRequire(import.meta.url).resolve(
'three/addons/inspector/Inspector.js',
),
),
'extensions',
)
function inspectorExtensions() {
let extensionsOutDir = 'dist/extensions'
return {
name: 'inspector-extensions',
apply: 'build',
configResolved({ root, build }) {
extensionsOutDir = join(resolve(root, build.outDir), 'extensions')
},
async closeBundle() {
await cp(extensionsDir, extensionsOutDir, { recursive: true })
},
}
}
export default defineConfig({
optimizeDeps: {
entries: ['index.html', 'demo2.html', 'demo3.html'],
exclude: ['three'],
},
build: {
rollupOptions: {
input: {
main: 'index.html',
demo2: 'demo2.html',
demo3: 'demo3.html',
},
},
},
plugins: [inspectorExtensions()],
})
Original author attribution실행 안내·자료
Relighting Images with Depth Maps and Three.js
Original author: Dominik Fojcik
Published by Codrops. Copyright (c) 2026 Codrops.
License: MIT, pursuant to the publisher's downloadable-demo grant.
Keep CODROPS-MIT-2026.txt and the original project notices with copies.
Third-party media exceptions and credits are recorded separately in the source inventory.
Media credits and license evidence실행 안내·자료
README.md
## Credits
- [Three.js](https://threejs.org/) — 3D rendering and TSL/WebGPU.
- [Depth Anything 3](https://github.com/ByteDance-Seed/Depth-Anything-3) — depth estimation.
- [Depth Generation Tool](https://depth.fojcikdominik.com/) — used to generate the depth maps.
## License
[MIT](LICENSE)
Bundled dependency licenses실행 안내·자료
three@0.184.0 — LICENSE
The MIT License
Copyright © 2010-2026 three.js authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
BEHIND THE EXAMPLE
이 장면을 만드는 원리
깊이 기울기와 이미지 밝기 기울기를 합친 법선
화면 크기·입력·동시 작업을 고정하고 실제 렌더링 실패와 비용을 관찰합니다.
- 이 예제에서는
- normalNode는 깊이 맵의 네 이웃 차이를 texture 크기·modelScale·coverScale로 보정합니다. 별도로 이미지 luminance 차이를 detail로 더한 뒤 법선을 정규화합니다.
