Babylon.js로 셰이더 만들기

게시 됨: 2022-03-10
빠른 요약 ↬ 셰이더는 GPU의 강력한 성능을 최대한 활용하려는 경우 핵심 개념입니다. Babylon.js 덕분에 그들이 어떻게 작동하는지 이해하고 쉽게 내면의 힘을 실험할 수 있도록 도와드리겠습니다. 실험하기 전에 내부적으로 어떻게 작동하는지 확인해야 합니다. 하드웨어 가속 3D를 처리할 때 메인 CPU와 GPU라는 두 개의 CPU를 처리해야 합니다. GPU는 일종의 극도로 전문화된 CPU입니다.

GPU의 성능을 최대한 활용하려면 셰이더가 핵심 개념입니다. Babylon.js 덕분에 그들이 어떻게 작동하는지 이해하고 쉽게 내면의 힘을 실험할 수 있도록 도와드리겠습니다.

어떻게 작동합니까?

실험하기 전에 내부적으로 어떻게 작동하는지 확인해야 합니다.

하드웨어 가속 3D를 처리할 때 메인 CPU와 GPU라는 두 개의 CPU를 처리해야 합니다. GPU는 일종의 극도로 전문화된 CPU입니다.

SmashingMag에 대한 추가 정보:

  • Babylon.js로 크로스 플랫폼 WebGL 게임 만들기
  • 웹 게임에서 게임패드 API 사용하기
  • 다각형 모델링 및 Three.js 소개
  • 반응형 8비트 드럼 머신을 만드는 방법
점프 후 더! 아래에서 계속 읽기 ↓

GPU는 CPU를 사용하여 설정한 상태 머신입니다. 예를 들어 CPU는 삼각형 대신 선을 렌더링하도록 GPU를 구성합니다. 투명도가 켜져 있는지 여부를 정의합니다. 등등.

모든 상태가 설정되면 CPU는 렌더링할 대상을 정의할 수 있습니다. 지오메트리.

지오메트리는 다음으로 구성됩니다.

  • 정점이라고 하고 정점 버퍼라는 배열에 저장된 점의 목록,
  • 인덱스 버퍼라는 배열에 저장된 면(또는 삼각형)을 정의하는 인덱스 목록입니다.

CPU의 마지막 단계는 지오메트리를 렌더링하는 방법을 정의하는 것입니다. 이 작업을 위해 CPU는 GPU에서 셰이더를 정의합니다. 셰이더는 GPU가 렌더링해야 하는 각 정점과 픽셀에 대해 GPU가 실행할 코드 조각입니다. (꼭짓점(또는 여러 개가 있는 경우 꼭짓점)은 3D의 "점"입니다.

셰이더에는 정점 셰이더와 픽셀(또는 조각) 셰이더의 두 가지 종류가 있습니다.

그래픽 파이프라인

셰이더에 대해 알아보기 전에 뒤로 물러나겠습니다. 픽셀을 렌더링하기 위해 GPU는 CPU에서 정의한 지오메트리를 취하고 다음을 수행합니다.

  • 인덱스 버퍼를 사용하여 세 개의 정점을 모아 삼각형을 정의합니다.
  • 인덱스 버퍼는 정점 인덱스 목록을 포함합니다. 이것은 인덱스 버퍼의 각 항목이 정점 버퍼의 정점 번호임을 의미합니다.
  • 이것은 정점 복제를 방지하는 데 정말 유용합니다.

예를 들어 다음 인덱스 버퍼는 [1 2 3 1 3 4]의 두 얼굴 목록입니다. 첫 번째 면에는 꼭짓점 1, 꼭짓점 2, 꼭짓점 3이 있습니다. 두 번째 면에는 꼭짓점 1, 꼭짓점 3, 꼭짓점 4가 있습니다. 따라서 이 기하학에는 4개의 꼭짓점이 있습니다.

(큰 버전 보기)

정점 셰이더는 삼각형의 각 정점에 적용됩니다. 정점 셰이더의 주요 목표는 각 정점(3D 정점의 2D 화면에 투영)에 대한 픽셀을 생성하는 것입니다.

(큰 버전 보기)

이 3개의 픽셀(화면에서 2D 삼각형을 정의)을 사용하여 GPU는 픽셀에 연결된 모든 값(최소한 해당 위치)을 보간하고 픽셀 셰이더는 2D 삼각형에 포함된 모든 픽셀에 적용됩니다. 모든 픽셀에 대한 색상 생성:

(큰 버전 보기)

이 프로세스는 인덱스 버퍼에 의해 정의된 모든 면에 대해 수행됩니다.

분명히 GPU는 병렬 특성으로 인해 많은 얼굴에 대해 이 단계를 동시에 처리하고 정말 좋은 성능을 얻을 수 있습니다.

GLSL

우리는 삼각형을 렌더링하기 위해 GPU에 정점 셰이더와 픽셀 셰이더의 두 가지 셰이더가 필요하다는 것을 방금 보았습니다. 이러한 셰이더는 GLSL(그래픽 라이브러리 셰이더 언어)이라는 언어로 작성됩니다. C처럼 보입니다.

다음은 일반적인 정점 셰이더의 샘플입니다.

 precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }

