如何为自定义着色器材质正确设置光照

时间:2017-04-25 21:26:46

标签: javascript three.js glsl

我有一个使用自定义着色器在three.js中使用高度图的演示。它工作正常(现在)。 jsfiddle是HERE。着色器本身就在这里:

<script id="vertexShader" type="x-shader/x-vertex">
        uniform sampler2D   vTexture;
        uniform float       vScale;
        uniform vec3        vLut[ 256 ];

        varying vec3        vColor;

        void main() {

            vec4 heightData = texture2D( vTexture, uv );

            // if the map is grayscale it doesn't matter if you use r, g, or b.
            float vAmount = heightData.r;

            // fetch the color from the lookup table so it gets passed to the fragment shader
            int index = int(heightData.r * 255.0);
            vColor = vLut[index];

            // move the position along the normal
            vec3 newPosition = position + normal * vScale * vAmount;

            gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );
        }
    </script>

    <script id="fragmentShader" type="x-shader/x-vertex">

        varying vec3  vColor;

        void main() {

            gl_FragColor = vec4(vColor, 1.0);
        }
    </script>

问题是它没有正确点亮所以阴影等不正确。我怀疑,因为我已经改变了着色器中的顶点位置,我应该以某种方式更新顶点法线,但也许它不止于此。我尝试了各种各样的&#34; * needsUpdating = true&#34;但无济于事。我已经在three.js文档和网络上进行了处理,没有找到圣杯。

任何建议或指示都将不胜感激。

1 个答案:

答案 0 :(得分:1)

由于您使用自定义着色器,因此您需要在着色器中自行进行光照计算。这可能会对您有所帮助:Using lights in three.js shader

这是自定义照明的完整示例(online demo):

<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/85/three.min.js"> </script>
  <script id="vertShader" type="shader">
varying vec2 vUv;
varying vec3 vecPos;
varying vec3 vecNormal;

void main() {
  vUv = uv;
  // Since the light is in camera coordinates,
  // I'll need the vertex position in camera coords too
  vecPos = (modelViewMatrix * vec4(position, 1.0)).xyz;
  // That's NOT exacly how you should transform your
  // normals but this will work fine, since my model
  // matrix is pretty basic
  vecNormal = (modelViewMatrix * vec4(normal, 0.0)).xyz;
  gl_Position = projectionMatrix *
                vec4(vecPos, 1.0);
}
</script>
  <script id="fragShader" type="shader">
precision highp float;

varying vec2 vUv;
varying vec3 vecPos;
varying vec3 vecNormal;

uniform float lightIntensity;
uniform sampler2D textureSampler;

struct PointLight {
  vec3 color;
  vec3 position; // light position, in camera coordinates
  float distance; // used for attenuation purposes. Since
                  // we're writing our own shader, it can
                  // really be anything we want (as long as
                  // we assign it to our light in its
                  // "distance" field
};

uniform PointLight pointLights[NUM_POINT_LIGHTS];

void main(void) {
  // Pretty basic lambertian lighting...
  vec4 addedLights = vec4(0.0,
                          0.0,
                          0.0,
                          1.0);
  for(int l = 0; l < NUM_POINT_LIGHTS; l++) {
      vec3 lightDirection = normalize(vecPos
                            - pointLights[l].position);
      addedLights.rgb += clamp(dot(-lightDirection,
                               vecNormal), 0.0, 1.0)
                         * pointLights[l].color
                         * lightIntensity;
  }
  gl_FragColor = texture2D(textureSampler, vUv)
                 * addedLights;
}
</script>
</head>
<body style="margin: 0px;" onload="init()">
<script>
  // standard global variables
  var scene, camera, renderer, textureLoader, light;

  // Character 3d object
  var character = null;

  // FUNCTIONS
  function init() {
    // SCENE
    scene = new THREE.Scene();
    textureLoader = new THREE.TextureLoader();

    // CAMERA
    var SCREEN_WIDTH = window.innerWidth;
    var SCREEN_HEIGHT = window.innerHeight;
    var VIEW_ANGLE = 45;
    var ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT;
    var NEAR = 0.1;
    var FAR = 1000;
    camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT,
      NEAR, FAR);
    scene.add(camera);
    camera.position.set(0,0,5);
    camera.lookAt(scene.position);

    // RENDERER
    renderer = new THREE.WebGLRenderer({
      antialias:true,
      alpha: true
    });
    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    var container = document.body;
    container.appendChild( renderer.domElement );

    // Create light
    light = new THREE.PointLight(0xffffff, 1.0);
    // We want it to be very close to our character
    light.position.set(0.0, 0.0, 0.1);
    scene.add(light);

    // Create character
    character = buildCharacter();
    scene.add(character);

    // Start animation
    animate();
  }

  var buildCharacter = (function() {
    var _geo = null;

    // Share the same geometry across all planar objects
    function getPlaneGeometry() {
      if(_geo == null) {
        _geo = new THREE.PlaneGeometry(1.0, 1.0);
      }

      return _geo;
    };

    return function() {
      var g = getPlaneGeometry();
      var creatureImage = textureLoader.load('g.png');
      creatureImage.magFilter = THREE.NearestFilter;

      var mat = new THREE.ShaderMaterial({
        uniforms: THREE.UniformsUtils.merge([
          THREE.UniformsLib['lights'],
          {
            lightIntensity: {type: 'f', value: 1.0},
            textureSampler: {type: 't', value: null}
          }
        ]),
        vertexShader: document.getElementById('vertShader').text,
        fragmentShader: document.getElementById('fragShader').text,
        transparent: true,
        lights: true
      });
      // THREE.UniformsUtils.merge() call THREE.clone() on
      // each uniform. We don't want our texture to be
      // duplicated, so I assign it to the uniform value
      // right here.
      mat.uniforms.textureSampler.value = creatureImage;

      var obj = new THREE.Mesh(g, mat);

      return obj;
    }
  })();

  function animate() {
    // Update light profile
    var timestampNow = new Date().getTime()/1000.0;
    var lightIntensity = 0.75 +
      0.25 * Math.cos(timestampNow *
        Math.PI);

    character.material.uniforms
      .lightIntensity.value = lightIntensity;
    light.color.setHSL(lightIntensity, 1.0, 0.5);

    // Render scene
    renderer.render(scene, camera);
    requestAnimationFrame(animate);
  }
</script>
</body>
</html>