const diffuseMaterial = new THREE.ShaderMaterial({
vertexShader: `
void main() {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(0.5,0.2,0.5,1.0);
}
`
});
먼저 디퓨즈 쉐이더이다. 단순히 오브젝트의 디퓨즈 정보를 내보내는 쉐이더이다.
const normalMaterial = new THREE.ShaderMaterial({
vertexShader: `
varying vec4 pos;
void main() {
pos = modelMatrix * vec4(normal,0.0);
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec4 pos;
void main() {
vec3 nPos = vec3(pos);
nPos = normalize(nPos);
gl_FragColor = vec4((nPos+1.0)/2.0,1.0);
}
`
});
다음으로는 노말 쉐이더이다. 객체의 노말 정보를 내보낸다. 이 때 월드 행렬 정보만 곱해서 넘겨주는데 그 이유는 월드 공간에서 객체가 회전되는 경우 각 표면의 법선 벡터도 같이 회전되어야 하기 때문이다. 방향 벡터 정보이기 때문에 w 값은 0.0으로 지정하여 연산한다.
const geometryMaterial = new THREE.ShaderMaterial({
vertexShader: `
varying vec4 pos;
void main() {
pos = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec4 pos;
void main() {
vec4 nPos = pos/pos.w;
gl_FragColor = vec4((nPos+1.0)/2.0);
}
`
});
다음으로는 지오메트리 쉐이더이다. 각종 공간 좌표 정보를 텍스처에 담는 역할을 한다.
const postMaterial = new THREE.ShaderMaterial({
uniforms:{
inputTexture:{value: inputRenderTarget.texture},
normalGBuffer:{value:gBufferNormalTarget.texture},
geometryGBuffer:{value:gBufferGeometryTarget.texture},
diffuseGBuffer:{value:gBufferDiffuseTarget.texture},
lightPos:{value:new THREE.Vector3(0,0,0)}
},
vertexShader: `
varying vec2 oUV;
void main() {
oUV = uv;
gl_Position = vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D inputTexture;
uniform sampler2D normalGBuffer;
uniform sampler2D geometryGBuffer;
uniform sampler2D diffuseGBuffer;
uniform mat4 projectionMatrix;
uniform mat4 modelMatrix;
uniform vec3 lightPos;
varying vec2 oUV;
void main() {
vec4 inputColor = texture(inputTexture, oUV);
mat4 matrix = inverse(projectionMatrix * viewMatrix);
vec4 geometryColor = texture(geometryGBuffer, oUV);
vec4 geometry = matrix * vec4(vec3((geometryColor * 2.0) - 1.0), 1.0);
geometry = geometry / geometry.w;
vec4 normalColor = texture(normalGBuffer, oUV);
vec4 normal = vec4(vec3((normalColor * 2.0) - 1.0),0.0);
normal = vec4(normalize(vec3(normal)),0.0);
vec4 diffuseColor = texture(diffuseGBuffer, oUV);
vec3 lightToGeometry = normalize(vec3(geometry) - lightPos);
float pointVelocity = dot(-lightToGeometry,vec3(normal));
vec4 finalColor = vec4(1.0) * pointVelocity ;
finalColor.a=1.0;
if(inputColor.a==0.0){
gl_FragColor=finalColor;
}else{
gl_FragColor=clamp(finalColor + inputColor,0.0,1.0) ;
}
}
`
});
마지막으로 각종 normalBuffer, geometryBuffer, diffuseBuffer Map을 읽어들여 입력한 맵과 합성하여 빛 연산후에 렌더링하는 쉐이더 코드이다. 특이점은 노말 맵을 읽여들여 해당 픽셀의 월드 공간 내에서의 노말 방향 벡터를, 그리고 지오메트리 맵을 읽여들여 Zbuffer 등을 함께 조합하여 원본 월드 좌표를 알아낸다. 이 후는 일반 난반사광 처리 방법과 동일하게 연산을 수행하고, 밝아지는 지점에 대해서 렌더링을 한다. 이 때 인풋 맵에 들어온 이전 연산 결과가 있는 경우 단순히 더해서 내보내고, 없으면 현재 연산된 빛 세기만 내보낸다. 여기서 주의할 점은 이 쉐이더는 빛의 세기만 내보내는 것이다. 디퓨즈 맵과 실제 합성하지는 않는다. 광원이 2개 있는 경우, 이 쉐이더를 2번 거쳐야하는데 그 과정에서 어두운 부분도 두 번 연산하면서 밝아야할 부분도 조금씩 더 어두워지는 문제가 존재한다. 따라서 빛 세기 정도만 내보내고 그 범위가 0,1 내를 유지하도록 구현한다.
const diffuseApplyMaterial = new THREE.ShaderMaterial({
uniforms:{
brightnessTexture:{value: null},
diffuseGBuffer:{value: gBufferDiffuseTarget.texture},
},
vertexShader: `
varying vec2 oUV;
void main() {
oUV = uv;
gl_Position = vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D brightnessTexture;
uniform sampler2D diffuseGBuffer;
varying vec2 oUV;
void main() {
vec4 brightnessColor = texture(brightnessTexture, oUV);
vec4 diffuseColor = texture(diffuseGBuffer, oUV);
gl_FragColor = diffuseColor * brightnessColor;
}
`
});
그리고 마지막에 비로소 세기 맵과 디퓨즈 맵을 혼합하여 세기가 적용된 텍스처를 내보낸다.
파이프라인 과정