정점 셰이더 구조

정점 셰이더에는 다음이 포함됩니다.

  • 속성 . 속성은 정점의 일부를 정의합니다. 기본적으로 정점은 최소한 위치( vector3:x, y, z )를 포함해야 합니다. 그러나 개발자는 더 많은 정보를 추가할 수 있습니다. 예를 들어, 이전 셰이더에는 uv (즉, 2D 텍스처를 3D 개체에 적용할 수 있는 텍스처 좌표)라는 vector2 가 있습니다.
  • 유니폼 . 유니폼은 셰이더에서 사용하고 CPU에서 정의하는 변수입니다. 여기에 있는 유일한 유니폼은 정점(x, y, z)의 위치를 ​​화면(x, y)에 투영하는 데 사용되는 행렬입니다.
  • 다양한 . 가변 변수는 정점 셰이더에 의해 생성되어 픽셀 셰이더로 전송되는 값입니다. 여기에서 정점 셰이더는 vUV ( uv 의 단순 복사본) 값을 픽셀 셰이더에 전송합니다. 이것은 픽셀이 여기에서 위치 및 텍스처 좌표로 정의됨을 의미합니다. 이 값은 GPU에서 보간되고 픽셀 셰이더에서 사용됩니다.
  • 메인 . main 이라는 이름의 함수는 각 꼭짓점에 대해 GPU에서 실행하는 코드이며 최소한 gl_position (화면에서 현재 꼭짓점의 위치)에 대한 값을 생성해야 합니다.

샘플에서 정점 셰이더가 매우 간단하다는 것을 알 수 있습니다. 연결된 픽셀의 위치를 ​​정의하기 위해 gl_position 이라는 시스템 변수( gl_ 로 시작)를 생성하고 gl_ 라는 가변 변수를 vUV 합니다.

행렬 뒤에 있는 부두교

셰이더에 대한 점은 worldViewProjection 이라는 행렬이 있고 이 행렬을 사용하여 정점 위치를 gl_position 변수에 투영한다는 것입니다. 멋지네요. 하지만 이 행렬의 값을 어떻게 구합니까? 유니폼이므로 CPU 측에서 정의해야 합니다(JavaScript 사용).

이것은 3D 작업의 복잡한 부분 중 하나입니다. 복잡한 수학을 이해해야 합니다(또는 나중에 볼 Babylon.js와 같은 3D 엔진을 사용해야 함).

worldViewProjection 행렬은 세 가지 다른 행렬의 조합입니다.

(큰 버전 보기)

결과 행렬을 사용하면 시점과 현재 개체의 위치, 크기 및 회전과 관련된 모든 것을 고려하면서 3D 정점을 2D 픽셀로 변환할 수 있습니다.

이 매트릭스를 생성하고 최신 상태로 유지하는 것은 3D 개발자로서 귀하의 책임입니다.

셰이더로 돌아가기

정점 셰이더가 모든 정점에서 실행되면(그러면 세 번) 올바른 gl_positionvUV 값을 가진 3개의 픽셀을 갖게 됩니다. GPU는 이러한 픽셀로 생성된 삼각형에 포함된 모든 픽셀에서 이러한 값을 보간합니다.

그런 다음 각 픽셀에 대해 픽셀 셰이더를 실행합니다.

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }

픽셀(또는 조각) 셰이더 구조

