【问题标题】:How can I warp a shader matrix to match isometric perspective in a 3d scene?如何扭曲着色器矩阵以匹配 3d 场景中的等距透视?
【发布时间】:2019-09-29 11:26:04
【问题描述】:

我将着色器作为纹理应用到等距场景中的平面。平面以 x,z 尺寸平放。我无法让着色器模式与场景中的等距透视相匹配。

这是一个示例,其中着色器通过将方向作为统一传递来随平面旋转(如常规纹理)。

这是着色器纹理的“2d”(正交)投影:

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>

它在等距空间的平面上(旋转相同):

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

旋转说明了着色器如何不模仿等距透视。请注意,着色器图案在旋转时不会相对于平面的角保持固定。

这是片段着色器:

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);
    }

有人能帮我理解如何“扭曲”着色器中的矩阵,以便在旋转时模拟平面在等距空间中的旋转吗?


编辑:我想知道是否应该将扭曲矩阵和应用精确旋转分成两个单独的问题?我正在尝试根据 0 到 TWO_PI 方向更改旋转速度,但也许这是此示例特有的解决方案...

【问题讨论】:

    标签: three.js glsl shader fragment-shader isometric


    【解决方案1】:

    非常有趣的问题(+1)。如何将单位圆转换为椭圆并使用其中内接的 90 度偏移基向量?

    此处忽略矩阵数学 GL/GLSL/C++ 示例:

    CPU 侧平局:

    // 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);
    

    顶点:

    #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);
        }
    

    片段:

    #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);
        }
    

    最后预览:

    重要的东西在顶点着色器中。因此,只需使用u,v 基向量,只需通过公式即可将 world 2D 转换为 Isometric 2D 位置:

    isometric = world.x*u + world.y*v
    

    剩下的就是panzoom ...

    【讨论】:

    • 您好,非常感谢您的回复。我无法将此逻辑移植到我给定的示例中,想知道您是否可以看一下:jsfiddle.net/cmj8bauL/5。我不确定panzoom 在这种情况下是什么(或者pan 完全在等距空间中)......
    • @A__ 您的顶点包含错误的矩阵。你得到了pan,zoom,u,v 而不是所有的矩阵。但是如果你仍然想要矩阵,你可以construct one 匹配我的方程顶点着色器。在片段中你有某种旋转?我不认为这是必要的,因为您只需使用 u,v 基向量(因为它们已经旋转)在 Vertex 中旋转。如果您还想要 z 轴,则应将网格 z 添加到等距位置 y 轴 ...
    • @A__ search v[0] = 1.0 * Math.cos(a * deg); // why +90? 它应该是 v[0] = 1.0 * Math.cos(a + 90.0*deg); 和下一行。 90 deg 因为您希望您的 XY 平面基矢量 u,v 彼此相距 90 度。 “等距”3D 错觉由椭圆完成,因此 x 具有 1.0 半径,y 具有 0.5 半径。 pan 是位于屏幕中心的网格单元格中的位置(因此,如果地图大于屏幕,您可以移动),zoom 调整呈现为没有矩阵的 GL 屏幕的单元格的大小在 范围内...
    • @A__ 如果我没看错,您不是在增加动画角度,而是在缩放时间。没关系,但是角度范围的限制是错误的,应该改为这样的:wire_cube.rotation.y = time * 0.2 * TWO_PI; wire_cube.rotation.y-=floor(wire_cube.rotation.y); wire_cube.rotation.y/=TWO_PI; 因为缩放时间可以导致TWO_PI 范围的任意倍数...
    • “而不是所有矩阵”是什么意思?你是说这些都不是矩阵,但它们应该是?我不认为我理解那句话的措辞。 “但如果你仍然想要矩阵......” - 如果有更简单的方法,我想不会!我觉得我对你的措辞细节感到困惑,你可能不打算按字面意思理解,但我说不出来。我感觉比以前更失落了。你能解释一下我原来的例子吗,也许用伪代码或新的 jsfiddle?
    【解决方案2】:

    解决方案非常简单。我发现我的问题是dupe,原文包含一个说明解决方案的示例(也在下面解释)。

    在我的原始代码中,我使用 vec2 st = gl_FragCoord.xy/u_resolution.xy; 获取像素 xy 位置,这是全局窗口位置。在frag shader中获取相对uv位置需要将uv表面的宽度和高度传入顶点着色器,以便使用threejs获得归一化的像素位置predefinedvec3 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);
    }
    

    然后可以在片段着色器中使用它们:

    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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-28
      • 2012-08-19
      相关资源
      最近更新 更多