Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Texture for illuminated objects in aframe

Tags:

shader

aframe

Is there a way to set a special texture for illuminated object parts in aframe? For example I want to set a night texture for earth where one side that isn't illuminated has another texture.

Edit: It seems I need some kind of shader here but I can't find anything useful for this problem.

Edit 2: The shader has to be flexible and has to work with different types of lighting sources and on objects that aren't a perfect sphere.

like image 234
Niko Lang Avatar asked Apr 26 '18 22:04

Niko Lang


3 Answers

Demo code below. Three files index.html, sky.js, and earth.js.

  1. Mostly stitched together from two samples for loading textures and fragment shaders. The main contributions are:

    1-1. Lines worldDayTex: { type: 'map', is: 'uniform' } and uniform sampler2D worldDayTex; in the shader earth.js. Here, the WebGL shader declares a texture map uniform for input.

    1-2. Line skyEl.setAttribute('material', 'worldDayTex', '#worldDay' ); in index.html. Here, the texture map is input from HTML->WebGL and assigned to the uniform.

  2. Shading the earth from day to night, you probably want to know how much sunlight falls on a given point on earth. Can we safely assume the sun is your only source of light? If so, it's a simple equation of the earth's surface normal at each given point, with the direction of the sun. Accordingly, this example isn't based on sampling light intensity per se, but only an equation between the two vectors.

  3. The animation seems pretty slow (5 fps in my testing), I don't know why. The official fragment shader example is the same way.

  4. You'll need to provide your own textures worldDay.png and worldNight.png. For testing I used images on this site.

  5. Probably obvious, but you'll need to patch the path to aframe-master.min.js before using this, or download it to the same directory for testing.

  6. Tested in Firefox version 59, 64-bit, on Windows. Chrome failed to support the A-Frame texture handling, in my testing.

index.html:

<!DOCTYPE html>
<html>
<head>

<meta charset="utf-8">
<title>Earth day to night example — A-Frame</title>
<meta name="description" content="Earth day to night example — A-Frame">
<script src="./aframe-master.min.js"></script>
<script src="./sky.js"></script>
<script src="./earth.js"></script>
</head>
<body>

<script>
    AFRAME.registerComponent('sun-position-setter', {
        init: function () {
            var skyEl = this.el;
            var orbitEl = this.el.sceneEl.querySelector('#orbit');

            orbitEl.addEventListener('componentchanged',
                function changeSun (evt)
                {
                    var sunPosition;
                    var phase;
                    var phi;

                    if (evt.detail.name !== 'rotation') { return; }

                    sunPosition = orbitEl.getAttribute('rotation');

                    if(sunPosition === null) { return; }

                    phase = (sunPosition.y / 360); // varies from 0 to 1
                    phi = 2 * Math.PI * (phase - 0.5); // varies from 0 to two pi
                    skyEl.setAttribute('material', 'sunDirection',
                        {
                            x: Math.cos(phi),   // use x and y to indicate 2D rotation vector
                            y: Math.sin(phi),
                            z: phase            // use z to indicate the phase
                        }
                    );
                    skyEl.setAttribute('material', 'worldDayTex', '#worldDay' );
                    skyEl.setAttribute('material', 'worldNightTex', '#worldNight' );
                }
            );
        }
    });
</script>

<a-scene background="color: #ECECEC">
<a-assets>
<img id="worldDay" src="./worldDay.png">
<img id="worldNight" src="./worldNight.png">
</a-assets>

<a-entity id="earth" position="0 0 -5" geometry="primitive: sphere; radius: 2" material="shader: earth" sun-position-setter>
    <a-entity id="orbit">
        <a-animation attribute="rotation" from="0 0 0" to="0 360 0" dur="5000" repeat="indefinite" easing="linear"></a-animation>
    </a-entity>
</a-entity>

<a-entity id="sky" geometry="primitive: sphere; radius: 100;" material="shader: sky; side: back" sun-position-setter>
</a-entity>

</a-scene>
</body>
</html>

sky.js