픽셀 셰이더의 구조는 정점 셰이더의 구조와 유사합니다.

  • 다양한 . 가변 변수는 정점 셰이더에 의해 생성되어 픽셀 셰이더에 전달되는 값입니다. 여기에서 픽셀 셰이더는 정점 셰이더에서 vUV 값을 받습니다.
  • 유니폼 . 유니폼은 셰이더에서 사용하고 CPU에서 정의하는 변수입니다. 여기에 있는 유일한 유니폼은 텍스처 색상을 읽는 데 사용되는 도구인 샘플러뿐입니다.
  • 메인 . main 이라는 이름의 함수는 각 픽셀에 대해 GPU가 실행하는 코드이며 최소한 gl_FragColor 에 대한 값(즉, 현재 픽셀의 색상)을 생성해야 합니다.

이 픽셀 셰이더는 매우 간단합니다. 정점 셰이더의 텍스처 좌표를 사용하여 텍스처에서 색상을 읽습니다(다시 정점에서 가져옴).

문제는 셰이더가 개발될 때 많은 WebGL 코드를 처리해야 하기 때문에 절반밖에 되지 않는다는 것입니다. 실제로 WebGL은 매우 강력하지만 매우 낮은 수준이며 버퍼 생성에서 정점 구조 정의에 이르기까지 모든 작업을 직접 수행해야 합니다. 또한 모든 수학을 수행하고, 모든 상태를 설정하고, 텍스처 로딩을 처리하는 등의 작업을 수행해야 합니다.

너무 열심히? BABYLON.ShaderMaterial To The Rescue

나는 당신이 무슨 생각을 하는지 압니다. "셰이더는 정말 훌륭하지만 WebGL의 내부 배관이나 수학에 신경쓰고 싶지 않습니다."

그리고 당신이 옳습니다! 이것은 완벽하게 정당한 질문이며 이것이 바로 내가 Babylon.js를 만든 이유입니다!

Babylon.js를 사용하려면 먼저 간단한 웹 페이지가 필요합니다.

 <!DOCTYPE html> <html> <head> <title>Babylon.js</title> <script src="Babylon.js"></script> <script type="application/vertexShader"> precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Normal varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } </script> <script type="application/fragmentShader"> precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); } </script> <script src="index.js"></script> <style> html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; margin: 0px; overflow: hidden; } #renderCanvas { width: 100%; height: 100%; touch-action: none; -ms-touch-action: none; } </style> </head> <body> <canvas></canvas> </body> </html>

셰이더는 <script> 태그로 정의됩니다. Babylon.js를 사용하면 별도의 파일( .fx 파일)로 정의할 수도 있습니다.

  • Babylon.js 소스
  • GitHub 저장소

