Reputation: 1751
I'm applying a shader as a texture to a plane in an isometric scene. The plane lays flat with x,z dimensions. I'm having trouble getting the shader pattern to match the isometric perspective as the scene it's in.
Here's an example where the shader rotates with the plane (like a regular texture) by passing in the orientation as a uniform.
Here's a "2d" (orthographic) projection of the shader texture:
var TWO_PI = Math.PI * 2;
var PI = Math.PI;
var width = window.innerHeight - 50;
var height = window.innerHeight - 50;
var aspect = width / height;
var planeSize = width * 0.75;
var clock = new THREE.Clock();
var camera, scene, renderer;
var plane, geom_plane, mat_plane;
function init() {
// ---------- scene
scene = new THREE.Scene();
// ---------- plane
var plane_w = planeSize;
var plane_h = planeSize;
var geom_plane = new THREE.PlaneGeometry(plane_w,
plane_h,
0);
var mat_plane = new THREE.MeshBasicMaterial({
color: 0xffff00,
side: THREE.DoubleSide
});
var shaderMaterial_plane = new THREE.ShaderMaterial({
uniforms: {
u_resolution: {
value: new THREE.Vector2(planeSize, planeSize)
},
u_rotation_x: {
value: performance.now() * 0.001
},
u_rotation_y: {
value: performance.now() * 0.001
}
},
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
blending: THREE.NormalBlending,
depthTest: true,
transparent: true
});
plane = new THREE.Mesh(geom_plane, shaderMaterial_plane);
scene.add(plane);
// ---------- cam
camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 5000);
camera.position.set(0, 0, planeSize);
camera.lookAt(scene.position);
// ---------- renderer
renderer = new THREE.WebGLRenderer({
antialias: false,
alpha: true
});
renderer.setSize(width, height);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
var time = performance.now() * 0.001;
plane.material.uniforms.u_rotation_x.value = Math.sin(time * 0.2);
plane.material.uniforms.u_rotation_y.value = Math.cos(time * 0.2);
var delta = clock.getDelta();
render();
}
function render() {
renderer.render(scene, camera);
}
init();
animate();
<script type="x-shader/x-vertex" id="vertexshader">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec2 u_resolution; // Canvas size (width,height)
uniform float u_rotation_x;
uniform float u_rotation_y;
mat2 rotate2d(vec2 _angles){
return mat2(_angles.x,
-_angles.x,
_angles.y,
_angles.y);
}
float map(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(1.0,1.0,1.0);
float gradientLength = 0.2;
float t = 18.;
// move matrix in order to set rotation pivot point to center
st -= vec2(0.5);
// rotate
vec2 u_rotation = vec2(u_rotation_x, u_rotation_y);
st = rotate2d(u_rotation) * st;
// move matrix back
st += vec2(0.5);
// apply gradient pattern
vec2 p = vec2(floor(gl_FragCoord.x), floor(gl_FragCoord.y));
float pp = clamp(gl_FragCoord.y,-0.5,st.y);
float val = mod((pp + t), gradientLength);
float alpha = map(val, 0.0, gradientLength, 1.0, 0.0);
gl_FragColor = vec4(color,alpha);
}
</script>
<div id="threejs_canvas"></div>
<script src="https://threejs.org/build/three.min.js"></script>
And here it is on the plane in isometric space (with the same rotation):
var TWO_PI = Math.PI * 2;
var PI = Math.PI;
var width = window.innerHeight - 50;
var height = window.innerHeight - 50;
var aspect = width / height;
var canvasCubeSize = width;
var clock = new THREE.Clock();
var camera, scene, renderer;
var wire_cube;
var plane, geom_plane, mat_plane;
function init() {
// ---------- scene
scene = new THREE.Scene();
// ---------- wire cube
var wire_geometry = new THREE.BoxGeometry(canvasCubeSize / 2, canvasCubeSize / 2, canvasCubeSize / 2);
var wire_material = new THREE.MeshBasicMaterial({
wireframe: true,
color: 0xff0000
});
wire_cube = new THREE.Mesh(wire_geometry, wire_material);
scene.add(wire_cube);
// ---------- plane
var plane_w = canvasCubeSize / 2;
var plane_h = plane_w;
var geom_plane = new THREE.PlaneGeometry(plane_w,
plane_h,
0);
var mat_plane = new THREE.MeshBasicMaterial({
color: 0xffff00,
side: THREE.DoubleSide
});
var shaderMaterial_plane = new THREE.ShaderMaterial({
uniforms: {
u_time: {
value: 1.0
},
u_resolution: {
value: new THREE.Vector2(canvasCubeSize, canvasCubeSize)
},
u_rotation_x: {
value: wire_cube.rotation.y
},
u_rotation_y: {
value: wire_cube.rotation.y
}
},
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
blending: THREE.NormalBlending,
depthTest: true,
transparent: true
});
plane = new THREE.Mesh(geom_plane, shaderMaterial_plane);
plane.rotation.x = -PI / 2;
wire_cube.add(plane);
// ---------- cam
camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 5000);
camera.position.set(canvasCubeSize, canvasCubeSize, canvasCubeSize);
camera.lookAt(scene.position);
// ---------- renderer
renderer = new THREE.WebGLRenderer({
antialias: false,
alpha: true
});
renderer.setSize(width, height);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
var time = performance.now() * 0.001;
wire_cube.rotation.y = time * 0.2;
if (wire_cube.rotation.y >= TWO_PI) {
wire_cube.rotation.y -= TWO_PI;
}
plane.material.uniforms.u_time.value = time * 0.005;
plane.material.uniforms.u_rotation_x.value = Math.sin(wire_cube.rotation.y);
plane.material.uniforms.u_rotation_y.value = Math.cos(wire_cube.rotation.y);
var delta = clock.getDelta();
render();
}
function render() {
renderer.render(scene, camera);
}
init();
animate();
<script type="x-shader/x-vertex" id="vertexshader">
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec2 u_resolution; // Canvas size (width,height)
uniform float u_rotation_x;
uniform float u_rotation_y;
mat2 rotate2d(vec2 _angles){
return mat2(_angles.x,
-_angles.x,
_angles.y,
_angles.y);
}
float map(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(1.0,1.0,1.0);
float gradientLength = 0.2;
float t = 18.;
// move matrix in order to set rotation pivot point to center
st -= vec2(0.5);
// rotate
vec2 u_rotation = vec2(u_rotation_x, u_rotation_y);
st = rotate2d(u_rotation) * st;
// move matrix back
st += vec2(0.5);
// apply gradient pattern
vec2 p = vec2(floor(gl_FragCoord.x), floor(gl_FragCoord.y));
float pp = clamp(gl_FragCoord.y,-0.5,st.y);
float val = mod((pp + t), gradientLength);
float alpha = map(val, 0.0, gradientLength, 1.0, 0.0);
gl_FragColor = vec4(color,alpha);
}
</script>
<div id="threejs_canvas">
</div>
<script src="https://threejs.org/build/three.min.js"></script>
if snippet output is too small see here
The rotation illustrates how the shader isn't mimicking isometric perspective. Notice how the shader pattern doesn't stay fixed relative to the plane's corners as they rotate.
Here's the frag shader:
uniform vec2 u_resolution; // canvas size (width,height)
uniform float u_rotation_x;
uniform float u_rotation_y;
mat2 rotate2d(vec2 _angles){
return mat2(_angles.x,
-_angles.x,
_angles.y,
_angles.y);
}
float map(float value, float min1, float max1, float min2, float max2) {
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(1.0,1.0,1.0);
float gradientLength = 0.2;
float t = 18.;
// move matrix in order to set rotation pivot point to center
st -= vec2(0.5);
// rotate
vec2 u_rotation = vec2(u_rotation_x, u_rotation_y);
st = rotate2d(u_rotation) * st;
// move matrix back
st += vec2(0.5);
// apply gradient pattern
vec2 p = vec2(floor(gl_FragCoord.x), floor(gl_FragCoord.y));
float pp = clamp(gl_FragCoord.y,-0.5,st.y);
float val = mod((pp + t), gradientLength);
float alpha = map(val, 0.0, gradientLength, 1.0, 0.0);
gl_FragColor = vec4(color,alpha);
}
Could someone help me understand how to "warp" the matrix in the shader so that when it rotates, it mimics the rotation of a flat plane in isometric space?
Edit: I'm wondering if warping the matrix and applying accurate rotation should be broken up into two separate issues? I'm playing around with changing the rotation speed based on 0 to TWO_PI orientation but maybe that's a solution specific to this example...
Upvotes: 1
Views: 957
Reputation: 1751
The solution turns out to be pretty simple. I discovered my question is a dupe, the original containing an example (also explained below) that illustrates the solution.
In my original code I'm getting the pixel xy position using vec2 st = gl_FragCoord.xy/u_resolution.xy;
which is the global window position. Getting the relative uv position in the frag shader requires passing in the width and height of the uv surface into the vertex shader in order to get a normalized pixel position using the threejs predefined vec3 position
:
uniform float width;
uniform float height;
varying float x;
varying float y;
void main() {
// Get normalized position
x = position.x / width;
y = position.y / height;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
Then they can be used in the frag shader:
varying float x; // -0.5 to 0.5
varying float y; // -0.5 to 0.5
void main() {
gl_FragColor = vec4(x, y, 0.0, 1.0);
}
Upvotes: 0
Reputation: 51893
Very interesting problem (+1 for that). How about converting unit circle to ellipse and using 90 degree offseted basis vectors inscribed in it?
Ignoring matrix math here GL/GLSL/C++ example:
CPU side draw:
// GLSL Isometric view
float pan[2]={0.5,0.5};
float u[2]={1.0,0.0};
float v[2]={0.5,0.5};
const float deg=M_PI/180.0;
const float da=1.0*deg;;
static float a=0.0;
u[0]=1.0*cos(a);
u[1]=0.5*sin(a);
v[0]=1.0*cos(a+90.0*deg);
v[1]=0.5*sin(a+90.0*deg);
a+=da; if (a>=2.0*M_PI) a-=2.0*M_PI;
glUseProgram(prog_id);
id=glGetUniformLocation(prog_id,"zoom"); glUniform1f(id,0.5);
id=glGetUniformLocation(prog_id,"pan"); glUniform2fv(id,1,pan);
id=glGetUniformLocation(prog_id,"u"); glUniform2fv(id,1,u);
id=glGetUniformLocation(prog_id,"v"); glUniform2fv(id,1,v);
glBegin(GL_QUADS);
glColor3f(1,1,1);
float x=0.0,y=0.0;
glVertex2f(x+0.0,y+0.0);
glVertex2f(x+0.0,y+1.0);
glVertex2f(x+1.0,y+1.0);
glVertex2f(x+1.0,y+0.0);
glEnd();
glUseProgram(0);
Vertex:
#version 120
// Vertex
uniform vec2 pan=vec2(0.5,0.5); // origin [grid cells]
uniform float zoom=0.5; // scale
uniform vec2 u=vec2(1.0,0.0); // basis vectors
uniform vec2 v=vec2(0.5,0.5);
varying vec2 pos; // position [grid cells]
void main()
{
pos=gl_Vertex.xy;
vec2 a=zoom*(gl_Vertex.xy-pan);
gl_Position=vec4((u*a.x)+(v*a.y),0.0,1.0);
}
Fragment:
#version 120
// Fragment
varying vec2 pos; // texture coordinate
void main()
{
float a;
a=2.0*(pos.x+pos.y);
a-=floor(a);
gl_FragColor=vec4(a,a,a,1.0);
}
And finally preview:
The important stuff is in the Vertex shader. So simply use u,v
basis vectors to convert from world 2D to Isometric 2D position simply by formula:
isometric = world.x*u + world.y*v
The rest is just pan
and zoom
...
Upvotes: 1