Twitter
2021年02月20日 - Three.js・WebVR

WebGLで半球ライト

WebGLでポイントライト」でポイントライトを試してみましたが「半球ライティング-wgld.org」を参考に、WebGLで半球ライトを試してみました。

WebGLで半球ライト

Three.jsにもHemisphereLightがありますが、半球ライトは上方からの色と下方からの色を分けられる環境光で、上方からの天空色と下方からの地面色を設定して、シェーダーで計算します。

● 行列演算用ライブラリ

minMatrixb.js リファレンス-wgld.org」を参考に、行列演算用ライブラリを読み込みます。minMatrixb.jsにはトーラスと球体を生成する関数が実装されています。

<script src="js/lib/minMatrixb.js"></script>
<script src="js/script.js" type="module"></script>

● script.js

//===============================================================
// GLSL
//===============================================================
const vertexShader =`
	attribute vec3 position;
	attribute vec3 normal;
	attribute vec4 color;
	uniform mat4 mMatrix;
	uniform mat4 mvpMatrix;
	uniform mat4 invMatrix;
	uniform vec3 skyDirection;
	uniform vec3 lightDirection;
	uniform vec3 eyePosition;

	//天空色
	uniform vec4 skyColor;

	//地面色
	uniform vec4 groundColor;
	varying vec4 vColor;

	void main(void){

		//天空方向のベクトル
		vec3 invSky = normalize(invMatrix * vec4(skyDirection,0.0)).xyz;

		//モデル座標変換の影響を相殺
		vec3 invLight = normalize(invMatrix * vec4(lightDirection,0.0)).xyz;
		vec3 invEye = normalize(invMatrix * vec4(eyePosition,0.0)).xyz;
		vec3 halfLE = normalize(invLight + invEye);

		//拡散光
		float diffuse = clamp(dot(normal,invLight),0.1,1.0);

		//反射光
		float specular = pow(clamp(dot(normal,halfLE),0.1,0.9),50.0);

		//半球ライティング
		float hemisphere = (dot(normal,invSky) + 1.0) * 0.5;
		vec4 ambient = mix(groundColor,skyColor,hemisphere);

		//半球ライティングで描画色を算出
		vColor = color * vec4(vec3(diffuse),1.0) + vec4(vec3(specular),1.0) + ambient;

		gl_Position = mvpMatrix * vec4(position,1.0);
	}
`;
const fragmentShader =`
	precision mediump float;

	varying vec4 vColor;

	void main(void){
		gl_FragColor = vColor;
	}
`;

//===============================================================
// Init & Redering
//===============================================================
window.addEventListener('load',function(){
	init();
  	rendering();
});

let canvas,gl;
let m,mMatrix,vMatrix,pMatrix,vpMatrix,mvpMatrix,invMatrix;
let prg,attLocation,attStride,uniLocation;
let torusData,tVBOList,tIndex;
let sphereData,sVBOList,sIndex;
let skyDirection,lightDirection,skyColor,groundColor,eyePosition;
let count = 0;

function init(){
    canvas = document.getElementById('webgl-canvas');
	canvas.width = window.innerWidth;
	canvas.height = window.innerHeight;

	gl = canvas.getContext('webgl');
	gl.enable(gl.DEPTH_TEST);
	gl.depthFunc(gl.LEQUAL);

	const vShader = createShader(gl.VERTEX_SHADER,vertexShader);
	const fShader = createShader(gl.FRAGMENT_SHADER,fragmentShader);
	prg = createProgram(vShader,fShader);

	attLocation = [];
	attLocation[0] = gl.getAttribLocation(prg,'position');
	attLocation[1] = gl.getAttribLocation(prg,'normal');
	attLocation[2] = gl.getAttribLocation(prg,'color');
	attStride = [];
	attStride[0] = 3;
	attStride[1] = 3;
	attStride[2] = 4;

	uniLocation = [];
	uniLocation[0] = gl.getUniformLocation(prg,'mMatrix');
	uniLocation[1] = gl.getUniformLocation(prg,'mvpMatrix');
	uniLocation[2] = gl.getUniformLocation(prg,'invMatrix');
	uniLocation[3] = gl.getUniformLocation(prg,'skyDirection');
	uniLocation[4] = gl.getUniformLocation(prg,'lightDirection');
	uniLocation[5] = gl.getUniformLocation(prg,'eyePosition');
	uniLocation[6] = gl.getUniformLocation(prg,'skyColor');
	uniLocation[7] = gl.getUniformLocation(prg,'groundColor');

	//トーラスを生成
	torusData = torus(64,64,0.9,1.8,[0.7,0.7,0.7,1.0]);
	const tPosition = createVbo(torusData.p);
	const tNormal = createVbo(torusData.n);
	const tColor = createVbo(torusData.c);
	tVBOList = [tPosition,tNormal,tColor];
	tIndex = createIbo(torusData.i);

	//球体を生成
	sphereData = sphere(64,64,2.1,[0.7,0.7,0.7,1.0]);
	const sPosition = createVbo(sphereData.p);
	const sNormal = createVbo(sphereData.n);
	const sColor = createVbo(sphereData.c);
	sVBOList = [sPosition,sNormal,sColor];
	sIndex = createIbo(sphereData.i);

	m = new matIV();
	mMatrix = m.identity(m.create());
	vMatrix = m.identity(m.create());
	pMatrix = m.identity(m.create());
	vpMatrix = m.identity(m.create());
	mvpMatrix = m.identity(m.create());
	invMatrix = m.identity(m.create());

	//天空方向のベクトル
	skyDirection = [0.0,1.0,0.0];

	//平行光源ベクトル
	lightDirection = [-1.0,-1.0,0.0];

	//天空色
	skyColor = [0.0,0.2,1.0,1.0];

	//地面色
	groundColor = [0.65,0.5,0.20,1.0];

	//視点ベクトル
	eyePosition = [0.0,0.0,20.0];

	m.lookAt(eyePosition,[0,0,0],[0,1,0],vMatrix);
    m.perspective(50,canvas.width/canvas.height,0.1,100,pMatrix);
    m.multiply(pMatrix,vMatrix,vpMatrix);
}

