Directional Lighting
한방향으로 오는 빛, 광선은 물체 표면을 평행하게 비춘다.
물체의 방향과 빛의 방향이 마주보면 dot 결과 값이 1, 같은 방향을 바라보면 -1
dot(reverseLightDirection,surfaceDirection) = 1.00
- 법선에 대하여
법선은 접선과 수직을 이루는 선이다. 입방체(정육면체)는 각 모서리에 3개의 법선을 갖는다. 이는 큐브의 각 면이 향하는 방향을 나타내려면 3개의 다른 법선이 필요하기 때문이다.
- 3D F의 조명 표현
"use strict";
function main() {
// Get A WebGL context
/** @type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);
// 정점 데이터가 어디로 가야하는지 탐색
var positionLocation = gl.getAttribLocation(program, "a_position");
var normalLocation = gl.getAttribLocation(program, "a_normal");
// Uniform 탐색
var matrixLocation = gl.getUniformLocation(program, "u_matrix");
var colorLocation = gl.getUniformLocation(program, "u_color");
var reverseLightDirectionLocation =
gl.getUniformLocation(program, "u_reverseLightDirection");
// Create a buffer to put positions in
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Put geometry data into buffer
setGeometry(gl);
// 법선을 넣을 buffer 생성
var normalBuffer = gl.createBuffer();
// ARRAY_BUFFER에 바인딩 (think of it as ARRAY_BUFFER = normalBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
// 법선 데이터를 buffer에 넣기 into buffer
setNormals(gl);
function radToDeg(r) {
return r * 180 / Math.PI;
}
function degToRad(d) {
return d * Math.PI / 180;
}
var fieldOfViewRadians = degToRad(60);
var fRotationRadians = 0;
drawScene();
// Setup a ui.
webglLessonsUI.setupSlider("#fRotation", {value: radToDeg(fRotationRadians), slide: updateRotation, min: -360, max: 360});
function updateRotation(event, ui) {
fRotationRadians = degToRad(ui.value);
drawScene();
}
// Draw the scene.
function drawScene() {
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas AND the depth buffer.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Turn on culling. By default backfacing triangles
// will be culled.
gl.enable(gl.CULL_FACE);
// Enable the depth buffer
gl.enable(gl.DEPTH_TEST);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 3; // 3 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionLocation, size, type, normalize, stride, offset);
// normal attribute 활성화
gl.enableVertexAttribArray(normalLocation);
// normal buffer 바인딩
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
// normalBuffer (ARRAY_BUFFER)에서 데이터 가져오는 방법을 attribute에 제시
var size = 3; // 반복마다 3개의 컴포넌트
var type = gl.FLOAT; // 데이터는 32bit 부동 소수점
var normalize = false; // 데이터 정규화 (convert from 0-255 to 0-1)
var stride = 0; // 0 = 다음 위치를 얻기 위해 반복마다 size * sizeof(type) 만큼 앞으로 이동
var offset = 0; // Buffer의 처음부터 시작
gl.vertexAttribPointer(
normalLocation, size, type, normalize, stride, offset);
// Compute the projection matrix
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 1;
var zFar = 2000;
var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);
// Compute the camera's matrix
var camera = [100, 150, 200];
var target = [0, 35, 0];
var up = [0, 1, 0];
var cameraMatrix = m4.lookAt(camera, target, up);
// Make a view matrix from the camera matrix.
var viewMatrix = m4.inverse(cameraMatrix);
// Compute a view projection matrix
var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
// Draw a F at the origin
var worldMatrix = m4.yRotation(fRotationRadians);
// Multiply the matrices.
var worldViewProjectionMatrix = m4.multiply(viewProjectionMatrix, worldMatrix);
// 행렬 설정
gl.uniformMatrix4fv(matrixLocation, false, worldViewProjectionMatrix);
// 사용할 색상 설정
gl.uniform4fv(colorLocation, [0.2, 1, 0.2, 1]); // green
// 빛의 방향 설정
gl.uniform3fv(reverseLightDirectionLocation, m4.normalize([0.5, 0.7, 1]));
// 밝기 설정
gl.uniform1f(shininessLocation, shininess);
// 밝은 색상 설정
gl.uniform3fv(lightColorLocation, m4.normalize([1, 0.6, 0.6])); // 빨간불
// 반사 색상 설정
gl.uniform3fv(specularColorLocation, m4.normalize([1, 0.6, 0.6])); // 빨간불
// Draw the geometry.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 16 * 6;
gl.drawArrays(primitiveType, offset, count);
}
}
// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
var positions = new Float32Array([
// left column front
0, 0, 0,
0, 150, 0,
30, 0, 0,
0, 150, 0,
30, 150, 0,
30, 0, 0,
// top rung front
30, 0, 0,
30, 30, 0,
100, 0, 0,
30, 30, 0,
100, 30, 0,
100, 0, 0,
// middle rung front
30, 60, 0,
30, 90, 0,
67, 60, 0,
30, 90, 0,
67, 90, 0,
67, 60, 0,
// left column back
0, 0, 30,
30, 0, 30,
0, 150, 30,
0, 150, 30,
30, 0, 30,
30, 150, 30,
// top rung back
30, 0, 30,
100, 0, 30,
30, 30, 30,
30, 30, 30,
100, 0, 30,
100, 30, 30,
// middle rung back
30, 60, 30,
67, 60, 30,
30, 90, 30,
30, 90, 30,
67, 60, 30,
67, 90, 30,
// top
0, 0, 0,
100, 0, 0,
100, 0, 30,
0, 0, 0,
100, 0, 30,
0, 0, 30,
// top rung right
100, 0, 0,
100, 30, 0,
100, 30, 30,
100, 0, 0,
100, 30, 30,
100, 0, 30,
// under top rung
30, 30, 0,
30, 30, 30,
100, 30, 30,
30, 30, 0,
100, 30, 30,
100, 30, 0,
// between top rung and middle
30, 30, 0,
30, 60, 30,
30, 30, 30,
30, 30, 0,
30, 60, 0,
30, 60, 30,
// top of middle rung
30, 60, 0,
67, 60, 30,
30, 60, 30,
30, 60, 0,
67, 60, 0,
67, 60, 30,
// right of middle rung
67, 60, 0,
67, 90, 30,
67, 60, 30,
67, 60, 0,
67, 90, 0,
67, 90, 30,
// bottom of middle rung.
30, 90, 0,
30, 90, 30,
67, 90, 30,
30, 90, 0,
67, 90, 30,
67, 90, 0,
// right of bottom
30, 90, 0,
30, 150, 30,
30, 90, 30,
30, 90, 0,
30, 150, 0,
30, 150, 30,
// bottom
0, 150, 0,
0, 150, 30,
30, 150, 30,
0, 150, 0,
30, 150, 30,
30, 150, 0,
// left side
0, 0, 0,
0, 0, 30,
0, 150, 30,
0, 0, 0,
0, 150, 30,
0, 150, 0]);
// Center the F around the origin and Flip it around. We do this because
// we're in 3D now with and +Y is up where as before when we started with 2D
// we had +Y as down.
// We could do by changing all the values above but I'm lazy.
// We could also do it with a matrix at draw time but you should
// never do stuff at draw time if you can do it at init time.
var matrix = m4.xRotation(Math.PI);
matrix = m4.translate(matrix, -50, -75, -15);
for (var ii = 0; ii < positions.length; ii += 3) {
var vector = m4.transformPoint(matrix, [positions[ii + 0], positions[ii + 1], positions[ii + 2], 1]);
positions[ii + 0] = vector[0];
positions[ii + 1] = vector[1];
positions[ii + 2] = vector[2];
}
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}
// 앞쪽을 향하는 법선은 0,0,1 뒤쪽을 향하는 것은 0,0,-1 왼쪽은 -1,0,0 오른쪽은 1,0,0 위쪽은 0,1,0 아래쪽은 0,-1,0
function setNormals(gl) {
var normals = new Float32Array([
// left column front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// top rung front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// middle rung front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// left column back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// top rung back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// middle rung back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// top
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
// top rung right
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// under top rung
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// between top rung and middle
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// top of middle rung
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
// right of middle rung
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// bottom of middle rung.
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// right of bottom
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// bottom
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// left side
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0]);
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
}
main();
<canvas id="canvas"></canvas>
<div id="uiContainer">
<div id="ui">
<div id="fRotation"></div>
</div>
</div>
<!-- vertex shader -->
<script id="vertex-shader-3d" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec3 a_normal;
uniform mat4 u_matrix;
varying vec3 v_normal;
void main() {
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;
// Pass the normal to the fragment shader
v_normal = a_normal;
}
</script>
<!-- fragment shader -->
<script id="fragment-shader-3d" type="x-shader/x-fragment">
precision mediump float;
// Passed in from the vertex shader.
varying vec3 v_normal;
uniform vec3 u_reverseLightDirection;
uniform vec4 u_color;
void main() {
// v_normal이 varying이기 때문에 보간되므로 단위 벡터가 아닙니다.
// 정규화하면 다시 단위 벡터가 됩니다.
vec3 normal = normalize(v_normal);
float light = dot(normal, u_reverseLightDirection);
gl_FragColor = u_color;
// 색상 부분(알파 제외)에만 빛을 곱하기
gl_FragColor.rgb *= light;
}
</script><!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-lessons-ui.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
Point Lighting
3D 공간의 한 점을 선택하고 shader에서 모델 표면의 임의 지점에서 방향을 계산
표면 법선과 각 개별 표면의 내적을 라이트 벡터로 가져오면 표면의 각 지점에서 다른 값을 얻을 수 있다.
"use strict";
function main() {
// Get A WebGL context
/** @type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var normalLocation = gl.getAttribLocation(program, "a_normal");
// lookup uniforms
var worldViewProjectionLocation = gl.getUniformLocation(program, "u_worldViewProjection");
var worldInverseTransposeLocation = gl.getUniformLocation(program, "u_worldInverseTranspose");
var colorLocation = gl.getUniformLocation(program, "u_color");
var shininessLocation = gl.getUniformLocation(program, "u_shininess");
var lightWorldPositionLocation =
gl.getUniformLocation(program, "u_lightWorldPosition");
var viewWorldPositionLocation = gl.getUniformLocation(program, "u_viewWorldPosition");
var worldLocation =
gl.getUniformLocation(program, "u_world");
var lightColorLocation =
gl.getUniformLocation(program, "u_lightColor");
var specularColorLocation =
gl.getUniformLocation(program, "u_specularColor");
// Create a buffer to put positions in
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Put geometry data into buffer
setGeometry(gl);
// Create a buffer to put normals in
var normalBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = normalBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
// Put normals data into buffer
setNormals(gl);
function radToDeg(r) {
return r * 180 / Math.PI;
}
function degToRad(d) {
return d * Math.PI / 180;
}
var fieldOfViewRadians = degToRad(60);
var fRotationRadians = 0;
drawScene();
// Setup a ui.
webglLessonsUI.setupSlider("#fRotation", {value: radToDeg(fRotationRadians), slide: updateRotation, min: -360, max: 360});
function updateRotation(event, ui) {
fRotationRadians = degToRad(ui.value);
drawScene();
}
// Draw the scene.
function drawScene() {
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas AND the depth buffer.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Turn on culling. By default backfacing triangles
// will be culled.
gl.enable(gl.CULL_FACE);
// Enable the depth buffer
gl.enable(gl.DEPTH_TEST);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 3; // 3 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionLocation, size, type, normalize, stride, offset);
// Turn on the normal attribute
gl.enableVertexAttribArray(normalLocation);
// Bind the normal buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
// Tell the attribute how to get data out of normalBuffer (ARRAY_BUFFER)
var size = 3; // 3 components per iteration
var type = gl.FLOAT; // the data is 32bit floating point values
var normalize = false; // normalize the data (convert from 0-255 to 0-1)
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
normalLocation, size, type, normalize, stride, offset);
// Compute the projection matrix
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 1;
var zFar = 2000;
var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);
// Compute the camera's matrix
var camera = [100, 150, 200];
var target = [0, 35, 0];
var up = [0, 1, 0];
var cameraMatrix = m4.lookAt(camera, target, up);
// Make a view matrix from the camera matrix.
var viewMatrix = m4.inverse(cameraMatrix);
// Compute a view projection matrix
var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
// Draw a F at the origin
var worldMatrix = m4.yRotation(fRotationRadians);
// Multiply the matrices.
var worldViewProjectionMatrix = m4.multiply(viewProjectionMatrix, worldMatrix);
var worldInverseMatrix = m4.inverse(worldMatrix);
var worldInverseTransposeMatrix = m4.transpose(worldInverseMatrix);
// Set the matrices
gl.uniformMatrix4fv(worldViewProjectionLocation, false, worldViewProjectionMatrix);
gl.uniformMatrix4fv(worldInverseTransposeLocation, false, worldInverseTransposeMatrix);
gl.uniformMatrix4fv(worldLocation, false, worldMatrix);
// Set the color to use
gl.uniform4fv(colorLocation, [0.2, 1, 0.2, 1]); // green
// set the light position
gl.uniform3fv(lightWorldPositionLocation, [20, 30, 60]);
// Draw the geometry.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 16 * 6;
gl.drawArrays(primitiveType, offset, count);
}
}
// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
var positions = new Float32Array([
// left column front
0, 0, 0,
0, 150, 0,
30, 0, 0,
0, 150, 0,
30, 150, 0,
30, 0, 0,
// top rung front
30, 0, 0,
30, 30, 0,
100, 0, 0,
30, 30, 0,
100, 30, 0,
100, 0, 0,
// middle rung front
30, 60, 0,
30, 90, 0,
67, 60, 0,
30, 90, 0,
67, 90, 0,
67, 60, 0,
// left column back
0, 0, 30,
30, 0, 30,
0, 150, 30,
0, 150, 30,
30, 0, 30,
30, 150, 30,
// top rung back
30, 0, 30,
100, 0, 30,
30, 30, 30,
30, 30, 30,
100, 0, 30,
100, 30, 30,
// middle rung back
30, 60, 30,
67, 60, 30,
30, 90, 30,
30, 90, 30,
67, 60, 30,
67, 90, 30,
// top
0, 0, 0,
100, 0, 0,
100, 0, 30,
0, 0, 0,
100, 0, 30,
0, 0, 30,
// top rung right
100, 0, 0,
100, 30, 0,
100, 30, 30,
100, 0, 0,
100, 30, 30,
100, 0, 30,
// under top rung
30, 30, 0,
30, 30, 30,
100, 30, 30,
30, 30, 0,
100, 30, 30,
100, 30, 0,
// between top rung and middle
30, 30, 0,
30, 60, 30,
30, 30, 30,
30, 30, 0,
30, 60, 0,
30, 60, 30,
// top of middle rung
30, 60, 0,
67, 60, 30,
30, 60, 30,
30, 60, 0,
67, 60, 0,
67, 60, 30,
// right of middle rung
67, 60, 0,
67, 90, 30,
67, 60, 30,
67, 60, 0,
67, 90, 0,
67, 90, 30,
// bottom of middle rung.
30, 90, 0,
30, 90, 30,
67, 90, 30,
30, 90, 0,
67, 90, 30,
67, 90, 0,
// right of bottom
30, 90, 0,
30, 150, 30,
30, 90, 30,
30, 90, 0,
30, 150, 0,
30, 150, 30,
// bottom
0, 150, 0,
0, 150, 30,
30, 150, 30,
0, 150, 0,
30, 150, 30,
30, 150, 0,
// left side
0, 0, 0,
0, 0, 30,
0, 150, 30,
0, 0, 0,
0, 150, 30,
0, 150, 0]);
// Center the F around the origin and Flip it around. We do this because
// we're in 3D now with and +Y is up where as before when we started with 2D
// we had +Y as down.
// We could do by changing all the values above but I'm lazy.
// We could also do it with a matrix at draw time but you should
// never do stuff at draw time if you can do it at init time.
var matrix = m4.xRotation(Math.PI);
matrix = m4.translate(matrix, -50, -75, -15);
for (var ii = 0; ii < positions.length; ii += 3) {
var vector = m4.transformPoint(matrix, [positions[ii + 0], positions[ii + 1], positions[ii + 2], 1]);
positions[ii + 0] = vector[0];
positions[ii + 1] = vector[1];
positions[ii + 2] = vector[2];
}
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}
function setNormals(gl) {
var normals = new Float32Array([
// left column front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// top rung front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// middle rung front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// left column back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// top rung back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// middle rung back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// top
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
// top rung right
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// under top rung
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// between top rung and middle
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// top of middle rung
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
// right of middle rung
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// bottom of middle rung.
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// right of bottom
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// bottom
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// left side
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0]);
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
}
main();
<canvas id="canvas"></canvas>
<div id="uiContainer">
<div id="ui">
<div id="fRotation"></div>
</div>
</div>
<!-- vertex shader -->
<script id="vertex-shader-3d" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec3 a_normal;
//조명의 위치
uniform vec3 u_lightWorldPosition;
uniform vec3 u_viewWorldPosition;
uniform mat4 u_world;
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
// 위치에 행렬을 곱한다.
gl_Position = u_worldViewProjection * a_position;
// 볍선 방향을 정하고 조각 셰이더로 전달
v_normal = mat3(u_worldInverseTranspose) * a_normal;
// 표면의 세계 위치 계산
vec3 surfaceWorldPosition = (u_world * a_position).xyz;
// 표면에서 빛까지의 벡터를 계산해서 fagment shader로 보낸다.
v_surfaceToLight = u_lightWorldPosition - surfaceWorldPosition;
// 뷰 / 카메라에 대한 표면의 벡터 계산
// fragment shader에 전달
v_surfaceToview = u_viewWorldPosition - surfaceWorldPosition;
}
</script>
<!-- fragment shader -->
<script id="fragment-shader-3d" type="x-shader/x-fragment">
precision mediump float;
// vertex shader에서 전달
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_color;
uniform float u_shininess;
uniform vec3 u_lightColor;
uniform vec3 u_specularColor;
void main() {
// v_normal은 가변이기 때문에 보간된다.
// 단위 벡터가 아니기 때문에 정규화하여 단위 벡터로 만든다.
vec3 normal = normalize(v_normal);
vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
vec3 surfaceToviewDirection = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLightDirection + surfaceToViewDirection);
float light = dot(normal, surfaceToLightDirection);
float specular = 0.0;
if(light > 0.0) {
specular = pow(dot(normal, halfVector), u_shininess);
}
gl_FragColor = u_color;
// Lets multiply just the color portion (not the alpha)
// by the light
gl_FragColor.rgb *= light * u_lightColor;
gl_FragColor.rgb += specular * u_specularColor;
}
</script><!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-lessons-ui.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
Spot Lighting
Spot Light 지점에서 방햐을 선택하고 해당 방향의 내적을 취할 수 있다. 점의 limit을 계산하고 limit의 cosine을 취한다. 각 광선의 방향에 대한 Spot light의 선택한 방향의 내적이 dot limit을 추과하면 조명을 수행한다.
"use strict";
function main() {
// Get A WebGL context
/** @type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var normalLocation = gl.getAttribLocation(program, "a_normal");
// lookup uniforms
var worldViewProjectionLocation = gl.getUniformLocation(program, "u_worldViewProjection");
var worldInverseTransposeLocation = gl.getUniformLocation(program, "u_worldInverseTranspose");
var colorLocation = gl.getUniformLocation(program, "u_color");
var shininessLocation = gl.getUniformLocation(program, "u_shininess");
var lightDirectionLocation = gl.getUniformLocation(program, "u_lightDirection");
var limitLocation = gl.getUniformLocation(program, "u_limit");
var lightWorldPositionLocation =
gl.getUniformLocation(program, "u_lightWorldPosition");
var viewWorldPositionLocation =
gl.getUniformLocation(program, "u_viewWorldPosition");
var worldLocation =
gl.getUniformLocation(program, "u_world");
// Create a buffer to put positions in
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Put geometry data into buffer
setGeometry(gl);
// Create a buffer to put normals in
var normalBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = normalBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
// Put normals data into buffer
setNormals(gl);
function radToDeg(r) {
return r * 180 / Math.PI;
}
function degToRad(d) {
return d * Math.PI / 180;
}
var fieldOfViewRadians = degToRad(60);
var fRotationRadians = 0;
var shininess = 150;
var lightRotationX = 0;
var lightRotationY = 0;
var lightDirection = [0, 0, 1]; // this is computed in updateScene
var limit = degToRad(10);
drawScene();
// Setup a ui.
webglLessonsUI.setupSlider("#fRotation", {value: radToDeg(fRotationRadians), slide: updateRotation, min: -360, max: 360});
webglLessonsUI.setupSlider("#lightRotationX", {value: lightRotationX, slide: updatelightRotationX, min: -2, max: 2, precision: 2, step: 0.001});
webglLessonsUI.setupSlider("#lightRotationY", {value: lightRotationY, slide: updatelightRotationY, min: -2, max: 2, precision: 2, step: 0.001});
webglLessonsUI.setupSlider("#limit", {value: radToDeg(limit), slide: updateLimit, min: 0, max: 180});
function updateRotation(event, ui) {
fRotationRadians = degToRad(ui.value);
drawScene();
}
function updatelightRotationX(event, ui) {
lightRotationX = ui.value;
drawScene();
}
function updatelightRotationY(event, ui) {
lightRotationY = ui.value;
drawScene();
}
function updateLimit(event, ui) {
limit = degToRad(ui.value);
drawScene();
}
// Draw the scene.
function drawScene() {
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas AND the depth buffer.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Turn on culling. By default backfacing triangles
// will be culled.
gl.enable(gl.CULL_FACE);
// Enable the depth buffer
gl.enable(gl.DEPTH_TEST);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 3; // 3 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionLocation, size, type, normalize, stride, offset);
// Turn on the normal attribute
gl.enableVertexAttribArray(normalLocation);
// Bind the normal buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
// Tell the attribute how to get data out of normalBuffer (ARRAY_BUFFER)
var size = 3; // 3 components per iteration
var type = gl.FLOAT; // the data is 32bit floating point values
var normalize = false; // normalize the data (convert from 0-255 to 0-1)
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
normalLocation, size, type, normalize, stride, offset);
// Compute the projection matrix
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 1;
var zFar = 2000;
var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);
// Compute the camera's matrix
var camera = [100, 150, 200];
var target = [0, 35, 0];
var up = [0, 1, 0];
var cameraMatrix = m4.lookAt(camera, target, up);
// Make a view matrix from the camera matrix.
var viewMatrix = m4.inverse(cameraMatrix);
// Compute a view projection matrix
var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
// Draw a F at the origin
var worldMatrix = m4.yRotation(fRotationRadians);
// Multiply the matrices.
var worldViewProjectionMatrix = m4.multiply(viewProjectionMatrix, worldMatrix);
var worldInverseMatrix = m4.inverse(worldMatrix);
var worldInverseTransposeMatrix = m4.transpose(worldInverseMatrix);
// Set the matrices
gl.uniformMatrix4fv(worldViewProjectionLocation, false, worldViewProjectionMatrix);
gl.uniformMatrix4fv(worldInverseTransposeLocation, false, worldInverseTransposeMatrix);
gl.uniformMatrix4fv(worldLocation, false, worldMatrix);
// Set the color to use
gl.uniform4fv(colorLocation, [0.2, 1, 0.2, 1]); // green
// set the light position
const lightPosition = [40, 60, 120];
gl.uniform3fv(lightWorldPositionLocation, lightPosition);
// set the camera/view position
gl.uniform3fv(viewWorldPositionLocation, camera);
// set the shininess
gl.uniform1f(shininessLocation, shininess);
// set the spotlight uniforms
// since we don't have a plane like most spotlight examples
// let's point the spot light at the F
{
var lmat = m4.lookAt(lightPosition, target, up);
lmat = m4.multiply(m4.xRotation(lightRotationX), lmat);
lmat = m4.multiply(m4.yRotation(lightRotationY), lmat);
// get the zAxis from the matrix
// negate it because lookAt looks down the -Z axis
lightDirection = [-lmat[8], -lmat[9],-lmat[10]];
}
gl.uniform3fv(lightDirectionLocation, lightDirection);
gl.uniform1f(limitLocation, Math.cos(limit));
// Draw the geometry.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 16 * 6;
gl.drawArrays(primitiveType, offset, count);
}
}
// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
var positions = new Float32Array([
// left column front
0, 0, 0,
0, 150, 0,
30, 0, 0,
0, 150, 0,
30, 150, 0,
30, 0, 0,
// top rung front
30, 0, 0,
30, 30, 0,
100, 0, 0,
30, 30, 0,
100, 30, 0,
100, 0, 0,
// middle rung front
30, 60, 0,
30, 90, 0,
67, 60, 0,
30, 90, 0,
67, 90, 0,
67, 60, 0,
// left column back
0, 0, 30,
30, 0, 30,
0, 150, 30,
0, 150, 30,
30, 0, 30,
30, 150, 30,
// top rung back
30, 0, 30,
100, 0, 30,
30, 30, 30,
30, 30, 30,
100, 0, 30,
100, 30, 30,
// middle rung back
30, 60, 30,
67, 60, 30,
30, 90, 30,
30, 90, 30,
67, 60, 30,
67, 90, 30,
// top
0, 0, 0,
100, 0, 0,
100, 0, 30,
0, 0, 0,
100, 0, 30,
0, 0, 30,
// top rung right
100, 0, 0,
100, 30, 0,
100, 30, 30,
100, 0, 0,
100, 30, 30,
100, 0, 30,
// under top rung
30, 30, 0,
30, 30, 30,
100, 30, 30,
30, 30, 0,
100, 30, 30,
100, 30, 0,
// between top rung and middle
30, 30, 0,
30, 60, 30,
30, 30, 30,
30, 30, 0,
30, 60, 0,
30, 60, 30,
// top of middle rung
30, 60, 0,
67, 60, 30,
30, 60, 30,
30, 60, 0,
67, 60, 0,
67, 60, 30,
// right of middle rung
67, 60, 0,
67, 90, 30,
67, 60, 30,
67, 60, 0,
67, 90, 0,
67, 90, 30,
// bottom of middle rung.
30, 90, 0,
30, 90, 30,
67, 90, 30,
30, 90, 0,
67, 90, 30,
67, 90, 0,
// right of bottom
30, 90, 0,
30, 150, 30,
30, 90, 30,
30, 90, 0,
30, 150, 0,
30, 150, 30,
// bottom
0, 150, 0,
0, 150, 30,
30, 150, 30,
0, 150, 0,
30, 150, 30,
30, 150, 0,
// left side
0, 0, 0,
0, 0, 30,
0, 150, 30,
0, 0, 0,
0, 150, 30,
0, 150, 0]);
// Center the F around the origin and Flip it around. We do this because
// we're in 3D now with and +Y is up where as before when we started with 2D
// we had +Y as down.
// We could do by changing all the values above but I'm lazy.
// We could also do it with a matrix at draw time but you should
// never do stuff at draw time if you can do it at init time.
var matrix = m4.xRotation(Math.PI);
matrix = m4.translate(matrix, -50, -75, -15);
for (var ii = 0; ii < positions.length; ii += 3) {
var vector = m4.transformPoint(matrix, [positions[ii + 0], positions[ii + 1], positions[ii + 2], 1]);
positions[ii + 0] = vector[0];
positions[ii + 1] = vector[1];
positions[ii + 2] = vector[2];
}
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
}
function setNormals(gl) {
var normals = new Float32Array([
// left column front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// top rung front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// middle rung front
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// left column back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// top rung back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// middle rung back
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// top
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
// top rung right
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// under top rung
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// between top rung and middle
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// top of middle rung
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
// right of middle rung
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// bottom of middle rung.
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// right of bottom
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// bottom
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// left side
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0]);
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
}
main();
<canvas id="canvas"></canvas>
<div id="uiContainer">
<div id="ui">
<div id="fRotation"></div>
<div id="lightRotationX"></div>
<div id="lightRotationY"></div>
<div id="limit"></div>
</div>
</div>
<!-- vertex shader -->
<script id="vertex-shader-3d" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec3 a_normal;
uniform vec3 u_lightWorldPosition;
uniform vec3 u_viewWorldPosition;
uniform mat4 u_world;
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
// Multiply the position by the matrix.
gl_Position = u_worldViewProjection * a_position;
// orient the normals and pass to the fragment shader
v_normal = mat3(u_worldInverseTranspose) * a_normal;
// compute the world position of the surfoace
vec3 surfaceWorldPosition = (u_world * a_position).xyz;
// compute the vector of the surface to the light
// and pass it to the fragment shader
v_surfaceToLight = u_lightWorldPosition - surfaceWorldPosition;
// compute the vector of the surface to the view/camera
// and pass it to the fragment shader
v_surfaceToView = u_viewWorldPosition - surfaceWorldPosition;
}
</script>
<!-- fragment shader -->
<script id="fragment-shader-3d" type="x-shader/x-fragment">
precision mediump float;
// Passed in from the vertex shader.
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_color;
uniform float u_shininess;
uniform vec3 u_lightDirection;
uniform float u_limit; // in dot space
void main() {
// because v_normal is a varying it's interpolated
// so it will not be a unit vector. Normalizing it
// will make it a unit vector again
vec3 normal = normalize(v_normal);
vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
vec3 surfaceToViewDirection = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLightDirection + surfaceToViewDirection);
float light = 0.0;
float specular = 0.0;
float dotFromDirection = dot(surfaceToLightDirection,
-u_lightDirection);
if (dotFromDirection >= u_limit) {
light = dot(normal, surfaceToLightDirection);
if (light > 0.0) {
specular = pow(dot(normal, halfVector), u_shininess);
}
}
gl_FragColor = u_color;
// Lets multiply just the color portion (not the alpha)
// by the light
gl_FragColor.rgb *= light;
// Just add in the specular
gl_FragColor.rgb += specular;
}
</script><!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-lessons-ui.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
출처
https://webglfundamentals.org/webgl/lessons/webgl-3d-lighting-directional.html https://webglfundamentals.org/webgl/lessons/webgl-3d-lighting-point.html https://webglfundamentals.org/webgl/lessons/webgl-3d-lighting-spot.html