fix: suffer text is now rendered in forward

This commit is contained in:
FMS-Cat
2021-03-28 21:12:30 +09:00
parent dfbfac46d5
commit 7b4bf5029b
11 changed files with 204 additions and 155 deletions

View File

@@ -6,19 +6,19 @@ import { LightEntity } from './LightEntity';
import { PerspectiveCamera } from '../heck/components/PerspectiveCamera';
export interface CubemapCameraEntityOptions {
root: Entity;
scenes: Entity[];
lights: LightEntity[];
}
export class CubemapCameraEntity extends Entity {
public root: Entity;
public scenes: Entity[];
public camera: PerspectiveCamera;
public readonly target: CubemapRenderTarget;
public constructor( options: CubemapCameraEntityOptions ) {
super();
this.root = options.root;
this.scenes = options.scenes;
this.target = new CubemapRenderTarget( {
width: CUBEMAP_RESOLUTION[ 0 ],
@@ -26,7 +26,7 @@ export class CubemapCameraEntity extends Entity {
} );
this.camera = new CubemapCamera( {
scene: this.root,
scenes: options.scenes,
renderTarget: this.target,
near: 1.0,
far: 20.0,

View File

@@ -16,43 +16,39 @@ import aoFrag from '../shaders/ao.frag';
import quadVert from '../shaders/quad.vert';
import shadingFrag from '../shaders/shading.frag';
export interface CameraEntityOptions {
root: Entity;
export interface DeferredCameraOptions {
scenes: Entity[];
target: RenderTarget;
lights: LightEntity[];
textureIBLLUT: GLCatTexture;
textureEnv: GLCatTexture;
}
export class CameraEntity extends Entity {
public root: Entity;
public camera: PerspectiveCamera;
public constructor( options: CameraEntityOptions ) {
export class DeferredCamera extends Entity {
public constructor( options: DeferredCameraOptions ) {
super();
this.root = options.root;
const cameraTarget = new BufferRenderTarget( {
width: options.target.width,
height: options.target.height,
numBuffers: 4,
name: 'CameraEntity/cameraTarget',
name: 'DeferredCamera/cameraTarget',
} );
this.camera = new PerspectiveCamera( {
scene: this.root,
const camera = new PerspectiveCamera( {
scenes: options.scenes,
renderTarget: cameraTarget,
near: 0.1,
far: 20.0,
name: 'CameraEntity/camera',
name: 'DeferredCamera/camera',
materialTag: 'deferred',
} );
camera.clear = [];
const aoTarget = new BufferRenderTarget( {
width: AO_RESOLUTION_RATIO * options.target.width,
height: AO_RESOLUTION_RATIO * options.target.height,
name: 'CameraEntity/aoTarget',
name: 'DeferredCamera/aoTarget',
} );
const aoMaterial = new Material(
@@ -68,12 +64,12 @@ export class CameraEntity extends Entity {
aoMaterial.addUniformMatrixVector(
'cameraPV',
'Matrix4fv',
this.camera.projectionMatrix.multiply(
camera.projectionMatrix.multiply(
cameraView
).elements
);
},
name: process.env.DEV && 'CameraEntity/ao/setCameraUniforms',
name: process.env.DEV && 'DeferredCamera/ao/setCameraUniforms',
} );
for ( let i = 0; i < 2; i ++ ) { // it doesn't need 2 and 3
@@ -88,7 +84,7 @@ export class CameraEntity extends Entity {
const aoQuad = new Quad( {
material: aoMaterial,
target: aoTarget,
name: process.env.DEV && 'CameraEntity/ao/quad',
name: process.env.DEV && 'DeferredCamera/ao/quad',
} );
const shadingMaterial = new Material(
@@ -102,7 +98,7 @@ export class CameraEntity extends Entity {
const shadingQuad = new Quad( {
material: shadingMaterial,
target: options.target,
name: process.env.DEV && 'CameraEntity/shading/quad',
name: process.env.DEV && 'DeferredCamera/shading/quad',
} );
shadingQuad.clear = [];
@@ -129,7 +125,7 @@ export class CameraEntity extends Entity {
shadingMaterial.addUniformMatrixVector(
'cameraPV',
'Matrix4fv',
this.camera.projectionMatrix.multiply(
camera.projectionMatrix.multiply(
cameraView
).elements
);
@@ -137,8 +133,8 @@ export class CameraEntity extends Entity {
shadingMaterial.addUniform(
'cameraNearFar',
'2f',
this.camera.near,
this.camera.far
camera.near,
camera.far
);
shadingMaterial.addUniform(
@@ -180,7 +176,7 @@ export class CameraEntity extends Entity {
lights.map( ( light ) => light.shadowMap.texture ),
);
},
name: process.env.DEV && 'CameraEntity/shading/setCameraUniforms',
name: process.env.DEV && 'DeferredCamera/shading/setCameraUniforms',
} );
for ( let i = 0; i < 4; i ++ ) {
@@ -196,7 +192,7 @@ export class CameraEntity extends Entity {
shadingMaterial.addUniformTexture( 'samplerRandom', randomTexture.texture );
this.components.push(
this.camera,
camera,
lambdaAoSetCameraUniforms,
aoQuad,
lambda,

View File

@@ -0,0 +1,28 @@
import { Entity } from '../heck/Entity';
import { LightEntity } from './LightEntity';
import { PerspectiveCamera } from '../heck/components/PerspectiveCamera';
import { RenderTarget } from '../heck/RenderTarget';
export interface ForwardCameraOptions {
scenes: Entity[];
target: RenderTarget;
lights: LightEntity[];
}
export class ForwardCamera extends Entity {
public constructor( options: ForwardCameraOptions ) {
super();
const camera = new PerspectiveCamera( {
scenes: options.scenes,
renderTarget: options.target,
near: 0.1,
far: 20.0,
name: 'CameraEntity/camera',
materialTag: 'forward',
} );
camera.clear = false;
this.components.push( camera );
}
}

View File

@@ -10,7 +10,7 @@ import quadVert from '../shaders/quad.vert';
import shadowBlurFrag from '../shaders/shadow-blur.frag';
export interface LightEntityOptions {
root: Entity;
scenes: Entity[];
shadowMapFov?: number;
shadowMapNear?: number;
shadowMapFar?: number;
@@ -21,15 +21,12 @@ export interface LightEntityOptions {
export class LightEntity extends Entity {
public color: [ number, number, number ] = [ 1.0, 1.0, 1.0 ];
public root: Entity;
public camera: PerspectiveCamera;
public shadowMap: BufferRenderTarget;
public constructor( options: LightEntityOptions ) {
super();
this.root = options.root;
const swapOptions = {
width: options.shadowMapWidth ?? 1024,
height: options.shadowMapHeight ?? 1024
@@ -56,7 +53,7 @@ export class LightEntity extends Entity {
near,
far,
renderTarget: swap.o,
scene: this.root,
scenes: options.scenes,
name: process.env.DEV && `${ options.namePrefix }/shadowMapCamera`,
materialTag: 'depth',
} );

View File

@@ -64,19 +64,18 @@ export class SufferTexts extends Entity {
geometryRender.primcount = PARTICLES;
// -- material render --------------------------------------------------------------------------
const deferred = new Material(
const forward = new Material(
sufferTextsRenderVert,
sufferTextsRenderFrag,
{
defines: [ 'DEFERRED 1' ],
initOptions: { geometry: geometryRender, target: dummyRenderTargetFourDrawBuffers },
},
);
const materialsRender = { deferred };
const materialsRender = { forward };
deferred.addUniformTexture( 'samplerRandomStatic', randomTextureStatic.texture );
deferred.addUniformTexture( 'samplerTinyChar', tinyCharTexture );
forward.addUniformTexture( 'samplerRandomStatic', randomTextureStatic.texture );
forward.addUniformTexture( 'samplerTinyChar', tinyCharTexture );
if ( process.env.DEV ) {
if ( module.hot ) {
@@ -86,7 +85,7 @@ export class SufferTexts extends Entity {
'../shaders/suffer-texts-render.frag',
],
() => {
deferred.replaceShader( sufferTextsRenderVert, sufferTextsRenderFrag );
forward.replaceShader( sufferTextsRenderVert, sufferTextsRenderFrag );
}
);
}

View File

@@ -10,7 +10,7 @@ export interface CameraOptions extends ComponentOptions {
renderTarget?: RenderTarget;
projectionMatrix: Matrix4;
materialTag: MaterialTag;
scene?: Entity;
scenes?: Entity[];
clear?: Array<number | undefined> | false;
}
@@ -19,7 +19,7 @@ export abstract class Camera extends Component {
public renderTarget?: RenderTarget;
public scene?: Entity;
public scenes?: Entity[];
public clear: Array<number | undefined> | false = [];
@@ -35,21 +35,21 @@ export abstract class Camera extends Component {
this.visible = false;
this.renderTarget = options.renderTarget;
this.scene = options.scene;
this.scenes = options.scenes;
this.projectionMatrix = options.projectionMatrix;
this.materialTag = options.materialTag;
if ( options.clear !== undefined ) { this.clear = options.clear; }
}
protected __updateImpl( event: ComponentUpdateEvent ): void {
const { renderTarget, scene } = this;
const { renderTarget, scenes } = this;
if ( !renderTarget ) {
throw process.env.DEV && new Error( 'You must assign a renderTarget to the Camera' );
}
if ( !scene ) {
throw process.env.DEV && new Error( 'You must assign a scene to the Camera' );
if ( !scenes ) {
throw process.env.DEV && new Error( 'You must assign scenes to the Camera' );
}
const viewMatrix = event.globalTransform.matrix.inverse!;
@@ -60,16 +60,18 @@ export abstract class Camera extends Component {
glCat.clear( ...this.clear );
}
scene.draw( {
frameCount: event.frameCount,
time: event.time,
renderTarget: renderTarget,
cameraTransform: event.globalTransform,
globalTransform: new Transform(),
viewMatrix,
projectionMatrix: this.projectionMatrix,
camera: this,
materialTag: this.materialTag,
scenes.map( ( scene ) => {
scene.draw( {
frameCount: event.frameCount,
time: event.time,
renderTarget: renderTarget,
cameraTransform: event.globalTransform,
globalTransform: new Transform(),
viewMatrix,
projectionMatrix: this.projectionMatrix,
camera: this,
materialTag: this.materialTag,
} );
} );
if ( process.env.DEV ) {

View File

@@ -23,7 +23,7 @@ export interface CubemapCameraOptions extends ComponentOptions {
renderTarget?: CubemapRenderTarget;
near?: number;
far?: number;
scene?: Entity;
scenes?: Entity[];
clear?: Array<number | undefined> | false;
}
@@ -43,7 +43,7 @@ export class CubemapCamera extends Camera {
...options,
projectionMatrix,
renderTarget: options.renderTarget,
scene: options.scene,
scenes: options.scenes,
clear: options.clear,
materialTag: options.materialTag,
} );

View File

@@ -11,7 +11,7 @@ export interface PerspectiveCameraOptions extends ComponentOptions {
near?: number;
far?: number;
fov?: number;
scene?: Entity;
scenes?: Entity[];
clear?: Array<number | undefined> | false;
}
@@ -30,7 +30,7 @@ export class PerspectiveCamera extends Camera {
...options,
projectionMatrix,
renderTarget: options.renderTarget,
scene: options.scene,
scenes: options.scenes,
clear: options.clear,
} );

View File

@@ -1,17 +1,18 @@
import { Antialias } from './entities/Antialias';
import { Bloom } from './entities/Bloom';
import { BufferRenderTarget } from './heck/BufferRenderTarget';
import { CameraEntity } from './entities/CameraEntity';
import { CanvasRenderTarget } from './heck/CanvasRenderTarget';
import { Component } from './heck/components/Component';
import { Component, ComponentUpdateEvent } from './heck/components/Component';
import { Condition } from './entities/Condition';
import { Cube } from './entities/Cube';
import { CubemapCameraEntity } from './entities/CubemapCameraEntity';
import { DeferredCamera } from './entities/DeferredCamera';
import { Dog } from './heck/Dog';
import { Entity } from './heck/Entity';
import { EnvironmentMap } from './entities/EnvironmentMap';
import { FlashyTerrain } from './entities/FlashyTerrain';
import { FlickyParticles } from './entities/FlickyParticles';
import { ForwardCamera } from './entities/ForwardCamera';
import { Glitch } from './entities/Glitch';
import { IBLLUT } from './entities/IBLLUT';
import { IFSPistons } from './entities/IFSPistons';
@@ -52,10 +53,12 @@ dog.root.components.push( new Lambda( {
// -- util -----------------------------------------------------------------------------------------
class EntityReplacer<T extends Entity> {
private __root: Entity;
public current!: T;
public creator: () => T;
public constructor( creator: () => T, name?: string ) {
public constructor( root: Entity, creator: () => T, name?: string ) {
this.__root = root;
this.creator = creator;
this.replace();
@@ -73,12 +76,12 @@ class EntityReplacer<T extends Entity> {
public replace(): void {
if ( process.env.DEV ) {
if ( this.current ) {
arraySetDelete( dog.root.children, this.current );
arraySetDelete( this.__root.children, this.current );
}
}
this.current = this.creator();
dog.root.children.push( this.current );
this.__root.children.push( this.current );
// not visible by default
this.current.active = false;
@@ -90,8 +93,12 @@ class EntityReplacer<T extends Entity> {
const ibllut = new IBLLUT();
dog.root.children.push( ibllut.entity );
// -- "objects" ------------------------------------------------------------------------------------
// -- deferred stuff -------------------------------------------------------------------------------
const deferredRoot = new Entity();
dog.root.children.push( deferredRoot );
const replacerSphereParticles = new EntityReplacer(
deferredRoot,
() => new SphereParticles(),
'SphereParticles',
);
@@ -101,35 +108,39 @@ if ( process.env.DEV && module.hot ) {
} );
}
const replacerCondition = new EntityReplacer( () => new Condition(), 'Condition' );
const replacerCondition = new EntityReplacer( deferredRoot, () => new Condition(), 'Condition' );
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/Condition', () => {
replacerCondition.replace();
} );
}
const replacerFlashyTerrain = new EntityReplacer( () => new FlashyTerrain(), 'FlashyTerrain' );
const replacerFlashyTerrain = new EntityReplacer(
deferredRoot,
() => new FlashyTerrain(),
'FlashyTerrain',
);
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/FlashyTerrain', () => {
replacerFlashyTerrain.replace();
} );
}
const replacerTrails = new EntityReplacer( () => new Trails(), 'Trails' );
const replacerTrails = new EntityReplacer( deferredRoot, () => new Trails(), 'Trails' );
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/Trails', () => {
replacerTrails.replace();
} );
}
const replacerRings = new EntityReplacer( () => new Rings(), 'Rings' );
const replacerRings = new EntityReplacer( deferredRoot, () => new Rings(), 'Rings' );
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/Rings', () => {
replacerRings.replace();
} );
}
const replacerCube = new EntityReplacer( () => new Cube(), 'Cube' );
const replacerCube = new EntityReplacer( deferredRoot, () => new Cube(), 'Cube' );
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/Cube', () => {
replacerCube.replace();
@@ -137,6 +148,7 @@ if ( process.env.DEV && module.hot ) {
}
const replacerFlickyParticles = new EntityReplacer(
deferredRoot,
() => new FlickyParticles(),
'FlickyParticles',
);
@@ -146,30 +158,35 @@ if ( process.env.DEV && module.hot ) {
} );
}
const replacerSufferTexts = new EntityReplacer(
() => new SufferTexts(),
'SufferTexts',
);
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/SufferTexts', () => {
replacerSufferTexts.replace();
} );
}
const replacerWobbleball = new EntityReplacer( () => new Wobbleball(), 'Wobbleball' );
const replacerWobbleball = new EntityReplacer( deferredRoot, () => new Wobbleball(), 'Wobbleball' );
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/Wobbleball', () => {
replacerWobbleball.replace();
} );
}
const replacerIFSPistons = new EntityReplacer( () => new IFSPistons(), 'IFSPistons' );
const replacerIFSPistons = new EntityReplacer( deferredRoot, () => new IFSPistons(), 'IFSPistons' );
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/IFSPistons', () => {
replacerIFSPistons.replace();
} );
}
// -- forward stuff --------------------------------------------------------------------------------
const forwardRoot = new Entity();
dog.root.children.push( forwardRoot );
const replacerSufferTexts = new EntityReplacer(
forwardRoot,
() => new SufferTexts(),
'SufferTexts',
);
if ( process.env.DEV && module.hot ) {
module.hot.accept( './entities/SufferTexts', () => {
replacerSufferTexts.replace();
} );
}
// -- things that is not an "object" ---------------------------------------------------------------
const swapOptions = {
width: canvasRenderTarget.width,
@@ -187,9 +204,9 @@ const swap = new Swap(
} ),
);
const replacerLightFirst = new EntityReplacer( () => {
const replacerLightFirst = new EntityReplacer( dog.root, () => {
const light = new LightEntity( {
root: dog.root,
scenes: [ dog.root ],
shadowMapFov: 90.0,
shadowMapNear: 1.0,
shadowMapFar: 20.0,
@@ -201,9 +218,9 @@ const replacerLightFirst = new EntityReplacer( () => {
}, 'LightFirst' );
const lightFirst = replacerLightFirst.current;
const replacerLightPink = new EntityReplacer( () => {
const replacerLightPink = new EntityReplacer( dog.root, () => {
const light = new LightEntity( {
root: dog.root,
scenes: [ dog.root ],
shadowMapFov: 90.0,
shadowMapNear: 1.0,
shadowMapFar: 20.0,
@@ -234,7 +251,7 @@ if ( process.env.DEV && module.hot ) {
// dog.root.children.push( light2 );
const cubemapCamera = new CubemapCameraEntity( {
root: dog.root,
scenes: [ dog.root ],
lights: [
lightFirst,
lightPink,
@@ -248,8 +265,59 @@ const environmentMap = new EnvironmentMap( {
} );
dog.root.children.push( environmentMap );
const camera = new CameraEntity( {
root: dog.root,
// -- camera ---------------------------------------------------------------------------------------
const cameraOnUpdate = ( { time }: ComponentUpdateEvent ): void => {
const r = auto( 'Camera/rot/r' );
const t = auto( 'Camera/rot/t' );
const p = auto( 'Camera/rot/p' );
const x = auto( 'Camera/pos/x' );
const y = auto( 'Camera/pos/y' );
const z = auto( 'Camera/pos/z' );
const roll = auto( 'Camera/roll' );
const shake = auto( 'Camera/shake' );
const st = Math.sin( t );
const ct = Math.cos( t );
const sp = Math.sin( p );
const cp = Math.cos( p );
const wubPosAmp = 0.01;
const wubPosTheta = 3.0 * time;
const wubTarAmp = 0.02;
const wubTarTheta = 4.21 * time;
deferredCamera.transform.lookAt(
new Vector3( [
r * ct * sp + wubPosAmp * Math.sin( wubPosTheta ),
r * st + wubPosAmp * Math.sin( 2.0 + wubPosTheta ),
r * ct * cp + wubPosAmp * Math.sin( 4.0 + wubPosTheta ),
] ),
new Vector3( [
wubTarAmp * Math.sin( wubTarTheta ),
wubTarAmp * Math.sin( 2.0 + wubTarTheta ),
wubTarAmp * Math.sin( 4.0 + wubTarTheta ),
] ),
undefined,
0.02 * Math.sin( 2.74 * time ) + roll,
);
deferredCamera.transform.position = deferredCamera.transform.position.add(
new Vector3( [ x, y, z ] )
);
if ( shake > 0.0 ) {
deferredCamera.transform.position = deferredCamera.transform.position.add(
new Vector3( [
Math.sin( 145.0 * time ),
Math.sin( 2.0 + 148.0 * time ),
Math.sin( 4.0 + 151.0 * time )
] ).scale( shake )
);
}
};
const deferredCamera = new DeferredCamera( {
scenes: [ deferredRoot ],
target: swap.o,
lights: [
lightFirst,
@@ -259,61 +327,30 @@ const camera = new CameraEntity( {
textureIBLLUT: ibllut.texture,
textureEnv: environmentMap.texture,
} );
camera.camera.clear = [ 0.0, 0.0, 0.0, 0.0 ];
camera.components.unshift( new Lambda( {
onUpdate: ( { time } ) => {
const r = auto( 'Camera/rot/r' );
const t = auto( 'Camera/rot/t' );
const p = auto( 'Camera/rot/p' );
const x = auto( 'Camera/pos/x' );
const y = auto( 'Camera/pos/y' );
const z = auto( 'Camera/pos/z' );
const roll = auto( 'Camera/roll' );
const shake = auto( 'Camera/shake' );
const st = Math.sin( t );
const ct = Math.cos( t );
const sp = Math.sin( p );
const cp = Math.cos( p );
const wubPosAmp = 0.01;
const wubPosTheta = 3.0 * time;
const wubTarAmp = 0.02;
const wubTarTheta = 4.21 * time;
camera.transform.lookAt(
new Vector3( [
r * ct * sp + wubPosAmp * Math.sin( wubPosTheta ),
r * st + wubPosAmp * Math.sin( 2.0 + wubPosTheta ),
r * ct * cp + wubPosAmp * Math.sin( 4.0 + wubPosTheta ),
] ),
new Vector3( [
wubTarAmp * Math.sin( wubTarTheta ),
wubTarAmp * Math.sin( 2.0 + wubTarTheta ),
wubTarAmp * Math.sin( 4.0 + wubTarTheta ),
] ),
undefined,
0.02 * Math.sin( 2.74 * time ) + roll,
);
camera.transform.position = camera.transform.position.add(
new Vector3( [ x, y, z ] )
);
if ( shake > 0.0 ) {
camera.transform.position = camera.transform.position.add(
new Vector3( [
Math.sin( 145.0 * time ),
Math.sin( 2.0 + 148.0 * time ),
Math.sin( 4.0 + 151.0 * time )
] ).scale( shake )
);
}
},
name: process.env.DEV && 'main/updateCamera',
deferredCamera.components.unshift( new Lambda( {
onUpdate: cameraOnUpdate,
name: process.env.DEV && 'main/updateDeferredCamera',
} ) );
dog.root.children.push( camera );
dog.root.children.push( deferredCamera );
const forwardCamera = new ForwardCamera( {
scenes: [ forwardRoot ],
target: swap.o,
lights: [
lightFirst,
lightPink,
// light2
],
} );
forwardCamera.components.unshift( new Lambda( {
onUpdate: cameraOnUpdate,
name: process.env.DEV && 'main/updateForwardCamera',
} ) );
dog.root.children.push( forwardCamera );
// -- post -----------------------------------------------------------------------------------------
swap.swap();
const antialias = new Antialias( {
input: swap.i,

View File

@@ -17,16 +17,11 @@ uniform float time;
uniform sampler2D samplerRandomStatic;
uniform sampler2D samplerTinyChar;
#ifdef DEFERRED
layout (location = 0) out vec4 fragPosition;
layout (location = 1) out vec4 fragNormal;
layout (location = 2) out vec4 fragColor;
layout (location = 3) out vec4 fragWTF;
#endif
out vec4 fragColor;
// == main procedure ===============================================================================
void main() {
if ( vLife > 1.0 ) { discard; }
if ( vLife > 1.3 ) { discard; }
float tex = texture( samplerTinyChar, vUv ).x;
if ( tex < 0.5 ) { discard; }
@@ -35,13 +30,8 @@ void main() {
color = mix(
step( 2.0, mod( 40.0 * vLife + vec3( 0.0, 1.0, 2.0 ), 3.0 ) ),
color,
smoothstep( 0.0, 0.3, vLife ) * smoothstep( 1.0, 0.7, vLife )
smoothstep( 0.0, 0.3, vLife ) * smoothstep( 1.3, 1.0, vLife )
);
#ifdef DEFERRED
fragPosition = vPosition;
fragNormal = vec4( vNormal, 1.0 );
fragColor = vec4( color, 1.0 );
fragWTF = vec4( vec3( 0.0 ), MTL_UNLIT );
#endif
fragColor = vec4( color, 1.0 );
}

View File

@@ -39,7 +39,7 @@ void main() {
vLife = tex0.w;
float char = tex0.z;
char += 64.0 * ( min( vLife, 0.3 ) + max( vLife, 0.7 ) - 1.0 );
char += 64.0 * ( min( vLife, 0.3 ) + max( vLife, 1.0 ) - 1.3 );
vUv = yflip( 0.5 + 0.499 * position );
vUv = ( vUv + floor( mod( vec2( char / vec2( 1.0, 16.0 ) ), 16.0 ) ) ) / 16.0;