A translucent object (using volumetric ray-marching) with non-volumetric diffuse and specular lighting on its surface (like colored glass marble). Think to implement a mode to display one of the specular/diffuse term of the lighting to verify that your lighting works.
위 과제에서 요구하는 반투명 물체와 비체적 디퓨즈 및 스페큘러 조명이 적용된 구슬을 구현했다. 구슬은 레이마칭 기법을 사용해 구현했고, 색상 계산 및 조명 효과를 추가해 완성했다.
렌더 함수
렌더 함수는 한 프레임마다 호출되어 구슬을 그린다. 구슬이 렌더링될 공간은 구체 오브젝트로 할당했으며, 구의 반지름은 1로 설정하고 그보다 약간 큰 영역에서 렌더링했다.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
m_beadProgram->Use();
model = glm::mat4(1.0f);
model = glm::translate(model, m_beadPos);
model = glm::scale(model, glm::vec3(2.1f));
m_beadProgram->SetUniform("uView", view);
m_beadProgram->SetUniform("uProjection", projection);
m_beadProgram->SetUniform("uModel", model);
m_beadProgram->SetUniform("uTransform", projection * view * model);
m_beadProgram->SetUniform("uCenter", m_beadPos);
m_beadProgram->SetUniform("uViewPos", m_cameraPos);
m_beadProgram->SetUniform("uResolution", glm::vec2(m_width, m_height));
m_beadProgram->SetUniform("uLightPos", m_lightPos);
m_sphere->Draw(m_beadProgram.get());
glDisable(GL_BLEND);
레이마칭 구현
1. 레이 방향 계산
calculateRayDirection 함수를 사용해 화면의 각 픽셀에서 카메라 방향으로 레이를 계산했다.
2. 구와의 충돌 감지
레이를 카메라 위치에서 시작해 구의 거리 함수를 사용하여 충돌 여부를 확인했다. 레이가 구에 닿기 전까지는 거리에 따라 전진했다.
vec3 rayDir = calculateRayDirection(gl_FragCoord.xy);
vec3 rayPos = uViewPos;
bool hit = false;
for (int i = 0; i < 100; i++) {
float dist = sdSphere(rayPos - uCenter, 1.0f);
if (dist < 0.01) {
hit = true;
break;
}
rayPos += rayDir * dist;
}
3. 구 내부의 깊이 계산
구에 닿은 후 표면으로부터 내부를 따라 전진하며 깊이를 계산했다. 이 과정에서 구 내부에 위치한 토러스 모양의 오브젝트도 감지했다.
float vol = 0.0;
float dt = 0.01;
bool torusHit = false;
for (int i = 0; i < 400; i++) {
float dist = sdSphere(rayPos - uCenter, 1.0f);
if (dist > 0.1) {
break ;
}
for (int j = 0; j < 16; j++) {
dist = sdCappedTorus(rayPos - uCenter, vec2(0.8, 0.4), 0.8, 0.05, 22.5 * j);
if (dist < 0.01) {
torusHit = true;
break ;
}
}
if (torusHit)
break ;
rayPos += rayDir * dt;
vol += dt;
}
vol /= 2;
vol = smoothstep(0.0, 1.0, vol);
색상 계산
1. 법선 벡터 계산
구의 표면에서 법선 벡터를 계산해 빛과 깊이 효과를 구현했다.
vec3 normal = normalize(hitPos - uCenter);
2. 디퓨즈와 스페큘러
디퓨즈와 스페큘러 색상은 광원 방향과 법선 벡터, 뷰 방향 등을 활용해 계산했다. 프레넬 효과를 추가해 구슬 가장자리가 빛나는 효과를 주었다.
float fresnel = pow(1.0 - abs(dot(viewDir, normal)), 2.0);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * lightColor * objectColor;
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
vec3 specular = vec3(1.0) * spec;
vec3 lightColorSum = mix(diffuse + specular, edgeColor, fresnel);
3. 깊이감 표현
구 내부에서의 깊이를 활용해 색을 점점 어두운 톤으로 전환하며 깊이감을 표현했다. 토러스가 감지되면 해당 영역에 별도의 색상을 섞어주었다.
vec3 depthColor = mix(lColor, dColor, vol);
if (torusHit) {
depthColor = mix(torusColor, depthColor, vol);
vol = 1;
}
vec4 finalColor = mix(vec4(lightColorSum, 1.0), vec4(depthColor, 1.0), 0.7);
finalColor.a = clamp(vol, 0.6, 1.0);
fragColor = finalColor;
결과

약간 더 보완할 부분은 남아 보이지만 시간이 없기 때문에 일단 여기까지 하고 다른 오브젝트 구현부터 해야겠다.
개인적으로 CubeMap을 가져와서 배경만 저 구슬에 비치면 더 구슬같아 보일 거 같다.
'Computer Graphics > ShaderPixel' 카테고리의 다른 글
| ShaderPixel - 7. 구름 안 장애물 추가 (0) | 2024.11.18 |
|---|---|
| ShaderPixel - 6. 구름 구현 (Volumetric Cloud) (0) | 2024.11.18 |
| ShaderPixel - 4. 버텍스 셰이더에서 계산한 노말 값과 월드 좌표는 실제로 프래그먼트에 그려지는 픽셀의 월드 좌표와는 다르다 (2) | 2024.11.15 |
| ShaderPixel - 3. 렌더링 전략 고민.. (1) | 2024.11.13 |
| ShaderPixel - 2. 바닥 구성 (Normal map) (0) | 2024.11.11 |