/* global AFRAME */
AFRAME.registerShader('sky', {
  schema: {
    worldDayTex: { type: 'map', is: 'uniform' },
    worldNightTex: { type: 'map', is: 'uniform' },
    sunDirection: { type: 'vec3', default: 'vec3(1,0,0)', is: 'uniform' }
  },

  vertexShader:
    `  // use backtick for multi-line text
    varying vec3 vWorldPosition;
    varying vec2 vUV;

    void main() {
        vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
        vWorldPosition = worldPosition.xyz;
        vUV = uv;

        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
    `
  ,

  fragmentShader: 
    `  // use backtick for multi-line text
    uniform vec3 sunDirection;
    varying vec3 vWorldPosition;
    varying vec2 vUV;

    void main() 
    {
        vec2 sunUV = vec2( sunDirection.z, 0.5 );
        vec2 cmpUV = vec2( 1.0-vUV.x, vUV.y );
        vec2 diffUV = vec2( sunUV.x-cmpUV.x, sunUV.y-cmpUV.y );

        float dist = sqrt( (diffUV.x*diffUV.x) + (diffUV.y*diffUV.y) );

        if( dist<0.01 )
            gl_FragColor.rgb = vec3(1,0.98,0.7);
        else
            gl_FragColor.rgb = vec3(0,0,0);

        gl_FragColor.a = 1.0;
    }
    `

});

earth.js

/* global AFRAME */
AFRAME.registerShader('earth', {
  schema: {
    worldDayTex: { type: 'map', is: 'uniform' },
    worldNightTex: { type: 'map', is: 'uniform' },
    sunDirection: { type: 'vec3', default: 'vec3(1,0,0)', is: 'uniform' }
  },

  vertexShader:
    `  // use backtick for multi-line text
    varying vec3 vWorldPosition;
    varying vec2 vUV;
    varying vec3 vNormal;

    void main() {
        vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
        vWorldPosition = worldPosition.xyz;
        vUV = uv;
        vNormal = normal;

        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
    `
  ,

  fragmentShader:
    `  // use backtick for multi-line text
    uniform sampler2D worldDayTex;
    uniform sampler2D worldNightTex;
    uniform vec3 sunDirection;

    varying vec3 vWorldPosition;
    varying vec2 vUV;
    varying vec3 vNormal;

    void main()
    {
        vec3 worldDayColor = texture2D(worldDayTex, vUV).rgb;
        vec3 worldNightColor = texture2D(worldNightTex, vUV).rgb;

        // 2D rotational direction of the sun is stored in x and y
        // but we need it to rotate around the y axis, so shuffle components
        vec3 sunDir = vec3( sunDirection.x, 0, sunDirection.y );

        // sunFactor +1 means sun directly overhead, noon
        // sunFactor -1 means sun directly behind, midnight',
        // sunFactor 0 means sun at horizon, sunset or sunrise',
        float sunFactor = dot( vNormal, sunDir );

        // amplify so we tend more towards pure day or pure night
        if( sunFactor>0.0 )
            sunFactor = sqrt( sunFactor );
        else
            sunFactor = -sqrt( -sunFactor );

        float sunFactorNorm = (sunFactor + 1.0) * 0.5;

        gl_FragColor.rgb = mix( worldNightColor, worldDayColor, sunFactorNorm );

        gl_FragColor.a = 1.0;
    }
    `
});
like image 66
MichaelsonBritt Avatar answered Jan 02 '23 18:01

MichaelsonBritt


In very rough terms (because it is a lot of work if you are newbie in 3D):

  1. Create 2 earth textures. 1 full day and 1 full night.
  2. Create a fragment shader component that receives the 2 textures and sun light position.
  3. Use the dot product betwen light and earth pixel position to determine if the pixel is under the sun or not.
  4. If under the sun, sample the day texture pixel and put it into render target; if not, sample the night texture pixel and put it into render target;
like image 39
jlvaquero Avatar answered Jan 02 '23 18:01

jlvaquero


 I Hope this will solve your issue


<!DOCTYPE html>
<html>
  <head>
    <title>Hello, WebVR! - A-Frame</title>
    <meta name="description" content="Hello, WebVR! - A-Frame">
    <script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-box position="-1 0.5 -3" rotation="0 45 0" width="1" height="3" color="#4CC3D9" shadow></a-box>     
      <a-plane position="0 0 -3" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>
</html>
like image 33
Bhaskar Reddy Avatar answered Jan 02 '23 19:01

Bhaskar Reddy