CMH Studio

Déjà Visité

Created by 吳宛恬


Concept


Déjà Visité is an immersive walkthrough work built upon 3D Gaussian Splatting technology. The viewer is invited into a garden — a garden that feels visited, yet has never existed.
The space of the work emerges from a layered translation between actual places photos, AI-generated images, and a generative 3D world model — a place at once familiar, yet never truly arrived at. Three viewing positions along the path invite the viewer to pause and gaze. In each stillness, a breeze seems to drift through the particles of light and shadow, evoking a sense of green as it exists in the digital age.

Figure 1. Photos translate into AI-generated images

Videos

GLSL Code

// --- Restored: Multi-Color & Shrink Non-Green ---
uniform sampler2D sPositions;
uniform sampler2D sRotation;
uniform sampler2D sScale;
uniform sampler2D sColors;
uniform sampler2D sLookup;
uniform vec3 uFocal;
uniform vec3 uT;
uniform vec3 uR;
uniform float uS;
uniform ivec2 uPointResolution;
uniform float uScale;
uniform float uAlphaThreshold;
uniform float u_time;
uniform sampler2D sHDR;

#ifdef TD_VERTEX_SHADER
out Vertex
#else
in Vertex
#endif
{
    vec3 position;
    vec4 color;
    vec2 uv;
    vec3 conic;
    vec3 worldSpaceNorm;
    int cameraIndex;
} Vert;

#ifdef TD_VERTEX_SHADER
mat3 RotZYX(vec3 a) {
    vec3 c = cos(a), s = sin(a);
    return mat3(
        c.x*c.y, c.x*s.y*s.z - s.x*c.z, s.x*s.z + c.x*s.y*c.z,
        s.x*c.y, c.x*c.z + s.x*s.y*s.z, s.x*s.y*c.z - c.x*s.z,
        -s.y, s.z*c.y, c.y*c.z
    );
}

mat3 quatToMat3(vec4 q) {
    float qx = q.y, qy = q.z, qz = q.w, qw = q.x;
    float qxx = qx*qx, qyy = qy*qy, qzz = qz*qz, qxz = qx*qz, qxy = qx*qy, qyw = qy*qw, qzw = qz*qw, qyz = qy*qz, qxw = qx*qw;
    return mat3(
        vec3(1.0 - 2.0 * (qyy + qzz), 2.0 * (qxy - qzw), 2.0 * (qxz + qyw)),
        vec3(2.0 * (qxy + qzw), 1.0 - 2.0 * (qxx + qzz), 2.0 * (qyz - qxw)),
        vec3(2.0 * (qxz - qyw), 2.0 * (qyz + qxw), 1.0 - 2.0 * (qxx + qyy))
    );
}

mat3 RotScale(vec4 rot, vec3 scale, float globalScale) {
    mat3 ms = mat3(
        uS*globalScale*exp(scale.x), 0, 0,
        0, uS*globalScale*exp(scale.y), 0,
        0, 0, uS*globalScale*exp(scale.z)
    );
    return ms*quatToMat3(rot)*RotZYX(radians(uR.zyx));
}

vec3 Covariance(vec3 p, int cameraIndex, mat3 sigma) {
    mat4 camMatrix = uTDMats[cameraIndex].cam;
    vec2 limit = 1.3 * uFocal.xy;
    vec4 camPos = camMatrix*vec4(p,1.);
    camPos.xy = clamp(camPos.xy/camPos.z, -limit, limit)*camPos.z;
    float focal = uFocal.z;
    mat3 J = mat3(
        focal/camPos.z, 0., -(focal*camPos.x)/(camPos.z*camPos.z),
        0., focal/camPos.z, -(focal*camPos.y)/(camPos.z*camPos.z),
        0, 0, 0
    );
    mat3 T = mat3(uTDMats[cameraIndex].camInverse)*J;
    mat3 cov = transpose(T)*transpose(sigma)*T;
    return vec3(cov[0][0] + .3, cov[0][1], cov[1][1] + .3);
}

vec3 MinAxis(vec3 s) {
    return (s.x <= s.y && s.x <= s.z) ? vec3(1.0, 0.0, 0.0) :
           (s.y <= s.z) ? vec3(0.0, 1.0, 0.0) :
           vec3(0.0, 0.0, 1.0);
}