function rendering(){
	count += 0.5;

	const rad = (count % 360) * Math.PI / 180;
	const rad2 = ((count + 180) % 360) * Math.PI / 180;

	gl.clearColor(0.0,0.0,0.0,1.0);
	gl.clearDepth(1.0);
	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

	//トーラスのアニメーション
	setAttribute(tVBOList,attLocation,attStride);
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,tIndex);

	m.identity(mMatrix);
	m.rotate(mMatrix,rad2,[0,0,1],mMatrix);
	m.translate(mMatrix,[4.0,0.0,0.0],mMatrix);
	m.rotate(mMatrix,rad,[1,0,1],mMatrix);
	m.multiply(vpMatrix,mMatrix,mvpMatrix);
	m.inverse(mMatrix,invMatrix);

	gl.uniformMatrix4fv(uniLocation[0],false,mMatrix);
	gl.uniformMatrix4fv(uniLocation[1],false,mvpMatrix);
	gl.uniformMatrix4fv(uniLocation[2],false,invMatrix);
	gl.uniform3fv(uniLocation[3],skyDirection);
	gl.uniform3fv(uniLocation[4],lightDirection);
	gl.uniform3fv(uniLocation[5],eyePosition);
	gl.uniform4fv(uniLocation[6],skyColor);
	gl.uniform4fv(uniLocation[7],groundColor);
	gl.drawElements(gl.TRIANGLES,torusData.i.length,gl.UNSIGNED_SHORT,0);

	//球体のアニメーション
	setAttribute(sVBOList,attLocation,attStride);
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,sIndex);

	m.identity(mMatrix);
	m.rotate(mMatrix,rad,[0,0,1],mMatrix);
	m.translate(mMatrix,[4.0,0.0,0.0],mMatrix);
	m.multiply(vpMatrix,mMatrix,mvpMatrix);
	m.inverse(mMatrix,invMatrix);

	gl.uniformMatrix4fv(uniLocation[0],false,mMatrix);
	gl.uniformMatrix4fv(uniLocation[1],false,mvpMatrix);
	gl.uniformMatrix4fv(uniLocation[2],false,invMatrix);
	gl.drawElements(gl.TRIANGLES,sphereData.i.length,gl.UNSIGNED_SHORT,0);

	gl.flush();

	requestAnimationFrame(rendering);
}

//===============================================================
// Function
//===============================================================
function createShader(shaderType,shaderText){
	const shader = gl.createShader(shaderType);
	gl.shaderSource(shader,shaderText);
	gl.compileShader(shader);
	return shader;
}

function createProgram(vs,fs){
	const program = gl.createProgram();
	gl.attachShader(program, vs);
	gl.attachShader(program, fs);
	gl.linkProgram(program);
	gl.useProgram(program);
	return program;
}

function createVbo(data){
	const vbo = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER,vbo);
	gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(data),gl.STATIC_DRAW);
	gl.bindBuffer(gl.ARRAY_BUFFER,null);
	return vbo;
}

function createIbo(data){
	const ibo = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,ibo);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,new Int16Array(data),gl.STATIC_DRAW);
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,null);
	return ibo;
}

function setAttribute(vbo,attL,attS){
	for(let i in vbo){
		gl.bindBuffer(gl.ARRAY_BUFFER,vbo[i]);
		gl.enableVertexAttribArray(attL[i]);
		gl.vertexAttribPointer(attL[i],attS[i],gl.FLOAT,false,0,0);
	}
}

完成したデモになります。WebGLで半球ライトを試してみました。

  • このエントリーをはてなブックマークに追加

関連記事

前の記事へ

WebGLでキューブ環境マッピング

次の記事へ

WebGLでハーフトーンシェーディング