I'm working with Three.js and am facing an issue when compiling a fragment shader. Here's my code: https://jsbin.com/cigadibeya/3/edit?html,js,output. I tried to implement animation like here: https://www.shadertoy.com/view/wlyXRD
THREE.WebGLProgram: shader error: 0 gl.VALIDATE_STATUS false gl.getProgramInfoLog Fragment shader is not compiled.
ERROR: 0:241: 'texture' : no matching overloaded function found
ERROR: 0:241: 'xyz' : field selection requires structure or vector on left hand sid
The problem occurs due to the differences between THREE
WebGL
GLSL
versions. You are using version r82
, which uses GLSL 1.0
, which means you have to use texture2D
, which is deprecated in GLSL 3.0
, which ShaderToy
uses.
https://registry.khronos.org/OpenGL-Refpages/gl4/index.php
You can check the current GLSL
version for a given THREE
version.
From r119
the GLSL
version is added automatically.
https://github.com/mrdoob/three.js/wiki/Migration-Guide#r119--r120
const renderer = new THREE.WebGLRenderer();
const gl = renderer.getContext();
const glslVersion = gl.getParameter(gl.SHADING_LANGUAGE_VERSION); console.log('GLSL Version:', glslVersion);
You also need to replace the name of the main function on main
, because ShaderToy
accepts a mainImage
/ fragColor => gl_FragColor
etc. ShaderToy
provides its own environment and abstractions.
Image shaders implement the mainImage() function in order to generate the procedural images by computing a color for each pixel.
But even if you change everything I wrote, you will still see a black screen... because your camera
is set too close.
Try set:
camera.position.z = 10;
//...
return (lightIntensity * (k_d * dotLN + k_s * pow(dotRV, alpha)) * 0.5 + 0.5 * texture2D(iChannel0, ref.xy).xyz);
//...
void main() {
vec2 fragCoord = gl_FragCoord.xy;
vec3 dir = rayDirection(45.0, iResolution.xy, fragCoord);
vec3 eye = vec3(0.0, 0.0, 5.0);
vec2 sdf = shortestDistanceToSurface(eye, dir, MIN_DIST, MAX_DIST);
float dist = sdf.x;
if (dist > MAX_DIST - EPSILON) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
return;
}
vec3 p = eye + dist * dir;
vec3 N = estimateNormal(p);
float l1 = length(cross(vec3(0., 0.0, 1.), N));
l1 = smoothstep(0.65, 0.6, l1) - smoothstep(l1, 0.65, 0.6);
float l2 = length(cross(vec3(0., 0.01, 1.05), N));
l2 = smoothstep(0.65, 0.6, l2) - smoothstep(l2, 0.65, 0.6);
float l3 = length(cross(vec3(0.02, 0.0, 1.1), N));
l3 = smoothstep(0.65, 0.59, l3) - smoothstep(l3, 0.65, 0.59);
vec3 color = vec3(l1, l2, l3);
gl_FragColor = vec4(color, 1.0);
}
Full changes
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r82/three.min.js"></script>
<canvas class="webgl"></canvas>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec2 vertexUV;
void main() {
vertexUV = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision highp float;
uniform vec3 iResolution;
uniform float iTime;
uniform sampler2D iChannel0;
#define M_NONE -1.0
#define M_NOISE 1.0
float hash(float h) {
return fract(sin(h) * 43758.5453123);
}
float noise(vec3 x) {
vec3 p = floor(x);
vec3 f = fract(x);
f = f * f * (3.0 - 2.0 * f);
float n = p.x + p.y * 157.0 + 113.0 * p.z;
return mix(
mix(mix(hash(n + 0.0), hash(n + 1.0), f.x),
mix(hash(n + 157.0), hash(n + 158.0), f.x), f.y),
mix(mix(hash(n + 113.0), hash(n + 114.0), f.x),
mix(hash(n + 270.0), hash(n + 271.0), f.x), f.y), f.z);
}
#define OCTAVES 4
float fbm(vec3 x) {
float v = 0.0;
float a = 0.5;
vec3 shift = vec3(100);
for (int i = 0; i < OCTAVES; ++i) {
v += a * noise(x);
x = x * 2.0 + shift;
a *= 0.5;
}
return v;
}
const int MAX_MARCHING_STEPS = 200;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float EPSILON = 0.002;
float sdBox(vec3 p, vec3 b) {
vec3 q = abs(p) - b;
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) - 2.0;
}
float opSmI(float d1, float d2, float k) {
float h = clamp(0.5 - 0.5 * (d2 - d1) / k, 0.0, 1.0);
return mix(d2, d1, h) + k * h * (1.0 - h);
}
mat3 rotateY(float t) {
float c = cos(t);
float s = sin(t);
return mat3(vec3(c, 0, s),
vec3(0, 1, 0),
vec3(-s, 0, c));
}
mat3 rotateX(float t) {
float c = cos(t);
float s = sin(t);
return mat3(vec3(1, 0, 0),
vec3(0, c, -s),
vec3(0, s, c));
}
mat3 rotateZ(float t) {
float c = cos(t);
float s = sin(t);
return mat3(vec3(c, -s, 0),
vec3(s, c, 0),
vec3(0, 0, 1));
}
float sdCapsule(vec3 p, vec3 a, vec3 b, float r) {
vec3 pa = p - a;
vec3 ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - r;
}
float sceneSDF(vec3 sP) {
float f = sdCapsule(sP, vec3(-1.7, 0., 0.), vec3(1.7, 0., 0.), 0.3);
float d = 0.02 * sin(sP.x * 5.0) * sin((sin(sP.x * 2.0 + iTime) + 1.0) * sP.x * 20.0) + noise(vec3(sP.z * 2.5 - iTime * 1.0)) - 1.2;
return f + 0.5 * d;
}
vec2 shortestDistanceToSurface(vec3 eye, vec3 marchingDirection, float start, float end) {
float depth = start;
for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
float dist = sceneSDF(eye + depth * marchingDirection);
if (dist < EPSILON) {
return vec2(depth, float(i + 1));
}
depth += dist;
if (depth >= end) {
return vec2(end, 0.0);
}
}
return vec2(end, 0.0);
}
vec3 rayDirection(float fieldOfView, vec2 size, vec2 fragCoord) {
vec2 xy = fragCoord - size / 2.0;
float z = size.y / tan(radians(fieldOfView) / 2.0);
return normalize(vec3(xy, -z));
}
vec3 estimateNormal(vec3 p) {
return normalize(vec3(
sceneSDF(vec3(p.x + EPSILON, p.y, p.z)) - sceneSDF(vec3(p.x - EPSILON, p.y, p.z)),
sceneSDF(vec3(p.x, p.y + EPSILON, p.z)) - sceneSDF(vec3(p.x, p.y - EPSILON, p.z)),
sceneSDF(vec3(p.x, p.y, p.z + EPSILON)) - sceneSDF(vec3(p.x, p.y, p.z - EPSILON))
));
}
vec3 phongContribForLight(vec3 k_d, vec3 k_s, float alpha, vec3 p, vec3 eye,
vec3 lightPos, vec3 lightIntensity) {
vec3 N = estimateNormal(p);
vec3 L = normalize(lightPos - p);
vec3 V = normalize(eye - p);
vec3 R = normalize(reflect(-L, N));
vec3 ref = reflect(p - eye, N);
float dotLN = dot(L, N);
float dotRV = dot(R, V);
if (dotLN < 0.0) {
return vec3(0.0, 0.0, 0.0);
}
if (dotRV < 0.0) {
return lightIntensity * (k_d * dotLN);
}
return (lightIntensity * (k_d * dotLN + k_s * pow(dotRV, alpha)) * 0.5 + 0.5 * texture2D(iChannel0, ref.xy).xyz);
}
void main() {
vec2 fragCoord = gl_FragCoord.xy;
vec3 dir = rayDirection(45.0, iResolution.xy, fragCoord);
vec3 eye = vec3(0.0, 0.0, 5.0);
vec2 sdf = shortestDistanceToSurface(eye, dir, MIN_DIST, MAX_DIST);
float dist = sdf.x;
if (dist > MAX_DIST - EPSILON) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
return;
}
vec3 p = eye + dist * dir;
vec3 N = estimateNormal(p);
float l1 = length(cross(vec3(0., 0.0, 1.), N));
l1 = smoothstep(0.65, 0.6, l1) - smoothstep(l1, 0.65, 0.6);
float l2 = length(cross(vec3(0., 0.01, 1.05), N));
l2 = smoothstep(0.65, 0.6, l2) - smoothstep(l2, 0.65, 0.6);
float l3 = length(cross(vec3(0.02, 0.0, 1.1), N));
l3 = smoothstep(0.65, 0.59, l3) - smoothstep(l3, 0.65, 0.59);
vec3 color = vec3(l1, l2, l3);
gl_FragColor = vec4(color, 1.0);
}
</script>
<script>
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene();
// Objects
const geometry = new THREE.SphereGeometry(5, 50, 50);
// Materials
const loader = new THREE.TextureLoader();
const material = new THREE.ShaderMaterial({
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent,
uniforms: {
iResolution: { value: new THREE.Vector3(window.innerWidth, window.innerHeight, 1) },
iTime: { value: 0 },
iChannel0: { value: loader.load('https://i.ibb.co/CsjHTSQ/test.png') }
}
});
// Mesh
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Camera
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 10;
// Renderer
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(window.innerWidth, window.innerHeight);
const gl = renderer.getContext();
const glslVersion = gl.getParameter(gl.SHADING_LANGUAGE_VERSION);
console.log('GLSL Version:', glslVersion);
// Animation loop
function animate() {
requestAnimationFrame(animate);
// Update uniforms
material.uniforms.iTime.value += 0.05;
renderer.render(scene, camera);
}
animate();
</script>