마지막으로 주요 JavaScript 코드는 다음과 같습니다.

 "use strict"; document.addEventListener("DOMContentLoaded", startGame, false); function startGame() { if (BABYLON.Engine.isSupported()) { var canvas = document.getElementById("renderCanvas"); var engine = new BABYLON.Engine(canvas, false); var scene = new BABYLON.Scene(engine); var camera = new BABYLON.ArcRotateCamera("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene); camera.attachControl(canvas); // Creating sphere var sphere = BABYLON.Mesh.CreateSphere("Sphere", 16, 5, scene); var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] }); amigaMaterial.setTexture("textureSampler", new BABYLON.Texture("amiga.jpg", scene)); sphere.material = amigaMaterial; engine.runRenderLoop(function () { sphere.rotation.y += 0.05; scene.render(); }); } };

내가 BABYLON.ShaderMaterial 을 사용하여 셰이더를 컴파일하고 연결하고 처리하는 부담을 없애는 것을 볼 수 있습니다.

BABYLON.ShaderMaterial 을 생성할 때 셰이더를 저장하는 데 사용되는 DOM 요소 또는 셰이더가 있는 파일의 기본 이름을 지정해야 합니다. 파일을 사용하기로 선택한 경우 각 셰이더에 대한 파일을 생성하고 basename.vertex.fxbasename.fragment.fx 패턴을 사용해야 합니다. 그런 다음 다음과 같은 재질을 만들어야 합니다.

 var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader", { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] });

사용하는 속성 및 유니폼의 이름도 지정해야 합니다.

그런 다음 setTexture , setFloat , setFloats , setColor3 , setColor4 , setVector2 , setVector3 , setVector4 , setMatrix 함수를 사용하여 유니폼 및 샘플러의 값을 직접 설정할 수 있습니다.

아주 간단하죠?

그리고 Babylon.js와 BABYLON.ShaderMaterial 을 사용한 이전 worldViewProjection 행렬을 기억하시나요? 당신은 그것에 대해 걱정할 필요가 없습니다! BABYLON.ShaderMaterial 은 유니폼 목록에서 선언할 것이기 때문에 자동으로 계산합니다.

BABYLON.ShaderMaterial 은 다음 행렬도 처리할 수 있습니다.

  • world ,
  • view ,
  • projection ,
  • worldView ,
  • worldViewProjection .

더 이상 수학이 필요하지 않습니다. 예를 들어 sphere.rotation.y += 0.05 를 실행할 때마다 구의 world 행렬이 생성되어 GPU로 전송됩니다.

라이브 결과를 직접 확인하십시오.

나만의 셰이더 만들기(CYOS)

이제 더 크게 가서 자신만의 셰이더를 동적으로 만들고 결과를 즉시 볼 수 있는 페이지를 만들어 보겠습니다. 이 페이지에서는 이전에 논의한 것과 동일한 코드를 사용하고 BABYLON.ShaderMaterial 객체를 사용하여 생성할 셰이더를 컴파일하고 실행할 것입니다.

Create Your Own Shader(CYOS)에 ACE 코드 편집기를 사용했습니다. 구문 강조 기능이 있는 놀라운 코드 편집기입니다. 부담 없이 살펴보세요.

첫 번째 콤보 상자를 사용하여 미리 정의된 셰이더를 선택할 수 있습니다. 우리는 그들 각각을 직후에 볼 것입니다.

두 번째 콤보 상자를 사용하여 셰이더를 미리 보는 데 사용되는 메시(즉, 3D 개체)를 변경할 수도 있습니다.

컴파일 버튼은 셰이더에서 새 BABYLON.ShaderMaterial 을 만드는 데 사용됩니다. 이 버튼에서 사용하는 코드는 다음과 같습니다.

 // Compile shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "normal", "uv"], uniforms: ["world", "worldView", "worldViewProjection"] }); var refTexture = new BABYLON.Texture("ref.jpg", scene); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = new BABYLON.Texture("amiga.jpg", scene); shaderMaterial.setTexture("textureSampler", amigaTexture); shaderMaterial.setTexture("refSampler", refTexture); shaderMaterial.setFloat("time", 0); shaderMaterial.setVector3("cameraPosition", BABYLON.Vector3.Zero()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial;

엄청나게 간단하죠? 자료는 미리 계산된 세 가지 행렬( world , worldViewworldViewProjection )을 보낼 준비가 되었습니다. 정점은 위치, 법선 및 텍스처 좌표와 함께 제공됩니다. 두 개의 텍스처도 이미 로드되어 있습니다.

amiga.jpg (큰 버전 보기)
ref.jpg (큰 버전 보기)

마지막으로 renderLoop 에서 두 가지 편리한 유니폼을 업데이트합니다.

  • 하나는 time 이라고 하고 재미있는 애니메이션을 얻습니다.
  • 다른 하나는 카메라 위치를 셰이더로 가져오는 cameraPosition 입니다(조명 방정식에 유용).

 engine.runRenderLoop(function () { mesh.rotation.y += 0.001; if (shaderMaterial) { shaderMaterial.setFloat("time", time); time += 0.02; shaderMaterial.setVector3("cameraPosition", camera.position); } scene.render(); });

기본 셰이더

CYOS에 정의된 최초의 셰이더인 기본 셰이더부터 시작하겠습니다.

우리는 이미 이 셰이더를 알고 있습니다. gl_position 을 계산하고 텍스처 좌표를 사용하여 모든 픽셀의 색상을 가져옵니다.

픽셀 위치를 계산하려면 worldViewProjection 행렬과 꼭짓점의 위치만 있으면 됩니다.

 precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }

텍스처 좌표( uv )는 수정되지 않은 상태로 픽셀 셰이더로 전송됩니다.

Chrome에서 요구하기 때문에 정점 및 픽셀 셰이더 모두에 대해 첫 번째 줄에 precision mediump float 를 추가해야 합니다. 더 나은 성능을 위해 전체 정밀도 부동 값을 사용하지 않음을 지정합니다.

픽셀 셰이더는 훨씬 더 간단합니다. 텍스처 좌표를 사용하고 텍스처 색상을 가져와야 하기 때문입니다.

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }

우리는 이전에 textureSampler 유니폼이 amiga 텍스처로 채워진 것을 보았습니다. 따라서 결과는 다음과 같습니다.

(큰 버전 보기)

흑백 셰이더

계속해서 새로운 셰이더인 흑백 셰이더를 사용해 보겠습니다. 이 셰이더의 목표는 이전 셰이더를 사용하되 흑백 전용 렌더링 모드를 사용하는 것입니다.

그렇게 하려면 동일한 정점 셰이더를 유지할 수 있습니다. 픽셀 셰이더가 약간 수정됩니다.

첫 번째 옵션은 녹색과 같은 하나의 구성 요소만 사용하는 것입니다.

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0); }

