2021年02月20日 - WebVR・Three.js
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で半球ライトを試してみました。