vec3 EstimateWorldNormal(vec4 rot, vec3 scale, vec3 worldPos, int cameraIndex) {
    vec3 s = exp(scale);
    mat3 r = quatToMat3(rot) * RotZYX(radians(uR.zyx));
    vec3 normal = normalize(r * MinAxis(s));
    vec3 camPos = uTDMats[cameraIndex].camInverse[3].xyz;
    vec3 viewDir = normalize(camPos - worldPos);
    if (dot(normal, viewDir) < 0.0) normal = -normal;
    return normal;
}

void main() {
    int instanceIndex = TDInstanceID();
    int vertexIndex = int(gl_VertexID/4);
    int index = instanceIndex * 1024 + vertexIndex;
    int cameraIndex = TDCameraIndex();
    Vert.cameraIndex = cameraIndex;
    
    ivec2 inRes = uPointResolution;
    ivec2 xy = ivec2(index % inRes.x, index / inRes.x);
    vec2 lookupData = texelFetch(sLookup, xy, 0).xy;
    int lookupIndex = int(lookupData.y);
    xy = ivec2(lookupIndex % inRes.x, lookupIndex / inRes.x);
    
    vec3 pos = P; //[-1,-1,0][1,-1,0][1,1,0][-1,1,0]
    vec2 uv = P.xy; //[-1,-1][1,-1][1,1][-1,1]
    vec3 conic = vec3(0.);
    vec2 ndc = vec2(0.);
    vec4 color = texelFetch(sColors, xy, 0);
    vec3 worldSpaceNorm = vec3(0.0, 0.0, 1.0);
    
    mat3 m = mat3(1);
    vec4 posData = vec4(0.), rotData = vec4(0.);
    vec3 scale = vec3(0.);
    
    float whiteFactor = 0.0;
    float outerWhiteFactor = 0.0;
    float centerWhiteFactor = 0.0;
    float inGlassCover = 0.0;

    if (color.a > 0.0 && lookupData.x > 0 && lookupData.x < 1e5) {
        posData = texelFetch(sPositions, xy, 0);
        rotData = normalize(texelFetch(sRotation, xy, 0));
        scale = texelFetch(sScale, xy, 0).xyz;

        // --- 新增:偵測白色妥球 (高亮度、低色差) ---
        float lumOrig = dot(color.rgb, vec3(0.299, 0.587, 0.114));
        float colorDiff = max(max(color.r, color.g), color.b) - min(min(color.r, color.g), color.b);
        whiteFactor = smoothstep(0.2, 0.9, lumOrig) * smoothstep(0.5, 0.05, colorDiff);

        // --- 區分「中間玻璃罩」與「外圍」的白點 ---
        vec3 coverCenter = vec3(1.0, 0.0, 10.0);
        float distToCenter = length(posData.xyz - coverCenter);
        inGlassCover = 1.0 - smoothstep(5.0, 10.0, distToCenter);
        centerWhiteFactor = whiteFactor * inGlassCover;
        outerWhiteFactor = whiteFactor * (1.0 - inGlassCover);

        float randomVal = fract(sin(dot(posData.xyz, vec3(12.9898, 78.233, 37.719))) * 43758.5453);
        outerWhiteFactor *= step(0.6, randomVal);
        centerWhiteFactor *= step(0.2, randomVal);
        whiteFactor = centerWhiteFactor + outerWhiteFactor;

        // --- 讓白色妥球產生如光影般的漂浮位移 ---
        vec3 lightOffset = vec3(
            sin(u_time * 0.8 + posData.y * 15.0) + cos(u_time * 0.35 + posData.z * 22.0),
            cos(u_time * 0.7 + posData.z * 15.0) + sin(u_time * 0.42 - posData.x * 18.0),
            sin(u_time * 0.8 + posData.x * 15.0) + cos(u_time * 0.73 + posData.y * 25.0)
        ) * 0.15 * whiteFactor;
        
        posData.xyz += lightOffset;
        
        float greenFactor = smoothstep(0.1, 0.2, color.g - max(color.r, color.b));
        float brownFactor = smoothstep(0.02, 0.15, color.r - color.g) * smoothstep(0.05, 0.2, color.r - color.b);

        float scaleModifier = 0.05;
        scaleModifier = mix(scaleModifier, 1.5, greenFactor);
        scaleModifier = mix(scaleModifier, 1.0, brownFactor);

        // --- 針對白色發光點給予獨立的大小放大與呼吸閃爍效果 ---
        float pulse = 1.0 + 0.3 * sin(u_time * 1.0 + posData.x * 15.0);
        scaleModifier = mix(scaleModifier, 0.6 * pulse, outerWhiteFactor);
        scaleModifier = mix(scaleModifier, 8.0 * pulse, centerWhiteFactor);
        
        m = RotScale(rotData, scale, uScale * scaleModifier);

        mat3 sigma = transpose(m)*m;
        vec3 cov = Covariance(posData.xyz, cameraIndex, sigma);
        float det = cov.x * cov.z - cov.y * cov.y;
        conic = vec3(cov.z,-cov.y,cov.x)/det;

        // WorldNormal
        if (color.a > 0.0 && lookupData.x > 0 && lookupData.x < 1e5) {
            worldSpaceNorm = EstimateWorldNormal(rotData, scale, worldSpacePos.xyz, cameraIndex);
        }
    }
    
    Vert.position = worldSpacePos.xyz; // WorldPos
    
    vec4 projectionSpace = TDWorldToProj(worldSpacePos);
    projectionSpace /= projectionSpace.w;
    projectionSpace.xy += ndc*pos.xy*1.0; // scale
    gl_Position = projectionSpace;
    
    float luminance = dot(color.rgb, vec3(0.299, 0.587, 0.114));
    float saturation = 0.6;
    color.rgb = mix(vec3(luminance), color.rgb, saturation);
    
    // --- 讓白色光點更亮,產生過曝的光源感 ---
    color.rgb += vec3(outerWhiteFactor * 0.6);
    color.rgb *= (1.0 - centerWhiteFactor * 1.1);
    color.a *= (1.0 - centerWhiteFactor * 1.0);
    
    // color.rgb = mix(color.rgb, vec3(1.0, 0.0, 0.0), inGlassCover);
    // color.a = max(color.a, inGlassCover * 0.8);
    
    Vert.color = color;
    // Vert.color = vec4(color.rgb*0.28209479177387814+0.5, color.a); 
    Vert.uv = uv;
    Vert.conic = conic;
    Vert.worldSpaceNorm = worldSpaceNorm;
}
#endif

