Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Voxel and Vertex Terrain generation

in Code Sharing Posts: 1,795

This was way harder than I expected it to be!

I this project turns @dave1707’s voxel terrain into a Craft model using the same points.

It also uses voxel volumes to generate the blocks. Volumes render way faster than the direct voxels used in the original, and because of that the project can do neat things like spinning parts of the terrain around separately.

To make the Craft model a little more interesting, and make the contours more visible in the absence of contour shadows, I applied some random color variation and random and positional variation to each vertex, which results in the bumpy and blotchy terrain you can see in the video.

With the default project settings you can also see both the voxels and the Craft model rendered right on top of each other, which makes it look kind of like a Minecraft world with little weeds and dirt clumps and patches of snow pilling up around the blocks. (I found that a surprisingly pleasing effect, actually.)

Give it a whirl!



  • Posts: 2,689

    @UberGoober - superb demo, now I know how to transfer some of my meshes to Craft. Thanks.

    Will take some time but will post code if I manage to transfer.

  • Posts: 1,795
    @Bri_G If you want to convert meshes to Craft, I think there are better projects to look at, including the code @binaryblues wrote to do that exact thing in the ProcTree project.

    That specific code, however, might be a little hard to extract from the JavaScript-related code.

    Binaryblues, any chance you want to take that code and convert it into a standalone mesh-conversion project—it doesn’t seem like it should be too hard, given that you’ve done the hard work already?
  • Posts: 1,350

    You're doing some nifty work, @UberGoober , keep it up!

  • Posts: 1,795

    @RonJeffries thanks Ron!

  • Posts: 297

    @UberGoober There is nothing particularly noteworthy about the conversion program. In fact, there is a one-to-one correspondence between the mesh data generated by JS in that program and the data from the craft model. Because the js has a faces property, it corresponds to the craft model's indices property. All the conversion takes place in the 2 functions:

    function convert(verts, normals, UV)
        local v,n,uv = {},{},{}
        for i=1,#verts do
            local x,y,z =verts[i][1], verts[i][2], verts[i][3]
            local n1,n2,n3 = normals[i][1], normals[i][2], normals[i][3]
            local u1,v1 = UV[i][1], UV[i][2]      
            table.insert(v, vec3(x,y,z))
            table.insert(n, vec3(n1,n2,n3))
            table.insert(uv, vec2(u1,v1))
        return v,n,uv
    function convertFaces(faces)
        local f = {}
        -- 若 face 数据来自js,则需要 +1,保证索引从1开始,
        -- 因为 js 中 faces 使用形式:faces[0],faces[1],...,faces[#faces-1]
        -- 对应于 lua 中从1开始的索引
        if useJSSavedData then k =1 else k=0 end
        for i=1,#faces do
            local v1,v2,v3 = faces[i][1]+k, faces[i][2]+k, faces[i][3]+k
            table.insert(f, v3)
            table.insert(f, v2)
            table.insert(f, v1)
        return f
  • Posts: 1,795

    …ah, but GLSL meshes don’t automatically have a faces property, is that right?

  • Posts: 297

    I also wrote a function that converts the craft model to mesh data, which you can use if you want

    LoadObject = class()
    function LoadObject:init(objPath,id)
        self.e = scene:entity()
        -- self.e.position = vec3(0,0,0)
        local model = craft.model(objPath)
        -- self.e.material = craft.material(asset.builtin.Materials.Standard)
        -- = readImage("Dropbox:face1") = true
    = id
        self.m = self:model2mesh(self.e.model,
        self.m.texture = readImage(asset.builtin.Blocks.Ice)
        ---[[ 使用OpenGLES3.0 教程中9_1 的 shader
        self.m.shader = shader(s.v3,s.f3)
        -- m.shader = shader(s.v2,s.f2)
        self.m.shader.modelMatrix = matrix()
        self.m.shader.viewMatrix = matrix()
        self.m.shader.projectionMatrix = matrix()    
        -- self.m.shader.modelViewProjection = matrix()    
        -- m.shader.modelMatrix=m.shader.modelMatrix:rotate(50,     0,  1,  0)
        self.m.shader.uCamera = vec3(0,5,1)
        self.m.shader.uLightLocation = vec3(100,103,103)
        self.m.shader.sTexture = self.m.texture    
    function LoadObject:model2mesh(model,id)
        local m = mesh()
        local indices = model.indices    
        local vb = m:buffer("position")
        local tb = m:buffer("texCoord")
        local nb = m:buffer("normal")
        local cb = m:buffer("color")
        local vt,tt,nt,ct = {{}},{{}},{{}},{{}}
        -- 根据部件 id 读取对应模型数据文件    
        local str = readText(asset.."Model"".txt")
        -- 若首次执行数据文件为空串,则写入数据文件
        if str == "" then       
            for k=1,#indices do
                vb[k] = model.positions[model.indices[k]]
                tb[k] = model.uvs[model.indices[k]]
                nb[k] = model.normals[model.indices[k]]
                cb[k] = model.colors[model.indices[k]]
                -- 把vec3,vec2 用户数据类型元素分解转换为普通表元素
                vt[k] = {vb[k].x, vb[k].y, vb[k].z}
                tt[k] = {tb[k].x, tb[k].y}
                nt[k] = {nb[k].x, nb[k].y, nb[k].z}
                ct[k] = {cb[k].x, cb[k].y, cb[k].z}
            local modelString = json.encode({vt,tt,nt,ct})
            saveText(asset.."Model"".txt", modelString)
            local t = json.decode(str)   
            vt,tt,nt,ct = t[1], t[2], t[3], t[4]              
            -- print("t[3][2], t[1][1][1]", #t[3][2], t[1][1][1])
            -- 重组坐标:顶点,纹理,法线,颜色
            for k = 1,#vt do
                vb[k] = vec3(vt[k][1], vt[k][2], vt[k][3])
                tb[k] = vec2(tt[k][1], tt[k][2])
                nb[k] = vec3(nt[k][1], nt[k][2], nt[k][3])
                cb[k] = vec3(ct[k][1], ct[k][2], ct[k][3])
        print("indices: ", #indices, type(id), id)
        return m
    function LoadObject:update(dt)
    function LoadObject:drawSelf()    
        -- 绘制 mesh 模型
    s = {
    v3 =[[#version 300 es
    uniform mat4 modelViewProjection; //总变换矩阵
    uniform mat4 modelMatrix; //变换矩阵
    uniform mat4 viewMatrix; //变换矩阵
    uniform mat4 projectionMatrix; //变换矩阵
    uniform vec3 uLightLocation;    //光源位置
    uniform vec3 uCamera;   //摄像机位置
    in vec3 position;  //顶点位置
    in vec3 normal;    //顶点法向量
    in vec2 texCoord;    //顶点纹理坐标
    out vec4 ambient;
    out vec4 diffuse;
    out vec4 specular;
    out vec2 vTextureCoord; 
    void pointLight(                    //定位光光照计算的方法
      in vec3 iNormal,              //法向量
      inout vec4 ambient,           //环境光最终强度
      inout vec4 diffuse,               //散射光最终强度
      inout vec4 specular,          //镜面光最终强度
      in vec3 lightLocation,            //光源位置
      in vec4 lightAmbient,         //环境光强度
      in vec4 lightDiffuse,         //散射光强度
      in vec4 lightSpecular         //镜面光强度
      ambient=lightAmbient;         //直接得出环境光的最终强度  
      vec3 normalTarget=position+iNormal;   //计算变换后的法向量
      vec3 newNormal=(modelMatrix*vec4(normalTarget,1)).xyz-(modelMatrix*vec4(position,1)).xyz;
      newNormal=normalize(newNormal);   //对法向量规格化
      vec3 eye= normalize(uCamera-(modelMatrix*vec4(position,1)).xyz);  
      vec3 vp= normalize(lightLocation-(modelMatrix*vec4(position,1)).xyz);  
      vec3 halfVector=normalize(vp+eye);    //求视线与光线的半向量    
      float shininess=30.0;         //粗糙度,越小越光滑
      float nDotViewPosition=max(0.0,dot(newNormal,vp));    //求法向量与vp的点积与0的最大值
      diffuse = lightDiffuse*nDotViewPosition;              //计算散射光的最终强度
      float nDotViewHalfVector=dot(newNormal,halfVector);   //法线与半向量的点积 
      float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess));     //镜面反射光强度因子
      specular=lightSpecular*powerFactor;               //计算镜面光的最终强度
    void main()     
    // mat4 modelViewProject = projectionMatrix*(viewMatrix* modelMatrix);
        gl_Position = modelViewProjection * vec4(position,1); //根据总变换矩阵计算此次绘制此顶点位置  
       // 存放环境光、散射光、镜面反射光的临时变量      
       vec4 ambientTemp, diffuseTemp, specularTemp;   
       vTextureCoord = texCoord;//将接收的纹理坐标传递给片元着色器
    f3 = [[#version 300 es 
    precision mediump float;
    uniform sampler2D sTexture;//纹理内容数据
    in vec4 ambient;
    in vec4 diffuse;
    in vec4 specular;
    in vec2 vTextureCoord;
    out vec4 fragColor;
    void main()                         
       vec4 finalColor=texture(sTexture, vTextureCoord);    
       fragColor = finalColor*ambient+finalColor*specular+finalColor*diffuse;
  • Posts: 1,795

    Project now available on WebRepo.

  • edited April 14 Posts: 297

    In fact, the complete OpenGL ES has a vertex index array, but Codea's mesh is streamlined to use the order of the vertex array as a vertex index instead of using a separate vertex index.
    So, if we use Codea's mesh to construct the craft model, we have to manually construct the corresponding vertex index (generally, a set of three vertices) in real time according to the permutation order of each vertex, the specific code depends on the vertex construction algorithm, it is difficult to deal with this part of the independent abstract, because the order of its input vertices is not fixed, there is no regularity.

Sign In or Register to comment.