Демо-код ниже. Три файла index.html
, sky.js
и earth.js
.
В основном сшитые из двух образцов для загрузки текстур и фрагментных шейдеров . Основные вклады:
1-1. Строки worldDayTex: { type: 'map', is: 'uniform' }
и uniform sampler2D worldDayTex;
в шейдере earth.js
. Здесь шейдер WebGL
объявляет форму карты текстуры для ввода.
1-2. Линия skyEl.setAttribute('material', 'worldDayTex', '#worldDay' );
в
index.html
. Здесь карта текстуры вводится из HTML-> WebGL и присваивается униформе.
Затеняя Землю днем и ночью, вы, вероятно, хотите знать, сколько солнечного света падает на данную точку на земле. Можем ли мы с уверенностью предположить, что солнце - ваш единственный источник света? Если это так, то это простое уравнение нормали земной поверхности в каждой точке с направлением солнца . Соответственно, этот пример основан не на выборке интенсивности света как таковой, а только на уравнении между двумя векторами.
Анимация кажется довольно медленной (5 кадров в секунду в моем тестировании), я не знаю почему. Официальный пример фрагмента шейдера аналогичен.
Вам нужно будет предоставить свои собственные текстуры worldDay.png
и worldNight.png
. Для тестирования я использовал изображения на этом сайте .
Вероятно, очевидно, но вам нужно будет исправить путь к aframe-master.min.js
перед использованием или загрузить его в тот же каталог для тестирования.
Протестировано в Firefox версии 59, 64-битная, в Windows В моем тестировании Chrome не поддерживал обработку текстур A-Frame.
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;
}
`
});