@threlte/extras
<RadialGradientTexture>
A reactive radial gradient texture that attaches to the “map” property of its parent. The underlying texture uses an OffscreenCanvas and a CanvasTexture and is assigned the same colorspace as the renderer.
<script lang="ts">
import Scene from './Scene.svelte'
import type { ToneMapping, Wrapping } from 'three'
import { Canvas } from '@threlte/core'
import {
ClampToEdgeWrapping,
MirroredRepeatWrapping,
RepeatWrapping,
ACESFilmicToneMapping,
AgXToneMapping,
CineonToneMapping,
LinearToneMapping,
NeutralToneMapping,
NoToneMapping,
ReinhardToneMapping
} from 'three'
import { Checkbox, Color, Folder, List, Pane, Slider } from 'svelte-tweakpane-ui'
const toneMappingOptions: Record<PropertyKey, ToneMapping> = {
ACESFilmic: ACESFilmicToneMapping,
AgX: AgXToneMapping,
Cineon: CineonToneMapping,
Linear: LinearToneMapping,
NeutralToneMapping,
None: NoToneMapping,
Reinhard: ReinhardToneMapping
}
const wrappingOptions: Record<PropertyKey, Wrapping> = {
ClampToEdge: ClampToEdgeWrapping,
MirroredRepeat: MirroredRepeatWrapping,
Repeat: RepeatWrapping
}
const canvasSize = 1024
const halfCanvasSize = 0.5 * 1024
// from center to one of the corners
const diagonal = Math.floor(Math.hypot(halfCanvasSize, halfCanvasSize))
let sceneClearColor = $state('#000000')
let sceneToneMapping = $state(AgXToneMapping)
let gradientStartColor = $state('#ffff00')
let gradientEndColor = $state('#ff00ff')
let gradientInnerRadius = $state(0)
let gradientOuterRadiusNumber = $state(diagonal)
let gradientUseOuterRadiusAuto = $state(true)
let textureCenterX = $state(0)
let textureCenterY = $state(0)
let textureOffsetX = $state(0)
let textureOffsetY = $state(0)
let textureRepeatX = $state(1)
let textureRepeatY = $state(1)
let textureRotationDegrees = $state(0)
let textureWrapS: Wrapping = $state(ClampToEdgeWrapping)
let textureWrapT: Wrapping = $state(ClampToEdgeWrapping)
let textureRotation = $derived((Math.PI / 180) * textureRotationDegrees)
let gradientOuterRadius = $derived(
gradientUseOuterRadiusAuto ? 'auto' : gradientOuterRadiusNumber
)
</script>
<Pane position="fixed">
<List
bind:value={sceneToneMapping}
options={toneMappingOptions}
label="tone mapping"
/>
<Color
bind:value={sceneClearColor}
label="clear color"
/>
<Folder title="gradient props">
<Color
bind:value={gradientStartColor}
label="start color"
/>
<Color
bind:value={gradientEndColor}
label="end color"
/>
<Slider
bind:value={gradientInnerRadius}
label="inner radius"
min={0}
max={gradientUseOuterRadiusAuto ? diagonal : gradientOuterRadiusNumber}
step={1}
/>
<Checkbox
bind:value={gradientUseOuterRadiusAuto}
label="use 'auto' outer radius"
/>
{#if !gradientUseOuterRadiusAuto}
<Slider
bind:value={gradientOuterRadiusNumber}
label="outer radius"
min={0}
max={canvasSize}
step={1}
on:change={({ detail }) => {
gradientInnerRadius = Math.min(detail.value, gradientInnerRadius)
}}
/>
{/if}
</Folder>
<Folder title="texture props">
<List
bind:value={textureWrapS}
label="wrapS"
options={wrappingOptions}
/>
<List
bind:value={textureWrapT}
label="wrapT"
options={wrappingOptions}
/>
<Slider
label="centerX"
bind:value={textureCenterX}
min={-0.5}
max={1.5}
step={0.5}
/>
<Slider
label="centerY"
bind:value={textureCenterY}
min={-0.5}
max={1.5}
step={0.5}
/>
<Slider
label="offsetX"
bind:value={textureOffsetX}
min={-2}
max={2}
step={1}
/>
<Slider
label="offsetY"
bind:value={textureOffsetY}
min={-2}
max={2}
step={1}
/>
<Slider
label="repeatX"
bind:value={textureRepeatX}
min={0}
max={5}
step={1}
/>
<Slider
label="repeatY"
bind:value={textureRepeatY}
min={0}
max={5}
step={1}
/>
<Slider
label="rotation (degrees)"
bind:value={textureRotationDegrees}
min={-360}
max={360}
step={1}
/>
</Folder>
</Pane>
<div>
<Canvas>
<Scene
{gradientEndColor}
{gradientInnerRadius}
{gradientOuterRadius}
{gradientStartColor}
{sceneClearColor}
{sceneToneMapping}
{textureCenterX}
{textureCenterY}
{textureOffsetX}
{textureOffsetY}
{textureRepeatX}
{textureRepeatY}
{textureRotation}
{textureWrapS}
{textureWrapT}
{canvasSize}
/>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import type { ColorRepresentation, ToneMapping, Wrapping } from 'three'
import type { GradientStop } from '@threlte/extras'
import { DoubleSide } from 'three'
import { RadialGradientTexture, OrbitControls } from '@threlte/extras'
import { T, useThrelte } from '@threlte/core'
type SceneProps = {
canvasSize: number
gradientEndColor: ColorRepresentation
gradientInnerRadius: number
gradientOuterRadius: number
gradientStartColor: ColorRepresentation
sceneClearColor: ColorRepresentation
sceneToneMapping: ToneMapping
textureCenterX: number
textureCenterY: number
textureOffsetX: number
textureOffsetY: number
textureRepeatX: number
textureRepeatY: number
textureRotation: number
textureWrapS: Wrapping
textureWrapT: Wrapping
}
let {
canvasSize,
gradientInnerRadius,
gradientOuterRadius,
gradientEndColor,
gradientStartColor,
sceneClearColor,
sceneToneMapping,
textureCenterX,
textureCenterY,
textureOffsetX,
textureOffsetY,
textureRepeatX,
textureRepeatY,
textureRotation,
textureWrapS,
textureWrapT
}: SceneProps = $props()
let stops: GradientStop[] = $derived([
{ color: gradientStartColor, offset: 0 },
{ color: gradientEndColor, offset: 1 }
])
const { invalidate, renderer, toneMapping } = useThrelte()
$effect(() => {
toneMapping.set(sceneToneMapping)
invalidate()
})
$effect(() => {
renderer.setClearColor(sceneClearColor)
invalidate()
})
</script>
<T.PerspectiveCamera
makeDefault
position.z={5}
>
<OrbitControls />
</T.PerspectiveCamera>
<T.Mesh scale={2}>
<T.PlaneGeometry />
<T.MeshBasicMaterial side={DoubleSide}>
<RadialGradientTexture
width={canvasSize}
height={canvasSize}
innerRadius={gradientInnerRadius}
outerRadius={gradientOuterRadius}
center.x={textureCenterX}
center.y={textureCenterY}
offset.x={textureOffsetX}
offset.y={textureOffsetY}
repeat.x={textureRepeatX}
repeat.y={textureRepeatY}
rotation={textureRotation}
wrapS={textureWrapS}
wrapT={textureWrapT}
{stops}
/>
</T.MeshBasicMaterial>
</T.Mesh>
Attaching the Texture
The texture is automatically attached to the map
property of its parent. You can disable this behaviour by setting the attach
prop to false
. This may be useful if you want to create the texture but use it somewhere else.
<script>
let texture = null
</script>
<RadialGradientTexture
attach={false}
bind:ref={texture}
/>
<SomeComponent {texture} />
Radius Props
The innerRadius
and outerRadius
props control the size of the gradient. The innerRadius
prop should be less than the outerRadius
prop but it is not enforced. If outerRadius
is set to 'auto'
the outerRadius
is effectively set to the radius of the circle that circumscribes the canvas. For example, if the canvas’s width and height are 1024, and outerRadius
is set to 'auto'
, the radius that will be used is sqrt(1024**2 + 1024**2) or roughly 724.
It is also not enforced that innerRadius
and outerRadius
are both positive.
Gradient Stops
<RadialGradientTexture>
accepts a stops
prop which is an array of color stops that define the gradient. A stop is defined by two things; an offset
and a color
. Gradient stops are identical to how you would use them with a 2D context, notably the offset
should be a number between 0 and 1 inclusive. Stop colors can be any valid color representation in ThreeJS. Here are a couple examples of valid stops.
<RadialGradientTexture
stops={[
{ color: 'black', offset: 0 },
{ color: 'white', offset: 1 }
]}
/>
<RadialGradientTexture
stops={[
{ color: '#00ffff', offset: 0 },
{ color: '#ff00ff', offset: 0.5 },
{ color: '#ffff00', offset: 1 }
]}
/>
You can even mix and match color representations
<RadialGradientTexture
stops={[
{ color: 'red', offset: 0 },
{ color: 0xff_00_00, offset: 0.25 },
{ color: 'rgb(255, 0, 0)', offset: 0.5 },
{ color: '#ff0000', offset: 0.75 },
{ color: new Color(new Color(new Color())).set(1, 0, 0), offset: 1 }
]}
/>
Adjusting Scene Colors
If the colors in your scene do not match the color in your stops, you may need to adjust the tone mapping of the scene. ToneMapping
constants are imported from the three library.
<script>
import { useThrelte } from '@threlte/core'
import { LinearToneMapping } from 'three'
const { toneMapping } = useThrelte()
toneMapping.set(LinearToneMapping)
</script>
Component Signature
<RadialGradientTexture>
extends
<T
.
CanvasTexture>
and supports all its props, slot props, bindings and events.