I'm trying to load 2 images as textures, interpolate between them in the fragment shader and apply the color to a plane. Unfortunately, I can't even display a single texture.
I'm creating the plane and loading images as follows:
const planeGeometry = new THREE.PlaneBufferGeometry(imageSize * screenRatio, imageSize);
loader.load(
"textures/IMG_6831.jpeg",
(image) => {
texture1 = new THREE.MeshBasicMaterial({ map: image });
//texture1 = new THREE.Texture({ image: image });
}
)
It displayed the image on the plane correctly when I used MeshBasicMaterial directly as a material for Mesh as normal. When trying to do the same in a custom shader I get only black color:
const uniforms = {
texture1: { type: "sampler2D", value: texture1 },
texture2: { type: "sampler2D", value: texture2 }
};
const planeMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
fragmentShader: fragmentShader(),
vertexShader: vertexShader()
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
Shaders:
const vertexShader = () => {
return `
varying vec2 vUv;
void main() {
vUv = uv;
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
`;
}
const fragmentShader = () => {
return `
uniform sampler2D texture1;
uniform sampler2D texture2;
varying vec2 vUv;
void main() {
vec4 color1 = texture2D(texture1, vUv);
vec4 color2 = texture2D(texture2, vUv);
//vec4 fColor = mix(color1, color2, vUv.y);
//fColor.a = 1.0;
gl_FragColor = color1;
}
`;
}
To fix it, I tried:
texture1
and texture2
are defined before passing them to the shaderTHREE.Texture
instead of THREE.MeshBasicMaterial
texture1: { type: "t", value: texture1 },
I think the problem might be in the part of passing the texture as uniform to the shader. I might be using the wrong type somewhere.. I'd appreciate any help!
Assuming loader
is a TextureLoader
then it calls you with a texture so
loader.load(
"textures/IMG_6831.jpeg",
(texture) => {
texture1 = texture;
}
)
otherwise while it's not a bug, type
is not used for three.js uniforms anymore.
const planeGeometry = new THREE.PlaneBufferGeometry(1, 1);
const loader = new THREE.TextureLoader();
let texture1;
loader.load(
"https://i.imgur.com/KjUybBD.png",
(texture) => {
texture1 = texture;
start();
}
);
const vertexShader = () => {
return `
varying vec2 vUv;
void main() {
vUv = uv;
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
`;
}
const fragmentShader = () => {
return `
uniform sampler2D texture1;
uniform sampler2D texture2;
varying vec2 vUv;
void main() {
vec4 color1 = texture2D(texture1, vUv);
vec4 color2 = texture2D(texture2, vUv);
//vec4 fColor = mix(color1, color2, vUv.y);
//fColor.a = 1.0;
gl_FragColor = color1;
}
`;
}
function start() {
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.Camera();
const uniforms = {
texture1: { value: texture1 },
texture2: { value: texture1 },
};
const planeMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
fragmentShader: fragmentShader(),
vertexShader: vertexShader()
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
renderer.render(scene, camera);
}
<script src="https://cdn.jsdelivr.net/npm/three@0.111.0/build/three.min.js"></script>
also TextureLoader
returns a texture so if you're rendering continously in a requestAnimationFrame loop you can do write it like this
const planeGeometry = new THREE.PlaneBufferGeometry(1, 1);
const loader = new THREE.TextureLoader();
const texture1 = loader.load("https://i.imgur.com/KjUybBD.png");
const texture2 = loader.load("https://i.imgur.com/UKBsvV0.jpg");
const vertexShader = () => {
return `
varying vec2 vUv;
void main() {
vUv = uv;
vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * modelViewPosition;
}
`;
}
const fragmentShader = () => {
return `
uniform sampler2D texture1;
uniform sampler2D texture2;
varying vec2 vUv;
void main() {
vec4 color1 = texture2D(texture1, vUv);
vec4 color2 = texture2D(texture2, vUv);
gl_FragColor = mix(color1, color2, vUv.y);
}
`;
}
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.Camera();
const uniforms = {
texture1: { value: texture1 },
texture2: { value: texture2 },
};
const planeMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
fragmentShader: fragmentShader(),
vertexShader: vertexShader()
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://cdn.jsdelivr.net/npm/three@0.111.0/build/three.min.js"></script>
The textures will be blank but three.js will update them when the images have loaded.
You might be interested in some more up to date tutorials