29 Mar 2018
Using a Canvas Texture inside Custom Shaders
This post can be seen as part III coming after two of my previous posts:
High-Performance 3D markups with PointCloud in the Forge Viewer
Using a Dynamic Texture inside Custom Shaders
This time I am showing you another trick in order to display advanced graphics inside our custom shader: using a texture generated from a 2d canvas.
This allows to easily display more advanced shapes without the hassle to implement logic generating the texture. An obvious use case for that approach is displaying text inside our shader. It is also pretty easy to update that text by simply updating the shader similarly to the approach described in the previous article.
Here is how easy it is to use a canvas to generate a texture with Three.js:
const generateCanvasTexture = () => {
const canvas = document.createElement("canvas")
const ctx = canvas.getContext('2d')
ctx.font = '20pt Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(new Date().toLocaleString(),
canvas.width / 2, canvas.height / 2)
const canvasTexture = new THREE.Texture(canvas)
canvasTexture.needsUpdate = true
canvasTexture.flipX = false
canvasTexture.flipY = false
return canvasTexture
}
You can simply switch the generateTexture() method by the new generateCanvasTexture() in the previous shader code and go to town :)
/////////////////////////////////////////////////////////
// Generates custom shader using an updatable
// dynamic texture generated programmatically
//
/////////////////////////////////////////////////////////
createShader (options) {
// Vertex Shader code
const vertexShader = options.vertexShader || `
attribute float pointSize;
attribute vec4 color;
varying vec4 vColor;
void main() {
vec4 vPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * vPosition;
gl_PointSize = pointSize;
vColor = color;
}
`
// Fragment Shader code
const fragmentShader = options.fragmentShader || `
uniform sampler2D texture;
varying vec4 vColor;
void main() {
vec4 tex = texture2D(texture, gl_PointCoord);
if (tex.a < 0.2) discard;
if (vColor.a == 0.0) {
gl_FragColor = vec4(tex.r, tex.g, tex.b, tex.a);
} else {
gl_FragColor = vColor;
}
}
`
const tex = options.texture || defaultTex
// Shader material parameters
const shaderParams = options.shaderParams || {
side: THREE.DoubleSide,
depthWrite: false,
depthTest: false,
fragmentShader,
vertexShader,
opacity: 0.5,
attributes: {
pointSize: {
type: 'f',
value: []
},
color: {
type: 'v4',
value: []
}
},
uniforms: {
texture: {
value: THREE.ImageUtils.loadTexture(tex),
type: 't'
}
}
}
// creates shader material
const material =
new THREE.ShaderMaterial(
shaderParams)
const generateCanvasTexture = () => {
const canvas = document.createElement("canvas")
const ctx = canvas.getContext('2d')
ctx.font = '20pt Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(new Date().toLocaleString(),
canvas.width / 2, canvas.height / 2)
const canvasTexture = new THREE.Texture(canvas)
canvasTexture.needsUpdate = true
canvasTexture.flipX = false
canvasTexture.flipY = false
return canvasTexture
}
const stopwatch = new Stopwatch()
let radius = 0.0
return {
setTexture: (tex) => {
const {texture} = shaderParams.uniforms
texture.value = THREE.ImageUtils.loadTexture(tex)
texture.needsUpdate = true
},
update: () => {
const dt = stopwatch.getElapsedMs() * 0.001
radius += dt * 0.25
radius = radius > 0.5 ? 0.0 : radius
const {texture} = shaderParams.uniforms
texture.value = generateCanvasTexture()
texture.needsUpdate = true
},
material
}
}
See complete sample code at Viewing.Extension.PointCloudMarkup.