보시다시피 .rgb (이 작업을 swizzle이라고 함)를 사용하는 대신 .ggg 를 사용했습니다.

그러나 정말로 정확한 흑백 효과를 원한다면 휘도(모든 구성 요소를 고려함)를 계산하는 것이 더 좋습니다.

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { float luminance = dot(texture2D(textureSampler, vUV).rgb, vec3(0.3, 0.59, 0.11)); gl_FragColor = vec4(luminance, luminance, luminance, 1.0); }

dot 연산(또는 dot )은 다음과 같이 계산됩니다. result = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z .

따라서 우리의 경우 luminance = r * 0.3 + g * 0.59 + b * 0.11 입니다. (이 값은 사람의 눈이 녹색에 더 민감하다는 사실을 기반으로 합니다.)

멋지지 않나요?

(큰 버전 보기)

셀 셰이딩 셰이더

좀 더 복잡한 셰이더인 셀 셰이딩 셰이더로 이동해 보겠습니다.

이것은 정점의 법선과 정점의 위치를 ​​픽셀 셰이더에 가져와야 합니다. 따라서 정점 셰이더는 다음과 같이 보일 것입니다.

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 world; uniform mat4 worldViewProjection; // Varying varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vPositionW = vec3(world * vec4(position, 1.0)); vNormalW = normalize(vec3(world * vec4(normal, 0.0))); vUV = uv; }

위치와 법선이 변환 없이 저장되기 때문에 세계 행렬도 사용하고 객체의 회전을 고려하기 위해 세계 행렬을 적용해야 합니다.

픽셀 셰이더는 다음과 같습니다.

 precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { float ToonThresholds[4]; ToonThresholds[0] = 0.95; ToonThresholds[1] = 0.5; ToonThresholds[2] = 0.2; ToonThresholds[3] = 0.03; float ToonBrightnessLevels[5]; ToonBrightnessLevels[0] = 1.0; ToonBrightnessLevels[1] = 0.8; ToonBrightnessLevels[2] = 0.6; ToonBrightnessLevels[3] = 0.35; ToonBrightnessLevels[4] = 0.2; vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); vec3 color = texture2D(textureSampler, vUV).rgb; if (ndl > ToonThresholds[0]) { color *= ToonBrightnessLevels[0]; } else if (ndl > ToonThresholds[1]) { color *= ToonBrightnessLevels[1]; } else if (ndl > ToonThresholds[2]) { color *= ToonBrightnessLevels[2]; } else if (ndl > ToonThresholds[3]) { color *= ToonBrightnessLevels[3]; } else { color *= ToonBrightnessLevels[4]; } gl_FragColor = vec4(color, 1.); }

이 셰이더의 목표는 빛을 시뮬레이션하는 것이며 부드러운 음영을 계산하는 대신 특정 밝기 임계값에 따라 빛을 적용합니다. 예를 들어, 빛의 강도가 1(최대)에서 0.95 사이인 경우(텍스처에서 가져온) 개체의 색상이 직접 적용됩니다. 강도가 0.95와 0.5 사이인 경우 색상은 0.8의 비율로 감쇠됩니다. 등등.

이 셰이더에는 주로 4단계가 있습니다.

먼저 임계값과 수준 상수를 선언합니다.

그런 다음 Phong 방정식을 사용하여 조명을 계산합니다(빛이 움직이지 않는 것으로 간주).

 vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW));

픽셀당 빛의 강도는 법선 방향과 빛 방향 사이의 각도에 따라 다릅니다.

그런 다음 픽셀의 텍스처 색상을 얻습니다.

마지막으로 임계값을 확인하고 레벨을 색상에 적용합니다.

결과는 만화 개체처럼 보입니다.

(큰 버전 보기)

퐁 셰이더

이전 쉐이더에서 Phong 방정식의 일부를 사용했습니다. 이제 완전히 사용합시다.

정점 셰이더는 모든 것이 픽셀 셰이더에서 수행되기 때문에 여기에서 분명히 간단합니다.

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vUV = uv; vPosition = position; vNormal = normal; }