#ifdef TD_PIXEL_SHADER
out vec4 fragColor;

void main() {
    vec4 color = Vert.color;
    if (color.a == 0) discard;
    
    vec2 d = Vert.uv;
    vec3 conic = Vert.conic;
    float power = -0.5 * (conic.x * d.x * d.x + conic.z * d.y * d.y) - conic.y * d.x * d.y;
    if (power > 0.0) discard;
    
    // --- 1. 降低基礎透明度,產生空氣輕透感 ---
    float alpha = clamp(color.a * 0.13, 0.0, 1.0) * exp(power);
    if (alpha < max(uAlphaThreshold, 0.01)) discard;
    color.a = alpha;
    
    // --- 2. 增加夢幻感:亮部藍紫色反光 ---
    float lum = dot(color.rgb, vec3(0.299, 0.587, 0.114));
    vec3 dreamyTint = vec3(0.3, 0.2, 1.0); 
    color.rgb += dreamyTint * smoothstep(0.5, 1.0, lum) * 0.8;
    color.rgb = pow(color.rgb, vec3(0.9)) * 1.2;
    
    // --- 4. HDR 環境反射 (Environment Reflection) ---
    // 取得視角方向與法線
    vec3 viewDir = normalize(uTDMats[Vert.cameraIndex].camInverse[3].xyz - Vert.position);
    vec3 normal = normalize(Vert.worldSpaceNorm);
    vec3 refDir = reflect(-viewDir, normal);
    
    float u_hdr = atan(refDir.z, refDir.x) / (2.0 * 3.14159) + 0.5;
    float v_hdr = acos(clamp(refDir.y, -1.0, 1.0)) / 3.14159;
    vec3 envLight = texture(sHDR, vec2(u_hdr, v_hdr)).rgb;
    
    float fresnel = pow(1.0 - max(dot(normal, viewDir), 0.0), 1.0) + 0.4;
    color.rgb += envLight * fresnel * 2.5;
    color.rgb *= color.a; 
    
    vec4 addFog = TDFog(color, Vert.position, Vert.cameraIndex);
    TDAlphaTest(color.a);
    
    fragColor = TDOutputSwizzle(color);
}
#endif