Computer Graphics/ShaderPixel

ShaderPixel - 5. 색 구슬 구현

surkim 2024. 11. 17. 11:23

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을 가져와서 배경만 저 구슬에 비치면 더 구슬같아 보일 거 같다.