방정식에 따르면 빛의 방향과 정점의 법선을 사용하여 "확산" 및 "반사" 부분을 계산해야 합니다.

 precision highp float; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; // Uniforms uniform mat4 world; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 vLightPosition = vec3(0, 20, 10); // World values vec3 vPositionW = vec3(world * vec4(vPosition, 1.0)); vec3 vNormalW = normalize(vec3(world * vec4(vNormal, 0.0))); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); vec3 color = texture2D(textureSampler, vUV).rgb; // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); // Specular vec3 angleW = normalize(viewDirectionW + lightVectorW); float specComp = max(0., dot(vNormalW, angleW)); specComp = pow(specComp, max(1., 64.)) * 2.; gl_FragColor = vec4(color * ndl + vec3(specComp), 1.); }

우리는 이미 이전 셰이더에서 확산 부분을 사용했으므로 여기에 반사 부분을 추가하기만 하면 됩니다. Wikipedia에서 Phong 음영 처리에 대한 자세한 정보를 찾을 수 있습니다.

우리 구체의 결과:

(큰 버전 보기)

셰이더 폐기

폐기 셰이더에 대해 새로운 개념인 discard 키워드를 소개하고 싶습니다.

이 셰이더는 빨간색이 아닌 모든 픽셀을 버리고 파낸 개체의 환상을 만듭니다.

정점 셰이더는 기본 셰이더에서 사용하는 것과 동일합니다.

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }

측면의 픽셀 셰이더는 색상을 테스트하고 예를 들어 녹색 구성 요소가 너무 높을 때 폐기를 사용해야 합니다.

 precision highp float; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { vec3 color = texture2D(textureSampler, vUV).rgb; if (color.g > 0.5) { discard; } gl_FragColor = vec4(color, 1.); }

결과는 약간 재미있습니다.

(큰 버전 보기)

웨이브 셰이더

우리는 픽셀 셰이더로 많은 작업을 해왔지만 정점 셰이더로 많은 것을 할 수 있다는 것도 알려드리고 싶습니다.

웨이브 셰이더의 경우 Phong 픽셀 셰이더를 재사용합니다.

정점 셰이더는 균일한 명명된 time 을 사용하여 일부 애니메이션 값을 가져옵니다. 이 유니폼을 사용하여 셰이더는 정점 위치와 함께 웨이브를 생성합니다.

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; uniform float time; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec3 v = position; vx += sin(2.0 * position.y + (time)) * 0.5; gl_Position = worldViewProjection * vec4(v, 1.0); vPosition = position; vNormal = normal; vUV = uv; }

position.y 에 sinus가 적용되고 결과는 다음과 같습니다.

(큰 버전 보기)

구형 환경 매핑

이것은 "구면 반사/환경 매핑 셰이더 만들기" 기사에서 크게 영감을 받았습니다. 그 훌륭한 기사를 읽고 관련 셰이더를 사용하도록 할게요.

(큰 버전 보기)

프레넬 셰이더

제가 가장 좋아하는 프레넬 셰이더로 이 기사를 마무리하고 싶습니다.

이 셰이더는 뷰 방향과 정점의 법선 사이의 각도에 따라 다른 강도를 적용하는 데 사용됩니다.

정점 셰이더는 셀 셰이딩 셰이더에서 사용하는 것과 동일하며 픽셀 셰이더에서 프레넬 항을 쉽게 계산할 수 있습니다(보기 방향을 평가하는 데 사용할 수 있는 법선과 카메라 위치가 있기 때문).

 precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 color = vec3(1., 1., 1.); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Fresnel float fresnelTerm = dot(viewDirectionW, vNormalW); fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.); gl_FragColor = vec4(color * fresnelTerm, 1.); } 
(큰 버전 보기)

당신의 셰이더?

이제 자신만의 셰이더를 만들 준비가 되었습니다. 당신의 실험을 공유하기 위해 Babylon.js 포럼에 자유롭게 게시하세요!

더 나아가고 싶다면 다음과 같은 유용한 링크가 있습니다.

  • Babylon.js, 공식 웹사이트
  • Babylon.js, GitHub 저장소
  • Babylon.js 포럼, HTML5 게임 개발자
  • 나만의 셰이더 만들기(CYOS), Babylon.js
  • OpenGL 셰이딩 언어" Wikipedia
  • OpenGL 셰이딩 언어, 문서